diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index e2659bdd..b78150f3 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -66,6 +66,11 @@ namespace Kyoo.Controllers _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") }; } + + public Sort To() + { + return new Sort(Key.Convert>(), Descendant); + } } public interface IRepository : IDisposable, IAsyncDisposable where T : IResource diff --git a/Kyoo.Common/Models/Exceptions/DuplicatedItemException.cs b/Kyoo.Common/Models/Exceptions/DuplicatedItemException.cs index 6b04c8b2..19be8a04 100644 --- a/Kyoo.Common/Models/Exceptions/DuplicatedItemException.cs +++ b/Kyoo.Common/Models/Exceptions/DuplicatedItemException.cs @@ -6,7 +6,10 @@ namespace Kyoo.Models.Exceptions { public override string Message { get; } - public DuplicatedItemException() {} + public DuplicatedItemException() + { + Message = "Already exists in the databse."; + } public DuplicatedItemException(string message) { diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 07bae5bc..bfe02e7b 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -1,31 +1,17 @@ using Newtonsoft.Json; using System.Collections.Generic; -using System.Linq; -using Kyoo.Models.Attributes; namespace Kyoo.Models { public class Collection : IResource { - [JsonIgnore] public int ID { get; set; } + public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } public string Poster { get; set; } public string Overview { get; set; } - [NotMergable] [JsonIgnore] public virtual IEnumerable Links { get; set; } - [JsonIgnore] public virtual IEnumerable Shows - { - get => Links.Select(x => x.Show); - set => Links = value.Select(x => new CollectionLink(this, x)); - } - - [NotMergable] [JsonIgnore] public virtual IEnumerable LibraryLinks { get; set; } - - [NotMergable] [JsonIgnore] public IEnumerable Libraries - { - get => LibraryLinks?.Select(x => x.Library); - set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)); - } + [JsonIgnore] public virtual IEnumerable Shows { get; set; } + [JsonIgnore] public virtual IEnumerable Libraries { get; set; } public Collection() { } diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index a512ed75..250ee4db 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -7,10 +7,10 @@ namespace Kyoo.Models { public class Episode : IResource, IOnMerge { - [JsonIgnore] public int ID { get; set; } - [JsonIgnore] public int ShowID { get; set; } + public int ID { get; set; } + public int ShowID { get; set; } [JsonIgnore] public virtual Show Show { get; set; } - [JsonIgnore] public int? SeasonID { get; set; } + public int? SeasonID { get; set; } [JsonIgnore] public virtual Season Season { get; set; } public int SeasonNumber { get; set; } = -1; @@ -23,12 +23,12 @@ namespace Kyoo.Models public int Runtime { get; set; } //This runtime variable should be in minutes - [JsonIgnore] public string ImgPrimary { get; set; } + [JsonIgnore] public string Poster { get; set; } public virtual IEnumerable ExternalIDs { get; set; } [JsonIgnore] public virtual IEnumerable Tracks { get; set; } - public string ShowTitle => Show.Title; // Used in the API response only + public string ShowTitle => Show.Title; public string Slug => GetSlug(Show.Slug, SeasonNumber, EpisodeNumber); public string Thumb { @@ -36,7 +36,7 @@ namespace Kyoo.Models { if (Show != null) return "thumb/" + Slug; - return ImgPrimary; + return Poster; } } @@ -50,7 +50,7 @@ namespace Kyoo.Models string overview, DateTime? releaseDate, int runtime, - string imgPrimary, + string poster, IEnumerable externalIDs) { SeasonNumber = seasonNumber; @@ -60,7 +60,7 @@ namespace Kyoo.Models Overview = overview; ReleaseDate = releaseDate; Runtime = runtime; - ImgPrimary = imgPrimary; + Poster = poster; ExternalIDs = externalIDs; } @@ -74,7 +74,7 @@ namespace Kyoo.Models string overview, DateTime? releaseDate, int runtime, - string imgPrimary, + string poster, IEnumerable externalIDs) { ShowID = showID; @@ -87,7 +87,7 @@ namespace Kyoo.Models Overview = overview; ReleaseDate = releaseDate; Runtime = runtime; - ImgPrimary = imgPrimary; + Poster = poster; ExternalIDs = externalIDs; } diff --git a/Kyoo.Common/Models/Resources/Genre.cs b/Kyoo.Common/Models/Resources/Genre.cs index 2fe7c3a3..3b34abb0 100644 --- a/Kyoo.Common/Models/Resources/Genre.cs +++ b/Kyoo.Common/Models/Resources/Genre.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.Linq; -using Kyoo.Models.Attributes; using Newtonsoft.Json; namespace Kyoo.Models @@ -11,13 +9,7 @@ namespace Kyoo.Models public string Slug { get; set; } public string Name { get; set; } - [NotMergable] [JsonIgnore] public virtual IEnumerable Links { get; set; } - - [NotMergable] [JsonIgnore] public IEnumerable Shows - { - get => Links.Select(x => x.Show); - set => Links = value?.Select(x => new GenreLink(x, this)); - } + [JsonIgnore] public virtual IEnumerable Shows { get; set; } public Genre() {} diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index 11526f43..80f8d0ef 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.Linq; -using Kyoo.Models.Attributes; using Newtonsoft.Json; namespace Kyoo.Models @@ -12,28 +10,10 @@ namespace Kyoo.Models public string Name { get; set; } public IEnumerable Paths { get; set; } - public IEnumerable Providers - { - get => ProviderLinks?.Select(x => x.Provider); - set => ProviderLinks = value.Select(x => new ProviderLink(x, this)).ToList(); - } - [NotMergable] [JsonIgnore] public virtual IEnumerable ProviderLinks { get; set; } - [NotMergable] [JsonIgnore] public virtual IEnumerable Links { get; set; } + public virtual IEnumerable Providers { get; set; } - [JsonIgnore] public IEnumerable Shows - { - get => Links?.Where(x => x.Show != null).Select(x => x.Show); - set => Links = Utility.MergeLists( - value?.Select(x => new LibraryLink(this, x)), - Links?.Where(x => x.Show == null)); - } - [JsonIgnore] public IEnumerable Collections - { - get => Links?.Where(x => x.Collection != null).Select(x => x.Collection); - set => Links = Utility.MergeLists( - value?.Select(x => new LibraryLink(this, x)), - Links?.Where(x => x.Collection == null)); - } + [JsonIgnore] public virtual IEnumerable Shows { get; set; } + [JsonIgnore] public virtual IEnumerable Collections { get; set; } public Library() { } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 8e910814..1f519c38 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -7,8 +7,7 @@ namespace Kyoo.Models { public class Show : IResource, IOnMerge { - [JsonIgnore] public int ID { get; set; } - + public int ID { get; set; } public string Slug { get; set; } public string Title { get; set; } public IEnumerable Aliases { get; set; } @@ -24,38 +23,19 @@ namespace Kyoo.Models public string Logo { get; set; } public string Backdrop { get; set; } - public virtual IEnumerable ExternalIDs { get; set; } - public bool IsMovie { get; set; } + + public virtual IEnumerable ExternalIDs { get; set; } - public virtual IEnumerable Genres - { - get => GenreLinks?.Select(x => x.Genre); - set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList(); - } - [NotMergable] [JsonIgnore] public virtual IEnumerable GenreLinks { get; set; } [JsonIgnore] public int? StudioID { get; set; } - public virtual Studio Studio { get; set; } + [JsonIgnore] public virtual Studio Studio { get; set; } + [JsonIgnore] public virtual IEnumerable Genres { get; set; } [JsonIgnore] public virtual IEnumerable People { get; set; } [JsonIgnore] public virtual IEnumerable Seasons { get; set; } [JsonIgnore] public virtual IEnumerable Episodes { get; set; } - - [NotMergable] [JsonIgnore] public virtual IEnumerable LibraryLinks { get; set; } - - [NotMergable] [JsonIgnore] public IEnumerable Libraries - { - get => LibraryLinks?.Select(x => x.Library); - set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)); - } - - [NotMergable] [JsonIgnore] public virtual IEnumerable CollectionLinks { get; set; } - - [NotMergable] [JsonIgnore] public IEnumerable Collections - { - get => CollectionLinks?.Select(x => x.Collection); - set => CollectionLinks = value?.Select(x => new CollectionLink(x, this)); - } + [JsonIgnore] public virtual IEnumerable Libraries { get; set; } + [JsonIgnore] public virtual IEnumerable Collections { get; set; } public Show() { } @@ -117,14 +97,11 @@ namespace Kyoo.Models return ExternalIDs?.FirstOrDefault(x => x.Provider.Name == provider)?.DataID; } - public void OnMerge(object merged) + public virtual void OnMerge(object merged) { if (ExternalIDs != null) foreach (MetadataID id in ExternalIDs) id.Show = this; - if (GenreLinks != null) - foreach (GenreLink genre in GenreLinks) - genre.Show = this; if (People != null) foreach (PeopleRole link in People) link.Show = this; diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 1e8b79cf..fb03491a 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -3,9 +3,11 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.Models; using Kyoo.Models.Attributes; @@ -71,6 +73,21 @@ namespace Kyoo return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToList(); } + public static T Assign(T first, T second) + { + Type type = typeof(T); + foreach (PropertyInfo property in type.GetProperties()) + { + if (!property.CanRead || !property.CanWrite) + continue; + + object value = property.GetValue(second); + property.SetValue(first, value); + } + + return first; + } + public static T Complete(T first, T second) { Type type = typeof(T); @@ -149,7 +166,7 @@ namespace Kyoo [NotNull] Type owner, [NotNull] string methodName, [NotNull] Type type, - IEnumerable args) + params object[] args) { if (owner == null) throw new ArgumentNullException(nameof(owner)); @@ -157,7 +174,7 @@ namespace Kyoo throw new ArgumentNullException(nameof(methodName)); if (type == null) throw new ArgumentNullException(nameof(type)); - MethodInfo method = owner.GetMethod(methodName); + MethodInfo method = owner.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) throw new NullReferenceException($"A method named {methodName} could not be found on {owner.FullName}"); return method.MakeGenericMethod(type).Invoke(null, args?.ToArray()); @@ -167,7 +184,7 @@ namespace Kyoo [NotNull] object instance, [NotNull] string methodName, [NotNull] Type type, - IEnumerable args) + params object[] args) { if (instance == null) throw new ArgumentNullException(nameof(instance)); @@ -175,7 +192,7 @@ namespace Kyoo throw new ArgumentNullException(nameof(methodName)); if (type == null) throw new ArgumentNullException(nameof(type)); - MethodInfo method = instance.GetType().GetMethod(methodName); + MethodInfo method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) throw new NullReferenceException($"A method named {methodName} could not be found on {instance.GetType().FullName}"); return method.MakeGenericMethod(type).Invoke(instance, args?.ToArray()); @@ -230,5 +247,66 @@ namespace Kyoo return string.Empty; return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}")); } + + public static Task Cast(this Task task) + { + return (Task)task; + } + + public static Expression Convert(this Expression expr) + where T : Delegate + { + if (expr is LambdaExpression lambda) + return new ExpressionConverter(lambda).VisitAndConvert(); + throw new ArgumentException("Can't convert a non lambda."); + } + + private class ExpressionConverter : ExpressionVisitor + where TTo : Delegate + { + private readonly LambdaExpression _expression; + private readonly ParameterExpression[] _newParams; + + internal ExpressionConverter(LambdaExpression expression) + { + _expression = expression; + + Type[] paramTypes = typeof(TTo).GetGenericArguments()[..^1]; + if (paramTypes.Length != _expression.Parameters.Count) + throw new ArgumentException("Parameter count from internal and external lambda are not matched."); + + _newParams = new ParameterExpression[paramTypes.Length]; + for (int i = 0; i < paramTypes.Length; i++) + { + if (_expression.Parameters[i].Type == paramTypes[i]) + _newParams[i] = _expression.Parameters[i]; + else + _newParams[i] = Expression.Parameter(paramTypes[i], _expression.Parameters[i].Name); + } + } + + internal Expression VisitAndConvert() + { + return (Expression)RunGenericMethod( + this, + "VisitLambda", + _expression.GetType().GetGenericArguments().First(), + _expression); + } + + protected override Expression VisitLambda(Expression node) + { + Type returnType = _expression.Type.GetGenericArguments().Last(); + Expression body = node.ReturnType == returnType + ? Visit(node.Body) + : Expression.Convert(Visit(node.Body)!, returnType); + return Expression.Lambda(body!, _newParams); + } + + protected override Expression VisitParameter(ParameterExpression node) + { + return _newParams.First(x => x.Name == node.Name); + } + } } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index d7b8deda..5417ced1 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -9,15 +9,16 @@ using Kyoo.CommonApi; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; -using Npgsql; namespace Kyoo.Controllers { - public abstract class LocalRepository : IRepository where T : class, IResource + public abstract class LocalRepository : IRepository + where T : class, IResource + where TInternal : class, T { private readonly DbContext _database; - protected abstract Expression> DefaultSort { get; } + protected abstract Expression> DefaultSort { get; } protected LocalRepository(DbContext database) @@ -35,31 +36,57 @@ namespace Kyoo.Controllers return _database.DisposeAsync(); } - public virtual Task Get(int id) + public Task Get(int id) { - return _database.Set().FirstOrDefaultAsync(x => x.ID == id); + return _Get(id).Cast(); + } + + public Task Get(string slug) + { + return _Get(slug).Cast(); + } + + protected virtual Task _Get(int id) + { + return _database.Set().FirstOrDefaultAsync(x => x.ID == id); } - public virtual Task Get(string slug) + protected virtual Task _Get(string slug) { - return _database.Set().FirstOrDefaultAsync(x => x.Slug == slug); + return _database.Set().FirstOrDefaultAsync(x => x.Slug == slug); } public abstract Task> Search(string query); - + public virtual Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) { - return ApplyFilters(_database.Set(), where, sort, limit); + return ApplyFilters(_database.Set(), where, sort, limit); } - - protected Task> ApplyFilters(IQueryable query, + + protected async Task> ApplyFilters(IQueryable query, Expression> where = null, Sort sort = default, Pagination limit = default) { - return ApplyFilters(query, Get, DefaultSort, where, sort, limit); + ICollection items = await ApplyFilters(query, + _Get, + DefaultSort, + where.Convert>(), + sort.To(), + limit); + + return items.ToList(); + } + + protected async Task> ApplyFilters(IQueryable query, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection items = await ApplyFilters(query, _Get, DefaultSort, where, sort, limit); + return items.ToList(); } protected async Task> ApplyFilters(IQueryable query, @@ -125,7 +152,7 @@ namespace Kyoo.Controllers if (edited == null) throw new ArgumentNullException(nameof(edited)); - T old = await Get(edited.Slug); + TInternal old = (TInternal)await Get(edited.Slug); if (old == null) throw new ItemNotFound($"No ressource found with the slug {edited.Slug}."); @@ -138,9 +165,9 @@ namespace Kyoo.Controllers return old; } - protected virtual Task Validate(T ressource) + protected virtual Task Validate(TInternal ressource) { - foreach (PropertyInfo property in typeof(T).GetProperties() + foreach (PropertyInfo property in typeof(TInternal).GetProperties() .Where(x => typeof(IEnumerable).IsAssignableFrom(x.PropertyType) && !typeof(string).IsAssignableFrom(x.PropertyType))) { diff --git a/Kyoo/Controllers/ProviderManager.cs b/Kyoo/Controllers/ProviderManager.cs index 1dcef733..346f71be 100644 --- a/Kyoo/Controllers/ProviderManager.cs +++ b/Kyoo/Controllers/ProviderManager.cs @@ -93,7 +93,7 @@ namespace Kyoo.Controllers show.Slug = Utility.ToSlug(showName); show.Title ??= showName; show.IsMovie = isMovie; - show.GenreLinks = show.GenreLinks?.GroupBy(x => x.Genre.Slug).Select(x => x.First()).ToList(); + show.Genres = show.Genres?.GroupBy(x => x.Slug).Select(x => x.First()).ToList(); show.People = show.People?.GroupBy(x => x.Slug).Select(x => x.First()).ToList(); return show; } diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 8e69ab35..844b848d 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -10,12 +10,12 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { - public class CollectionRepository : LocalRepository, ICollectionRepository + public class CollectionRepository : LocalRepository, ICollectionRepository { private readonly DatabaseContext _database; private readonly Lazy _shows; private readonly Lazy _libraries; - protected override Expression> DefaultSort => x => x.Name; + protected override Expression> DefaultSort => x => x.Name; public CollectionRepository(DatabaseContext database, IServiceProvider services) : base(database) { @@ -47,7 +47,7 @@ namespace Kyoo.Controllers return await _database.Collections .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Take(20) - .ToListAsync(); + .ToListAsync(); } public override async Task Create(Collection obj) @@ -60,10 +60,11 @@ namespace Kyoo.Controllers return obj; } - public override async Task Delete(Collection obj) + public override async Task Delete(Collection item) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); + if (item == null) + throw new ArgumentNullException(nameof(item)); + CollectionDE obj = new CollectionDE(item); _database.Entry(obj).State = EntityState.Deleted; if (obj.Links != null) @@ -82,7 +83,7 @@ namespace Kyoo.Controllers { ICollection collections = await ApplyFilters(_database.CollectionLinks .Where(x => x.ShowID == showID) - .Select(x => x.Collection), + .Select(x => x.Collection as CollectionDE), where, sort, limit); @@ -98,7 +99,7 @@ namespace Kyoo.Controllers { ICollection collections = await ApplyFilters(_database.CollectionLinks .Where(x => x.Show.Slug == showSlug) - .Select(x => x.Collection), + .Select(x => x.Collection as CollectionDE), where, sort, limit); @@ -114,7 +115,7 @@ namespace Kyoo.Controllers { ICollection collections = await ApplyFilters(_database.LibraryLinks .Where(x => x.LibraryID == id && x.CollectionID != null) - .Select(x => x.Collection), + .Select(x => x.Collection as CollectionDE), where, sort, limit); @@ -130,7 +131,7 @@ namespace Kyoo.Controllers { ICollection collections = await ApplyFilters(_database.LibraryLinks .Where(x => x.Library.Slug == slug && x.CollectionID != null) - .Select(x => x.Collection), + .Select(x => x.Collection as CollectionDE), where, sort, limit); diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 089c8b93..9a1bebde 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class EpisodeRepository : LocalRepository, IEpisodeRepository + public class EpisodeRepository : LocalRepository, IEpisodeRepository { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; @@ -133,7 +133,7 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - ICollection episodes = await ApplyFilters(_database.Episodes.Where(x => x.ShowID == showID), + ICollection episodes = await ApplyFilters(_database.Episodes.Where(x => x.ShowID == showID), where, sort, limit); diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 65f83038..d9a7c937 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -10,12 +10,12 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { - public class LibraryRepository : LocalRepository, ILibraryRepository + public class LibraryRepository : LocalRepository, ILibraryRepository { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; private readonly Lazy _shows; - protected override Expression> DefaultSort => x => x.ID; + protected override Expression> DefaultSort => x => x.ID; public LibraryRepository(DatabaseContext database, IProviderRepository providers, IServiceProvider services) @@ -47,13 +47,14 @@ namespace Kyoo.Controllers return await _database.Libraries .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Take(20) - .ToListAsync(); + .ToListAsync(); } - public override async Task Create(Library obj) + public override async Task Create(Library item) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); + if (item == null) + throw new ArgumentNullException(nameof(item)); + LibraryDE obj = new LibraryDE(item); await Validate(obj); _database.Entry(obj).State = EntityState.Added; @@ -65,7 +66,7 @@ namespace Kyoo.Controllers return obj; } - protected override async Task Validate(Library obj) + protected override async Task Validate(LibraryDE obj) { if (string.IsNullOrEmpty(obj.Slug)) throw new ArgumentException("The library's slug must be set and not empty"); @@ -81,10 +82,11 @@ namespace Kyoo.Controllers link.Provider = await _providers.CreateIfNotExists(link.Provider); } - public override async Task Delete(Library obj) + public override async Task Delete(Library item) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); + if (item == null) + throw new ArgumentNullException(nameof(item)); + LibraryDE obj = new LibraryDE(item); _database.Entry(obj).State = EntityState.Deleted; if (obj.ProviderLinks != null) @@ -103,7 +105,7 @@ namespace Kyoo.Controllers { ICollection libraries = await ApplyFilters(_database.LibraryLinks .Where(x => x.ShowID == showID) - .Select(x => x.Library), + .Select(x => x.Library as LibraryDE), where, sort, limit); @@ -119,7 +121,7 @@ namespace Kyoo.Controllers { ICollection libraries = await ApplyFilters(_database.LibraryLinks .Where(x => x.Show.Slug == showSlug) - .Select(x => x.Library), + .Select(x => x.Library as LibraryDE), where, sort, limit); @@ -135,7 +137,7 @@ namespace Kyoo.Controllers { ICollection libraries = await ApplyFilters(_database.LibraryLinks .Where(x => x.CollectionID == id) - .Select(x => x.Library), + .Select(x => x.Library as LibraryDE), where, sort, limit); @@ -151,7 +153,7 @@ namespace Kyoo.Controllers { ICollection libraries = await ApplyFilters(_database.LibraryLinks .Where(x => x.Collection.Slug == slug) - .Select(x => x.Library), + .Select(x => x.Library as LibraryDE), where, sort, limit); diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 6f01d2cb..fe0994b9 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { - public class ShowRepository : LocalRepository, IShowRepository + public class ShowRepository : LocalRepository, IShowRepository { private readonly DatabaseContext _database; private readonly IStudioRepository _studios; @@ -21,7 +21,7 @@ namespace Kyoo.Controllers private readonly Lazy _episodes; private readonly Lazy _libraries; private readonly Lazy _collections; - protected override Expression> DefaultSort => x => x.Title; + protected override Expression> DefaultSort => x => x.Title; public ShowRepository(DatabaseContext database, IStudioRepository studios, @@ -83,13 +83,14 @@ namespace Kyoo.Controllers .Where(x => EF.Functions.ILike(x.Title, query) /*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE. .Take(20) - .ToListAsync(); + .ToListAsync(); } - public override async Task Create(Show obj) + public override async Task Create(Show item) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); + if (item == null) + throw new ArgumentNullException(nameof(item)); + ShowDE obj = new ShowDE(item); await Validate(obj); _database.Entry(obj).State = EntityState.Added; @@ -107,7 +108,7 @@ namespace Kyoo.Controllers return obj; } - protected override async Task Validate(Show obj) + protected override async Task Validate(ShowDE obj) { await base.Validate(obj); @@ -147,10 +148,11 @@ namespace Kyoo.Controllers } } - public override async Task Delete(Show obj) + public override async Task Delete(Show item) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); + if (item == null) + throw new ArgumentNullException(nameof(item)); + ShowDE obj = new ShowDE(item); _database.Entry(obj).State = EntityState.Deleted; @@ -190,7 +192,7 @@ namespace Kyoo.Controllers { ICollection shows = await ApplyFilters(_database.LibraryLinks .Where(x => x.LibraryID == id && x.ShowID != null) - .Select(x => x.Show), + .Select(x => x.Show as ShowDE), where, sort, limit); @@ -206,7 +208,7 @@ namespace Kyoo.Controllers { ICollection shows = await ApplyFilters(_database.LibraryLinks .Where(x => x.Library.Slug == slug && x.ShowID != null) - .Select(x => x.Show), + .Select(x => x.Show as ShowDE), where, sort, limit); @@ -222,7 +224,7 @@ namespace Kyoo.Controllers { ICollection shows = await ApplyFilters(_database.CollectionLinks .Where(x => x.CollectionID== id) - .Select(x => x.Show), + .Select(x => x.Show as ShowDE), where, sort, limit); @@ -238,7 +240,7 @@ namespace Kyoo.Controllers { ICollection shows = await ApplyFilters(_database.CollectionLinks .Where(x => x.Collection.Slug == slug) - .Select(x => x.Show), + .Select(x => x.Show as ShowDE), where, sort, limit); @@ -249,12 +251,16 @@ namespace Kyoo.Controllers public Task GetFromSeason(int seasonID) { - return _database.Shows.FirstOrDefaultAsync(x => x.Seasons.Any(y => y.ID == seasonID)); + return _database.Shows + .FirstOrDefaultAsync(x => x.Seasons.Any(y => y.ID == seasonID)) + .Cast(); } public Task GetFromEpisode(int episodeID) { - return _database.Shows.FirstOrDefaultAsync(x => x.Episodes.Any(y => y.ID == episodeID)); + return _database.Shows + .FirstOrDefaultAsync(x => x.Episodes.Any(y => y.ID == episodeID)) + .Cast(); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index cb457467..04520d04 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -96,11 +96,11 @@ namespace Kyoo.Controllers if (episode?.Path == null) return default; - if (episode.ImgPrimary != null) + if (episode.Poster != null) { string localPath = Path.ChangeExtension(episode.Path, "jpg"); if (alwaysDownload || !File.Exists(localPath)) - await DownloadImage(episode.ImgPrimary, localPath, $"The thumbnail of {episode.Show.Title}"); + await DownloadImage(episode.Poster, localPath, $"The thumbnail of {episode.Show.Title}"); } return episode; } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 883ac77d..90435111 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -1,69 +1,27 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; -using IdentityServer4.EntityFramework.Entities; -using IdentityServer4.EntityFramework.Extensions; -using IdentityServer4.EntityFramework.Interfaces; -using IdentityServer4.EntityFramework.Options; using Kyoo.Models; using Kyoo.Models.Exceptions; using Kyoo.Models.Watch; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.Extensions.Options; using Npgsql; namespace Kyoo { - public class IdentityDatabase : IdentityDbContext, IPersistedGrantDbContext - { - private readonly IOptions _operationalStoreOptions; - - public IdentityDatabase(DbContextOptions options, IOptions operationalStoreOptions) - : base(options) - { - _operationalStoreOptions = operationalStoreOptions; - } - - public DbSet Accounts { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value); - - modelBuilder.Entity().ToTable("User"); - modelBuilder.Entity>().ToTable("UserRole"); - modelBuilder.Entity>().ToTable("UserLogin"); - modelBuilder.Entity>().ToTable("UserClaim"); - modelBuilder.Entity().ToTable("UserRoles"); - modelBuilder.Entity>().ToTable("UserRoleClaim"); - modelBuilder.Entity>().ToTable("UserToken"); - } - - public Task SaveChangesAsync() => base.SaveChangesAsync(); - - public DbSet PersistedGrants { get; set; } - public DbSet DeviceFlowCodes { get; set; } - - } - public class DatabaseContext : DbContext { public DatabaseContext(DbContextOptions options) : base(options) { } - public DbSet Libraries { get; set; } - public DbSet Collections { get; set; } - public DbSet Shows { get; set; } + public DbSet Libraries { get; set; } + public DbSet Collections { get; set; } + public DbSet Shows { get; set; } public DbSet Seasons { get; set; } public DbSet Episodes { get; set; } public DbSet Tracks { get; set; } - public DbSet Genres { get; set; } + public DbSet Genres { get; set; } public DbSet People { get; set; } public DbSet Studios { get; set; } public DbSet Providers { get; set; } @@ -72,7 +30,6 @@ namespace Kyoo public DbSet PeopleRoles { get; set; } - // This is used because EF doesn't support Many-To-Many relationships so for now we need to override the getter/setters to store this. public DbSet LibraryLinks { get; set; } public DbSet CollectionLinks { get; set; } public DbSet GenreLinks { get; set; } diff --git a/Kyoo/Models/IdentityDatabase.cs b/Kyoo/Models/IdentityDatabase.cs new file mode 100644 index 00000000..99cc5384 --- /dev/null +++ b/Kyoo/Models/IdentityDatabase.cs @@ -0,0 +1,46 @@ +using System.Threading.Tasks; +using IdentityServer4.EntityFramework.Entities; +using IdentityServer4.EntityFramework.Extensions; +using IdentityServer4.EntityFramework.Interfaces; +using IdentityServer4.EntityFramework.Options; +using Kyoo.Models; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace Kyoo +{ + public class IdentityDatabase : IdentityDbContext, IPersistedGrantDbContext + { + private readonly IOptions _operationalStoreOptions; + + public IdentityDatabase(DbContextOptions options, IOptions operationalStoreOptions) + : base(options) + { + _operationalStoreOptions = operationalStoreOptions; + } + + public DbSet Accounts { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value); + + modelBuilder.Entity().ToTable("User"); + modelBuilder.Entity>().ToTable("UserRole"); + modelBuilder.Entity>().ToTable("UserLogin"); + modelBuilder.Entity>().ToTable("UserClaim"); + modelBuilder.Entity().ToTable("UserRoles"); + modelBuilder.Entity>().ToTable("UserRoleClaim"); + modelBuilder.Entity>().ToTable("UserToken"); + } + + public Task SaveChangesAsync() => base.SaveChangesAsync(); + + public DbSet PersistedGrants { get; set; } + public DbSet DeviceFlowCodes { get; set; } + + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/CollectionLink.cs b/Kyoo/Models/Links/CollectionLink.cs similarity index 100% rename from Kyoo.Common/Models/CollectionLink.cs rename to Kyoo/Models/Links/CollectionLink.cs diff --git a/Kyoo.Common/Models/GenreLink.cs b/Kyoo/Models/Links/GenreLink.cs similarity index 100% rename from Kyoo.Common/Models/GenreLink.cs rename to Kyoo/Models/Links/GenreLink.cs diff --git a/Kyoo.Common/Models/LibraryLink.cs b/Kyoo/Models/Links/LibraryLink.cs similarity index 100% rename from Kyoo.Common/Models/LibraryLink.cs rename to Kyoo/Models/Links/LibraryLink.cs diff --git a/Kyoo/Models/Resources/CollectionDE.cs b/Kyoo/Models/Resources/CollectionDE.cs new file mode 100644 index 00000000..e54bd0ce --- /dev/null +++ b/Kyoo/Models/Resources/CollectionDE.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models.Attributes; + +namespace Kyoo.Models +{ + public class CollectionDE : Collection + { + [NotMergable] public virtual IEnumerable Links { get; set; } + public override IEnumerable Shows + { + get => Links.Select(x => x.Show); + set => Links = value.Select(x => new CollectionLink(this, x)); + } + + [NotMergable] public virtual IEnumerable LibraryLinks { get; set; } + public override IEnumerable Libraries + { + get => LibraryLinks?.Select(x => x.Library); + set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)); + } + + public CollectionDE() {} + + public CollectionDE(Collection collection) + { + Utility.Assign(this, collection); + } + } +} \ No newline at end of file diff --git a/Kyoo/Models/Resources/GenreDE.cs b/Kyoo/Models/Resources/GenreDE.cs new file mode 100644 index 00000000..834c69cc --- /dev/null +++ b/Kyoo/Models/Resources/GenreDE.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models.Attributes; + +namespace Kyoo.Models +{ + public class GenreDE : Genre + { + [NotMergable] public virtual IEnumerable Links { get; set; } + + [NotMergable] public override IEnumerable Shows + { + get => Links.Select(x => x.Show); + set => Links = value?.Select(x => new GenreLink(x, this)); + } + + public GenreDE() {} + + public GenreDE(Genre item) + { + Utility.Assign(this, item); + } + } +} \ No newline at end of file diff --git a/Kyoo/Models/Resources/LibraryDE.cs b/Kyoo/Models/Resources/LibraryDE.cs new file mode 100644 index 00000000..b0e629ce --- /dev/null +++ b/Kyoo/Models/Resources/LibraryDE.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models.Attributes; + +namespace Kyoo.Models +{ + public class LibraryDE : Library + { + [NotMergable] public virtual IEnumerable ProviderLinks { get; set; } + public override IEnumerable Providers + { + get => ProviderLinks?.Select(x => x.Provider); + set => ProviderLinks = value.Select(x => new ProviderLink(x, this)).ToList(); + } + + [NotMergable] public virtual IEnumerable Links { get; set; } + public override IEnumerable Shows + { + get => Links?.Where(x => x.Show != null).Select(x => x.Show); + set => Links = Utility.MergeLists( + value?.Select(x => new LibraryLink(this, x)), + Links?.Where(x => x.Show == null)); + } + public override IEnumerable Collections + { + get => Links?.Where(x => x.Collection != null).Select(x => x.Collection); + set => Links = Utility.MergeLists( + value?.Select(x => new LibraryLink(this, x)), + Links?.Where(x => x.Collection == null)); + } + + public LibraryDE() {} + + public LibraryDE(Library item) + { + Utility.Assign(this, item); + } + } +} \ No newline at end of file diff --git a/Kyoo/Models/Resources/ShowDE.cs b/Kyoo/Models/Resources/ShowDE.cs new file mode 100644 index 00000000..9d772d93 --- /dev/null +++ b/Kyoo/Models/Resources/ShowDE.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models.Attributes; + +namespace Kyoo.Models +{ + public class ShowDE : Show + { + [NotMergable] public virtual IEnumerable GenreLinks { get; set; } + public override IEnumerable Genres + { + get => GenreLinks?.Select(x => x.Genre); + set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList(); + } + + [NotMergable] public virtual IEnumerable LibraryLinks { get; set; } + public override IEnumerable Libraries + { + get => LibraryLinks?.Select(x => x.Library); + set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)); + } + + [NotMergable] public virtual IEnumerable CollectionLinks { get; set; } + + public override IEnumerable Collections + { + get => CollectionLinks?.Select(x => x.Collection); + set => CollectionLinks = value?.Select(x => new CollectionLink(x, this)); + } + + public override void OnMerge(object merged) + { + base.OnMerge(merged); + + if (GenreLinks != null) + foreach (GenreLink genre in GenreLinks) + genre.Show = this; + } + + public ShowDE() {} + + public ShowDE(Show show) + { + Utility.Assign(this, show); + } + } +} \ No newline at end of file diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index d4202623..2e44a86c 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit d4202623007f0cefb22aa64e3c638142ba7d7831 +Subproject commit 2e44a86c4b9004012c10d259ae90925adcd66aa7