Adding a IResourceLink interface & fixing most of the editing

This commit is contained in:
Zoe Roux 2021-01-28 22:32:10 +01:00
parent 5b9c82480e
commit 079df8a1c7
19 changed files with 294 additions and 211 deletions

View File

@ -1,3 +1,4 @@
using System;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models

View File

@ -1,3 +1,6 @@
using System;
using System.Collections.Generic;
namespace Kyoo.Models namespace Kyoo.Models
{ {
public interface IResource public interface IResource
@ -5,4 +8,59 @@ namespace Kyoo.Models
public int ID { get; set; } public int ID { get; set; }
public string Slug { get; } public string Slug { get; }
} }
public interface IResourceLink<out T, out T2>
where T : IResource
where T2 : IResource
{
public T Parent { get; }
public int ParentID { get; }
public T2 Child { get; }
public int ChildID { get; }
}
public class ResourceComparer<T> : IEqualityComparer<T> where T : IResource
{
public bool Equals(T x, T y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null))
return false;
if (ReferenceEquals(y, null))
return false;
if (x.GetType() != y.GetType())
return false;
return x.ID == y.ID || x.Slug == y.Slug;
}
public int GetHashCode(T obj)
{
return HashCode.Combine(obj.ID, obj.Slug);
}
}
public class LinkComparer<T, T2> : IEqualityComparer<IResourceLink<T, T2>>
where T : IResource
where T2 : IResource
{
public bool Equals(IResourceLink<T, T2> x, IResourceLink<T, T2> y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null))
return false;
if (ReferenceEquals(y, null))
return false;
if (x.GetType() != y.GetType())
return false;
return Utility.LinkEquals(x.Parent, x.ParentID, y.Parent, y.ParentID)
&& Utility.LinkEquals(x.Child, x.ChildID, y.Child, y.ChildID);
}
public int GetHashCode(IResourceLink<T, T2> obj)
{
return HashCode.Combine(obj.Parent, obj.ParentID, obj.Child, obj.ChildID);
}
}
} }

View File

@ -147,7 +147,7 @@ namespace Kyoo
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
&& property.PropertyType != typeof(string)) && property.PropertyType != typeof(string))
{ {
property.SetValue(first, RunGenericMethod( property.SetValue(first, RunGenericMethod<object>(
typeof(Utility), typeof(Utility),
"MergeLists", "MergeLists",
GetEnumerableType(property.PropertyType), GetEnumerableType(property.PropertyType),
@ -189,18 +189,25 @@ namespace Kyoo
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
return IsOfGenericType(obj.GetType(), genericType);
}
public static bool IsOfGenericType([NotNull] Type type, [NotNull] Type genericType)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (genericType == null) if (genericType == null)
throw new ArgumentNullException(nameof(genericType)); throw new ArgumentNullException(nameof(genericType));
if (!genericType.IsGenericType) if (!genericType.IsGenericType)
throw new ArgumentException($"{nameof(genericType)} is not a generic type."); throw new ArgumentException($"{nameof(genericType)} is not a generic type.");
IEnumerable<Type> types = genericType.IsInterface IEnumerable<Type> types = genericType.IsInterface
? obj.GetType().GetInterfaces() ? type.GetInterfaces()
: obj.GetType().GetInheritanceTree(); : type.GetInheritanceTree();
return types.Any(type => type.IsGenericType && type.GetGenericTypeDefinition() == genericType); return types.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType);
} }
public static object RunGenericMethod( public static T RunGenericMethod<T>(
[NotNull] Type owner, [NotNull] Type owner,
[NotNull] string methodName, [NotNull] string methodName,
[NotNull] Type type, [NotNull] Type type,
@ -212,13 +219,14 @@ namespace Kyoo
throw new ArgumentNullException(nameof(methodName)); throw new ArgumentNullException(nameof(methodName));
if (type == null) if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
MethodInfo method = owner.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo method = owner.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
.SingleOrDefault(x => x.Name == methodName && x.GetParameters().Length == args.Length);
if (method == null) if (method == null)
throw new NullReferenceException($"A method named {methodName} could not be found on {owner.FullName}"); throw new NullReferenceException($"A method named {methodName} with {args.Length} arguments could not be found on {owner.FullName}");
return method.MakeGenericMethod(type).Invoke(null, args?.ToArray()); return (T)method.MakeGenericMethod(type).Invoke(null, args?.ToArray());
} }
public static object RunGenericMethod( public static T RunGenericMethod<T>(
[NotNull] object instance, [NotNull] object instance,
[NotNull] string methodName, [NotNull] string methodName,
[NotNull] Type type, [NotNull] Type type,
@ -233,9 +241,10 @@ namespace Kyoo
MethodInfo method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (method == null) if (method == null)
throw new NullReferenceException($"A method named {methodName} could not be found on {instance.GetType().FullName}"); throw new NullReferenceException($"A method named {methodName} could not be found on {instance.GetType().FullName}");
return method.MakeGenericMethod(type).Invoke(instance, args?.ToArray()); return (T)method.MakeGenericMethod(type).Invoke(instance, args?.ToArray());
} }
[NotNull]
public static Type GetEnumerableType([NoEnumeration] [NotNull] IEnumerable list) public static Type GetEnumerableType([NoEnumeration] [NotNull] IEnumerable list)
{ {
if (list == null) if (list == null)
@ -334,6 +343,59 @@ namespace Kyoo
}, TaskContinuationOptions.ExecuteSynchronously); }, TaskContinuationOptions.ExecuteSynchronously);
} }
public static bool ResourceEquals([CanBeNull] object first, [CanBeNull] object second)
{
if (ReferenceEquals(first, second))
return true;
if (first is IResource f && second is IResource s)
return ResourceEquals(f, s);
IEnumerable eno = first as IEnumerable;
IEnumerable ens = second as IEnumerable;
if (eno == null || ens == null)
throw new ArgumentException("Arguments are not resources or lists of resources.");
Type type = GetEnumerableType(eno);
if (typeof(IResource).IsAssignableFrom(type))
return ResourceEquals(eno.Cast<IResource>(), ens.Cast<IResource>());
// if (IsOfGenericType(type, typeof(IResourceLink<,>)))
// return ResourceEquals(eno.Cast<IResourceLink<,>>())
return RunGenericMethod<bool>(typeof(Enumerable), "SequenceEqual", type, first, second);
}
public static bool ResourceEquals<T>([CanBeNull] T first, [CanBeNull] T second)
where T : IResource
{
if (ReferenceEquals(first, second))
return true;
if (first == null || second == null)
return false;
return first.ID == second.ID || first.Slug == second.Slug;
}
public static bool ResourceEquals<T>([CanBeNull] IEnumerable<T> first, [CanBeNull] IEnumerable<T> second)
where T : IResource
{
if (ReferenceEquals(first, second))
return true;
if (first == null || second == null)
return false;
return first.SequenceEqual(second, new ResourceComparer<T>());
}
public static bool LinkEquals<T>([CanBeNull] T first, int? firstID, [CanBeNull] T second, int? secondID)
where T : IResource
{
if (ResourceEquals(first, second))
return true;
if (first == null && second != null
&& firstID == second.ID)
return true;
if (first != null && second == null
&& first.ID == secondID)
return true;
return firstID == secondID;
}
public static Expression<T> Convert<T>([CanBeNull] this Expression expr) public static Expression<T> Convert<T>([CanBeNull] this Expression expr)
where T : Delegate where T : Delegate
{ {

View File

@ -175,7 +175,7 @@ namespace Kyoo.Controllers
if (getter.HasDefaultValue(edited)) if (getter.HasDefaultValue(edited))
continue; continue;
await navigation.LoadAsync(); await navigation.LoadAsync();
if (getter.GetClrValue(edited) != getter.GetClrValue(old)) if (Utility.ResourceEquals(getter.GetClrValue(edited), getter.GetClrValue(old)))
{ {
navigation.Metadata.PropertyInfo.SetValue(edited, default); navigation.Metadata.PropertyInfo.SetValue(edited, default);
Console.WriteLine($"Loaded: {navigation.Metadata.Name}"); Console.WriteLine($"Loaded: {navigation.Metadata.Name}");
@ -237,7 +237,7 @@ namespace Kyoo.Controllers
object value = property.GetValue(resource); object value = property.GetValue(resource);
if (value == null || value is ICollection || Utility.IsOfGenericType(value, typeof(ICollection<>))) if (value == null || value is ICollection || Utility.IsOfGenericType(value, typeof(ICollection<>)))
continue; continue;
value = Utility.RunGenericMethod(typeof(Enumerable), "ToList", Utility.GetEnumerableType((IEnumerable)value), value); value = Utility.RunGenericMethod<object>(typeof(Enumerable), "ToList", Utility.GetEnumerableType((IEnumerable)value), value);
property.SetValue(resource, value); property.SetValue(resource, value);
} }
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -71,7 +71,7 @@ namespace Kyoo.Controllers
private IQueryable<LibraryItem> ItemsQuery private IQueryable<LibraryItem> ItemsQuery
=> _database.Shows => _database.Shows
.Where(x => !_database.CollectionLinks.Any(y => y.ShowID == x.ID)) .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID))
.Select(LibraryItem.FromShow) .Select(LibraryItem.FromShow)
.Concat(_database.Collections .Concat(_database.Collections
.Select(LibraryItem.FromCollection)); .Select(LibraryItem.FromCollection));
@ -118,7 +118,7 @@ namespace Kyoo.Controllers
.Where(selector) .Where(selector)
.Select(x => x.Show) .Select(x => x.Show)
.Where(x => x != null) .Where(x => x != null)
.Where(x => !_database.CollectionLinks.Any(y => y.ShowID == x.ID)) .Where(x => !_database.CollectionLinks.Any(y => y.ChildID == x.ID))
.Select(LibraryItem.FromShow) .Select(LibraryItem.FromShow)
.Concat(_database.LibraryLinks .Concat(_database.LibraryLinks
.Where(selector) .Where(selector)

View File

@ -77,7 +77,7 @@ namespace Kyoo.Controllers
if (resource.ProviderLinks != null) if (resource.ProviderLinks != null)
foreach (ProviderLink link in resource.ProviderLinks) foreach (ProviderLink link in resource.ProviderLinks)
if (ShouldValidate(link)) if (ShouldValidate(link))
link.Provider = await _providers.CreateIfNotExists(link.Provider, true); link.Child = await _providers.CreateIfNotExists(link.Child, true);
} }
public override async Task Delete(LibraryDE obj) public override async Task Delete(LibraryDE obj)

View File

@ -90,8 +90,8 @@ namespace Kyoo.Controllers
{ {
foreach (GenreLink entry in obj.GenreLinks) foreach (GenreLink entry in obj.GenreLinks)
{ {
if (!(entry.Genre is GenreDE)) if (!(entry.Child is GenreDE))
entry.Genre = new GenreDE(entry.Genre); entry.Child = new GenreDE(entry.Child);
_database.Entry(entry).State = EntityState.Added; _database.Entry(entry).State = EntityState.Added;
} }
} }
@ -117,7 +117,7 @@ namespace Kyoo.Controllers
if (resource.GenreLinks != null) if (resource.GenreLinks != null)
foreach (GenreLink link in resource.GenreLinks) foreach (GenreLink link in resource.GenreLinks)
if (ShouldValidate(link)) if (ShouldValidate(link))
link.Genre = await _genres.CreateIfNotExists(link.Genre, true); link.Child = await _genres.CreateIfNotExists(link.Child, true);
if (resource.People != null) if (resource.People != null)
foreach (PeopleRole link in resource.People) foreach (PeopleRole link in resource.People)
@ -134,7 +134,7 @@ namespace Kyoo.Controllers
{ {
if (collectionID != null) if (collectionID != null)
{ {
await _database.CollectionLinks.AddAsync(new CollectionLink {CollectionID = collectionID, ShowID = showID}); await _database.CollectionLinks.AddAsync(new CollectionLink {ParentID = collectionID.Value, ChildID = showID});
await _database.SaveIfNoDuplicates(); await _database.SaveIfNoDuplicates();
} }
if (libraryID != null) if (libraryID != null)

View File

@ -104,6 +104,7 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Models/DatabaseMigrations/Internal" /> <Folder Include="Models\DatabaseMigrations" />
<Folder Include="Models\DatabaseMigrations\Internal" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -4,7 +4,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Kyoo.Models.Watch;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using Npgsql; using Npgsql;
@ -78,8 +77,16 @@ namespace Kyoo
.Property(t => t.IsForced) .Property(t => t.IsForced)
.ValueGeneratedNever(); .ValueGeneratedNever();
modelBuilder.Entity<GenreLink>() modelBuilder.Entity<GenreLink>()
.HasKey(x => new {x.ShowID, x.GenreID}); .HasKey(x => new {ShowID = x.ParentID, GenreID = x.ChildID});
modelBuilder.Entity<CollectionLink>()
.HasKey(x => new {CollectionID = x.ParentID, ShowID = x.ChildID});
modelBuilder.Entity<ProviderLink>()
.HasKey(x => new {LibraryID = x.ParentID, ProviderID = x.ChildID});
modelBuilder.Entity<LibraryDE>() modelBuilder.Entity<LibraryDE>()
.Ignore(x => x.Shows) .Ignore(x => x.Shows)
@ -119,25 +126,25 @@ namespace Kyoo
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<CollectionLink>() modelBuilder.Entity<CollectionLink>()
.HasOne(x => x.Collection as CollectionDE) .HasOne(x => x.Parent as CollectionDE)
.WithMany(x => x.Links) .WithMany(x => x.Links)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<CollectionLink>() modelBuilder.Entity<CollectionLink>()
.HasOne(x => x.Show as ShowDE) .HasOne(x => x.Child as ShowDE)
.WithMany(x => x.CollectionLinks) .WithMany(x => x.CollectionLinks)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<GenreLink>() modelBuilder.Entity<GenreLink>()
.HasOne(x => x.Genre as GenreDE) .HasOne(x => x.Child as GenreDE)
.WithMany(x => x.Links) .WithMany(x => x.Links)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<GenreLink>() modelBuilder.Entity<GenreLink>()
.HasOne(x => x.Show as ShowDE) .HasOne(x => x.Parent as ShowDE)
.WithMany(x => x.GenreLinks) .WithMany(x => x.GenreLinks)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ProviderLink>() modelBuilder.Entity<ProviderLink>()
.HasOne(x => x.Library as LibraryDE) .HasOne(x => x.Parent as LibraryDE)
.WithMany(x => x.ProviderLinks) .WithMany(x => x.ProviderLinks)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
@ -211,9 +218,6 @@ namespace Kyoo
modelBuilder.Entity<LibraryLink>() modelBuilder.Entity<LibraryLink>()
.HasIndex(x => new {x.LibraryID, x.CollectionID}) .HasIndex(x => new {x.LibraryID, x.CollectionID})
.IsUnique(); .IsUnique();
modelBuilder.Entity<CollectionLink>()
.HasIndex(x => new {x.CollectionID, x.ShowID})
.IsUnique();
} }
public override int SaveChanges() public override int SaveChanges()

