Enhance metadata management in PLib video library manager by introducing new label handling features. Update LibraryDataKind to include Tags, Performers, Studios, and Collections, allowing for more granular data clearing options. Revise LibraryService to support label management during data resets, ensuring proper handling of user-added metadata. Update README.md to reflect these changes and clarify the implications of data clearing operations.

This commit is contained in:
Leonid Pershin
2026-08-10 10:50:03 +03:00
parent b82b0b4555
commit 4539a97d16
8 changed files with 216 additions and 21 deletions
@@ -283,6 +283,81 @@ public sealed class LibraryServiceTests
item.IsIndexed.ShouldBeFalse();
}
[Fact]
public async Task Clearing_one_kind_of_label_leaves_the_other_kinds_standing()
{
var item = FullyIndexed();
_repository.Seed(item);
var service = CreateService();
await service.AttachLabelAsync(item.Id, "драма", LabelKind.Tag, Token);
await service.AttachLabelAsync(item.Id, "Актёр", LabelKind.Performer, Token);
await service.AttachLabelAsync(item.Id, "Моя подборка", LabelKind.Collection, Token);
await service.ResetAsync(LibraryDataKind.Performers, Token);
// Gone from the library, not merely detached: the label is the relation, so removing
// it takes every attachment with it.
var remaining = await service.GetLabelsAsync(Token);
remaining.Select(label => label.Kind).ShouldBe([LabelKind.Tag, LabelKind.Collection], ignoreOrder: true);
item.Labels.ShouldNotContain(label => label.Kind == LabelKind.Performer);
}
[Fact]
public async Task Clearing_everything_now_takes_the_labels_as_well()
{
var item = FullyIndexed();
_repository.Seed(item);
var service = CreateService();
await service.AttachLabelAsync(item.Id, "драма", LabelKind.Tag, Token);
await service.AttachLabelAsync(item.Id, "Студия", LabelKind.Studio, Token);
await service.AttachLabelAsync(item.Id, "Моя подборка", LabelKind.Collection, Token);
await service.ResetAsync(LibraryDataKind.All, Token);
// "Everything" that left the labels behind was the complaint that put them here.
(await service.GetLabelsAsync(Token)).ShouldBeEmpty();
item.ThumbnailPath.ShouldBeNull();
item.PerceptualHash.ShouldBeNull();
}
[Fact]
public async Task Clearing_the_images_does_not_take_the_labels_that_wore_them()
{
var item = FullyIndexed();
_repository.Seed(item);
var service = CreateService();
var performer = await service.AttachLabelAsync(item.Id, "Актёр", LabelKind.Performer, Token);
performer.AttachImage(@"C:\cache\images\face.jpg");
await service.ResetAsync(LibraryDataKind.RemoteImages, Token);
// The picture is derived; the performer is the thing it was a picture of.
(await service.GetLabelsAsync(Token)).ShouldHaveSingleItem().ImagePath.ShouldBeNull();
}
[Fact]
public async Task Usage_counts_labels_by_kind_without_pretending_they_are_a_share_of_anything()
{
var item = FullyIndexed();
_repository.Seed(item);
var service = CreateService();
await service.AttachLabelAsync(item.Id, "драма", LabelKind.Tag, Token);
await service.AttachLabelAsync(item.Id, "нуар", LabelKind.Tag, Token);
await service.AttachLabelAsync(item.Id, "Актёр", LabelKind.Performer, Token);
var usage = (await service.GetDataUsageAsync(Token)).ToDictionary(entry => entry.Kind);
usage[LibraryDataKind.Tags].Present.ShouldBe(2);
usage[LibraryDataKind.Performers].Present.ShouldBe(1);
// No total: "2 из 2" would only invite the reader to look for the missing ones.
usage[LibraryDataKind.Tags].Total.ShouldBeNull();
}
[Fact]
public async Task Usage_says_how_far_each_kind_has_got_through_the_library()
{
@@ -294,7 +369,7 @@ public sealed class LibraryServiceTests
var usage = (await CreateService().GetDataUsageAsync(Token)).ToDictionary(entry => entry.Kind);
usage[LibraryDataKind.PerceptualHashes].ShouldSatisfyAllConditions(
entry => entry.Videos.ShouldBe(2),
entry => entry.Total.ShouldBe(2),
entry => entry.Present.ShouldBe(1));
// Only the caches that are files on disk have a size to report.