Fix cloud playback and organize ingest pipeline

This commit is contained in:
ShukeBta
2026-06-13 23:21:56 +08:00
parent 4b2839d753
commit 52b772ea45
18 changed files with 729 additions and 22 deletions
@@ -313,6 +313,73 @@ func TestOrganizeDirectoryDedup(t *testing.T) {
}
}
func TestOrganizeDirectoryTreatsScannedTargetPathAsLibraryDuplicate(t *testing.T) {
root := t.TempDir()
src := filepath.Join(root, "downloads")
dest := filepath.Join(root, "media")
writeOrgFile(t, filepath.Join(src, "Weird.Release.2024.1080p.mkv"), "source")
existing := filepath.Join(dest, "电影", "Weird Release (2024)", "Weird Release (2024).mkv")
writeOrgFile(t, existing, "existing")
repos := newOrganizerTestRepo(t)
row := model.Media{Title: "刮削后的正式片名", Path: existing, Year: 2024, Container: "mkv", Width: 1920, Height: 1080}
if err := repos.Media.Upsert(t.Context(), &row); err != nil {
t.Fatal(err)
}
org := NewOrganizerService(&config.Config{}, zap.NewNop(), repos)
res, err := org.OrganizeDirectory(t.Context(), OrganizeOptions{
SourcePath: src,
DestPath: dest,
TransferMode: TransferCopy,
})
if err != nil {
t.Fatalf("organize directory: %v", err)
}
if res.Organized != 0 || res.Skipped != 1 {
t.Fatalf("result = %+v, want organized=0 skipped=1", res)
}
if len(res.Items) != 1 || res.Items[0].Reason != organizeSkipDuplicateLibrary {
t.Fatalf("items = %+v, want duplicate in library", res.Items)
}
if OrganizeResultNeedsVisibilitySync(res) {
t.Fatal("path already present in DB should not trigger another visibility scan")
}
}
func TestOrganizeDirectorySkipsSampleClips(t *testing.T) {
root := t.TempDir()
src := filepath.Join(root, "downloads")
dest := filepath.Join(root, "media")
writeOrgFile(t, filepath.Join(src, "Demo.Movie.2024.1080p.mkv"), "main")
writeOrgFile(t, filepath.Join(src, "Samples", "Sample1.mkv"), "sample-dir")
writeOrgFile(t, filepath.Join(src, "Demo.Movie.2024.sample.mkv"), "sample-file")
org := NewOrganizerService(&config.Config{}, zap.NewNop(), newOrganizerTestRepo(t))
res, err := org.OrganizeDirectory(t.Context(), OrganizeOptions{
SourcePath: src,
DestPath: dest,
TransferMode: TransferCopy,
})
if err != nil {
t.Fatalf("organize directory: %v", err)
}
if res.Organized != 1 || res.Skipped != 2 {
t.Fatalf("result = %+v, want organized=1 skipped=2", res)
}
if got := OrganizeSkipReasonCounts(res)[organizeSkipSampleClip]; got != 2 {
t.Fatalf("sample skip count = %d, want 2; items=%+v", got, res.Items)
}
if _, err := os.Stat(filepath.Join(dest, "电影", "Sample1", "Sample1.mkv")); !os.IsNotExist(err) {
t.Fatalf("sample directory clip should not be organized, stat err=%v", err)
}
if _, err := os.Stat(filepath.Join(dest, "电影", "Demo Movie Sample (2024)", "Demo Movie Sample (2024).mkv")); !os.IsNotExist(err) {
t.Fatalf("sample suffix clip should not be organized, stat err=%v", err)
}
}
// TestOrganizeDirectorySkipsHigherResolutionWhenReplacementDisabled verifies
// that dedup wins by default: even a higher-resolution source must not replace
// an existing library item unless the caller explicitly allows washing.
@@ -549,6 +616,54 @@ func TestOrganizeDirectoryUsesExplicitCategoryLibraryRoot(t *testing.T) {
}
}
func TestOrganizeDirectoryCreatesMissingCategoryLibraryForVisibility(t *testing.T) {
root := t.TempDir()
srcRoot := filepath.Join(root, "downloads")
dest := filepath.Join(root, "media")
source := filepath.Join(srcRoot, "Gourd.Brothers.S01E01.2026.1080p.mkv")
target := filepath.Join(dest, "电视剧", "未分类", "Gourd Brothers", "Season 01", "Gourd Brothers - S01E01.mkv")
writeOrgFile(t, source, "source")
writeOrgFile(t, target, "already-there")
repos := newOrganizerTestRepo(t)
org := NewOrganizerService(&config.Config{}, zap.NewNop(), repos)
res, err := org.OrganizeDirectory(t.Context(), OrganizeOptions{
SourcePath: srcRoot,
DestPath: dest,
MediaType: "tv",
MediaCategory: "未分类",
TransferMode: TransferCopy,
AllowReplaceExisting: false,
})
if err != nil {
t.Fatalf("organize missing category: %v", err)
}
if res.Organized != 0 || res.Skipped != 1 || len(res.Items) != 1 || res.Items[0].Reason != organizeSkipTargetExists {
t.Fatalf("result = %+v, want skipped target exists", res)
}
var lib model.Library
if err := repos.DB.Where("path = ?", filepath.Join(dest, "电视剧", "未分类")).First(&lib).Error; err != nil {
t.Fatalf("missing auto-created category library: %v", err)
}
if lib.Name != "未分类" || lib.Type != "tv" || !lib.Enabled {
t.Fatalf("auto-created library = %+v, want enabled tv 未分类", lib)
}
scanner := NewScannerService(&config.Config{}, zap.NewNop(), repos, NewHub(zap.NewNop()), nil, nil)
scans := scanner.ScanLibrariesForPath(t.Context(), res.DestPath, "")
added := 0
for _, scan := range scans {
if scan.Error != "" {
t.Fatalf("scan failed: %#v", scan)
}
added += scan.Added
}
if added != 1 {
t.Fatalf("scan added = %d, want 1; scans=%#v", added, scans)
}
}
func TestOrganizeDirectorySmartClassifiesUncategorizedSources(t *testing.T) {
root := t.TempDir()
src := filepath.Join(root, "downloads")