Files
MeBox/internal/service/organizer_directory_source_existing.go
T
truewhile 495292ab3b bug处理优化
bug处理优化
2026-08-20 15:43:39 +08:00

138 lines
5.5 KiB
Go

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))
}
if err := transferSidecarArtwork(req.Source, plan.Target.Path, req.Mode); err != nil {
o.log.Warn("organize sidecar artwork failed",
zap.String("from", req.Source), zap.String("to", plan.Target.Path), zap.Error(err))
}
o.persistOrganizedSourceMetadata(ctx, plan)
req.Result.Organized++
return nil
}