mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-07 22:06:38 +08:00
refactor organizer source flow by responsibility
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func skipAlreadyOrganizedSource(req organizeSourceFileRequest, plan organizeSourceFilePlan) {
|
||||
req.Result.Skipped++
|
||||
req.Result.Items = append(req.Result.Items, OrganizePreviewItem{
|
||||
Source: req.Source, Target: plan.Target.Path, Action: "skip", Reason: organizeSkipAlreadyOrganized,
|
||||
MediaType: plan.Layout.MediaType, Category: plan.Layout.Category, Title: plan.Identity.Title,
|
||||
})
|
||||
}
|
||||
|
||||
func (o *OrganizerService) collectExistingSourceVersions(ctx context.Context, req organizeSourceFileRequest, plan organizeSourceFilePlan) organizeExistingSourceVersions {
|
||||
externalExisting := o.existingByExternalIdentity(ctx, req.DestRoot, plan.MetadataMatch, plan.Identity.Season, plan.Identity.Episode)
|
||||
identityExisting := o.existingByIdentity(ctx, req.DestRoot, plan.Identity.ParsedTitle, plan.Identity.Year, plan.Identity.Season, plan.Identity.Episode)
|
||||
folderExisting := o.existingByFolder(plan.Target.Dir, plan.Target.EpisodeTag)
|
||||
return organizeExistingSourceVersions{
|
||||
External: externalExisting,
|
||||
Identity: identityExisting,
|
||||
Folder: folderExisting,
|
||||
All: mergeExistingVersionPaths(externalExisting, identityExisting, folderExisting),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OrganizerService) handleExistingSourceVersions(ctx context.Context, req organizeSourceFileRequest, plan organizeSourceFilePlan, existing organizeExistingSourceVersions) error {
|
||||
reclassified, err := o.reclassifyExistingMedia(ctx, organizeExistingReclassifyRequest{
|
||||
Source: req.Source,
|
||||
Target: plan.Target.Path,
|
||||
DestRoot: req.DestRoot,
|
||||
TargetLibraryID: plan.TargetLibraryID,
|
||||
Existing: existing.All,
|
||||
DryRun: req.DryRun,
|
||||
MediaType: plan.Layout.MediaType,
|
||||
Category: plan.Layout.Category,
|
||||
Title: plan.Identity.Title,
|
||||
Year: plan.Identity.Year,
|
||||
Season: plan.Identity.Season,
|
||||
Episode: plan.Identity.Episode,
|
||||
MetadataMatch: plan.MetadataMatch,
|
||||
Result: req.Result,
|
||||
})
|
||||
if err != nil || reclassified {
|
||||
return err
|
||||
}
|
||||
if replaced, err := o.replaceSourceWithBetterVersion(ctx, req, plan, existing.All); replaced || err != nil {
|
||||
return err
|
||||
}
|
||||
o.skipExistingSourceDuplicate(ctx, req, plan, existing)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OrganizerService) replaceSourceWithBetterVersion(ctx context.Context, req organizeSourceFileRequest, plan organizeSourceFilePlan, existing []string) (bool, error) {
|
||||
srcArea := o.resolutionArea(ctx, req.Source)
|
||||
bestArea := 0
|
||||
for _, e := range existing {
|
||||
if a := o.resolutionArea(ctx, e); a > bestArea {
|
||||
bestArea = a
|
||||
}
|
||||
}
|
||||
if !req.AllowReplaceExisting || srcArea <= 0 || bestArea <= 0 || srcArea <= bestArea {
|
||||
return false, nil
|
||||
}
|
||||
req.Result.Items = append(req.Result.Items, OrganizePreviewItem{
|
||||
Source: req.Source, Target: plan.Target.Path, Action: "replace", Reason: "higher resolution",
|
||||
MediaType: plan.Layout.MediaType, Category: plan.Layout.Category, Title: plan.Identity.Title,
|
||||
})
|
||||
if req.DryRun {
|
||||
req.Result.Replaced++
|
||||
return true, nil
|
||||
}
|
||||
if err := o.replaceVersions(ctx, req.Source, existing, plan.Target.Path, req.Mode); err != nil {
|
||||
return true, err
|
||||
}
|
||||
o.log.Info("organize replaced lower-resolution media",
|
||||
zap.String("from", req.Source),
|
||||
zap.String("to", plan.Target.Path),
|
||||
zap.Int("src_area", srcArea),
|
||||
zap.Int("existing_area", bestArea),
|
||||
)
|
||||
req.Result.Replaced++
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (o *OrganizerService) skipExistingSourceDuplicate(ctx context.Context, req organizeSourceFileRequest, plan organizeSourceFilePlan, existing organizeExistingSourceVersions) {
|
||||
reason := organizeSkipTargetExists
|
||||
if len(existing.External) > 0 || len(existing.Identity) > 0 || o.allExistingPathsInDB(ctx, existing.All) {
|
||||
reason = organizeSkipDuplicateLibrary
|
||||
}
|
||||
o.log.Debug("organize skip duplicate",
|
||||
zap.String("src", req.Source), zap.String("dest_dir", plan.Target.Dir), zap.String("reason", reason))
|
||||
req.Result.Skipped++
|
||||
req.Result.Items = append(req.Result.Items, OrganizePreviewItem{
|
||||
Source: req.Source, Target: plan.Target.Path, Action: "skip", Reason: reason,
|
||||
MediaType: plan.Layout.MediaType, Category: plan.Layout.Category, Title: plan.Identity.Title,
|
||||
})
|
||||
}
|
||||
|
||||
func (o *OrganizerService) writeOrganizedSourceFile(ctx context.Context, req organizeSourceFileRequest, plan organizeSourceFilePlan) error {
|
||||
req.Result.Items = append(req.Result.Items, OrganizePreviewItem{
|
||||
Source: req.Source, Target: plan.Target.Path, Action: "organize",
|
||||
MediaType: plan.Layout.MediaType, Category: plan.Layout.Category, Title: plan.Identity.Title,
|
||||
})
|
||||
if req.DryRun {
|
||||
req.Result.Organized++
|
||||
return nil
|
||||
}
|
||||
if err := os.MkdirAll(plan.Target.Dir, 0o755); err != nil { // #nosec G301 -- organized media directories must remain readable by NAS/player users.
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(plan.Target.Path); err == nil {
|
||||
req.Result.Skipped++
|
||||
if len(req.Result.Items) > 0 {
|
||||
req.Result.Items[len(req.Result.Items)-1].Action = "skip"
|
||||
req.Result.Items[len(req.Result.Items)-1].Reason = organizeSkipTargetExists
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := transferFile(req.Source, plan.Target.Path, req.Mode); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := transferSidecarNFO(req.Source, plan.Target.Path, req.Mode); err != nil {
|
||||
o.log.Warn("organize sidecar nfo failed",
|
||||
zap.String("from", req.Source), zap.String("to", plan.Target.Path), zap.Error(err))
|
||||
}
|
||||
req.Result.Organized++
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user