mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 03:26:37 +08:00
b0fe40142a
* Rebrand MMTL to MeBox across codebase and assets Rename the project display name, Go module path, environment variable prefix (MEBOX_*), Docker image references, and UI branding from MMTL/mmtl to MeBox/mebox. Replace logo assets with the new MeBox icon and keep legacy SQLite migration support for existing mmtl.db deployments. Co-authored-by: truewhile <truewhile@users.noreply.github.com> * Fix logo icons: use cube-only crop without truncated text Previous icon generation cropped too much of the source image, including partial MeBox wordmark text that was cut off in square icon containers. Regenerate logo-64/192/512, favicon, and SVG from cube-only region. Co-authored-by: truewhile <truewhile@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: truewhile <truewhile@users.noreply.github.com>
117 lines
3.2 KiB
Go
117 lines
3.2 KiB
Go
// Package service — disk usage breakdown.
|
|
//
|
|
// StorageService aggregates "how much disk does each library use" for
|
|
// the React Storage tab. Numbers are computed from the in-DB
|
|
// media.size_bytes column so we never hit the disk on the hot path.
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/truewhile/MeBox/internal/repository"
|
|
)
|
|
|
|
// StorageService is the read-only aggregator.
|
|
type StorageService struct {
|
|
log *zap.Logger
|
|
repo *repository.Container
|
|
}
|
|
|
|
// NewStorageService is the constructor.
|
|
func NewStorageService(log *zap.Logger, repo *repository.Container) *StorageService {
|
|
return &StorageService{log: log, repo: repo}
|
|
}
|
|
|
|
// Breakdown is what /api/storage returns.
|
|
type Breakdown struct {
|
|
TotalBytes int64 `json:"total_bytes"`
|
|
TotalSeconds int64 `json:"total_seconds"`
|
|
ByLibrary []LibraryUsage `json:"by_library"`
|
|
ByContainer []ContainerStat `json:"by_container"`
|
|
}
|
|
|
|
// LibraryUsage is per-library disk + duration totals.
|
|
type LibraryUsage struct {
|
|
LibraryID string `json:"library_id"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Path string `json:"path"`
|
|
MediaCount int64 `json:"media_count"`
|
|
TotalBytes int64 `json:"total_bytes"`
|
|
TotalSeconds int64 `json:"total_seconds"`
|
|
}
|
|
|
|
// ContainerStat counts media items per container (mp4 / mkv / …).
|
|
type ContainerStat struct {
|
|
Container string `json:"container"`
|
|
Count int64 `json:"count"`
|
|
Bytes int64 `json:"bytes"`
|
|
}
|
|
|
|
// Compute returns the full breakdown.
|
|
func (s *StorageService) Compute(ctx context.Context) (*Breakdown, error) {
|
|
libs, err := s.repo.Library.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
libs = NormalizeCloudLibraryDisplay(libs)
|
|
out := &Breakdown{ByLibrary: make([]LibraryUsage, 0, len(libs))}
|
|
for _, l := range libs {
|
|
var usage LibraryUsage
|
|
usage.LibraryID = l.ID
|
|
usage.Name = l.Name
|
|
usage.Type = l.Type
|
|
usage.Path = l.Path
|
|
row := struct {
|
|
Count int64
|
|
Size int64
|
|
Seconds int64
|
|
}{}
|
|
err := s.repo.DB.WithContext(ctx).
|
|
Table("media").
|
|
Where("library_id = ? AND deleted_at IS NULL", l.ID).
|
|
Select("COUNT(*) as count, COALESCE(SUM(size_bytes),0) as size, COALESCE(SUM(duration_sec),0) as seconds").
|
|
Scan(&row).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
usage.MediaCount = row.Count
|
|
usage.TotalBytes = row.Size
|
|
usage.TotalSeconds = row.Seconds
|
|
out.TotalBytes += row.Size
|
|
out.TotalSeconds += row.Seconds
|
|
out.ByLibrary = append(out.ByLibrary, usage)
|
|
}
|
|
|
|
rows, err := s.containerStats(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out.ByContainer = rows
|
|
return out, nil
|
|
}
|
|
|
|
func (s *StorageService) containerStats(ctx context.Context) ([]ContainerStat, error) {
|
|
rows, err := s.repo.DB.WithContext(ctx).
|
|
Table("media").
|
|
Where("deleted_at IS NULL").
|
|
Select("COALESCE(NULLIF(container,''),'unknown') as container, COUNT(*) as count, COALESCE(SUM(size_bytes),0) as bytes").
|
|
Group("container").
|
|
Rows()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := []ContainerStat{}
|
|
for rows.Next() {
|
|
var c ContainerStat
|
|
if err := rows.Scan(&c.Container, &c.Count, &c.Bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, c)
|
|
}
|
|
return out, nil
|
|
}
|