186 lines
3.8 KiB
Go
186 lines
3.8 KiB
Go
// backend\generated_gc.go
|
||
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"io/fs"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync/atomic"
|
||
"time"
|
||
)
|
||
|
||
var generatedGCRunning int32
|
||
|
||
type generatedGCStats struct {
|
||
Checked int
|
||
Removed int
|
||
}
|
||
|
||
// Läuft synchron und liefert Zahlen zurück (für /api/settings/cleanup Response).
|
||
func triggerGeneratedGarbageCollectorSync() generatedGCStats {
|
||
// gleiches "nur 1 GC gleichzeitig" Verhalten wie async
|
||
if !atomic.CompareAndSwapInt32(&generatedGCRunning, 0, 1) {
|
||
fmt.Println("🧹 [gc] skip: already running")
|
||
return generatedGCStats{}
|
||
}
|
||
defer atomic.StoreInt32(&generatedGCRunning, 0)
|
||
|
||
stats := runGeneratedGarbageCollector()
|
||
return stats
|
||
}
|
||
|
||
// Läuft 1× nach Serverstart (mit Delay), löscht /generated/* Orphans.
|
||
func startGeneratedGarbageCollector() {
|
||
go func() {
|
||
time.Sleep(3 * time.Second)
|
||
triggerGeneratedGarbageCollectorSync()
|
||
}()
|
||
}
|
||
|
||
// Core-Logik ohne Delay (für manuelle Trigger, z.B. nach Cleanup)
|
||
// Liefert Stats zurück, damit /api/settings/cleanup die Zahlen anzeigen kann.
|
||
func runGeneratedGarbageCollector() generatedGCStats {
|
||
stats := generatedGCStats{}
|
||
|
||
s := getSettings()
|
||
|
||
doneAbs, err := resolvePathRelativeToApp(s.DoneDir)
|
||
if err != nil {
|
||
fmt.Println("🧹 [gc] resolve doneDir failed:", err)
|
||
return stats
|
||
}
|
||
doneAbs = strings.TrimSpace(doneAbs)
|
||
if doneAbs == "" {
|
||
return stats
|
||
}
|
||
|
||
// 1) Live-IDs sammeln: alle mp4/ts unter /done (rekursiv), .trash ignorieren
|
||
live := make(map[string]struct{}, 4096)
|
||
|
||
_ = filepath.WalkDir(doneAbs, func(p string, d fs.DirEntry, err error) error {
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
|
||
name := d.Name()
|
||
|
||
if d.IsDir() {
|
||
if strings.EqualFold(name, ".trash") {
|
||
return fs.SkipDir
|
||
}
|
||
return nil
|
||
}
|
||
|
||
ext := strings.ToLower(filepath.Ext(name))
|
||
if ext != ".mp4" && ext != ".ts" {
|
||
return nil
|
||
}
|
||
|
||
info, err := d.Info()
|
||
if err != nil || info.IsDir() || info.Size() <= 0 {
|
||
return nil
|
||
}
|
||
|
||
base := strings.TrimSuffix(name, ext)
|
||
id, err := sanitizeID(stripHotPrefix(base))
|
||
if err != nil || id == "" {
|
||
return nil
|
||
}
|
||
|
||
live[id] = struct{}{}
|
||
return nil
|
||
})
|
||
|
||
// 2) /generated/meta/<id> prüfen
|
||
metaRoot, err := generatedMetaRoot()
|
||
if err == nil {
|
||
metaRoot = strings.TrimSpace(metaRoot)
|
||
}
|
||
if err != nil || metaRoot == "" {
|
||
return stats
|
||
}
|
||
|
||
removedMeta := 0
|
||
checkedMeta := 0
|
||
|
||
if entries, err := os.ReadDir(metaRoot); err == nil {
|
||
for _, e := range entries {
|
||
if !e.IsDir() {
|
||
continue
|
||
}
|
||
id := strings.TrimSpace(e.Name())
|
||
if id == "" || strings.HasPrefix(id, ".") {
|
||
continue
|
||
}
|
||
|
||
checkedMeta++
|
||
if _, ok := live[id]; ok {
|
||
continue
|
||
}
|
||
|
||
removeGeneratedForID(id)
|
||
removedMeta++
|
||
}
|
||
}
|
||
|
||
fmt.Printf("🧹 [gc] generated/meta checked=%d removed_orphans=%d\n", checkedMeta, removedMeta)
|
||
stats.Checked += checkedMeta
|
||
stats.Removed += removedMeta
|
||
|
||
// 3) Optional: legacy /generated/<id>
|
||
genRoot, err := generatedRoot()
|
||
if err == nil {
|
||
genRoot = strings.TrimSpace(genRoot)
|
||
}
|
||
if err != nil || genRoot == "" {
|
||
return stats
|
||
}
|
||
|
||
reserved := map[string]struct{}{
|
||
"meta": {},
|
||
"covers": {},
|
||
"cover": {},
|
||
"temp": {},
|
||
"tmp": {},
|
||
".trash": {},
|
||
}
|
||
|
||
removedLegacy := 0
|
||
checkedLegacy := 0
|
||
|
||
if entries, err := os.ReadDir(genRoot); err == nil {
|
||
for _, e := range entries {
|
||
if !e.IsDir() {
|
||
continue
|
||
}
|
||
|
||
name := strings.TrimSpace(e.Name())
|
||
if name == "" || strings.HasPrefix(name, ".") {
|
||
continue
|
||
}
|
||
if _, ok := reserved[strings.ToLower(name)]; ok {
|
||
continue
|
||
}
|
||
|
||
checkedLegacy++
|
||
if _, ok := live[name]; ok {
|
||
continue
|
||
}
|
||
|
||
removeGeneratedForID(name)
|
||
removedLegacy++
|
||
}
|
||
}
|
||
|
||
if checkedLegacy > 0 || removedLegacy > 0 {
|
||
fmt.Printf("🧹 [gc] generated legacy checked=%d removed_orphans=%d\n", checkedLegacy, removedLegacy)
|
||
}
|
||
stats.Checked += checkedLegacy
|
||
stats.Removed += removedLegacy
|
||
|
||
return stats
|
||
}
|