View File

@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Kyoo.Models.DatabaseMigrations.Internal namespace Kyoo.Models.DatabaseMigrations.Internal
{ {
[DbContext(typeof(DatabaseContext))] [DbContext(typeof(DatabaseContext))]
[Migration("20200816151940_Initial")] [Migration("20210128212212_Initial")]
partial class Initial partial class Initial
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -20,7 +20,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder modelBuilder
.HasAnnotation("Npgsql:Enum:item_type", "show,movie,collection") .HasAnnotation("Npgsql:Enum:item_type", "show,movie,collection")
.HasAnnotation("Npgsql:Enum:status", "finished,airing,planned,unknown") .HasAnnotation("Npgsql:Enum:status", "finished,airing,planned,unknown")
.HasAnnotation("Npgsql:Enum:stream_type", "unknow,video,audio,subtitle") .HasAnnotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,font")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.3") .HasAnnotation("ProductVersion", "3.1.3")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
@ -57,23 +57,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.CollectionLink", b => modelBuilder.Entity("Kyoo.Models.CollectionLink", b =>
{ {
b.Property<int>("ID") b.Property<int>("ParentID")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int?>("CollectionID")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<int>("ShowID") b.Property<int>("ChildID")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("ID"); b.HasKey("ParentID", "ChildID");
b.HasIndex("ShowID"); b.HasIndex("ChildID");
b.HasIndex("CollectionID", "ShowID")
.IsUnique();
b.ToTable("CollectionLinks"); b.ToTable("CollectionLinks");
}); });
@ -154,15 +146,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.GenreLink", b => modelBuilder.Entity("Kyoo.Models.GenreLink", b =>
{ {
b.Property<int>("ShowID") b.Property<int>("ParentID")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<int>("GenreID") b.Property<int>("ChildID")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("ShowID", "GenreID"); b.HasKey("ParentID", "ChildID");
b.HasIndex("GenreID"); b.HasIndex("ChildID");
b.ToTable("GenreLinks"); b.ToTable("GenreLinks");
}); });
@ -348,22 +340,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.ProviderLink", b => modelBuilder.Entity("Kyoo.Models.ProviderLink", b =>
{ {
b.Property<int>("ID") b.Property<int>("ParentID")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int?>("LibraryID")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<int>("ProviderID") b.Property<int>("ChildID")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("ID"); b.HasKey("ParentID", "ChildID");
b.HasIndex("LibraryID"); b.HasIndex("ChildID");
b.HasIndex("ProviderID");
b.ToTable("ProviderLinks"); b.ToTable("ProviderLinks");
}); });
@ -528,14 +513,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.CollectionLink", b => modelBuilder.Entity("Kyoo.Models.CollectionLink", b =>
{ {
b.HasOne("Kyoo.Models.CollectionDE", "Collection") b.HasOne("Kyoo.Models.ShowDE", "Child")
.WithMany("Links")
.HasForeignKey("CollectionID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Kyoo.Models.ShowDE", "Show")
.WithMany("CollectionLinks") .WithMany("CollectionLinks")
.HasForeignKey("ShowID") .HasForeignKey("ChildID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Kyoo.Models.CollectionDE", "Parent")
.WithMany("Links")
.HasForeignKey("ParentID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
@ -555,15 +541,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.GenreLink", b => modelBuilder.Entity("Kyoo.Models.GenreLink", b =>
{ {
b.HasOne("Kyoo.Models.GenreDE", "Genre") b.HasOne("Kyoo.Models.GenreDE", "Child")
.WithMany("Links") .WithMany("Links")
.HasForeignKey("GenreID") .HasForeignKey("ChildID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Kyoo.Models.ShowDE", "Show") b.HasOne("Kyoo.Models.ShowDE", "Parent")
.WithMany("GenreLinks") .WithMany("GenreLinks")
.HasForeignKey("ShowID") .HasForeignKey("ParentID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
@ -633,14 +619,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.ProviderLink", b => modelBuilder.Entity("Kyoo.Models.ProviderLink", b =>
{ {
b.HasOne("Kyoo.Models.LibraryDE", "Library") b.HasOne("Kyoo.Models.ProviderID", "Child")
.WithMany("ProviderLinks")
.HasForeignKey("LibraryID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Kyoo.Models.ProviderID", "Provider")
.WithMany() .WithMany()
.HasForeignKey("ProviderID") .HasForeignKey("ChildID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Kyoo.Models.LibraryDE", "Parent")
.WithMany("ProviderLinks")
.HasForeignKey("ParentID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });

View File

@ -11,7 +11,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
migrationBuilder.AlterDatabase() migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:item_type", "show,movie,collection") .Annotation("Npgsql:Enum:item_type", "show,movie,collection")
.Annotation("Npgsql:Enum:status", "finished,airing,planned,unknown") .Annotation("Npgsql:Enum:status", "finished,airing,planned,unknown")
.Annotation("Npgsql:Enum:stream_type", "unknow,video,audio,subtitle"); .Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,font");
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "Collections", name: "Collections",
@ -106,24 +106,22 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
name: "ProviderLinks", name: "ProviderLinks",
columns: table => new columns: table => new
{ {
ID = table.Column<int>(nullable: false) ParentID = table.Column<int>(nullable: false),
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), ChildID = table.Column<int>(nullable: false)
ProviderID = table.Column<int>(nullable: false),
LibraryID = table.Column<int>(nullable: true)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_ProviderLinks", x => x.ID); table.PrimaryKey("PK_ProviderLinks", x => new { x.ParentID, x.ChildID });
table.ForeignKey( table.ForeignKey(
name: "FK_ProviderLinks_Libraries_LibraryID", name: "FK_ProviderLinks_Providers_ChildID",
column: x => x.LibraryID, column: x => x.ChildID,
principalTable: "Libraries", principalTable: "Providers",
principalColumn: "ID", principalColumn: "ID",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
table.ForeignKey( table.ForeignKey(
name: "FK_ProviderLinks_Providers_ProviderID", name: "FK_ProviderLinks_Libraries_ParentID",
column: x => x.ProviderID, column: x => x.ParentID,
principalTable: "Providers", principalTable: "Libraries",
principalColumn: "ID", principalColumn: "ID",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); });
@ -164,24 +162,22 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
name: "CollectionLinks", name: "CollectionLinks",
columns: table => new columns: table => new
{ {
ID = table.Column<int>(nullable: false) ParentID = table.Column<int>(nullable: false),
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), ChildID = table.Column<int>(nullable: false)
CollectionID = table.Column<int>(nullable: true),
ShowID = table.Column<int>(nullable: false)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_CollectionLinks", x => x.ID); table.PrimaryKey("PK_CollectionLinks", x => new { x.ParentID, x.ChildID });
table.ForeignKey( table.ForeignKey(
name: "FK_CollectionLinks_Collections_CollectionID", name: "FK_CollectionLinks_Shows_ChildID",
column: x => x.CollectionID, column: x => x.ChildID,
principalTable: "Collections", principalTable: "Shows",
principalColumn: "ID", principalColumn: "ID",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
table.ForeignKey( table.ForeignKey(
name: "FK_CollectionLinks_Shows_ShowID", name: "FK_CollectionLinks_Collections_ParentID",
column: x => x.ShowID, column: x => x.ParentID,
principalTable: "Shows", principalTable: "Collections",
principalColumn: "ID", principalColumn: "ID",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); });
@ -190,21 +186,21 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
name: "GenreLinks", name: "GenreLinks",
columns: table => new columns: table => new
{ {
ShowID = table.Column<int>(nullable: false), ParentID = table.Column<int>(nullable: false),
GenreID = table.Column<int>(nullable: false) ChildID = table.Column<int>(nullable: false)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_GenreLinks", x => new { x.ShowID, x.GenreID }); table.PrimaryKey("PK_GenreLinks", x => new { x.ParentID, x.ChildID });
table.ForeignKey( table.ForeignKey(
name: "FK_GenreLinks_Genres_GenreID", name: "FK_GenreLinks_Genres_ChildID",
column: x => x.GenreID, column: x => x.ChildID,
principalTable: "Genres", principalTable: "Genres",
principalColumn: "ID", principalColumn: "ID",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
table.ForeignKey( table.ForeignKey(
name: "FK_GenreLinks_Shows_ShowID", name: "FK_GenreLinks_Shows_ParentID",
column: x => x.ShowID, column: x => x.ParentID,
principalTable: "Shows", principalTable: "Shows",
principalColumn: "ID", principalColumn: "ID",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
@ -407,15 +403,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
}); });
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_CollectionLinks_ShowID", name: "IX_CollectionLinks_ChildID",
table: "CollectionLinks", table: "CollectionLinks",
column: "ShowID"); column: "ChildID");
migrationBuilder.CreateIndex(
name: "IX_CollectionLinks_CollectionID_ShowID",
table: "CollectionLinks",
columns: new[] { "CollectionID", "ShowID" },
unique: true);
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_Collections_Slug", name: "IX_Collections_Slug",
@ -435,9 +425,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
unique: true); unique: true);
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_GenreLinks_GenreID", name: "IX_GenreLinks_ChildID",
table: "GenreLinks", table: "GenreLinks",
column: "GenreID"); column: "ChildID");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_Genres_Slug", name: "IX_Genres_Slug",
@ -515,14 +505,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
column: "ShowID"); column: "ShowID");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_ProviderLinks_LibraryID", name: "IX_ProviderLinks_ChildID",
table: "ProviderLinks", table: "ProviderLinks",
column: "LibraryID"); column: "ChildID");
migrationBuilder.CreateIndex(
name: "IX_ProviderLinks_ProviderID",
table: "ProviderLinks",
column: "ProviderID");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_Providers_Slug", name: "IX_Providers_Slug",
@ -592,10 +577,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
name: "People"); name: "People");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "Libraries"); name: "Providers");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "Providers"); name: "Libraries");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "Episodes"); name: "Episodes");

