package useCases import ( "RenewCMS/internal/domain/image" "sort" "time" ) type VariantCacheEntry struct { Name string Enabled bool Files int Bytes int64 } type VariantCacheImage struct { ID uint32 Filename string URL string Files int Bytes int64 } type VariantCacheStats struct { GeneratedFiles int DiskUsed int64 VariantsDefined int VariantsEnabled int VariantsDisabled int RejectedRequests int RejectedWindowDays int Unused image.VariantFileTotals UnusedDays int ByVariant []VariantCacheEntry TopImages []VariantCacheImage } type GetVariantCacheStatsUseCase struct { variants image.VariantRepository files image.VariantFileRepository cache image.VariantCacheRepository images image.Repository usage image.VariantUsageRepository tracker image.VariantUsageTracker now func() time.Time } func NewGetVariantCacheStatsUseCase(variants image.VariantRepository, files image.VariantFileRepository, cache image.VariantCacheRepository, images image.Repository, usage image.VariantUsageRepository, tracker image.VariantUsageTracker, now func() time.Time) *GetVariantCacheStatsUseCase { return &GetVariantCacheStatsUseCase{variants, files, cache, images, usage, tracker, now} } func (u *GetVariantCacheStatsUseCase) Get() (VariantCacheStats, error) { _ = u.tracker.Flush() variants, err := u.variants.List() if err != nil { return VariantCacheStats{}, err } totals, err := u.files.Totals() if err != nil { return VariantCacheStats{}, err } now := u.now() unused, err := u.cache.UnusedTotals(image.UnusedFilter{VariantNames: enabledNames(variants), Cutoff: image.UnusedCutoff(now)}) if err != nil { return VariantCacheStats{}, err } rejected, err := u.usage.RejectedSince(image.RejectionWindowStart(now)) if err != nil { return VariantCacheStats{}, err } top, err := u.topImages() if err != nil { return VariantCacheStats{}, err } stats := VariantCacheStats{ VariantsDefined: len(variants), RejectedRequests: rejected, RejectedWindowDays: image.RejectionWindowDays, Unused: unused, UnusedDays: image.UnusedVariantDays, ByVariant: cacheEntries(variants, totals), TopImages: top, } for _, entry := range stats.ByVariant { stats.GeneratedFiles += entry.Files stats.DiskUsed += entry.Bytes if entry.Enabled { stats.VariantsEnabled++ } } stats.VariantsDisabled = stats.VariantsDefined - stats.VariantsEnabled return stats, nil } func enabledNames(variants []image.Variant) []string { names := []string{} for _, variant := range variants { if variant.Enabled { names = append(names, variant.Name) } } return names } func cacheEntries(variants []image.Variant, totals map[string]image.VariantFileTotals) []VariantCacheEntry { entries := make([]VariantCacheEntry, 0, len(variants)) for _, variant := range variants { own := totals[variant.Name] entries = append(entries, VariantCacheEntry{Name: variant.Name, Enabled: variant.Enabled, Files: own.Files, Bytes: own.Bytes}) } sort.Slice(entries, func(i, j int) bool { if entries[i].Bytes != entries[j].Bytes { return entries[i].Bytes > entries[j].Bytes } return entries[i].Name < entries[j].Name }) return entries } func (u *GetVariantCacheStatsUseCase) topImages() ([]VariantCacheImage, error) { limit := image.TopImagesLimit + 1 for { sources, err := u.cache.TopSources(limit) if err != nil { return nil, err } ranked, err := u.rank(sources) if err != nil { return nil, err } if len(sources) < limit || topIsSettled(ranked, sources) { return ranked[:min(len(ranked), image.TopImagesLimit)], nil } limit *= 2 } } func topIsSettled(ranked []VariantCacheImage, sources []image.SourceTotals) bool { return len(ranked) >= image.TopImagesLimit && ranked[image.TopImagesLimit-1].Bytes > sources[len(sources)-1].Bytes } func (u *GetVariantCacheStatsUseCase) rank(sources []image.SourceTotals) ([]VariantCacheImage, error) { names := make([]string, 0, len(sources)) for _, source := range sources { names = append(names, source.SourceName) } found, err := u.images.ListByStoredNames(names) if err != nil { return nil, err } byName := make(map[string]image.Image, len(found)) for _, img := range found { byName[image.StoredName(img.Path)] = img } ranked := make([]VariantCacheImage, 0, len(sources)) for _, source := range sources { if img, known := byName[source.SourceName]; known { ranked = append(ranked, VariantCacheImage{ID: img.ID, Filename: img.Filename, URL: image.PublicPath(source.SourceName), Files: source.Files, Bytes: source.Bytes}) } } sort.SliceStable(ranked, func(i, j int) bool { if ranked[i].Bytes != ranked[j].Bytes { return ranked[i].Bytes > ranked[j].Bytes } return ranked[i].ID < ranked[j].ID }) return ranked, nil }