From 7dbb84780ce4569f3ac348f4d46bc0f980d49b09 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 18 Apr 2021 02:17:15 +0200 Subject: [PATCH 01/12] Reworking the LibraryManager's interface --- Kyoo.Common/Controllers/ILibraryManager.cs | 560 +++++++---- Kyoo.Common/Controllers/IMetadataProvider.cs | 2 +- Kyoo.Common/Controllers/IRepository.cs | 19 +- Kyoo.Common/Controllers/IThumbnailsManager.cs | 4 +- .../Implementations/LibraryManager.cs | 935 +++++++----------- Kyoo.Common/Models/MetadataID.cs | 4 +- Kyoo.Common/Models/Resources/Library.cs | 6 +- .../Resources/{ProviderID.cs => Provider.cs} | 10 +- Kyoo.Common/Models/WatchItem.cs | 12 +- Kyoo.CommonAPI/LocalRepository.cs | 4 +- Kyoo.WebApp | 2 +- .../Repositories/ProviderRepository.cs | 10 +- Kyoo/Controllers/ThumbnailsManager.cs | 4 +- Kyoo/Models/DatabaseContext.cs | 12 +- ....cs => 20210417232515_Initial.Designer.cs} | 16 +- ...5_Initial.cs => 20210417232515_Initial.cs} | 14 +- .../Internal/DatabaseContextModelSnapshot.cs | 14 +- Kyoo/Startup.cs | 14 + Kyoo/Tasks/Crawler.cs | 46 +- Kyoo/Tasks/ExtractMetadata.cs | 14 +- Kyoo/Views/CollectionApi.cs | 16 +- Kyoo/Views/EpisodeApi.cs | 37 +- Kyoo/Views/GenreApi.cs | 8 +- Kyoo/Views/LibraryApi.cs | 20 +- Kyoo/Views/PeopleApi.cs | 4 +- Kyoo/Views/ProviderApi.cs | 6 +- Kyoo/Views/SearchApi.cs | 50 +- Kyoo/Views/SeasonApi.cs | 26 +- Kyoo/Views/ShowApi.cs | 58 +- Kyoo/Views/StudioApi.cs | 8 +- Kyoo/Views/TrackApi.cs | 4 +- Kyoo/Views/VideoApi.cs | 120 +-- Kyoo/Views/WatchApi.cs | 16 +- 33 files changed, 1008 insertions(+), 1067 deletions(-) rename Kyoo.Common/Models/Resources/{ProviderID.cs => Provider.cs} (79%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210325184215_Initial.Designer.cs => 20210417232515_Initial.Designer.cs} (98%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210325184215_Initial.cs => 20210417232515_Initial.cs} (98%) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index ae1d0ccb..fb597293 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -5,284 +5,454 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.Models; +using Kyoo.Models.Exceptions; namespace Kyoo.Controllers { + /// + /// An interface to interract with the database. Every repository is mapped through here. + /// public interface ILibraryManager : IDisposable, IAsyncDisposable { - // Repositories + /// + /// Get the repository corresponding to the T item. + /// + /// The type you want + /// If the item is not found + /// The repository corresponding + IRepository GetRepository() where T : class, IResource; + + /// + /// The repository that handle libraries. + /// ILibraryRepository LibraryRepository { get; } + + /// + /// The repository that handle libraries's items (a wrapper arround shows & collections). + /// ILibraryItemRepository LibraryItemRepository { get; } + + /// + /// The repository that handle collections. + /// ICollectionRepository CollectionRepository { get; } + + /// + /// The repository that handle shows. + /// IShowRepository ShowRepository { get; } + + /// + /// The repository that handle seasons. + /// ISeasonRepository SeasonRepository { get; } + + /// + /// The repository that handle episodes. + /// IEpisodeRepository EpisodeRepository { get; } + + /// + /// The repository that handle tracks. + /// ITrackRepository TrackRepository { get; } + + /// + /// The repository that handle people. + /// IPeopleRepository PeopleRepository { get; } + + /// + /// The repository that handle studios. + /// IStudioRepository StudioRepository { get; } + + /// + /// The repository that handle genres. + /// IGenreRepository GenreRepository { get; } + + /// + /// The repository that handle providers. + /// IProviderRepository ProviderRepository { get; } - // Get by id - Task GetLibrary(int id); - Task GetCollection(int id); - Task GetShow(int id); - Task GetSeason(int id); - Task GetSeason(int showID, int seasonNumber); - Task GetEpisode(int id); - Task GetEpisode(int showID, int seasonNumber, int episodeNumber); - Task GetGenre(int id); - Task GetTrack(int id); - Task GetStudio(int id); - Task GetPeople(int id); - Task GetProvider(int id); + /// + /// 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 + Task Get(int id) where T : class, IResource; - // Get by slug - Task GetLibrary(string slug); - Task GetCollection(string slug); - Task GetShow(string slug); - Task GetSeason(string slug); - Task GetSeason(string showSlug, int seasonNumber); - Task GetEpisode(string slug); - Task GetEpisode(string showSlug, int seasonNumber, int episodeNumber); - Task GetMovieEpisode(string movieSlug); - Task GetTrack(string slug, StreamType type = StreamType.Unknown); - Task GetGenre(string slug); - Task GetStudio(string slug); - Task GetPeople(string slug); - Task GetProvider(string slug); + /// + /// 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 + Task Get(string slug) where T : class, IResource; - // 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 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 + Task Get(Expression> where) where T : class, IResource; + /// + /// 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 + Task Get(int showID, int 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 + Task Get(string showSlug, int 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 + Task Get(int showID, int seasonNumber, int 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 + Task Get(string showSlug, int seasonNumber, int 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 + Task GetTrack(string slug, StreamType type = StreamType.Unknown); + + + /// + /// 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 Task Load([NotNull] T obj, Expression> member) where T : class, IResource where T2 : class, IResource, new(); + /// + /// 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 Task Load([NotNull] T obj, Expression>> member) where T : class, IResource where T2 : class, new(); + /// + /// 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 Task Load([NotNull] T obj, string memberName) where T : class, IResource; + /// + /// Load a related resource without specifing it's type. + /// + /// The source object. + /// The name of the resource to load (case sensitive) Task Load([NotNull] IResource obj, string memberName); - - // Library Items relations + + /// + /// 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 Task> GetItemsFromLibrary(int id, Expression> where = null, Sort sort = default, Pagination limit = default); + + /// + /// Get items (A wrapper arround shows or collections) from a library. + /// + /// The ID of the library + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters Task> GetItemsFromLibrary(int id, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetItemsFromLibrary(id, where, new Sort(sort), limit); - Task> GetItemsFromLibrary(string librarySlug, + /// + /// 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 + Task> GetItemsFromLibrary(string slug, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetItemsFromLibrary(string librarySlug, + + /// + /// Get items (A wrapper arround shows or collections) from a library. + /// + /// The slug of the library + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters + Task> GetItemsFromLibrary(string slug, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetItemsFromLibrary(librarySlug, where, new Sort(sort), limit); + ) => GetItemsFromLibrary(slug, where, new Sort(sort), limit); - // People Role relations + + /// + /// 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 Task> GetPeopleFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default); + + /// + /// Get people's roles from a show. + /// + /// The ID of the show + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters Task> GetPeopleFromShow(int showID, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetPeopleFromShow(showID, where, new Sort(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 Task> GetPeopleFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default); + + /// + /// Get people's roles from a show. + /// + /// The slug of the show + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters Task> GetPeopleFromShow(string showSlug, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetPeopleFromShow(showSlug, where, new Sort(sort), limit); - // Show Role relations - Task> GetRolesFromPeople(int showID, - Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetRolesFromPeople(int showID, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetRolesFromPeople(showID, where, new Sort(sort), limit); - Task> GetRolesFromPeople(string showSlug, + /// + /// 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 + Task> GetRolesFromPeople(int id, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetRolesFromPeople(string showSlug, + + /// + /// Get people's roles from a person. + /// + /// The id of the person + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters + Task> GetRolesFromPeople(int id, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetRolesFromPeople(showSlug, where, new Sort(sort), limit); + ) => GetRolesFromPeople(id, where, new Sort(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 + Task> GetRolesFromPeople(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + + /// + /// Get people's roles from a person. + /// + /// The slug of the person + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters + Task> GetRolesFromPeople(string slug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetRolesFromPeople(slug, where, new Sort(sort), limit); - // Helpers + + /// + /// 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) Task AddShowLink(int showID, int? libraryID, int? 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) Task AddShowLink([NotNull] Show show, Library library, Collection collection); - - // Get all - Task> GetLibraries(Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetCollections(Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetShows(Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetSeasons(Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetEpisodes(Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetTracks(Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetStudios(Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetPeople(Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetGenres(Expression> where = null, - Sort sort = default, - Pagination limit = default); - Task> GetProviders(Expression> where = null, - Sort sort = default, - Pagination limit = default); - - Task> GetLibraries([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetLibraries(where, new Sort(sort), limit); - Task> GetCollections([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetCollections(where, new Sort(sort), limit); - Task> GetShows([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetShows(where, new Sort(sort), limit); - Task> GetTracks([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetTracks(where, new Sort(sort), limit); - Task> GetStudios([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetStudios(where, new Sort(sort), limit); - Task> GetPeople([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetPeople(where, new Sort(sort), limit); - Task> GetGenres([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetGenres(where, new Sort(sort), limit); - Task> GetProviders([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetProviders(where, new Sort(sort), limit); + /// + /// 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 + Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination limit = default) where T : class, IResource; - // Counts - Task GetLibrariesCount(Expression> where = null); - Task GetCollectionsCount(Expression> where = null); - Task GetShowsCount(Expression> where = null); - Task GetSeasonsCount(Expression> where = null); - Task GetEpisodesCount(Expression> where = null); - Task GetTracksCount(Expression> where = null); - Task GetGenresCount(Expression> where = null); - Task GetStudiosCount(Expression> where = null); - Task GetPeopleCount(Expression> where = null); + /// + /// Get all resources with filters + /// + /// A filter function + /// A sort by function + /// How many items to return and where to start + /// The type of resources to load + /// A list of resources that match every filters + Task> GetAll([Optional] Expression> where, + Expression> sort, + Pagination limit = default) where T : class, IResource + { + return GetAll(where, new Sort(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 + Task GetCount(Expression> where = null) where T : class, IResource; + + /// + /// Search for a resource + /// + /// The search query + /// The type of resources + /// A list of 20 items that match the search query + Task> Search(string query) where T : class, IResource; + + /// + /// 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) + Task Create(T item) where T : class, IResource; - // Search - Task> SearchLibraries(string searchQuery); - Task> SearchCollections(string searchQuery); - Task> SearchShows(string searchQuery); - Task> SearchSeasons(string searchQuery); - Task> SearchEpisodes(string searchQuery); - Task> SearchGenres(string searchQuery); - Task> SearchStudios(string searchQuery); - Task> SearchPeople(string searchQuery); + /// + /// 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 + /// The resource edited and completed by database's informations (related items & so on) + Task Edit(T item, bool resetOld) where T : class, IResource; + + /// + /// Delete a resource. + /// + /// The resource to delete + /// The type of resource to delete + Task Delete(T item) where T : class, IResource; - //Register values - Task RegisterLibrary(Library library); - Task RegisterCollection(Collection collection); - Task RegisterShow(Show show); - Task RegisterSeason(Season season); - Task RegisterEpisode(Episode episode); - Task RegisterTrack(Track track); - Task RegisterGenre(Genre genre); - Task RegisterStudio(Studio studio); - Task RegisterPeople(People people); + /// + /// Delete a resource by it's ID. + /// + /// The id of the resource to delete + /// The type of resource to delete + Task Delete(int id) where T : class, IResource; - // Edit values - Task EditLibrary(Library library, bool resetOld); - Task EditCollection(Collection collection, bool resetOld); - Task EditShow(Show show, bool resetOld); - Task EditSeason(Season season, bool resetOld); - Task EditEpisode(Episode episode, bool resetOld); - Task EditTrack(Track track, bool resetOld); - Task EditGenre(Genre genre, bool resetOld); - Task EditStudio(Studio studio, bool resetOld); - Task EditPeople(People people, bool resetOld); - - // Delete values - Task DeleteLibrary(Library library); - Task DeleteCollection(Collection collection); - Task DeleteShow(Show show); - Task DeleteSeason(Season season); - Task DeleteEpisode(Episode episode); - Task DeleteTrack(Track track); - Task DeleteGenre(Genre genre); - Task DeleteStudio(Studio studio); - Task DeletePeople(People people); - - //Delete by slug - Task DeleteLibrary(string slug); - Task DeleteCollection(string slug); - Task DeleteShow(string slug); - Task DeleteSeason(string slug); - Task DeleteEpisode(string slug); - Task DeleteTrack(string slug); - Task DeleteGenre(string slug); - Task DeleteStudio(string slug); - Task DeletePeople(string slug); - - //Delete by id - Task DeleteLibrary(int id); - Task DeleteCollection(int id); - Task DeleteShow(int id); - Task DeleteSeason(int id); - Task DeleteEpisode(int id); - Task DeleteTrack(int id); - Task DeleteGenre(int id); - Task DeleteStudio(int id); - Task DeletePeople(int id); + /// + /// Delete a resource by it's slug. + /// + /// The slug of the resource to delete + /// The type of resource to delete + Task Delete(string slug) where T : class, IResource; } } diff --git a/Kyoo.Common/Controllers/IMetadataProvider.cs b/Kyoo.Common/Controllers/IMetadataProvider.cs index 6cf8a6ac..ce9592f9 100644 --- a/Kyoo.Common/Controllers/IMetadataProvider.cs +++ b/Kyoo.Common/Controllers/IMetadataProvider.cs @@ -6,7 +6,7 @@ namespace Kyoo.Controllers { public interface IMetadataProvider { - ProviderID Provider { get; } + Provider Provider { get; } Task GetCollectionFromName(string name); diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 5beee98b..1caccfc5 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -23,10 +23,10 @@ namespace Kyoo.Controllers public static implicit operator Pagination(int limit) => new(limit); } - public struct Sort + public readonly struct Sort { - public Expression> Key; - public bool Descendant; + public Expression> Key { get; } + public bool Descendant { get; } public Sort(Expression> key, bool descendant = false) { @@ -46,8 +46,8 @@ namespace Kyoo.Controllers return; } - string key = sortBy.Contains(':') ? sortBy.Substring(0, sortBy.IndexOf(':')) : sortBy; - string order = sortBy.Contains(':') ? sortBy.Substring(sortBy.IndexOf(':') + 1) : null; + string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy; + string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null; ParameterExpression param = Expression.Parameter(typeof(T), "x"); MemberExpression property = Expression.Property(param, key); @@ -64,8 +64,13 @@ namespace Kyoo.Controllers }; } } + + public interface IBaseRepository : IDisposable, IAsyncDisposable + { + Type RepositoryType { get; } + } - public interface IRepository : IDisposable, IAsyncDisposable where T : class, IResource + public interface IRepository : IBaseRepository where T : class, IResource { Task Get(int id); Task Get(string slug); @@ -204,7 +209,7 @@ namespace Kyoo.Controllers ) => GetFromPeople(showSlug, where, new Sort(sort), limit); } - public interface IProviderRepository : IRepository + public interface IProviderRepository : IRepository { Task> GetMetadataID(Expression> where = null, Sort sort = default, diff --git a/Kyoo.Common/Controllers/IThumbnailsManager.cs b/Kyoo.Common/Controllers/IThumbnailsManager.cs index 5d2597cf..2282981a 100644 --- a/Kyoo.Common/Controllers/IThumbnailsManager.cs +++ b/Kyoo.Common/Controllers/IThumbnailsManager.cs @@ -11,7 +11,7 @@ namespace Kyoo.Controllers Task Validate(Season season, bool alwaysDownload = false); Task Validate(Episode episode, bool alwaysDownload = false); Task Validate(People actors, bool alwaysDownload = false); - Task Validate(ProviderID actors, bool alwaysDownload = false); + Task Validate(Provider actors, bool alwaysDownload = false); Task GetShowPoster([NotNull] Show show); Task GetShowLogo([NotNull] Show show); @@ -19,6 +19,6 @@ namespace Kyoo.Controllers Task GetSeasonPoster([NotNull] Season season); Task GetEpisodeThumb([NotNull] Episode episode); Task GetPeoplePoster([NotNull] People people); - Task GetProviderLogo([NotNull] ProviderID provider); + Task GetProviderLogo([NotNull] Provider provider); } } diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 7cae9b4d..366cb0ea 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -4,248 +4,232 @@ 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; + + + 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; + } + + /// + /// 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; } - public IGenreRepository GenreRepository { get; } - public IStudioRepository StudioRepository { 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; } - public LibraryManager(ILibraryRepository libraryRepository, - ILibraryItemRepository libraryItemRepository, - ICollectionRepository collectionRepository, - IShowRepository showRepository, - ISeasonRepository seasonRepository, - IEpisodeRepository episodeRepository, - ITrackRepository trackRepository, - IGenreRepository genreRepository, - IStudioRepository studioRepository, - IProviderRepository providerRepository, - IPeopleRepository peopleRepository) + + /// + /// 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 { - LibraryRepository = libraryRepository; - LibraryItemRepository = libraryItemRepository; - CollectionRepository = collectionRepository; - ShowRepository = showRepository; - SeasonRepository = seasonRepository; - EpisodeRepository = episodeRepository; - TrackRepository = trackRepository; - GenreRepository = genreRepository; - StudioRepository = studioRepository; - ProviderRepository = providerRepository; - PeopleRepository = peopleRepository; - } - - public void Dispose() - { - LibraryRepository.Dispose(); - CollectionRepository.Dispose(); - ShowRepository.Dispose(); - SeasonRepository.Dispose(); - EpisodeRepository.Dispose(); - TrackRepository.Dispose(); - GenreRepository.Dispose(); - StudioRepository.Dispose(); - PeopleRepository.Dispose(); - ProviderRepository.Dispose(); - } - - public async ValueTask DisposeAsync() - { - await Task.WhenAll( - LibraryRepository.DisposeAsync().AsTask(), - CollectionRepository.DisposeAsync().AsTask(), - ShowRepository.DisposeAsync().AsTask(), - SeasonRepository.DisposeAsync().AsTask(), - EpisodeRepository.DisposeAsync().AsTask(), - TrackRepository.DisposeAsync().AsTask(), - GenreRepository.DisposeAsync().AsTask(), - StudioRepository.DisposeAsync().AsTask(), - PeopleRepository.DisposeAsync().AsTask(), - ProviderRepository.DisposeAsync().AsTask() - ); + return GetRepository().Get(id); } - public Task GetLibrary(int 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 LibraryRepository.Get(id); + return GetRepository().Get(slug); } - public Task GetCollection(int id) + /// + /// 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 CollectionRepository.Get(id); + return GetRepository().Get(where); } - public Task GetShow(int id) - { - return ShowRepository.Get(id); - } - - public Task GetSeason(int id) - { - return SeasonRepository.Get(id); - } - - public Task GetSeason(int showID, int seasonNumber) + /// + /// 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); } - - public Task GetEpisode(int id) + + /// + /// 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 EpisodeRepository.Get(id); + return SeasonRepository.Get(showSlug, seasonNumber); } - public Task GetEpisode(int showID, int seasonNumber, int episodeNumber) + /// + /// 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 GetTrack(string slug, StreamType type = StreamType.Unknown) { return TrackRepository.Get(slug, type); } - public Task GetGenre(int id) + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() { - return GenreRepository.Get(id); + foreach (IBaseRepository repo in _repositories) + repo.Dispose(); + GC.SuppressFinalize(this); } - public Task GetStudio(int id) + /// + /// 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() { - return StudioRepository.Get(id); + await Task.WhenAll(_repositories.Select(x => x.DisposeAsync().AsTask())); } - public Task GetPeople(int id) + /// + /// 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 { - return PeopleRepository.Get(id); - } - - public Task GetProvider(int id) - { - return ProviderRepository.Get(id); - } - - public Task GetLibrary(string slug) - { - return LibraryRepository.Get(slug); - } - - public Task GetCollection(string slug) - { - return CollectionRepository.Get(slug); - } - - public Task GetShow(string slug) - { - return ShowRepository.Get(slug); - } - - public Task GetSeason(string slug) - { - return SeasonRepository.Get(slug); - } - - public Task GetSeason(string showSlug, int seasonNumber) - { - return SeasonRepository.Get(showSlug, seasonNumber); - } - - public Task GetEpisode(string slug) - { - return EpisodeRepository.Get(slug); - } - - public Task GetEpisode(string showSlug, int seasonNumber, int episodeNumber) - { - return EpisodeRepository.Get(showSlug, seasonNumber, episodeNumber); - } - - public Task GetMovieEpisode(string movieSlug) - { - return EpisodeRepository.Get(movieSlug); - } - - public Task GetTrack(int id) - { - return TrackRepository.Get(id); - } - - public Task GetGenre(string slug) - { - return GenreRepository.Get(slug); - } - - public Task GetStudio(string slug) - { - return StudioRepository.Get(slug); - } - - public Task GetPeople(string slug) - { - return PeopleRepository.Get(slug); - } - - public Task GetProvider(string slug) - { - return ProviderRepository.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); + if (_repositories.FirstOrDefault(x => x.RepositoryType == typeof(T)) is IRepository ret) + return ret; + throw new ItemNotFound(); } + /// + /// 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() @@ -255,6 +239,14 @@ namespace Kyoo.Controllers 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() @@ -264,14 +256,30 @@ namespace Kyoo.Controllers return Load(obj, Utility.GetPropertyName(member)); } - public async Task Load(T obj, string 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, member); + await Load(obj as IResource, memberName); return obj; } - private async Task SetRelation(T1 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) @@ -282,12 +290,17 @@ namespace Kyoo.Controllers inverse(item, obj); } - public Task Load(IResource obj, string member) + /// + /// 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) switch + return (obj, member: memberName) switch { (Library l, nameof(Library.Providers)) => ProviderRepository .GetAll(x => x.Libraries.Any(y => y.ID == obj.ID)) @@ -426,85 +439,23 @@ namespace Kyoo.Controllers .Then(x => p.Roles = x), - (ProviderID p, nameof(ProviderID.Libraries)) => LibraryRepository + (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 {member} of {obj.Slug}.") + _ => throw new ArgumentException($"Couldn't find a way to load {memberName} of {obj.Slug}.") }; } - - public Task> GetLibraries(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - return LibraryRepository.GetAll(where, sort, page); - } - - public Task> GetCollections(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - return CollectionRepository.GetAll(where, sort, page); - } - - public Task> GetShows(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return ShowRepository.GetAll(where, sort, limit); - } - - public Task> GetSeasons(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return SeasonRepository.GetAll(where, sort, limit); - } - - public Task> GetEpisodes(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return EpisodeRepository.GetAll(where, sort, limit); - } - - public Task> GetTracks(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - return TrackRepository.GetAll(where, sort, page); - } - - public Task> GetStudios(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - return StudioRepository.GetAll(where, sort, page); - } - - public Task> GetPeople(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - return PeopleRepository.GetAll(where, sort, page); - } - - public Task> GetGenres(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - return GenreRepository.GetAll(where, sort, page); - } - - public Task> GetProviders(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - return ProviderRepository.GetAll(where, sort, page); - } + /// + /// 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, @@ -513,366 +464,206 @@ namespace Kyoo.Controllers return LibraryItemRepository.GetFromLibrary(id, where, sort, limit); } - public Task> GetItemsFromLibrary(string librarySlug, - Expression> where = null, + /// + /// 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(librarySlug, where, sort, limit); + return LibraryItemRepository.GetFromLibrary(slug, where, sort, limit); } - - public Task> GetPeopleFromShow(int showID, + + /// + /// 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); } - - public Task> GetPeopleFromShow(string showSlug, + + /// + /// 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, + 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, + 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, + /// + /// 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); } - public Task GetLibrariesCount(Expression> where = null) - { - return LibraryRepository.GetCount(where); - } - - public Task GetCollectionsCount(Expression> where = null) - { - return CollectionRepository.GetCount(where); - } - - public Task GetShowsCount(Expression> where = null) - { - return ShowRepository.GetCount(where); - } - - public Task GetSeasonsCount(Expression> where = null) - { - return SeasonRepository.GetCount(where); - } - - public Task GetEpisodesCount(Expression> where = null) - { - return EpisodeRepository.GetCount(where); - } - - public Task GetTracksCount(Expression> where = null) - { - return TrackRepository.GetCount(where); - } - - public Task GetGenresCount(Expression> where = null) - { - return GenreRepository.GetCount(where); - } - - public Task GetStudiosCount(Expression> where = null) - { - return StudioRepository.GetCount(where); - } - - public Task GetPeopleCount(Expression> where = null) - { - return PeopleRepository.GetCount(where); - } - + /// + /// 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 AddShowLink(show.ID, library?.ID, collection?.ID); - } - - public Task> SearchLibraries(string searchQuery) - { - return LibraryRepository.Search(searchQuery); + return ShowRepository.AddShowLink(show.ID, library?.ID, collection?.ID); } - public Task> SearchCollections(string searchQuery) + /// + /// 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 CollectionRepository.Search(searchQuery); + return GetRepository().GetAll(where, sort, limit); } - public Task> SearchShows(string searchQuery) + /// + /// 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 ShowRepository.Search(searchQuery); + return GetRepository().GetCount(where); } - public Task> SearchSeasons(string searchQuery) + /// + /// 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 SeasonRepository.Search(searchQuery); + return GetRepository().Search(query); } - public Task> SearchEpisodes(string searchQuery) + /// + /// 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 EpisodeRepository.Search(searchQuery); + return GetRepository().Create(item); } - public Task> SearchGenres(string searchQuery) + /// + /// 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 + /// 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 GenreRepository.Search(searchQuery); + return GetRepository().Edit(item, resetOld); } - public Task> SearchStudios(string searchQuery) + /// + /// Delete a resource. + /// + /// The resource to delete + /// The type of resource to delete + public Task Delete(T item) + where T : class, IResource { - return StudioRepository.Search(searchQuery); + return GetRepository().Delete(item); } - public Task> SearchPeople(string searchQuery) + /// + /// Delete a resource by it's ID. + /// + /// The id of the resource to delete + /// The type of resource to delete + public Task Delete(int id) + where T : class, IResource { - return PeopleRepository.Search(searchQuery); - } - - public Task RegisterLibrary(Library library) - { - return LibraryRepository.Create(library); + return GetRepository().Delete(id); } - public Task RegisterCollection(Collection collection) + /// + /// Delete a resource by it's slug. + /// + /// The slug of the resource to delete + /// The type of resource to delete + public Task Delete(string slug) + where T : class, IResource { - return CollectionRepository.Create(collection); - } - - public Task RegisterShow(Show show) - { - return ShowRepository.Create(show); - } - - public Task RegisterSeason(Season season) - { - return SeasonRepository.Create(season); - } - - public Task RegisterEpisode(Episode episode) - { - return EpisodeRepository.Create(episode); - } - - public Task RegisterTrack(Track track) - { - return TrackRepository.Create(track); - } - - public Task RegisterGenre(Genre genre) - { - return GenreRepository.Create(genre); - } - - public Task RegisterStudio(Studio studio) - { - return StudioRepository.Create(studio); - } - - public Task RegisterPeople(People people) - { - return PeopleRepository.Create(people); - } - - public Task EditLibrary(Library library, bool resetOld) - { - return LibraryRepository.Edit(library, resetOld); - } - - public Task EditCollection(Collection collection, bool resetOld) - { - return CollectionRepository.Edit(collection, resetOld); - } - - public Task EditShow(Show show, bool resetOld) - { - return ShowRepository.Edit(show, resetOld); - } - - public Task EditSeason(Season season, bool resetOld) - { - return SeasonRepository.Edit(season, resetOld); - } - - public Task EditEpisode(Episode episode, bool resetOld) - { - return EpisodeRepository.Edit(episode, resetOld); - } - - public Task EditTrack(Track track, bool resetOld) - { - return TrackRepository.Edit(track, resetOld); - } - - public Task EditGenre(Genre genre, bool resetOld) - { - return GenreRepository.Edit(genre, resetOld); - } - - public Task EditStudio(Studio studio, bool resetOld) - { - return StudioRepository.Edit(studio, resetOld); - } - - public Task EditPeople(People people, bool resetOld) - { - return PeopleRepository.Edit(people, resetOld); - } - - public Task DeleteLibrary(Library library) - { - return LibraryRepository.Delete(library); - } - - public Task DeleteCollection(Collection collection) - { - return CollectionRepository.Delete(collection); - } - - public Task DeleteShow(Show show) - { - return ShowRepository.Delete(show); - } - - public Task DeleteSeason(Season season) - { - return SeasonRepository.Delete(season); - } - - public Task DeleteEpisode(Episode episode) - { - return EpisodeRepository.Delete(episode); - } - - public Task DeleteTrack(Track track) - { - return TrackRepository.Delete(track); - } - - public Task DeleteGenre(Genre genre) - { - return GenreRepository.Delete(genre); - } - - public Task DeleteStudio(Studio studio) - { - return StudioRepository.Delete(studio); - } - - public Task DeletePeople(People people) - { - return PeopleRepository.Delete(people); - } - - public Task DeleteLibrary(string library) - { - return LibraryRepository.Delete(library); - } - - public Task DeleteCollection(string collection) - { - return CollectionRepository.Delete(collection); - } - - public Task DeleteShow(string show) - { - return ShowRepository.Delete(show); - } - - public Task DeleteSeason(string season) - { - return SeasonRepository.Delete(season); - } - - public Task DeleteEpisode(string episode) - { - return EpisodeRepository.Delete(episode); - } - - public Task DeleteTrack(string track) - { - return TrackRepository.Delete(track); - } - - public Task DeleteGenre(string genre) - { - return GenreRepository.Delete(genre); - } - - public Task DeleteStudio(string studio) - { - return StudioRepository.Delete(studio); - } - - public Task DeletePeople(string people) - { - return PeopleRepository.Delete(people); - } - - public Task DeleteLibrary(int library) - { - return LibraryRepository.Delete(library); - } - - public Task DeleteCollection(int collection) - { - return CollectionRepository.Delete(collection); - } - - public Task DeleteShow(int show) - { - return ShowRepository.Delete(show); - } - - public Task DeleteSeason(int season) - { - return SeasonRepository.Delete(season); - } - - public Task DeleteEpisode(int episode) - { - return EpisodeRepository.Delete(episode); - } - - public Task DeleteTrack(int track) - { - return TrackRepository.Delete(track); - } - - public Task DeleteGenre(int genre) - { - return GenreRepository.Delete(genre); - } - - public Task DeleteStudio(int studio) - { - return StudioRepository.Delete(studio); - } - - public Task DeletePeople(int people) - { - return PeopleRepository.Delete(people); + return GetRepository().Delete(slug); } } } diff --git a/Kyoo.Common/Models/MetadataID.cs b/Kyoo.Common/Models/MetadataID.cs index 71e1946a..cc7985ac 100644 --- a/Kyoo.Common/Models/MetadataID.cs +++ b/Kyoo.Common/Models/MetadataID.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models { [SerializeIgnore] public int ID { get; set; } [SerializeIgnore] public int ProviderID { get; set; } - public virtual ProviderID Provider {get; set; } + public virtual Provider Provider {get; set; } [SerializeIgnore] public int? ShowID { get; set; } [SerializeIgnore] public virtual Show Show { get; set; } @@ -25,7 +25,7 @@ namespace Kyoo.Models public MetadataID() { } - public MetadataID(ProviderID provider, string dataID, string link) + public MetadataID(Provider provider, string dataID, string link) { Provider = provider; DataID = dataID; diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index c1e17b56..86cb324d 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -11,20 +11,20 @@ namespace Kyoo.Models public string Name { get; set; } public string[] Paths { get; set; } - [EditableRelation] [LoadableRelation] public virtual ICollection Providers { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection Providers { get; set; } [LoadableRelation] public virtual ICollection Shows { get; set; } [LoadableRelation] public virtual ICollection Collections { get; set; } #if ENABLE_INTERNAL_LINKS - [SerializeIgnore] public virtual ICollection> ProviderLinks { get; set; } + [SerializeIgnore] public virtual ICollection> ProviderLinks { get; set; } [SerializeIgnore] public virtual ICollection> ShowLinks { get; set; } [SerializeIgnore] public virtual ICollection> CollectionLinks { get; set; } #endif public Library() { } - public Library(string slug, string name, IEnumerable paths, IEnumerable providers) + public Library(string slug, string name, IEnumerable paths, IEnumerable providers) { Slug = slug; Name = name; diff --git a/Kyoo.Common/Models/Resources/ProviderID.cs b/Kyoo.Common/Models/Resources/Provider.cs similarity index 79% rename from Kyoo.Common/Models/Resources/ProviderID.cs rename to Kyoo.Common/Models/Resources/Provider.cs index 2d1ec3d1..6a19f27c 100644 --- a/Kyoo.Common/Models/Resources/ProviderID.cs +++ b/Kyoo.Common/Models/Resources/Provider.cs @@ -3,7 +3,7 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { - public class ProviderID : IResource + public class Provider : IResource { public int ID { get; set; } public string Slug { get; set; } @@ -13,20 +13,20 @@ namespace Kyoo.Models [LoadableRelation] public virtual ICollection Libraries { get; set; } #if ENABLE_INTERNAL_LINKS - [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } + [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } [SerializeIgnore] public virtual ICollection MetadataLinks { get; set; } #endif - public ProviderID() { } + public Provider() { } - public ProviderID(string name, string logo) + public Provider(string name, string logo) { Slug = Utility.ToSlug(name); Name = name; Logo = logo; } - public ProviderID(int id, string name, string logo) + public Provider(int id, string name, string logo) { ID = id; Slug = Utility.ToSlug(name); diff --git a/Kyoo.Common/Models/WatchItem.cs b/Kyoo.Common/Models/WatchItem.cs index ccf5f602..8ab7b5dc 100644 --- a/Kyoo.Common/Models/WatchItem.cs +++ b/Kyoo.Common/Models/WatchItem.cs @@ -109,18 +109,18 @@ namespace Kyoo.Models if (!ep.Show.IsMovie) { if (ep.EpisodeNumber > 1) - previous = await library.GetEpisode(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber - 1); + previous = await library.Get(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber - 1); else if (ep.SeasonNumber > 1) { - int count = await library.GetEpisodesCount(x => x.ShowID == ep.ShowID + int count = await library.GetCount(x => x.ShowID == ep.ShowID && x.SeasonNumber == ep.SeasonNumber - 1); - previous = await library.GetEpisode(ep.ShowID, ep.SeasonNumber - 1, count); + previous = await library.Get(ep.ShowID, ep.SeasonNumber - 1, count); } - if (ep.EpisodeNumber >= await library.GetEpisodesCount(x => x.SeasonID == ep.SeasonID)) - next = await library.GetEpisode(ep.ShowID, ep.SeasonNumber + 1, 1); + if (ep.EpisodeNumber >= await library.GetCount(x => x.SeasonID == ep.SeasonID)) + next = await library.Get(ep.ShowID, ep.SeasonNumber + 1, 1); else - next = await library.GetEpisode(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber + 1); + next = await library.Get(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber + 1); } return new WatchItem(ep.ID, diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 5da8fcc0..1cea2af3 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -24,7 +24,9 @@ namespace Kyoo.Controllers { Database = database; } - + + public Type RepositoryType => typeof(T); + public virtual void Dispose() { Database.Dispose(); diff --git a/Kyoo.WebApp b/Kyoo.WebApp index ddf3337a..da35a725 160000 --- a/Kyoo.WebApp +++ b/Kyoo.WebApp @@ -1 +1 @@ -Subproject commit ddf3337acb766ae9e0987044ae38130d94fe60ad +Subproject commit da35a725a3e47db0994a697595aec4a10a4886e3 diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 79cc80b1..2d90b889 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -8,10 +8,10 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class ProviderRepository : LocalRepository, IProviderRepository + public class ProviderRepository : LocalRepository, IProviderRepository { private readonly DatabaseContext _database; - protected override Expression> DefaultSort => x => x.Slug; + protected override Expression> DefaultSort => x => x.Slug; public ProviderRepository(DatabaseContext database) : base(database) @@ -19,7 +19,7 @@ namespace Kyoo.Controllers _database = database; } - public override async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Providers .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) @@ -28,7 +28,7 @@ namespace Kyoo.Controllers .ToListAsync(); } - public override async Task Create(ProviderID obj) + public override async Task Create(Provider obj) { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; @@ -36,7 +36,7 @@ namespace Kyoo.Controllers return obj; } - public override async Task Delete(ProviderID obj) + public override async Task Delete(Provider obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 68876274..b75e3231 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -92,7 +92,7 @@ namespace Kyoo.Controllers await DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}"); } - public async Task Validate(ProviderID provider, bool alwaysDownload) + public async Task Validate(Provider provider, bool alwaysDownload) { if (provider.Logo == null) return; @@ -145,7 +145,7 @@ namespace Kyoo.Controllers return Task.FromResult(thumbPath.StartsWith(_peoplePath) ? thumbPath : null); } - public Task GetProviderLogo(ProviderID provider) + public Task GetProviderLogo(Provider provider) { if (provider == null) throw new ArgumentNullException(nameof(provider)); diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 435b01f7..0538ed67 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -27,7 +27,7 @@ namespace Kyoo public DbSet Genres { get; set; } public DbSet People { get; set; } public DbSet Studios { get; set; } - public DbSet Providers { get; set; } + public DbSet Providers { get; set; } public DbSet MetadataIds { get; set; } public DbSet PeopleRoles { get; set; } @@ -74,17 +74,17 @@ namespace Kyoo .Property(t => t.IsForced) .ValueGeneratedNever(); - modelBuilder.Entity() + modelBuilder.Entity() .HasMany(x => x.Libraries) .WithMany(x => x.Providers) - .UsingEntity>( + .UsingEntity>( y => y .HasOne(x => x.First) .WithMany(x => x.ProviderLinks), y => y .HasOne(x => x.Second) .WithMany(x => x.LibraryLinks), - y => y.HasKey(Link.PrimaryKey)); + y => y.HasKey(Link.PrimaryKey)); modelBuilder.Entity() .HasMany(x => x.Libraries) @@ -160,7 +160,7 @@ namespace Kyoo modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); - modelBuilder.Entity().Property(x => x.Slug).IsRequired(); + modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); modelBuilder.Entity().Property(x => x.Slug).IsRequired(); @@ -182,7 +182,7 @@ namespace Kyoo modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); - modelBuilder.Entity() + modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210325184215_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.Designer.cs similarity index 98% rename from Kyoo/Models/DatabaseMigrations/Internal/20210325184215_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.Designer.cs index b0c7add7..11722606 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210325184215_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210325184215_Initial")] + [Migration("20210417232515_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -179,7 +179,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Link"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => + modelBuilder.Entity("Kyoo.Models.Link", b => { b.Property("FirstID") .HasColumnType("integer"); @@ -191,7 +191,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("SecondID"); - b.ToTable("Link"); + b.ToTable("Link"); }); modelBuilder.Entity("Kyoo.Models.Link", b => @@ -320,7 +320,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("PeopleRoles"); }); - modelBuilder.Entity("Kyoo.Models.ProviderID", b => + modelBuilder.Entity("Kyoo.Models.Provider", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -563,7 +563,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Second"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => + modelBuilder.Entity("Kyoo.Models.Link", b => { b.HasOne("Kyoo.Models.Library", "First") .WithMany("ProviderLinks") @@ -571,7 +571,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Kyoo.Models.ProviderID", "Second") + b.HasOne("Kyoo.Models.Provider", "Second") .WithMany("LibraryLinks") .HasForeignKey("SecondID") .OnDelete(DeleteBehavior.Cascade) @@ -632,7 +632,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("PeopleID") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Kyoo.Models.ProviderID", "Provider") + b.HasOne("Kyoo.Models.Provider", "Provider") .WithMany("MetadataLinks") .HasForeignKey("ProviderID") .OnDelete(DeleteBehavior.Cascade) @@ -744,7 +744,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.ProviderID", b => + modelBuilder.Entity("Kyoo.Models.Provider", b => { b.Navigation("LibraryLinks"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210325184215_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.cs similarity index 98% rename from Kyoo/Models/DatabaseMigrations/Internal/20210325184215_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.cs index f3825e97..56051cb1 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210325184215_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.cs @@ -128,7 +128,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }); migrationBuilder.CreateTable( - name: "Link", + name: "Link", columns: table => new { FirstID = table.Column(type: "integer", nullable: false), @@ -136,15 +136,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }, constraints: table => { - table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); + table.PrimaryKey("PK_Link", x => new { x.FirstID, x.SecondID }); table.ForeignKey( - name: "FK_Link_Libraries_FirstID", + name: "FK_Link_Libraries_FirstID", column: x => x.FirstID, principalTable: "Libraries", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_Link_Providers_SecondID", + name: "FK_Link_Providers_SecondID", column: x => x.SecondID, principalTable: "Providers", principalColumn: "ID", @@ -459,8 +459,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal column: "SecondID"); migrationBuilder.CreateIndex( - name: "IX_Link_SecondID", - table: "Link", + name: "IX_Link_SecondID", + table: "Link", column: "SecondID"); migrationBuilder.CreateIndex( @@ -559,7 +559,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Link"); migrationBuilder.DropTable( - name: "Link"); + name: "Link"); migrationBuilder.DropTable( name: "Link"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 91eef22c..11d6b186 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -177,7 +177,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("Link"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => + modelBuilder.Entity("Kyoo.Models.Link", b => { b.Property("FirstID") .HasColumnType("integer"); @@ -189,7 +189,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("SecondID"); - b.ToTable("Link"); + b.ToTable("Link"); }); modelBuilder.Entity("Kyoo.Models.Link", b => @@ -318,7 +318,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.ToTable("PeopleRoles"); }); - modelBuilder.Entity("Kyoo.Models.ProviderID", b => + modelBuilder.Entity("Kyoo.Models.Provider", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -561,7 +561,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Second"); }); - modelBuilder.Entity("Kyoo.Models.Link", b => + modelBuilder.Entity("Kyoo.Models.Link", b => { b.HasOne("Kyoo.Models.Library", "First") .WithMany("ProviderLinks") @@ -569,7 +569,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Kyoo.Models.ProviderID", "Second") + b.HasOne("Kyoo.Models.Provider", "Second") .WithMany("LibraryLinks") .HasForeignKey("SecondID") .OnDelete(DeleteBehavior.Cascade) @@ -630,7 +630,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasForeignKey("PeopleID") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Kyoo.Models.ProviderID", "Provider") + b.HasOne("Kyoo.Models.Provider", "Provider") .WithMany("MetadataLinks") .HasForeignKey("ProviderID") .OnDelete(DeleteBehavior.Cascade) @@ -742,7 +742,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Navigation("Roles"); }); - modelBuilder.Entity("Kyoo.Models.ProviderID", b => + modelBuilder.Entity("Kyoo.Models.Provider", b => { b.Navigation("LibraryLinks"); diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index ac05a36d..8e1485e3 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -147,6 +147,20 @@ namespace Kyoo }); + // TODO Add custom method to the service container and expose those methods to the plugin + // TODO Add for example a AddRepository that will automatically register the complex interface, the IRepository and the IBaseRepository + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 41028ce9..b6ab53de 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -33,7 +33,7 @@ namespace Kyoo.Controllers { using IServiceScope serviceScope = _serviceProvider.CreateScope(); await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); - return (await libraryManager!.GetLibraries()).Select(x => x.Slug); + return (await libraryManager!.GetAll()).Select(x => x.Slug); } public int? Progress() @@ -58,23 +58,23 @@ namespace Kyoo.Controllers using IServiceScope serviceScope = _serviceProvider.CreateScope(); await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); - foreach (Show show in await libraryManager!.GetShows()) + foreach (Show show in await libraryManager!.GetAll()) if (!Directory.Exists(show.Path)) - await libraryManager.DeleteShow(show); + await libraryManager.Delete(show); - ICollection episodes = await libraryManager.GetEpisodes(); + ICollection episodes = await libraryManager.GetAll(); foreach (Episode episode in episodes) if (!File.Exists(episode.Path)) - await libraryManager.DeleteEpisode(episode); + await libraryManager.Delete(episode); - ICollection tracks = await libraryManager.GetTracks(); + ICollection tracks = await libraryManager.GetAll(); foreach (Track track in tracks) if (!File.Exists(track.Path)) - await libraryManager.DeleteTrack(track); + await libraryManager.Delete(track); ICollection libraries = argument == null - ? await libraryManager.GetLibraries() - : new [] { await libraryManager.GetLibrary(argument)}; + ? await libraryManager.GetAll() + : new [] { await libraryManager.Get(argument)}; if (argument != null && libraries.First() == null) throw new ArgumentException($"No library found with the name {argument}"); @@ -160,7 +160,7 @@ namespace Kyoo.Controllers } string episodePath = match.Groups["Episode"].Value; - Episode episode = await libraryManager!.GetEpisode(x => x.Path.StartsWith(episodePath)); + Episode episode = await libraryManager!.Get(x => x.Path.StartsWith(episodePath)); if (episode == null) { @@ -180,7 +180,7 @@ namespace Kyoo.Controllers Episode = episode }; - await libraryManager.RegisterTrack(track); + await libraryManager.Create(track); Console.WriteLine($"Registering subtitle at: {path}."); } @@ -215,7 +215,7 @@ namespace Kyoo.Controllers bool isMovie = seasonNumber == -1 && episodeNumber == -1 && absoluteNumber == -1; Show show = await GetShow(libraryManager, showName, showPath, isMovie, library); if (isMovie) - await libraryManager!.RegisterEpisode(await GetMovie(show, path)); + await libraryManager!.Create(await GetMovie(show, path)); else { Season season = await GetSeason(libraryManager, show, seasonNumber, library); @@ -226,7 +226,7 @@ namespace Kyoo.Controllers absoluteNumber, path, library); - await libraryManager!.RegisterEpisode(episode); + await libraryManager!.Create(episode); } await libraryManager.AddShowLink(show, library, collection); @@ -250,19 +250,19 @@ namespace Kyoo.Controllers { if (string.IsNullOrEmpty(collectionName)) return null; - Collection collection = await libraryManager.GetCollection(Utility.ToSlug(collectionName)); + Collection collection = await libraryManager.Get(Utility.ToSlug(collectionName)); if (collection != null) return collection; collection = await _metadataProvider.GetCollectionFromName(collectionName, library); try { - await libraryManager.RegisterCollection(collection); + await libraryManager.Create(collection); return collection; } catch (DuplicatedItemException) { - return await libraryManager.GetCollection(collection.Slug); + return await libraryManager.Get(collection.Slug); } } @@ -272,7 +272,7 @@ namespace Kyoo.Controllers bool isMovie, Library library) { - Show old = await libraryManager.GetShow(x => x.Path == showPath); + Show old = await libraryManager.Get(x => x.Path == showPath); if (old != null) { await libraryManager.Load(old, x => x.ExternalIDs); @@ -284,18 +284,18 @@ namespace Kyoo.Controllers try { - show = await libraryManager.RegisterShow(show); + show = await libraryManager.Create(show); } catch (DuplicatedItemException) { - old = await libraryManager.GetShow(show.Slug); + old = await libraryManager.Get(show.Slug); if (old.Path == showPath) { await libraryManager.Load(old, x => x.ExternalIDs); return old; } show.Slug += $"-{show.StartYear}"; - await libraryManager.RegisterShow(show); + await libraryManager.Create(show); } await _thumbnailsManager.Validate(show); return show; @@ -308,18 +308,18 @@ namespace Kyoo.Controllers { if (seasonNumber == -1) return default; - Season season = await libraryManager.GetSeason(show.Slug, seasonNumber); + Season season = await libraryManager.Get(show.Slug, seasonNumber); if (season == null) { season = await _metadataProvider.GetSeason(show, seasonNumber, library); try { - await libraryManager.RegisterSeason(season); + await libraryManager.Create(season); await _thumbnailsManager.Validate(season); } catch (DuplicatedItemException) { - season = await libraryManager.GetSeason(show.Slug, season.SeasonNumber); + season = await libraryManager.Get(show.Slug, season.SeasonNumber); } } season.Show = show; diff --git a/Kyoo/Tasks/ExtractMetadata.cs b/Kyoo/Tasks/ExtractMetadata.cs index 73ed031b..d9f47e43 100644 --- a/Kyoo/Tasks/ExtractMetadata.cs +++ b/Kyoo/Tasks/ExtractMetadata.cs @@ -45,22 +45,22 @@ namespace Kyoo.Tasks case "show": case "shows": Show show = await (int.TryParse(slug, out id) - ? _library!.GetShow(id) - : _library!.GetShow(slug)); + ? _library!.Get(id) + : _library!.Get(slug)); await ExtractShow(show, thumbs, subs, token); break; case "season": case "seasons": Season season = await (int.TryParse(slug, out id) - ? _library!.GetSeason(id) - : _library!.GetSeason(slug)); + ? _library!.Get(id) + : _library!.Get(slug)); await ExtractSeason(season, thumbs, subs, token); break; case "episode": case "episodes": Episode episode = await (int.TryParse(slug, out id) - ? _library!.GetEpisode(id) - : _library!.GetEpisode(slug)); + ? _library!.Get(id) + : _library!.Get(slug)); await ExtractEpisode(episode, thumbs, subs); break; } @@ -105,7 +105,7 @@ namespace Kyoo.Tasks .Where(x => x.Type != StreamType.Attachment) .Concat(episode.Tracks.Where(x => x.IsExternal)) .ToList(); - await _library.EditEpisode(episode, false); + await _library.Edit(episode, false); } } diff --git a/Kyoo/Views/CollectionApi.cs b/Kyoo/Views/CollectionApi.cs index 5bba0650..0d5b0be5 100644 --- a/Kyoo/Views/CollectionApi.cs +++ b/Kyoo/Views/CollectionApi.cs @@ -35,12 +35,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetShows( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Collections.Any(y => y.ID == id)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetCollection(id) == null) + if (!resources.Any() && await _libraryManager.Get(id) == null) return NotFound(); return Page(resources, limit); } @@ -61,12 +61,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetShows( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Collections.Any(y => y.Slug == slug)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetCollection(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } @@ -87,12 +87,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetLibraries( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Collections.Any(y => y.ID == id)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetCollection(id) == null) + if (!resources.Any() && await _libraryManager.Get(id) == null) return NotFound(); return Page(resources, limit); } @@ -113,12 +113,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetLibraries( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Collections.Any(y => y.Slug == slug)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetCollection(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } diff --git a/Kyoo/Views/EpisodeApi.cs b/Kyoo/Views/EpisodeApi.cs index c0c01a18..7bc76af7 100644 --- a/Kyoo/Views/EpisodeApi.cs +++ b/Kyoo/Views/EpisodeApi.cs @@ -35,42 +35,42 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task> GetShow(int episodeID) { - return await _libraryManager.GetShow(x => x.Episodes.Any(y => y.ID == episodeID)); + return await _libraryManager.Get(x => x.Episodes.Any(y => y.ID == episodeID)); } [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/show")] [Authorize(Policy = "Read")] - public async Task> GetShow(string showSlug) + public async Task> GetShow(string showSlug, int seasonNumber, int episodeNumber) { - return await _libraryManager.GetShow(showSlug); + return await _libraryManager.Get(showSlug); } [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/show")] [Authorize(Policy = "Read")] - public async Task> GetShow(int showID, int _) + public async Task> GetShow(int showID, int seasonNumber, int episodeNumber) { - return await _libraryManager.GetShow(showID); + return await _libraryManager.Get(showID); } [HttpGet("{episodeID:int}/season")] [Authorize(Policy = "Read")] public async Task> GetSeason(int episodeID) { - return await _libraryManager.GetSeason(x => x.Episodes.Any(y => y.ID == episodeID)); + return await _libraryManager.Get(x => x.Episodes.Any(y => y.ID == episodeID)); } [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/season")] [Authorize(Policy = "Read")] - public async Task> GetSeason(string showSlug, int seasonNuber) + public async Task> GetSeason(string showSlug, int seasonNumber, int episodeNumber) { - return await _libraryManager.GetSeason(showSlug, seasonNuber); + return await _libraryManager.Get(showSlug, seasonNumber); } [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/season")] [Authorize(Policy = "Read")] - public async Task> GetSeason(int showID, int seasonNumber) + public async Task> GetSeason(int showID, int seasonNumber, int episodeNumber) { - return await _libraryManager.GetSeason(showID, seasonNumber); + return await _libraryManager.Get(showID, seasonNumber); } [HttpGet("{episodeID:int}/track")] @@ -84,12 +84,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetTracks( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Episode.ID == episodeID), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetEpisode(episodeID) == null) + if (!resources.Any() && await _libraryManager.Get(episodeID) == null) return NotFound(); return Page(resources, limit); } @@ -112,14 +112,14 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetTracks( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Episode.ShowID == showID && x.Episode.SeasonNumber == seasonNumber && x.Episode.EpisodeNumber == episodeNumber), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetEpisode(showID, seasonNumber, episodeNumber) == null) + if (!resources.Any() && await _libraryManager.Get(showID, seasonNumber, episodeNumber) == null) return NotFound(); return Page(resources, limit); } @@ -142,13 +142,14 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetTracks(ApiHelper.ParseWhere(where, x => x.Episode.Show.Slug == showSlug + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, x => x.Episode.Show.Slug == showSlug && x.Episode.SeasonNumber == seasonNumber && x.Episode.EpisodeNumber == episodeNumber), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber) == null) + if (!resources.Any() && await _libraryManager.Get(showSlug, seasonNumber, episodeNumber) == null) return NotFound(); return Page(resources, limit); } @@ -162,7 +163,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetThumb(int id) { - Episode episode = await _libraryManager.GetEpisode(id); + Episode episode = await _libraryManager.Get(id); if (episode == null) return NotFound(); return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode)); @@ -172,7 +173,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetThumb(string slug) { - Episode episode = await _libraryManager.GetEpisode(slug); + Episode episode = await _libraryManager.Get(slug); if (episode == null) return NotFound(); return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode)); diff --git a/Kyoo/Views/GenreApi.cs b/Kyoo/Views/GenreApi.cs index 29393e97..ab40f20b 100644 --- a/Kyoo/Views/GenreApi.cs +++ b/Kyoo/Views/GenreApi.cs @@ -36,12 +36,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetShows( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Genres.Any(y => y.ID == id)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetGenre(id) == null) + if (!resources.Any() && await _libraryManager.Get(id) == null) return NotFound(); return Page(resources, limit); } @@ -62,12 +62,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetShows( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Genres.Any(y => y.Slug == slug)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetGenre(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } diff --git a/Kyoo/Views/LibraryApi.cs b/Kyoo/Views/LibraryApi.cs index ba9ad3d0..93081db6 100644 --- a/Kyoo/Views/LibraryApi.cs +++ b/Kyoo/Views/LibraryApi.cs @@ -47,12 +47,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetShows( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Libraries.Any(y => y.ID == id)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetLibrary(id) == null) + if (!resources.Any() && await _libraryManager.Get(id) == null) return NotFound(); return Page(resources, limit); } @@ -73,12 +73,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetShows( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Libraries.Any(y => y.Slug == slug)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetLibrary(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } @@ -99,12 +99,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetCollections( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Libraries.Any(y => y.ID == id)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetLibrary(id) == null) + if (!resources.Any() && await _libraryManager.Get(id) == null) return NotFound(); return Page(resources, limit); } @@ -125,12 +125,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetCollections( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Libraries.Any(y => y.Slug == slug)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetLibrary(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } @@ -156,7 +156,7 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetLibrary(id) == null) + if (!resources.Any() && await _libraryManager.Get(id) == null) return NotFound(); return Page(resources, limit); } @@ -182,7 +182,7 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetLibrary(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } diff --git a/Kyoo/Views/PeopleApi.cs b/Kyoo/Views/PeopleApi.cs index bfcb8a29..8f80b6f2 100644 --- a/Kyoo/Views/PeopleApi.cs +++ b/Kyoo/Views/PeopleApi.cs @@ -90,7 +90,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetPeopleIcon(int id) { - People people = await _libraryManager.GetPeople(id); + People people = await _libraryManager.Get(id); return _files.FileResult(await _thumbs.GetPeoplePoster(people)); } @@ -98,7 +98,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetPeopleIcon(string slug) { - People people = await _libraryManager.GetPeople(slug); + People people = await _libraryManager.Get(slug); return _files.FileResult(await _thumbs.GetPeoplePoster(people)); } } diff --git a/Kyoo/Views/ProviderApi.cs b/Kyoo/Views/ProviderApi.cs index 2d3aab3d..050f2681 100644 --- a/Kyoo/Views/ProviderApi.cs +++ b/Kyoo/Views/ProviderApi.cs @@ -11,7 +11,7 @@ namespace Kyoo.Api [Route("api/provider")] [Route("api/providers")] [ApiController] - public class ProviderAPI : CrudApi + public class ProviderAPI : CrudApi { private readonly IThumbnailsManager _thumbnails; private readonly ILibraryManager _libraryManager; @@ -32,7 +32,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetLogo(int id) { - ProviderID provider = await _libraryManager.GetProvider(id); + Provider provider = await _libraryManager.Get(id); return _files.FileResult(await _thumbnails.GetProviderLogo(provider)); } @@ -40,7 +40,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetLogo(string slug) { - ProviderID provider = await _libraryManager.GetProvider(slug); + Provider provider = await _libraryManager.Get(slug); return _files.FileResult(await _thumbnails.GetProviderLogo(provider)); } } diff --git a/Kyoo/Views/SearchApi.cs b/Kyoo/Views/SearchApi.cs index c253adfd..008bf0f5 100644 --- a/Kyoo/Views/SearchApi.cs +++ b/Kyoo/Views/SearchApi.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc; namespace Kyoo.Api { - [Route("api/search")] + [Route("api/search/{query}")] [ApiController] public class SearchApi : ControllerBase { @@ -18,67 +18,67 @@ namespace Kyoo.Api _libraryManager = libraryManager; } - [HttpGet("{query}")] + [HttpGet] [Authorize(Policy="Read")] public async Task> Search(string query) { return new SearchResult { Query = query, - Collections = await _libraryManager.SearchCollections(query), - Shows = await _libraryManager.SearchShows(query), - Episodes = await _libraryManager.SearchEpisodes(query), - People = await _libraryManager.SearchPeople(query), - Genres = await _libraryManager.SearchGenres(query), - Studios = await _libraryManager.SearchStudios(query) + Collections = await _libraryManager.Search(query), + Shows = await _libraryManager.Search(query), + Episodes = await _libraryManager.Search(query), + People = await _libraryManager.Search(query), + Genres = await _libraryManager.Search(query), + Studios = await _libraryManager.Search(query) }; } - [HttpGet("{query}/collection")] - [HttpGet("{query}/collections")] + [HttpGet("collection")] + [HttpGet("collections")] [Authorize(Policy="Read")] public Task> SearchCollections(string query) { - return _libraryManager.SearchCollections(query); + return _libraryManager.Search(query); } - [HttpGet("{query}/show")] - [HttpGet("{query}/shows")] + [HttpGet("show")] + [HttpGet("shows")] [Authorize(Policy="Read")] public Task> SearchShows(string query) { - return _libraryManager.SearchShows(query); + return _libraryManager.Search(query); } - [HttpGet("{query}/episode")] - [HttpGet("{query}/episodes")] + [HttpGet("episode")] + [HttpGet("episodes")] [Authorize(Policy="Read")] public Task> SearchEpisodes(string query) { - return _libraryManager.SearchEpisodes(query); + return _libraryManager.Search(query); } - [HttpGet("{query}/people")] + [HttpGet("people")] [Authorize(Policy="Read")] public Task> SearchPeople(string query) { - return _libraryManager.SearchPeople(query); + return _libraryManager.Search(query); } - [HttpGet("{query}/genre")] - [HttpGet("{query}/genres")] + [HttpGet("genre")] + [HttpGet("genres")] [Authorize(Policy="Read")] public Task> SearchGenres(string query) { - return _libraryManager.SearchGenres(query); + return _libraryManager.Search(query); } - [HttpGet("{query}/studio")] - [HttpGet("{query}/studios")] + [HttpGet("studio")] + [HttpGet("studios")] [Authorize(Policy="Read")] public Task> SearchStudios(string query) { - return _libraryManager.SearchStudios(query); + return _libraryManager.Search(query); } } } \ No newline at end of file diff --git a/Kyoo/Views/SeasonApi.cs b/Kyoo/Views/SeasonApi.cs index bc43cad5..4ca19c29 100644 --- a/Kyoo/Views/SeasonApi.cs +++ b/Kyoo/Views/SeasonApi.cs @@ -42,12 +42,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetEpisodes( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.SeasonID == seasonID), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetSeason(seasonID) == null) + if (!resources.Any() && await _libraryManager.Get(seasonID) == null) return NotFound(); return Page(resources, limit); } @@ -69,13 +69,13 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetEpisodes( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Show.Slug == showSlug && x.SeasonNumber == seasonNumber), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetSeason(showSlug, seasonNumber) == null) + if (!resources.Any() && await _libraryManager.Get(showSlug, seasonNumber) == null) return NotFound(); return Page(resources, limit); } @@ -97,12 +97,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetEpisodes( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.ShowID == showID && x.SeasonNumber == seasonNumber), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetSeason(showID, seasonNumber) == null) + if (!resources.Any() && await _libraryManager.Get(showID, seasonNumber) == null) return NotFound(); return Page(resources, limit); } @@ -116,28 +116,28 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task> GetShow(int seasonID) { - return await _libraryManager.GetShow(x => x.Seasons.Any(y => y.ID == seasonID)); + return await _libraryManager.Get(x => x.Seasons.Any(y => y.ID == seasonID)); } [HttpGet("{showSlug}-s{seasonNumber:int}/show")] [Authorize(Policy = "Read")] - public async Task> GetShow(string showSlug, int _) + public async Task> GetShow(string showSlug, int seasonNumber) { - return await _libraryManager.GetShow(showSlug); + return await _libraryManager.Get(showSlug); } [HttpGet("{showID:int}-s{seasonNumber:int}/show")] [Authorize(Policy = "Read")] - public async Task> GetShow(int showID, int _) + public async Task> GetShow(int showID, int seasonNumber) { - return await _libraryManager.GetShow(showID); + return await _libraryManager.Get(showID); } [HttpGet("{id:int}/thumb")] [Authorize(Policy="Read")] public async Task GetThumb(int id) { - Season season = await _libraryManager.GetSeason(id); + Season season = await _libraryManager.Get(id); await _libraryManager.Load(season, x => x.Show); return _files.FileResult(await _thumbs.GetSeasonPoster(season)); } @@ -146,7 +146,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetThumb(string slug) { - Season season = await _libraryManager.GetSeason(slug); + Season season = await _libraryManager.Get(slug); await _libraryManager.Load(season, x => x.Show); return _files.FileResult(await _thumbs.GetSeasonPoster(season)); } diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index 703f8c45..fc8c28f7 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -44,12 +44,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetSeasons( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.ShowID == showID), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(showID) == null) + if (!resources.Any() && await _libraryManager.Get(showID) == null) return NotFound(); return Page(resources, limit); } @@ -70,12 +70,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetSeasons( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Show.Slug == slug), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } @@ -96,12 +96,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetEpisodes( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.ShowID == showID), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(showID) == null) + if (!resources.Any() && await _libraryManager.Get(showID) == null) return NotFound(); return Page(resources, limit); } @@ -122,12 +122,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetEpisodes( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Show.Slug == slug), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } @@ -152,7 +152,7 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(showID) == null) + if (!resources.Any() && await _libraryManager.Get(showID) == null) return NotFound(); return Page(resources, limit); } @@ -177,7 +177,7 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } @@ -198,12 +198,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetGenres( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Shows.Any(y => y.ID == showID)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(showID) == null) + if (!resources.Any() && await _libraryManager.Get(showID) == null) return NotFound(); return Page(resources, limit); } @@ -224,12 +224,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetGenres( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Shows.Any(y => y.Slug == slug)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } @@ -245,7 +245,7 @@ namespace Kyoo.Api { try { - return await _libraryManager.GetStudio(x => x.Shows.Any(y => y.ID == showID)); + return await _libraryManager.Get(x => x.Shows.Any(y => y.ID == showID)); } catch (ItemNotFound) { @@ -259,7 +259,7 @@ namespace Kyoo.Api { try { - return await _libraryManager.GetStudio(x => x.Shows.Any(y => y.Slug == slug)); + return await _libraryManager.Get(x => x.Shows.Any(y => y.Slug == slug)); } catch (ItemNotFound) { @@ -278,12 +278,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetLibraries( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Shows.Any(y => y.ID == showID)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(showID) == null) + if (!resources.Any() && await _libraryManager.Get(showID) == null) return NotFound(); return Page(resources, limit); } @@ -304,12 +304,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetLibraries( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Shows.Any(y => y.Slug == slug)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } @@ -330,12 +330,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetCollections( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Shows.Any(y => y.ID == showID)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(showID) == null) + if (!resources.Any() && await _libraryManager.Get(showID) == null) return NotFound(); return Page(resources, limit); } @@ -356,12 +356,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetCollections( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Shows.Any(y => y.Slug == slug)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetShow(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } @@ -376,7 +376,7 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task>> GetFonts(string slug) { - Show show = await _libraryManager.GetShow(slug); + Show show = await _libraryManager.Get(slug); if (show == null) return NotFound(); string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments"); @@ -390,7 +390,7 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task GetFont(string showSlug, string slug) { - Show show = await _libraryManager.GetShow(showSlug); + Show show = await _libraryManager.Get(showSlug); if (show == null) return NotFound(); string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments", slug); @@ -401,7 +401,7 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task GetPoster(string slug) { - Show show = await _libraryManager.GetShow(slug); + Show show = await _libraryManager.Get(slug); if (show == null) return NotFound(); return _files.FileResult(await _thumbs.GetShowPoster(show)); @@ -411,7 +411,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetLogo(string slug) { - Show show = await _libraryManager.GetShow(slug); + Show show = await _libraryManager.Get(slug); if (show == null) return NotFound(); return _files.FileResult(await _thumbs.GetShowLogo(show)); @@ -421,7 +421,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetBackdrop(string slug) { - Show show = await _libraryManager.GetShow(slug); + Show show = await _libraryManager.Get(slug); if (show == null) return NotFound(); return _files.FileResult(await _thumbs.GetShowBackdrop(show)); diff --git a/Kyoo/Views/StudioApi.cs b/Kyoo/Views/StudioApi.cs index a0cf872e..45f46829 100644 --- a/Kyoo/Views/StudioApi.cs +++ b/Kyoo/Views/StudioApi.cs @@ -35,12 +35,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetShows( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.StudioID == id), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetStudio(id) == null) + if (!resources.Any() && await _libraryManager.Get(id) == null) return NotFound(); return Page(resources, limit); } @@ -61,12 +61,12 @@ namespace Kyoo.Api { try { - ICollection resources = await _libraryManager.GetShows( + ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, x => x.Studio.Slug == slug), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetStudio(slug) == null) + if (!resources.Any() && await _libraryManager.Get(slug) == null) return NotFound(); return Page(resources, limit); } diff --git a/Kyoo/Views/TrackApi.cs b/Kyoo/Views/TrackApi.cs index 689e904b..79bced62 100644 --- a/Kyoo/Views/TrackApi.cs +++ b/Kyoo/Views/TrackApi.cs @@ -29,7 +29,7 @@ namespace Kyoo.Api { try { - return await _libraryManager.GetEpisode(x => x.Tracks.Any(y => y.ID == id)); + return await _libraryManager.Get(x => x.Tracks.Any(y => y.ID == id)); } catch (ItemNotFound) { @@ -45,7 +45,7 @@ namespace Kyoo.Api { // TODO This won't work with the local repository implementation. // TODO Implement something like this (a dotnet-ef's QueryCompilationContext): https://stackoverflow.com/questions/62687811/how-can-i-convert-a-custom-function-to-a-sql-expression-for-entity-framework-cor - return await _libraryManager.GetEpisode(x => x.Tracks.Any(y => y.Slug == slug)); + return await _libraryManager.Get(x => x.Tracks.Any(y => y.Slug == slug)); } catch (ItemNotFound) { diff --git a/Kyoo/Views/VideoApi.cs b/Kyoo/Views/VideoApi.cs index 1e6651b5..13c53e40 100644 --- a/Kyoo/Views/VideoApi.cs +++ b/Kyoo/Views/VideoApi.cs @@ -4,6 +4,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using System.Threading.Tasks; +using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.Filters; @@ -40,92 +41,59 @@ namespace Kyoo.Api ctx.HttpContext.Response.Headers.Add("Expires", "0"); } - - [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}")] - [HttpGet("direct/{showSlug}-s{seasonNumber:int}e{episodeNumber:int}")] - [Authorize(Policy="Play")] - public async Task DirectEpisode(string showSlug, int seasonNumber, int episodeNumber) - { - if (seasonNumber < 0 || episodeNumber < 0) - return BadRequest(new {error = "Season number or episode number can not be negative."}); - - Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); - if (episode == null) - return NotFound(); - return _files.FileResult(episode.Path, true); - } - [HttpGet("{movieSlug}")] - [HttpGet("direct/{movieSlug}")] + [HttpGet("{slug}")] + [HttpGet("direct/{slug}")] [Authorize(Policy="Play")] - public async Task DirectMovie(string movieSlug) + public async Task Direct(string slug) { - Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); - - if (episode == null) + try + { + Episode episode = await _libraryManager.Get(slug); + return _files.FileResult(episode.Path, true); + } + catch (ItemNotFound) + { return NotFound(); - return _files.FileResult(episode.Path, true); - } - - - [HttpGet("transmux/{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/master.m3u8")] - [Authorize(Policy="Play")] - public async Task TransmuxEpisode(string showSlug, int seasonNumber, int episodeNumber) - { - if (seasonNumber < 0 || episodeNumber < 0) - return BadRequest(new {error = "Season number or episode number can not be negative."}); - - Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); - if (episode == null) - return NotFound(); - string path = await _transcoder.Transmux(episode); - if (path == null) - return StatusCode(500); - return _files.FileResult(path, true); - } - - [HttpGet("transmux/{movieSlug}/master.m3u8")] - [Authorize(Policy="Play")] - public async Task TransmuxMovie(string movieSlug) - { - Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); - - if (episode == null) - return NotFound(); - string path = await _transcoder.Transmux(episode); - if (path == null) - return StatusCode(500); - return _files.FileResult(path, true); + } } - [HttpGet("transcode/{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/master.m3u8")] + [HttpGet("transmux/{slug}/master.m3u8")] [Authorize(Policy="Play")] - public async Task TranscodeEpisode(string showSlug, int seasonNumber, int episodeNumber) + public async Task Transmux(string slug) { - if (seasonNumber < 0 || episodeNumber < 0) - return BadRequest(new {error = "Season number or episode number can not be negative."}); - - Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); - if (episode == null) - return NotFound(); - string path = await _transcoder.Transcode(episode); - if (path == null) - return StatusCode(500); - return _files.FileResult(path, true); - } - - [HttpGet("transcode/{movieSlug}/master.m3u8")] - [Authorize(Policy="Play")] - public async Task TranscodeMovie(string movieSlug) - { - Episode episode = await _libraryManager.GetMovieEpisode(movieSlug); + try + { + Episode episode = await _libraryManager.Get(slug); + string path = await _transcoder.Transmux(episode); - if (episode == null) + if (path == null) + return StatusCode(500); + return _files.FileResult(path, true); + } + catch (ItemNotFound) + { return NotFound(); - string path = await _transcoder.Transcode(episode); - if (path == null) - return StatusCode(500); - return _files.FileResult(path, true); + } + } + + [HttpGet("transcode/{slug}/master.m3u8")] + [Authorize(Policy="Play")] + public async Task Transcode(string slug) + { + try + { + Episode episode = await _libraryManager.Get(slug); + string path = await _transcoder.Transcode(episode); + + if (path == null) + return StatusCode(500); + return _files.FileResult(path, true); + } + catch (ItemNotFound) + { + return NotFound(); + } } diff --git a/Kyoo/Views/WatchApi.cs b/Kyoo/Views/WatchApi.cs index fec777bc..9d95d9ae 100644 --- a/Kyoo/Views/WatchApi.cs +++ b/Kyoo/Views/WatchApi.cs @@ -17,21 +17,11 @@ namespace Kyoo.Api _libraryManager = libraryManager; } - [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}")] + [HttpGet("{slug}")] [Authorize(Policy="Read")] - public async Task> GetWatchItem(string showSlug, int seasonNumber, int episodeNumber) + public async Task> GetWatchItem(string slug) { - Episode item = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); - if (item == null) - return NotFound(); - return await WatchItem.FromEpisode(item, _libraryManager); - } - - [HttpGet("{movieSlug}")] - [Authorize(Policy="Read")] - public async Task> GetWatchItem(string movieSlug) - { - Episode item = await _libraryManager.GetMovieEpisode(movieSlug); + Episode item = await _libraryManager.Get(slug); if (item == null) return NotFound(); return await WatchItem.FromEpisode(item, _libraryManager); From b6e23ce61e18af2ee0d301b3e689b53c469254e2 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 18 Apr 2021 19:27:19 +0200 Subject: [PATCH 02/12] Fixing small issue with episode's repository --- Kyoo/Controllers/Repositories/EpisodeRepository.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 121a1fed..64a43ce2 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -73,7 +73,8 @@ namespace Kyoo.Controllers } Episode episode = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug); - episode.ShowSlug = slug; + if (episode != null) + episode.ShowSlug = slug; return episode; } From f937d2f3081cc9a350b5beaa3f07f05a627a0bc9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 18 Apr 2021 23:42:15 +0200 Subject: [PATCH 03/12] Adding documentations to the repository interface --- .github/workflows/build.yml | 4 +- Kyoo.Common/Controllers/IRepository.cs | 46 +++++++++++++++++++ .../Implementations/LibraryManager.cs | 4 ++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5969ea76..bd06596b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,8 +21,8 @@ jobs: artifact: macos steps: - uses: actions/checkout@v1 - - name: Checkout submodules - run: git submodule update --init --recursive + with: + submodules: recursive - name: Setup .NET uses: actions/setup-dotnet@v1 with: diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 1caccfc5..bffe844e 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -9,25 +9,60 @@ using Kyoo.Models; namespace Kyoo.Controllers { + /// + /// Informations about the pagination. How many items should be displayed and where to start. + /// public readonly struct Pagination { + /// + /// The count of items to return. + /// public int Count { get; } + /// + /// Where to start? Using the given sort + /// public int AfterID { get; } + /// + /// Create a new instance. + /// + /// Set the value + /// Set the value. If not specified, it will start from the start public Pagination(int count, int afterID = 0) { Count = count; AfterID = afterID; } + /// + /// Implicitly create a new pagination from a limit number. + /// + /// Set the value + /// A new instance public static implicit operator Pagination(int limit) => new(limit); } + /// + /// Informations about how a query should be sorted. What factor should decide the sort and in which order. + /// + /// For witch type this sort applies public readonly struct Sort { + /// + /// The sort key. This member will be used to sort the results. + /// public Expression> Key { get; } + /// + /// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendent order. + /// public bool Descendant { get; } + /// + /// Create a new instance. + /// + /// The sort key given. It is assigned to . + /// Should this be in descendant order? The default is false. + /// If the given key is not a member. public Sort(Expression> key, bool descendant = false) { Key = key; @@ -37,6 +72,11 @@ namespace Kyoo.Controllers throw new ArgumentException("The given sort key is not valid."); } + /// + /// Create a new instance from a key's name (case insensitive). + /// + /// A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key". + /// An invalid key or sort specifier as been given. public Sort(string sortBy) { if (string.IsNullOrEmpty(sortBy)) @@ -65,8 +105,14 @@ namespace Kyoo.Controllers } } + /// + /// A base class for repositories. Every service implementing this will be handled by the . + /// public interface IBaseRepository : IDisposable, IAsyncDisposable { + /// + /// The type for witch this repository is responsible. + /// Type RepositoryType { get; } } diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 366cb0ea..bd4a9f91 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -16,6 +16,10 @@ namespace Kyoo.Controllers private readonly IBaseRepository[] _repositories; + /// + /// 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(); From d2de442b646048cb50b5450675e61dabcd5c570f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 19 Apr 2021 21:19:20 +0200 Subject: [PATCH 04/12] Adding documentation to the IRepository interface --- Kyoo.Common/Controllers/IRepository.cs | 111 ++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index bffe844e..4fa70691 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.Models; +using Kyoo.Models.Exceptions; namespace Kyoo.Controllers { @@ -111,44 +112,150 @@ namespace Kyoo.Controllers public interface IBaseRepository : IDisposable, IAsyncDisposable { /// - /// The type for witch this repository is responsible. + /// The type for witch this repository is responsible or null if non applicable. /// Type RepositoryType { get; } } + /// + /// A common repository for every resources. + /// It implement's and /. + /// + /// The resource's type that this repository manage. public interface IRepository : IBaseRepository where T : class, IResource { + /// + /// Get a resource from it's ID. + /// + /// The id of the resource + /// If the item could not be found. + /// The resource found Task Get(int id); + /// + /// Get a resource from it's slug. + /// + /// The slug of the resource + /// If the item could not be found. + /// The resource found Task Get(string slug); + /// + /// Get the first resource that match the predicate. + /// + /// A predicate to filter the resource. + /// If the item could not be found. + /// The resource found Task Get(Expression> where); + + /// + /// Search for resources. + /// + /// The query string. + /// A list of resources found Task> Search(string query); + /// + /// Get every resources that match all filters + /// + /// A filter predicate + /// Sort informations about the query (sort by, sort order) + /// How pagination should be done (where to start and how many to return) + /// A list of resources that match every filters Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default); - + /// + /// Get every resources that match all filters + /// + /// A filter predicate + /// A sort by predicate. The order is ascending. + /// How pagination should be done (where to start and how many to return) + /// A list of resources that match every filters Task> GetAll([Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetAll(where, new Sort(sort), limit); + /// + /// Get the number of resources that match the filter's predicate. + /// + /// A filter predicate + /// How many resources matched that filter Task GetCount(Expression> where = null); + /// + /// Create a new resource. + /// + /// The item to register + /// The resource registers and completed by database's informations (related items & so on) Task Create([NotNull] T obj); + + /// + /// Create a new resource if it does not exist already. If it does, the existing value is returned instead. + /// + /// The object to create + /// Allow issues to occurs in this method. Every issue is catched and ignored. + /// The newly created item or the existing value if it existed. Task CreateIfNotExists([NotNull] T obj, bool silentFail = false); + + /// + /// 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 resource edited and completed by database's informations (related items & so on) Task Edit([NotNull] T edited, bool resetOld); + /// + /// Delete a resource by it's ID + /// + /// The ID of the resource Task Delete(int id); + /// + /// Delete a resource by it's slug + /// + /// The slug of the resource Task Delete(string slug); + /// + /// Delete a resource + /// + /// The resource to delete Task Delete([NotNull] T obj); + /// + /// Delete a list of resources. + /// + /// One or multiple resources to delete Task DeleteRange(params T[] objs) => DeleteRange(objs.AsEnumerable()); + /// + /// Delete a list of resources. + /// + /// An enumerable of resources to delete Task DeleteRange(IEnumerable objs); + /// + /// Delete a list of resources. + /// + /// One or multiple resources's id Task DeleteRange(params int[] ids) => DeleteRange(ids.AsEnumerable()); + /// + /// Delete a list of resources. + /// + /// An enumearble of resources's id Task DeleteRange(IEnumerable ids); + /// + /// Delete a list of resources. + /// + /// One or multiple resources's slug Task DeleteRange(params string[] slugs) => DeleteRange(slugs.AsEnumerable()); + /// + /// Delete a list of resources. + /// + /// An enumerable of resources's slug Task DeleteRange(IEnumerable slugs); + /// + /// Delete a list of resources. + /// + /// A predicate to filter resources to delete. Every resource that match this will be deleted. Task DeleteRange([NotNull] Expression> where); } From fb4efa35c5e68ef4ea91ab3339909f5b685ae450 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 20 Apr 2021 00:57:17 +0200 Subject: [PATCH 05/12] Fixing a typo --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bccdb2b1..665f3f19 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. -## Informations +## Information - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] New public API added - [ ] Non-breaking changes From 411eaa7aed964183fb25723e85775824348752e1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 21 Apr 2021 01:49:21 +0200 Subject: [PATCH 06/12] Unifing ItemNotFound behaviors and adding GetOrDefault. --- Kyoo.Common/Controllers/ILibraryManager.cs | 84 ++++++- Kyoo.Common/Controllers/IRepository.cs | 140 +++++++++++- .../Implementations/LibraryManager.cs | 216 +++++++++++++----- Kyoo.CommonAPI/CrudApi.cs | 53 +++-- Kyoo.CommonAPI/LocalRepository.cs | 134 +++++++++-- Kyoo.Tests/Library/RepositoryTests.cs | 17 ++ Kyoo.Tests/Library/TestContext.cs | 79 +++++++ .../Repositories/EpisodeRepository.cs | 8 +- .../Repositories/SeasonRepository.cs | 76 ++++-- Kyoo/Models/DatabaseContext.cs | 14 +- ....cs => 20210420221509_Initial.Designer.cs} | 2 +- ...5_Initial.cs => 20210420221509_Initial.cs} | 0 Kyoo/Tasks/Crawler.cs | 94 ++++---- Kyoo/Views/EpisodeApi.cs | 23 +- Kyoo/Views/ShowApi.cs | 63 +++-- Kyoo/Views/SubtitleApi.cs | 2 +- Kyoo/Views/WatchApi.cs | 12 +- 17 files changed, 810 insertions(+), 207 deletions(-) create mode 100644 Kyoo.Tests/Library/RepositoryTests.cs create mode 100644 Kyoo.Tests/Library/TestContext.cs rename Kyoo/Models/DatabaseMigrations/Internal/{20210417232515_Initial.Designer.cs => 20210420221509_Initial.Designer.cs} (99%) rename Kyoo/Models/DatabaseMigrations/Internal/{20210417232515_Initial.cs => 20210420221509_Initial.cs} (100%) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index fb597293..ae3c321b 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -143,13 +143,79 @@ namespace Kyoo.Controllers Task Get(string showSlug, int seasonNumber, int episodeNumber); /// - /// Get a tracck from it's slug and it's type. + /// Get a track 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 - Task GetTrack(string slug, StreamType type = StreamType.Unknown); + Task Get(string slug, StreamType type = StreamType.Unknown); + + /// + /// 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 + Task GetOrDefault(int id) where T : class, IResource; + + /// + /// 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 + Task GetOrDefault(string slug) where T : class, IResource; + + /// + /// 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 + Task GetOrDefault(Expression> where) where T : class, IResource; + + /// + /// 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 + Task GetOrDefault(int showID, int 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 + Task GetOrDefault(string showSlug, int 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 + Task GetOrDefault(int showID, int seasonNumber, int 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 + Task GetOrDefault(string showSlug, int seasonNumber, int 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 + Task GetOrDefault(string slug, StreamType type = StreamType.Unknown); /// @@ -423,7 +489,15 @@ namespace Kyoo.Controllers /// The item to register /// The type of resource /// The resource registers and completed by database's informations (related items & so on) - Task Create(T item) where T : class, IResource; + Task Create([NotNull] T item) where T : class, IResource; + + /// + /// Create a new resource if it does not exist already. If it does, the existing value is returned instead. + /// + /// The item to register + /// The type of resource + /// The newly created item or the existing value if it existed. + Task CreateIfNotExists([NotNull] T item) where T : class, IResource; /// /// Edit a resource @@ -431,6 +505,7 @@ namespace Kyoo.Controllers /// 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) Task Edit(T item, bool resetOld) where T : class, IResource; @@ -439,6 +514,7 @@ namespace Kyoo.Controllers /// /// The resource to delete /// The type of resource to delete + /// If the item is not found Task Delete(T item) where T : class, IResource; /// @@ -446,6 +522,7 @@ namespace Kyoo.Controllers /// /// The id of the resource to delete /// The type of resource to delete + /// If the item is not found Task Delete(int id) where T : class, IResource; /// @@ -453,6 +530,7 @@ namespace Kyoo.Controllers /// /// The slug of the resource to delete /// The type of resource to delete + /// If the item is not found Task Delete(string slug) where T : class, IResource; } } diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 4fa70691..fbe20a95 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -146,6 +146,25 @@ namespace Kyoo.Controllers /// The resource found Task Get(Expression> where); + /// + /// Get a resource from it's ID or null if it is not found. + /// + /// The id of the resource + /// The resource found + Task GetOrDefault(int id); + /// + /// Get a resource from it's slug or null if it is not found. + /// + /// The slug of the resource + /// The resource found + Task GetOrDefault(string slug); + /// + /// Get the first resource that match the predicate or null if it is not found. + /// + /// A predicate to filter the resource. + /// The resource found + Task GetOrDefault(Expression> where); + /// /// Search for resources. /// @@ -203,6 +222,7 @@ namespace Kyoo.Controllers /// /// 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? + /// If the item is not found /// The resource edited and completed by database's informations (related items & so on) Task Edit([NotNull] T edited, bool resetOld); @@ -210,77 +230,193 @@ namespace Kyoo.Controllers /// Delete a resource by it's ID /// /// The ID of the resource + /// If the item is not found Task Delete(int id); /// /// Delete a resource by it's slug /// /// The slug of the resource + /// If the item is not found Task Delete(string slug); /// /// Delete a resource /// /// The resource to delete + /// If the item is not found Task Delete([NotNull] T obj); /// /// Delete a list of resources. /// /// One or multiple resources to delete + /// If the item is not found Task DeleteRange(params T[] objs) => DeleteRange(objs.AsEnumerable()); /// /// Delete a list of resources. /// /// An enumerable of resources to delete + /// If the item is not found Task DeleteRange(IEnumerable objs); /// /// Delete a list of resources. /// /// One or multiple resources's id + /// If the item is not found Task DeleteRange(params int[] ids) => DeleteRange(ids.AsEnumerable()); /// /// Delete a list of resources. /// /// An enumearble of resources's id + /// If the item is not found Task DeleteRange(IEnumerable ids); /// /// Delete a list of resources. /// /// One or multiple resources's slug + /// If the item is not found Task DeleteRange(params string[] slugs) => DeleteRange(slugs.AsEnumerable()); /// /// Delete a list of resources. /// /// An enumerable of resources's slug + /// If the item is not found Task DeleteRange(IEnumerable slugs); /// /// Delete a list of resources. /// /// A predicate to filter resources to delete. Every resource that match this will be deleted. + /// If the item is not found Task DeleteRange([NotNull] Expression> where); } + /// + /// A repository to handle shows. + /// public interface IShowRepository : IRepository { + /// + /// Link a show to a collection and/or a library. The given show is now part of thoses containers. + /// If both a library and a collection are given, the collection is added to the library too. + /// + /// The ID of the show + /// The ID of the library (optional) + /// The ID of the collection (optional) Task AddShowLink(int showID, int? libraryID, int? collectionID); + /// + /// Get a show's slug from it's ID. + /// + /// The ID of the show + /// If a show with the given ID is not found. + /// The show's slug Task GetSlug(int showID); } + /// + /// A repository to handle seasons. + /// public interface ISeasonRepository : IRepository { + /// + /// 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 Task Get(int showID, int 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 Task Get(string showSlug, int seasonNumber); - Task Delete(string showSlug, int seasonNumber); + + /// + /// 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 + Task GetOrDefault(int showID, int 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 + Task GetOrDefault(string showSlug, int seasonNumber); } + /// + /// The repository to handle episodes + /// public interface IEpisodeRepository : IRepository { + /// + /// 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 Task Get(int showID, int seasonNumber, int 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 Task Get(string showSlug, int seasonNumber, int episodeNumber); + /// + /// Get a episode from it's season ID and it's episode number. + /// + /// The ID of the season + /// The episode number + /// If the item is not found + /// The episode found Task Get(int seasonID, int episodeNumber); + + /// + /// 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 + Task GetOrDefault(int showID, int seasonNumber, int 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 + Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber); + + /// + /// Get a episode from it's showID and it's absolute number. + /// + /// The id of the show + /// The episode's absolute number (The episode number does not reset to 1 after the end of a season. + /// If the item is not found + /// The episode found Task GetAbsolute(int showID, int absoluteNumber); + /// + /// Get a episode from it's showID and it's absolute number. + /// + /// The slug of the show + /// The episode's absolute number (The episode number does not reset to 1 after the end of a season. + /// If the item is not found + /// The episode found Task GetAbsolute(string showSlug, int absoluteNumber); - Task Delete(string showSlug, int seasonNumber, int episodeNumber); } public interface ITrackRepository : IRepository diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index bd4a9f91..b1f66291 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -15,27 +15,6 @@ namespace Kyoo.Controllers /// private readonly IBaseRepository[] _repositories; - - /// - /// 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; - } - /// /// The repository that handle libraries. /// @@ -90,7 +69,60 @@ namespace Kyoo.Controllers /// 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 @@ -188,44 +220,104 @@ namespace Kyoo.Controllers /// The type (Video, Audio or Subtitle) /// If the item is not found /// The tracl found - public Task GetTrack(string slug, StreamType type = StreamType.Unknown) + public Task Get(string slug, StreamType type = StreamType.Unknown) { return TrackRepository.Get(slug, type); } /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// Get the resource by it's ID or null if it is not found. /// - 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() + /// The id of the resource + /// The type of the resource + /// The resource found + public async Task GetOrDefault(int id) where T : class, IResource { - if (_repositories.FirstOrDefault(x => x.RepositoryType == typeof(T)) is IRepository ret) - return ret; - throw new ItemNotFound(); + 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 /// @@ -360,7 +452,7 @@ namespace Kyoo.Controllers .Then(x => s.Collections = x), (Show s, nameof(Show.Studio)) => StudioRepository - .Get(x => x.Shows.Any(y => y.ID == obj.ID)) + .GetOrDefault(x => x.Shows.Any(y => y.ID == obj.ID)) .Then(x => { s.Studio = x; @@ -379,7 +471,7 @@ namespace Kyoo.Controllers (x, y) => { x.Season = y; x.SeasonID = y.ID; }), (Season s, nameof(Season.Show)) => ShowRepository - .Get(x => x.Seasons.Any(y => y.ID == obj.ID)) + .GetOrDefault(x => x.Seasons.Any(y => y.ID == obj.ID)) .Then(x => { s.Show = x; @@ -398,7 +490,7 @@ namespace Kyoo.Controllers (x, y) => { x.Episode = y; x.EpisodeID = y.ID; }), (Episode e, nameof(Episode.Show)) => ShowRepository - .Get(x => x.Episodes.Any(y => y.ID == obj.ID)) + .GetOrDefault(x => x.Episodes.Any(y => y.ID == obj.ID)) .Then(x => { e.Show = x; @@ -406,7 +498,7 @@ namespace Kyoo.Controllers }), (Episode e, nameof(Episode.Season)) => SeasonRepository - .Get(x => x.Episodes.Any(y => y.ID == e.ID)) + .GetOrDefault(x => x.Episodes.Any(y => y.ID == e.ID)) .Then(x => { e.Season = x; @@ -415,7 +507,7 @@ namespace Kyoo.Controllers (Track t, nameof(Track.Episode)) => EpisodeRepository - .Get(x => x.Tracks.Any(y => y.ID == obj.ID)) + .GetOrDefault(x => x.Tracks.Any(y => y.ID == obj.ID)) .Then(x => { t.Episode = x; @@ -623,6 +715,18 @@ namespace Kyoo.Controllers { 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 @@ -630,6 +734,7 @@ namespace Kyoo.Controllers /// 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 @@ -642,6 +747,7 @@ namespace Kyoo.Controllers /// /// 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 { @@ -653,6 +759,7 @@ namespace Kyoo.Controllers /// /// 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 { @@ -664,6 +771,7 @@ namespace Kyoo.Controllers /// /// 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 { diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index 0bbf7733..50dcb588 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -29,22 +29,28 @@ namespace Kyoo.CommonApi [Authorize(Policy = "Read")] public virtual async Task> Get(int id) { - T resource = await _repository.Get(id); - if (resource == null) + try + { + return await _repository.Get(id); + } + catch (ItemNotFound) + { return NotFound(); - - return resource; + } } [HttpGet("{slug}")] [Authorize(Policy = "Read")] public virtual async Task> Get(string slug) { - T resource = await _repository.Get(slug); - if (resource == null) + try + { + return await _repository.Get(slug); + } + catch (ItemNotFound) + { return NotFound(); - - return resource; + } } [HttpGet("count")] @@ -114,15 +120,19 @@ namespace Kyoo.CommonApi [Authorize(Policy = "Write")] public virtual async Task> Edit([FromQuery] bool resetOld, [FromBody] T resource) { - if (resource.ID > 0) + try + { + if (resource.ID > 0) + return await _repository.Edit(resource, resetOld); + + T old = await _repository.Get(resource.Slug); + resource.ID = old.ID; return await _repository.Edit(resource, resetOld); - - T old = await _repository.Get(resource.Slug); - if (old == null) + } + catch (ItemNotFound) + { return NotFound(); - - resource.ID = old.ID; - return await _repository.Edit(resource, resetOld); + } } [HttpPut("{id:int}")] @@ -144,11 +154,16 @@ namespace Kyoo.CommonApi [Authorize(Policy = "Write")] public virtual async Task> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T resource) { - T old = await _repository.Get(slug); - if (old == null) + try + { + T old = await _repository.Get(slug); + resource.ID = old.ID; + return await _repository.Edit(resource, resetOld); + } + catch (ItemNotFound) + { return NotFound(); - resource.ID = old.ID; - return await _repository.Edit(resource, resetOld); + } } [HttpDelete("{id:int}")] diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 1cea2af3..8f82093e 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -12,54 +12,112 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { + /// + /// A base class to create repositories using Entity Framework. + /// + /// The type of this repository public abstract class LocalRepository : IRepository where T : class, IResource { + /// + /// The Entity Framework's Database handle. + /// protected readonly DbContext Database; + /// + /// The default sort order that will be used for this resource's type. + /// protected abstract Expression> DefaultSort { get; } + /// + /// Create a new base with the given database handle. + /// + /// A database connection to load resources of type protected LocalRepository(DbContext database) { Database = database; } + /// public Type RepositoryType => typeof(T); + /// public virtual void Dispose() { Database.Dispose(); GC.SuppressFinalize(this); } + /// public virtual ValueTask DisposeAsync() { return Database.DisposeAsync(); } - public virtual Task Get(int id) + /// + /// Get a resource from it's ID and make the instance track it. + /// + /// The ID of the resource + /// If the item is not found + /// The tracked resource with the given ID + protected virtual async Task GetWithTracking(int id) + { + T ret = await Database.Set().AsTracking().FirstOrDefaultAsync(x => x.ID == id); + if (ret == null) + throw new ItemNotFound($"No {typeof(T).Name} found with the id {id}"); + return ret; + } + + /// + public virtual async Task Get(int id) + { + T ret = await GetOrDefault(id); + if (ret == null) + throw new ItemNotFound($"No {typeof(T).Name} found with the id {id}"); + return ret; + } + + /// + public virtual async Task Get(string slug) + { + T ret = await GetOrDefault(slug); + if (ret == null) + throw new ItemNotFound($"No {typeof(T).Name} found with the slug {slug}"); + return ret; + } + + /// + public virtual async Task Get(Expression> where) + { + T ret = await GetOrDefault(where); + if (ret == null) + throw new ItemNotFound($"No {typeof(T).Name} found with the given predicate."); + return ret; + } + + /// + public Task GetOrDefault(int id) { return Database.Set().FirstOrDefaultAsync(x => x.ID == id); } - public virtual Task GetWithTracking(int id) - { - return Database.Set().AsTracking().FirstOrDefaultAsync(x => x.ID == id); - } - - public virtual Task Get(string slug) + /// + public Task GetOrDefault(string slug) { return Database.Set().FirstOrDefaultAsync(x => x.Slug == slug); } - - public virtual Task Get(Expression> predicate) - { - return Database.Set().FirstOrDefaultAsync(predicate); - } + /// + public Task GetOrDefault(Expression> where) + { + return Database.Set().FirstOrDefaultAsync(where); + } + + /// public abstract Task> Search(string query); + /// public virtual Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) @@ -67,6 +125,14 @@ namespace Kyoo.Controllers return ApplyFilters(Database.Set(), where, sort, limit); } + /// + /// Apply filters to a query to ease sort, pagination & where queries for resources of this repository + /// + /// The base query to filter. + /// An expression to filter based on arbitrary conditions + /// The sort settings (sort order & sort by) + /// Paginations information (where to start and how many to get) + /// The filtered query protected Task> ApplyFilters(IQueryable query, Expression> where = null, Sort sort = default, @@ -75,6 +141,17 @@ namespace Kyoo.Controllers return ApplyFilters(query, Get, DefaultSort, where, sort, limit); } + /// + /// Apply filters to a query to ease sort, pagination & where queries for any resources types. + /// For resources of type , see + /// + /// A function to asynchronously get a resource from the database using it's ID. + /// The default sort order of this resource's type. + /// The base query to filter. + /// An expression to filter based on arbitrary conditions + /// The sort settings (sort order & sort by) + /// Paginations information (where to start and how many to get) + /// The filtered query protected async Task> ApplyFilters(IQueryable query, Func> get, Expression> defaultSort, @@ -110,6 +187,7 @@ namespace Kyoo.Controllers return await query.ToListAsync(); } + /// public virtual Task GetCount(Expression> where = null) { IQueryable query = Database.Set(); @@ -118,6 +196,7 @@ namespace Kyoo.Controllers return query.CountAsync(); } + /// public virtual async Task Create(T obj) { if (obj == null) @@ -126,6 +205,7 @@ namespace Kyoo.Controllers return obj; } + /// public virtual async Task CreateIfNotExists(T obj, bool silentFail = false) { try @@ -141,10 +221,7 @@ namespace Kyoo.Controllers } catch (DuplicatedItemException) { - T old = await Get(obj!.Slug); - if (old == null) - throw new SystemException("Unknown database state."); - return old; + return await Get(obj.Slug); } catch { @@ -154,6 +231,7 @@ namespace Kyoo.Controllers } } + /// public virtual async Task Edit(T edited, bool resetOld) { if (edited == null) @@ -164,9 +242,7 @@ namespace Kyoo.Controllers try { T old = await GetWithTracking(edited.ID); - if (old == null) - throw new ItemNotFound($"No resource found with the ID {edited.ID}."); - + if (resetOld) Utility.Nullify(old); Utility.Complete(old, edited, x => x.GetCustomAttribute() == null); @@ -180,11 +256,24 @@ namespace Kyoo.Controllers } } + /// + /// An overridable method to edit relatiosn of a resource. + /// + /// The non edited resource + /// The new version of . This item will be saved on the databse and replace + /// A boolean to indicate if all values of resource should be discarded or not. + /// protected virtual Task EditRelations(T resource, T changed, bool resetOld) { return Validate(resource); } + /// + /// A method called just before saving a new resource to the database. + /// It is also called on the default implementation of + /// + /// The resource that will be saved + /// You can throw this if the resource is illegal and should not be saved. protected virtual Task Validate(T resource) { if (string.IsNullOrEmpty(resource.Slug)) @@ -207,38 +296,45 @@ namespace Kyoo.Controllers return Task.CompletedTask; } + /// public virtual async Task Delete(int id) { T resource = await Get(id); await Delete(resource); } + /// public virtual async Task Delete(string slug) { T resource = await Get(slug); await Delete(resource); } + /// public abstract Task Delete(T obj); + /// public virtual async Task DeleteRange(IEnumerable objs) { foreach (T obj in objs) await Delete(obj); } + /// public virtual async Task DeleteRange(IEnumerable ids) { foreach (int id in ids) await Delete(id); } + /// public virtual async Task DeleteRange(IEnumerable slugs) { foreach (string slug in slugs) await Delete(slug); } + /// public async Task DeleteRange(Expression> where) { ICollection resources = await GetAll(where); diff --git a/Kyoo.Tests/Library/RepositoryTests.cs b/Kyoo.Tests/Library/RepositoryTests.cs new file mode 100644 index 00000000..75ebfd47 --- /dev/null +++ b/Kyoo.Tests/Library/RepositoryTests.cs @@ -0,0 +1,17 @@ +using System.Linq; +using Xunit; + +namespace Kyoo.Tests +{ + public class RepositoryTests + { + [Fact] + public void Get_Test() + { + TestContext context = new(); + using DatabaseContext database = context.New(); + + Assert.Equal(1, database.Shows.Count()); + } + } +} \ No newline at end of file diff --git a/Kyoo.Tests/Library/TestContext.cs b/Kyoo.Tests/Library/TestContext.cs new file mode 100644 index 00000000..c9a83ad0 --- /dev/null +++ b/Kyoo.Tests/Library/TestContext.cs @@ -0,0 +1,79 @@ +using Kyoo.Models; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace Kyoo.Tests +{ + /// + /// Class responsible to fill and create in memory databases for unit tests. + /// + public class TestContext + { + /// + /// The context's options that specify to use an in memory Sqlite database. + /// + private readonly DbContextOptions _context; + + /// + /// Create a new database and fill it with informations. + /// + public TestContext() + { + SqliteConnection connection = new("DataSource=:memory:"); + connection.Open(); + + try + { + _context = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + FillDatabase(); + } + finally + { + connection.Close(); + } + } + + /// + /// Fill the database with pre defined values using a clean context. + /// + private void FillDatabase() + { + using DatabaseContext context = new(_context); + context.Shows.Add(new Show + { + ID = 67, + Slug = "anohana", + Title = "Anohana: The Flower We Saw That Day", + Aliases = new[] + { + "Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.", + "AnoHana", + "We Still Don't Know the Name of the Flower We Saw That Day." + }, + Overview = "When Yadomi Jinta was a child, he was a central piece in a group of close friends. " + + "In time, however, these childhood friends drifted apart, and when they became high " + + "school students, they had long ceased to think of each other as friends.", + Status = Status.Finished, + TrailerUrl = null, + StartYear = 2011, + EndYear = 2011, + Poster = "poster", + Logo = "logo", + Backdrop = "backdrop", + IsMovie = false, + Studio = null + }); + } + + /// + /// Get a new databse context connected to a in memory Sqlite databse. + /// + /// A valid DatabaseContext + public DatabaseContext New() + { + return new(_context); + } + } +} \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 64a43ce2..84232067 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -210,13 +210,7 @@ namespace Kyoo.Controllers return x; }).ToListAsync(); } - - public async Task Delete(string showSlug, int seasonNumber, int episodeNumber) - { - Episode obj = await Get(showSlug, seasonNumber, episodeNumber); - await Delete(obj); - } - + public override async Task Delete(Episode obj) { if (obj == null) diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 29c3e400..fb17d58f 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -5,22 +5,39 @@ using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { + /// + /// A local repository to handle seasons. + /// public class SeasonRepository : LocalRepository, ISeasonRepository { + /// + /// Has this instance been disposed and should not handle requests? + /// private bool _disposed; private readonly DatabaseContext _database; private readonly IProviderRepository _providers; private readonly IShowRepository _shows; private readonly Lazy _episodes; + + /// protected override Expression> DefaultSort => x => x.SeasonNumber; - public SeasonRepository(DatabaseContext database, + /// + /// Create a new using the provided handle, a provider & a show repository and + /// a service provider to lazilly request an episode repository. + /// + /// The database handle that will be used + /// A provider repository + /// A show repository + /// A service provider to lazilly request an episode repository. + public SeasonRepository(DatabaseContext database, IProviderRepository providers, IShowRepository shows, IServiceProvider services) @@ -33,6 +50,7 @@ namespace Kyoo.Controllers } + /// public override void Dispose() { if (_disposed) @@ -46,6 +64,7 @@ namespace Kyoo.Controllers GC.SuppressFinalize(this); } + /// public override async ValueTask DisposeAsync() { if (_disposed) @@ -58,22 +77,23 @@ namespace Kyoo.Controllers await _episodes.Value.DisposeAsync(); } + /// public override async Task Get(int id) { Season ret = await base.Get(id); - if (ret != null) - ret.ShowSlug = await _shows.GetSlug(ret.ShowID); + ret.ShowSlug = await _shows.GetSlug(ret.ShowID); return ret; } + /// public override async Task Get(Expression> predicate) { Season ret = await base.Get(predicate); - if (ret != null) - ret.ShowSlug = await _shows.GetSlug(ret.ShowID); + ret.ShowSlug = await _shows.GetSlug(ret.ShowID); return ret; } + /// public override Task Get(string slug) { Match match = Regex.Match(slug, @"(?.*)-s(?\d*)"); @@ -83,24 +103,41 @@ namespace Kyoo.Controllers return Get(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value)); } + /// public async Task Get(int showID, int seasonNumber) { - Season ret = await _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID - && x.SeasonNumber == seasonNumber); - if (ret != null) - ret.ShowSlug = await _shows.GetSlug(showID); + Season ret = await GetOrDefault(showID, seasonNumber); + if (ret == null) + throw new ItemNotFound($"No season {seasonNumber} found for the show {showID}"); + ret.ShowSlug = await _shows.GetSlug(showID); return ret; } + /// public async Task Get(string showSlug, int seasonNumber) { - Season ret = await _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug - && x.SeasonNumber == seasonNumber); - if (ret != null) - ret.ShowSlug = showSlug; + Season ret = await GetOrDefault(showSlug, seasonNumber); + if (ret == null) + throw new ItemNotFound($"No season {seasonNumber} found for the show {showSlug}"); + ret.ShowSlug = showSlug; return ret; } + /// + public Task GetOrDefault(int showID, int seasonNumber) + { + return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID + && x.SeasonNumber == seasonNumber); + } + + /// + public Task GetOrDefault(string showSlug, int seasonNumber) + { + return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + && x.SeasonNumber == seasonNumber); + } + + /// public override async Task> Search(string query) { List seasons = await _database.Seasons @@ -113,6 +150,7 @@ namespace Kyoo.Controllers return seasons; } + /// public override async Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) @@ -123,6 +161,7 @@ namespace Kyoo.Controllers return seasons; } + /// public override async Task Create(Season obj) { await base.Create(obj); @@ -132,6 +171,7 @@ namespace Kyoo.Controllers return obj; } + /// protected override async Task Validate(Season resource) { if (resource.ShowID <= 0) @@ -146,6 +186,7 @@ namespace Kyoo.Controllers }); } + /// protected override async Task EditRelations(Season resource, Season changed, bool resetOld) { if (changed.ExternalIDs != null || resetOld) @@ -155,13 +196,8 @@ namespace Kyoo.Controllers } await base.EditRelations(resource, changed, resetOld); } - - public async Task Delete(string showSlug, int seasonNumber) - { - Season obj = await Get(showSlug, seasonNumber); - await Delete(obj); - } - + + /// public override async Task Delete(Season obj) { if (obj == null) diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 0538ed67..7d76a3d4 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -58,13 +58,13 @@ namespace Kyoo modelBuilder.HasPostgresEnum(); modelBuilder.HasPostgresEnum(); - modelBuilder.Entity() - .Property(x => x.Paths) - .HasColumnType("text[]"); - - modelBuilder.Entity() - .Property(x => x.Aliases) - .HasColumnType("text[]"); + // modelBuilder.Entity() + // .Property(x => x.Paths) + // .HasColumnType("text[]"); + // + // modelBuilder.Entity() + // .Property(x => x.Aliases) + // .HasColumnType("text[]"); modelBuilder.Entity() .Property(t => t.IsDefault) diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210420221509_Initial.Designer.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210420221509_Initial.Designer.cs index 11722606..c27925c9 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210420221509_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210417232515_Initial")] + [Migration("20210420221509_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210420221509_Initial.cs similarity index 100% rename from Kyoo/Models/DatabaseMigrations/Internal/20210417232515_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210420221509_Initial.cs diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index b6ab53de..2634a3bb 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -144,44 +144,48 @@ namespace Kyoo.Controllers private async Task RegisterExternalSubtitle(string path, CancellationToken token) { - if (token.IsCancellationRequested || path.Split(Path.DirectorySeparatorChar).Contains("Subtitles")) - return; - using IServiceScope serviceScope = _serviceProvider.CreateScope(); - await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); - - string patern = _config.GetValue("subtitleRegex"); - Regex regex = new(patern, RegexOptions.IgnoreCase); - Match match = regex.Match(path); - - if (!match.Success) + try { - await Console.Error.WriteLineAsync($"The subtitle at {path} does not match the subtitle's regex."); - return; + if (token.IsCancellationRequested || path.Split(Path.DirectorySeparatorChar).Contains("Subtitles")) + return; + using IServiceScope serviceScope = _serviceProvider.CreateScope(); + await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); + + string patern = _config.GetValue("subtitleRegex"); + Regex regex = new(patern, RegexOptions.IgnoreCase); + Match match = regex.Match(path); + + if (!match.Success) + { + await Console.Error.WriteLineAsync($"The subtitle at {path} does not match the subtitle's regex."); + return; + } + + string episodePath = match.Groups["Episode"].Value; + Episode episode = await libraryManager!.Get(x => x.Path.StartsWith(episodePath)); + Track track = new() + { + Type = StreamType.Subtitle, + Language = match.Groups["Language"].Value, + IsDefault = match.Groups["Default"].Value.Length > 0, + IsForced = match.Groups["Forced"].Value.Length > 0, + Codec = SubtitleExtensions[Path.GetExtension(path)], + IsExternal = true, + Path = path, + Episode = episode + }; + + await libraryManager.Create(track); + Console.WriteLine($"Registering subtitle at: {path}."); } - - string episodePath = match.Groups["Episode"].Value; - Episode episode = await libraryManager!.Get(x => x.Path.StartsWith(episodePath)); - - if (episode == null) + catch (ItemNotFound) { await Console.Error.WriteLineAsync($"No episode found for subtitle at: ${path}."); - return; } - - Track track = new(StreamType.Subtitle, - null, - match.Groups["Language"].Value, - match.Groups["Default"].Value.Length > 0, - match.Groups["Forced"].Value.Length > 0, - SubtitleExtensions[Path.GetExtension(path)], - true, - path) + catch (Exception ex) { - Episode = episode - }; - - await libraryManager.Create(track); - Console.WriteLine($"Registering subtitle at: {path}."); + await Console.Error.WriteLineAsync($"Unknown error while registering subtitle: {ex.Message}"); + } } private async Task RegisterFile(string path, string relativePath, Library library, CancellationToken token) @@ -308,22 +312,20 @@ namespace Kyoo.Controllers { if (seasonNumber == -1) return default; - Season season = await libraryManager.Get(show.Slug, seasonNumber); - if (season == null) + try { - season = await _metadataProvider.GetSeason(show, seasonNumber, library); - try - { - await libraryManager.Create(season); - await _thumbnailsManager.Validate(season); - } - catch (DuplicatedItemException) - { - season = await libraryManager.Get(show.Slug, season.SeasonNumber); - } + Season season = await libraryManager.Get(show.Slug, seasonNumber); + season.Show = show; + return season; + } + catch (ItemNotFound) + { + Season season = await _metadataProvider.GetSeason(show, seasonNumber, library); + await libraryManager.CreateIfNotExists(season); + await _thumbnailsManager.Validate(season); + season.Show = show; + return season; } - season.Show = show; - return season; } private async Task GetEpisode(ILibraryManager libraryManager, diff --git a/Kyoo/Views/EpisodeApi.cs b/Kyoo/Views/EpisodeApi.cs index 7bc76af7..9d240e55 100644 --- a/Kyoo/Views/EpisodeApi.cs +++ b/Kyoo/Views/EpisodeApi.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; +using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; @@ -163,20 +164,30 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task GetThumb(int id) { - Episode episode = await _libraryManager.Get(id); - if (episode == null) + try + { + Episode episode = await _libraryManager.Get(id); + return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode)); + } + catch (ItemNotFound) + { return NotFound(); - return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode)); + } } [HttpGet("{slug}/thumb")] [Authorize(Policy="Read")] public async Task GetThumb(string slug) { - Episode episode = await _libraryManager.Get(slug); - if (episode == null) + try + { + Episode episode = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode)); + } + catch (ItemNotFound) + { return NotFound(); - return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode)); + } } } } \ No newline at end of file diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index fc8c28f7..de623916 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -376,13 +376,18 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task>> GetFonts(string slug) { - Show show = await _libraryManager.Get(slug); - if (show == null) + try + { + Show show = await _libraryManager.Get(slug); + string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments"); + return (await _files.ListFiles(path)) + .ToDictionary(Path.GetFileNameWithoutExtension, + x => $"{BaseURL}/api/shows/{slug}/fonts/{Path.GetFileName(x)}"); + } + catch (ItemNotFound) + { return NotFound(); - string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments"); - return (await _files.ListFiles(path)) - .ToDictionary(Path.GetFileNameWithoutExtension, - x => $"{BaseURL}/api/shows/{slug}/fonts/{Path.GetFileName(x)}"); + } } [HttpGet("{showSlug}/font/{slug}")] @@ -390,41 +395,61 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task GetFont(string showSlug, string slug) { - Show show = await _libraryManager.Get(showSlug); - if (show == null) + try + { + Show show = await _libraryManager.Get(showSlug); + string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments", slug); + return _files.FileResult(path); + } + catch (ItemNotFound) + { return NotFound(); - string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments", slug); - return _files.FileResult(path); + } } [HttpGet("{slug}/poster")] [Authorize(Policy = "Read")] public async Task GetPoster(string slug) { - Show show = await _libraryManager.Get(slug); - if (show == null) + try + { + Show show = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbs.GetShowPoster(show)); + } + catch (ItemNotFound) + { return NotFound(); - return _files.FileResult(await _thumbs.GetShowPoster(show)); + } } [HttpGet("{slug}/logo")] [Authorize(Policy="Read")] public async Task GetLogo(string slug) { - Show show = await _libraryManager.Get(slug); - if (show == null) + try + { + Show show = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbs.GetShowLogo(show)); + } + catch (ItemNotFound) + { return NotFound(); - return _files.FileResult(await _thumbs.GetShowLogo(show)); + } } [HttpGet("{slug}/backdrop")] [Authorize(Policy="Read")] public async Task GetBackdrop(string slug) { - Show show = await _libraryManager.Get(slug); - if (show == null) + try + { + Show show = await _libraryManager.Get(slug); + return _files.FileResult(await _thumbs.GetShowBackdrop(show)); + } + catch (ItemNotFound) + { return NotFound(); - return _files.FileResult(await _thumbs.GetShowBackdrop(show)); + } } } } diff --git a/Kyoo/Views/SubtitleApi.cs b/Kyoo/Views/SubtitleApi.cs index 73f151ee..f1a38ff3 100644 --- a/Kyoo/Views/SubtitleApi.cs +++ b/Kyoo/Views/SubtitleApi.cs @@ -30,7 +30,7 @@ namespace Kyoo.Api Track subtitle; try { - subtitle = await _libraryManager.GetTrack(slug, StreamType.Subtitle); + subtitle = await _libraryManager.Get(slug, StreamType.Subtitle); } catch (ArgumentException ex) { diff --git a/Kyoo/Views/WatchApi.cs b/Kyoo/Views/WatchApi.cs index 9d95d9ae..cd9327ae 100644 --- a/Kyoo/Views/WatchApi.cs +++ b/Kyoo/Views/WatchApi.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -21,10 +22,15 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task> GetWatchItem(string slug) { - Episode item = await _libraryManager.Get(slug); - if (item == null) + try + { + Episode item = await _libraryManager.Get(slug); + return await WatchItem.FromEpisode(item, _libraryManager); + } + catch (ItemNotFound) + { return NotFound(); - return await WatchItem.FromEpisode(item, _libraryManager); + } } } } From 6cc65d5aa8c657a0d178d9630accf8d496449bb6 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 21 Apr 2021 22:44:18 +0200 Subject: [PATCH 07/12] Add documentation to episodes and show repositories --- Kyoo.Common/Controllers/IRepository.cs | 8 -- Kyoo.CommonAPI/LocalRepository.cs | 6 +- .../Repositories/EpisodeRepository.cs | 87 +++++++++++++++---- .../Repositories/SeasonRepository.cs | 12 +++ .../Repositories/ShowRepository.cs | 47 ++++++++++ 5 files changed, 134 insertions(+), 26 deletions(-) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index fbe20a95..d4485716 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -375,14 +375,6 @@ namespace Kyoo.Controllers /// If the item is not found /// The episode found Task Get(string showSlug, int seasonNumber, int episodeNumber); - /// - /// Get a episode from it's season ID and it's episode number. - /// - /// The ID of the season - /// The episode number - /// If the item is not found - /// The episode found - Task Get(int seasonID, int episodeNumber); /// /// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found. diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 8f82093e..8e78964f 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -97,19 +97,19 @@ namespace Kyoo.Controllers } /// - public Task GetOrDefault(int id) + public virtual Task GetOrDefault(int id) { return Database.Set().FirstOrDefaultAsync(x => x.ID == id); } /// - public Task GetOrDefault(string slug) + public virtual Task GetOrDefault(string slug) { return Database.Set().FirstOrDefaultAsync(x => x.Slug == slug); } /// - public Task GetOrDefault(Expression> where) + public virtual Task GetOrDefault(Expression> where) { return Database.Set().FirstOrDefaultAsync(where); } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 84232067..fc384904 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -5,21 +5,49 @@ using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { + /// + /// A local repository to handle episodes. + /// public class EpisodeRepository : LocalRepository, IEpisodeRepository { + /// + /// Has this instance been disposed and should not handle requests? + /// private bool _disposed; + /// + /// The databse handle + /// private readonly DatabaseContext _database; + /// + /// A provider repository to handle externalID creation and deletion + /// private readonly IProviderRepository _providers; + /// + /// A show repository to get show's slug from their ID and keep the slug in each episode. + /// private readonly IShowRepository _shows; + /// + /// A track repository to handle creation and deletion of tracks related to the current episode. + /// private readonly ITrackRepository _tracks; + + /// protected override Expression> DefaultSort => x => x.EpisodeNumber; - public EpisodeRepository(DatabaseContext database, + /// + /// Create a new . + /// + /// The database handle to use. + /// A provider repository + /// A show repository + /// A track repository + public EpisodeRepository(DatabaseContext database, IProviderRepository providers, IShowRepository shows, ITrackRepository tracks) @@ -32,6 +60,7 @@ namespace Kyoo.Controllers } + /// public override void Dispose() { if (_disposed) @@ -43,6 +72,7 @@ namespace Kyoo.Controllers GC.SuppressFinalize(this); } + /// public override async ValueTask DisposeAsync() { if (_disposed) @@ -53,7 +83,8 @@ namespace Kyoo.Controllers await _shows.DisposeAsync(); } - public override async Task Get(int id) + /// + public override async Task GetOrDefault(int id) { Episode ret = await base.Get(id); if (ret != null) @@ -61,13 +92,14 @@ namespace Kyoo.Controllers return ret; } - public override async Task Get(string slug) + /// + public override async Task GetOrDefault(string slug) { Match match = Regex.Match(slug, @"(?.*)-s(?\d*)e(?\d*)"); if (match.Success) { - return await Get(match.Groups["show"].Value, + return await GetOrDefault(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value), int.Parse(match.Groups["episode"].Value)); } @@ -78,7 +110,8 @@ namespace Kyoo.Controllers return episode; } - public override async Task Get(Expression> predicate) + /// + public override async Task GetOrDefault(Expression> predicate) { Episode ret = await base.Get(predicate); if (ret != null) @@ -86,7 +119,8 @@ namespace Kyoo.Controllers return ret; } - public async Task Get(string showSlug, int seasonNumber, int episodeNumber) + /// + public async Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber) { Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug && x.SeasonNumber == seasonNumber @@ -96,7 +130,26 @@ namespace Kyoo.Controllers return ret; } + /// public async Task Get(int showID, int seasonNumber, int episodeNumber) + { + Episode ret = await GetOrDefault(showID, seasonNumber, episodeNumber); + if (ret == null) + throw new ItemNotFound($"No episode S{seasonNumber}E{episodeNumber} found on the show {showID}."); + return ret; + } + + /// + public async Task Get(string showSlug, int seasonNumber, int episodeNumber) + { + Episode ret = await GetOrDefault(showSlug, seasonNumber, episodeNumber); + if (ret == null) + throw new ItemNotFound($"No episode S{seasonNumber}E{episodeNumber} found on the show {showSlug}."); + return ret; + } + + /// + public async Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID && x.SeasonNumber == seasonNumber @@ -106,15 +159,7 @@ namespace Kyoo.Controllers return ret; } - public async Task Get(int seasonID, int episodeNumber) - { - Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.SeasonID == seasonID - && x.EpisodeNumber == episodeNumber); - if (ret != null) - ret.ShowSlug = await _shows.GetSlug(ret.ShowID); - return ret; - } - + /// public async Task GetAbsolute(int showID, int absoluteNumber) { Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID @@ -124,6 +169,7 @@ namespace Kyoo.Controllers return ret; } + /// public async Task GetAbsolute(string showSlug, int absoluteNumber) { Episode ret = await _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug @@ -133,6 +179,7 @@ namespace Kyoo.Controllers return ret; } + /// public override async Task> Search(string query) { List episodes = await _database.Episodes @@ -145,6 +192,7 @@ namespace Kyoo.Controllers return episodes; } + /// public override async Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) @@ -155,6 +203,7 @@ namespace Kyoo.Controllers return episodes; } + /// public override async Task Create(Episode obj) { await base.Create(obj); @@ -164,6 +213,7 @@ namespace Kyoo.Controllers return await ValidateTracks(obj); } + /// protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld) { if (resource.ShowID <= 0) @@ -185,6 +235,11 @@ namespace Kyoo.Controllers await Validate(resource); } + /// + /// Set track's index and ensure that every tracks is well-formed. + /// + /// The resource to fix. + /// The parameter is returnned. private async Task ValidateTracks(Episode resource) { resource.Tracks = await resource.Tracks.MapAsync((x, i) => @@ -199,6 +254,7 @@ namespace Kyoo.Controllers return resource; } + /// protected override async Task Validate(Episode resource) { await base.Validate(resource); @@ -211,6 +267,7 @@ namespace Kyoo.Controllers }).ToListAsync(); } + /// public override async Task Delete(Episode obj) { if (obj == null) diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index fb17d58f..22ee9a64 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -20,9 +20,21 @@ namespace Kyoo.Controllers /// Has this instance been disposed and should not handle requests? /// private bool _disposed; + /// + /// The database handle + /// private readonly DatabaseContext _database; + /// + /// A provider repository to handle externalID creation and deletion + /// private readonly IProviderRepository _providers; + /// + /// A show repository to get show's slug from their ID and keep the slug in each episode. + /// private readonly IShowRepository _shows; + /// + /// A lazilly loaded episode repository to handle deletion of episodes with the season. + /// private readonly Lazy _episodes; /// diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index f07e4f5a..e94419c2 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -9,18 +9,56 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { + /// + /// A local repository to handle shows + /// public class ShowRepository : LocalRepository, IShowRepository { + /// + /// Has this instance been disposed and should not handle requests? + /// private bool _disposed; + /// + /// The databse handle + /// private readonly DatabaseContext _database; + /// + /// A studio repository to handle creation/validation of related studios. + /// private readonly IStudioRepository _studios; + /// + /// A people repository to handle creation/validation of related people. + /// private readonly IPeopleRepository _people; + /// + /// A genres repository to handle creation/validation of related genres. + /// private readonly IGenreRepository _genres; + /// + /// A provider repository to handle externalID creation and deletion + /// private readonly IProviderRepository _providers; + /// + /// A lazy loaded season repository to handle cascade deletion (seasons deletion whith it's show) + /// private readonly Lazy _seasons; + /// + /// A lazy loaded episode repository to handle cascade deletion (episode deletion whith it's show) + /// private readonly Lazy _episodes; + + /// protected override Expression> DefaultSort => x => x.Title; + /// + /// Create a new . + /// + /// The database handle to use + /// A studio repository + /// A people repository + /// A genres repository + /// A provider repository + /// A service provider to lazilly request a season and an episode repository public ShowRepository(DatabaseContext database, IStudioRepository studios, IPeopleRepository people, @@ -38,6 +76,7 @@ namespace Kyoo.Controllers _episodes = new Lazy(services.GetRequiredService); } + /// public override void Dispose() { if (_disposed) @@ -55,6 +94,7 @@ namespace Kyoo.Controllers GC.SuppressFinalize(this); } + /// public override async ValueTask DisposeAsync() { if (_disposed) @@ -71,6 +111,7 @@ namespace Kyoo.Controllers await _episodes.Value.DisposeAsync(); } + /// public override async Task> Search(string query) { query = $"%{query}%"; @@ -83,6 +124,7 @@ namespace Kyoo.Controllers .ToListAsync(); } + /// public override async Task Create(Show obj) { await base.Create(obj); @@ -94,6 +136,7 @@ namespace Kyoo.Controllers return obj; } + /// protected override async Task Validate(Show resource) { await base.Validate(resource); @@ -119,6 +162,7 @@ namespace Kyoo.Controllers }); } + /// protected override async Task EditRelations(Show resource, Show changed, bool resetOld) { await Validate(changed); @@ -145,6 +189,7 @@ namespace Kyoo.Controllers } } + /// public async Task AddShowLink(int showID, int? libraryID, int? collectionID) { if (collectionID != null) @@ -168,6 +213,7 @@ namespace Kyoo.Controllers } } + /// public Task GetSlug(int showID) { return _database.Shows.Where(x => x.ID == showID) @@ -175,6 +221,7 @@ namespace Kyoo.Controllers .FirstOrDefaultAsync(); } + /// public override async Task Delete(Show obj) { if (obj == null) From 8db47d62fdbdb7387fda57cd090d2383ad2a1679 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 21 Apr 2021 23:28:22 +0200 Subject: [PATCH 08/12] Finishing documentation for all repositories --- Kyoo.Common/Controllers/IRepository.cs | 171 +++++++- .../Implementations/LibraryManager.cs | 364 +++--------------- .../Repositories/CollectionRepository.cs | 36 +- .../Repositories/GenreRepository.cs | 36 +- .../Repositories/LibraryItemRepository.cs | 66 +++- .../Repositories/LibraryRepository.cs | 26 ++ .../Repositories/PeopleRepository.cs | 46 ++- .../Repositories/ProviderRepository.cs | 19 +- .../Repositories/StudioRepository.cs | 18 +- .../Repositories/TrackRepository.cs | 72 ++-- 10 files changed, 449 insertions(+), 405 deletions(-) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index d4485716..71a6db8b 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -411,91 +411,240 @@ namespace Kyoo.Controllers Task GetAbsolute(string showSlug, int absoluteNumber); } + /// + /// A repository to handle tracks + /// public interface ITrackRepository : IRepository { + /// + /// Get a track 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 Task Get(string slug, StreamType type = StreamType.Unknown); + + /// + /// 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 + Task GetOrDefault(string slug, StreamType type = StreamType.Unknown); } + /// + /// A repository to handle libraries. + /// public interface ILibraryRepository : IRepository { } + /// + /// A repository to handle library items (A wrapper arround shows and collections). + /// public interface ILibraryItemRepository : IRepository { + /// + /// 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> GetFromLibrary(int id, Expression> where = null, Sort sort = default, Pagination limit = default); - + /// + /// Get items (A wrapper arround shows or collections) from a library. + /// + /// The ID of the library + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters public Task> GetFromLibrary(int id, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetFromLibrary(id, where, new Sort(sort), limit); - public Task> GetFromLibrary(string librarySlug, + /// + /// 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> GetFromLibrary(string slug, Expression> where = null, Sort sort = default, Pagination limit = default); - - public Task> GetFromLibrary(string librarySlug, + /// + /// Get items (A wrapper arround shows or collections) from a library. + /// + /// The slug of the library + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters + public Task> GetFromLibrary(string slug, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetFromLibrary(librarySlug, where, new Sort(sort), limit); + ) => GetFromLibrary(slug, where, new Sort(sort), limit); } + /// + /// A repository for collections + /// public interface ICollectionRepository : IRepository { } + + /// + /// A repository for genres. + /// public interface IGenreRepository : IRepository { } + + /// + /// A repository for studios. + /// public interface IStudioRepository : IRepository { } + /// + /// A repository for people. + /// public interface IPeopleRepository : IRepository { + /// + /// 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 Task> GetFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default); + /// + /// Get people's roles from a show. + /// + /// The ID of the show + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters Task> GetFromShow(int showID, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetFromShow(showID, where, new Sort(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 Task> GetFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default); + /// + /// Get people's roles from a show. + /// + /// The slug of the show + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters Task> GetFromShow(string showSlug, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); - Task> GetFromPeople(int showID, + /// + /// 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 + Task> GetFromPeople(int id, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetFromPeople(int showID, + /// + /// Get people's roles from a person. + /// + /// The id of the person + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters + Task> GetFromPeople(int id, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetFromPeople(showID, where, new Sort(sort), limit); + ) => GetFromPeople(id, where, new Sort(sort), limit); - Task> GetFromPeople(string showSlug, + /// + /// 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 + Task> GetFromPeople(string slug, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetFromPeople(string showSlug, + /// + /// Get people's roles from a person. + /// + /// The slug of the person + /// A filter function + /// A sort by method + /// How many items to return and where to start + /// A list of items that match every filters + Task> GetFromPeople(string slug, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetFromPeople(showSlug, where, new Sort(sort), limit); + ) => GetFromPeople(slug, where, new Sort(sort), limit); } + /// + /// A repository to handle providers. + /// public interface IProviderRepository : IRepository { + /// + /// Get a list of external ids that match all filters + /// + /// A predicate to add arbitrary filter + /// Sort information (sort order & sort by) + /// Paginations information (where to start and how many to get) + /// A filtered list of external ids. Task> GetMetadataID(Expression> where = null, Sort sort = default, Pagination limit = default); + /// + /// Get a list of external ids that match all filters + /// + /// A predicate to add arbitrary filter + /// A sort by expression + /// Paginations information (where to start and how many to get) + /// A filtered list of external ids. Task> GetMetadataID([Optional] Expression> where, Expression> sort, Pagination limit = default diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index b1f66291..de35db92 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -15,66 +15,35 @@ namespace Kyoo.Controllers /// 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. + /// 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(); @@ -91,9 +60,7 @@ namespace Kyoo.Controllers ProviderRepository = GetRepository() as IProviderRepository; } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// + /// public void Dispose() { foreach (IBaseRepository repo in _repositories) @@ -101,21 +68,13 @@ namespace Kyoo.Controllers 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 { @@ -124,208 +83,109 @@ namespace Kyoo.Controllers 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() @@ -335,14 +195,7 @@ namespace Kyoo.Controllers 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() @@ -352,13 +205,7 @@ namespace Kyoo.Controllers 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 { @@ -386,11 +233,7 @@ namespace Kyoo.Controllers 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) @@ -544,14 +387,7 @@ namespace Kyoo.Controllers }; } - /// - /// 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, @@ -560,14 +396,7 @@ namespace Kyoo.Controllers 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, @@ -576,14 +405,7 @@ namespace Kyoo.Controllers 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, @@ -592,14 +414,7 @@ namespace Kyoo.Controllers 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, @@ -608,14 +423,7 @@ namespace Kyoo.Controllers 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, @@ -624,14 +432,7 @@ namespace Kyoo.Controllers 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, @@ -640,23 +441,13 @@ namespace Kyoo.Controllers 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) @@ -664,14 +455,7 @@ namespace Kyoo.Controllers 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) @@ -680,98 +464,56 @@ namespace Kyoo.Controllers 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 { diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index ccebc298..bbef77af 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -8,34 +8,30 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { + /// + /// A local repository to handle collections + /// public class CollectionRepository : LocalRepository, ICollectionRepository { - private bool _disposed; + /// + /// The database handle + /// private readonly DatabaseContext _database; + + /// protected override Expression> DefaultSort => x => x.Name; - public CollectionRepository(DatabaseContext database) : base(database) + /// + /// Create a new . + /// + /// The database handle to use + public CollectionRepository(DatabaseContext database) + : base(database) { _database = database; } - public override void Dispose() - { - if (_disposed) - return; - _disposed = true; - _database.Dispose(); - GC.SuppressFinalize(this); - } - - public override async ValueTask DisposeAsync() - { - if (_disposed) - return; - _disposed = true; - await _database.DisposeAsync(); - } - + /// public override async Task> Search(string query) { return await _database.Collections @@ -45,6 +41,7 @@ namespace Kyoo.Controllers .ToListAsync(); } + /// public override async Task Create(Collection obj) { await base.Create(obj); @@ -53,6 +50,7 @@ namespace Kyoo.Controllers return obj; } + /// public override async Task Delete(Collection obj) { if (obj == null) diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index bfbc1e3c..0ce1a155 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -8,35 +8,31 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { + /// + /// A local repository for genres. + /// public class GenreRepository : LocalRepository, IGenreRepository { - private bool _disposed; + /// + /// The database handle + /// private readonly DatabaseContext _database; + + /// protected override Expression> DefaultSort => x => x.Slug; - public GenreRepository(DatabaseContext database) : base(database) + /// + /// Create a new . + /// + /// The database handle + public GenreRepository(DatabaseContext database) + : base(database) { _database = database; } - public override void Dispose() - { - if (_disposed) - return; - _disposed = true; - _database.Dispose(); - GC.SuppressFinalize(this); - } - - public override async ValueTask DisposeAsync() - { - if (_disposed) - return; - _disposed = true; - await _database.DisposeAsync(); - } - + /// public override async Task> Search(string query) { return await _database.Genres @@ -46,6 +42,7 @@ namespace Kyoo.Controllers .ToListAsync(); } + /// public override async Task Create(Genre obj) { await base.Create(obj); @@ -54,6 +51,7 @@ namespace Kyoo.Controllers return obj; } + /// public override async Task Delete(Genre obj) { if (obj == null) diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 57f293b5..9d96b23f 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -10,18 +10,49 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { + /// + /// A local repository to handle library items. + /// public class LibraryItemRepository : LocalRepository, ILibraryItemRepository { + /// + /// Has this instance been disposed and should not handle requests? + /// private bool _disposed; + /// + /// The database handle + /// private readonly DatabaseContext _database; + /// + /// A provider repository to handle externalID creation and deletion + /// private readonly IProviderRepository _providers; + /// + /// A lazy loaded library repository to validate queries (check if a library does exist) + /// private readonly Lazy _libraries; + /// + /// A lazy loaded show repository to get a show from it's id. + /// private readonly Lazy _shows; + /// + /// A lazy loaded collection repository to get a collection from it's id. + /// private readonly Lazy _collections; + + /// protected override Expression> DefaultSort => x => x.Title; - public LibraryItemRepository(DatabaseContext database, IProviderRepository providers, IServiceProvider services) + /// + /// Create a new . + /// + /// The databse instance + /// A provider repository + /// A service provider to lazilly request a library, show or collection repository. + public LibraryItemRepository(DatabaseContext database, + IProviderRepository providers, + IServiceProvider services) : base(database) { _database = database; @@ -31,6 +62,7 @@ namespace Kyoo.Controllers _collections = new Lazy(services.GetRequiredService); } + /// public override void Dispose() { if (_disposed) @@ -45,6 +77,7 @@ namespace Kyoo.Controllers GC.SuppressFinalize(this); } + /// public override async ValueTask DisposeAsync() { if (_disposed) @@ -58,6 +91,7 @@ namespace Kyoo.Controllers await _collections.Value.DisposeAsync(); } + /// public override async Task Get(int id) { return id > 0 @@ -65,18 +99,24 @@ namespace Kyoo.Controllers : new LibraryItem(await _collections.Value.Get(-id)); } + /// public override Task Get(string slug) { - throw new InvalidOperationException(); + throw new InvalidOperationException("You can't get a library item by a slug."); } - private IQueryable ItemsQuery + /// + /// Get a basic queryable with the right mapping from shows & collections. + /// Shows contained in a collection are excluded. + /// + private IQueryable ItemsQuery => _database.Shows .Where(x => !x.Collections.Any()) .Select(LibraryItem.FromShow) .Concat(_database.Collections .Select(LibraryItem.FromCollection)); + /// public override Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) @@ -84,6 +124,7 @@ namespace Kyoo.Controllers return ApplyFilters(ItemsQuery, where, sort, limit); } + /// public override Task GetCount(Expression> where = null) { IQueryable query = ItemsQuery; @@ -92,6 +133,7 @@ namespace Kyoo.Controllers return query.CountAsync(); } + /// public override async Task> Search(string query) { return await ItemsQuery @@ -101,19 +143,31 @@ namespace Kyoo.Controllers .ToListAsync(); } + /// public override Task Create(LibraryItem obj) => throw new InvalidOperationException(); + /// public override Task CreateIfNotExists(LibraryItem obj, bool silentFail = false) { if (silentFail) return Task.FromResult(default); throw new InvalidOperationException(); } + /// public override Task Edit(LibraryItem obj, bool reset) => throw new InvalidOperationException(); + /// public override Task Delete(int id) => throw new InvalidOperationException(); + /// public override Task Delete(string slug) => throw new InvalidOperationException(); + /// public override Task Delete(LibraryItem obj) => throw new InvalidOperationException(); + /// + /// Get a basic queryable for a library with the right mapping from shows & collections. + /// Shows contained in a collection are excluded. + /// + /// Only items that are part of a library that match this predicate will be returned. + /// A queryable containing items that are part of a library matching the selector. private IQueryable LibraryRelatedQuery(Expression> selector) => _database.Libraries .Where(selector) @@ -125,7 +179,8 @@ namespace Kyoo.Controllers .SelectMany(x => x.Collections) .Select(LibraryItem.FromCollection)); - public async Task> GetFromLibrary(int id, + /// + public async Task> GetFromLibrary(int id, Expression> where = null, Sort sort = default, Pagination limit = default) @@ -139,7 +194,8 @@ namespace Kyoo.Controllers return items; } - public async Task> GetFromLibrary(string slug, + /// + public async Task> GetFromLibrary(string slug, Expression> where = null, Sort sort = default, Pagination limit = default) diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index b4d725c1..f61e4445 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -8,14 +8,33 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { + /// + /// A local repository to handle libraries. + /// public class LibraryRepository : LocalRepository, ILibraryRepository { + /// + /// Has this instance been disposed and should not handle requests? + /// private bool _disposed; + /// + /// The database handle + /// private readonly DatabaseContext _database; + /// + /// A provider repository to handle externalID creation and deletion + /// private readonly IProviderRepository _providers; + + /// protected override Expression> DefaultSort => x => x.ID; + /// + /// Create a new instance. + /// + /// The database handle + /// The providere repository public LibraryRepository(DatabaseContext database, IProviderRepository providers) : base(database) { @@ -23,6 +42,7 @@ namespace Kyoo.Controllers _providers = providers; } + /// public override void Dispose() { if (_disposed) @@ -33,6 +53,7 @@ namespace Kyoo.Controllers GC.SuppressFinalize(this); } + /// public override async ValueTask DisposeAsync() { if (_disposed) @@ -42,6 +63,7 @@ namespace Kyoo.Controllers await _providers.DisposeAsync(); } + /// public override async Task> Search(string query) { return await _database.Libraries @@ -51,6 +73,7 @@ namespace Kyoo.Controllers .ToListAsync(); } + /// public override async Task Create(Library obj) { await base.Create(obj); @@ -61,6 +84,7 @@ namespace Kyoo.Controllers return obj; } + /// protected override async Task Validate(Library resource) { await base.Validate(resource); @@ -69,6 +93,7 @@ namespace Kyoo.Controllers .ToListAsync(); } + /// protected override async Task EditRelations(Library resource, Library changed, bool resetOld) { if (string.IsNullOrEmpty(resource.Slug)) @@ -86,6 +111,7 @@ namespace Kyoo.Controllers } } + /// public override async Task Delete(Library obj) { if (obj == null) diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 9097bea8..7d1c870e 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -10,15 +10,40 @@ using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { + /// + /// A local repository to handle people. + /// public class PeopleRepository : LocalRepository, IPeopleRepository { + /// + /// Has this instance been disposed and should not handle requests? + /// private bool _disposed; + /// + /// The database handle + /// private readonly DatabaseContext _database; + /// + /// A provider repository to handle externalID creation and deletion + /// private readonly IProviderRepository _providers; + /// + /// A lazy loaded show repository to validate requests from shows. + /// private readonly Lazy _shows; + + /// protected override Expression> DefaultSort => x => x.Name; - public PeopleRepository(DatabaseContext database, IProviderRepository providers, IServiceProvider services) + /// + /// Create a new + /// + /// The database handle + /// A provider repository + /// A service provider to lazy load a show repository + public PeopleRepository(DatabaseContext database, + IProviderRepository providers, + IServiceProvider services) : base(database) { _database = database; @@ -27,6 +52,7 @@ namespace Kyoo.Controllers } + /// public override void Dispose() { if (_disposed) @@ -39,6 +65,7 @@ namespace Kyoo.Controllers GC.SuppressFinalize(this); } + /// public override async ValueTask DisposeAsync() { if (_disposed) @@ -50,6 +77,7 @@ namespace Kyoo.Controllers await _shows.Value.DisposeAsync(); } + /// public override async Task> Search(string query) { return await _database.People @@ -59,6 +87,7 @@ namespace Kyoo.Controllers .ToListAsync(); } + /// public override async Task Create(People obj) { await base.Create(obj); @@ -68,6 +97,7 @@ namespace Kyoo.Controllers return obj; } + /// protected override async Task Validate(People resource) { await base.Validate(resource); @@ -85,6 +115,7 @@ namespace Kyoo.Controllers }); } + /// protected override async Task EditRelations(People resource, People changed, bool resetOld) { if (changed.Roles != null || resetOld) @@ -102,6 +133,7 @@ namespace Kyoo.Controllers await base.EditRelations(resource, changed, resetOld); } + /// public override async Task Delete(People obj) { if (obj == null) @@ -113,6 +145,7 @@ namespace Kyoo.Controllers await _database.SaveChangesAsync(); } + /// public async Task> GetFromShow(int showID, Expression> where = null, Sort sort = default, @@ -133,6 +166,7 @@ namespace Kyoo.Controllers return people; } + /// public async Task> GetFromShow(string showSlug, Expression> where = null, Sort sort = default, @@ -154,24 +188,26 @@ namespace Kyoo.Controllers return people; } - public async Task> GetFromPeople(int peopleID, + /// + public async Task> GetFromPeople(int id, Expression> where = null, Sort sort = default, Pagination limit = default) { ICollection roles = await ApplyFilters(_database.PeopleRoles - .Where(x => x.PeopleID == peopleID) + .Where(x => x.PeopleID == id) .Include(x => x.Show), - id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), + y => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == y), x => x.Show.Title, where, sort, limit); - if (!roles.Any() && await Get(peopleID) == null) + if (!roles.Any() && await Get(id) == null) throw new ItemNotFound(); return roles; } + /// public async Task> GetFromPeople(string slug, Expression> where = null, Sort sort = default, diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 2d90b889..31c283d3 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -8,17 +8,31 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { + /// + /// A local repository to handle providers. + /// public class ProviderRepository : LocalRepository, IProviderRepository { + /// + /// The database handle + /// private readonly DatabaseContext _database; + + /// protected override Expression> DefaultSort => x => x.Slug; - public ProviderRepository(DatabaseContext database) : base(database) + /// + /// Create a new . + /// + /// The database handle + public ProviderRepository(DatabaseContext database) + : base(database) { _database = database; } + /// public override async Task> Search(string query) { return await _database.Providers @@ -28,6 +42,7 @@ namespace Kyoo.Controllers .ToListAsync(); } + /// public override async Task Create(Provider obj) { await base.Create(obj); @@ -36,6 +51,7 @@ namespace Kyoo.Controllers return obj; } + /// public override async Task Delete(Provider obj) { if (obj == null) @@ -46,6 +62,7 @@ namespace Kyoo.Controllers await _database.SaveChangesAsync(); } + /// public Task> GetMetadataID(Expression> where = null, Sort sort = default, Pagination limit = default) diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index f8afc757..6c813f65 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -8,17 +8,31 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { + /// + /// A local repository to handle studios + /// public class StudioRepository : LocalRepository, IStudioRepository { + /// + /// The database handle + /// private readonly DatabaseContext _database; + + /// protected override Expression> DefaultSort => x => x.Name; - public StudioRepository(DatabaseContext database) : base(database) + /// + /// Create a new . + /// + /// The database handle + public StudioRepository(DatabaseContext database) + : base(database) { _database = database; } + /// public override async Task> Search(string query) { return await _database.Studios @@ -28,6 +42,7 @@ namespace Kyoo.Controllers .ToListAsync(); } + /// public override async Task Create(Studio obj) { await base.Create(obj); @@ -36,6 +51,7 @@ namespace Kyoo.Controllers return obj; } + /// public override async Task Delete(Studio obj) { if (obj == null) diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index d4e04376..4d3c725d 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -9,40 +10,48 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { + /// + /// A local repository to handle tracks. + /// public class TrackRepository : LocalRepository, ITrackRepository { - private bool _disposed; + /// + /// The databse handle + /// private readonly DatabaseContext _database; + + /// protected override Expression> DefaultSort => x => x.TrackIndex; - public TrackRepository(DatabaseContext database) : base(database) + /// + /// Create a new . + /// + /// The datatabse handle + public TrackRepository(DatabaseContext database) + : base(database) { _database = database; } + - public override void Dispose() - { - if (_disposed) - return; - _disposed = true; - _database.Dispose(); - } - - public override async ValueTask DisposeAsync() - { - if (_disposed) - return; - _disposed = true; - await _database.DisposeAsync(); - } - + /// public override Task Get(string slug) { return Get(slug, StreamType.Unknown); } + + /// + public async Task Get(string slug, StreamType type) + { + Track ret = await GetOrDefault(slug, type); + if (ret == null) + throw new ItemNotFound($"No track found with the slug {slug} and the type {type}."); + return ret; + } - public Task Get(string slug, StreamType type) + /// + public Task GetOrDefault(string slug, StreamType type) { Match match = Regex.Match(slug, @"(?.*)-s(?\d+)e(?\d+)(\.(?\w*))?\.(?.{0,3})(?-forced)?(\..*)?"); @@ -65,27 +74,23 @@ namespace Kyoo.Controllers if (match.Groups["type"].Success) type = Enum.Parse(match.Groups["type"].Value, true); - if (type == StreamType.Unknown) - { - return _database.Tracks.FirstOrDefaultAsync(x => x.Episode.Show.Slug == showSlug - && x.Episode.SeasonNumber == seasonNumber - && x.Episode.EpisodeNumber == episodeNumber - && x.Language == language - && x.IsForced == forced); - } - return _database.Tracks.FirstOrDefaultAsync(x => x.Episode.Show.Slug == showSlug - && x.Episode.SeasonNumber == seasonNumber - && x.Episode.EpisodeNumber == episodeNumber - && x.Type == type - && x.Language == language - && x.IsForced == forced); + IQueryable query = _database.Tracks.Where(x => x.Episode.Show.Slug == showSlug + && x.Episode.SeasonNumber == seasonNumber + && x.Episode.EpisodeNumber == episodeNumber + && x.Language == language + && x.IsForced == forced); + if (type != StreamType.Unknown) + return query.FirstOrDefaultAsync(x => x.Type == type); + return query.FirstOrDefaultAsync(); } + /// public override Task> Search(string query) { throw new InvalidOperationException("Tracks do not support the search method."); } + /// public override async Task Create(Track obj) { if (obj.EpisodeID <= 0) @@ -107,6 +112,7 @@ namespace Kyoo.Controllers return obj; } + /// public override async Task Delete(Track obj) { if (obj == null) From 540a3c27de015a14468c67657aabfaf60b487039 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 23 Apr 2021 00:46:38 +0200 Subject: [PATCH 09/12] Cleaning up the configuration settings --- Kyoo.Common/Models/WatchItem.cs | 8 +- .../Repositories/EpisodeRepository.cs | 6 +- .../Repositories/LibraryItemRepository.cs | 12 +- .../Repositories/SeasonRepository.cs | 4 +- .../Repositories/TrackRepository.cs | 8 +- Kyoo/Kyoo.csproj | 6 +- Kyoo/Models/DatabaseContext.cs | 168 ++++++++++++++++-- Kyoo/Program.cs | 58 ++++-- Kyoo/Views/EpisodeApi.cs | 30 +++- Kyoo/Views/SeasonApi.cs | 4 +- Kyoo/Views/SubtitleApi.cs | 4 +- Kyoo/{appsettings.json => settings.json} | 34 ++-- deployment/kyoo.service | 3 +- settings.json | 42 +++++ 14 files changed, 304 insertions(+), 83 deletions(-) rename Kyoo/{appsettings.json => settings.json} (56%) create mode 100644 settings.json diff --git a/Kyoo.Common/Models/WatchItem.cs b/Kyoo.Common/Models/WatchItem.cs index 8ab7b5dc..71ec1993 100644 --- a/Kyoo.Common/Models/WatchItem.cs +++ b/Kyoo.Common/Models/WatchItem.cs @@ -109,18 +109,18 @@ namespace Kyoo.Models if (!ep.Show.IsMovie) { if (ep.EpisodeNumber > 1) - previous = await library.Get(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber - 1); + previous = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber - 1); else if (ep.SeasonNumber > 1) { int count = await library.GetCount(x => x.ShowID == ep.ShowID && x.SeasonNumber == ep.SeasonNumber - 1); - previous = await library.Get(ep.ShowID, ep.SeasonNumber - 1, count); + previous = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber - 1, count); } if (ep.EpisodeNumber >= await library.GetCount(x => x.SeasonID == ep.SeasonID)) - next = await library.Get(ep.ShowID, ep.SeasonNumber + 1, 1); + next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber + 1, 1); else - next = await library.Get(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber + 1); + next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber + 1); } return new WatchItem(ep.ID, diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index fc384904..032dff38 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -86,7 +86,7 @@ namespace Kyoo.Controllers /// public override async Task GetOrDefault(int id) { - Episode ret = await base.Get(id); + Episode ret = await base.GetOrDefault(id); if (ret != null) ret.ShowSlug = await _shows.GetSlug(ret.ShowID); return ret; @@ -111,9 +111,9 @@ namespace Kyoo.Controllers } /// - public override async Task GetOrDefault(Expression> predicate) + public override async Task GetOrDefault(Expression> where) { - Episode ret = await base.Get(predicate); + Episode ret = await base.GetOrDefault(where); if (ret != null) ret.ShowSlug = await _shows.GetSlug(ret.ShowID); return ret; diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 9d96b23f..44fd81d3 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -92,15 +92,15 @@ namespace Kyoo.Controllers } /// - public override async Task Get(int id) + public override async Task GetOrDefault(int id) { return id > 0 - ? new LibraryItem(await _shows.Value.Get(id)) - : new LibraryItem(await _collections.Value.Get(-id)); + ? new LibraryItem(await _shows.Value.GetOrDefault(id)) + : new LibraryItem(await _collections.Value.GetOrDefault(-id)); } /// - public override Task Get(string slug) + public override Task GetOrDefault(string slug) { throw new InvalidOperationException("You can't get a library item by a slug."); } @@ -189,7 +189,7 @@ namespace Kyoo.Controllers where, sort, limit); - if (!items.Any() && await _libraries.Value.Get(id) == null) + if (!items.Any() && await _libraries.Value.GetOrDefault(id) == null) throw new ItemNotFound(); return items; } @@ -204,7 +204,7 @@ namespace Kyoo.Controllers where, sort, limit); - if (!items.Any() && await _libraries.Value.Get(slug) == null) + if (!items.Any() && await _libraries.Value.GetOrDefault(slug) == null) throw new ItemNotFound(); return items; } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 22ee9a64..a2dce413 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -98,9 +98,9 @@ namespace Kyoo.Controllers } /// - public override async Task Get(Expression> predicate) + public override async Task Get(Expression> where) { - Season ret = await base.Get(predicate); + Season ret = await base.Get(where); ret.ShowSlug = await _shows.GetSlug(ret.ShowID); return ret; } diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 4d3c725d..55ddb427 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -36,13 +36,13 @@ namespace Kyoo.Controllers /// - public override Task Get(string slug) + Task IRepository.Get(string slug) { - return Get(slug, StreamType.Unknown); + return Get(slug); } /// - public async Task Get(string slug, StreamType type) + public async Task Get(string slug, StreamType type = StreamType.Unknown) { Track ret = await GetOrDefault(slug, type); if (ret == null) @@ -51,7 +51,7 @@ namespace Kyoo.Controllers } /// - public Task GetOrDefault(string slug, StreamType type) + public Task GetOrDefault(string slug, StreamType type = StreamType.Unknown) { Match match = Regex.Match(slug, @"(?.*)-s(?\d+)e(?\d+)(\.(?\w*))?\.(?.{0,3})(?-forced)?(\..*)?"); diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 713d807c..2d624b9c 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -113,10 +113,8 @@ - - + + diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 7d76a3d4..577e9903 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; @@ -10,28 +11,71 @@ using Npgsql; namespace Kyoo { + /// + /// The database handle used for all local repositories. + /// + /// + /// It should not be used directly, to access the database use a or repositories. + /// public class DatabaseContext : DbContext { - public DatabaseContext(DbContextOptions options) : base(options) - { - ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - ChangeTracker.LazyLoadingEnabled = false; - } - + /// + /// All libraries of Kyoo. See . + /// public DbSet Libraries { get; set; } + /// + /// All collections of Kyoo. See . + /// public DbSet Collections { get; set; } + /// + /// All shows of Kyoo. See . + /// public DbSet Shows { get; set; } + /// + /// All seasons of Kyoo. See . + /// public DbSet Seasons { get; set; } + /// + /// All episodes of Kyoo. See . + /// public DbSet Episodes { get; set; } + /// + /// All tracks of Kyoo. See . + /// public DbSet Tracks { get; set; } + /// + /// All genres of Kyoo. See . + /// public DbSet Genres { get; set; } + /// + /// All people of Kyoo. See . + /// public DbSet People { get; set; } + /// + /// All studios of Kyoo. See . + /// public DbSet Studios { get; set; } + /// + /// All providers of Kyoo. See . + /// public DbSet Providers { get; set; } + /// + /// All metadataIDs (ExternalIDs) of Kyoo. See . + /// public DbSet MetadataIds { get; set; } + /// + /// All people's role. See . + /// public DbSet PeopleRoles { get; set; } + /// + /// Get a generic link between two resource types. + /// + /// Types are order dependant. You can't inverse the order. Please always put the owner first. + /// The first resource type of the relation. It is the owner of the second + /// The second resource type of the relation. It is the contained resource. + /// All links between the two types. public DbSet> Links() where T1 : class, IResource where T2 : class, IResource @@ -39,7 +83,10 @@ namespace Kyoo return Set>(); } - + + /// + /// A basic constructor that set default values (query tracker behaviors, mapping enums...) + /// public DatabaseContext() { NpgsqlConnection.GlobalTypeMapper.MapEnum(); @@ -50,6 +97,21 @@ namespace Kyoo ChangeTracker.LazyLoadingEnabled = false; } + /// + /// Create a new . + /// + /// Connection options to use (witch databse provider to use, connection strings...) + public DatabaseContext(DbContextOptions options) + : base(options) + { + ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + ChangeTracker.LazyLoadingEnabled = false; + } + + /// + /// Set database parameters to support every types of Kyoo. + /// + /// The database's model builder. protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -58,14 +120,6 @@ namespace Kyoo modelBuilder.HasPostgresEnum(); modelBuilder.HasPostgresEnum(); - // modelBuilder.Entity() - // .Property(x => x.Paths) - // .HasColumnType("text[]"); - // - // modelBuilder.Entity() - // .Property(x => x.Aliases) - // .HasColumnType("text[]"); - modelBuilder.Entity() .Property(t => t.IsDefault) .ValueGeneratedNever(); @@ -196,6 +250,13 @@ namespace Kyoo .IsUnique(); } + /// + /// Return a new or an in cache temporary object wih the same ID as the one given + /// + /// If a resource with the same ID is found in the database, it will be used. + /// will be used overwise + /// The type of the resource + /// A resource that is now tracked by this context. public T GetTemporaryObject(T model) where T : class, IResource { @@ -206,6 +267,11 @@ namespace Kyoo return model; } + /// + /// Save changes that are applied to this context. + /// + /// A duplicated item has been found. + /// The number of state entries written to the database. public override int SaveChanges() { try @@ -221,6 +287,13 @@ namespace Kyoo } } + /// + /// Save changes that are applied to this context. + /// + /// Indicates whether AcceptAllChanges() is called after the changes + /// have been sent successfully to the database. + /// A duplicated item has been found. + /// The number of state entries written to the database. public override int SaveChanges(bool acceptAllChangesOnSuccess) { try @@ -236,6 +309,13 @@ namespace Kyoo } } + /// + /// Save changes that are applied to this context. + /// + /// The message that will have the + /// (if a duplicate is found). + /// A duplicated item has been found. + /// The number of state entries written to the database. public int SaveChanges(string duplicateMessage) { try @@ -251,6 +331,14 @@ namespace Kyoo } } + /// + /// Save changes that are applied to this context. + /// + /// Indicates whether AcceptAllChanges() is called after the changes + /// have been sent successfully to the database. + /// A to observe while waiting for the task to complete + /// A duplicated item has been found. + /// The number of state entries written to the database. public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new()) { @@ -267,6 +355,12 @@ namespace Kyoo } } + /// + /// Save changes that are applied to this context. + /// + /// A to observe while waiting for the task to complete + /// A duplicated item has been found. + /// The number of state entries written to the database. public override async Task SaveChangesAsync(CancellationToken cancellationToken = new()) { try @@ -282,6 +376,14 @@ namespace Kyoo } } + /// + /// Save changes that are applied to this context. + /// + /// The message that will have the + /// (if a duplicate is found). + /// A to observe while waiting for the task to complete + /// A duplicated item has been found. + /// The number of state entries written to the database. public async Task SaveChangesAsync(string duplicateMessage, CancellationToken cancellationToken = new()) { @@ -298,6 +400,12 @@ namespace Kyoo } } + /// + /// Save changes if no duplicates are found. If one is found, no change are saved but the current changes are no discarded. + /// The current context will still hold those invalid changes. + /// + /// A to observe while waiting for the task to complete + /// The number of state entries written to the database or -1 if a duplicate exist. public async Task SaveIfNoDuplicates(CancellationToken cancellationToken = new()) { try @@ -310,12 +418,31 @@ namespace Kyoo } } + /// + /// Save items or retry with a custom method if a duplicate is found. + /// + /// The item to save (other changes of this context will also be saved) + /// A function to run on fail, the param wil be mapped. + /// The second parameter is the current retry number. + /// A to observe while waiting for the task to complete + /// The type of the item to save + /// The number of state entries written to the database. public Task SaveOrRetry(T obj, Func onFail, CancellationToken cancellationToken = new()) { return SaveOrRetry(obj, onFail, 0, cancellationToken); } - public async Task SaveOrRetry(T obj, + /// + /// Save items or retry with a custom method if a duplicate is found. + /// + /// The item to save (other changes of this context will also be saved) + /// A function to run on fail, the param wil be mapped. + /// The second parameter is the current retry number. + /// The current retry number. + /// A to observe while waiting for the task to complete + /// The type of the item to save + /// The number of state entries written to the database. + private async Task SaveOrRetry(T obj, Func onFail, int recurse, CancellationToken cancellationToken = new()) @@ -337,11 +464,20 @@ namespace Kyoo } } + /// + /// Check if the exception is a duplicated exception. + /// + /// WARNING: this only works for PostgreSQL + /// The exception to check + /// True if the exception is a duplicate exception. False otherwise private static bool IsDuplicateException(Exception ex) { return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation}; } + /// + /// Delete every changes that are on this context. + /// private void DiscardChanges() { foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index 81e73def..1fa2b7c3 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -3,7 +3,11 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; -using Microsoft.VisualBasic.FileIO; +using Microsoft.AspNetCore.Hosting.StaticWebAssets; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace Kyoo { @@ -18,12 +22,9 @@ namespace Kyoo /// Command line arguments public static async Task Main(string[] args) { - if (args.Length > 0) - FileSystem.CurrentDirectory = args[0]; - if (!File.Exists("./appsettings.json")) - File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "appsettings.json"), "appsettings.json"); - - + if (!File.Exists("./settings.json")) + File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "settings.json"), "settings.json"); + bool? debug = Environment.GetEnvironmentVariable("ENVIRONMENT")?.ToLowerInvariant() switch { "d" => true, @@ -49,15 +50,50 @@ namespace Kyoo await host.Build().RunAsync(); } + /// + /// Register settings.json, environment variables and command lines arguments as configuration. + /// + /// The configuration builder to use + /// The command line arguments + /// The modified configuration builder + private static IConfigurationBuilder SetupConfig(IConfigurationBuilder builder, string[] args) + { + return builder.AddJsonFile("./settings.json", false, true) + .AddEnvironmentVariables() + .AddCommandLine(args); + } + /// /// Createa a web host /// /// Command line parameters that can be handled by kestrel /// A new web host instance - private static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseKestrel(config => { config.AddServerHeader = false; }) - .UseUrls("http://*:5000") + private static IWebHostBuilder CreateWebHostBuilder(string[] args) + { + WebHost.CreateDefaultBuilder(args); + + return new WebHostBuilder() + .UseContentRoot(AppDomain.CurrentDomain.BaseDirectory) + .UseConfiguration(SetupConfig(new ConfigurationBuilder(), args).Build()) + .ConfigureAppConfiguration(x => SetupConfig(x, args)) + .ConfigureLogging((context, builder) => + { + builder.AddConfiguration(context.Configuration.GetSection("logging")) + .AddConsole() + .AddDebug() + .AddEventSourceLogger(); + }) + .UseDefaultServiceProvider((context, options) => + { + options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); + if (context.HostingEnvironment.IsDevelopment()) + StaticWebAssetsLoader.UseStaticWebAssets(context.HostingEnvironment, context.Configuration); + }) + .ConfigureServices(x => x.AddRouting()) + .UseKestrel(options => { options.AddServerHeader = false; }) + .UseIIS() + .UseIISIntegration() .UseStartup(); + } } } diff --git a/Kyoo/Views/EpisodeApi.cs b/Kyoo/Views/EpisodeApi.cs index 9d240e55..8dd28956 100644 --- a/Kyoo/Views/EpisodeApi.cs +++ b/Kyoo/Views/EpisodeApi.cs @@ -64,14 +64,28 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task> GetSeason(string showSlug, int seasonNumber, int episodeNumber) { - return await _libraryManager.Get(showSlug, seasonNumber); + try + { + return await _libraryManager.Get(showSlug, seasonNumber); + } + catch (ItemNotFound) + { + return NotFound(); + } } [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/season")] [Authorize(Policy = "Read")] public async Task> GetSeason(int showID, int seasonNumber, int episodeNumber) { - return await _libraryManager.Get(showID, seasonNumber); + try + { + return await _libraryManager.Get(showID, seasonNumber); + } + catch (ItemNotFound) + { + return NotFound(); + } } [HttpGet("{episodeID:int}/track")] @@ -120,7 +134,7 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.Get(showID, seasonNumber, episodeNumber) == null) + if (!resources.Any() && await _libraryManager.GetOrDefault(showID, seasonNumber, episodeNumber) == null) return NotFound(); return Page(resources, limit); } @@ -130,10 +144,10 @@ namespace Kyoo.Api } } - [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/track")] - [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/tracks")] + [HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/track")] + [HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/tracks")] [Authorize(Policy = "Read")] - public async Task>> GetEpisode(string showSlug, + public async Task>> GetEpisode(string slug, int seasonNumber, int episodeNumber, [FromQuery] string sortBy, @@ -144,13 +158,13 @@ namespace Kyoo.Api try { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.Episode.Show.Slug == showSlug + ApiHelper.ParseWhere(where, x => x.Episode.Show.Slug == slug && x.Episode.SeasonNumber == seasonNumber && x.Episode.EpisodeNumber == episodeNumber), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.Get(showSlug, seasonNumber, episodeNumber) == null) + if (!resources.Any() && await _libraryManager.GetOrDefault(slug, seasonNumber, episodeNumber) == null) return NotFound(); return Page(resources, limit); } diff --git a/Kyoo/Views/SeasonApi.cs b/Kyoo/Views/SeasonApi.cs index 4ca19c29..9803f956 100644 --- a/Kyoo/Views/SeasonApi.cs +++ b/Kyoo/Views/SeasonApi.cs @@ -75,7 +75,7 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.Get(showSlug, seasonNumber) == null) + if (!resources.Any() && await _libraryManager.GetOrDefault(showSlug, seasonNumber) == null) return NotFound(); return Page(resources, limit); } @@ -102,7 +102,7 @@ namespace Kyoo.Api new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.Get(showID, seasonNumber) == null) + if (!resources.Any() && await _libraryManager.GetOrDefault(showID, seasonNumber) == null) return NotFound(); return Page(resources, limit); } diff --git a/Kyoo/Views/SubtitleApi.cs b/Kyoo/Views/SubtitleApi.cs index f1a38ff3..4ae053de 100644 --- a/Kyoo/Views/SubtitleApi.cs +++ b/Kyoo/Views/SubtitleApi.cs @@ -30,14 +30,14 @@ namespace Kyoo.Api Track subtitle; try { - subtitle = await _libraryManager.Get(slug, StreamType.Subtitle); + subtitle = await _libraryManager.GetOrDefault(slug, StreamType.Subtitle); } catch (ArgumentException ex) { return BadRequest(new {error = ex.Message}); } - if (subtitle == null || subtitle.Type != StreamType.Subtitle) + if (subtitle is not {Type: StreamType.Subtitle}) return NotFound(); if (subtitle.Codec == "subrip" && extension == "vtt") diff --git a/Kyoo/appsettings.json b/Kyoo/settings.json similarity index 56% rename from Kyoo/appsettings.json rename to Kyoo/settings.json index c49df121..bdd2f362 100644 --- a/Kyoo/appsettings.json +++ b/Kyoo/settings.json @@ -1,31 +1,25 @@ { - "server.urls": "http://0.0.0.0:5000", + "server.urls": "http://*:5000", "public_url": "http://localhost:5000/", - "http_port": 5000, - "https_port": 44300, - "Database": { - "Server": "127.0.0.1", - "Port": "5432", - "Database": "kyooDB", - "User Id": "kyoo", - "Password": "kyooPassword", - "Pooling": "true", - "MaxPoolSize": "95", - "Timeout": "30" + "database": { + "server": "127.0.0.1", + "port": "5432", + "database": "kyooDB", + "user ID": "kyoo", + "password": "kyooPassword", + "pooling": "true", + "maxPoolSize": "95", + "timeout": "30" }, - "Logging": { - "LogLevel": { - "Default": "Warning", + "logging": { + "logLevel": { + "default": "Warning", "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.EntityFrameworkCore.DbUpdateException": "None", - "Microsoft.EntityFrameworkCore.Update": "None", - "Microsoft.EntityFrameworkCore.Database.Command": "None" + "Microsoft.Hosting.Lifetime": "Information" } }, - "AllowedHosts": "*", "parallelTasks": "1", diff --git a/deployment/kyoo.service b/deployment/kyoo.service index 0eceef42..277c26ca 100644 --- a/deployment/kyoo.service +++ b/deployment/kyoo.service @@ -5,7 +5,8 @@ After=network.target [Service] User=kyoo -ExecStart=/usr/lib/kyoo/Kyoo /var/lib/kyoo +WorkingDirectory=/var/lib/kyoo +ExecStart=/usr/lib/kyoo/Kyoo Restart=on-abort TimeoutSec=20 diff --git a/settings.json b/settings.json new file mode 100644 index 00000000..bdd2f362 --- /dev/null +++ b/settings.json @@ -0,0 +1,42 @@ +{ + "server.urls": "http://*:5000", + "public_url": "http://localhost:5000/", + + "database": { + "server": "127.0.0.1", + "port": "5432", + "database": "kyooDB", + "user ID": "kyoo", + "password": "kyooPassword", + "pooling": "true", + "maxPoolSize": "95", + "timeout": "30" + }, + + "logging": { + "logLevel": { + "default": "Warning", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + + "parallelTasks": "1", + + "scheduledTasks": { + "scan": "24:00:00" + }, + + "certificatePassword": "passphrase", + + "transmuxTempPath": "cached/kyoo/transmux", + "transcodeTempPath": "cached/kyoo/transcode", + "peoplePath": "people", + "providerPath": "providers", + "profilePicturePath": "users/", + "plugins": "plugins/", + "defaultPermissions": "read,play,write,admin", + "newUserPermissions": "read,play,write,admin", + "regex": "(?:\\/(?.*?))?\\/(?.*?)(?: \\(\\d+\\))?\\/\\k(?: \\(\\d+\\))?(?:(?: S(?\\d+)E(?\\d+))| (?\\d+))?.*$", + "subtitleRegex": "^(?.*)\\.(?\\w{1,3})\\.(?default\\.)?(?forced\\.)?.*$" +} From 22b9f3240f15600b55b6915b6fa974cda57ca567 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 23 Apr 2021 18:15:17 +0200 Subject: [PATCH 10/12] Removing usless IDisposable patterns --- Kyoo.Common/Controllers/ILibraryManager.cs | 2 +- Kyoo.Common/Controllers/IRepository.cs | 3 +- .../Implementations/LibraryManager.cs | 14 ------- Kyoo.CommonAPI/ResourceViewAttribute.cs | 3 +- .../Repositories/EpisodeRepository.cs | 29 +------------- .../Repositories/LibraryItemRepository.cs | 32 --------------- .../Repositories/LibraryRepository.cs | 26 +----------- .../Repositories/PeopleRepository.cs | 31 +------------- .../Repositories/SeasonRepository.cs | 33 +-------------- .../Repositories/ShowRepository.cs | 40 +------------------ Kyoo/Tasks/Crawler.cs | 8 ++-- Kyoo/Tasks/ExtractMetadata.cs | 2 - 12 files changed, 12 insertions(+), 211 deletions(-) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index ae3c321b..d15987de 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -12,7 +12,7 @@ namespace Kyoo.Controllers /// /// An interface to interract with the database. Every repository is mapped through here. /// - public interface ILibraryManager : IDisposable, IAsyncDisposable + public interface ILibraryManager { /// /// Get the repository corresponding to the T item. diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 71a6db8b..2ed0b19a 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -109,7 +109,7 @@ namespace Kyoo.Controllers /// /// A base class for repositories. Every service implementing this will be handled by the . /// - public interface IBaseRepository : IDisposable, IAsyncDisposable + public interface IBaseRepository { /// /// The type for witch this repository is responsible or null if non applicable. @@ -119,7 +119,6 @@ namespace Kyoo.Controllers /// /// A common repository for every resources. - /// It implement's and /. /// /// The resource's type that this repository manage. public interface IRepository : IBaseRepository where T : class, IResource diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index de35db92..2fb16735 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -59,20 +59,6 @@ namespace Kyoo.Controllers GenreRepository = GetRepository() as IGenreRepository; ProviderRepository = GetRepository() as IProviderRepository; } - - /// - public void Dispose() - { - foreach (IBaseRepository repo in _repositories) - repo.Dispose(); - GC.SuppressFinalize(this); - } - - /// - public async ValueTask DisposeAsync() - { - await Task.WhenAll(_repositories.Select(x => x.DisposeAsync().AsTask())); - } /// public IRepository GetRepository() diff --git a/Kyoo.CommonAPI/ResourceViewAttribute.cs b/Kyoo.CommonAPI/ResourceViewAttribute.cs index fa177342..06198663 100644 --- a/Kyoo.CommonAPI/ResourceViewAttribute.cs +++ b/Kyoo.CommonAPI/ResourceViewAttribute.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; namespace Kyoo.CommonApi { @@ -77,7 +76,7 @@ namespace Kyoo.CommonApi if (result.DeclaredType == null) return; - await using ILibraryManager library = context.HttpContext.RequestServices.GetService(); + ILibraryManager library = context.HttpContext.RequestServices.GetService(); ICollection fields = (ICollection)context.HttpContext.Items["fields"]; Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>)); diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 032dff38..1af6c485 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -15,10 +15,6 @@ namespace Kyoo.Controllers /// public class EpisodeRepository : LocalRepository, IEpisodeRepository { - /// - /// Has this instance been disposed and should not handle requests? - /// - private bool _disposed; /// /// The databse handle /// @@ -58,30 +54,7 @@ namespace Kyoo.Controllers _shows = shows; _tracks = tracks; } - - - /// - public override void Dispose() - { - if (_disposed) - return; - _disposed = true; - _database.Dispose(); - _providers.Dispose(); - _shows.Dispose(); - GC.SuppressFinalize(this); - } - - /// - public override async ValueTask DisposeAsync() - { - if (_disposed) - return; - _disposed = true; - await _database.DisposeAsync(); - await _providers.DisposeAsync(); - await _shows.DisposeAsync(); - } + /// public override async Task GetOrDefault(int id) diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 44fd81d3..43afd407 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -15,10 +15,6 @@ namespace Kyoo.Controllers /// public class LibraryItemRepository : LocalRepository, ILibraryItemRepository { - /// - /// Has this instance been disposed and should not handle requests? - /// - private bool _disposed; /// /// The database handle /// @@ -61,35 +57,7 @@ namespace Kyoo.Controllers _shows = new Lazy(services.GetRequiredService); _collections = new Lazy(services.GetRequiredService); } - - /// - public override void Dispose() - { - if (_disposed) - return; - _disposed = true; - _database.Dispose(); - _providers.Dispose(); - if (_shows.IsValueCreated) - _shows.Value.Dispose(); - if (_collections.IsValueCreated) - _collections.Value.Dispose(); - GC.SuppressFinalize(this); - } - /// - public override async ValueTask DisposeAsync() - { - if (_disposed) - return; - _disposed = true; - await _database.DisposeAsync(); - await _providers.DisposeAsync(); - if (_shows.IsValueCreated) - await _shows.Value.DisposeAsync(); - if (_collections.IsValueCreated) - await _collections.Value.DisposeAsync(); - } /// public override async Task GetOrDefault(int id) diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index f61e4445..b4cab3b9 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -13,10 +13,6 @@ namespace Kyoo.Controllers /// public class LibraryRepository : LocalRepository, ILibraryRepository { - /// - /// Has this instance been disposed and should not handle requests? - /// - private bool _disposed; /// /// The database handle /// @@ -41,28 +37,8 @@ namespace Kyoo.Controllers _database = database; _providers = providers; } + - /// - public override void Dispose() - { - if (_disposed) - return; - _disposed = true; - _database.Dispose(); - _providers.Dispose(); - GC.SuppressFinalize(this); - } - - /// - public override async ValueTask DisposeAsync() - { - if (_disposed) - return; - _disposed = true; - await _database.DisposeAsync(); - await _providers.DisposeAsync(); - } - /// public override async Task> Search(string query) { diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 7d1c870e..526a3286 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -15,10 +15,6 @@ namespace Kyoo.Controllers /// public class PeopleRepository : LocalRepository, IPeopleRepository { - /// - /// Has this instance been disposed and should not handle requests? - /// - private bool _disposed; /// /// The database handle /// @@ -50,32 +46,7 @@ namespace Kyoo.Controllers _providers = providers; _shows = new Lazy(services.GetRequiredService); } - - - /// - public override void Dispose() - { - if (_disposed) - return; - _disposed = true; - _database.Dispose(); - _providers.Dispose(); - if (_shows.IsValueCreated) - _shows.Value.Dispose(); - GC.SuppressFinalize(this); - } - - /// - public override async ValueTask DisposeAsync() - { - if (_disposed) - return; - _disposed = true; - await _database.DisposeAsync(); - await _providers.DisposeAsync(); - if (_shows.IsValueCreated) - await _shows.Value.DisposeAsync(); - } + /// public override async Task> Search(string query) diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index a2dce413..e35042ad 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -16,10 +16,6 @@ namespace Kyoo.Controllers /// public class SeasonRepository : LocalRepository, ISeasonRepository { - /// - /// Has this instance been disposed and should not handle requests? - /// - private bool _disposed; /// /// The database handle /// @@ -60,34 +56,7 @@ namespace Kyoo.Controllers _shows = shows; _episodes = new Lazy(services.GetRequiredService); } - - - /// - public override void Dispose() - { - if (_disposed) - return; - _disposed = true; - _database.Dispose(); - _providers.Dispose(); - _shows.Dispose(); - if (_episodes.IsValueCreated) - _episodes.Value.Dispose(); - GC.SuppressFinalize(this); - } - - /// - public override async ValueTask DisposeAsync() - { - if (_disposed) - return; - _disposed = true; - await _database.DisposeAsync(); - await _providers.DisposeAsync(); - await _shows.DisposeAsync(); - if (_episodes.IsValueCreated) - await _episodes.Value.DisposeAsync(); - } + /// public override async Task Get(int id) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index e94419c2..1129cd07 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -14,10 +14,6 @@ namespace Kyoo.Controllers /// public class ShowRepository : LocalRepository, IShowRepository { - /// - /// Has this instance been disposed and should not handle requests? - /// - private bool _disposed; /// /// The databse handle /// @@ -75,41 +71,7 @@ namespace Kyoo.Controllers _seasons = new Lazy(services.GetRequiredService); _episodes = new Lazy(services.GetRequiredService); } - - /// - public override void Dispose() - { - if (_disposed) - return; - _disposed = true; - _database.Dispose(); - _studios.Dispose(); - _people.Dispose(); - _genres.Dispose(); - _providers.Dispose(); - if (_seasons.IsValueCreated) - _seasons.Value.Dispose(); - if (_episodes.IsValueCreated) - _episodes.Value.Dispose(); - GC.SuppressFinalize(this); - } - - /// - public override async ValueTask DisposeAsync() - { - if (_disposed) - return; - _disposed = true; - await _database.DisposeAsync(); - await _studios.DisposeAsync(); - await _people.DisposeAsync(); - await _genres.DisposeAsync(); - await _providers.DisposeAsync(); - if (_seasons.IsValueCreated) - await _seasons.Value.DisposeAsync(); - if (_episodes.IsValueCreated) - await _episodes.Value.DisposeAsync(); - } + /// public override async Task> Search(string query) diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 2634a3bb..47c931c9 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -32,7 +32,7 @@ namespace Kyoo.Controllers public async Task> GetPossibleParameters() { using IServiceScope serviceScope = _serviceProvider.CreateScope(); - await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); + ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); return (await libraryManager!.GetAll()).Select(x => x.Slug); } @@ -56,7 +56,7 @@ namespace Kyoo.Controllers _parallelTasks = 30; using IServiceScope serviceScope = _serviceProvider.CreateScope(); - await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); + ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); foreach (Show show in await libraryManager!.GetAll()) if (!Directory.Exists(show.Path)) @@ -149,7 +149,7 @@ namespace Kyoo.Controllers if (token.IsCancellationRequested || path.Split(Path.DirectorySeparatorChar).Contains("Subtitles")) return; using IServiceScope serviceScope = _serviceProvider.CreateScope(); - await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); + ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); string patern = _config.GetValue("subtitleRegex"); Regex regex = new(patern, RegexOptions.IgnoreCase); @@ -196,7 +196,7 @@ namespace Kyoo.Controllers try { using IServiceScope serviceScope = _serviceProvider.CreateScope(); - await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); + ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService(); string patern = _config.GetValue("regex"); Regex regex = new(patern, RegexOptions.IgnoreCase); diff --git a/Kyoo/Tasks/ExtractMetadata.cs b/Kyoo/Tasks/ExtractMetadata.cs index d9f47e43..d5513c97 100644 --- a/Kyoo/Tasks/ExtractMetadata.cs +++ b/Kyoo/Tasks/ExtractMetadata.cs @@ -64,8 +64,6 @@ namespace Kyoo.Tasks await ExtractEpisode(episode, thumbs, subs); break; } - - await _library!.DisposeAsync(); } private async Task ExtractShow(Show show, bool thumbs, bool subs, CancellationToken token) From 1bcd587401901ca8260e8307191fb374766475b7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 23 Apr 2021 21:12:29 +0200 Subject: [PATCH 11/12] Adding tests coverages on sonar --- .github/workflows/analysis.yml | 15 +++++++--- Kyoo.Tests/Kyoo.Tests.csproj | 4 +++ Kyoo.Tests/Library/RepositoryTests.cs | 17 ----------- Kyoo.Tests/Library/SetupTests.cs | 17 +++++++++++ settings.json | 42 --------------------------- 5 files changed, 32 insertions(+), 63 deletions(-) delete mode 100644 Kyoo.Tests/Library/RepositoryTests.cs create mode 100644 Kyoo.Tests/Library/SetupTests.cs delete mode 100644 settings.json diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 333672da..01ef1a22 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -34,11 +34,18 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} shell: bash run: | + dotnet test \ + '-p:CollectCoverage=true;CoverletOutputFormat=opencover' \ + '-p:SkipTranscoder=true;SkipWebApp=true' || echo "Test failed. Skipping..." + + dotnet build-server shutdown + ./.sonar/scanner/dotnet-sonarscanner begin \ - -k:"AnonymusRaccoon_Kyoo" \ - -o:"anonymus-raccoon" \ - -d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ - -d:sonar.host.url="https://sonarcloud.io" + -k:"AnonymusRaccoon_Kyoo" \ + -o:"anonymus-raccoon" \ + -d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ + -d:sonar.host.url="https://sonarcloud.io" \ + -d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" dotnet build --no-incremental '-p:SkipTranscoder=true;SkipWebApp=true' diff --git a/Kyoo.Tests/Kyoo.Tests.csproj b/Kyoo.Tests/Kyoo.Tests.csproj index 0a5a4355..65e01c1f 100644 --- a/Kyoo.Tests/Kyoo.Tests.csproj +++ b/Kyoo.Tests/Kyoo.Tests.csproj @@ -10,6 +10,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/Kyoo.Tests/Library/RepositoryTests.cs b/Kyoo.Tests/Library/RepositoryTests.cs deleted file mode 100644 index 75ebfd47..00000000 --- a/Kyoo.Tests/Library/RepositoryTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Linq; -using Xunit; - -namespace Kyoo.Tests -{ - public class RepositoryTests - { - [Fact] - public void Get_Test() - { - TestContext context = new(); - using DatabaseContext database = context.New(); - - Assert.Equal(1, database.Shows.Count()); - } - } -} \ No newline at end of file diff --git a/Kyoo.Tests/Library/SetupTests.cs b/Kyoo.Tests/Library/SetupTests.cs new file mode 100644 index 00000000..15b5d2d5 --- /dev/null +++ b/Kyoo.Tests/Library/SetupTests.cs @@ -0,0 +1,17 @@ +using System.Linq; +using Xunit; + +namespace Kyoo.Tests +{ + public class SetupTests + { + // [Fact] + // public void Get_Test() + // { + // TestContext context = new(); + // using DatabaseContext database = context.New(); + // + // Assert.Equal(1, database.Shows.Count()); + // } + } +} \ No newline at end of file diff --git a/settings.json b/settings.json deleted file mode 100644 index bdd2f362..00000000 --- a/settings.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "server.urls": "http://*:5000", - "public_url": "http://localhost:5000/", - - "database": { - "server": "127.0.0.1", - "port": "5432", - "database": "kyooDB", - "user ID": "kyoo", - "password": "kyooPassword", - "pooling": "true", - "maxPoolSize": "95", - "timeout": "30" - }, - - "logging": { - "logLevel": { - "default": "Warning", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - - "parallelTasks": "1", - - "scheduledTasks": { - "scan": "24:00:00" - }, - - "certificatePassword": "passphrase", - - "transmuxTempPath": "cached/kyoo/transmux", - "transcodeTempPath": "cached/kyoo/transcode", - "peoplePath": "people", - "providerPath": "providers", - "profilePicturePath": "users/", - "plugins": "plugins/", - "defaultPermissions": "read,play,write,admin", - "newUserPermissions": "read,play,write,admin", - "regex": "(?:\\/(?.*?))?\\/(?.*?)(?: \\(\\d+\\))?\\/\\k(?: \\(\\d+\\))?(?:(?: S(?\\d+)E(?\\d+))| (?\\d+))?.*$", - "subtitleRegex": "^(?.*)\\.(?\\w{1,3})\\.(?default\\.)?(?forced\\.)?.*$" -} From a4b4622fa5e2376ed661cf57138d3c7eb85ee6dd Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 23 Apr 2021 21:26:04 +0200 Subject: [PATCH 12/12] Cleaning up --- Kyoo.CommonAPI/LocalRepository.cs | 13 ------------- Kyoo.Tests/Library/SetupTests.cs | 3 +++ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 8e78964f..c1e14a6e 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -42,19 +42,6 @@ namespace Kyoo.Controllers /// public Type RepositoryType => typeof(T); - /// - public virtual void Dispose() - { - Database.Dispose(); - GC.SuppressFinalize(this); - } - - /// - public virtual ValueTask DisposeAsync() - { - return Database.DisposeAsync(); - } - /// /// Get a resource from it's ID and make the instance track it. /// diff --git a/Kyoo.Tests/Library/SetupTests.cs b/Kyoo.Tests/Library/SetupTests.cs index 15b5d2d5..e1852a17 100644 --- a/Kyoo.Tests/Library/SetupTests.cs +++ b/Kyoo.Tests/Library/SetupTests.cs @@ -5,6 +5,9 @@ namespace Kyoo.Tests { public class SetupTests { + // TODO test libraries & repositories via a on-memory SQLite database. + // TODO Requires: Kyoo should be database agonistic and database implementations should be available via a plugin. + // [Fact] // public void Get_Test() // {