mirror of https://github.com/ErsatzTV/ErsatzTV.git
31 changed files with 14379 additions and 146 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
#nullable disable |
||||
|
||||
namespace ErsatzTV.Infrastructure.MySql.Migrations |
||||
{ |
||||
/// <inheritdoc />
|
||||
public partial class Backfill_Tag_ExternalTypeId : Migration |
||||
{ |
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
// the order is important; each collection scanner takes its own rows first
|
||||
// a row that still has a collection id after that came from a plex label
|
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = 'plex/collection' |
||||
WHERE ExternalTypeId IS NULL |
||||
AND ExternalCollectionId IN (SELECT `Key` FROM PlexCollection); |
||||
""");
|
||||
|
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = 'emby/collection' |
||||
WHERE ExternalTypeId IS NULL |
||||
AND ExternalCollectionId IN (SELECT ItemId FROM EmbyCollection); |
||||
""");
|
||||
|
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = 'jellyfin/collection' |
||||
WHERE ExternalTypeId IS NULL |
||||
AND ExternalCollectionId IN (SELECT ItemId FROM JellyfinCollection); |
||||
""");
|
||||
|
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = 'plex/label' |
||||
WHERE ExternalTypeId IS NULL |
||||
AND ExternalCollectionId IS NOT NULL; |
||||
""");
|
||||
} |
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = NULL |
||||
WHERE ExternalTypeId IN ('plex/label', 'plex/collection', 'emby/collection', 'jellyfin/collection'); |
||||
""");
|
||||
} |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
#nullable disable |
||||
|
||||
namespace ErsatzTV.Infrastructure.Sqlite.Migrations |
||||
{ |
||||
/// <inheritdoc />
|
||||
public partial class Backfill_Tag_ExternalTypeId : Migration |
||||
{ |
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
// the order is important; each collection scanner takes its own rows first
|
||||
// a row that still has a collection id after that came from a plex label
|
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = 'plex/collection' |
||||
WHERE ExternalTypeId IS NULL |
||||
AND ExternalCollectionId IN (SELECT `Key` FROM PlexCollection); |
||||
""");
|
||||
|
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = 'emby/collection' |
||||
WHERE ExternalTypeId IS NULL |
||||
AND ExternalCollectionId IN (SELECT ItemId FROM EmbyCollection); |
||||
""");
|
||||
|
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = 'jellyfin/collection' |
||||
WHERE ExternalTypeId IS NULL |
||||
AND ExternalCollectionId IN (SELECT ItemId FROM JellyfinCollection); |
||||
""");
|
||||
|
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = 'plex/label' |
||||
WHERE ExternalTypeId IS NULL |
||||
AND ExternalCollectionId IS NOT NULL; |
||||
""");
|
||||
} |
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.Sql( |
||||
"""
|
||||
UPDATE Tag SET ExternalTypeId = NULL |
||||
WHERE ExternalTypeId IN ('plex/label', 'plex/collection', 'emby/collection', 'jellyfin/collection'); |
||||
""");
|
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Scanner.Core.Plex; |
||||
using NUnit.Framework; |
||||
using Shouldly; |
||||
|
||||
namespace ErsatzTV.Scanner.Tests.Core.Plex; |
||||
|
||||
[TestFixture] |
||||
public class PlexTagOwnershipTests |
||||
{ |
||||
private static Tag Label(string name, string id) => |
||||
new() { Name = name, ExternalCollectionId = id, ExternalTypeId = Tag.PlexLabelTypeId }; |
||||
|
||||
private static Tag Collection(string name, string key) => |
||||
new() { Name = name, ExternalCollectionId = key, ExternalTypeId = Tag.PlexCollectionTypeId }; |
||||
|
||||
private static Tag Network(string name) => |
||||
new() { Name = name, ExternalTypeId = Tag.PlexNetworkTypeId }; |
||||
|
||||
private static Tag Country(string name) => |
||||
new() { Name = name, ExternalTypeId = Tag.NfoCountryTypeId }; |
||||
|
||||
private static Tag Untyped(string name) => new() { Name = name }; |
||||
|
||||
[Test] |
||||
public void TagsToRemove_Should_Keep_Tags_Of_Other_Scanners() |
||||
{ |
||||
List<Tag> existing = |
||||
[ |
||||
Collection("Marvel", "90210"), |
||||
Network("HBO"), |
||||
Country("USA") |
||||
]; |
||||
|
||||
PlexTagOwnership.TagsToRemove(existing, []).ShouldBeEmpty(); |
||||
} |
||||
|
||||
[Test] |
||||
public void TagsToRemove_Should_Remove_A_Label_That_Plex_No_Longer_Has() |
||||
{ |
||||
List<Tag> existing = [Label("4K Remux", "5"), Collection("Marvel", "90210")]; |
||||
|
||||
List<Tag> result = PlexTagOwnership.TagsToRemove(existing, []).ToList(); |
||||
|
||||
result.Count.ShouldBe(1); |
||||
result[0].Name.ShouldBe("4K Remux"); |
||||
} |
||||
|
||||
[Test] |
||||
public void TagsToRemove_Should_Keep_A_Label_That_Plex_Still_Has() |
||||
{ |
||||
List<Tag> existing = [Label("4K Remux", "5")]; |
||||
List<Tag> incoming = [Label("4K Remux", "5")]; |
||||
|
||||
PlexTagOwnership.TagsToRemove(existing, incoming).ShouldBeEmpty(); |
||||
} |
||||
|
||||
[Test] |
||||
public void TagsToRemove_Should_Remove_An_Untyped_Label_From_An_Older_Version() |
||||
{ |
||||
List<Tag> existing = [Untyped("4K Remux")]; |
||||
List<Tag> incoming = [Label("4K Remux", "5")]; |
||||
|
||||
List<Tag> result = PlexTagOwnership.TagsToRemove(existing, incoming).ToList(); |
||||
|
||||
result.Count.ShouldBe(1); |
||||
result[0].ExternalTypeId.ShouldBeNull(); |
||||
} |
||||
|
||||
[Test] |
||||
public void A_Renamed_Label_Should_Be_Removed_And_Added() |
||||
{ |
||||
List<Tag> existing = [Label("4K Remux", "5")]; |
||||
List<Tag> incoming = [Label("4K Remaster", "5")]; |
||||
|
||||
List<Tag> toRemove = PlexTagOwnership.TagsToRemove(existing, incoming).ToList(); |
||||
List<Tag> toAdd = PlexTagOwnership.TagsToAdd(existing, incoming).ToList(); |
||||
|
||||
toRemove.Count.ShouldBe(1); |
||||
toRemove[0].Name.ShouldBe("4K Remux"); |
||||
toAdd.Count.ShouldBe(1); |
||||
toAdd[0].Name.ShouldBe("4K Remaster"); |
||||
} |
||||
|
||||
[Test] |
||||
public void TagsToAdd_Should_Not_Repeat_A_Label_That_Exists() |
||||
{ |
||||
List<Tag> existing = [Label("4K Remux", "5"), Network("HBO")]; |
||||
List<Tag> incoming = [Label("4K Remux", "5")]; |
||||
|
||||
PlexTagOwnership.TagsToAdd(existing, incoming).ShouldBeEmpty(); |
||||
} |
||||
|
||||
[Test] |
||||
public void TagsToAdd_Should_Add_A_Label_That_Shares_A_Name_With_A_Network() |
||||
{ |
||||
List<Tag> existing = [Network("HBO")]; |
||||
List<Tag> incoming = [Label("HBO", "5")]; |
||||
|
||||
List<Tag> result = PlexTagOwnership.TagsToAdd(existing, incoming).ToList(); |
||||
|
||||
result.Count.ShouldBe(1); |
||||
result[0].ExternalTypeId.ShouldBe(Tag.PlexLabelTypeId); |
||||
} |
||||
|
||||
[Test] |
||||
public void IsSearchTag_Should_Hold_Labels_And_Collections_But_Not_Networks() |
||||
{ |
||||
Tag.IsSearchTag(Label("4K Remux", "5")).ShouldBeTrue(); |
||||
Tag.IsSearchTag(Collection("Marvel", "90210")).ShouldBeTrue(); |
||||
Tag.IsSearchTag(Untyped("local")).ShouldBeTrue(); |
||||
Tag.IsSearchTag(Network("HBO")).ShouldBeFalse(); |
||||
Tag.IsSearchTag(Country("USA")).ShouldBeFalse(); |
||||
} |
||||
} |
||||
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
|
||||
namespace ErsatzTV.Scanner.Core.Plex; |
||||
|
||||
internal static class PlexTagOwnership |
||||
{ |
||||
// an untyped row is a label that an older version wrote before labels had a type id
|
||||
public static bool IsLibraryScannerOwned(Tag tag) => |
||||
tag.ExternalTypeId == Tag.PlexLabelTypeId || |
||||
(tag.ExternalTypeId is null && tag.ExternalCollectionId is null); |
||||
|
||||
// a rename keeps the plex id, so the name is part of the identity; without the name
|
||||
// a renamed label never goes away and never comes back
|
||||
public static bool IsSameLabel(Tag left, Tag right) => |
||||
left.ExternalCollectionId == right.ExternalCollectionId && left.Name == right.Name; |
||||
|
||||
public static IEnumerable<Tag> TagsToRemove(IEnumerable<Tag> existing, IEnumerable<Tag> incoming) |
||||
{ |
||||
var incomingLabels = incoming.ToList(); |
||||
return existing |
||||
.Filter(IsLibraryScannerOwned) |
||||
.Filter(tag => incomingLabels.All(label => !IsSameLabel(tag, label))); |
||||
} |
||||
|
||||
public static IEnumerable<Tag> TagsToAdd(IEnumerable<Tag> existing, IEnumerable<Tag> incoming) |
||||
{ |
||||
var existingTags = existing.ToList(); |
||||
return incoming.Filter(label => existingTags.All(tag => !IsSameLabel(label, tag))); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue