From 14e4772dd1ab1e544ec7acb1b2f668a52f8a1307 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 21 Feb 2021 16:00:09 +0100 Subject: [PATCH] Recreating the Load method using repositories and pattern matching --- Kyoo.Common/Controllers/ILibraryManager.cs | 6 +- Kyoo.Common/Controllers/IRepository.cs | 29 +++++++-- .../{ALibraryManager.cs => LibraryManager.cs} | 60 ++++++++++++++++--- Kyoo.Common/ExpressionRewrite.cs | 13 ++-- Kyoo.Common/Utility.cs | 14 ++++- Kyoo.CommonAPI/LibraryManager.cs | 58 ------------------ 6 files changed, 97 insertions(+), 83 deletions(-) rename Kyoo.Common/Controllers/Implementations/{ALibraryManager.cs => LibraryManager.cs} (84%) delete mode 100644 Kyoo.CommonAPI/LibraryManager.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index cd763d84..f6d746c3 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -61,11 +61,11 @@ namespace Kyoo.Controllers Task GetStudio(Expression> where); Task GetPerson(Expression> where); - Task Load([NotNull] T obj, Expression> member) + Task Load([NotNull] T obj, [CanBeNull] Expression> member) where T : class, IResource - where T2 : class; + where T2 : class, IResource; - Task Load([NotNull] T obj, Expression>> member) + Task Load([NotNull] T obj, [CanBeNull] Expression>> member) where T : class, IResource where T2 : class; diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 522154c3..41729858 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -20,7 +20,7 @@ namespace Kyoo.Controllers AfterID = afterID; } - public static implicit operator Pagination(int limit) => new Pagination(limit); + public static implicit operator Pagination(int limit) => new(limit); } public struct Sort @@ -67,7 +67,7 @@ namespace Kyoo.Controllers public Sort To() { - return new Sort(Key.Convert>(), Descendant); + return new(Key.Convert>(), Descendant); } } @@ -206,6 +206,27 @@ namespace Kyoo.Controllers Pagination limit = default ) => GetFromPeople(showSlug, where, new Sort(sort), limit); } - - public interface IProviderRepository : IRepository {} + + public interface IProviderRepository : IRepository + { + Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(int showID, + [Optional] Expression> where, + Expression> sort = default, + Pagination limit = default + ) => GetFromShow(showID, where, new Sort(sort), limit); + + Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort = default, + Pagination limit = default + ) => GetFromShow(showSlug, where, new Sort(sort), limit); + } } diff --git a/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs similarity index 84% rename from Kyoo.Common/Controllers/Implementations/ALibraryManager.cs rename to Kyoo.Common/Controllers/Implementations/LibraryManager.cs index d2988241..8fe32489 100644 --- a/Kyoo.Common/Controllers/Implementations/ALibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -6,7 +6,7 @@ using Kyoo.Models; namespace Kyoo.Controllers { - public class ALibraryManager : ILibraryManager + public class LibraryManager : ILibraryManager { public ILibraryRepository LibraryRepository { get; } public ILibraryItemRepository LibraryItemRepository { get; } @@ -20,7 +20,7 @@ namespace Kyoo.Controllers public IPeopleRepository PeopleRepository { get; } public IProviderRepository ProviderRepository { get; } - protected ALibraryManager(ILibraryRepository libraryRepository, + protected LibraryManager(ILibraryRepository libraryRepository, ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, @@ -235,19 +235,63 @@ namespace Kyoo.Controllers return PeopleRepository.Get(where); } - public virtual Task Load(T obj, Expression> member) + public async Task Load(T obj, Expression> member) where T : class, IResource - where T2 : class + where T2 : class, IResource { - // TODO figure out why setting this method as abstract prevent the app from loading this assembly. - throw new NotImplementedException(); + dynamic identifier = obj.ID > 0 ? obj.ID : obj.Slug; + switch (obj, (T2)default) + { + case (Show s, Studio): s.Studio = await StudioRepository.GetFromShow(identifier); break; + + case (Season s, Show): s.Show = await ShowRepository.GetFromSeason(identifier); break; + + case (Episode e, Show): e.Show = await ShowRepository.GetFromEpisode(identifier); break; + case (Episode e, Season): e.Season = await SeasonRepository.GetFromEpisode(identifier); break; + + case (Track t, Episode): t.Episode = await EpisodeRepository.GetFromTrack(identifier); break; + + default: throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}."); + } } - public virtual Task Load(T obj, Expression>> member) + public async Task Load(T obj, Expression>> member) where T : class, IResource where T2 : class { - throw new NotImplementedException(); + dynamic identifier = obj.ID > 0 ? obj.ID : obj.Slug; + switch (obj, (T2)default) + { + case (Library l, ProviderID): l.Providers = await ProviderRepository.GetFromLibrary(identifier); break; + case (Library l, Show): l.Shows = await ShowRepository.GetFromLibrary(identifier); break; + case (Library l, Collection): l.Collections = await CollectionRepository.GetFromLibrary(identifier); break; + + case (Collection c, Show): c.Shows = await ShowRepository.GetFromCollection(identifier); break; + case (Collection c, Library): c.Libraries = await LibraryRepository.GetFromCollection(identifier); break; + + case (Show s, MetadataID): s.ExternalIDs = await ProviderRepository.GetFromShow(identifier); break; + case (Show s, Genre): s.Genres = await GenreRepository.GetFromShow(identifier); break; + case (Show s, PeopleRole): s.People = await PeopleRepository.GetFromShow(identifier); break; + case (Show s, Season): s.Seasons = await SeasonRepository.GetFromShow(identifier); break; + case (Show s, Episode): s.Episodes = await EpisodeRepository.GetFromShow(identifier); break; + case (Show s, Library): s.Libraries = await LibraryRepository.GetFromShow(identifier); break; + case (Show s, Collection): s.Collections = await CollectionRepository.GetFromShow(identifier); break; + + case (Season s, MetadataID): s.ExternalIDs = await ProviderRepository.GetFromSeason(identifier); break; + case (Season s, Episode): s.Episodes = await EpisodeRepository.GetFromSeason(identifier); break; + + case (Episode e, MetadataID): e.ExternalIDs = await ProviderRepository.GetFromEpisode(identifier); break; + case (Episode e, Track): e.Tracks = await TrackRepository.GetFromEpisode(identifier); break; + + case (Genre g, Show): g.Shows = await ShowRepository.GetFromGenre(identifier); break; + + case (Studio s, Show): s.Shows = await ShowRepository.GetFromStudio(identifier); break; + + case (People p, MetadataID): p.ExternalIDs = await ProviderRepository.GetFromPeople(identifier); break; + case (People p, PeopleRole): p.Roles = await ShowRepository.GetFromPeople(identifier); break; + + default: throw new ArgumentException($"Couldn't find a way to load {member} of {typeof(T).Name}."); + }; } public Task> GetLibraries(Expression> where = null, diff --git a/Kyoo.Common/ExpressionRewrite.cs b/Kyoo.Common/ExpressionRewrite.cs index c7db9636..3bb5bc5c 100644 --- a/Kyoo.Common/ExpressionRewrite.cs +++ b/Kyoo.Common/ExpressionRewrite.cs @@ -43,26 +43,21 @@ namespace Kyoo (string inner, _, ParameterExpression p) = _innerRewrites.FirstOrDefault(x => x.param == node.Expression); if (inner != null) { - Expression param = p; - foreach (string accessor in inner.Split('.')) - param = Expression.Property(param, accessor); + Expression param = inner.Split('.').Aggregate(p, Expression.Property); node = Expression.Property(param, node.Member.Name); } // Can't use node.Member directly because we want to support attribute override - MemberInfo member = node.Expression.Type.GetProperty(node.Member.Name) ?? node.Member; + MemberInfo member = node.Expression!.Type.GetProperty(node.Member.Name) ?? node.Member; ExpressionRewriteAttribute attr = member!.GetCustomAttribute(); if (attr == null) return base.VisitMember(node); - Expression property = node.Expression; - foreach (string child in attr.Link.Split('.')) - property = Expression.Property(property, child); - + Expression property = attr.Link.Split('.').Aggregate(node.Expression, Expression.Property); if (property is MemberExpression expr) Visit(expr.Expression); _inner = attr.Inner; - return property; + return property!; } protected override Expression VisitLambda(Expression node) diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index dbbc400c..bb627d0b 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -17,7 +17,7 @@ namespace Kyoo { public static class Utility { - public static bool IsPropertyExpression(Expression> ex) + public static bool IsPropertyExpression(LambdaExpression ex) { return ex == null || ex.Body is MemberExpression || @@ -490,6 +490,18 @@ namespace Kyoo return first.SequenceEqual(second, new LinkComparer()); } + public static string GetMemberName([NotNull] Expression expression) + { + var t = ExpressionRewrite.Rewrite(expression); + return ExpressionRewrite.Rewrite(expression) switch + { + MemberExpression member => member.Member.Name, + LambdaExpression lambda when IsPropertyExpression(lambda) => GetMemberName(lambda.Body), + null => throw new ArgumentNullException(nameof(expression)), + _ => throw new ArgumentException($"Can't get member.") + }; + } + public static Expression Convert([CanBeNull] this Expression expr) where T : Delegate { diff --git a/Kyoo.CommonAPI/LibraryManager.cs b/Kyoo.CommonAPI/LibraryManager.cs deleted file mode 100644 index 5bc2559d..00000000 --- a/Kyoo.CommonAPI/LibraryManager.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; - -namespace Kyoo.Controllers -{ - public class LibraryManager : ALibraryManager - { - private readonly DbContext _database; - - public LibraryManager(ILibraryRepository libraryRepository, - ILibraryItemRepository libraryItemRepository, - ICollectionRepository collectionRepository, - IShowRepository showRepository, - ISeasonRepository seasonRepository, - IEpisodeRepository episodeRepository, - ITrackRepository trackRepository, - IGenreRepository genreRepository, - IStudioRepository studioRepository, - IProviderRepository providerRepository, - IPeopleRepository peopleRepository, - DbContext database) - : base(libraryRepository, - libraryItemRepository, - collectionRepository, - showRepository, - seasonRepository, - episodeRepository, - trackRepository, - genreRepository, - studioRepository, - providerRepository, - peopleRepository) - { - _database = database; - } - - public override Task Load(T obj, Expression>> member) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - if (!Utility.IsPropertyExpression(member) || member == null) - throw new ArgumentException($"{nameof(member)} is not a property."); - return _database.Entry(obj).Collection(member).LoadAsync(); - } - - public override Task Load(T obj, Expression> member) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - if (!Utility.IsPropertyExpression(member) || member == null) - throw new ArgumentException($"{nameof(member)} is not a property."); - return _database.Entry(obj).Reference(member).LoadAsync(); - } - } -} \ No newline at end of file