Unifing ItemNotFound behaviors and adding GetOrDefault.

This commit is contained in:
Zoe Roux 2021-04-21 01:49:21 +02:00
parent 5dd79d59eb
commit 411eaa7aed
17 changed files with 810 additions and 207 deletions

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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
{

View File

@ -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")]
@ -114,15 +120,19 @@ namespace Kyoo.CommonApi
[Authorize(Policy = "Write")]
public virtual async Task<ActionResult<T>> 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<ActionResult<T>> 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}")]

View File

@ -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);

View 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());
}
}
}

View 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);
}
}
}

View File

@ -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)

View File

@ -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);
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);
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)
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;
}
/// <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)
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;
}
/// <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)

View File

@ -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)

View File

@ -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)

View File

@ -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<ILibraryManager>();
string patern = _config.GetValue<string>("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<ILibraryManager>();
string patern = _config.GetValue<string>("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<Episode>(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<Episode>(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<Episode> GetEpisode(ILibraryManager libraryManager,

View File

@ -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<IActionResult> GetThumb(int id)
{
Episode episode = await _libraryManager.Get<Episode>(id);
if (episode == null)
try
{
Episode episode = await _libraryManager.Get<Episode>(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<IActionResult> GetThumb(string slug)
{
Episode episode = await _libraryManager.Get<Episode>(slug);
if (episode == null)
try
{
Episode episode = await _libraryManager.Get<Episode>(slug);
return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode));
}
catch (ItemNotFound)
{
return NotFound();
return _files.FileResult(await _thumbnails.GetEpisodeThumb(episode));
}
}
}
}

View File

@ -376,13 +376,18 @@ namespace Kyoo.Api
[Authorize(Policy = "Read")]
public async Task<ActionResult<Dictionary<string, string>>> GetFonts(string slug)
{
Show show = await _libraryManager.Get<Show>(slug);
if (show == null)
try
{
Show show = await _libraryManager.Get<Show>(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<IActionResult> GetFont(string showSlug, string slug)
{
Show show = await _libraryManager.Get<Show>(showSlug);
if (show == null)
try
{
Show show = await _libraryManager.Get<Show>(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<IActionResult> GetPoster(string slug)
{
Show show = await _libraryManager.Get<Show>(slug);
if (show == null)
try
{
Show show = await _libraryManager.Get<Show>(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<IActionResult> GetLogo(string slug)
{
Show show = await _libraryManager.Get<Show>(slug);
if (show == null)
try
{
Show show = await _libraryManager.Get<Show>(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<IActionResult> GetBackdrop(string slug)
{
Show show = await _libraryManager.Get<Show>(slug);
if (show == null)
try
{
Show show = await _libraryManager.Get<Show>(slug);
return _files.FileResult(await _thumbs.GetShowBackdrop(show));
}
catch (ItemNotFound)
{
return NotFound();
return _files.FileResult(await _thumbs.GetShowBackdrop(show));
}
}
}
}

View File

@ -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)
{

View File

@ -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<ActionResult<WatchItem>> GetWatchItem(string slug)
{
Episode item = await _libraryManager.Get<Episode>(slug);
if (item == null)
try
{
Episode item = await _libraryManager.Get<Episode>(slug);
return await WatchItem.FromEpisode(item, _libraryManager);
}
catch (ItemNotFound)
{
return NotFound();
return await WatchItem.FromEpisode(item, _libraryManager);
}
}
}
}