Files
91/backend/internal/storageusage/storageusage.go
T
2026-05-12 19:57:48 +08:00

141 lines
3.4 KiB
Go

package storageusage
import (
"errors"
"os"
"path/filepath"
"strings"
)
type VideoAssetRef struct {
ID string
DriveID string
PreviewLocal string
}
type DiskStats struct {
AvailableBytes int64 `json:"availableBytes"`
CapacityBytes int64 `json:"capacityBytes"`
}
type DriveUsage struct {
ThumbnailBytes int64 `json:"thumbnailBytes"`
TeaserBytes int64 `json:"teaserBytes"`
TotalBytes int64 `json:"totalBytes"`
}
type Usage struct {
ThumbnailBytes int64 `json:"thumbnailBytes"`
TeaserBytes int64 `json:"teaserBytes"`
TotalBytes int64 `json:"totalBytes"`
AvailableBytes int64 `json:"availableBytes"`
CapacityBytes int64 `json:"capacityBytes"`
Drives map[string]DriveUsage `json:"drives"`
}
func Compute(
localDir string,
refs []VideoAssetRef,
driveIDs []string,
diskStats func(string) (DiskStats, error),
) (Usage, error) {
localDir = strings.TrimSpace(localDir)
if localDir == "" {
return Usage{}, errors.New("local preview dir is not configured")
}
if diskStats == nil {
diskStats = func(string) (DiskStats, error) { return DiskStats{}, nil }
}
stats, err := diskStats(localDir)
if err != nil {
return Usage{}, err
}
out := Usage{
AvailableBytes: stats.AvailableBytes,
CapacityBytes: stats.CapacityBytes,
Drives: make(map[string]DriveUsage, len(driveIDs)),
}
allowed := make(map[string]bool, len(driveIDs))
for _, id := range driveIDs {
if id == "" {
continue
}
allowed[id] = true
out.Drives[id] = DriveUsage{}
}
seen := make(map[string]bool)
for _, ref := range refs {
if ref.ID == "" || ref.DriveID == "" || !allowed[ref.DriveID] {
continue
}
driveUsage := out.Drives[ref.DriveID]
thumbPath := filepath.Join(localDir, "thumbs", ref.ID+".jpg")
if size, exists, err := regularFileSize(thumbPath); err != nil {
return Usage{}, err
} else if exists {
key := ref.DriveID + "\x00thumb\x00" + thumbPath
if !seen[key] {
driveUsage.ThumbnailBytes += size
seen[key] = true
}
}
if previewPath, ok := pathWithin(localDir, ref.PreviewLocal); ok {
if size, exists, err := regularFileSize(previewPath); err != nil {
return Usage{}, err
} else if exists {
key := ref.DriveID + "\x00teaser\x00" + previewPath
if !seen[key] {
driveUsage.TeaserBytes += size
seen[key] = true
}
}
}
driveUsage.TotalBytes = driveUsage.ThumbnailBytes + driveUsage.TeaserBytes
out.Drives[ref.DriveID] = driveUsage
}
for _, driveUsage := range out.Drives {
out.ThumbnailBytes += driveUsage.ThumbnailBytes
out.TeaserBytes += driveUsage.TeaserBytes
}
out.TotalBytes = out.ThumbnailBytes + out.TeaserBytes
return out, nil
}
func regularFileSize(path string) (int64, bool, error) {
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return 0, false, nil
}
return 0, false, err
}
if !info.Mode().IsRegular() {
return 0, false, nil
}
return info.Size(), true, nil
}
func pathWithin(root, path string) (string, bool) {
if strings.TrimSpace(path) == "" {
return "", false
}
rootAbs, err := filepath.Abs(root)
if err != nil {
return "", false
}
pathAbs, err := filepath.Abs(path)
if err != nil {
return "", false
}
rel, err := filepath.Rel(rootAbs, pathAbs)
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
return "", false
}
return pathAbs, true
}