mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Unifing ItemNotFound behaviors and adding GetOrDefault.
This commit is contained in:
parent
5dd79d59eb
commit
411eaa7aed
@ -143,13 +143,79 @@ namespace Kyoo.Controllers
|
||||
Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a tracck from it's slug and it's type.
|
||||
/// Get a track from it's slug and it's type.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the track</param>
|
||||
/// <param name="type">The type (Video, Audio or Subtitle)</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The tracl found</returns>
|
||||
Task<Track> GetTrack(string slug, StreamType type = StreamType.Unknown);
|
||||
Task<Track> Get(string slug, StreamType type = StreamType.Unknown);
|
||||
|
||||
/// <summary>
|
||||
/// Get the resource by it's ID or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>The resource found</returns>
|
||||
Task<T> GetOrDefault<T>(int id) where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
/// Get the resource by it's slug or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>The resource found</returns>
|
||||
Task<T> GetOrDefault<T>(string slug) where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
/// Get the resource by a filter function or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="where">The filter function.</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>The first resource found that match the where function</returns>
|
||||
Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where) where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
/// Get a season from it's showID and it's seasonNumber or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <returns>The season found</returns>
|
||||
Task<Season> GetOrDefault(int showID, int seasonNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a season from it's show slug and it's seasonNumber or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <returns>The season found</returns>
|
||||
Task<Season> GetOrDefault(string showSlug, int seasonNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a track from it's slug and it's type or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the track</param>
|
||||
/// <param name="type">The type (Video, Audio or Subtitle)</param>
|
||||
/// <returns>The tracl found</returns>
|
||||
Task<Track> GetOrDefault(string slug, StreamType type = StreamType.Unknown);
|
||||
|
||||
|
||||
/// <summary>
|
||||
@ -423,7 +489,15 @@ namespace Kyoo.Controllers
|
||||
/// <param name="item">The item to register</param>
|
||||
/// <typeparam name="T">The type of resource</typeparam>
|
||||
/// <returns>The resource registers and completed by database's informations (related items & so on)</returns>
|
||||
Task<T> Create<T>(T item) where T : class, IResource;
|
||||
Task<T> Create<T>([NotNull] T item) where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new resource if it does not exist already. If it does, the existing value is returned instead.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to register</param>
|
||||
/// <typeparam name="T">The type of resource</typeparam>
|
||||
/// <returns>The newly created item or the existing value if it existed.</returns>
|
||||
Task<T> CreateIfNotExists<T>([NotNull] T item) where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
/// Edit a resource
|
||||
@ -431,6 +505,7 @@ namespace Kyoo.Controllers
|
||||
/// <param name="item">The resourcce to edit, it's ID can't change.</param>
|
||||
/// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param>
|
||||
/// <typeparam name="T">The type of resources</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's informations (related items & so on)</returns>
|
||||
Task<T> Edit<T>(T item, bool resetOld) where T : class, IResource;
|
||||
|
||||
@ -439,6 +514,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="item">The resource to delete</param>
|
||||
/// <typeparam name="T">The type of resource to delete</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task Delete<T>(T item) where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -446,6 +522,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource to delete</param>
|
||||
/// <typeparam name="T">The type of resource to delete</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task Delete<T>(int id) where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -453,6 +530,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource to delete</param>
|
||||
/// <typeparam name="T">The type of resource to delete</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task Delete<T>(string slug) where T : class, IResource;
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +146,25 @@ namespace Kyoo.Controllers
|
||||
/// <returns>The resource found</returns>
|
||||
Task<T> Get(Expression<Func<T, bool>> where);
|
||||
|
||||
/// <summary>
|
||||
/// Get a resource from it's ID or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource</param>
|
||||
/// <returns>The resource found</returns>
|
||||
Task<T> GetOrDefault(int id);
|
||||
/// <summary>
|
||||
/// Get a resource from it's slug or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource</param>
|
||||
/// <returns>The resource found</returns>
|
||||
Task<T> GetOrDefault(string slug);
|
||||
/// <summary>
|
||||
/// Get the first resource that match the predicate or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="where">A predicate to filter the resource.</param>
|
||||
/// <returns>The resource found</returns>
|
||||
Task<T> GetOrDefault(Expression<Func<T, bool>> where);
|
||||
|
||||
/// <summary>
|
||||
/// Search for resources.
|
||||
/// </summary>
|
||||
@ -203,6 +222,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="edited">The resourcce to edit, it's ID can't change.</param>
|
||||
/// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's informations (related items & so on)</returns>
|
||||
Task<T> Edit([NotNull] T edited, bool resetOld);
|
||||
|
||||
@ -210,77 +230,193 @@ namespace Kyoo.Controllers
|
||||
/// Delete a resource by it's ID
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the resource</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task Delete(int id);
|
||||
/// <summary>
|
||||
/// Delete a resource by it's slug
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task Delete(string slug);
|
||||
/// <summary>
|
||||
/// Delete a resource
|
||||
/// </summary>
|
||||
/// <param name="obj">The resource to delete</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task Delete([NotNull] T obj);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="objs">One or multiple resources to delete</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task DeleteRange(params T[] objs) => DeleteRange(objs.AsEnumerable());
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="objs">An enumerable of resources to delete</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task DeleteRange(IEnumerable<T> objs);
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="ids">One or multiple resources's id</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task DeleteRange(params int[] ids) => DeleteRange(ids.AsEnumerable());
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="ids">An enumearble of resources's id</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task DeleteRange(IEnumerable<int> ids);
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="slugs">One or multiple resources's slug</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task DeleteRange(params string[] slugs) => DeleteRange(slugs.AsEnumerable());
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="slugs">An enumerable of resources's slug</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task DeleteRange(IEnumerable<string> slugs);
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="where">A predicate to filter resources to delete. Every resource that match this will be deleted.</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
Task DeleteRange([NotNull] Expression<Func<T, bool>> where);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle shows.
|
||||
/// </summary>
|
||||
public interface IShowRepository : IRepository<Show>
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="showID">The ID of the show</param>
|
||||
/// <param name="libraryID">The ID of the library (optional)</param>
|
||||
/// <param name="collectionID">The ID of the collection (optional)</param>
|
||||
Task AddShowLink(int showID, int? libraryID, int? collectionID);
|
||||
|
||||
/// <summary>
|
||||
/// Get a show's slug from it's ID.
|
||||
/// </summary>
|
||||
/// <param name="showID">The ID of the show</param>
|
||||
/// <exception cref="ItemNotFound">If a show with the given ID is not found.</exception>
|
||||
/// <returns>The show's slug</returns>
|
||||
Task<string> GetSlug(int showID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle seasons.
|
||||
/// </summary>
|
||||
public interface ISeasonRepository : IRepository<Season>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a season from it's showID and it's seasonNumber
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The season found</returns>
|
||||
Task<Season> Get(int showID, int seasonNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a season from it's show slug and it's seasonNumber
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The season found</returns>
|
||||
Task<Season> Get(string showSlug, int seasonNumber);
|
||||
Task Delete(string showSlug, int seasonNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a season from it's showID and it's seasonNumber or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <returns>The season found</returns>
|
||||
Task<Season> GetOrDefault(int showID, int seasonNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a season from it's show slug and it's seasonNumber or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <returns>The season found</returns>
|
||||
Task<Season> GetOrDefault(string showSlug, int seasonNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The repository to handle episodes
|
||||
/// </summary>
|
||||
public interface IEpisodeRepository : IRepository<Episode>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a episode from it's showID, it's seasonNumber and it's episode number.
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> Get(int showID, int seasonNumber, int episodeNumber);
|
||||
/// <summary>
|
||||
/// Get a episode from it's show slug, it's seasonNumber and it's episode number.
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber);
|
||||
/// <summary>
|
||||
/// Get a episode from it's season ID and it's episode number.
|
||||
/// </summary>
|
||||
/// <param name="seasonID">The ID of the season</param>
|
||||
/// <param name="episodeNumber">The episode number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> Get(int seasonID, int episodeNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber);
|
||||
/// <summary>
|
||||
/// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Get a episode from it's showID and it's absolute number.
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="absoluteNumber">The episode's absolute number (The episode number does not reset to 1 after the end of a season.</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> GetAbsolute(int showID, int absoluteNumber);
|
||||
/// <summary>
|
||||
/// Get a episode from it's showID and it's absolute number.
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="absoluteNumber">The episode's absolute number (The episode number does not reset to 1 after the end of a season.</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> GetAbsolute(string showSlug, int absoluteNumber);
|
||||
Task Delete(string showSlug, int seasonNumber, int episodeNumber);
|
||||
}
|
||||
|
||||
public interface ITrackRepository : IRepository<Track>
|
||||
|
@ -15,27 +15,6 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
private readonly IBaseRepository[] _repositories;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="LibraryManager"/> instancce with every repository available.
|
||||
/// </summary>
|
||||
/// <param name="repositories">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.</param>
|
||||
public LibraryManager(IEnumerable<IBaseRepository> repositories)
|
||||
{
|
||||
_repositories = repositories.ToArray();
|
||||
LibraryRepository = GetRepository<Library>() as ILibraryRepository;
|
||||
LibraryItemRepository = GetRepository<LibraryItem>() as ILibraryItemRepository;
|
||||
CollectionRepository = GetRepository<Collection>() as ICollectionRepository;
|
||||
ShowRepository = GetRepository<Show>() as IShowRepository;
|
||||
SeasonRepository = GetRepository<Season>() as ISeasonRepository;
|
||||
EpisodeRepository = GetRepository<Episode>() as IEpisodeRepository;
|
||||
TrackRepository = GetRepository<Track>() as ITrackRepository;
|
||||
PeopleRepository = GetRepository<People>() as IPeopleRepository;
|
||||
StudioRepository = GetRepository<Studio>() as IStudioRepository;
|
||||
GenreRepository = GetRepository<Genre>() as IGenreRepository;
|
||||
ProviderRepository = GetRepository<Provider>() as IProviderRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle libraries.
|
||||
/// </summary>
|
||||
@ -92,6 +71,59 @@ namespace Kyoo.Controllers
|
||||
public IProviderRepository ProviderRepository { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="LibraryManager"/> instancce with every repository available.
|
||||
/// </summary>
|
||||
/// <param name="repositories">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.</param>
|
||||
public LibraryManager(IEnumerable<IBaseRepository> repositories)
|
||||
{
|
||||
_repositories = repositories.ToArray();
|
||||
LibraryRepository = GetRepository<Library>() as ILibraryRepository;
|
||||
LibraryItemRepository = GetRepository<LibraryItem>() as ILibraryItemRepository;
|
||||
CollectionRepository = GetRepository<Collection>() as ICollectionRepository;
|
||||
ShowRepository = GetRepository<Show>() as IShowRepository;
|
||||
SeasonRepository = GetRepository<Season>() as ISeasonRepository;
|
||||
EpisodeRepository = GetRepository<Episode>() as IEpisodeRepository;
|
||||
TrackRepository = GetRepository<Track>() as ITrackRepository;
|
||||
PeopleRepository = GetRepository<People>() as IPeopleRepository;
|
||||
StudioRepository = GetRepository<Studio>() as IStudioRepository;
|
||||
GenreRepository = GetRepository<Genre>() as IGenreRepository;
|
||||
ProviderRepository = GetRepository<Provider>() as IProviderRepository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (IBaseRepository repo in _repositories)
|
||||
repo.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous dispose operation.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await Task.WhenAll(_repositories.Select(x => x.DisposeAsync().AsTask()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the repository corresponding to the T item.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type you want</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The repository corresponding</returns>
|
||||
public IRepository<T> GetRepository<T>()
|
||||
where T : class, IResource
|
||||
{
|
||||
if (_repositories.FirstOrDefault(x => x.RepositoryType == typeof(T)) is IRepository<T> ret)
|
||||
return ret;
|
||||
throw new ItemNotFound();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the resource by it's ID
|
||||
/// </summary>
|
||||
@ -188,42 +220,102 @@ namespace Kyoo.Controllers
|
||||
/// <param name="type">The type (Video, Audio or Subtitle)</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The tracl found</returns>
|
||||
public Task<Track> GetTrack(string slug, StreamType type = StreamType.Unknown)
|
||||
public Task<Track> Get(string slug, StreamType type = StreamType.Unknown)
|
||||
{
|
||||
return TrackRepository.Get(slug, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (IBaseRepository repo in _repositories)
|
||||
repo.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous dispose operation.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await Task.WhenAll(_repositories.Select(x => x.DisposeAsync().AsTask()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the repository corresponding to the T item.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type you want</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The repository corresponding</returns>
|
||||
public IRepository<T> GetRepository<T>()
|
||||
/// <param name="id">The id of the resource</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>The resource found</returns>
|
||||
public async Task<T> GetOrDefault<T>(int id)
|
||||
where T : class, IResource
|
||||
{
|
||||
if (_repositories.FirstOrDefault(x => x.RepositoryType == typeof(T)) is IRepository<T> ret)
|
||||
return ret;
|
||||
throw new ItemNotFound();
|
||||
return await GetRepository<T>().GetOrDefault(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the resource by it's slug or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>The resource found</returns>
|
||||
public async Task<T> GetOrDefault<T>(string slug)
|
||||
where T : class, IResource
|
||||
{
|
||||
return await GetRepository<T>().GetOrDefault(slug);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the resource by a filter function or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="where">The filter function.</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>The first resource found that match the where function</returns>
|
||||
public async Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where)
|
||||
where T : class, IResource
|
||||
{
|
||||
return await GetRepository<T>().GetOrDefault(where);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a season from it's showID and it's seasonNumber or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <returns>The season found</returns>
|
||||
public async Task<Season> GetOrDefault(int showID, int seasonNumber)
|
||||
{
|
||||
return await SeasonRepository.GetOrDefault(showID, seasonNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a season from it's show slug and it's seasonNumber or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <returns>The season found</returns>
|
||||
public async Task<Season> GetOrDefault(string showSlug, int seasonNumber)
|
||||
{
|
||||
return await SeasonRepository.GetOrDefault(showSlug, seasonNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <returns>The episode found</returns>
|
||||
public async Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
return await EpisodeRepository.GetOrDefault(showID, seasonNumber, episodeNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <returns>The episode found</returns>
|
||||
public async Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a track from it's slug and it's type or null if it is not found.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the track</param>
|
||||
/// <param name="type">The type (Video, Audio or Subtitle)</param>
|
||||
/// <returns>The tracl found</returns>
|
||||
public async Task<Track> GetOrDefault(string slug, StreamType type = StreamType.Unknown)
|
||||
{
|
||||
return await TrackRepository.GetOrDefault(slug, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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;
|
||||
@ -624,12 +716,25 @@ namespace Kyoo.Controllers
|
||||
return GetRepository<T>().Create(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new resource if it does not exist already. If it does, the existing value is returned instead.
|
||||
/// </summary>
|
||||
/// <param name="item">The object to create</param>
|
||||
/// <typeparam name="T">The type of resource</typeparam>
|
||||
/// <returns>The newly created item or the existing value if it existed.</returns>
|
||||
public Task<T> CreateIfNotExists<T>(T item)
|
||||
where T : class, IResource
|
||||
{
|
||||
return GetRepository<T>().CreateIfNotExists(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit a resource
|
||||
/// </summary>
|
||||
/// <param name="item">The resourcce to edit, it's ID can't change.</param>
|
||||
/// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param>
|
||||
/// <typeparam name="T">The type of resources</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's informations (related items & so on)</returns>
|
||||
public Task<T> Edit<T>(T item, bool resetOld)
|
||||
where T : class, IResource
|
||||
@ -642,6 +747,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="item">The resource to delete</param>
|
||||
/// <typeparam name="T">The type of resource to delete</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
public Task Delete<T>(T item)
|
||||
where T : class, IResource
|
||||
{
|
||||
@ -653,6 +759,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource to delete</param>
|
||||
/// <typeparam name="T">The type of resource to delete</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
public Task Delete<T>(int id)
|
||||
where T : class, IResource
|
||||
{
|
||||
@ -664,6 +771,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource to delete</param>
|
||||
/// <typeparam name="T">The type of resource to delete</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
public Task Delete<T>(string slug)
|
||||
where T : class, IResource
|
||||
{
|
||||
|
@ -29,22 +29,28 @@ namespace Kyoo.CommonApi
|
||||
[Authorize(Policy = "Read")]
|
||||
public virtual async Task<ActionResult<T>> 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<ActionResult<T>> 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")]
|
||||
@ -113,17 +119,21 @@ namespace Kyoo.CommonApi
|
||||
[HttpPut]
|
||||
[Authorize(Policy = "Write")]
|
||||
public virtual async Task<ActionResult<T>> Edit([FromQuery] bool resetOld, [FromBody] T resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (resource.ID > 0)
|
||||
return await _repository.Edit(resource, resetOld);
|
||||
|
||||
T old = await _repository.Get(resource.Slug);
|
||||
if (old == null)
|
||||
return NotFound();
|
||||
|
||||
resource.ID = old.ID;
|
||||
return await _repository.Edit(resource, resetOld);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[Authorize(Policy = "Write")]
|
||||
@ -143,13 +153,18 @@ namespace Kyoo.CommonApi
|
||||
[HttpPut("{slug}")]
|
||||
[Authorize(Policy = "Write")]
|
||||
public virtual async Task<ActionResult<T>> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
T old = await _repository.Get(slug);
|
||||
if (old == null)
|
||||
return NotFound();
|
||||
resource.ID = old.ID;
|
||||
return await _repository.Edit(resource, resetOld);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "Write")]
|
||||
|
@ -12,54 +12,112 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A base class to create repositories using Entity Framework.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of this repository</typeparam>
|
||||
public abstract class LocalRepository<T> : IRepository<T>
|
||||
where T : class, IResource
|
||||
{
|
||||
/// <summary>
|
||||
/// The Entity Framework's Database handle.
|
||||
/// </summary>
|
||||
protected readonly DbContext Database;
|
||||
|
||||
/// <summary>
|
||||
/// The default sort order that will be used for this resource's type.
|
||||
/// </summary>
|
||||
protected abstract Expression<Func<T, object>> DefaultSort { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new base <see cref="LocalRepository{T}"/> with the given database handle.
|
||||
/// </summary>
|
||||
/// <param name="database">A database connection to load resources of type <see cref="T"/></param>
|
||||
protected LocalRepository(DbContext database)
|
||||
{
|
||||
Database = database;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Type RepositoryType => typeof(T);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Database.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual ValueTask DisposeAsync()
|
||||
{
|
||||
return Database.DisposeAsync();
|
||||
}
|
||||
|
||||
public virtual Task<T> Get(int id)
|
||||
/// <summary>
|
||||
/// Get a resource from it's ID and make the <see cref="Database"/> instance track it.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the resource</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The tracked resource with the given ID</returns>
|
||||
protected virtual async Task<T> GetWithTracking(int id)
|
||||
{
|
||||
T ret = await Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.ID == id);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No {typeof(T).Name} found with the id {id}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> Get(Expression<Func<T, bool>> where)
|
||||
{
|
||||
T ret = await GetOrDefault(where);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No {typeof(T).Name} found with the given predicate.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T> GetOrDefault(int id)
|
||||
{
|
||||
return Database.Set<T>().FirstOrDefaultAsync(x => x.ID == id);
|
||||
}
|
||||
|
||||
public virtual Task<T> GetWithTracking(int id)
|
||||
{
|
||||
return Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.ID == id);
|
||||
}
|
||||
|
||||
public virtual Task<T> Get(string slug)
|
||||
/// <inheritdoc />
|
||||
public Task<T> GetOrDefault(string slug)
|
||||
{
|
||||
return Database.Set<T>().FirstOrDefaultAsync(x => x.Slug == slug);
|
||||
}
|
||||
|
||||
public virtual Task<T> Get(Expression<Func<T, bool>> predicate)
|
||||
/// <inheritdoc />
|
||||
public Task<T> GetOrDefault(Expression<Func<T, bool>> where)
|
||||
{
|
||||
return Database.Set<T>().FirstOrDefaultAsync(predicate);
|
||||
return Database.Set<T>().FirstOrDefaultAsync(where);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract Task<ICollection<T>> Search(string query);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
|
||||
Sort<T> sort = default,
|
||||
Pagination limit = default)
|
||||
@ -67,6 +125,14 @@ namespace Kyoo.Controllers
|
||||
return ApplyFilters(Database.Set<T>(), where, sort, limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply filters to a query to ease sort, pagination & where queries for resources of this repository
|
||||
/// </summary>
|
||||
/// <param name="query">The base query to filter.</param>
|
||||
/// <param name="where">An expression to filter based on arbitrary conditions</param>
|
||||
/// <param name="sort">The sort settings (sort order & sort by)</param>
|
||||
/// <param name="limit">Paginations information (where to start and how many to get)</param>
|
||||
/// <returns>The filtered query</returns>
|
||||
protected Task<ICollection<T>> ApplyFilters(IQueryable<T> query,
|
||||
Expression<Func<T, bool>> where = null,
|
||||
Sort<T> sort = default,
|
||||
@ -75,6 +141,17 @@ namespace Kyoo.Controllers
|
||||
return ApplyFilters(query, Get, DefaultSort, where, sort, limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply filters to a query to ease sort, pagination & where queries for any resources types.
|
||||
/// For resources of type <see cref="T"/>, see <see cref="ApplyFilters"/>
|
||||
/// </summary>
|
||||
/// <param name="get">A function to asynchronously get a resource from the database using it's ID.</param>
|
||||
/// <param name="defaultSort">The default sort order of this resource's type.</param>
|
||||
/// <param name="query">The base query to filter.</param>
|
||||
/// <param name="where">An expression to filter based on arbitrary conditions</param>
|
||||
/// <param name="sort">The sort settings (sort order & sort by)</param>
|
||||
/// <param name="limit">Paginations information (where to start and how many to get)</param>
|
||||
/// <returns>The filtered query</returns>
|
||||
protected async Task<ICollection<TValue>> ApplyFilters<TValue>(IQueryable<TValue> query,
|
||||
Func<int, Task<TValue>> get,
|
||||
Expression<Func<TValue, object>> defaultSort,
|
||||
@ -110,6 +187,7 @@ namespace Kyoo.Controllers
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual Task<int> GetCount(Expression<Func<T, bool>> where = null)
|
||||
{
|
||||
IQueryable<T> query = Database.Set<T>();
|
||||
@ -118,6 +196,7 @@ namespace Kyoo.Controllers
|
||||
return query.CountAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> Create(T obj)
|
||||
{
|
||||
if (obj == null)
|
||||
@ -126,6 +205,7 @@ namespace Kyoo.Controllers
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> 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
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> Edit(T edited, bool resetOld)
|
||||
{
|
||||
if (edited == null)
|
||||
@ -164,8 +242,6 @@ 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);
|
||||
@ -180,11 +256,24 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An overridable method to edit relatiosn of a resource.
|
||||
/// </summary>
|
||||
/// <param name="resource">The non edited resource</param>
|
||||
/// <param name="changed">The new version of <see cref="resource"/>. This item will be saved on the databse and replace <see cref="resource"/></param>
|
||||
/// <param name="resetOld">A boolean to indicate if all values of resource should be discarded or not.</param>
|
||||
/// <returns></returns>
|
||||
protected virtual Task EditRelations(T resource, T changed, bool resetOld)
|
||||
{
|
||||
return Validate(resource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A method called just before saving a new resource to the database.
|
||||
/// It is also called on the default implementation of <see cref="EditRelations"/>
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource that will be saved</param>
|
||||
/// <exception cref="ArgumentException">You can throw this if the resource is illegal and should not be saved.</exception>
|
||||
protected virtual Task Validate(T resource)
|
||||
{
|
||||
if (string.IsNullOrEmpty(resource.Slug))
|
||||
@ -207,38 +296,45 @@ namespace Kyoo.Controllers
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task Delete(int id)
|
||||
{
|
||||
T resource = await Get(id);
|
||||
await Delete(resource);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task Delete(string slug)
|
||||
{
|
||||
T resource = await Get(slug);
|
||||
await Delete(resource);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract Task Delete(T obj);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task DeleteRange(IEnumerable<T> objs)
|
||||
{
|
||||
foreach (T obj in objs)
|
||||
await Delete(obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task DeleteRange(IEnumerable<int> ids)
|
||||
{
|
||||
foreach (int id in ids)
|
||||
await Delete(id);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task DeleteRange(IEnumerable<string> slugs)
|
||||
{
|
||||
foreach (string slug in slugs)
|
||||
await Delete(slug);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task DeleteRange(Expression<Func<T, bool>> where)
|
||||
{
|
||||
ICollection<T> resources = await GetAll(where);
|
||||
|
17
Kyoo.Tests/Library/RepositoryTests.cs
Normal file
17
Kyoo.Tests/Library/RepositoryTests.cs
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
79
Kyoo.Tests/Library/TestContext.cs
Normal file
79
Kyoo.Tests/Library/TestContext.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using Kyoo.Models;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Class responsible to fill and create in memory databases for unit tests.
|
||||
/// </summary>
|
||||
public class TestContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The context's options that specify to use an in memory Sqlite database.
|
||||
/// </summary>
|
||||
private readonly DbContextOptions<DatabaseContext> _context;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new database and fill it with informations.
|
||||
/// </summary>
|
||||
public TestContext()
|
||||
{
|
||||
SqliteConnection connection = new("DataSource=:memory:");
|
||||
connection.Open();
|
||||
|
||||
try
|
||||
{
|
||||
_context = new DbContextOptionsBuilder<DatabaseContext>()
|
||||
.UseSqlite(connection)
|
||||
.Options;
|
||||
FillDatabase();
|
||||
}
|
||||
finally
|
||||
{
|
||||
connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill the database with pre defined values using a clean context.
|
||||
/// </summary>
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a new databse context connected to a in memory Sqlite databse.
|
||||
/// </summary>
|
||||
/// <returns>A valid DatabaseContext</returns>
|
||||
public DatabaseContext New()
|
||||
{
|
||||
return new(_context);
|
||||
}
|
||||
}
|
||||
}
|
@ -211,12 +211,6 @@ namespace Kyoo.Controllers
|
||||
}).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)
|
||||
|
@ -5,21 +5,38 @@ 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
|
||||
{
|
||||
/// <summary>
|
||||
/// A local repository to handle seasons.
|
||||
/// </summary>
|
||||
public class SeasonRepository : LocalRepository<Season>, ISeasonRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Has this instance been disposed and should not handle requests?
|
||||
/// </summary>
|
||||
private bool _disposed;
|
||||
private readonly DatabaseContext _database;
|
||||
private readonly IProviderRepository _providers;
|
||||
private readonly IShowRepository _shows;
|
||||
private readonly Lazy<IEpisodeRepository> _episodes;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Expression<Func<Season, object>> DefaultSort => x => x.SeasonNumber;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SeasonRepository"/> using the provided handle, a provider & a show repository and
|
||||
/// a service provider to lazilly request an episode repository.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle that will be used</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="shows">A show repository</param>
|
||||
/// <param name="services">A service provider to lazilly request an episode repository.</param>
|
||||
public SeasonRepository(DatabaseContext database,
|
||||
IProviderRepository providers,
|
||||
IShowRepository shows,
|
||||
@ -33,6 +50,7 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
@ -46,6 +64,7 @@ namespace Kyoo.Controllers
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_disposed)
|
||||
@ -58,22 +77,23 @@ namespace Kyoo.Controllers
|
||||
await _episodes.Value.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<Season> Get(int id)
|
||||
{
|
||||
Season ret = await base.Get(id);
|
||||
if (ret != null)
|
||||
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<Season> Get(Expression<Func<Season, bool>> predicate)
|
||||
{
|
||||
Season ret = await base.Get(predicate);
|
||||
if (ret != null)
|
||||
ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Task<Season> Get(string slug)
|
||||
{
|
||||
Match match = Regex.Match(slug, @"(?<show>.*)-s(?<season>\d*)");
|
||||
@ -83,24 +103,41 @@ namespace Kyoo.Controllers
|
||||
return Get(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Season> Get(int showID, int seasonNumber)
|
||||
{
|
||||
Season ret = await _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID
|
||||
&& x.SeasonNumber == seasonNumber);
|
||||
if (ret != null)
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Season> Get(string showSlug, int seasonNumber)
|
||||
{
|
||||
Season ret = await _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
|
||||
&& x.SeasonNumber == seasonNumber);
|
||||
if (ret != null)
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<Season> GetOrDefault(int showID, int seasonNumber)
|
||||
{
|
||||
return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID
|
||||
&& x.SeasonNumber == seasonNumber);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<Season> GetOrDefault(string showSlug, int seasonNumber)
|
||||
{
|
||||
return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
|
||||
&& x.SeasonNumber == seasonNumber);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<ICollection<Season>> Search(string query)
|
||||
{
|
||||
List<Season> seasons = await _database.Seasons
|
||||
@ -113,6 +150,7 @@ namespace Kyoo.Controllers
|
||||
return seasons;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<ICollection<Season>> GetAll(Expression<Func<Season, bool>> where = null,
|
||||
Sort<Season> sort = default,
|
||||
Pagination limit = default)
|
||||
@ -123,6 +161,7 @@ namespace Kyoo.Controllers
|
||||
return seasons;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<Season> Create(Season obj)
|
||||
{
|
||||
await base.Create(obj);
|
||||
@ -132,6 +171,7 @@ namespace Kyoo.Controllers
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task Validate(Season resource)
|
||||
{
|
||||
if (resource.ShowID <= 0)
|
||||
@ -146,6 +186,7 @@ namespace Kyoo.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task EditRelations(Season resource, Season changed, bool resetOld)
|
||||
{
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
@ -156,12 +197,7 @@ 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task Delete(Season obj)
|
||||
{
|
||||
if (obj == null)
|
||||
|
@ -58,13 +58,13 @@ namespace Kyoo
|
||||
modelBuilder.HasPostgresEnum<ItemType>();
|
||||
modelBuilder.HasPostgresEnum<StreamType>();
|
||||
|
||||
modelBuilder.Entity<Library>()
|
||||
.Property(x => x.Paths)
|
||||
.HasColumnType("text[]");
|
||||
|
||||
modelBuilder.Entity<Show>()
|
||||
.Property(x => x.Aliases)
|
||||
.HasColumnType("text[]");
|
||||
// modelBuilder.Entity<Library>()
|
||||
// .Property(x => x.Paths)
|
||||
// .HasColumnType("text[]");
|
||||
//
|
||||
// modelBuilder.Entity<Show>()
|
||||
// .Property(x => x.Aliases)
|
||||
// .HasColumnType("text[]");
|
||||
|
||||
modelBuilder.Entity<Track>()
|
||||
.Property(t => t.IsDefault)
|
||||
|
@ -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)
|
@ -143,6 +143,8 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
|
||||
private async Task RegisterExternalSubtitle(string path, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (token.IsCancellationRequested || path.Split(Path.DirectorySeparatorChar).Contains("Subtitles"))
|
||||
return;
|
||||
@ -161,28 +163,30 @@ namespace Kyoo.Controllers
|
||||
|
||||
string episodePath = match.Groups["Episode"].Value;
|
||||
Episode episode = await libraryManager!.Get<Episode>(x => x.Path.StartsWith(episodePath));
|
||||
|
||||
if (episode == null)
|
||||
{
|
||||
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)
|
||||
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}.");
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
await Console.Error.WriteLineAsync($"No episode found for subtitle at: ${path}.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Console.Error.WriteLineAsync($"Unknown error while registering subtitle: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RegisterFile(string path, string relativePath, Library library, CancellationToken token)
|
||||
{
|
||||
@ -308,23 +312,21 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
if (seasonNumber == -1)
|
||||
return default;
|
||||
Season season = await libraryManager.Get(show.Slug, seasonNumber);
|
||||
if (season == null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Episode> GetEpisode(ILibraryManager libraryManager,
|
||||
Show show,
|
||||
|
@ -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;
|
||||
|
||||
@ -162,21 +163,31 @@ namespace Kyoo.Api
|
||||
[HttpGet("{id:int}/thumb")]
|
||||
[Authorize(Policy="Read")]
|
||||
public async Task<IActionResult> GetThumb(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
Episode episode = await _libraryManager.Get<Episode>(id);
|
||||
if (episode == null)
|
||||
return NotFound();
|
||||
return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode));
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{slug}/thumb")]
|
||||
[Authorize(Policy="Read")]
|
||||
public async Task<IActionResult> GetThumb(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
Episode episode = await _libraryManager.Get<Episode>(slug);
|
||||
if (episode == null)
|
||||
return NotFound();
|
||||
return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode));
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -375,56 +375,81 @@ namespace Kyoo.Api
|
||||
[HttpGet("{slug}/fonts")]
|
||||
[Authorize(Policy = "Read")]
|
||||
public async Task<ActionResult<Dictionary<string, string>>> GetFonts(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
Show show = await _libraryManager.Get<Show>(slug);
|
||||
if (show == null)
|
||||
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)}");
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{showSlug}/font/{slug}")]
|
||||
[HttpGet("{showSlug}/fonts/{slug}")]
|
||||
[Authorize(Policy = "Read")]
|
||||
public async Task<IActionResult> GetFont(string showSlug, string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
Show show = await _libraryManager.Get<Show>(showSlug);
|
||||
if (show == null)
|
||||
return NotFound();
|
||||
string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments", slug);
|
||||
return _files.FileResult(path);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{slug}/poster")]
|
||||
[Authorize(Policy = "Read")]
|
||||
public async Task<IActionResult> GetPoster(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
Show show = await _libraryManager.Get<Show>(slug);
|
||||
if (show == null)
|
||||
return NotFound();
|
||||
return _files.FileResult(await _thumbs.GetShowPoster(show));
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{slug}/logo")]
|
||||
[Authorize(Policy="Read")]
|
||||
public async Task<IActionResult> GetLogo(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
Show show = await _libraryManager.Get<Show>(slug);
|
||||
if (show == null)
|
||||
return NotFound();
|
||||
return _files.FileResult(await _thumbs.GetShowLogo(show));
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{slug}/backdrop")]
|
||||
[Authorize(Policy="Read")]
|
||||
public async Task<IActionResult> GetBackdrop(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
Show show = await _libraryManager.Get<Show>(slug);
|
||||
if (show == null)
|
||||
return NotFound();
|
||||
return _files.FileResult(await _thumbs.GetShowBackdrop(show));
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
@ -20,11 +21,16 @@ namespace Kyoo.Api
|
||||
[HttpGet("{slug}")]
|
||||
[Authorize(Policy="Read")]
|
||||
public async Task<ActionResult<WatchItem>> GetWatchItem(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
Episode item = await _libraryManager.Get<Episode>(slug);
|
||||
if (item == null)
|
||||
return NotFound();
|
||||
return await WatchItem.FromEpisode(item, _libraryManager);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user