View File

@ -18,7 +18,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder modelBuilder
.HasAnnotation("Npgsql:Enum:item_type", "show,movie,collection") .HasAnnotation("Npgsql:Enum:item_type", "show,movie,collection")
.HasAnnotation("Npgsql:Enum:status", "finished,airing,planned,unknown") .HasAnnotation("Npgsql:Enum:status", "finished,airing,planned,unknown")
.HasAnnotation("Npgsql:Enum:stream_type", "unknow,video,audio,subtitle") .HasAnnotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,font")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.3") .HasAnnotation("ProductVersion", "3.1.3")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
@ -55,23 +55,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.CollectionLink", b => modelBuilder.Entity("Kyoo.Models.CollectionLink", b =>
{ {
b.Property<int>("ID") b.Property<int>("ParentID")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int?>("CollectionID")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<int>("ShowID") b.Property<int>("ChildID")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("ID"); b.HasKey("ParentID", "ChildID");
b.HasIndex("ShowID"); b.HasIndex("ChildID");
b.HasIndex("CollectionID", "ShowID")
.IsUnique();
b.ToTable("CollectionLinks"); b.ToTable("CollectionLinks");
}); });
@ -152,15 +144,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.GenreLink", b => modelBuilder.Entity("Kyoo.Models.GenreLink", b =>
{ {
b.Property<int>("ShowID") b.Property<int>("ParentID")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<int>("GenreID") b.Property<int>("ChildID")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("ShowID", "GenreID"); b.HasKey("ParentID", "ChildID");
b.HasIndex("GenreID"); b.HasIndex("ChildID");
b.ToTable("GenreLinks"); b.ToTable("GenreLinks");
}); });
@ -346,22 +338,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.ProviderLink", b => modelBuilder.Entity("Kyoo.Models.ProviderLink", b =>
{ {
b.Property<int>("ID") b.Property<int>("ParentID")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<int?>("LibraryID")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<int>("ProviderID") b.Property<int>("ChildID")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("ID"); b.HasKey("ParentID", "ChildID");
b.HasIndex("LibraryID"); b.HasIndex("ChildID");
b.HasIndex("ProviderID");
b.ToTable("ProviderLinks"); b.ToTable("ProviderLinks");
}); });
@ -526,14 +511,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.CollectionLink", b => modelBuilder.Entity("Kyoo.Models.CollectionLink", b =>
{ {
b.HasOne("Kyoo.Models.CollectionDE", "Collection") b.HasOne("Kyoo.Models.ShowDE", "Child")
.WithMany("Links")
.HasForeignKey("CollectionID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Kyoo.Models.ShowDE", "Show")
.WithMany("CollectionLinks") .WithMany("CollectionLinks")
.HasForeignKey("ShowID") .HasForeignKey("ChildID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Kyoo.Models.CollectionDE", "Parent")
.WithMany("Links")
.HasForeignKey("ParentID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
@ -553,15 +539,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.GenreLink", b => modelBuilder.Entity("Kyoo.Models.GenreLink", b =>
{ {
b.HasOne("Kyoo.Models.GenreDE", "Genre") b.HasOne("Kyoo.Models.GenreDE", "Child")
.WithMany("Links") .WithMany("Links")
.HasForeignKey("GenreID") .HasForeignKey("ChildID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Kyoo.Models.ShowDE", "Show") b.HasOne("Kyoo.Models.ShowDE", "Parent")
.WithMany("GenreLinks") .WithMany("GenreLinks")
.HasForeignKey("ShowID") .HasForeignKey("ParentID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
@ -631,14 +617,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
modelBuilder.Entity("Kyoo.Models.ProviderLink", b => modelBuilder.Entity("Kyoo.Models.ProviderLink", b =>
{ {
b.HasOne("Kyoo.Models.LibraryDE", "Library") b.HasOne("Kyoo.Models.ProviderID", "Child")
.WithMany("ProviderLinks")
.HasForeignKey("LibraryID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Kyoo.Models.ProviderID", "Provider")
.WithMany() .WithMany()
.HasForeignKey("ProviderID") .HasForeignKey("ChildID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Kyoo.Models.LibraryDE", "Parent")
.WithMany("ProviderLinks")
.HasForeignKey("ParentID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });

View File

@ -1,21 +1,20 @@
namespace Kyoo.Models namespace Kyoo.Models
{ {
public class CollectionLink public class CollectionLink : IResourceLink<Collection, Show>
{ {
public int ID { get; set; } public int ParentID { get; set; }
public int? CollectionID { get; set; } public virtual Collection Parent { get; set; }
public virtual Collection Collection { get; set; } public int ChildID { get; set; }
public int ShowID { get; set; } public virtual Show Child { get; set; }
public virtual Show Show { get; set; }
public CollectionLink() { } public CollectionLink() { }
public CollectionLink(Collection collection, Show show) public CollectionLink(Collection parent, Show child)
{ {
Collection = collection; Parent = parent;
CollectionID = collection.ID; ParentID = parent.ID;
Show = show; Child = child;
ShowID = show.ID; ChildID = child.ID;
} }
} }
} }

View File

@ -1,18 +1,18 @@
namespace Kyoo.Models namespace Kyoo.Models
{ {
public class GenreLink public class GenreLink : IResourceLink<Show, Genre>
{ {
public int ShowID { get; set; } public int ParentID { get; set; }
public virtual Show Show { get; set; } public virtual Show Parent { get; set; }
public int GenreID { get; set; } public int ChildID { get; set; }
public virtual Genre Genre { get; set; } public virtual Genre Child { get; set; }
public GenreLink() {} public GenreLink() {}
public GenreLink(Show show, Genre genre) public GenreLink(Show parent, Genre child)
{ {
Show = show; Parent = parent;
Genre = genre; Child = child;
} }
} }
} }

View File

@ -2,20 +2,19 @@ using Newtonsoft.Json;
namespace Kyoo.Models namespace Kyoo.Models
{ {
public class ProviderLink public class ProviderLink : IResourceLink<Library, ProviderID>
{ {
[JsonIgnore] public int ID { get; set; } [JsonIgnore] public int ParentID { get; set; }
[JsonIgnore] public int ProviderID { get; set; } [JsonIgnore] public virtual Library Parent { get; set; }
[JsonIgnore] public virtual ProviderID Provider { get; set; } [JsonIgnore] public int ChildID { get; set; }
[JsonIgnore] public int? LibraryID { get; set; } [JsonIgnore] public virtual ProviderID Child { get; set; }
[JsonIgnore] public virtual Library Library { get; set; }
public ProviderLink() { } public ProviderLink() { }
public ProviderLink(ProviderID provider, Library library) public ProviderLink(ProviderID child, Library parent)
{ {
Provider = provider; Child = child;
Library = library; Parent = parent;
} }
} }
} }

View File

@ -7,16 +7,16 @@ namespace Kyoo.Models
public class CollectionDE : Collection public class CollectionDE : Collection
{ {
[JsonIgnore] [NotMergable] public virtual ICollection<CollectionLink> Links { get; set; } [JsonIgnore] [NotMergable] public virtual ICollection<CollectionLink> Links { get; set; }
[ExpressionRewrite(nameof(Links), nameof(CollectionLink.Show))] [ExpressionRewrite(nameof(Links), nameof(CollectionLink.Child))]
public override IEnumerable<Show> Shows public override IEnumerable<Show> Shows
{ {
get => Links?.Select(x => x.Show); get => Links?.Select(x => x.Child);
set => Links = value?.Select(x => new CollectionLink(this, x)).ToList(); set => Links = value?.Select(x => new CollectionLink(this, x)).ToList();
} }
[JsonIgnore] [NotMergable] public virtual ICollection<LibraryLink> LibraryLinks { get; set; } [JsonIgnore] [NotMergable] public virtual ICollection<LibraryLink> LibraryLinks { get; set; }
[ExpressionRewrite(nameof(LibraryLinks), nameof(GenreLink.Genre))] [ExpressionRewrite(nameof(LibraryLinks), nameof(GenreLink.Child))]
public override IEnumerable<Library> Libraries public override IEnumerable<Library> Libraries
{ {
get => LibraryLinks?.Select(x => x.Library); get => LibraryLinks?.Select(x => x.Library);

View File

@ -8,10 +8,10 @@ namespace Kyoo.Models
{ {
[JsonIgnore] [NotMergable] public virtual ICollection<GenreLink> Links { get; set; } [JsonIgnore] [NotMergable] public virtual ICollection<GenreLink> Links { get; set; }
[ExpressionRewrite(nameof(Links), nameof(GenreLink.Genre))] [ExpressionRewrite(nameof(Links), nameof(GenreLink.Child))]
[JsonIgnore] [NotMergable] public override IEnumerable<Show> Shows [JsonIgnore] [NotMergable] public override IEnumerable<Show> Shows
{ {
get => Links?.Select(x => x.Show); get => Links?.Select(x => x.Parent);
set => Links = value?.Select(x => new GenreLink(x, this)).ToList(); set => Links = value?.Select(x => new GenreLink(x, this)).ToList();
} }

View File

@ -7,10 +7,10 @@ namespace Kyoo.Models
public class LibraryDE : Library public class LibraryDE : Library
{ {
[EditableRelation] [JsonIgnore] [NotMergable] public virtual ICollection<ProviderLink> ProviderLinks { get; set; } [EditableRelation] [JsonIgnore] [NotMergable] public virtual ICollection<ProviderLink> ProviderLinks { get; set; }
[ExpressionRewrite(nameof(ProviderLinks), nameof(ProviderLink.Provider))] [ExpressionRewrite(nameof(ProviderLinks), nameof(ProviderLink.Child))]
public override IEnumerable<ProviderID> Providers public override IEnumerable<ProviderID> Providers
{ {
get => ProviderLinks?.Select(x => x.Provider); get => ProviderLinks?.Select(x => x.Child);
set => ProviderLinks = value?.Select(x => new ProviderLink(x, this)).ToList(); set => ProviderLinks = value?.Select(x => new ProviderLink(x, this)).ToList();
} }

View File

@ -7,10 +7,10 @@ namespace Kyoo.Models
public class ShowDE : Show public class ShowDE : Show
{ {
[EditableRelation] [JsonReadOnly] [NotMergable] public virtual ICollection<GenreLink> GenreLinks { get; set; } [EditableRelation] [JsonReadOnly] [NotMergable] public virtual ICollection<GenreLink> GenreLinks { get; set; }
[ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Genre))] [ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Child))]
public override IEnumerable<Genre> Genres public override IEnumerable<Genre> Genres
{ {
get => GenreLinks?.Select(x => x.Genre); get => GenreLinks?.Select(x => x.Child);
set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList(); set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList();
} }
@ -23,10 +23,10 @@ namespace Kyoo.Models
} }
[JsonReadOnly] [NotMergable] public virtual ICollection<CollectionLink> CollectionLinks { get; set; } [JsonReadOnly] [NotMergable] public virtual ICollection<CollectionLink> CollectionLinks { get; set; }
[ExpressionRewrite(nameof(CollectionLinks), nameof(CollectionLink.Collection))] [ExpressionRewrite(nameof(CollectionLinks), nameof(CollectionLink.Parent))]
public override IEnumerable<Collection> Collections public override IEnumerable<Collection> Collections
{ {
get => CollectionLinks?.Select(x => x.Collection); get => CollectionLinks?.Select(x => x.Parent);
set => CollectionLinks = value?.Select(x => new CollectionLink(x, this)).ToList(); set => CollectionLinks = value?.Select(x => new CollectionLink(x, this)).ToList();
} }