using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; namespace Kyoo.Controllers { public class LibraryManager : ILibraryManager { /// /// The list of repositories /// private readonly IBaseRepository[] _repositories; /// /// The repository that handle libraries. /// public ILibraryRepository LibraryRepository { get; } /// /// The repository that handle libraries's items (a wrapper arround shows & collections). /// public ILibraryItemRepository LibraryItemRepository { get; } /// /// The repository that handle collections. /// public ICollectionRepository CollectionRepository { get; } /// /// The repository that handle shows. /// public IShowRepository ShowRepository { get; } /// /// The repository that handle seasons. /// public ISeasonRepository SeasonRepository { get; } /// /// The repository that handle episodes. /// public IEpisodeRepository EpisodeRepository { get; } /// /// The repository that handle tracks. /// public ITrackRepository TrackRepository { get; } /// /// The repository that handle people. /// public IPeopleRepository PeopleRepository { get; } /// /// The repository that handle studios. /// public IStudioRepository StudioRepository { get; } /// /// The repository that handle genres. /// public IGenreRepository GenreRepository { get; } /// /// The repository that handle providers. /// public IProviderRepository ProviderRepository { get; } /// /// Create a new instancce 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; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { foreach (IBaseRepository repo in _repositories) repo.Dispose(); GC.SuppressFinalize(this); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously. /// /// A task that represents the asynchronous dispose operation. public async ValueTask DisposeAsync() { await Task.WhenAll(_repositories.Select(x => x.DisposeAsync().AsTask())); } /// /// Get the repository corresponding to the T item. /// /// The type you want /// If the item is not found /// The repository corresponding public IRepository GetRepository() where T : class, IResource { if (_repositories.FirstOrDefault(x => x.RepositoryType == typeof(T)) is IRepository ret) return ret; throw new ItemNotFound(); } /// /// Get the resource by it's ID /// /// The id of the resource /// The type of the resource /// If the item is not found /// The resource found public Task Get(int id) where T : class, IResource { return GetRepository().Get(id); } /// /// Get the resource by it's slug /// /// The slug of the resource /// The type of the resource /// If the item is not found /// The resource found public Task Get(string slug) where T : class, IResource { return GetRepository().Get(slug); } /// /// Get the resource by a filter function. /// /// The filter function. /// The type of the resource /// If the item is not found /// The first resource found that match the where function public Task Get(Expression> where) where T : class, IResource { return GetRepository().Get(where); } /// /// Get a season from it's showID and it's seasonNumber /// /// The id of the show /// The season's number /// If the item is not found /// The season found public Task Get(int showID, int seasonNumber) { return SeasonRepository.Get(showID, seasonNumber); } /// /// Get a season from it's show slug and it's seasonNumber /// /// The slug of the show /// The season's number /// If the item is not found /// The season found public Task Get(string showSlug, int seasonNumber) { return SeasonRepository.Get(showSlug, seasonNumber); } /// /// Get a episode from it's showID, it's seasonNumber and it's episode number. /// /// The id of the show /// The season's number /// The episode's number /// If the item is not found /// The episode found public Task Get(int showID, int seasonNumber, int episodeNumber) { return EpisodeRepository.Get(showID, seasonNumber, episodeNumber); } /// /// Get a episode from it's show slug, it's seasonNumber and it's episode number. /// /// The slug of the show /// The season's number /// The episode's number /// If the item is not found /// The episode found public Task Get(string showSlug, int seasonNumber, int episodeNumber) { return EpisodeRepository.Get(showSlug, seasonNumber, episodeNumber); } /// /// Get a tracck from it's slug and it's type. /// /// The slug of the track /// The type (Video, Audio or Subtitle) /// If the item is not found /// The tracl found public Task Get(string slug, StreamType type = StreamType.Unknown) { return TrackRepository.Get(slug, type); } /// /// Get the resource by it's ID or null if it is not found. /// /// The id of the resource /// The type of the resource /// The resource found public async Task GetOrDefault(int id) where T : class, IResource { return await GetRepository().GetOrDefault(id); } /// /// Get the resource by it's slug or null if it is not found. /// /// The slug of the resource /// The type of the resource /// The resource found public async Task GetOrDefault(string slug) where T : class, IResource { return await GetRepository().GetOrDefault(slug); } /// /// Get the resource by a filter function or null if it is not found. /// /// The filter function. /// The type of the resource /// The first resource found that match the where function public async Task GetOrDefault(Expression> where) where T : class, IResource { return await GetRepository().GetOrDefault(where); } /// /// Get a season from it's showID and it's seasonNumber or null if it is not found. /// /// The id of the show /// The season's number /// The season found public async Task GetOrDefault(int showID, int seasonNumber) { return await SeasonRepository.GetOrDefault(showID, seasonNumber); } /// /// Get a season from it's show slug and it's seasonNumber or null if it is not found. /// /// The slug of the show /// The season's number /// The season found public async Task GetOrDefault(string showSlug, int seasonNumber) { return await SeasonRepository.GetOrDefault(showSlug, seasonNumber); } /// /// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found. /// /// The id of the show /// The season's number /// The episode's number /// The episode found public async Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { return await EpisodeRepository.GetOrDefault(showID, seasonNumber, episodeNumber); } /// /// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found. /// /// The slug of the show /// The season's number /// The episode's number /// The episode found public async Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber) { return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber); } /// /// Get a track from it's slug and it's type or null if it is not found. /// /// The slug of the track /// The type (Video, Audio or Subtitle) /// The tracl found public async Task GetOrDefault(string slug, StreamType type = StreamType.Unknown) { return await TrackRepository.GetOrDefault(slug, type); } /// /// Load a related resource /// /// The source object. /// A getter function for the member to load /// The type of the source object /// The related resource's type /// The param public Task Load(T obj, Expression> member) where T : class, IResource where T2 : class, IResource, new() { if (member == null) throw new ArgumentNullException(nameof(member)); return Load(obj, Utility.GetPropertyName(member)); } /// /// Load a collection of related resource /// /// The source object. /// A getter function for the member to load /// The type of the source object /// The related resource's type /// The param public Task Load(T obj, Expression>> member) where T : class, IResource where T2 : class, new() { if (member == null) throw new ArgumentNullException(nameof(member)); return Load(obj, Utility.GetPropertyName(member)); } /// /// Load a related resource by it's name /// /// The source object. /// The name of the resource to load (case sensitive) /// The type of the source object /// The param public async Task Load(T obj, string memberName) where T : class, IResource { await Load(obj as IResource, memberName); return obj; } /// /// 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); } /// /// Load a related resource without specifing it's type. /// /// The source object. /// The name of the resource to load (case sensitive) public Task Load(IResource obj, string memberName) { if (obj == null) throw new ArgumentNullException(nameof(obj)); 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(Library.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.ShowID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.Show = y; x.ShowID = 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.SeasonID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.Season = y; x.SeasonID = 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.EpisodeID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.Episode = y; x.EpisodeID = 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), (People p, nameof(People.ExternalIDs)) => SetRelation(p, ProviderRepository.GetMetadataID(x => x.PeopleID == obj.ID), (x, y) => x.ExternalIDs = y, (x, y) => { x.People = y; x.PeopleID = 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}.") }; } /// /// Get items (A wrapper arround shows or collections) from a library. /// /// The ID of the library /// A filter function /// Sort informations (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetItemsFromLibrary(int id, Expression> where = null, Sort sort = default, Pagination limit = default) { return LibraryItemRepository.GetFromLibrary(id, where, sort, limit); } /// /// Get items (A wrapper arround shows or collections) from a library. /// /// The slug of the library /// A filter function /// Sort informations (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetItemsFromLibrary(string slug, Expression> where = null, Sort sort = default, Pagination limit = default) { return LibraryItemRepository.GetFromLibrary(slug, where, sort, limit); } /// /// Get people's roles from a show. /// /// The ID of the show /// A filter function /// Sort informations (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetPeopleFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromShow(showID, where, sort, limit); } /// /// Get people's roles from a show. /// /// The slug of the show /// A filter function /// Sort informations (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetPeopleFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromShow(showSlug, where, sort, limit); } /// /// Get people's roles from a person. /// /// The id of the person /// A filter function /// Sort informations (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetRolesFromPeople(int id, Expression> where = null, Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromPeople(id, where, sort, limit); } /// /// Get people's roles from a person. /// /// The slug of the person /// A filter function /// Sort informations (sort order & sort by) /// How many items to return and where to start /// A list of items that match every filters public Task> GetRolesFromPeople(string slug, Expression> where = null, Sort sort = default, Pagination limit = default) { return PeopleRepository.GetFromPeople(slug, where, sort, limit); } /// /// Setup relations between a show, a library and a collection /// /// The show's ID to setup relations with /// The library's ID to setup relations with (optional) /// The collection's ID to setup relations with (optional) public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return ShowRepository.AddShowLink(showID, libraryID, collectionID); } /// /// Setup relations between a show, a library and a collection /// /// The show to setup relations with /// The library to setup relations with (optional) /// The collection to setup relations with (optional) 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); } /// /// Get all resources with filters /// /// A filter function /// Sort informations (sort order & sort by) /// How many items to return and where to start /// The type of resources to load /// A list of resources that match every filters public Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) where T : class, IResource { return GetRepository().GetAll(where, sort, limit); } /// /// Get the count of resources that match the filter /// /// A filter function /// The type of resources to load /// A list of resources that match every filters public Task GetCount(Expression> where = null) where T : class, IResource { return GetRepository().GetCount(where); } /// /// Search for a resource /// /// The search query /// The type of resources /// A list of 20 items that match the search query public Task> Search(string query) where T : class, IResource { return GetRepository().Search(query); } /// /// Create a new resource. /// /// The item to register /// The type of resource /// The resource registers and completed by database's informations (related items & so on) public Task Create(T item) where T : class, IResource { return GetRepository().Create(item); } /// /// Create a new resource if it does not exist already. If it does, the existing value is returned instead. /// /// The object to create /// The type of resource /// The newly created item or the existing value if it existed. public Task CreateIfNotExists(T item) where T : class, IResource { return GetRepository().CreateIfNotExists(item); } /// /// Edit a resource /// /// The resourcce to edit, it's ID can't change. /// Should old properties of the resource be discarded or should null values considered as not changed? /// The type of resources /// If the item is not found /// The resource edited and completed by database's informations (related items & so on) public Task Edit(T item, bool resetOld) where T : class, IResource { return GetRepository().Edit(item, resetOld); } /// /// Delete a resource. /// /// The resource to delete /// The type of resource to delete /// If the item is not found public Task Delete(T item) where T : class, IResource { return GetRepository().Delete(item); } /// /// Delete a resource by it's ID. /// /// The id of the resource to delete /// The type of resource to delete /// If the item is not found public Task Delete(int id) where T : class, IResource { return GetRepository().Delete(id); } /// /// Delete a resource by it's slug. /// /// The slug of the resource to delete /// The type of resource to delete /// If the item is not found public Task Delete(string slug) where T : class, IResource { return GetRepository().Delete(slug); } } }