Files
LitePan/internal/app/wire_mediaorganize.go
2026-07-27 19:31:43 +08:00

151 lines
3.5 KiB
Go

package app
import (
"context"
"strings"
"litepan/internal/domain"
"litepan/internal/file"
"litepan/internal/logx"
"litepan/internal/mediaorganize"
"litepan/internal/mediaorganize/executor"
"litepan/internal/mediaorganize/planner"
"litepan/internal/mediaorganize/tmdb"
"litepan/internal/settings"
)
func wireMediaOrganize(st *storeBundle, files *file.Service, logs *logx.Manager, dataDir string) *mediaorganize.Service {
return mediaorganize.NewService(mediaorganize.ServiceOptions{
Repo: st.store.MediaOrganizeTasks,
Files: files,
Settings: st.settings,
DataDir: dataDir,
Log: logs.For(logx.ModuleFileOp),
Planner: plannerAdapter{files: files, settings: st.settings},
Executor: executorAdapter{files: files},
})
}
type plannerAdapter struct {
files *file.Service
settings *settings.Service
}
func (a plannerAdapter) Build(
ctx context.Context,
taskID string,
task *domain.MediaOrganizeTask,
cfg map[string]any,
settings map[string]any,
hooks mediaorganize.PlannerHooks,
) (*mediaorganize.Plan, error) {
accountID := mediaorganize.CfgAccountID(cfg)
if accountID == 0 && task != nil {
accountID = task.AccountID
}
plannerSettings := mediaorganize.EnrichPlannerSettings(a.settings, settings)
apiKey := mediaorganize.PlannerTMDBAPIKey(plannerSettings)
tmdbClient := tmdb.NewClient(tmdb.Options{
APIKey: apiKey,
Language: mediaorganize.PlannerTMDBLanguage(plannerSettings),
ProxyURL: tmdb.BuildProxyURL(mediaorganize.TmdbProxyFromSettings(plannerSettings)),
})
var progressFn planner.ProgressFunc
if hooks.Progress != nil {
progressFn = func(p planner.Progress) {
hooks.Progress(map[string]any{
"stage": p.Stage,
"scanned_dirs": p.ScannedDirs,
"scanned_files": p.ScannedFiles,
"groups": p.Groups,
"actions": p.Actions,
"skipped": p.Skipped,
"current_dir": p.CurrentDir,
"planned_works": p.PlannedWorks,
"max_works": p.MaxWorks,
"quota_reached": p.QuotaReached,
})
}
}
logFn := func(string) {}
if hooks.Log != nil {
logFn = hooks.Log
}
stopFn := func() error { return nil }
if hooks.CheckStop != nil {
stopFn = hooks.CheckStop
}
p := planner.New(
ctx,
a.files,
accountID,
planner.TaskConfigFromMap(cfg),
plannerSettings,
taskID,
tmdbClient,
logFn,
progressFn,
stopFn,
)
return p.Build()
}
type executorAdapter struct {
files *file.Service
}
func (a executorAdapter) Apply(
ctx context.Context,
plan *mediaorganize.Plan,
_ string,
accountID int64,
_ map[string]any,
settings map[string]any,
hooks mediaorganize.ExecutorHooks,
) error {
if plan == nil {
return nil
}
overwrite := boolAny(settings["overwrite_existing"], false)
logFn := func(string) {}
if hooks.Log != nil {
logFn = hooks.Log
}
stopFn := func() error { return nil }
if hooks.CheckStop != nil {
stopFn = func() error {
if err := hooks.CheckStop(); err != nil {
return executor.ErrStopped
}
return nil
}
}
ex := executor.New(ctx, a.files, plan, accountID, overwrite, logFn, stopFn)
_, err := ex.Apply()
if err != nil && strings.Contains(err.Error(), "stopped") {
return mediaorganize.ErrTaskAborted
}
return err
}
func boolAny(v any, def bool) bool {
switch x := v.(type) {
case bool:
return x
case string:
s := strings.TrimSpace(strings.ToLower(x))
if s == "" {
return def
}
return s == "1" || s == "true" || s == "yes" || s == "on"
case float64:
return x != 0
case int:
return x != 0
default:
return def
}
}