diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 9fef9a4a..b89f251f 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -46,6 +46,17 @@ namespace Kyoo.Controllers Task GetGenre(string slug); Task GetStudio(string slug); Task GetPeople(string slug); + + // Get by predicate + Task GetLibrary(Expression> where); + Task GetCollection(Expression> where); + Task GetShow(Expression> where); + Task GetSeason(Expression> where); + Task GetEpisode(Expression> where); + Task GetTrack(Expression> where); + Task GetGenre(Expression> where); + Task GetStudio(Expression> where); + Task GetPerson(Expression> where); // Get by relations Task> GetSeasonsFromShow(int showID, diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index ce526a01..f8c534b7 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -78,6 +78,7 @@ namespace Kyoo.Controllers { Task Get(int id); Task Get(string slug); + async Task Get(Expression> where) => (await GetAll(where, limit: 1)).FirstOrDefault(); Task> Search(string query); Task> GetAll(Expression> where = null, diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 6743bc56..9fcd0730 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -175,6 +175,51 @@ namespace Kyoo.Controllers return PeopleRepository.Get(slug); } + public Task GetLibrary(Expression> where) + { + return LibraryRepository.Get(where); + } + + public Task GetCollection(Expression> where) + { + return CollectionRepository.Get(where); + } + + public Task GetShow(Expression> where) + { + return ShowRepository.Get(where); + } + + public Task GetSeason(Expression> where) + { + return SeasonRepository.Get(where); + } + + public Task GetEpisode(Expression> where) + { + return EpisodeRepository.Get(where); + } + + public Task GetTrack(Expression> where) + { + return TrackRepository.Get(where); + } + + public Task GetGenre(Expression> where) + { + return GenreRepository.Get(where); + } + + public Task GetStudio(Expression> where) + { + return StudioRepository.Get(where); + } + + public Task GetPerson(Expression> where) + { + return PeopleRepository.Get(where); + } + public Task> GetLibraries(Expression> where = null, Sort sort = default, Pagination page = default) diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 84c79ffa..97f96e95 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -76,26 +76,35 @@ namespace Kyoo public static T Assign(T first, T second) { Type type = typeof(T); - foreach (PropertyInfo property in type.GetProperties()) + IEnumerable properties = type.GetProperties() + .Where(x => x.CanRead && x.CanWrite + && Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null); + + foreach (PropertyInfo property in properties) { - if (!property.CanRead || !property.CanWrite) - continue; - object value = property.GetValue(second); property.SetValue(first, value); } + if (first is IOnMerge merge) + merge.OnMerge(second); return first; } public static T Complete(T first, T second) { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + return first; + Type type = typeof(T); - foreach (PropertyInfo property in type.GetProperties()) + IEnumerable properties = type.GetProperties() + .Where(x => x.CanRead && x.CanWrite + && Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null); + + foreach (PropertyInfo property in properties) { - if (!property.CanRead || !property.CanWrite) - continue; - object value = property.GetValue(second); object defaultValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) @@ -105,6 +114,8 @@ namespace Kyoo property.SetValue(first, value); } + if (first is IOnMerge merge) + merge.OnMerge(second); return first; } @@ -116,11 +127,12 @@ namespace Kyoo return first; Type type = typeof(T); - foreach (PropertyInfo property in type.GetProperties().Where(x => x.CanRead && x.CanWrite)) + IEnumerable properties = type.GetProperties() + .Where(x => x.CanRead && x.CanWrite + && Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null); + + foreach (PropertyInfo property in properties) { - if (Attribute.GetCustomAttribute(property, typeof(NotMergableAttribute)) != null) - continue; - object oldValue = property.GetValue(first); object newValue = property.GetValue(second); object defaultValue = property.PropertyType.IsValueType @@ -135,8 +147,8 @@ namespace Kyoo property.SetValue(first, RunGenericMethod( typeof(Utility), "MergeLists", - GetEnumerableType(property.PropertyType), - new []{ oldValue, newValue, null})); + GetEnumerableType(property.PropertyType), + oldValue, newValue, null)); } } @@ -247,11 +259,42 @@ namespace Kyoo return string.Empty; return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}")); } + + public static Task Then(this Task task, Action map) + { + return task.ContinueWith(x => + { + if (x.IsFaulted) + throw x.Exception!.InnerException!; + if (x.IsCanceled) + throw new TaskCanceledException(); + map(x.Result); + return x.Result; + }, TaskContinuationOptions.ExecuteSynchronously); + } + + public static Task Map(this Task task, Func map) + { + return task.ContinueWith(x => + { + if (x.IsFaulted) + throw x.Exception!.InnerException!; + if (x.IsCanceled) + throw new TaskCanceledException(); + return map(x.Result); + }, TaskContinuationOptions.ExecuteSynchronously); + } public static Task Cast(this Task task) { - return task.ContinueWith(x => (T)((dynamic)x).Result, - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion); + return task.ContinueWith(x => + { + if (x.IsFaulted) + throw x.Exception!.InnerException!; + if (x.IsCanceled) + throw new TaskCanceledException(); + return (T)((dynamic)x).Result; + }, TaskContinuationOptions.ExecuteSynchronously); } public static Expression Convert([CanBeNull] this Expression expr) diff --git a/Kyoo.CommonAPI/ApiHelper.cs b/Kyoo.CommonAPI/ApiHelper.cs index 8258cbe6..7db41430 100644 --- a/Kyoo.CommonAPI/ApiHelper.cs +++ b/Kyoo.CommonAPI/ApiHelper.cs @@ -24,7 +24,7 @@ namespace Kyoo.CommonApi Expression> defaultWhere = null) { if (where == null || where.Count == 0) - return null; + return defaultWhere; ParameterExpression param = Expression.Parameter(typeof(T)); Expression expression = defaultWhere?.Body; diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 742c1a2e..c8ed3c4b 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -231,14 +231,16 @@ namespace Kyoo.Controllers { TInternal obj = new TInternal(); Utility.Assign(obj, item); - return Create(obj).Cast(); + return Create(obj).Cast() + .Then(x => item.ID = x.ID); } Task IRepository.CreateIfNotExists(T item) { TInternal obj = new TInternal(); Utility.Assign(obj, item); - return CreateIfNotExists(obj).Cast(); + return CreateIfNotExists(obj).Cast() + .Then(x => item.ID = x.ID); } public Task Edit(T edited, bool resetOld) diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index e0914257..572abf84 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -224,8 +224,7 @@ namespace Kyoo.Controllers bool isMovie, Library library) { - Show show = (await libraryManager.GetShows(x => x.Path == showPath, limit: 1)) - .FirstOrDefault(); + Show show = await libraryManager.GetShow(x => x.Path == showPath); if (show != null) return show; show = await _metadataProvider.SearchShow(showTitle, isMovie, library); diff --git a/Kyoo/Views/API/GenreApi.cs b/Kyoo/Views/API/GenreApi.cs index 10f15922..efe626cc 100644 --- a/Kyoo/Views/API/GenreApi.cs +++ b/Kyoo/Views/API/GenreApi.cs @@ -45,6 +45,8 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); + if (!ressources.Any() && await _libraryManager.GetGenre(id) == null) + return NotFound(); return Page(ressources, limit); } catch (ItemNotFound) @@ -77,6 +79,8 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); + if (!ressources.Any() && await _libraryManager.GetGenre(slug) == null) + return NotFound(); return Page(ressources, limit); } catch (ItemNotFound) diff --git a/Kyoo/Views/API/StudioApi.cs b/Kyoo/Views/API/StudioApi.cs index 8c15be4a..e2b7f88a 100644 --- a/Kyoo/Views/API/StudioApi.cs +++ b/Kyoo/Views/API/StudioApi.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; @@ -44,6 +45,8 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); + if (!ressources.Any() && await _libraryManager.GetStudio(id) == null) + return NotFound(); return Page(ressources, limit); } catch (ItemNotFound) @@ -75,7 +78,9 @@ namespace Kyoo.Api ApiHelper.ParseWhere(where, x => x.Studio.Slug == slug), new Sort(sortBy), new Pagination(limit, afterID)); - + + if (!ressources.Any() && await _libraryManager.GetStudio(slug) == null) + return NotFound(); return Page(ressources, limit); } catch (ItemNotFound)