diff --git a/README.md b/README.md index da405ff..fdb6991 100644 --- a/README.md +++ b/README.md @@ -183,9 +183,15 @@ dotnet test **Признак «уже размечено» — отдельная отметка, а не наличие описания.** Сначала прогон пропускал видео с непустым `Description`, и это ломалось на источниках, у которых есть название, актёры и теги, но нет синопсиса: такие видео возвращались на каждом запуске. - Теперь применение ставит `MetadataAppliedAt` и имя источника, а миграция проставляет - отметку тем, у кого описание уже есть, — иначе первый же запуск после обновления предложил - бы заново всё, что уже было сделано. + Хранится **набор** источников (`VideoMetadataSources`), а не последний применённый. + Одного поля хватало ровно до второго применения, после которого первый источник + забывался и предлагался заново. Набор же отвечает на вопрос, который прогон и задаёт, — + «этот источник уже высказался?», — и делает осмысленным режим «источники, которых ещё + не спрашивали»: у видео уже есть ответы PornDB, вопрос в том, что добавит StashDB. + Спрашивать того, кто уже ответил, значит потратить запрос, чтобы услышать написанное. + Режимов три: только неразмеченные, недостающие источники, всё заново. + Миграция переносит старую одиночную колонку в набор первой строкой, и делает это до + удаления колонок — сгенерированный вариант удалял их первыми и терял всё разом. - **Картинки скачиваются один раз и по размеру.** `images` в stash-box есть у сцены, у актёра и у студии; у тега такого поля нет вовсе, поэтому там карточка показывает первую букву — это норма, а не отсутствие данных. Обложка сцены нужна там, где выбирают из diff --git a/tests/PLib.Tests/Library/MetadataScanTests.cs b/tests/PLib.Tests/Library/MetadataScanTests.cs index 711eb02..a6697d3 100644 --- a/tests/PLib.Tests/Library/MetadataScanTests.cs +++ b/tests/PLib.Tests/Library/MetadataScanTests.cs @@ -50,7 +50,7 @@ public sealed class MetadataScanTests (await CollectAsync()).OfType().Single().Processed.ShouldBe(1); // Asking again for everything is what the switch is for. - var all = await CollectAsync(new MetadataScanRequest(OnlyWithoutMetadata: false)); + var all = await CollectAsync(new MetadataScanRequest(MetadataScanScope.Everything)); all.OfType().Single().Processed.ShouldBe(2); } @@ -66,8 +66,8 @@ public sealed class MetadataScanTests await CollectAsync(new MetadataScanRequest(ApplyUnambiguous: true)); video.Description.ShouldBeNull(); - video.MetadataAppliedAt.ShouldNotBeNull(); - video.MetadataSource.ShouldBe("StashDB"); + video.HasMetadata.ShouldBeTrue(); + video.AppliedSources.ShouldHaveSingleItem().SourceName.ShouldBe("StashDB"); // Judging "already done" by the description sent these back on every single run. var again = await CollectAsync(new MetadataScanRequest(ApplyUnambiguous: true)); @@ -148,6 +148,66 @@ public sealed class MetadataScanTests events.OfType().Single().Processed.ShouldBe(3); } + [Fact] + public async Task A_new_source_can_be_asked_about_videos_the_old_one_already_answered() + { + var video = Video("видео", hash: 1); + video.MarkMetadataApplied("PornDb"); + _videos.Seed(video); + + var fresh = new MetadataSourceOptions { Name = "StashDB", Endpoint = "https://stashdb/graphql" }; + var known = new MetadataSourceOptions { Name = "PornDb", Endpoint = "https://porndb/graphql" }; + + Answer(fresh, [Match("Из нового источника")]); + Answer(known, [Match("Из старого источника")]); + + var events = await CollectAsync( + new MetadataScanRequest(MetadataScanScope.MissingSources), + known, + fresh); + + // The point of the mode: the source that already had its say is not asked to repeat + // itself, and only what the new one adds comes back. + var matched = events.OfType().ShouldHaveSingleItem(); + matched.Matches.ShouldHaveSingleItem().Title.ShouldBe("Из нового источника"); + + await _provider.DidNotReceive().FindByPerceptualHashAsync( + known, + Arg.Any(), + Arg.Any()); + } + + [Fact] + public async Task A_video_every_source_has_already_answered_is_left_out_of_that_mode() + { + var video = Video("видео", hash: 1); + video.MarkMetadataApplied("StashDB"); + _videos.Seed(video); + + Answer(_source, [Match("Название")]); + + var events = await CollectAsync(new MetadataScanRequest(MetadataScanScope.MissingSources)); + + events.OfType().Single().Processed.ShouldBe(0); + } + + [Fact] + public async Task Applying_the_same_source_twice_records_it_once() + { + var video = Video("видео", hash: 1); + _videos.Seed(video); + + video.MarkMetadataApplied("StashDB"); + video.MarkMetadataApplied("stashdb"); + + // Free text the user typed: "PornDb" and "PornDB" are plainly the same source to + // everyone but a byte comparison. + video.AppliedSources.ShouldHaveSingleItem(); + video.HasMetadataFrom("STASHDB").ShouldBeTrue(); + + await Task.CompletedTask; + } + private void Answer(MetadataSourceOptions source, IReadOnlyList matches) => _provider.FindByPerceptualHashAsync(source, Arg.Any(), Arg.Any()) .Returns(matches);