Раскладка: сходимость папки сериала (второй сезон в ту же папку)
При подтверждённом матче база папки (имя+год) наследуется от живой папки-якоря того же (provider, provider_id) вместо печати заново из выхода LLM — так второй/последующий сезон ложится в ТУ ЖЕ папку, а не заводит рядом почти одинаковую. Отдельная сущность «тайтл» не вводится. - layout: Plan.FolderBase перекрывает базу в папке и именах файлов; TitleFolder разбирает dst_path в папку тайтла и базу (снятие тега). - store: LiveTitleFolders — dst_path живых ссылок того же матча. - worker: resolveFolderBase (живость якоря — по наличию папки на диске, os.Lstat, а не по статусу ссылки в БД) в linkPlan и в превью ревью (превью=применение); рассинхрон нескольких живых папок → review из linking (deferred→review в графе нет). Схема БД не менялась. Change series-folder-convergence заархивирован, требования влиты в openspec/specs/file-layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,78 @@ func TestBuildLinks_Series(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLinks_FolderBaseOverridesSeries(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
// LLM дал «Fargo» 2017, но живой якорь — «Фарго (2014)». FolderBase должна
|
||||
// перекрыть Title/Year и в папке, И в имени файла.
|
||||
plan := Plan{
|
||||
Type: Series, Title: "Fargo", Year: 2017, ProviderTag: "tvdbid-269613",
|
||||
FolderBase: "Фарго (2014)",
|
||||
Files: []PlanFile{
|
||||
{Src: f.srcFile(t, "s/e1.mkv", "1"), Role: RoleEpisode, Season: intp(2), Episode: intp(1)},
|
||||
},
|
||||
}
|
||||
links, err := f.l.BuildLinks(plan)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildLinks: %v", err)
|
||||
}
|
||||
want := filepath.Join(f.series, "Фарго (2014) [tvdbid-269613]", "Season 02", "Фарго (2014) S02E01.mkv")
|
||||
if links[0].Dst != want {
|
||||
t.Errorf("ep = %q, want %q", links[0].Dst, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLinks_FolderBaseOverridesMovie(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
plan := Plan{
|
||||
Type: Movie, Title: "Dune", Year: 2021, ProviderTag: "tmdbid-693134",
|
||||
FolderBase: "Дюна Часть вторая (2024)",
|
||||
Files: []PlanFile{{Src: f.srcFile(t, "m/f.mkv", "1"), Role: RoleMain}},
|
||||
}
|
||||
links, err := f.l.BuildLinks(plan)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildLinks: %v", err)
|
||||
}
|
||||
want := filepath.Join(f.movies, "Дюна Часть вторая (2024) [tmdbid-693134]", "Дюна Часть вторая (2024).mkv")
|
||||
if links[0].Dst != want {
|
||||
t.Errorf("main = %q, want %q", links[0].Dst, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTitleFolder(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
epDst := filepath.Join(f.series, "Фарго (2014) [tvdbid-269613]", "Season 01", "Фарго (2014) S01E01.mkv")
|
||||
dir, base, ok := f.l.TitleFolder(Series, epDst)
|
||||
if !ok {
|
||||
t.Fatal("want ok for path under series root")
|
||||
}
|
||||
if wantDir := filepath.Join(f.series, "Фарго (2014) [tvdbid-269613]"); dir != wantDir {
|
||||
t.Errorf("dir = %q, want %q", dir, wantDir)
|
||||
}
|
||||
if base != "Фарго (2014)" {
|
||||
t.Errorf("base = %q, want %q", base, "Фарго (2014)")
|
||||
}
|
||||
|
||||
// Фильм: папка тайтла — первый сегмент, база без тега.
|
||||
mvDst := filepath.Join(f.movies, "Dune (2024) [tmdbid-1]", "Dune (2024).mkv")
|
||||
if dir, base, ok := f.l.TitleFolder(Movie, mvDst); !ok ||
|
||||
dir != filepath.Join(f.movies, "Dune (2024) [tmdbid-1]") || base != "Dune (2024)" {
|
||||
t.Errorf("movie: dir=%q base=%q ok=%v", dir, base, ok)
|
||||
}
|
||||
|
||||
// Путь не под корнем нужной библиотеки → not ok.
|
||||
if _, _, ok := f.l.TitleFolder(Series, mvDst); ok {
|
||||
t.Error("movie path must not resolve under series root")
|
||||
}
|
||||
if _, _, ok := f.l.TitleFolder(Movie, "/etc/passwd"); ok {
|
||||
t.Error("path outside library must be rejected")
|
||||
}
|
||||
// Сам корень (нет сегмента папки) → not ok.
|
||||
if _, _, ok := f.l.TitleFolder(Movie, f.movies); ok {
|
||||
t.Error("root itself has no title folder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLinks_SeriesEpisodeWithoutNumber(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
plan := Plan{
|
||||
|
||||
Reference in New Issue
Block a user