228 lines
6.6 KiB
Go
228 lines
6.6 KiB
Go
package scanner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/models"
|
|
"github.com/Silo-Server/silo-server/internal/naming"
|
|
)
|
|
|
|
// FinalizeVariantsByPathPrefix recomputes edition/presentation metadata after
|
|
// canonical root ownership and item linkage are stable for the scanned scope.
|
|
func (s *Scanner) FinalizeVariantsByPathPrefix(
|
|
ctx context.Context,
|
|
folder *models.MediaFolder,
|
|
pathPrefix string,
|
|
) error {
|
|
if s == nil || s.fileRepo == nil || folder == nil {
|
|
return nil
|
|
}
|
|
|
|
scopedFiles, err := s.fileRepo.GetByFolderAndPathPrefix(ctx, folder.ID, pathPrefix)
|
|
if err != nil {
|
|
return fmt.Errorf("loading files for variant finalization: %w", err)
|
|
}
|
|
if len(scopedFiles) == 0 {
|
|
return nil
|
|
}
|
|
|
|
files, err := s.variantFinalizationFilesForScope(ctx, folder.ID, scopedFiles)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(files) == 0 {
|
|
return nil
|
|
}
|
|
|
|
type groupSummary struct {
|
|
maxPartIndex int
|
|
}
|
|
|
|
groupTotals := make(map[string]*groupSummary)
|
|
for _, file := range files {
|
|
if file == nil || file.MissingSince != nil {
|
|
continue
|
|
}
|
|
ownerKey := stableOwnerKey(file)
|
|
if ownerKey == "" {
|
|
continue
|
|
}
|
|
hints := naming.ParseVariantHints(file.FilePath, folder.Type)
|
|
if file.EditionSource == "import" && file.EditionKey != "" {
|
|
hints = &naming.VariantHints{
|
|
EditionRaw: file.EditionRaw,
|
|
EditionKey: file.EditionKey,
|
|
EditionSource: file.EditionSource,
|
|
EditionConfidence: file.EditionConfidence,
|
|
PresentationKind: file.PresentationKind,
|
|
PresentationGroupKey: file.PresentationGroupKey,
|
|
PresentationPartIndex: file.PresentationPartIndex,
|
|
MultiEpisodeStart: file.MultiEpisodeStart,
|
|
MultiEpisodeEnd: file.MultiEpisodeEnd,
|
|
}
|
|
}
|
|
if hints == nil {
|
|
continue
|
|
}
|
|
if (hints.PresentationKind != "multipart_movie" && hints.PresentationKind != "split_episode") ||
|
|
hints.PresentationGroupKey == "" || hints.PresentationPartIndex <= 0 {
|
|
continue
|
|
}
|
|
groupKey := ownerKey + "|" + hints.EditionKey + "|" + hints.PresentationKind + "|" + hints.PresentationGroupKey
|
|
summary := groupTotals[groupKey]
|
|
if summary == nil {
|
|
summary = &groupSummary{}
|
|
groupTotals[groupKey] = summary
|
|
}
|
|
if hints.PresentationPartIndex > summary.maxPartIndex {
|
|
summary.maxPartIndex = hints.PresentationPartIndex
|
|
}
|
|
}
|
|
|
|
for _, file := range files {
|
|
if file == nil || file.MissingSince != nil {
|
|
continue
|
|
}
|
|
ownerKey := stableOwnerKey(file)
|
|
if ownerKey == "" {
|
|
continue
|
|
}
|
|
|
|
hints := naming.ParseVariantHints(file.FilePath, folder.Type)
|
|
if file.EditionSource == "import" && file.EditionKey != "" {
|
|
hints = &naming.VariantHints{
|
|
EditionRaw: file.EditionRaw,
|
|
EditionKey: file.EditionKey,
|
|
EditionSource: file.EditionSource,
|
|
EditionConfidence: file.EditionConfidence,
|
|
PresentationKind: file.PresentationKind,
|
|
PresentationGroupKey: file.PresentationGroupKey,
|
|
PresentationPartIndex: file.PresentationPartIndex,
|
|
MultiEpisodeStart: file.MultiEpisodeStart,
|
|
MultiEpisodeEnd: file.MultiEpisodeEnd,
|
|
}
|
|
}
|
|
if hints == nil {
|
|
hints = &naming.VariantHints{}
|
|
}
|
|
partTotal := 0
|
|
if (hints.PresentationKind == "multipart_movie" || hints.PresentationKind == "split_episode") &&
|
|
hints.PresentationGroupKey != "" && hints.PresentationPartIndex > 0 {
|
|
groupKey := ownerKey + "|" + hints.EditionKey + "|" + hints.PresentationKind + "|" + hints.PresentationGroupKey
|
|
if summary := groupTotals[groupKey]; summary != nil {
|
|
partTotal = summary.maxPartIndex
|
|
}
|
|
}
|
|
|
|
if !variantMetadataChanged(file, hints, partTotal) {
|
|
continue
|
|
}
|
|
|
|
updated := *file
|
|
updated.EditionRaw = hints.EditionRaw
|
|
updated.EditionKey = hints.EditionKey
|
|
updated.EditionConfidence = hints.EditionConfidence
|
|
updated.EditionSource = hints.EditionSource
|
|
updated.PresentationKind = hints.PresentationKind
|
|
updated.PresentationGroupKey = hints.PresentationGroupKey
|
|
updated.PresentationPartIndex = hints.PresentationPartIndex
|
|
updated.PresentationPartTotal = partTotal
|
|
updated.MultiEpisodeStart = hints.MultiEpisodeStart
|
|
updated.MultiEpisodeEnd = hints.MultiEpisodeEnd
|
|
if _, err := s.fileRepo.Upsert(ctx, updated); err != nil {
|
|
return fmt.Errorf("finalizing variant metadata for %s: %w", file.FilePath, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Scanner) variantFinalizationFilesForScope(
|
|
ctx context.Context,
|
|
folderID int,
|
|
scopedFiles []*models.MediaFile,
|
|
) ([]*models.MediaFile, error) {
|
|
if len(scopedFiles) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
filesByID := make(map[int]*models.MediaFile, len(scopedFiles))
|
|
for _, file := range scopedFiles {
|
|
if file == nil || file.MissingSince != nil {
|
|
continue
|
|
}
|
|
if file.EpisodeID == "" && file.ContentID == "" {
|
|
if file.MediaFolderID == folderID {
|
|
filesByID[file.ID] = file
|
|
}
|
|
continue
|
|
}
|
|
|
|
var related []*models.MediaFile
|
|
var err error
|
|
switch {
|
|
case file.EpisodeID != "":
|
|
related, err = s.fileRepo.GetByEpisodeID(ctx, file.EpisodeID)
|
|
case file.ContentID != "":
|
|
related, err = s.fileRepo.GetByContentID(ctx, file.ContentID)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("loading related files for variant finalization: %w", err)
|
|
}
|
|
for _, relatedFile := range related {
|
|
if relatedFile == nil || relatedFile.MissingSince != nil || relatedFile.MediaFolderID != folderID {
|
|
continue
|
|
}
|
|
filesByID[relatedFile.ID] = relatedFile
|
|
}
|
|
}
|
|
|
|
files := make([]*models.MediaFile, 0, len(filesByID))
|
|
for _, file := range filesByID {
|
|
files = append(files, file)
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
func stableOwnerKey(file *models.MediaFile) string {
|
|
switch {
|
|
case file == nil:
|
|
return ""
|
|
case file.EpisodeID != "":
|
|
return "episode:" + file.EpisodeID
|
|
case file.ContentID != "":
|
|
return "content:" + file.ContentID
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func variantMetadataChanged(file *models.MediaFile, hints *naming.VariantHints, partTotal int) bool {
|
|
if file == nil {
|
|
return false
|
|
}
|
|
if hints == nil {
|
|
hints = &naming.VariantHints{}
|
|
}
|
|
if file.EditionRaw != hints.EditionRaw ||
|
|
file.EditionKey != hints.EditionKey ||
|
|
file.EditionSource != hints.EditionSource ||
|
|
file.PresentationKind != hints.PresentationKind ||
|
|
file.PresentationGroupKey != hints.PresentationGroupKey ||
|
|
file.PresentationPartIndex != hints.PresentationPartIndex ||
|
|
file.PresentationPartTotal != partTotal ||
|
|
file.MultiEpisodeStart != hints.MultiEpisodeStart ||
|
|
file.MultiEpisodeEnd != hints.MultiEpisodeEnd {
|
|
return true
|
|
}
|
|
switch {
|
|
case file.EditionConfidence == nil && hints.EditionConfidence == nil:
|
|
return false
|
|
case file.EditionConfidence == nil || hints.EditionConfidence == nil:
|
|
return true
|
|
default:
|
|
return *file.EditionConfidence != *hints.EditionConfidence
|
|
}
|
|
}
|