148 lines
4.5 KiB
Go
148 lines
4.5 KiB
Go
package metadata
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"sort"
|
||
|
|
"sync"
|
||
|
|
|
||
|
|
"github.com/Silo-Server/silo-server/internal/models"
|
||
|
|
)
|
||
|
|
|
||
|
|
// NFOSeriesHarness bridges the in-package fakes to the external NFO
|
||
|
|
// integration tests (package metadata_test). Those tests must live outside
|
||
|
|
// the package so they can import internal/metadata/nfo (which itself imports
|
||
|
|
// this package) without an import cycle.
|
||
|
|
type NFOSeriesHarness struct {
|
||
|
|
h *testHarness
|
||
|
|
seasons *fakeSeasonRepo
|
||
|
|
episodes *fakeEpisodeRepo
|
||
|
|
jobs *capturingImageJobEnqueuer
|
||
|
|
}
|
||
|
|
|
||
|
|
type capturingImageJobEnqueuer struct {
|
||
|
|
mu sync.Mutex
|
||
|
|
inputs []EnqueueImageCacheJobInput
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *capturingImageJobEnqueuer) Enqueue(_ context.Context, in EnqueueImageCacheJobInput) error {
|
||
|
|
c.mu.Lock()
|
||
|
|
defer c.mu.Unlock()
|
||
|
|
c.inputs = append(c.inputs, in)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *capturingImageJobEnqueuer) EnqueueBatch(_ context.Context, inputs []EnqueueImageCacheJobInput) (int, error) {
|
||
|
|
c.mu.Lock()
|
||
|
|
defer c.mu.Unlock()
|
||
|
|
c.inputs = append(c.inputs, inputs...)
|
||
|
|
return len(inputs), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewNFOSeriesHarness() *NFOSeriesHarness {
|
||
|
|
h := newTestHarness()
|
||
|
|
x := &NFOSeriesHarness{
|
||
|
|
h: h,
|
||
|
|
seasons: newFakeSeasonRepo(),
|
||
|
|
episodes: newFakeEpisodeRepo(),
|
||
|
|
jobs: &capturingImageJobEnqueuer{},
|
||
|
|
}
|
||
|
|
h.service.seasonRepo = x.seasons
|
||
|
|
h.service.episodeRepo = x.episodes
|
||
|
|
h.service.imageCacheJobs = x.jobs
|
||
|
|
h.service.autoCacheImages.Store(true)
|
||
|
|
return x
|
||
|
|
}
|
||
|
|
|
||
|
|
func (x *NFOSeriesHarness) Service() *MetadataService { return x.h.service }
|
||
|
|
|
||
|
|
// SeedSeriesSkeleton seeds a pending series item, mirroring the scanner's
|
||
|
|
// skeleton for an unmatched series root.
|
||
|
|
func (x *NFOSeriesHarness) SeedSeriesSkeleton(contentID, title string) error {
|
||
|
|
return x.seedSkeleton(contentID, title, "series")
|
||
|
|
}
|
||
|
|
|
||
|
|
// SeedMovieSkeleton seeds a pending movie item, mirroring the scanner's
|
||
|
|
// skeleton for an unmatched movie group (e.g. the movie lane of a mixed
|
||
|
|
// library).
|
||
|
|
func (x *NFOSeriesHarness) SeedMovieSkeleton(contentID, title string) error {
|
||
|
|
return x.seedSkeleton(contentID, title, "movie")
|
||
|
|
}
|
||
|
|
|
||
|
|
func (x *NFOSeriesHarness) seedSkeleton(contentID, title, contentType string) error {
|
||
|
|
return x.h.itemRepo.Upsert(context.Background(), &models.MediaItem{
|
||
|
|
ContentID: contentID,
|
||
|
|
Type: contentType,
|
||
|
|
Title: title,
|
||
|
|
Status: "pending_match",
|
||
|
|
Studios: []string{},
|
||
|
|
Networks: []string{},
|
||
|
|
Countries: []string{},
|
||
|
|
Genres: []string{},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (x *NFOSeriesHarness) Item(contentID string) (*models.MediaItem, error) {
|
||
|
|
return x.h.itemRepo.GetByID(context.Background(), contentID)
|
||
|
|
}
|
||
|
|
|
||
|
|
// SeedSeriesFile registers a persisted media file linked to a content id, so
|
||
|
|
// refresh flows (which reconstruct local context from the file repo instead
|
||
|
|
// of scan hints) can find the series' episode files. observedRootPath is the
|
||
|
|
// series root eligible for directory-level sidecars.
|
||
|
|
func (x *NFOSeriesHarness) SeedSeriesFile(fileID, folderID int, filePath, observedRootPath, contentID string) {
|
||
|
|
x.h.fileRepo.setGroupFiles(folderID, 1, fmt.Sprintf("group-%s-%d", contentID, fileID), &models.MediaFile{
|
||
|
|
ID: fileID,
|
||
|
|
MediaFolderID: folderID,
|
||
|
|
FilePath: filePath,
|
||
|
|
ObservedRootPath: observedRootPath,
|
||
|
|
})
|
||
|
|
x.h.fileRepo.mu.Lock()
|
||
|
|
x.h.fileRepo.contentIDs[fileID] = contentID
|
||
|
|
x.h.fileRepo.mu.Unlock()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Seasons returns the persisted seasons for a series sorted by season number.
|
||
|
|
func (x *NFOSeriesHarness) Seasons(seriesID string) []*models.Season {
|
||
|
|
x.seasons.mu.Lock()
|
||
|
|
defer x.seasons.mu.Unlock()
|
||
|
|
var out []*models.Season
|
||
|
|
for _, s := range x.seasons.seasons {
|
||
|
|
if s.SeriesID == seriesID {
|
||
|
|
cp := *s
|
||
|
|
out = append(out, &cp)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sort.Slice(out, func(i, j int) bool { return out[i].SeasonNumber < out[j].SeasonNumber })
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
// Episodes returns the persisted episodes for a series sorted by (season, episode).
|
||
|
|
func (x *NFOSeriesHarness) Episodes(seriesID string) []*models.Episode {
|
||
|
|
x.episodes.mu.Lock()
|
||
|
|
defer x.episodes.mu.Unlock()
|
||
|
|
var out []*models.Episode
|
||
|
|
for _, e := range x.episodes.episodes {
|
||
|
|
if e.SeriesID == seriesID {
|
||
|
|
cp := *e
|
||
|
|
out = append(out, &cp)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sort.Slice(out, func(i, j int) bool {
|
||
|
|
if out[i].SeasonNumber != out[j].SeasonNumber {
|
||
|
|
return out[i].SeasonNumber < out[j].SeasonNumber
|
||
|
|
}
|
||
|
|
return out[i].EpisodeNumber < out[j].EpisodeNumber
|
||
|
|
})
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
// EnqueuedImageJobs returns every image cache job the pipeline enqueued.
|
||
|
|
func (x *NFOSeriesHarness) EnqueuedImageJobs() []EnqueueImageCacheJobInput {
|
||
|
|
x.jobs.mu.Lock()
|
||
|
|
defer x.jobs.mu.Unlock()
|
||
|
|
out := make([]EnqueueImageCacheJobInput, len(x.jobs.inputs))
|
||
|
|
copy(out, x.jobs.inputs)
|
||
|
|
return out
|
||
|
|
}
|