// Kyoo - A portable and vast media library solution. // Copyright (c) Kyoo. // // See AUTHORS.md and LICENSE file in the project root for full license information. // // Kyoo is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // Kyoo is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Utils; namespace Kyoo.Core.Controllers { public class LibraryManager : ILibraryManager { /// /// The list of repositories /// private readonly IBaseRepository[] _repositories; /// public ILibraryRepository LibraryRepository { get; } /// public ILibraryItemRepository LibraryItemRepository { get; } /// public ICollectionRepository CollectionRepository { get; } /// public IShowRepository ShowRepository { get; } /// public ISeasonRepository SeasonRepository { get; } /// public IEpisodeRepository EpisodeRepository { get; } /// public ITrackRepository TrackRepository { get; } /// public IPeopleRepository PeopleRepository { get; } /// public IStudioRepository StudioRepository { get; } /// public IGenreRepository GenreRepository { get; } /// public IProviderRepository ProviderRepository { get; } /// public IUserRepository UserRepository { get; } /// /// Create a new instance with every repository available. /// /// The list of repositories that this library manager should manage. /// If a repository for every base type is not available, this instance won't be stable. public LibraryManager(IEnumerable repositories) { _repositories = repositories.ToArray(); LibraryRepository = GetRepository() as ILibraryRepository; LibraryItemRepository = GetRepository() as ILibraryItemRepository; CollectionRepository = GetRepository() as ICollectionRepository; ShowRepository = GetRepository() as IShowRepository; SeasonRepository = GetRepository() as ISeasonRepository; EpisodeRepository = GetRepository() as IEpisodeRepository; TrackRepository = GetRepository() as ITrackRepository; PeopleRepository = GetRepository() as IPeopleRepository; StudioRepository = GetRepository() as IStudioRepository; GenreRepository = GetRepository() as IGenreRepository; ProviderRepository = GetRepository() as IProviderRepository; UserRepository = GetRepository() as IUserRepository; } /// public IRepository GetRepository() where T : class, IResource { if (_repositories.FirstOrDefault(x => x.RepositoryType == typeof(T)) is IRepository ret) return ret; throw new ItemNotFoundException($"No repository found for the type {typeof(T).Name}."); } /// public Task Get(int id) where T : class, IResource { return GetRepository().Get(id); } /// public Task Get(string slug) where T : class, IResource { return GetRepository().Get(slug); } /// public Task Get(Expression> where) where T : class, IResource { return GetRepository().Get(where); } /// public Task Get(int showID, int seasonNumber) { return SeasonRepository.Get(showID, seasonNumber); } /// public Task Get(string showSlug, int seasonNumber) { return SeasonRepository.Get(showSlug, seasonNumber); } /// public Task Get(int showID, int seasonNumber, int episodeNumber) { return EpisodeRepository.Get(showID, seasonNumber, episodeNumber); } /// public Task Get(string showSlug, int seasonNumber, int episodeNumber) { return EpisodeRepository.Get(showSlug, seasonNumber, episodeNumber); } /// public async Task GetOrDefault(int id) where T : class, IResource { return await GetRepository().GetOrDefault(id); } /// public async Task GetOrDefault(string slug) where T : class, IResource { return await GetRepository().GetOrDefault(slug); } /// public async Task GetOrDefault(Expression> where) where T : class, IResource { return await GetRepository().GetOrDefault(where); } /// public async Task GetOrDefault(int showID, int seasonNumber) { return await SeasonRepository.GetOrDefault(showID, seasonNumber); } /// public async Task GetOrDefault(string showSlug, int seasonNumber) { return await SeasonRepository.GetOrDefault(showSlug, seasonNumber); } /// public async Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { return await EpisodeRepository.GetOrDefault(showID, seasonNumber, episodeNumber); } /// public async Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber) { return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber); } /// /// Set relations between to objects. /// /// The owner object /// A Task to load a collection of related objects /// A setter function to store the collection of related objects /// A setter function to store the owner of a releated object loaded /// The type of the owner object /// The type of the related object private static async Task _SetRelation(T1 obj, Task> loader, Action> setter, Action inverse) { ICollection loaded = await loader; setter(obj, loaded); foreach (T2 item in loaded) inverse(item, obj); } /// public Task Load(T obj, Expression> member, bool force = false) where T : class, IResource where T2 : class, IResource { if (member == null) throw new ArgumentNullException(nameof(member)); return Load(obj, Utility.GetPropertyName(member), force); } /// public Task Load(T obj, Expression>> member, bool force = false) where T : class, IResource where T2 : class { if (member == null) throw new ArgumentNullException(nameof(member)); return Load(obj, Utility.GetPropertyName(member), force); } /// public async Task Load(T obj, string memberName, bool force = false) where T : class, IResource { await Load(obj as IResource, memberName, force); return obj; } /// [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1507:CodeMustNotContainMultipleBlankLinesInARow", Justification = "Separate the code by semantics and simplify the code read.")] [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1107:Code should not contain multiple statements on one line", Justification = "Assing IDs and Values in the same line.")] public Task Load(IResource obj, string memberName, bool force = false) { if (obj == null) throw new ArgumentNullException(nameof(obj)); object existingValue = obj.GetType() .GetProperties() .FirstOrDefault(x => string.Equals(x.Name, memberName, StringComparison.InvariantCultureIgnoreCase)) ?.GetValue(obj); if (existingValue != null && !force) return Task.CompletedTask; return (obj, member: memberName) switch { (Library l, nameof(Library.Providers)) => ProviderRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) .Then(x => l.Providers = x), (Library l, nameof(Library.Shows)) => ShowRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) .Then(x => l.Shows = x), (Library l, nameof(Library.Collections)) => CollectionRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) .Then(x => l.Collections = x), (Collection c, nameof(Collection.ExternalIDs)) => _SetRelation(c, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), (Collection c, nameof(Collection.Shows)) => ShowRepository .GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) .Then(x => c.Shows = x), (Collection c, nameof(Collection.Libraries)) => LibraryRepository .GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) .Then(x => c.Libraries = x), (Show s, nameof(Show.ExternalIDs)) => _SetRelation(s, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), (Show s, nameof(Show.Genres)) => GenreRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Genres = x), (Show s, nameof(Show.People)) => PeopleRepository .GetFromShow(obj.ID) .Then(x => s.People = x), (Show s, nameof(Show.Seasons)) => _SetRelation(s, SeasonRepository.GetAll(x => x.Show.ID == obj.ID), (x, y) => x.Seasons = y, (x, y) => { x.Show = y; x.ShowID = y.ID; }), (Show s, nameof(Show.Episodes)) => _SetRelation(s, EpisodeRepository.GetAll(x => x.Show.ID == obj.ID), (x, y) => x.Episodes = y, (x, y) => { x.Show = y; x.ShowID = y.ID; }), (Show s, nameof(Show.Libraries)) => LibraryRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Libraries = x), (Show s, nameof(Show.Collections)) => CollectionRepository .GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => s.Collections = x), (Show s, nameof(Show.Studio)) => StudioRepository .GetOrDefault(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => { s.Studio = x; s.StudioID = x?.ID ?? 0; }), (Season s, nameof(Season.ExternalIDs)) => _SetRelation(s, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), (Season s, nameof(Season.Episodes)) => _SetRelation(s, EpisodeRepository.GetAll(x => x.Season.ID == obj.ID), (x, y) => x.Episodes = y, (x, y) => { x.Season = y; x.SeasonID = y.ID; }), (Season s, nameof(Season.Show)) => ShowRepository .GetOrDefault(x => x.Seasons.Any(y => y.ID == obj.ID)) .Then(x => { s.Show = x; s.ShowID = x?.ID ?? 0; }), (Episode e, nameof(Episode.ExternalIDs)) => _SetRelation(e, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), (Episode e, nameof(Episode.Tracks)) => _SetRelation(e, TrackRepository.GetAll(x => x.Episode.ID == obj.ID), (x, y) => x.Tracks = y, (x, y) => { x.Episode = y; x.EpisodeID = y.ID; }), (Episode e, nameof(Episode.Show)) => ShowRepository .GetOrDefault(x => x.Episodes.Any(y => y.ID == obj.ID)) .Then(x => { e.Show = x; e.ShowID = x?.ID ?? 0; }), (Episode e, nameof(Episode.Season)) => SeasonRepository .GetOrDefault(x => x.Episodes.Any(y => y.ID == e.ID)) .Then(x => { e.Season = x; e.SeasonID = x?.ID ?? 0; }), (Track t, nameof(Track.Episode)) => EpisodeRepository .GetOrDefault(x => x.Tracks.Any(y => y.ID == obj.ID)) .Then(x => { t.Episode = x; t.EpisodeID = x?.ID ?? 0; }), (Genre g, nameof(Genre.Shows)) => ShowRepository .GetAll(x => x.Genres.Any(y => y.ID == obj.ID)) .Then(x => g.Shows = x), (Studio s, nameof(Studio.Shows)) => ShowRepository .GetAll(x => x.Studio.ID == obj.ID) .Then(x => s.Shows = x), (Studio s, nameof(Studio.ExternalIDs)) => _SetRelation(s, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), (People p, nameof(People.ExternalIDs)) => _SetRelation(p, ProviderRepository.GetMetadataID(x => x.ResourceID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.ResourceID = y.ID; }), (People p, nameof(People.Roles)) => PeopleRepository .GetFromPeople(obj.ID) .Then(x => p.Roles = x), (Provider p, nameof(Provider.Libraries)) => LibraryRepository .GetAll(x => x.Providers.Any(y => y.ID == obj.ID)) .Then(x => p.Libraries = x), _ => throw new ArgumentException($"Couldn't find a way to load {memberName} of {obj.Slug}.") }; } /// public Task> GetItemsFromLibrary(int id, Expression> where = null, Sort sort = default, Pagination limit = default) { return LibraryItemRepository.GetFromLibrary(id, where, sort, limit); } /// public Task> GetItemsFromLibrary(string slug, Expression> where = null, Sort sort = default, Pagination limit = default) { return LibraryItemRepository.GetFromLibrary(slug, where, sort, limit); } /// public Task> GetPeopleFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromShow(showID, where, sort, limit); } /// public Task> GetPeopleFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromShow(showSlug, where, sort, limit); } /// public Task> GetRolesFromPeople(int id, Expression> where = null, Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromPeople(id, where, sort, limit); } /// public Task> GetRolesFromPeople(string slug, Expression> where = null, Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromPeople(slug, where, sort, limit); } /// public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return ShowRepository.AddShowLink(showID, libraryID, collectionID); } /// public Task AddShowLink(Show show, Library library, Collection collection) { if (show == null) throw new ArgumentNullException(nameof(show)); return ShowRepository.AddShowLink(show.ID, library?.ID, collection?.ID); } /// public Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) where T : class, IResource { return GetRepository().GetAll(where, sort, limit); } /// public Task GetCount(Expression> where = null) where T : class, IResource { return GetRepository().GetCount(where); } /// public Task> Search(string query) where T : class, IResource { return GetRepository().Search(query); } /// public Task Create(T item) where T : class, IResource { return GetRepository().Create(item); } /// public Task CreateIfNotExists(T item) where T : class, IResource { return GetRepository().CreateIfNotExists(item); } /// public Task Edit(T item, bool resetOld) where T : class, IResource { return GetRepository().Edit(item, resetOld); } /// public Task Delete(T item) where T : class, IResource { return GetRepository().Delete(item); } /// public Task Delete(int id) where T : class, IResource { return GetRepository().Delete(id); } /// public Task Delete(string slug) where T : class, IResource { return GetRepository().Delete(slug); } } }