mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Feat rework images, delete providers (#191)
This commit is contained in:
commit
105aa7874f
@ -33,10 +33,11 @@
|
||||
<Rule Id="SA1513" Action="None"/> <!-- ClosingBraceMustBeFollowedByBlankLine -->
|
||||
</Rules>
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.DocumentationRules">
|
||||
<Rule Id="SA1600" Action="None" /> <!-- Elements Shuld be Documented -->
|
||||
<Rule Id="SA1602" Action="None" /> <!-- Enums should be documented -->
|
||||
<Rule Id="SA1642" Action="None" /> <!-- ConstructorSummaryDocumentationMustBeginWithStandardText -->
|
||||
<Rule Id="SA1643" Action="None" /> <!-- DestructorSummaryDocumentationMustBeginWithStandardText -->
|
||||
<Rule Id="SA1623" Action="None" /> <!-- PropertySummaryDocumentationMustMatchAccessors -->
|
||||
<Rule Id="SA1629" Action="None" /> <!-- DocumentationTextMustEndWithAPeriod -->
|
||||
<Rule Id="SA1600" Action="None" /> <!-- Elements Shuld be Documented -->
|
||||
</Rules>
|
||||
</RuleSet>
|
||||
|
@ -46,7 +46,7 @@
|
||||
|
||||
<PropertyGroup Condition="$(CheckCodingStyle) == true">
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)../Kyoo.ruleset</CodeAnalysisRuleSet>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<NoWarn>1591;1305;8618</NoWarn>
|
||||
<!-- <AnalysisMode>All</AnalysisMode> -->
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
@ -20,7 +20,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
|
||||
@ -40,11 +39,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
IRepository<T> GetRepository<T>()
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle libraries.
|
||||
/// </summary>
|
||||
ILibraryRepository LibraryRepository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle libraries items (a wrapper around shows and collections).
|
||||
/// </summary>
|
||||
@ -55,6 +49,11 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// </summary>
|
||||
ICollectionRepository CollectionRepository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle shows.
|
||||
/// </summary>
|
||||
IMovieRepository MovieRepository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle shows.
|
||||
/// </summary>
|
||||
@ -80,16 +79,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// </summary>
|
||||
IStudioRepository StudioRepository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle genres.
|
||||
/// </summary>
|
||||
IGenreRepository GenreRepository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle providers.
|
||||
/// </summary>
|
||||
IProviderRepository ProviderRepository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle users.
|
||||
/// </summary>
|
||||
@ -102,7 +91,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The resource found</returns>
|
||||
[ItemNotNull]
|
||||
Task<T> Get<T>(int id)
|
||||
where T : class, IResource;
|
||||
|
||||
@ -113,7 +101,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The resource found</returns>
|
||||
[ItemNotNull]
|
||||
Task<T> Get<T>(string slug)
|
||||
where T : class, IResource;
|
||||
|
||||
@ -124,7 +111,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The first resource found that match the where function</returns>
|
||||
[ItemNotNull]
|
||||
Task<T> Get<T>(Expression<Func<T, bool>> where)
|
||||
where T : class, IResource;
|
||||
|
||||
@ -135,7 +121,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The season found</returns>
|
||||
[ItemNotNull]
|
||||
Task<Season> Get(int showID, int seasonNumber);
|
||||
|
||||
/// <summary>
|
||||
@ -145,7 +130,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The season found</returns>
|
||||
[ItemNotNull]
|
||||
Task<Season> Get(string showSlug, int seasonNumber);
|
||||
|
||||
/// <summary>
|
||||
@ -156,7 +140,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
[ItemNotNull]
|
||||
Task<Episode> Get(int showID, int seasonNumber, int episodeNumber);
|
||||
|
||||
/// <summary>
|
||||
@ -167,7 +150,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
[ItemNotNull]
|
||||
Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber);
|
||||
|
||||
/// <summary>
|
||||
@ -176,8 +158,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="id">The id of the resource</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>The resource found</returns>
|
||||
[ItemCanBeNull]
|
||||
Task<T> GetOrDefault<T>(int id)
|
||||
Task<T?> GetOrDefault<T>(int id)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -186,8 +167,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="slug">The slug of the resource</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>The resource found</returns>
|
||||
[ItemCanBeNull]
|
||||
Task<T> GetOrDefault<T>(string slug)
|
||||
Task<T?> GetOrDefault<T>(string slug)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -197,8 +177,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>The first resource found that match the where function</returns>
|
||||
[ItemCanBeNull]
|
||||
Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
|
||||
Task<T?> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T>? sortBy = default)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -207,8 +186,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <returns>The season found</returns>
|
||||
[ItemCanBeNull]
|
||||
Task<Season> GetOrDefault(int showID, int seasonNumber);
|
||||
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.
|
||||
@ -216,8 +194,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <returns>The season found</returns>
|
||||
[ItemCanBeNull]
|
||||
Task<Season> GetOrDefault(string showSlug, int seasonNumber);
|
||||
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.
|
||||
@ -226,8 +203,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <returns>The episode found</returns>
|
||||
[ItemCanBeNull]
|
||||
Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber);
|
||||
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.
|
||||
@ -236,8 +212,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <returns>The episode found</returns>
|
||||
[ItemCanBeNull]
|
||||
Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
|
||||
Task<Episode?> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Load a related resource
|
||||
@ -253,7 +228,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/>
|
||||
/// <seealso cref="Load{T}(T, string, bool)"/>
|
||||
/// <seealso cref="Load(IResource, string, bool)"/>
|
||||
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, T2>> member, bool force = false)
|
||||
Task<T> Load<T, T2>(T obj, Expression<Func<T, T2>> member, bool force = false)
|
||||
where T : class, IResource
|
||||
where T2 : class, IResource;
|
||||
|
||||
@ -271,7 +246,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,T2}}, bool)"/>
|
||||
/// <seealso cref="Load{T}(T, string, bool)"/>
|
||||
/// <seealso cref="Load(IResource, string, bool)"/>
|
||||
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, ICollection<T2>>> member, bool force = false)
|
||||
Task<T> Load<T, T2>(T obj, Expression<Func<T, ICollection<T2>>> member, bool force = false)
|
||||
where T : class, IResource
|
||||
where T2 : class;
|
||||
|
||||
@ -288,7 +263,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,T2}}, bool)"/>
|
||||
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/>
|
||||
/// <seealso cref="Load(IResource, string, bool)"/>
|
||||
Task<T> Load<T>([NotNull] T obj, string memberName, bool force = false)
|
||||
Task<T> Load<T>(T obj, string memberName, bool force = false)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -303,35 +278,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/>
|
||||
/// <seealso cref="Load{T}(T, string, bool)"/>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task Load([NotNull] IResource obj, string memberName, bool force = false);
|
||||
|
||||
/// <summary>
|
||||
/// Get items (A wrapper around shows or collections) from a library.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the library</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,
|
||||
Expression<Func<LibraryItem, bool>> where = null,
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get items (A wrapper around shows or collections) from a library.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the library</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<LibraryItem>> GetItemsFromLibrary(string slug,
|
||||
Expression<Func<LibraryItem, bool>> where = null,
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default);
|
||||
Task Load(IResource obj, string memberName, bool force = false);
|
||||
|
||||
/// <summary>
|
||||
/// Get people's roles from a show.
|
||||
@ -343,9 +290,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
|
||||
Expression<Func<PeopleRole, bool>> where = null,
|
||||
Sort<PeopleRole> sort = default,
|
||||
Pagination limit = default);
|
||||
Expression<Func<PeopleRole, bool>>? where = null,
|
||||
Sort<PeopleRole>? sort = default,
|
||||
Pagination? limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get people's roles from a show.
|
||||
@ -357,9 +304,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetPeopleFromShow(string showSlug,
|
||||
Expression<Func<PeopleRole, bool>> where = null,
|
||||
Sort<PeopleRole> sort = default,
|
||||
Pagination limit = default);
|
||||
Expression<Func<PeopleRole, bool>>? where = null,
|
||||
Sort<PeopleRole>? sort = default,
|
||||
Pagination? limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get people's roles from a person.
|
||||
@ -371,9 +318,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetRolesFromPeople(int id,
|
||||
Expression<Func<PeopleRole, bool>> where = null,
|
||||
Sort<PeopleRole> sort = default,
|
||||
Pagination limit = default);
|
||||
Expression<Func<PeopleRole, bool>>? where = null,
|
||||
Sort<PeopleRole>? sort = default,
|
||||
Pagination? limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get people's roles from a person.
|
||||
@ -385,27 +332,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetRolesFromPeople(string slug,
|
||||
Expression<Func<PeopleRole, bool>> where = null,
|
||||
Sort<PeopleRole> sort = default,
|
||||
Pagination limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Setup relations between a show, a library and a collection
|
||||
/// </summary>
|
||||
/// <param name="showID">The show's ID to setup relations with</param>
|
||||
/// <param name="libraryID">The library's ID to setup relations with (optional)</param>
|
||||
/// <param name="collectionID">The collection's ID to setup relations with (optional)</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task AddShowLink(int showID, int? libraryID, int? collectionID);
|
||||
|
||||
/// <summary>
|
||||
/// Setup relations between a show, a library and a collection
|
||||
/// </summary>
|
||||
/// <param name="show">The show to setup relations with</param>
|
||||
/// <param name="library">The library to setup relations with (optional)</param>
|
||||
/// <param name="collection">The collection to setup relations with (optional)</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task AddShowLink([NotNull] Show show, Library library, Collection collection);
|
||||
Expression<Func<PeopleRole, bool>>? where = null,
|
||||
Sort<PeopleRole>? sort = default,
|
||||
Pagination? limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get all resources with filters
|
||||
@ -415,9 +344,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <typeparam name="T">The type of resources to load</typeparam>
|
||||
/// <returns>A list of resources that match every filters</returns>
|
||||
Task<ICollection<T>> GetAll<T>(Expression<Func<T, bool>> where = null,
|
||||
Sort<T> sort = default,
|
||||
Pagination limit = default)
|
||||
Task<ICollection<T>> GetAll<T>(Expression<Func<T, bool>>? where = null,
|
||||
Sort<T>? sort = default,
|
||||
Pagination? limit = default)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -426,7 +355,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <typeparam name="T">The type of resources to load</typeparam>
|
||||
/// <returns>A list of resources that match every filters</returns>
|
||||
Task<int> GetCount<T>(Expression<Func<T, bool>> where = null)
|
||||
Task<int> GetCount<T>(Expression<Func<T, bool>>? where = null)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -444,7 +373,7 @@ namespace Kyoo.Abstractions.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 information (related items and so on)</returns>
|
||||
Task<T> Create<T>([NotNull] T item)
|
||||
Task<T> Create<T>(T item)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -453,18 +382,31 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <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)
|
||||
Task<T> CreateIfNotExists<T>(T item)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
/// Edit a resource
|
||||
/// </summary>
|
||||
/// <param name="item">The resource 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="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's information (related items and so on)</returns>
|
||||
Task<T> Edit<T>(T item, bool resetOld)
|
||||
Task<T> Edit<T>(T item)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
/// Edit only specific properties of a resource
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource to edit</param>
|
||||
/// <param name="patch">
|
||||
/// A method that will be called when you need to update every properties that you want to
|
||||
/// persist. It can return false to abort the process via an ArgumentException
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of resources</typeparam>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's information (related items and so on)</returns>
|
||||
Task<T> Patch<T>(int id, Func<T, Task<bool>> patch)
|
||||
where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
|
@ -20,7 +20,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
|
||||
@ -45,7 +44,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="id">The id of the resource</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
|
||||
/// <returns>The resource found</returns>
|
||||
[ItemNotNull]
|
||||
Task<T> Get(int id);
|
||||
|
||||
/// <summary>
|
||||
@ -54,7 +52,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="slug">The slug of the resource</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
|
||||
/// <returns>The resource found</returns>
|
||||
[ItemNotNull]
|
||||
Task<T> Get(string slug);
|
||||
|
||||
/// <summary>
|
||||
@ -63,7 +60,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="where">A predicate to filter the resource.</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
|
||||
/// <returns>The resource found</returns>
|
||||
[ItemNotNull]
|
||||
Task<T> Get(Expression<Func<T, bool>> where);
|
||||
|
||||
/// <summary>
|
||||
@ -71,16 +67,14 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource</param>
|
||||
/// <returns>The resource found</returns>
|
||||
[ItemCanBeNull]
|
||||
Task<T> GetOrDefault(int id);
|
||||
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>
|
||||
[ItemCanBeNull]
|
||||
Task<T> GetOrDefault(string slug);
|
||||
Task<T?> GetOrDefault(string slug);
|
||||
|
||||
/// <summary>
|
||||
/// Get the first resource that match the predicate or null if it is not found.
|
||||
@ -88,15 +82,13 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="where">A predicate to filter the resource.</param>
|
||||
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
|
||||
/// <returns>The resource found</returns>
|
||||
[ItemCanBeNull]
|
||||
Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default);
|
||||
Task<T?> GetOrDefault(Expression<Func<T, bool>> where, Sort<T>? sortBy = default);
|
||||
|
||||
/// <summary>
|
||||
/// Search for resources.
|
||||
/// </summary>
|
||||
/// <param name="query">The query string.</param>
|
||||
/// <returns>A list of resources found</returns>
|
||||
[ItemNotNull]
|
||||
Task<ICollection<T>> Search(string query);
|
||||
|
||||
/// <summary>
|
||||
@ -106,33 +98,30 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="sort">Sort information about the query (sort by, sort order)</param>
|
||||
/// <param name="limit">How pagination should be done (where to start and how many to return)</param>
|
||||
/// <returns>A list of resources that match every filters</returns>
|
||||
[ItemNotNull]
|
||||
Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
|
||||
Sort<T> sort = default,
|
||||
Pagination limit = default);
|
||||
Task<ICollection<T>> GetAll(Expression<Func<T, bool>>? where = null,
|
||||
Sort<T>? sort = default,
|
||||
Pagination? limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of resources that match the filter's predicate.
|
||||
/// </summary>
|
||||
/// <param name="where">A filter predicate</param>
|
||||
/// <returns>How many resources matched that filter</returns>
|
||||
Task<int> GetCount(Expression<Func<T, bool>> where = null);
|
||||
Task<int> GetCount(Expression<Func<T, bool>>? where = null);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new resource.
|
||||
/// </summary>
|
||||
/// <param name="obj">The item to register</param>
|
||||
/// <returns>The resource registers and completed by database's information (related items and so on)</returns>
|
||||
[ItemNotNull]
|
||||
Task<T> Create([NotNull] T obj);
|
||||
Task<T> Create(T obj);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new resource if it does not exist already. If it does, the existing value is returned instead.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to create</param>
|
||||
/// <returns>The newly created item or the existing value if it existed.</returns>
|
||||
[ItemNotNull]
|
||||
Task<T> CreateIfNotExists([NotNull] T obj);
|
||||
Task<T> CreateIfNotExists(T obj);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a resource has been created.
|
||||
@ -140,14 +129,24 @@ namespace Kyoo.Abstractions.Controllers
|
||||
event ResourceEventHandler OnCreated;
|
||||
|
||||
/// <summary>
|
||||
/// Edit a resource
|
||||
/// Edit a resource and replace every property
|
||||
/// </summary>
|
||||
/// <param name="edited">The resource 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="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's information (related items and so on)</returns>
|
||||
[ItemNotNull]
|
||||
Task<T> Edit([NotNull] T edited, bool resetOld);
|
||||
Task<T> Edit(T edited);
|
||||
|
||||
/// <summary>
|
||||
/// Edit only specific properties of a resource
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource to edit</param>
|
||||
/// <param name="patch">
|
||||
/// A method that will be called when you need to update every properties that you want to
|
||||
/// persist. It can return false to abort the process via an ArgumentException
|
||||
/// </param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's information (related items and so on)</returns>
|
||||
Task<T> Patch(int id, Func<T, Task<bool>> patch);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a resource has been edited.
|
||||
@ -176,14 +175,14 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="obj">The resource to delete</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task Delete([NotNull] T obj);
|
||||
Task Delete(T obj);
|
||||
|
||||
/// <summary>
|
||||
/// Delete all resources that match the predicate.
|
||||
/// </summary>
|
||||
/// <param name="where">A predicate to filter resources to delete. Every resource that match this will be deleted.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task DeleteAll([NotNull] Expression<Func<T, bool>> where);
|
||||
Task DeleteAll(Expression<Func<T, bool>> where);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a resource has been edited.
|
||||
@ -202,21 +201,16 @@ namespace Kyoo.Abstractions.Controllers
|
||||
Type RepositoryType { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle shows.
|
||||
/// </summary>
|
||||
public interface IMovieRepository : IRepository<Movie> { }
|
||||
|
||||
/// <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 those 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>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task AddShowLink(int showID, int? libraryID, int? collectionID);
|
||||
|
||||
/// <summary>
|
||||
/// Get a show's slug from it's ID.
|
||||
/// </summary>
|
||||
@ -330,55 +324,16 @@ namespace Kyoo.Abstractions.Controllers
|
||||
Task<Episode> GetAbsolute(string showSlug, int absoluteNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle libraries.
|
||||
/// </summary>
|
||||
public interface ILibraryRepository : IRepository<Library> { }
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle library items (A wrapper around shows and collections).
|
||||
/// </summary>
|
||||
public interface ILibraryItemRepository : IRepository<LibraryItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get items (A wrapper around shows or collections) from a library.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the library</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
|
||||
Expression<Func<LibraryItem, bool>> where = null,
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get items (A wrapper around shows or collections) from a library.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the library</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
|
||||
Expression<Func<LibraryItem, bool>> where = null,
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default);
|
||||
}
|
||||
public interface ILibraryItemRepository : IRepository<ILibraryItem> { }
|
||||
|
||||
/// <summary>
|
||||
/// A repository for collections
|
||||
/// </summary>
|
||||
public interface ICollectionRepository : IRepository<Collection> { }
|
||||
|
||||
/// <summary>
|
||||
/// A repository for genres.
|
||||
/// </summary>
|
||||
public interface IGenreRepository : IRepository<Genre> { }
|
||||
|
||||
/// <summary>
|
||||
/// A repository for studios.
|
||||
/// </summary>
|
||||
@ -399,9 +354,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetFromShow(int showID,
|
||||
Expression<Func<PeopleRole, bool>> where = null,
|
||||
Sort<PeopleRole> sort = default,
|
||||
Pagination limit = default);
|
||||
Expression<Func<PeopleRole, bool>>? where = null,
|
||||
Sort<PeopleRole>? sort = default,
|
||||
Pagination? limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get people's roles from a show.
|
||||
@ -413,9 +368,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
|
||||
Expression<Func<PeopleRole, bool>> where = null,
|
||||
Sort<PeopleRole> sort = default,
|
||||
Pagination limit = default);
|
||||
Expression<Func<PeopleRole, bool>>? where = null,
|
||||
Sort<PeopleRole>? sort = default,
|
||||
Pagination? limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get people's roles from a person.
|
||||
@ -427,9 +382,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetFromPeople(int id,
|
||||
Expression<Func<PeopleRole, bool>> where = null,
|
||||
Sort<PeopleRole> sort = default,
|
||||
Pagination limit = default);
|
||||
Expression<Func<PeopleRole, bool>>? where = null,
|
||||
Sort<PeopleRole>? sort = default,
|
||||
Pagination? limit = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get people's roles from a person.
|
||||
@ -441,28 +396,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetFromPeople(string slug,
|
||||
Expression<Func<PeopleRole, bool>> where = null,
|
||||
Sort<PeopleRole> sort = default,
|
||||
Pagination limit = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle providers.
|
||||
/// </summary>
|
||||
public interface IProviderRepository : IRepository<Provider>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a list of external ids that match all filters
|
||||
/// </summary>
|
||||
/// <param name="where">A predicate to add arbitrary filter</param>
|
||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||
/// <typeparam name="T">The type of metadata to retrieve</typeparam>
|
||||
/// <returns>A filtered list of external ids.</returns>
|
||||
Task<ICollection<MetadataID>> GetMetadataID<T>(Expression<Func<MetadataID, bool>> where = null,
|
||||
Sort<MetadataID> sort = default,
|
||||
Pagination limit = default)
|
||||
where T : class, IMetadata;
|
||||
Expression<Func<PeopleRole, bool>>? where = null,
|
||||
Sort<PeopleRole>? sort = default,
|
||||
Pagination? limit = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -35,22 +35,20 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <param name="item">
|
||||
/// The item to cache images.
|
||||
/// </param>
|
||||
/// <param name="alwaysDownload">
|
||||
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of the item</typeparam>
|
||||
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
||||
Task<bool> DownloadImages<T>(T item, bool alwaysDownload = false)
|
||||
Task DownloadImages<T>(T item)
|
||||
where T : IThumbnails;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the local path of an image of the given item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to retrieve the poster from.</param>
|
||||
/// <param name="imageId">The ID of the image. See <see cref="Images"/> for values.</param>
|
||||
/// <param name="image">The ID of the image.</param>
|
||||
/// <param name="quality">The quality of the image</param>
|
||||
/// <typeparam name="T">The type of the item</typeparam>
|
||||
/// <returns>The path of the image for the given resource or null if it does not exists.</returns>
|
||||
string? GetImagePath<T>(T item, int imageId)
|
||||
string GetImagePath<T>(T item, string image, ImageQuality quality)
|
||||
where T : IThumbnails;
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <typeparam name="T">A dependency that this action will use.</typeparam>
|
||||
/// <returns>A new <see cref="StartupAction"/></returns>
|
||||
public static StartupAction<T> New<T>(Action<T> action, int priority)
|
||||
where T : notnull
|
||||
=> new(action, priority);
|
||||
|
||||
/// <summary>
|
||||
@ -94,6 +95,8 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <typeparam name="T2">A second dependency that this action will use.</typeparam>
|
||||
/// <returns>A new <see cref="StartupAction"/></returns>
|
||||
public static StartupAction<T, T2> New<T, T2>(Action<T, T2> action, int priority)
|
||||
where T : notnull
|
||||
where T2 : notnull
|
||||
=> new(action, priority);
|
||||
|
||||
/// <summary>
|
||||
@ -106,6 +109,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <typeparam name="T3">A third dependency that this action will use.</typeparam>
|
||||
/// <returns>A new <see cref="StartupAction"/></returns>
|
||||
public static StartupAction<T, T2, T3> New<T, T2, T3>(Action<T, T2, T3> action, int priority)
|
||||
where T : notnull
|
||||
where T2 : notnull
|
||||
where T3 : notnull
|
||||
=> new(action, priority);
|
||||
|
||||
/// <summary>
|
||||
@ -144,6 +150,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The dependency to use.</typeparam>
|
||||
public class StartupAction<T> : IStartupAction
|
||||
where T : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// The action to execute at startup.
|
||||
@ -177,6 +184,8 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <typeparam name="T">The dependency to use.</typeparam>
|
||||
/// <typeparam name="T2">The second dependency to use.</typeparam>
|
||||
public class StartupAction<T, T2> : IStartupAction
|
||||
where T : notnull
|
||||
where T2 : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// The action to execute at startup.
|
||||
@ -214,6 +223,9 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <typeparam name="T2">The second dependency to use.</typeparam>
|
||||
/// <typeparam name="T3">The third dependency to use.</typeparam>
|
||||
public class StartupAction<T, T2, T3> : IStartupAction
|
||||
where T : notnull
|
||||
where T2 : notnull
|
||||
where T3 : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// The action to execute at startup.
|
||||
|
@ -3,6 +3,7 @@
|
||||
<Title>Kyoo.Abstractions</Title>
|
||||
<Description>Base package to create plugins for Kyoo.</Description>
|
||||
<RootNamespace>Kyoo.Abstractions</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -17,7 +17,6 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Attributes
|
||||
{
|
||||
@ -32,23 +31,21 @@ namespace Kyoo.Abstractions.Models.Attributes
|
||||
/// <summary>
|
||||
/// The public name of this api.
|
||||
/// </summary>
|
||||
[NotNull] public string Name { get; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the group in witch this API is. You can also specify a custom sort order using the following
|
||||
/// format: <code>order:name</code>. Everything before the first <c>:</c> will be removed but kept for
|
||||
/// th alphabetical ordering.
|
||||
/// </summary>
|
||||
public string Group { get; set; }
|
||||
public string? Group { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ApiDefinitionAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the api that will be used on the documentation page.</param>
|
||||
public ApiDefinitionAttribute([NotNull] string name)
|
||||
public ApiDefinitionAttribute(string name)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ namespace Kyoo.Abstractions.Models.Attributes
|
||||
/// <summary>
|
||||
/// The name of the field containing the related resource's ID.
|
||||
/// </summary>
|
||||
public string RelationID { get; }
|
||||
public string? RelationID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="LoadableRelationAttribute"/>.
|
||||
|
@ -32,17 +32,17 @@ namespace Kyoo.Abstractions.Models.Permissions
|
||||
/// <summary>
|
||||
/// The needed permission type.
|
||||
/// </summary>
|
||||
public string Type { get; }
|
||||
public string? Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The needed permission kind.
|
||||
/// </summary>
|
||||
public Kind Kind { get; }
|
||||
public Kind? Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The group of this permission.
|
||||
/// </summary>
|
||||
public Group Group { get; set; }
|
||||
public Group? Group { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ask a permission to run an action.
|
||||
|
@ -1,121 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Utils;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A class given information about a strongly typed configuration.
|
||||
/// </summary>
|
||||
public class ConfigurationReference
|
||||
{
|
||||
/// <summary>
|
||||
/// The path of the resource (separated by ':')
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the resource.
|
||||
/// </summary>
|
||||
public Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ConfigurationReference"/> using a given path and type.
|
||||
/// This method does not create sub configuration resources. Please see <see cref="CreateReference"/>
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the resource (separated by ':' or "__")</param>
|
||||
/// <param name="type">The type of the resource</param>
|
||||
/// <seealso cref="CreateReference"/>
|
||||
public ConfigurationReference(string path, Type type)
|
||||
{
|
||||
Path = path;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the list of configuration reference a type has.
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// The base path of the type (separated by ':' or "__". If empty, it will start at root)
|
||||
/// </param>
|
||||
/// <param name="type">The type of the object</param>
|
||||
/// <returns>The list of configuration reference a type has.</returns>
|
||||
public static IEnumerable<ConfigurationReference> CreateReference(string path, [NotNull] Type type)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
List<ConfigurationReference> ret = new()
|
||||
{
|
||||
new ConfigurationReference(path, type)
|
||||
};
|
||||
|
||||
if (!type.IsClass || type.AssemblyQualifiedName?.StartsWith("System") == true)
|
||||
return ret;
|
||||
|
||||
Type enumerable = Utility.GetGenericDefinition(type, typeof(IEnumerable<>));
|
||||
Type dictionary = Utility.GetGenericDefinition(type, typeof(IDictionary<,>));
|
||||
Type dictionaryKey = dictionary?.GetGenericArguments()[0];
|
||||
|
||||
if (dictionary != null && dictionaryKey == typeof(string))
|
||||
ret.AddRange(CreateReference($"{path}:{type.Name}:*", dictionary.GetGenericArguments()[1]));
|
||||
else if (dictionary != null && dictionaryKey == typeof(int))
|
||||
ret.AddRange(CreateReference($"{path}:{type.Name}:", dictionary.GetGenericArguments()[1]));
|
||||
else if (enumerable != null)
|
||||
ret.AddRange(CreateReference($"{path}:{type.Name}:", enumerable.GetGenericArguments()[0]));
|
||||
else
|
||||
{
|
||||
foreach (PropertyInfo child in type.GetProperties())
|
||||
ret.AddRange(CreateReference($"{path}:{child.Name}", child.PropertyType));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the list of configuration reference a type has.
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// The base path of the type (separated by ':' or "__". If empty, it will start at root)
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of the object</typeparam>
|
||||
/// <returns>The list of configuration reference a type has.</returns>
|
||||
public static IEnumerable<ConfigurationReference> CreateReference<T>(string path)
|
||||
{
|
||||
return CreateReference(path, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="ConfigurationReference"/> meaning that the given path is of any type.
|
||||
/// It means that the type can't be edited.
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// The path that will be untyped (separated by ':' or "__". If empty, it will start at root).
|
||||
/// </param>
|
||||
/// <returns>A configuration reference representing a path of any type.</returns>
|
||||
public static ConfigurationReference CreateUntyped(string path)
|
||||
{
|
||||
return new ConfigurationReference(path, null);
|
||||
}
|
||||
}
|
||||
}
|
@ -30,13 +30,13 @@ namespace Kyoo.Abstractions.Models.Exceptions
|
||||
/// <summary>
|
||||
/// The existing object.
|
||||
/// </summary>
|
||||
public object Existing { get; }
|
||||
public object? Existing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="DuplicatedItemException"/> with the default message.
|
||||
/// </summary>
|
||||
/// <param name="existing">The existing object.</param>
|
||||
public DuplicatedItemException(object existing = null)
|
||||
public DuplicatedItemException(object? existing = null)
|
||||
: base("Already exists in the database.")
|
||||
{
|
||||
Existing = existing;
|
||||
|
@ -19,13 +19,27 @@
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface to represent resources that should have a link field in their return values (like videos).
|
||||
/// A genre that allow one to specify categories for shows.
|
||||
/// </summary>
|
||||
public interface ILink
|
||||
public enum Genre
|
||||
{
|
||||
/// <summary>
|
||||
/// The link to return, in most cases this should be a string.
|
||||
/// </summary>
|
||||
public object Link { get; }
|
||||
Action,
|
||||
Adventure,
|
||||
Animation,
|
||||
Comedy,
|
||||
Crime,
|
||||
Documentary,
|
||||
Drama,
|
||||
Family,
|
||||
Fantasy,
|
||||
History,
|
||||
Horror,
|
||||
Music,
|
||||
Mystery,
|
||||
Romance,
|
||||
ScienceFiction,
|
||||
Thriller,
|
||||
War,
|
||||
Western,
|
||||
}
|
||||
}
|
@ -18,15 +18,16 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq.Expressions;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using Kyoo.Utils;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of item, ether a show, a movie or a collection.
|
||||
/// </summary>
|
||||
public enum ItemType
|
||||
public enum ItemKind
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="LibraryItem"/> is a <see cref="Show"/>.
|
||||
@ -34,8 +35,7 @@ namespace Kyoo.Abstractions.Models
|
||||
Show,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LibraryItem"/> is a Movie (a <see cref="Show"/> with
|
||||
/// <see cref="Models.Show.IsMovie"/> equals to true).
|
||||
/// The <see cref="LibraryItem"/> is a Movie.
|
||||
/// </summary>
|
||||
Movie,
|
||||
|
||||
@ -45,128 +45,135 @@ namespace Kyoo.Abstractions.Models
|
||||
Collection
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A type union between <see cref="Show"/> and <see cref="Collection"/>.
|
||||
/// This is used to list content put inside a library.
|
||||
/// </summary>
|
||||
public class LibraryItem : CustomTypeDescriptor, IResource, IThumbnails
|
||||
public class LibraryItem : IResource, ILibraryItem, IThumbnails, IMetadata
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MaxLength(256)]
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of the show or collection.
|
||||
/// The title of this show.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The summary of the show or collection.
|
||||
/// A catchphrase for this movie.
|
||||
/// </summary>
|
||||
public string Overview { get; set; }
|
||||
public string? Tagline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this show airing, not aired yet or finished? This is only applicable for shows.
|
||||
/// The list of alternative titles of this show.
|
||||
/// </summary>
|
||||
public Status? Status { get; set; }
|
||||
public string[] Aliases { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The date this show or collection started airing. It can be null if this is unknown.
|
||||
/// The path of the movie video file.
|
||||
/// </summary>
|
||||
public string? Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The summary of this show.
|
||||
/// </summary>
|
||||
public string? Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of tags that match this movie.
|
||||
/// </summary>
|
||||
public string[] Tags { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The list of genres (themes) this show has.
|
||||
/// </summary>
|
||||
public Genre[] Genres { get; set; } = Array.Empty<Genre>();
|
||||
|
||||
/// <summary>
|
||||
/// Is this show airing, not aired yet or finished?
|
||||
/// </summary>
|
||||
public Status Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date this show started airing. It can be null if this is unknown.
|
||||
/// </summary>
|
||||
public DateTime? StartAir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date this show or collection finished airing.
|
||||
/// It must be after the <see cref="StartAir"/> but can be the same (example: for movies).
|
||||
/// The date this show finished airing.
|
||||
/// It can also be null if this is unknown.
|
||||
/// </summary>
|
||||
public DateTime? EndAir { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date this movie aired.
|
||||
/// </summary>
|
||||
public DateTime? AirDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Logo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of this item (ether a collection, a show or a movie).
|
||||
/// A video of a few minutes that tease the content.
|
||||
/// </summary>
|
||||
public ItemType Type { get; set; }
|
||||
public string? Trailer { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ItemKind Kind { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty <see cref="LibraryItem"/>.
|
||||
/// Links to watch this movie.
|
||||
/// </summary>
|
||||
public VideoLinks? Links => Kind == ItemKind.Movie ? new()
|
||||
{
|
||||
Direct = $"/video/movie/{Slug}/direct",
|
||||
Hls = $"/video/movie/{Slug}/master.m3u8",
|
||||
}
|
||||
: null;
|
||||
|
||||
public LibraryItem() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="LibraryItem"/> from a show.
|
||||
/// </summary>
|
||||
/// <param name="show">The show that this library item should represent.</param>
|
||||
public LibraryItem(Show show)
|
||||
[JsonConstructor]
|
||||
public LibraryItem(string name)
|
||||
{
|
||||
ID = show.ID;
|
||||
Slug = show.Slug;
|
||||
Title = show.Title;
|
||||
Overview = show.Overview;
|
||||
Status = show.Status;
|
||||
StartAir = show.StartAir;
|
||||
EndAir = show.EndAir;
|
||||
Images = show.Images;
|
||||
Type = show.IsMovie ? ItemType.Movie : ItemType.Show;
|
||||
Slug = Utility.ToSlug(name);
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="LibraryItem"/> from a collection
|
||||
/// A type union between <see cref="Show"/> and <see cref="Collection"/>.
|
||||
/// This is used to list content put inside a library.
|
||||
/// </summary>
|
||||
/// <param name="collection">The collection that this library item should represent.</param>
|
||||
public LibraryItem(Collection collection)
|
||||
public interface ILibraryItem : IResource
|
||||
{
|
||||
ID = -collection.ID;
|
||||
Slug = collection.Slug;
|
||||
Title = collection.Name;
|
||||
Overview = collection.Overview;
|
||||
Status = Models.Status.Unknown;
|
||||
StartAir = null;
|
||||
EndAir = null;
|
||||
Images = collection.Images;
|
||||
Type = ItemType.Collection;
|
||||
}
|
||||
/// <summary>
|
||||
/// Is the item a collection, a movie or a show?
|
||||
/// </summary>
|
||||
public ItemKind Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An expression to create a <see cref="LibraryItem"/> representing a show.
|
||||
/// The title of this show.
|
||||
/// </summary>
|
||||
public static Expression<Func<Show, LibraryItem>> FromShow => x => new LibraryItem
|
||||
{
|
||||
ID = x.ID,
|
||||
Slug = x.Slug,
|
||||
Title = x.Title,
|
||||
Overview = x.Overview,
|
||||
Status = x.Status,
|
||||
StartAir = x.StartAir,
|
||||
EndAir = x.EndAir,
|
||||
Images = x.Images,
|
||||
Type = x.IsMovie ? ItemType.Movie : ItemType.Show
|
||||
};
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An expression to create a <see cref="LibraryItem"/> representing a collection.
|
||||
/// The summary of this show.
|
||||
/// </summary>
|
||||
public static Expression<Func<Collection, LibraryItem>> FromCollection => x => new LibraryItem
|
||||
{
|
||||
ID = -x.ID,
|
||||
Slug = x.Slug,
|
||||
Title = x.Name,
|
||||
Overview = x.Overview,
|
||||
Status = Models.Status.Unknown,
|
||||
StartAir = null,
|
||||
EndAir = null,
|
||||
Images = x.Images,
|
||||
Type = ItemType.Collection
|
||||
};
|
||||
public string? Overview { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetClassName()
|
||||
{
|
||||
return Type.ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// The date this movie aired.
|
||||
/// </summary>
|
||||
public DateTime? AirDate { get; }
|
||||
}
|
||||
}
|
||||
|
@ -16,48 +16,21 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// ID and link of an item on an external provider.
|
||||
/// </summary>
|
||||
public class MetadataID
|
||||
public class MetadataId
|
||||
{
|
||||
/// <summary>
|
||||
/// The expression to retrieve the unique ID of a MetadataID. This is an aggregate of the two resources IDs.
|
||||
/// </summary>
|
||||
public static Expression<Func<MetadataID, object>> PrimaryKey
|
||||
{
|
||||
get { return x => new { First = x.ResourceID, Second = x.ProviderID }; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the resource which possess the metadata.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int ResourceID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the provider.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int ProviderID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The provider that can do something with this ID.
|
||||
/// </summary>
|
||||
public Provider Provider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the resource on the external provider.
|
||||
/// </summary>
|
||||
public string DataID { get; set; }
|
||||
public string DataId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the resource on the external provider.
|
||||
/// </summary>
|
||||
public string Link { get; set; }
|
||||
public string? Link { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -93,14 +93,14 @@ namespace Kyoo.Abstractions.Models
|
||||
|
||||
if (items.Count > 0 && query.ContainsKey("afterID"))
|
||||
{
|
||||
query["afterID"] = items.First().ID.ToString();
|
||||
query["afterID"] = items.First().Id.ToString();
|
||||
query["reverse"] = "true";
|
||||
Previous = url + query.ToQueryString();
|
||||
}
|
||||
query.Remove("reverse");
|
||||
if (items.Count == limit && limit > 0)
|
||||
{
|
||||
query["afterID"] = items.Last().ID.ToString();
|
||||
query["afterID"] = items.Last().Id.ToString();
|
||||
Next = url + query.ToQueryString();
|
||||
}
|
||||
|
||||
|
@ -16,33 +16,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
namespace Kyoo.Models;
|
||||
|
||||
namespace Kyoo.Tests.Database
|
||||
public class PartialResource
|
||||
{
|
||||
namespace PostgreSQL
|
||||
{
|
||||
[Collection(nameof(Postgresql))]
|
||||
public class ProviderTests : AProviderTests
|
||||
{
|
||||
public ProviderTests(PostgresFixture postgres, ITestOutputHelper output)
|
||||
: base(new RepositoryActivator(output, postgres)) { }
|
||||
}
|
||||
}
|
||||
public int? Id { get; set; }
|
||||
|
||||
public abstract class AProviderTests : RepositoryTests<Provider>
|
||||
{
|
||||
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
|
||||
private readonly IProviderRepository _repository;
|
||||
|
||||
protected AProviderTests(RepositoryActivator repositories)
|
||||
: base(repositories)
|
||||
{
|
||||
_repository = Repositories.LibraryManager.ProviderRepository;
|
||||
}
|
||||
}
|
||||
public string? Slug { get; set; }
|
||||
}
|
@ -29,10 +29,10 @@ namespace Kyoo.Abstractions.Models
|
||||
public class PeopleRole : IResource
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Slug => ForPeople ? Show.Slug : People.Slug;
|
||||
public string Slug => ForPeople ? Show!.Slug : People.Slug;
|
||||
|
||||
/// <summary>
|
||||
/// Should this role be used as a Show substitute (the value is <c>true</c>) or
|
||||
@ -53,12 +53,16 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// The ID of the Show where the People playing in.
|
||||
/// </summary>
|
||||
public int ShowID { get; set; }
|
||||
public int? ShowID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The show where the People played in.
|
||||
/// </summary>
|
||||
public Show Show { get; set; }
|
||||
public Show? Show { get; set; }
|
||||
|
||||
public int? MovieID { get; set; }
|
||||
|
||||
public Movie? Movie { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of work the person has done for the show.
|
||||
|
@ -17,46 +17,63 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Utils;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A class representing collections of <see cref="Show"/>.
|
||||
/// A collection can also be stored in a <see cref="Library"/>.
|
||||
/// </summary>
|
||||
public class Collection : IResource, IMetadata, IThumbnails
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Slug { get; set; }
|
||||
[MaxLength(256)] public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this collection.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of this collection.
|
||||
/// </summary>
|
||||
public string Overview { get; set; }
|
||||
public string? Overview { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Logo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of movies contained in this collection.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Movie>? Movies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of shows contained in this collection.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Show> Shows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of libraries that contains this collection.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
|
||||
[LoadableRelation] public ICollection<Show>? Shows { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[EditableRelation][LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
||||
public Collection() { }
|
||||
|
||||
[JsonConstructor]
|
||||
public Collection(string name)
|
||||
{
|
||||
Slug = Utility.ToSlug(name);
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
@ -31,28 +32,23 @@ namespace Kyoo.Abstractions.Models
|
||||
public class Episode : IResource, IMetadata, IThumbnails
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Computed]
|
||||
[MaxLength(256)]
|
||||
public string Slug
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ShowSlug != null || Show?.Slug != null)
|
||||
return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber);
|
||||
return ShowID != 0
|
||||
? GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber)
|
||||
: null;
|
||||
return GetSlug(ShowSlug ?? Show!.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber);
|
||||
return GetSlug(ShowId.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber);
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[NotNull]
|
||||
private set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
Match match = Regex.Match(value, @"(?<show>.+)-s(?<season>\d+)e(?<episode>\d+)");
|
||||
|
||||
if (match.Success)
|
||||
@ -80,22 +76,22 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public string ShowSlug { private get; set; }
|
||||
[SerializeIgnore] public string? ShowSlug { private get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the Show containing this episode.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int ShowID { get; set; }
|
||||
[SerializeIgnore] public int ShowId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The show that contains this episode. This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
||||
/// </summary>
|
||||
[LoadableRelation(nameof(ShowID))] public Show Show { get; set; }
|
||||
[LoadableRelation(nameof(ShowId))] public Show? Show { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the Season containing this episode.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int? SeasonID { get; set; }
|
||||
[SerializeIgnore] public int? SeasonId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The season that contains this episode.
|
||||
@ -105,7 +101,7 @@ namespace Kyoo.Abstractions.Models
|
||||
/// This can be null if the season is unknown and the episode is only identified
|
||||
/// by it's <see cref="AbsoluteNumber"/>.
|
||||
/// </remarks>
|
||||
[LoadableRelation(nameof(SeasonID))] public Season Season { get; set; }
|
||||
[LoadableRelation(nameof(SeasonId))] public Season? Season { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The season in witch this episode is in.
|
||||
@ -127,18 +123,15 @@ namespace Kyoo.Abstractions.Models
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of this episode.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The overview of this episode.
|
||||
/// </summary>
|
||||
public string Overview { get; set; }
|
||||
public string? Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The release date of this episode. It can be null if unknown.
|
||||
@ -146,7 +139,35 @@ namespace Kyoo.Abstractions.Models
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[EditableRelation][LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Logo { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The previous episode that should be seen before viewing this one.
|
||||
/// </summary>
|
||||
[LoadableRelation] public Episode? PreviousEpisode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The next episode to watch after this one.
|
||||
/// </summary>
|
||||
[LoadableRelation] public Episode? NextEpisode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Links to watch this episode.
|
||||
/// </summary>
|
||||
public VideoLinks Links => new()
|
||||
{
|
||||
Direct = $"/video/episode/{Slug}/direct",
|
||||
Hls = $"/video/episode/{Slug}/master.m3u8",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Get the slug of an episode.
|
||||
@ -165,14 +186,11 @@ namespace Kyoo.Abstractions.Models
|
||||
/// If you don't know it or this is a movie, use null
|
||||
/// </param>
|
||||
/// <returns>The slug corresponding to the given arguments</returns>
|
||||
/// <exception cref="ArgumentNullException">The given show slug was null.</exception>
|
||||
public static string GetSlug([NotNull] string showSlug,
|
||||
public static string GetSlug(string showSlug,
|
||||
int? seasonNumber,
|
||||
int? episodeNumber,
|
||||
int? absoluteNumber = null)
|
||||
{
|
||||
if (showSlug == null)
|
||||
throw new ArgumentNullException(nameof(showSlug));
|
||||
return seasonNumber switch
|
||||
{
|
||||
null when absoluteNumber == null => showSlug,
|
||||
|
@ -1,62 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Utils;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A genre that allow one to specify categories for shows.
|
||||
/// </summary>
|
||||
public class Genre : IResource
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this genre.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of shows that have this genre.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Show> Shows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty <see cref="Genre"/>.
|
||||
/// </summary>
|
||||
public Genre() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Genre"/> and specify it's <see cref="Name"/>.
|
||||
/// The <see cref="Slug"/> is automatically calculated from it's name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of this genre.</param>
|
||||
public Genre(string name)
|
||||
{
|
||||
Slug = Utility.ToSlug(name);
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,11 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
@ -30,69 +26,8 @@ namespace Kyoo.Abstractions.Models
|
||||
public interface IMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// The link to metadata providers that this show has. See <see cref="MetadataID"/> for more information.
|
||||
/// The link to metadata providers that this show has. See <see cref="MetadataId"/> for more information.
|
||||
/// </summary>
|
||||
[EditableRelation]
|
||||
[LoadableRelation]
|
||||
public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A static class containing extensions method for every <see cref="IMetadata"/> class.
|
||||
/// This allow one to use metadata more easily.
|
||||
/// </summary>
|
||||
public static class MetadataExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve the internal provider's ID of an item using it's provider slug.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method will never return anything if the <see cref="IMetadata.ExternalIDs"/> are not loaded.
|
||||
/// </remarks>
|
||||
/// <param name="self">An instance of <see cref="IMetadata"/> to retrieve the ID from.</param>
|
||||
/// <param name="provider">The slug of the provider</param>
|
||||
/// <returns>The <see cref="MetadataID.DataID"/> field of the asked provider.</returns>
|
||||
[CanBeNull]
|
||||
public static string GetID(this IMetadata self, string provider)
|
||||
{
|
||||
return self.ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the internal provider's ID of an item using it's provider slug.
|
||||
/// If the ID could be found, it is converted to the <typeparamref name="T"/> type and <c>true</c> is returned.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method will never succeed if the <see cref="IMetadata.ExternalIDs"/> are not loaded.
|
||||
/// </remarks>
|
||||
/// <param name="self">An instance of <see cref="IMetadata"/> to retrieve the ID from.</param>
|
||||
/// <param name="provider">The slug of the provider</param>
|
||||
/// <param name="id">
|
||||
/// The <see cref="MetadataID.DataID"/> field of the asked provider parsed
|
||||
/// and converted to the <typeparamref name="T"/> type.
|
||||
/// It is only relevant if this method returns <c>true</c>.
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type to convert the <see cref="MetadataID.DataID"/> to.</typeparam>
|
||||
/// <returns><c>true</c> if this method succeeded, <c>false</c> otherwise.</returns>
|
||||
public static bool TryGetID<T>(this IMetadata self, string provider, out T id)
|
||||
{
|
||||
string dataID = self.ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID;
|
||||
if (dataID == null)
|
||||
{
|
||||
id = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
id = (T)Convert.ChangeType(dataID, typeof(T));
|
||||
}
|
||||
catch
|
||||
{
|
||||
id = default;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
@ -32,7 +33,7 @@ namespace Kyoo.Abstractions.Models
|
||||
/// You don't need to specify an ID manually when creating a new resource,
|
||||
/// this field is automatically assigned by the <see cref="IRepository{T}"/>.
|
||||
/// </remarks>
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A human-readable identifier that can be used instead of an ID.
|
||||
@ -42,6 +43,7 @@ namespace Kyoo.Abstractions.Models
|
||||
/// There is no setter for a slug since it can be computed from other fields.
|
||||
/// For example, a season slug is {ShowSlug}-s{SeasonNumber}.
|
||||
/// </remarks>
|
||||
[MaxLength(256)]
|
||||
public string Slug { get; }
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
@ -27,51 +30,104 @@ namespace Kyoo.Abstractions.Models
|
||||
public interface IThumbnails
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of images mapped to a certain index.
|
||||
/// A poster is a 2/3 format image with the cover of the resource.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An arbitrary index should not be used, instead use indexes from <see cref="Models.Images"/>
|
||||
/// </remarks>
|
||||
/// <example>{"0": "example.com/dune/poster"}</example>
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A class containing constant values for images. To be used as index of a <see cref="IThumbnails.Images"/>.
|
||||
/// </summary>
|
||||
public static class Images
|
||||
{
|
||||
/// <summary>
|
||||
/// A poster is a 9/16 format image with the cover of the resource.
|
||||
/// </summary>
|
||||
public const int Poster = 0;
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A thumbnail is a 16/9 format image, it could ether be used as a background or as a preview but it usually
|
||||
/// is not an official image.
|
||||
/// </summary>
|
||||
public const int Thumbnail = 1;
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A logo is a small image representing the resource.
|
||||
/// </summary>
|
||||
public const int Logo = 2;
|
||||
public Image? Logo { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A video of a few minutes that tease the content.
|
||||
/// </summary>
|
||||
public const int Trailer = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the name of an image using it's ID. It is also used by the serializer to retrieve all named images.
|
||||
/// If a plugin adds a new image type, it should add it's value and name here to allow the serializer to add it.
|
||||
/// </summary>
|
||||
public static Dictionary<int, string> ImageName { get; } = new()
|
||||
[TypeConverter(typeof(ImageConvertor))]
|
||||
public class Image
|
||||
{
|
||||
[Poster] = nameof(Poster),
|
||||
[Thumbnail] = nameof(Thumbnail),
|
||||
[Logo] = nameof(Logo),
|
||||
[Trailer] = nameof(Trailer)
|
||||
};
|
||||
/// <summary>
|
||||
/// The original image from another server.
|
||||
/// </summary>
|
||||
public string Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A hash to display as placeholder while the image is loading.
|
||||
/// </summary>
|
||||
[MaxLength(32)]
|
||||
public string Blurhash { get; set; }
|
||||
|
||||
[SerializeIgnore]
|
||||
public string Path { private get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The url to retrieve the low quality image.
|
||||
/// </summary>
|
||||
public string Low => $"{Path}?quality=low";
|
||||
|
||||
/// <summary>
|
||||
/// The url to retrieve the medium quality image.
|
||||
/// </summary>
|
||||
public string Medium => $"{Path}?quality=medium";
|
||||
|
||||
/// <summary>
|
||||
/// The url to retrieve the high quality image.
|
||||
/// </summary>
|
||||
public string High => $"{Path}?quality=high";
|
||||
|
||||
public Image(string source, string? blurhash = null)
|
||||
{
|
||||
Source = source;
|
||||
Blurhash = blurhash ?? "00000000000000";
|
||||
}
|
||||
|
||||
public class ImageConvertor : TypeConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
return true;
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
|
||||
{
|
||||
if (value is not string source)
|
||||
return base.ConvertFrom(context, culture, value)!;
|
||||
return new Image(source);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The quality of an image
|
||||
/// </summary>
|
||||
public enum ImageQuality
|
||||
{
|
||||
/// <summary>
|
||||
/// Small
|
||||
/// </summary>
|
||||
Low,
|
||||
|
||||
/// <summary>
|
||||
/// Medium
|
||||
/// </summary>
|
||||
Medium,
|
||||
|
||||
/// <summary>
|
||||
/// Large
|
||||
/// </summary>
|
||||
High,
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A library containing <see cref="Show"/> and <see cref="Collection"/>.
|
||||
/// </summary>
|
||||
public class Library : IResource
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this library.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of paths that this library is responsible for. This is mainly used by the Scan task.
|
||||
/// </summary>
|
||||
public string[] Paths { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of <see cref="Provider"/> used for items in this library.
|
||||
/// </summary>
|
||||
[EditableRelation][LoadableRelation] public ICollection<Provider> Providers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of shows in this library.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Show> Shows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of collections in this library.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Collection> Collections { get; set; }
|
||||
}
|
||||
}
|
152
back/src/Kyoo.Abstractions/Models/Resources/Movie.cs
Normal file
152
back/src/Kyoo.Abstractions/Models/Resources/Movie.cs
Normal file
@ -0,0 +1,152 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Utils;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A series or a movie.
|
||||
/// </summary>
|
||||
public class Movie : IResource, IMetadata, IOnMerge, IThumbnails
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MaxLength(256)]
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of this show.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A catchphrase for this movie.
|
||||
/// </summary>
|
||||
public string? Tagline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of alternative titles of this show.
|
||||
/// </summary>
|
||||
public string[] Aliases { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The path of the movie video file.
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The summary of this show.
|
||||
/// </summary>
|
||||
public string? Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of tags that match this movie.
|
||||
/// </summary>
|
||||
public string[] Tags { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The list of genres (themes) this show has.
|
||||
/// </summary>
|
||||
public Genre[] Genres { get; set; } = Array.Empty<Genre>();
|
||||
|
||||
/// <summary>
|
||||
/// Is this show airing, not aired yet or finished?
|
||||
/// </summary>
|
||||
public Status Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date this movie aired.
|
||||
/// </summary>
|
||||
public DateTime? AirDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Logo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A video of a few minutes that tease the content.
|
||||
/// </summary>
|
||||
public string? Trailer { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the Studio that made this show.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int? StudioID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Studio that made this show.
|
||||
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
||||
/// </summary>
|
||||
[LoadableRelation(nameof(StudioID))][EditableRelation] public Studio? Studio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of people that made this show.
|
||||
/// </summary>
|
||||
[LoadableRelation][EditableRelation] public ICollection<PeopleRole>? People { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of collections that contains this show.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Collection>? Collections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Links to watch this movie.
|
||||
/// </summary>
|
||||
public VideoLinks Links => new()
|
||||
{
|
||||
Direct = $"/video/movie/{Slug}/direct",
|
||||
Hls = $"/video/movie/{Slug}/master.m3u8",
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnMerge(object merged)
|
||||
{
|
||||
if (People != null)
|
||||
{
|
||||
foreach (PeopleRole link in People)
|
||||
link.Movie = this;
|
||||
}
|
||||
}
|
||||
|
||||
public Movie() { }
|
||||
|
||||
[JsonConstructor]
|
||||
public Movie(string name)
|
||||
{
|
||||
Slug = Utility.ToSlug(name);
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,10 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Utils;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
@ -27,9 +30,10 @@ namespace Kyoo.Abstractions.Models
|
||||
public class People : IResource, IMetadata, IThumbnails
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MaxLength(256)]
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -38,14 +42,29 @@ namespace Kyoo.Abstractions.Models
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[EditableRelation][LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Logo { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of roles this person has played in. See <see cref="PeopleRole"/> for more information.
|
||||
/// </summary>
|
||||
[EditableRelation][LoadableRelation] public ICollection<PeopleRole> Roles { get; set; }
|
||||
[EditableRelation][LoadableRelation] public ICollection<PeopleRole>? Roles { get; set; }
|
||||
|
||||
public People() { }
|
||||
|
||||
[JsonConstructor]
|
||||
public People(string name)
|
||||
{
|
||||
Slug = Utility.ToSlug(name);
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,71 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Utils;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A dead class that will be removed later.
|
||||
/// </summary>
|
||||
// TODO: Delete this class
|
||||
public class Provider : IResource, IThumbnails
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this provider.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of libraries that uses this provider.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, default, <see cref="Provider"/>
|
||||
/// </summary>
|
||||
public Provider() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Provider"/> and specify it's <see cref="Name"/>.
|
||||
/// The <see cref="Slug"/> is automatically calculated from it's name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of this provider.</param>
|
||||
/// <param name="logo">The logo of this provider.</param>
|
||||
public Provider(string name, string logo)
|
||||
{
|
||||
Slug = Utility.ToSlug(name);
|
||||
Name = name;
|
||||
Images = new Dictionary<int, string>
|
||||
{
|
||||
[Models.Images.Logo] = logo
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
@ -31,16 +32,17 @@ namespace Kyoo.Abstractions.Models
|
||||
public class Season : IResource, IMetadata, IThumbnails
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Computed]
|
||||
[MaxLength(256)]
|
||||
public string Slug
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ShowSlug == null && Show == null)
|
||||
return $"{ShowID}-s{SeasonNumber}";
|
||||
return $"{ShowId}-s{SeasonNumber}";
|
||||
return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}";
|
||||
}
|
||||
|
||||
@ -48,7 +50,7 @@ namespace Kyoo.Abstractions.Models
|
||||
[NotNull]
|
||||
private set
|
||||
{
|
||||
Match match = Regex.Match(value ?? string.Empty, @"(?<show>.+)-s(?<season>\d+)");
|
||||
Match match = Regex.Match(value, @"(?<show>.+)-s(?<season>\d+)");
|
||||
|
||||
if (!match.Success)
|
||||
throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}");
|
||||
@ -60,18 +62,18 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// The slug of the Show that contain this episode. If this is not set, this season is ill-formed.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public string ShowSlug { private get; set; }
|
||||
[SerializeIgnore] public string? ShowSlug { private get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the Show containing this season.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int ShowID { get; set; }
|
||||
[SerializeIgnore] public int ShowId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The show that contains this season.
|
||||
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
||||
/// </summary>
|
||||
[LoadableRelation(nameof(ShowID))] public Show Show { get; set; }
|
||||
[LoadableRelation(nameof(ShowId))] public Show? Show { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of this season. This can be set to 0 to indicate specials.
|
||||
@ -81,12 +83,12 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// The title of this season.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A quick overview of this season.
|
||||
/// </summary>
|
||||
public string Overview { get; set; }
|
||||
public string? Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The starting air date of this season.
|
||||
@ -99,14 +101,20 @@ namespace Kyoo.Abstractions.Models
|
||||
public DateTime? EndDate { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[EditableRelation][LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Logo { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of episodes that this season contains.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Episode> Episodes { get; set; }
|
||||
[LoadableRelation] public ICollection<Episode>? Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Utils;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
@ -29,43 +32,47 @@ namespace Kyoo.Abstractions.Models
|
||||
public class Show : IResource, IMetadata, IOnMerge, IThumbnails
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MaxLength(256)]
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of this show.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A catchphrase for this show.
|
||||
/// </summary>
|
||||
public string? Tagline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of alternative titles of this show.
|
||||
/// </summary>
|
||||
[EditableRelation] public string[] Aliases { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path of the root directory of this show.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public string Path { get; set; }
|
||||
public List<string> Aliases { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The summary of this show.
|
||||
/// </summary>
|
||||
public string Overview { get; set; }
|
||||
public string? Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of tags that match this movie.
|
||||
/// </summary>
|
||||
public List<string> Tags { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of genres (themes) this show has.
|
||||
/// </summary>
|
||||
public List<Genre> Genres { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Is this show airing, not aired yet or finished?
|
||||
/// </summary>
|
||||
public Status Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An URL to a trailer.
|
||||
/// </summary>
|
||||
/// TODO for now, this is set to a youtube url. It should be cached and converted to a local file.
|
||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||
public string TrailerUrl => Images?.GetValueOrDefault(Models.Images.Trailer);
|
||||
|
||||
/// <summary>
|
||||
/// The date this show started airing. It can be null if this is unknown.
|
||||
/// </summary>
|
||||
@ -73,64 +80,61 @@ namespace Kyoo.Abstractions.Models
|
||||
|
||||
/// <summary>
|
||||
/// The date this show finished airing.
|
||||
/// It must be after the <see cref="StartAir"/> but can be the same (example: for movies).
|
||||
/// It can also be null if this is unknown.
|
||||
/// </summary>
|
||||
public DateTime? EndAir { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if this show represent a movie, false otherwise.
|
||||
/// </summary>
|
||||
public bool IsMovie { get; set; }
|
||||
public Image? Poster { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[EditableRelation][LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
public Image? Thumbnail { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Image? Logo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A video of a few minutes that tease the content.
|
||||
/// </summary>
|
||||
public string? Trailer { get; set; }
|
||||
|
||||
[SerializeIgnore] public DateTime? AirDate => StartAir;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the Studio that made this show.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int? StudioID { get; set; }
|
||||
[SerializeIgnore] public int? StudioId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Studio that made this show.
|
||||
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
||||
/// </summary>
|
||||
[LoadableRelation(nameof(StudioID))][EditableRelation] public Studio Studio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of genres (themes) this show has.
|
||||
/// </summary>
|
||||
[LoadableRelation][EditableRelation] public ICollection<Genre> Genres { get; set; }
|
||||
[LoadableRelation(nameof(StudioId))][EditableRelation] public Studio? Studio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of people that made this show.
|
||||
/// </summary>
|
||||
[LoadableRelation][EditableRelation] public ICollection<PeopleRole> People { get; set; }
|
||||
[LoadableRelation][EditableRelation] public ICollection<PeopleRole>? People { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The different seasons in this show. If this is a movie, this list is always null or empty.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Season> Seasons { get; set; }
|
||||
[LoadableRelation] public ICollection<Season>? Seasons { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of episodes in this show.
|
||||
/// If this is a movie, there will be a unique episode (with the seasonNumber and episodeNumber set to null).
|
||||
/// Having an episode is necessary to store metadata and tracks.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Episode> Episodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of libraries that contains this show.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
|
||||
[LoadableRelation] public ICollection<Episode>? Episodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of collections that contains this show.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Collection> Collections { get; set; }
|
||||
[LoadableRelation] public ICollection<Collection>? Collections { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnMerge(object merged)
|
||||
@ -153,6 +157,15 @@ namespace Kyoo.Abstractions.Models
|
||||
episode.Show = this;
|
||||
}
|
||||
}
|
||||
|
||||
public Show() { }
|
||||
|
||||
[JsonConstructor]
|
||||
public Show(string name)
|
||||
{
|
||||
Slug = Utility.ToSlug(name);
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -17,8 +17,10 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Utils;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
@ -28,9 +30,10 @@ namespace Kyoo.Abstractions.Models
|
||||
public class Studio : IResource, IMetadata
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MaxLength(256)]
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -41,10 +44,15 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// The list of shows that are made by this studio.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Show> Shows { get; set; }
|
||||
[LoadableRelation] public ICollection<Show>? Shows { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of movies that are made by this studio.
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Movie>? Movies { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[EditableRelation][LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty, <see cref="Studio"/>.
|
||||
@ -55,6 +63,7 @@ namespace Kyoo.Abstractions.Models
|
||||
/// Create a new <see cref="Studio"/> with a specific name, the slug is calculated automatically.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the studio.</param>
|
||||
[JsonConstructor]
|
||||
public Studio(string name)
|
||||
{
|
||||
Slug = Utility.ToSlug(name);
|
||||
|
@ -16,20 +16,25 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Utils;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A single user of the app.
|
||||
/// </summary>
|
||||
public class User : IResource, IThumbnails
|
||||
public class User : IResource
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MaxLength(256)]
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -51,27 +56,32 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// The list of permissions of the user. The format of this is implementation dependent.
|
||||
/// </summary>
|
||||
public string[] Permissions { get; set; }
|
||||
public string[] Permissions { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Arbitrary extra data that can be used by specific authentication implementations.
|
||||
/// A logo is a small image representing the resource.
|
||||
/// </summary>
|
||||
[SerializeIgnore]
|
||||
public Dictionary<string, string> ExtraData { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
public Image? Logo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of shows the user has finished.
|
||||
/// </summary>
|
||||
[SerializeIgnore]
|
||||
public ICollection<Show> Watched { get; set; }
|
||||
public ICollection<Show>? Watched { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of episodes the user is watching (stopped in progress or the next episode of the show)
|
||||
/// </summary>
|
||||
[SerializeIgnore]
|
||||
public ICollection<WatchedEpisode> CurrentlyWatching { get; set; }
|
||||
public ICollection<WatchedEpisode>? CurrentlyWatching { get; set; }
|
||||
|
||||
public User() { }
|
||||
|
||||
[JsonConstructor]
|
||||
public User(string username)
|
||||
{
|
||||
Slug = Utility.ToSlug(username);
|
||||
Username = username;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// The <see cref="Episode"/> started.
|
||||
/// </summary>
|
||||
public Episode Episode { get; set; }
|
||||
public Episode? Episode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Where the player has stopped watching the episode (between 0 and 100).
|
||||
|
@ -35,6 +35,16 @@ namespace Kyoo.Abstractions.Models
|
||||
/// </summary>
|
||||
public ICollection<Collection> Collections { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The items that matched the search.
|
||||
/// </summary>
|
||||
public ICollection<ILibraryItem> Items { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The movies that matched the search.
|
||||
/// </summary>
|
||||
public ICollection<Movie> Movies { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The shows that matched the search.
|
||||
/// </summary>
|
||||
@ -50,11 +60,6 @@ namespace Kyoo.Abstractions.Models
|
||||
/// </summary>
|
||||
public ICollection<People> People { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The genres that matched the search.
|
||||
/// </summary>
|
||||
public ICollection<Genre> Genres { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The studios that matched the search.
|
||||
/// </summary>
|
||||
|
@ -23,7 +23,6 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Utils
|
||||
{
|
||||
@ -43,7 +42,7 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
/// <summary>
|
||||
/// The slug of the resource or null if the id is specified.
|
||||
/// </summary>
|
||||
private readonly string _slug;
|
||||
private readonly string? _slug;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Identifier"/> for the given id.
|
||||
@ -58,10 +57,8 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
/// Create a new <see cref="Identifier"/> for the given slug.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource.</param>
|
||||
public Identifier([NotNull] string slug)
|
||||
public Identifier(string slug)
|
||||
{
|
||||
if (slug == null)
|
||||
throw new ArgumentNullException(nameof(slug));
|
||||
_slug = slug;
|
||||
}
|
||||
|
||||
@ -87,7 +84,7 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
{
|
||||
return _id.HasValue
|
||||
? idFunc(_id.Value)
|
||||
: slugFunc(_slug);
|
||||
: slugFunc(_slug!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -139,7 +136,7 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
public bool IsSame(IResource resource)
|
||||
{
|
||||
return Match(
|
||||
id => resource.ID == id,
|
||||
id => resource.Id == id,
|
||||
slug => resource.Slug == slug
|
||||
);
|
||||
}
|
||||
@ -155,7 +152,7 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
where T : IResource
|
||||
{
|
||||
return _id.HasValue
|
||||
? x => x.ID == _id.Value
|
||||
? x => x.Id == _id.Value
|
||||
: x => x.Slug == _slug;
|
||||
}
|
||||
|
||||
@ -174,7 +171,7 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
.Where(x => x.Name == nameof(Enumerable.Any))
|
||||
.FirstOrDefault(x => x.GetParameters().Length == 2)!
|
||||
.MakeGenericMethod(typeof(T2));
|
||||
MethodCallExpression call = Expression.Call(null, method!, listGetter.Body, IsSame<T2>());
|
||||
MethodCallExpression call = Expression.Call(null, method, listGetter.Body, IsSame<T2>());
|
||||
return Expression.Lambda<Func<T, bool>>(call, listGetter.Parameters);
|
||||
}
|
||||
|
||||
@ -183,7 +180,7 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
{
|
||||
return _id.HasValue
|
||||
? _id.Value.ToString()
|
||||
: _slug;
|
||||
: _slug!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -192,7 +189,7 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
public class IdentifierConvertor : TypeConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(int) || sourceType == typeof(string))
|
||||
return true;
|
||||
@ -200,12 +197,12 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
|
||||
{
|
||||
if (value is int id)
|
||||
return new Identifier(id);
|
||||
if (value is not string slug)
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
return base.ConvertFrom(context, culture, value)!;
|
||||
return int.TryParse(slug, out id)
|
||||
? new Identifier(id)
|
||||
: new Identifier(slug);
|
||||
|
@ -31,13 +31,13 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
/// The list of errors that where made in the request.
|
||||
/// </summary>
|
||||
/// <example><c>["InvalidFilter: no field 'startYear' on a collection"]</c></example>
|
||||
[NotNull] public string[] Errors { get; set; }
|
||||
public string[] Errors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="RequestError"/> with one error.
|
||||
/// </summary>
|
||||
/// <param name="error">The error to specify in the response.</param>
|
||||
public RequestError([NotNull] string error)
|
||||
public RequestError(string error)
|
||||
{
|
||||
if (error == null)
|
||||
throw new ArgumentNullException(nameof(error));
|
||||
@ -48,7 +48,7 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
/// Create a new <see cref="RequestError"/> with multiple errors.
|
||||
/// </summary>
|
||||
/// <param name="errors">The errors to specify in the response.</param>
|
||||
public RequestError([NotNull] string[] errors)
|
||||
public RequestError(string[] errors)
|
||||
{
|
||||
if (errors == null || !errors.Any())
|
||||
throw new ArgumentException("Errors must be non null and not empty", nameof(errors));
|
||||
|
@ -33,11 +33,11 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <summary>
|
||||
/// Sort by a specific key
|
||||
/// </summary>
|
||||
/// <param name="key">The sort keys. This members will be used to sort the results.</param>
|
||||
/// <param name="desendant">
|
||||
/// <param name="Key">The sort keys. This members will be used to sort the results.</param>
|
||||
/// <param name="Desendant">
|
||||
/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order.
|
||||
/// </param>
|
||||
public record By(string key, bool desendant = false) : Sort<T>
|
||||
public record By(string Key, bool Desendant = false) : Sort<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Sort by a specific key
|
||||
@ -53,8 +53,8 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <summary>
|
||||
/// Sort by multiple keys.
|
||||
/// </summary>
|
||||
/// <param name="list">The list of keys to sort by.</param>
|
||||
public record Conglomerate(params Sort<T>[] list) : Sort<T>;
|
||||
/// <param name="List">The list of keys to sort by.</param>
|
||||
public record Conglomerate(params Sort<T>[] List) : Sort<T>;
|
||||
|
||||
/// <summary>The default sort method for the given type.</summary>
|
||||
public record Default : Sort<T>;
|
||||
@ -73,7 +73,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
return new Conglomerate(sortBy.Split(',').Select(From).ToArray());
|
||||
|
||||
string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy;
|
||||
string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null;
|
||||
string? order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null;
|
||||
bool desendant = order switch
|
||||
{
|
||||
"desc" => true,
|
||||
@ -81,7 +81,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
null => false,
|
||||
_ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.")
|
||||
};
|
||||
PropertyInfo property = typeof(T).GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
PropertyInfo? property = typeof(T).GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
if (property == null)
|
||||
throw new ArgumentException("The given sort key is not valid.");
|
||||
return new By(property.Name, desendant);
|
||||
|
@ -16,33 +16,21 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Kyoo.Tests.Database
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
namespace PostgreSQL
|
||||
/// <summary>
|
||||
/// The links to see a movie or an episode.
|
||||
/// </summary>
|
||||
public class VideoLinks
|
||||
{
|
||||
[Collection(nameof(Postgresql))]
|
||||
public class GenreTests : AGenreTests
|
||||
{
|
||||
public GenreTests(PostgresFixture postgres, ITestOutputHelper output)
|
||||
: base(new RepositoryActivator(output, postgres)) { }
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The direct link to the unprocessed video (pristine quality).
|
||||
/// </summary>
|
||||
public string Direct { get; set; }
|
||||
|
||||
public abstract class AGenreTests : RepositoryTests<Genre>
|
||||
{
|
||||
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
|
||||
private readonly IGenreRepository _repository;
|
||||
|
||||
protected AGenreTests(RepositoryActivator repositories)
|
||||
: base(repositories)
|
||||
{
|
||||
_repository = Repositories.LibraryManager.GenreRepository;
|
||||
}
|
||||
/// <summary>
|
||||
/// The link to an HLS master playlist containing all qualities available for this video.
|
||||
/// </summary>
|
||||
public string Hls { get; set; }
|
||||
}
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A watch item give information useful for playback.
|
||||
/// Information about tracks and display information that could be used by the player.
|
||||
/// This contains mostly data from an <see cref="Episode"/> with another form.
|
||||
/// </summary>
|
||||
public class WatchItem : CustomTypeDescriptor, IThumbnails, ILink
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the episode associated with this item.
|
||||
/// </summary>
|
||||
public int EpisodeID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The slug of this episode.
|
||||
/// </summary>
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of the show containing this episode.
|
||||
/// </summary>
|
||||
public string ShowTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The slug of the show containing this episode
|
||||
/// </summary>
|
||||
public string ShowSlug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The season in witch this episode is in.
|
||||
/// </summary>
|
||||
public int? SeasonNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of this episode is it's season.
|
||||
/// </summary>
|
||||
public int? EpisodeNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The absolute number of this episode. It's an episode number that is not reset to 1 after a new season.
|
||||
/// </summary>
|
||||
public int? AbsoluteNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of this episode.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The summary of this episode.
|
||||
/// </summary>
|
||||
public string Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The release date of this episode. It can be null if unknown.
|
||||
/// </summary>
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The episode that come before this one if you follow usual watch orders.
|
||||
/// If this is the first episode or this is a movie, it will be null.
|
||||
/// </summary>
|
||||
public Episode PreviousEpisode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The episode that come after this one if you follow usual watch orders.
|
||||
/// If this is the last aired episode or this is a movie, it will be null.
|
||||
/// </summary>
|
||||
public Episode NextEpisode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if this is a movie, <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsMovie { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The transcoder's info for this item. This include subtitles, fonts, chapters...
|
||||
/// </summary>
|
||||
public object Info { get; set; }
|
||||
|
||||
[SerializeIgnore]
|
||||
private string _Type => IsMovie ? "movie" : "episode";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Link => new
|
||||
{
|
||||
Direct = $"/video/{_Type}/{Slug}/direct",
|
||||
Hls = $"/video/{_Type}/{Slug}/master.m3u8",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="WatchItem"/> from an <see cref="Episode"/>.
|
||||
/// </summary>
|
||||
/// <param name="ep">The episode to transform.</param>
|
||||
/// <param name="library">
|
||||
/// A library manager to retrieve the next and previous episode and load the show and tracks of the episode.
|
||||
/// </param>
|
||||
/// <param name="client">A http client to reach the transcoder.</param>
|
||||
/// <returns>A new WatchItem representing the given episode.</returns>
|
||||
public static async Task<WatchItem> FromEpisode(Episode ep, ILibraryManager library, HttpClient client)
|
||||
{
|
||||
await library.Load(ep, x => x.Show);
|
||||
|
||||
return new WatchItem
|
||||
{
|
||||
EpisodeID = ep.ID,
|
||||
Slug = ep.Slug,
|
||||
ShowSlug = ep.Show.Slug,
|
||||
ShowTitle = ep.Show.Title,
|
||||
SeasonNumber = ep.SeasonNumber,
|
||||
EpisodeNumber = ep.EpisodeNumber,
|
||||
AbsoluteNumber = ep.AbsoluteNumber,
|
||||
Title = ep.Title,
|
||||
Overview = ep.Overview,
|
||||
ReleaseDate = ep.ReleaseDate,
|
||||
Images = ep.Show.Images,
|
||||
PreviousEpisode = ep.Show.IsMovie
|
||||
? null
|
||||
: (await library.GetAll<Episode>(
|
||||
where: x => x.ShowID == ep.ShowID,
|
||||
limit: new Pagination(1, ep.ID, true)
|
||||
)).FirstOrDefault(),
|
||||
NextEpisode = ep.Show.IsMovie
|
||||
? null
|
||||
: (await library.GetAll<Episode>(
|
||||
where: x => x.ShowID == ep.ShowID,
|
||||
limit: new Pagination(1, ep.ID)
|
||||
)).FirstOrDefault(),
|
||||
IsMovie = ep.Show.IsMovie,
|
||||
Info = await _GetInfo(ep, client),
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task<object> _GetInfo(Episode ep, HttpClient client)
|
||||
{
|
||||
HttpResponseMessage ret = await client.GetAsync($"http://transcoder:7666/{(ep.Show.IsMovie ? "movie" : "episode")}/{ep.Slug}/info");
|
||||
ret.EnsureSuccessStatusCode();
|
||||
string content = await ret.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<object>(content);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetClassName()
|
||||
{
|
||||
return nameof(Show);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetComponentName()
|
||||
{
|
||||
return ShowSlug;
|
||||
}
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ namespace Kyoo.Abstractions
|
||||
{
|
||||
return builder.RegisterType<T>()
|
||||
.As<IBaseRepository>()
|
||||
.As(Utility.GetGenericDefinition(typeof(T), typeof(IRepository<>)))
|
||||
.As(Utility.GetGenericDefinition(typeof(T), typeof(IRepository<>))!)
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
@ -59,6 +59,7 @@ namespace Kyoo.Abstractions
|
||||
/// <returns>The initial container.</returns>
|
||||
public static IRegistrationBuilder<T2, ConcreteReflectionActivatorData, SingleRegistrationStyle>
|
||||
RegisterRepository<T, T2>(this ContainerBuilder builder)
|
||||
where T : notnull
|
||||
where T2 : IBaseRepository, T
|
||||
{
|
||||
return builder.RegisterRepository<T2>().As<T>();
|
||||
|
@ -17,9 +17,7 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Kyoo.Utils
|
||||
@ -29,141 +27,16 @@ namespace Kyoo.Utils
|
||||
/// </summary>
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A Select where the index of the item can be used.
|
||||
/// </summary>
|
||||
/// <param name="self">The IEnumerable to map. If self is null, an empty list is returned</param>
|
||||
/// <param name="mapper">The function that will map each items</param>
|
||||
/// <typeparam name="T">The type of items in <paramref name="self"/></typeparam>
|
||||
/// <typeparam name="T2">The type of items in the returned list</typeparam>
|
||||
/// <returns>The list mapped.</returns>
|
||||
/// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception>
|
||||
[LinqTunnel]
|
||||
public static IEnumerable<T2> Map<T, T2>([NotNull] this IEnumerable<T> self,
|
||||
[NotNull] Func<T, int, T2> mapper)
|
||||
{
|
||||
if (self == null)
|
||||
throw new ArgumentNullException(nameof(self));
|
||||
if (mapper == null)
|
||||
throw new ArgumentNullException(nameof(mapper));
|
||||
|
||||
static IEnumerable<T2> Generator(IEnumerable<T> self, Func<T, int, T2> mapper)
|
||||
{
|
||||
using IEnumerator<T> enumerator = self.GetEnumerator();
|
||||
int index = 0;
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
yield return mapper(enumerator.Current, index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return Generator(self, mapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A map where the mapping function is asynchronous.
|
||||
/// Note: <see cref="SelectAsync{T,T2}"/> might interest you.
|
||||
/// </summary>
|
||||
/// <param name="self">The IEnumerable to map.</param>
|
||||
/// <param name="mapper">The asynchronous function that will map each items.</param>
|
||||
/// <typeparam name="T">The type of items in <paramref name="self"/>.</typeparam>
|
||||
/// <typeparam name="T2">The type of items in the returned list.</typeparam>
|
||||
/// <returns>The list mapped as an AsyncEnumerable.</returns>
|
||||
/// <exception cref="ArgumentNullException">The list or the mapper can't be null.</exception>
|
||||
[LinqTunnel]
|
||||
public static IAsyncEnumerable<T2> MapAsync<T, T2>([NotNull] this IEnumerable<T> self,
|
||||
[NotNull] Func<T, int, Task<T2>> mapper)
|
||||
{
|
||||
if (self == null)
|
||||
throw new ArgumentNullException(nameof(self));
|
||||
if (mapper == null)
|
||||
throw new ArgumentNullException(nameof(mapper));
|
||||
|
||||
static async IAsyncEnumerable<T2> Generator(IEnumerable<T> self, Func<T, int, Task<T2>> mapper)
|
||||
{
|
||||
using IEnumerator<T> enumerator = self.GetEnumerator();
|
||||
int index = 0;
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
yield return await mapper(enumerator.Current, index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return Generator(self, mapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An asynchronous version of Select.
|
||||
/// </summary>
|
||||
/// <param name="self">The IEnumerable to map</param>
|
||||
/// <param name="mapper">The asynchronous function that will map each items</param>
|
||||
/// <typeparam name="T">The type of items in <paramref name="self"/></typeparam>
|
||||
/// <typeparam name="T2">The type of items in the returned list</typeparam>
|
||||
/// <returns>The list mapped as an AsyncEnumerable</returns>
|
||||
/// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception>
|
||||
[LinqTunnel]
|
||||
public static IAsyncEnumerable<T2> SelectAsync<T, T2>([NotNull] this IEnumerable<T> self,
|
||||
[NotNull] Func<T, Task<T2>> mapper)
|
||||
{
|
||||
if (self == null)
|
||||
throw new ArgumentNullException(nameof(self));
|
||||
if (mapper == null)
|
||||
throw new ArgumentNullException(nameof(mapper));
|
||||
|
||||
static async IAsyncEnumerable<T2> Generator(IEnumerable<T> self, Func<T, Task<T2>> mapper)
|
||||
{
|
||||
using IEnumerator<T> enumerator = self.GetEnumerator();
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
yield return await mapper(enumerator.Current);
|
||||
}
|
||||
|
||||
return Generator(self, mapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert an AsyncEnumerable to a List by waiting for every item.
|
||||
/// </summary>
|
||||
/// <param name="self">The async list</param>
|
||||
/// <typeparam name="T">The type of items in the async list and in the returned list.</typeparam>
|
||||
/// <returns>A task that will return a simple list</returns>
|
||||
/// <exception cref="ArgumentNullException">The list can't be null</exception>
|
||||
[LinqTunnel]
|
||||
public static Task<List<T>> ToListAsync<T>([NotNull] this IAsyncEnumerable<T> self)
|
||||
{
|
||||
if (self == null)
|
||||
throw new ArgumentNullException(nameof(self));
|
||||
|
||||
static async Task<List<T>> ToList(IAsyncEnumerable<T> self)
|
||||
{
|
||||
List<T> ret = new();
|
||||
await foreach (T i in self)
|
||||
ret.Add(i);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ToList(self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the enumerable is empty, execute an action.
|
||||
/// </summary>
|
||||
/// <param name="self">The enumerable to check</param>
|
||||
/// <param name="action">The action to execute is the list is empty</param>
|
||||
/// <typeparam name="T">The type of items inside the list</typeparam>
|
||||
/// <exception cref="ArgumentNullException">The iterable and the action can't be null.</exception>
|
||||
/// <returns>The iterator proxied, there is no dual iterations.</returns>
|
||||
[LinqTunnel]
|
||||
public static IEnumerable<T> IfEmpty<T>([NotNull] this IEnumerable<T> self, [NotNull] Action action)
|
||||
public static IEnumerable<T> IfEmpty<T>(this IEnumerable<T> self, Action action)
|
||||
{
|
||||
if (self == null)
|
||||
throw new ArgumentNullException(nameof(self));
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
static IEnumerable<T> Generator(IEnumerable<T> self, Action action)
|
||||
{
|
||||
using IEnumerator<T> enumerator = self.GetEnumerator();
|
||||
@ -190,111 +63,12 @@ namespace Kyoo.Utils
|
||||
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
|
||||
/// <param name="action">The action to execute for each arguments</param>
|
||||
/// <typeparam name="T">The type of items in the list</typeparam>
|
||||
public static void ForEach<T>([CanBeNull] this IEnumerable<T> self, Action<T> action)
|
||||
public static void ForEach<T>(this IEnumerable<T>? self, Action<T> action)
|
||||
{
|
||||
if (self == null)
|
||||
return;
|
||||
foreach (T i in self)
|
||||
action(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A foreach used as a function with a little specificity: the list can be null.
|
||||
/// </summary>
|
||||
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
|
||||
/// <param name="action">The action to execute for each arguments</param>
|
||||
public static void ForEach([CanBeNull] this IEnumerable self, Action<object> action)
|
||||
{
|
||||
if (self == null)
|
||||
return;
|
||||
foreach (object i in self)
|
||||
action(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A foreach used as a function with a little specificity: the list can be null.
|
||||
/// </summary>
|
||||
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
|
||||
/// <param name="action">The action to execute for each arguments</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public static async Task ForEachAsync([CanBeNull] this IEnumerable self, Func<object, Task> action)
|
||||
{
|
||||
if (self == null)
|
||||
return;
|
||||
foreach (object i in self)
|
||||
await action(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A foreach used as a function with a little specificity: the list can be null.
|
||||
/// </summary>
|
||||
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
|
||||
/// <param name="action">The asynchronous action to execute for each arguments</param>
|
||||
/// <typeparam name="T">The type of items in the list.</typeparam>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public static async Task ForEachAsync<T>([CanBeNull] this IEnumerable<T> self, Func<T, Task> action)
|
||||
{
|
||||
if (self == null)
|
||||
return;
|
||||
foreach (T i in self)
|
||||
await action(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A foreach used as a function with a little specificity: the list can be null.
|
||||
/// </summary>
|
||||
/// <param name="self">The async list to enumerate. If this is null, the function result in a no-op</param>
|
||||
/// <param name="action">The action to execute for each arguments</param>
|
||||
/// <typeparam name="T">The type of items in the list.</typeparam>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public static async Task ForEachAsync<T>([CanBeNull] this IAsyncEnumerable<T> self, Action<T> action)
|
||||
{
|
||||
if (self == null)
|
||||
return;
|
||||
await foreach (T i in self)
|
||||
action(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split a list in a small chunk of data.
|
||||
/// </summary>
|
||||
/// <param name="list">The list to split</param>
|
||||
/// <param name="countPerList">The number of items in each chunk</param>
|
||||
/// <typeparam name="T">The type of data in the initial list.</typeparam>
|
||||
/// <returns>A list of chunks</returns>
|
||||
[LinqTunnel]
|
||||
public static IEnumerable<List<T>> BatchBy<T>(this List<T> list, int countPerList)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i += countPerList)
|
||||
yield return list.GetRange(i, Math.Min(list.Count - i, countPerList));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split a list in a small chunk of data.
|
||||
/// </summary>
|
||||
/// <param name="list">The list to split</param>
|
||||
/// <param name="countPerList">The number of items in each chunk</param>
|
||||
/// <typeparam name="T">The type of data in the initial list.</typeparam>
|
||||
/// <returns>A list of chunks</returns>
|
||||
[LinqTunnel]
|
||||
public static IEnumerable<T[]> BatchBy<T>(this IEnumerable<T> list, int countPerList)
|
||||
{
|
||||
T[] ret = new T[countPerList];
|
||||
int i = 0;
|
||||
|
||||
using IEnumerator<T> enumerator = list.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
ret[i] = enumerator.Current;
|
||||
i++;
|
||||
if (i < countPerList)
|
||||
continue;
|
||||
i = 0;
|
||||
yield return ret;
|
||||
}
|
||||
|
||||
Array.Resize(ref ret, i);
|
||||
yield return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,10 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Utils
|
||||
@ -33,99 +30,9 @@ namespace Kyoo.Utils
|
||||
/// </summary>
|
||||
public static class Merger
|
||||
{
|
||||
/// <summary>
|
||||
/// Merge two lists, can keep duplicates or remove them.
|
||||
/// </summary>
|
||||
/// <param name="first">The first enumerable to merge</param>
|
||||
/// <param name="second">The second enumerable to merge, if items from this list are equals to one from the first, they are not kept</param>
|
||||
/// <param name="isEqual">Equality function to compare items. If this is null, duplicated elements are kept</param>
|
||||
/// <typeparam name="T">The type of items in the lists to merge.</typeparam>
|
||||
/// <returns>The two list merged as an array</returns>
|
||||
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||
public static T[] MergeLists<T>([CanBeNull] IEnumerable<T> first,
|
||||
[CanBeNull] IEnumerable<T> second,
|
||||
[CanBeNull] Func<T, T, bool> isEqual = null)
|
||||
{
|
||||
if (first == null)
|
||||
return second?.ToArray();
|
||||
if (second == null)
|
||||
return first.ToArray();
|
||||
if (isEqual == null)
|
||||
return first.Concat(second).ToArray();
|
||||
List<T> list = first.ToList();
|
||||
return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept.
|
||||
/// </summary>
|
||||
/// <param name="first">The first dictionary to merge</param>
|
||||
/// <param name="second">The second dictionary to merge</param>
|
||||
/// <typeparam name="T">The type of the keys in dictionaries</typeparam>
|
||||
/// <typeparam name="T2">The type of values in the dictionaries</typeparam>
|
||||
/// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns>
|
||||
/// <seealso cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2},out bool)"/>
|
||||
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||
public static IDictionary<T, T2> MergeDictionaries<T, T2>([CanBeNull] IDictionary<T, T2> first,
|
||||
[CanBeNull] IDictionary<T, T2> second)
|
||||
{
|
||||
return MergeDictionaries(first, second, out bool _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept.
|
||||
/// </summary>
|
||||
/// <param name="first">The first dictionary to merge</param>
|
||||
/// <param name="second">The second dictionary to merge</param>
|
||||
/// <param name="hasChanged">
|
||||
/// <c>true</c> if a new items has been added to the dictionary, <c>false</c> otherwise.
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of the keys in dictionaries</typeparam>
|
||||
/// <typeparam name="T2">The type of values in the dictionaries</typeparam>
|
||||
/// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns>
|
||||
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||
public static IDictionary<T, T2> MergeDictionaries<T, T2>([CanBeNull] IDictionary<T, T2> first,
|
||||
[CanBeNull] IDictionary<T, T2> second,
|
||||
out bool hasChanged)
|
||||
{
|
||||
if (first == null)
|
||||
{
|
||||
hasChanged = true;
|
||||
return second;
|
||||
}
|
||||
|
||||
hasChanged = false;
|
||||
if (second == null)
|
||||
return first;
|
||||
foreach ((T key, T2 value) in second)
|
||||
{
|
||||
bool success = first.TryAdd(key, value);
|
||||
hasChanged |= success;
|
||||
|
||||
if (success || first[key]?.Equals(default) == false || value?.Equals(default) != false)
|
||||
continue;
|
||||
first[key] = value;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge two dictionary, if the same key is found on both dictionary, the values of the second one is kept.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The only difference in this function compared to
|
||||
/// <see cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2}, out bool)"/>
|
||||
/// is the way <paramref name="hasChanged"/> is calculated and the order of the arguments.
|
||||
/// <code>
|
||||
/// MergeDictionaries(first, second);
|
||||
/// </code>
|
||||
/// will do the same thing as
|
||||
/// <code>
|
||||
/// CompleteDictionaries(second, first, out bool _);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
/// <param name="first">The first dictionary to merge</param>
|
||||
/// <param name="second">The second dictionary to merge</param>
|
||||
/// <param name="hasChanged">
|
||||
@ -138,8 +45,8 @@ namespace Kyoo.Utils
|
||||
/// set to those of <paramref name="first"/>.
|
||||
/// </returns>
|
||||
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||
public static IDictionary<T, T2> CompleteDictionaries<T, T2>([CanBeNull] IDictionary<T, T2> first,
|
||||
[CanBeNull] IDictionary<T, T2> second,
|
||||
public static IDictionary<T, T2>? CompleteDictionaries<T, T2>(IDictionary<T, T2>? first,
|
||||
IDictionary<T, T2>? second,
|
||||
out bool hasChanged)
|
||||
{
|
||||
if (first == null)
|
||||
@ -151,49 +58,17 @@ namespace Kyoo.Utils
|
||||
hasChanged = false;
|
||||
if (second == null)
|
||||
return first;
|
||||
hasChanged = second.Any(x => x.Value?.Equals(first[x.Key]) == false);
|
||||
hasChanged = second.Any(x => !first.ContainsKey(x.Key) || x.Value?.Equals(first[x.Key]) == false);
|
||||
foreach ((T key, T2 value) in first)
|
||||
second.TryAdd(key, value);
|
||||
return second;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set every fields of first to those of second. Ignore fields marked with the <see cref="NotMergeableAttribute"/> attribute
|
||||
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>
|
||||
/// </summary>
|
||||
/// <param name="first">The object to assign</param>
|
||||
/// <param name="second">The object containing new values</param>
|
||||
/// <typeparam name="T">Fields of T will be used</typeparam>
|
||||
/// <returns><paramref name="first"/></returns>
|
||||
public static T Assign<T>(T first, T second)
|
||||
{
|
||||
Type type = typeof(T);
|
||||
IEnumerable<PropertyInfo> properties = type.GetProperties()
|
||||
.Where(x => x.CanRead && x.CanWrite
|
||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
object value = property.GetValue(second);
|
||||
property.SetValue(first, value);
|
||||
}
|
||||
|
||||
if (first is IOnMerge merge)
|
||||
merge.OnMerge(second);
|
||||
return first;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set every non-default values of seconds to the corresponding property of second.
|
||||
/// Dictionaries are handled like anonymous objects with a property per key/pair value
|
||||
/// (see
|
||||
/// <see cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2})"/>
|
||||
/// for more details).
|
||||
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does the opposite of <see cref="Merge{T}"/>.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "foo"}
|
||||
/// </example>
|
||||
@ -208,19 +83,16 @@ namespace Kyoo.Utils
|
||||
/// </param>
|
||||
/// <typeparam name="T">Fields of T will be completed</typeparam>
|
||||
/// <returns><paramref name="first"/></returns>
|
||||
/// <exception cref="ArgumentNullException">If first is null</exception>
|
||||
public static T Complete<T>([NotNull] T first,
|
||||
[CanBeNull] T second,
|
||||
[InstantHandle] Func<PropertyInfo, bool> where = null)
|
||||
public static T Complete<T>(T first,
|
||||
T? second,
|
||||
[InstantHandle] Func<PropertyInfo, bool>? where = null)
|
||||
{
|
||||
if (first == null)
|
||||
throw new ArgumentNullException(nameof(first));
|
||||
if (second == null)
|
||||
return first;
|
||||
|
||||
Type type = typeof(T);
|
||||
IEnumerable<PropertyInfo> properties = type.GetProperties()
|
||||
.Where(x => x.CanRead && x.CanWrite
|
||||
.Where(x => x is { CanRead: true, CanWrite: true }
|
||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
|
||||
|
||||
if (where != null)
|
||||
@ -228,17 +100,16 @@ namespace Kyoo.Utils
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
object value = property.GetValue(second);
|
||||
object defaultValue = property.GetCustomAttribute<DefaultValueAttribute>()?.Value
|
||||
?? property.PropertyType.GetClrDefault();
|
||||
object? value = property.GetValue(second);
|
||||
|
||||
if (value?.Equals(defaultValue) != false || value.Equals(property.GetValue(first)))
|
||||
if (value?.Equals(property.GetValue(first)) == true)
|
||||
continue;
|
||||
|
||||
if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>)))
|
||||
{
|
||||
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))
|
||||
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))!
|
||||
.GenericTypeArguments;
|
||||
object[] parameters =
|
||||
object?[] parameters =
|
||||
{
|
||||
property.GetValue(first),
|
||||
value,
|
||||
@ -248,8 +119,8 @@ namespace Kyoo.Utils
|
||||
typeof(Merger),
|
||||
nameof(CompleteDictionaries),
|
||||
dictionaryTypes,
|
||||
parameters);
|
||||
if ((bool)parameters[2])
|
||||
parameters)!;
|
||||
if ((bool)parameters[2]!)
|
||||
property.SetValue(first, newDictionary);
|
||||
}
|
||||
else
|
||||
@ -260,109 +131,5 @@ namespace Kyoo.Utils
|
||||
merge.OnMerge(second);
|
||||
return first;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will set missing values of <paramref name="first"/> to the corresponding values of <paramref name="second"/>.
|
||||
/// Enumerable will be merged (concatenated) and Dictionaries too.
|
||||
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"}
|
||||
/// </example>
|
||||
/// <param name="first">
|
||||
/// The object to complete
|
||||
/// </param>
|
||||
/// <param name="second">
|
||||
/// Missing fields of first will be completed by fields of this item. If second is null, the function no-op.
|
||||
/// </param>
|
||||
/// <param name="where">
|
||||
/// Filter fields that will be merged
|
||||
/// </param>
|
||||
/// <typeparam name="T">Fields of T will be merged</typeparam>
|
||||
/// <returns><paramref name="first"/></returns>
|
||||
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||
public static T Merge<T>([CanBeNull] T first,
|
||||
[CanBeNull] T second,
|
||||
[InstantHandle] Func<PropertyInfo, bool> where = null)
|
||||
{
|
||||
if (first == null)
|
||||
return second;
|
||||
if (second == null)
|
||||
return first;
|
||||
|
||||
Type type = typeof(T);
|
||||
IEnumerable<PropertyInfo> properties = type.GetProperties()
|
||||
.Where(x => x.CanRead && x.CanWrite
|
||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
|
||||
|
||||
if (where != null)
|
||||
properties = properties.Where(where);
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
object oldValue = property.GetValue(first);
|
||||
object newValue = property.GetValue(second);
|
||||
object defaultValue = property.PropertyType.GetClrDefault();
|
||||
|
||||
if (oldValue?.Equals(defaultValue) != false)
|
||||
property.SetValue(first, newValue);
|
||||
else if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>)))
|
||||
{
|
||||
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))
|
||||
.GenericTypeArguments;
|
||||
object[] parameters =
|
||||
{
|
||||
oldValue,
|
||||
newValue,
|
||||
false
|
||||
};
|
||||
object newDictionary = Utility.RunGenericMethod<object>(
|
||||
typeof(Merger),
|
||||
nameof(MergeDictionaries),
|
||||
dictionaryTypes,
|
||||
parameters);
|
||||
if ((bool)parameters[2])
|
||||
property.SetValue(first, newDictionary);
|
||||
}
|
||||
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
|
||||
&& property.PropertyType != typeof(string))
|
||||
{
|
||||
Type enumerableType = Utility.GetGenericDefinition(property.PropertyType, typeof(IEnumerable<>))
|
||||
.GenericTypeArguments
|
||||
.First();
|
||||
Func<IResource, IResource, bool> equalityComparer = enumerableType.IsAssignableTo(typeof(IResource))
|
||||
? (x, y) => x.Slug == y.Slug
|
||||
: null;
|
||||
property.SetValue(first, Utility.RunGenericMethod<object>(
|
||||
typeof(Merger),
|
||||
nameof(MergeLists),
|
||||
enumerableType,
|
||||
oldValue, newValue, equalityComparer));
|
||||
}
|
||||
}
|
||||
|
||||
if (first is IOnMerge merge)
|
||||
merge.OnMerge(second);
|
||||
return first;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set every fields of <paramref name="obj"/> to the default value.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to nullify</param>
|
||||
/// <typeparam name="T">Fields of T will be nullified</typeparam>
|
||||
/// <returns><paramref name="obj"/></returns>
|
||||
public static T Nullify<T>(T obj)
|
||||
{
|
||||
Type type = typeof(T);
|
||||
foreach (PropertyInfo property in type.GetProperties())
|
||||
{
|
||||
if (!property.CanWrite || property.GetCustomAttribute<ComputedAttribute>() != null)
|
||||
continue;
|
||||
property.SetValue(obj, property.PropertyType.GetClrDefault());
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Kyoo.Utils
|
||||
{
|
||||
@ -49,38 +48,5 @@ namespace Kyoo.Utils
|
||||
return x.Result;
|
||||
}, TaskContinuationOptions.ExecuteSynchronously);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map the result of a task to another result.
|
||||
/// </summary>
|
||||
/// <param name="task">The task to map.</param>
|
||||
/// <param name="map">The mapper method, it take the task's result as a parameter and should return the new result.</param>
|
||||
/// <typeparam name="T">The type of returns of the given task</typeparam>
|
||||
/// <typeparam name="TResult">The resulting task after the mapping method</typeparam>
|
||||
/// <returns>A task wrapping the initial task and mapping the initial result.</returns>
|
||||
/// <exception cref="TaskCanceledException">The source task has been canceled.</exception>
|
||||
public static Task<TResult> Map<T, TResult>(this Task<T> task, Func<T, TResult> map)
|
||||
{
|
||||
return task.ContinueWith(x =>
|
||||
{
|
||||
if (x.IsFaulted)
|
||||
x.Exception!.InnerException!.ReThrow();
|
||||
if (x.IsCanceled)
|
||||
throw new TaskCanceledException();
|
||||
return map(x.Result);
|
||||
}, TaskContinuationOptions.ExecuteSynchronously);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A method to return the a default value from a task if the initial task is null.
|
||||
/// </summary>
|
||||
/// <param name="value">The initial task</param>
|
||||
/// <typeparam name="T">The type that the task will return</typeparam>
|
||||
/// <returns>A non-null task.</returns>
|
||||
[NotNull]
|
||||
public static Task<T> DefaultIfNull<T>([CanBeNull] Task<T> value)
|
||||
{
|
||||
return value ?? Task.FromResult<T>(default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,8 +41,6 @@ namespace Kyoo.Utils
|
||||
/// <returns>True if the expression is a member, false otherwise</returns>
|
||||
public static bool IsPropertyExpression(LambdaExpression ex)
|
||||
{
|
||||
if (ex == null)
|
||||
return false;
|
||||
return ex.Body is MemberExpression
|
||||
|| (ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression);
|
||||
}
|
||||
@ -57,44 +55,19 @@ namespace Kyoo.Utils
|
||||
{
|
||||
if (!IsPropertyExpression(ex))
|
||||
throw new ArgumentException($"{ex} is not a property expression.");
|
||||
MemberExpression member = ex.Body.NodeType == ExpressionType.Convert
|
||||
MemberExpression? member = ex.Body.NodeType == ExpressionType.Convert
|
||||
? ((UnaryExpression)ex.Body).Operand as MemberExpression
|
||||
: ex.Body as MemberExpression;
|
||||
return member!.Member.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of a member (property or field)
|
||||
/// </summary>
|
||||
/// <param name="member">The member value</param>
|
||||
/// <param name="obj">The owner of this member</param>
|
||||
/// <returns>The value boxed as an object</returns>
|
||||
/// <exception cref="ArgumentNullException">if <paramref name="member"/> or <paramref name="obj"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The member is not a field or a property.</exception>
|
||||
public static object GetValue([NotNull] this MemberInfo member, [NotNull] object obj)
|
||||
{
|
||||
if (member == null)
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
return member switch
|
||||
{
|
||||
PropertyInfo property => property.GetValue(obj),
|
||||
FieldInfo field => field.GetValue(obj),
|
||||
_ => throw new ArgumentException($"Can't get value of a non property/field (member: {member}).")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slugify a string (Replace spaces by -, Uniformize accents)
|
||||
/// </summary>
|
||||
/// <param name="str">The string to slugify</param>
|
||||
/// <returns>The slug version of the given string</returns>
|
||||
public static string ToSlug([CanBeNull] string str)
|
||||
public static string ToSlug(string str)
|
||||
{
|
||||
if (str == null)
|
||||
return null;
|
||||
|
||||
str = str.ToLowerInvariant();
|
||||
|
||||
string normalizedString = str.Normalize(NormalizationForm.FormD);
|
||||
@ -114,59 +87,25 @@ namespace Kyoo.Utils
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the default value of a type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to get the default value</param>
|
||||
/// <returns>The default value of the given type.</returns>
|
||||
public static object GetClrDefault(this Type type)
|
||||
{
|
||||
return type.IsValueType
|
||||
? Activator.CreateInstance(type)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return every <see cref="Type"/> in the inheritance tree of the parameter (interfaces are not returned)
|
||||
/// </summary>
|
||||
/// <param name="type">The starting type</param>
|
||||
/// <param name="self">The starting type</param>
|
||||
/// <returns>A list of types</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="type"/> can't be null</exception>
|
||||
public static IEnumerable<Type> GetInheritanceTree([NotNull] this Type type)
|
||||
public static IEnumerable<Type> GetInheritanceTree(this Type self)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
for (; type != null; type = type.BaseType)
|
||||
for (Type? type = self; type != null; type = type.BaseType)
|
||||
yield return type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if <paramref name="obj"/> inherit from a generic type <paramref name="genericType"/>.
|
||||
/// </summary>
|
||||
/// <param name="obj">Does this object's type is a <paramref name="genericType"/></param>
|
||||
/// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable<>).</param>
|
||||
/// <returns>True if obj inherit from genericType. False otherwise</returns>
|
||||
/// <exception cref="ArgumentNullException">obj and genericType can't be null</exception>
|
||||
public static bool IsOfGenericType([NotNull] object obj, [NotNull] Type genericType)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
return IsOfGenericType(obj.GetType(), genericType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if <paramref name="type"/> inherit from a generic type <paramref name="genericType"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check</param>
|
||||
/// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable<>).</param>
|
||||
/// <returns>True if obj inherit from genericType. False otherwise</returns>
|
||||
/// <exception cref="ArgumentNullException">obj and genericType can't be null</exception>
|
||||
public static bool IsOfGenericType([NotNull] Type type, [NotNull] Type genericType)
|
||||
public static bool IsOfGenericType(Type type, Type genericType)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
if (genericType == null)
|
||||
throw new ArgumentNullException(nameof(genericType));
|
||||
if (!genericType.IsGenericType)
|
||||
throw new ArgumentException($"{nameof(genericType)} is not a generic type.");
|
||||
|
||||
@ -184,14 +123,9 @@ namespace Kyoo.Utils
|
||||
/// <param name="type">The type to check</param>
|
||||
/// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable<>).</param>
|
||||
/// <returns>The generic definition of genericType that type inherit or null if type does not implement the generic type.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="type"/> and <paramref name="genericType"/> can't be null</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="genericType"/> must be a generic type</exception>
|
||||
public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType)
|
||||
public static Type? GetGenericDefinition(Type type, Type genericType)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
if (genericType == null)
|
||||
throw new ArgumentNullException(nameof(genericType));
|
||||
if (!genericType.IsGenericType)
|
||||
throw new ArgumentException($"{nameof(genericType)} is not a generic type.");
|
||||
|
||||
@ -224,20 +158,12 @@ namespace Kyoo.Utils
|
||||
/// <exception cref="ArgumentException">No method match the given constraints.</exception>
|
||||
/// <returns>The method handle of the matching method.</returns>
|
||||
[PublicAPI]
|
||||
[NotNull]
|
||||
public static MethodInfo GetMethod([NotNull] Type type,
|
||||
public static MethodInfo GetMethod(Type type,
|
||||
BindingFlags flag,
|
||||
string name,
|
||||
[NotNull] Type[] generics,
|
||||
[NotNull] object[] args)
|
||||
Type[] generics,
|
||||
object?[] args)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
if (generics == null)
|
||||
throw new ArgumentNullException(nameof(generics));
|
||||
if (args == null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public)
|
||||
.Where(x => x.Name == name)
|
||||
.Where(x => x.GetGenericArguments().Length == generics.Length)
|
||||
@ -275,7 +201,7 @@ namespace Kyoo.Utils
|
||||
/// Run a generic static method for a runtime <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// To run <see cref="Merger.MergeLists{T}"/> for a List where you don't know the type at compile type,
|
||||
/// To run Merger.MergeLists{T} for a List where you don't know the type at compile type,
|
||||
/// you could do:
|
||||
/// <code lang="C#">
|
||||
/// Utility.RunGenericMethod<object>(
|
||||
@ -294,12 +220,11 @@ namespace Kyoo.Utils
|
||||
/// </typeparam>
|
||||
/// <exception cref="ArgumentException">No method match the given constraints.</exception>
|
||||
/// <returns>The return of the method you wanted to run.</returns>
|
||||
/// <seealso cref="RunGenericMethod{T}(object,string,System.Type,object[])"/>
|
||||
/// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type[],object[])"/>
|
||||
public static T RunGenericMethod<T>(
|
||||
[NotNull] Type owner,
|
||||
[NotNull] string methodName,
|
||||
[NotNull] Type type,
|
||||
public static T? RunGenericMethod<T>(
|
||||
Type owner,
|
||||
string methodName,
|
||||
Type type,
|
||||
params object[] args)
|
||||
{
|
||||
return RunGenericMethod<T>(owner, methodName, new[] { type }, args);
|
||||
@ -311,7 +236,7 @@ namespace Kyoo.Utils
|
||||
/// <see cref="RunGenericMethod{T}(System.Type,string,System.Type,object[])"/>
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// To run <see cref="Merger.MergeLists{T}"/> for a List where you don't know the type at compile type,
|
||||
/// To run Merger.MergeLists{T} for a List where you don't know the type at compile type,
|
||||
/// you could do:
|
||||
/// <code>
|
||||
/// Utility.RunGenericMethod<object>(
|
||||
@ -330,102 +255,17 @@ namespace Kyoo.Utils
|
||||
/// </typeparam>
|
||||
/// <exception cref="ArgumentException">No method match the given constraints.</exception>
|
||||
/// <returns>The return of the method you wanted to run.</returns>
|
||||
/// <seealso cref="RunGenericMethod{T}(object,string,System.Type[],object[])"/>
|
||||
/// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type,object[])"/>
|
||||
[PublicAPI]
|
||||
public static T RunGenericMethod<T>(
|
||||
[NotNull] Type owner,
|
||||
[NotNull] string methodName,
|
||||
[NotNull] Type[] types,
|
||||
params object[] args)
|
||||
public static T? RunGenericMethod<T>(
|
||||
Type owner,
|
||||
string methodName,
|
||||
Type[] types,
|
||||
params object?[] args)
|
||||
{
|
||||
if (owner == null)
|
||||
throw new ArgumentNullException(nameof(owner));
|
||||
if (methodName == null)
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
if (types == null)
|
||||
throw new ArgumentNullException(nameof(types));
|
||||
if (types.Length < 1)
|
||||
throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed.");
|
||||
MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args);
|
||||
return (T)method.MakeGenericMethod(types).Invoke(null, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a generic method for a runtime <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// To run <see cref="Merger.MergeLists{T}"/> for a List where you don't know the type at compile type,
|
||||
/// you could do:
|
||||
/// <code>
|
||||
/// Utility.RunGenericMethod<object>(
|
||||
/// typeof(Utility),
|
||||
/// nameof(MergeLists),
|
||||
/// enumerableType,
|
||||
/// oldValue, newValue, equalityComparer)
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="instance">The <c>this</c> of the method to run.</param>
|
||||
/// <param name="methodName">The name of the method. You should use the <c>nameof</c> keyword.</param>
|
||||
/// <param name="type">The generic type to run the method with.</param>
|
||||
/// <param name="args">The list of arguments of the method</param>
|
||||
/// <typeparam name="T">
|
||||
/// The return type of the method. You can put <see cref="object"/> for an unknown one.
|
||||
/// </typeparam>
|
||||
/// <exception cref="ArgumentException">No method match the given constraints.</exception>
|
||||
/// <returns>The return of the method you wanted to run.</returns>
|
||||
/// <seealso cref="RunGenericMethod{T}(object,string,System.Type,object[])"/>
|
||||
/// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type[],object[])"/>
|
||||
public static T RunGenericMethod<T>(
|
||||
[NotNull] object instance,
|
||||
[NotNull] string methodName,
|
||||
[NotNull] Type type,
|
||||
params object[] args)
|
||||
{
|
||||
return RunGenericMethod<T>(instance, methodName, new[] { type }, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a generic method for a multiple runtime <see cref="Type"/>.
|
||||
/// If your generic method only needs one type, see
|
||||
/// <see cref="RunGenericMethod{T}(object,string,System.Type,object[])"/>
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// To run <see cref="Merger.MergeLists{T}"/> for a List where you don't know the type at compile type,
|
||||
/// you could do:
|
||||
/// <code>
|
||||
/// Utility.RunGenericMethod<object>(
|
||||
/// typeof(Utility),
|
||||
/// nameof(MergeLists),
|
||||
/// enumerableType,
|
||||
/// oldValue, newValue, equalityComparer)
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="instance">The <c>this</c> of the method to run.</param>
|
||||
/// <param name="methodName">The name of the method. You should use the <c>nameof</c> keyword.</param>
|
||||
/// <param name="types">The list of generic types to run the method with.</param>
|
||||
/// <param name="args">The list of arguments of the method</param>
|
||||
/// <typeparam name="T">
|
||||
/// The return type of the method. You can put <see cref="object"/> for an unknown one.
|
||||
/// </typeparam>
|
||||
/// <exception cref="ArgumentException">No method match the given constraints.</exception>
|
||||
/// <returns>The return of the method you wanted to run.</returns>
|
||||
/// <seealso cref="RunGenericMethod{T}(object,string,System.Type[],object[])"/>
|
||||
/// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type,object[])"/>
|
||||
public static T RunGenericMethod<T>(
|
||||
[NotNull] object instance,
|
||||
[NotNull] string methodName,
|
||||
[NotNull] Type[] types,
|
||||
params object[] args)
|
||||
{
|
||||
if (instance == null)
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
if (methodName == null)
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
if (types == null || types.Length == 0)
|
||||
throw new ArgumentNullException(nameof(types));
|
||||
MethodInfo method = GetMethod(instance.GetType(), BindingFlags.Instance, methodName, types, args);
|
||||
return (T)method.MakeGenericMethod(types).Invoke(instance, args.ToArray());
|
||||
return (T?)method.MakeGenericMethod(types).Invoke(null, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -446,25 +286,11 @@ namespace Kyoo.Utils
|
||||
/// </summary>
|
||||
/// <param name="ex">The exception to rethrow.</param>
|
||||
[System.Diagnostics.CodeAnalysis.DoesNotReturn]
|
||||
public static void ReThrow([NotNull] this Exception ex)
|
||||
public static void ReThrow(this Exception ex)
|
||||
{
|
||||
if (ex == null)
|
||||
throw new ArgumentNullException(nameof(ex));
|
||||
ExceptionDispatchInfo.Capture(ex).Throw();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a friendly type name (supporting generics)
|
||||
/// For example a list of string will be displayed as List<string> and not as List`1.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to use</param>
|
||||
/// <returns>The friendly name of the type</returns>
|
||||
public static string FriendlyName(this Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
return type.Name;
|
||||
string generics = string.Join(", ", type.GetGenericArguments().Select(x => x.FriendlyName()));
|
||||
return $"{type.Name[..type.Name.IndexOf('`')]}<{generics}>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ namespace Kyoo.Authentication
|
||||
/// <inheritdoc />
|
||||
public IFilterMetadata Create(PartialPermissionAttribute attribute)
|
||||
{
|
||||
return new PermissionValidatorFilter((object)attribute.Type ?? attribute.Kind, attribute.Group, _options);
|
||||
return new PermissionValidatorFilter(((object?)attribute.Type ?? attribute.Kind)!, attribute.Group, _options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -61,7 +61,7 @@ namespace Kyoo.Authentication
|
||||
: string.Empty;
|
||||
List<Claim> claims = new()
|
||||
{
|
||||
new Claim(Claims.Id, user.ID.ToString(CultureInfo.InvariantCulture)),
|
||||
new Claim(Claims.Id, user.Id.ToString(CultureInfo.InvariantCulture)),
|
||||
new Claim(Claims.Name, user.Username),
|
||||
new Claim(Claims.Permissions, permissions),
|
||||
new Claim(Claims.Type, "access")
|
||||
@ -85,7 +85,7 @@ namespace Kyoo.Authentication
|
||||
signingCredentials: credential,
|
||||
claims: new[]
|
||||
{
|
||||
new Claim(Claims.Id, user.ID.ToString(CultureInfo.InvariantCulture)),
|
||||
new Claim(Claims.Id, user.Id.ToString(CultureInfo.InvariantCulture)),
|
||||
new Claim(Claims.Guid, Guid.NewGuid().ToString()),
|
||||
new Claim(Claims.Type, "refresh")
|
||||
},
|
||||
|
@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Utils;
|
||||
@ -72,7 +71,6 @@ namespace Kyoo.Authentication.Models.DTO
|
||||
Username = Username,
|
||||
Password = BCryptNet.HashPassword(Password),
|
||||
Email = Email,
|
||||
ExtraData = new Dictionary<string, string>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Authentication.Models;
|
||||
using Kyoo.Authentication.Models.DTO;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
@ -228,8 +229,8 @@ namespace Kyoo.Authentication.Views
|
||||
return Unauthorized(new RequestError("User not authenticated or token invalid."));
|
||||
try
|
||||
{
|
||||
user.ID = userID;
|
||||
return await _users.Edit(user, true);
|
||||
user.Id = userID;
|
||||
return await _users.Edit(user);
|
||||
}
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
@ -252,14 +253,15 @@ namespace Kyoo.Authentication.Views
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
|
||||
public async Task<ActionResult<User>> PatchMe(User user)
|
||||
public async Task<ActionResult<User>> PatchMe(PartialResource user)
|
||||
{
|
||||
if (!int.TryParse(User.FindFirstValue(Claims.Id), out int userID))
|
||||
return Unauthorized(new RequestError("User not authenticated or token invalid."));
|
||||
try
|
||||
{
|
||||
user.ID = userID;
|
||||
return await _users.Edit(user, false);
|
||||
if (user.Id.HasValue && user.Id != userID)
|
||||
throw new ArgumentException("Can't edit your user id.");
|
||||
return await _users.Patch(userID, TryUpdateModelAsync);
|
||||
}
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
|
@ -39,15 +39,15 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
private readonly IBaseRepository[] _repositories;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILibraryRepository LibraryRepository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILibraryItemRepository LibraryItemRepository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollectionRepository CollectionRepository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMovieRepository MovieRepository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IShowRepository ShowRepository { get; }
|
||||
|
||||
@ -63,12 +63,6 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc />
|
||||
public IStudioRepository StudioRepository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IGenreRepository GenreRepository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IProviderRepository ProviderRepository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUserRepository UserRepository { get; }
|
||||
|
||||
@ -80,16 +74,14 @@ namespace Kyoo.Core.Controllers
|
||||
public LibraryManager(IEnumerable<IBaseRepository> repositories)
|
||||
{
|
||||
_repositories = repositories.ToArray();
|
||||
LibraryRepository = GetRepository<Library>() as ILibraryRepository;
|
||||
LibraryItemRepository = GetRepository<LibraryItem>() as ILibraryItemRepository;
|
||||
LibraryItemRepository = GetRepository<ILibraryItem>() as ILibraryItemRepository;
|
||||
CollectionRepository = GetRepository<Collection>() as ICollectionRepository;
|
||||
MovieRepository = GetRepository<Movie>() as IMovieRepository;
|
||||
ShowRepository = GetRepository<Show>() as IShowRepository;
|
||||
SeasonRepository = GetRepository<Season>() as ISeasonRepository;
|
||||
EpisodeRepository = GetRepository<Episode>() as IEpisodeRepository;
|
||||
PeopleRepository = GetRepository<People>() as IPeopleRepository;
|
||||
StudioRepository = GetRepository<Studio>() as IStudioRepository;
|
||||
GenreRepository = GetRepository<Genre>() as IGenreRepository;
|
||||
ProviderRepository = GetRepository<Provider>() as IProviderRepository;
|
||||
UserRepository = GetRepository<User>() as IUserRepository;
|
||||
}
|
||||
|
||||
@ -217,8 +209,6 @@ namespace Kyoo.Core.Controllers
|
||||
where T : class, IResource
|
||||
where T2 : class, IResource
|
||||
{
|
||||
if (member == null)
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
return Load(obj, Utility.GetPropertyName(member), force);
|
||||
}
|
||||
|
||||
@ -227,8 +217,6 @@ namespace Kyoo.Core.Controllers
|
||||
where T : class, IResource
|
||||
where T2 : class
|
||||
{
|
||||
if (member == null)
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
return Load(obj, Utility.GetPropertyName(member), force);
|
||||
}
|
||||
|
||||
@ -259,166 +247,119 @@ namespace Kyoo.Core.Controllers
|
||||
|
||||
return (obj, member: memberName) switch
|
||||
{
|
||||
(Library l, nameof(Library.Providers)) => ProviderRepository
|
||||
.GetAll(x => x.Libraries.Any(y => y.ID == obj.ID))
|
||||
.Then(x => l.Providers = x),
|
||||
|
||||
(Library l, nameof(Library.Shows)) => ShowRepository
|
||||
.GetAll(x => x.Libraries.Any(y => y.ID == obj.ID))
|
||||
.Then(x => l.Shows = x),
|
||||
|
||||
(Library l, nameof(Library.Collections)) => CollectionRepository
|
||||
.GetAll(x => x.Libraries.Any(y => y.ID == obj.ID))
|
||||
.Then(x => l.Collections = x),
|
||||
|
||||
|
||||
(Collection c, nameof(Collection.ExternalIDs)) => _SetRelation(c,
|
||||
ProviderRepository.GetMetadataID<Collection>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(Collection c, nameof(Collection.Shows)) => ShowRepository
|
||||
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
|
||||
.GetAll(x => x.Collections.Any(y => y.Id == obj.Id))
|
||||
.Then(x => c.Shows = x),
|
||||
|
||||
(Collection c, nameof(Collection.Libraries)) => LibraryRepository
|
||||
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
|
||||
.Then(x => c.Libraries = x),
|
||||
(Collection c, nameof(Collection.Movies)) => MovieRepository
|
||||
.GetAll(x => x.Collections.Any(y => y.Id == obj.Id))
|
||||
.Then(x => c.Movies = x),
|
||||
|
||||
|
||||
(Show s, nameof(Show.ExternalIDs)) => _SetRelation(s,
|
||||
ProviderRepository.GetMetadataID<Show>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
(Movie m, nameof(Movie.People)) => PeopleRepository
|
||||
.GetFromShow(obj.Id)
|
||||
.Then(x => m.People = x),
|
||||
|
||||
(Movie m, nameof(Movie.Collections)) => CollectionRepository
|
||||
.GetAll(x => x.Movies.Any(y => y.Id == obj.Id))
|
||||
.Then(x => m.Collections = x),
|
||||
|
||||
(Movie m, nameof(Movie.Studio)) => StudioRepository
|
||||
.GetOrDefault(x => x.Movies.Any(y => y.Id == obj.Id))
|
||||
.Then(x =>
|
||||
{
|
||||
m.Studio = x;
|
||||
m.StudioID = x?.Id ?? 0;
|
||||
}),
|
||||
|
||||
(Show s, nameof(Show.Genres)) => GenreRepository
|
||||
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
|
||||
.Then(x => s.Genres = x),
|
||||
|
||||
(Show s, nameof(Show.People)) => PeopleRepository
|
||||
.GetFromShow(obj.ID)
|
||||
.GetFromShow(obj.Id)
|
||||
.Then(x => s.People = x),
|
||||
|
||||
(Show s, nameof(Show.Seasons)) => _SetRelation(s,
|
||||
SeasonRepository.GetAll(x => x.Show.ID == obj.ID),
|
||||
SeasonRepository.GetAll(x => x.Show.Id == obj.Id),
|
||||
(x, y) => x.Seasons = y,
|
||||
(x, y) => { x.Show = y; x.ShowID = y.ID; }),
|
||||
(x, y) => { x.Show = y; x.ShowId = y.Id; }),
|
||||
|
||||
(Show s, nameof(Show.Episodes)) => _SetRelation(s,
|
||||
EpisodeRepository.GetAll(x => x.Show.ID == obj.ID),
|
||||
EpisodeRepository.GetAll(x => x.Show.Id == obj.Id),
|
||||
(x, y) => x.Episodes = y,
|
||||
(x, y) => { x.Show = y; x.ShowID = y.ID; }),
|
||||
|
||||
(Show s, nameof(Show.Libraries)) => LibraryRepository
|
||||
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
|
||||
.Then(x => s.Libraries = x),
|
||||
(x, y) => { x.Show = y; x.ShowId = y.Id; }),
|
||||
|
||||
(Show s, nameof(Show.Collections)) => CollectionRepository
|
||||
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
|
||||
.GetAll(x => x.Shows.Any(y => y.Id == obj.Id))
|
||||
.Then(x => s.Collections = x),
|
||||
|
||||
(Show s, nameof(Show.Studio)) => StudioRepository
|
||||
.GetOrDefault(x => x.Shows.Any(y => y.ID == obj.ID))
|
||||
.GetOrDefault(x => x.Shows.Any(y => y.Id == obj.Id))
|
||||
.Then(x =>
|
||||
{
|
||||
s.Studio = x;
|
||||
s.StudioID = x?.ID ?? 0;
|
||||
s.StudioId = x?.Id ?? 0;
|
||||
}),
|
||||
|
||||
|
||||
(Season s, nameof(Season.ExternalIDs)) => _SetRelation(s,
|
||||
ProviderRepository.GetMetadataID<Season>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(Season s, nameof(Season.Episodes)) => _SetRelation(s,
|
||||
EpisodeRepository.GetAll(x => x.Season.ID == obj.ID),
|
||||
EpisodeRepository.GetAll(x => x.Season.Id == obj.Id),
|
||||
(x, y) => x.Episodes = y,
|
||||
(x, y) => { x.Season = y; x.SeasonID = y.ID; }),
|
||||
(x, y) => { x.Season = y; x.SeasonId = y.Id; }),
|
||||
|
||||
(Season s, nameof(Season.Show)) => ShowRepository
|
||||
.GetOrDefault(x => x.Seasons.Any(y => y.ID == obj.ID))
|
||||
.GetOrDefault(x => x.Seasons.Any(y => y.Id == obj.Id))
|
||||
.Then(x =>
|
||||
{
|
||||
s.Show = x;
|
||||
s.ShowID = x?.ID ?? 0;
|
||||
s.ShowId = x?.Id ?? 0;
|
||||
}),
|
||||
|
||||
|
||||
(Episode e, nameof(Episode.ExternalIDs)) => _SetRelation(e,
|
||||
ProviderRepository.GetMetadataID<Episode>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(Episode e, nameof(Episode.Show)) => ShowRepository
|
||||
.GetOrDefault(x => x.Episodes.Any(y => y.ID == obj.ID))
|
||||
.GetOrDefault(x => x.Episodes.Any(y => y.Id == obj.Id))
|
||||
.Then(x =>
|
||||
{
|
||||
e.Show = x;
|
||||
e.ShowID = x?.ID ?? 0;
|
||||
e.ShowId = x?.Id ?? 0;
|
||||
}),
|
||||
|
||||
(Episode e, nameof(Episode.Season)) => SeasonRepository
|
||||
.GetOrDefault(x => x.Episodes.Any(y => y.ID == e.ID))
|
||||
.GetOrDefault(x => x.Episodes.Any(y => y.Id == e.Id))
|
||||
.Then(x =>
|
||||
{
|
||||
e.Season = x;
|
||||
e.SeasonID = x?.ID ?? 0;
|
||||
e.SeasonId = x?.Id ?? 0;
|
||||
}),
|
||||
|
||||
(Episode e, nameof(Episode.PreviousEpisode)) => EpisodeRepository
|
||||
.GetAll(
|
||||
where: x => x.ShowId == e.ShowId,
|
||||
limit: new Pagination(1, e.Id, true)
|
||||
).Then(x => e.PreviousEpisode = x.FirstOrDefault()),
|
||||
|
||||
(Genre g, nameof(Genre.Shows)) => ShowRepository
|
||||
.GetAll(x => x.Genres.Any(y => y.ID == obj.ID))
|
||||
.Then(x => g.Shows = x),
|
||||
(Episode e, nameof(Episode.NextEpisode)) => EpisodeRepository
|
||||
.GetAll(
|
||||
where: x => x.ShowId == e.ShowId,
|
||||
limit: new Pagination(1, e.Id)
|
||||
).Then(x => e.NextEpisode = x.FirstOrDefault()),
|
||||
|
||||
|
||||
(Studio s, nameof(Studio.Shows)) => ShowRepository
|
||||
.GetAll(x => x.Studio.ID == obj.ID)
|
||||
.GetAll(x => x.Studio.Id == obj.Id)
|
||||
.Then(x => s.Shows = x),
|
||||
|
||||
(Studio s, nameof(Studio.ExternalIDs)) => _SetRelation(s,
|
||||
ProviderRepository.GetMetadataID<Studio>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
(Studio s, nameof(Studio.Movies)) => MovieRepository
|
||||
.GetAll(x => x.Studio.Id == obj.Id)
|
||||
.Then(x => s.Movies = x),
|
||||
|
||||
|
||||
(People p, nameof(People.ExternalIDs)) => _SetRelation(p,
|
||||
ProviderRepository.GetMetadataID<People>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(People p, nameof(People.Roles)) => PeopleRepository
|
||||
.GetFromPeople(obj.ID)
|
||||
.GetFromPeople(obj.Id)
|
||||
.Then(x => p.Roles = x),
|
||||
|
||||
|
||||
(Provider p, nameof(Provider.Libraries)) => LibraryRepository
|
||||
.GetAll(x => x.Providers.Any(y => y.ID == obj.ID))
|
||||
.Then(x => p.Libraries = x),
|
||||
|
||||
|
||||
_ => throw new ArgumentException($"Couldn't find a way to load {memberName} of {obj.Slug}.")
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,
|
||||
Expression<Func<LibraryItem, bool>> where = null,
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default)
|
||||
{
|
||||
return LibraryItemRepository.GetFromLibrary(id, where, sort, limit);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<LibraryItem>> GetItemsFromLibrary(string slug,
|
||||
Expression<Func<LibraryItem, bool>> where = null,
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default)
|
||||
{
|
||||
return LibraryItemRepository.GetFromLibrary(slug, where, sort, limit);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
|
||||
Expression<Func<PeopleRole, bool>> where = null,
|
||||
@ -455,20 +396,6 @@ namespace Kyoo.Core.Controllers
|
||||
return PeopleRepository.GetFromPeople(slug, where, sort, limit);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task AddShowLink(int showID, int? libraryID, int? collectionID)
|
||||
{
|
||||
return ShowRepository.AddShowLink(showID, libraryID, collectionID);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task AddShowLink(Show show, Library library, Collection collection)
|
||||
{
|
||||
if (show == null)
|
||||
throw new ArgumentNullException(nameof(show));
|
||||
return ShowRepository.AddShowLink(show.ID, library?.ID, collection?.ID);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<T>> GetAll<T>(Expression<Func<T, bool>> where = null,
|
||||
Sort<T> sort = default,
|
||||
@ -507,10 +434,17 @@ namespace Kyoo.Core.Controllers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T> Edit<T>(T item, bool resetOld)
|
||||
public Task<T> Edit<T>(T item)
|
||||
where T : class, IResource
|
||||
{
|
||||
return GetRepository<T>().Edit(item, resetOld);
|
||||
return GetRepository<T>().Edit(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T> Patch<T>(int id, Func<T, Task<bool>> patch)
|
||||
where T : class, IResource
|
||||
{
|
||||
return GetRepository<T>().Patch(id, patch);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -37,11 +37,6 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sort<Collection> DefaultSort => new Sort<Collection>.By(nameof(Collection.Name));
|
||||
|
||||
@ -49,22 +44,22 @@ namespace Kyoo.Core.Controllers
|
||||
/// Create a new <see cref="CollectionRepository"/>.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle to use</param>
|
||||
/// /// <param name="providers">A provider repository</param>
|
||||
public CollectionRepository(DatabaseContext database, IProviderRepository providers)
|
||||
: base(database)
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
public CollectionRepository(DatabaseContext database, IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<Collection>> Search(string query)
|
||||
{
|
||||
return await Sort(
|
||||
return (await Sort(
|
||||
_database.Collections
|
||||
.Where(_database.Like<Collection>(x => x.Name + " " + x.Slug, $"%{query}%"))
|
||||
.Take(20)
|
||||
).ToListAsync();
|
||||
).ToListAsync())
|
||||
.Select(SetBackingImageSelf).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -82,41 +77,13 @@ namespace Kyoo.Core.Controllers
|
||||
{
|
||||
await base.Validate(resource);
|
||||
|
||||
if (string.IsNullOrEmpty(resource.Slug))
|
||||
throw new ArgumentException("The collection's slug must be set and not empty");
|
||||
if (string.IsNullOrEmpty(resource.Name))
|
||||
throw new ArgumentException("The collection's name must be set and not empty");
|
||||
|
||||
if (resource.ExternalIDs != null)
|
||||
{
|
||||
foreach (MetadataID id in resource.ExternalIDs)
|
||||
{
|
||||
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||
?? await _providers.CreateIfNotExists(id.Provider);
|
||||
id.ProviderID = id.Provider.ID;
|
||||
}
|
||||
_database.MetadataIds<Collection>().AttachRange(resource.ExternalIDs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld)
|
||||
{
|
||||
await Validate(changed);
|
||||
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||
resource.ExternalIDs = changed.ExternalIDs;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Collection obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
|
@ -39,11 +39,6 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
private readonly IShowRepository _shows;
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -59,20 +54,19 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle to use.</param>
|
||||
/// <param name="shows">A show repository</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
public EpisodeRepository(DatabaseContext database,
|
||||
IShowRepository shows,
|
||||
IProviderRepository providers)
|
||||
: base(database)
|
||||
IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
_shows = shows;
|
||||
|
||||
// Edit episode slugs when the show's slug changes.
|
||||
shows.OnEdited += (show) =>
|
||||
{
|
||||
List<Episode> episodes = _database.Episodes.AsTracking().Where(x => x.ShowID == show.ID).ToList();
|
||||
List<Episode> episodes = _database.Episodes.AsTracking().Where(x => x.ShowId == show.Id).ToList();
|
||||
foreach (Episode ep in episodes)
|
||||
{
|
||||
ep.ShowSlug = show.Slug;
|
||||
@ -85,9 +79,9 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc />
|
||||
public Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID
|
||||
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID
|
||||
&& x.SeasonNumber == seasonNumber
|
||||
&& x.EpisodeNumber == episodeNumber);
|
||||
&& x.EpisodeNumber == episodeNumber).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -95,7 +89,7 @@ namespace Kyoo.Core.Controllers
|
||||
{
|
||||
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
|
||||
&& x.SeasonNumber == seasonNumber
|
||||
&& x.EpisodeNumber == episodeNumber);
|
||||
&& x.EpisodeNumber == episodeNumber).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -119,15 +113,15 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc />
|
||||
public Task<Episode> GetAbsolute(int showID, int absoluteNumber)
|
||||
{
|
||||
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID
|
||||
&& x.AbsoluteNumber == absoluteNumber);
|
||||
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID
|
||||
&& x.AbsoluteNumber == absoluteNumber).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Episode> GetAbsolute(string showSlug, int absoluteNumber)
|
||||
{
|
||||
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
|
||||
&& x.AbsoluteNumber == absoluteNumber);
|
||||
&& x.AbsoluteNumber == absoluteNumber).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -137,80 +131,56 @@ namespace Kyoo.Core.Controllers
|
||||
_database.Episodes
|
||||
.Include(x => x.Show)
|
||||
.Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null)
|
||||
.Where(_database.Like<Episode>(x => x.Title, $"%{query}%"))
|
||||
.Where(_database.Like<Episode>(x => x.Name, $"%{query}%"))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
foreach (Episode ep in ret)
|
||||
{
|
||||
ep.Show.Episodes = null;
|
||||
SetBackingImage(ep);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Episode> Create(Episode obj)
|
||||
{
|
||||
obj.ShowSlug = obj.Show?.Slug ?? _database.Shows.First(x => x.Id == obj.ShowId).Slug;
|
||||
await base.Create(obj);
|
||||
obj.ShowSlug = obj.Show?.Slug ?? _database.Shows.First(x => x.ID == obj.ShowID).Slug;
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
await _database.SaveChangesAsync(() =>
|
||||
obj.SeasonNumber != null && obj.EpisodeNumber != null
|
||||
? Get(obj.ShowID, obj.SeasonNumber.Value, obj.EpisodeNumber.Value)
|
||||
: GetAbsolute(obj.ShowID, obj.AbsoluteNumber.Value));
|
||||
? Get(obj.ShowId, obj.SeasonNumber.Value, obj.EpisodeNumber.Value)
|
||||
: GetAbsolute(obj.ShowId, obj.AbsoluteNumber.Value));
|
||||
OnResourceCreated(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld)
|
||||
{
|
||||
await Validate(changed);
|
||||
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||
resource.ExternalIDs = changed.ExternalIDs;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task Validate(Episode resource)
|
||||
{
|
||||
await base.Validate(resource);
|
||||
if (resource.ShowID <= 0)
|
||||
if (resource.ShowId <= 0)
|
||||
{
|
||||
if (resource.Show == null)
|
||||
{
|
||||
throw new ArgumentException($"Can't store an episode not related " +
|
||||
$"to any show (showID: {resource.ShowID}).");
|
||||
$"to any show (showID: {resource.ShowId}).");
|
||||
}
|
||||
resource.ShowID = resource.Show.ID;
|
||||
}
|
||||
|
||||
if (resource.ExternalIDs != null)
|
||||
{
|
||||
foreach (MetadataID id in resource.ExternalIDs)
|
||||
{
|
||||
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||
?? await _providers.CreateIfNotExists(id.Provider);
|
||||
id.ProviderID = id.Provider.ID;
|
||||
}
|
||||
_database.MetadataIds<Episode>().AttachRange(resource.ExternalIDs);
|
||||
resource.ShowId = resource.Show.Id;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Episode obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
int epCount = await _database.Episodes.Where(x => x.ShowID == obj.ShowID).Take(2).CountAsync();
|
||||
int epCount = await _database.Episodes.Where(x => x.ShowId == obj.ShowId).Take(2).CountAsync();
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted);
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
if (epCount == 1)
|
||||
await _shows.Delete(obj.ShowID);
|
||||
await _shows.Delete(obj.ShowId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,93 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Postgresql;
|
||||
using Kyoo.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A local repository for genres.
|
||||
/// </summary>
|
||||
public class GenreRepository : LocalRepository<Genre>, IGenreRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The database handle
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sort<Genre> DefaultSort => new Sort<Genre>.By(x => x.Slug);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="GenreRepository"/>.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle</param>
|
||||
public GenreRepository(DatabaseContext database)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<Genre>> Search(string query)
|
||||
{
|
||||
return await Sort(
|
||||
_database.Genres
|
||||
.Where(_database.Like<Genre>(x => x.Name, $"%{query}%"))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task Validate(Genre resource)
|
||||
{
|
||||
resource.Slug ??= Utility.ToSlug(resource.Name);
|
||||
await base.Validate(resource);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Genre> Create(Genre obj)
|
||||
{
|
||||
await base.Create(obj);
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
await _database.SaveChangesAsync(() => Get(obj.Slug));
|
||||
OnResourceCreated(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Genre obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,8 +23,8 @@ using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
using Kyoo.Postgresql;
|
||||
using Kyoo.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
@ -32,84 +32,90 @@ namespace Kyoo.Core.Controllers
|
||||
/// <summary>
|
||||
/// A local repository to handle library items.
|
||||
/// </summary>
|
||||
public class LibraryItemRepository : LocalRepository<LibraryItem>, ILibraryItemRepository
|
||||
public class LibraryItemRepository : LocalRepository<ILibraryItem>, ILibraryItemRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The database handle
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A lazy loaded library repository to validate queries (check if a library does exist)
|
||||
/// </summary>
|
||||
private readonly Lazy<ILibraryRepository> _libraries;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sort<LibraryItem> DefaultSort => new Sort<LibraryItem>.By(x => x.Title);
|
||||
protected override Sort<ILibraryItem> DefaultSort => new Sort<ILibraryItem>.By(x => x.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="LibraryItemRepository"/>.
|
||||
/// Create a new <see cref="ILibraryItemRepository"/>.
|
||||
/// </summary>
|
||||
/// <param name="database">The database instance</param>
|
||||
/// <param name="libraries">A lazy loaded library repository</param>
|
||||
public LibraryItemRepository(DatabaseContext database,
|
||||
Lazy<ILibraryRepository> libraries)
|
||||
: base(database)
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
public LibraryItemRepository(DatabaseContext database, IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
{
|
||||
_database = database;
|
||||
_libraries = libraries;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<LibraryItem> GetOrDefault(int id)
|
||||
public override async Task<ILibraryItem> GetOrDefault(int id)
|
||||
{
|
||||
return _database.LibraryItems.FirstOrDefaultAsync(x => x.ID == id);
|
||||
return await _database.LibraryItems.SingleOrDefaultAsync(x => x.Id == id).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<LibraryItem> GetOrDefault(string slug)
|
||||
public override async Task<ILibraryItem> GetOrDefault(string slug)
|
||||
{
|
||||
return _database.LibraryItems.SingleOrDefaultAsync(x => x.Slug == slug);
|
||||
return await _database.LibraryItems.SingleOrDefaultAsync(x => x.Slug == slug).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<ICollection<LibraryItem>> GetAll(Expression<Func<LibraryItem, bool>> where = null,
|
||||
Sort<LibraryItem> sort = default,
|
||||
public override async Task<ILibraryItem> GetOrDefault(Expression<Func<ILibraryItem, bool>> where, Sort<ILibraryItem> sortBy = default)
|
||||
{
|
||||
return await Sort(_database.LibraryItems, sortBy).FirstOrDefaultAsync(where).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<ILibraryItem>> GetAll(Expression<Func<ILibraryItem, bool>> where = null,
|
||||
Sort<ILibraryItem> sort = default,
|
||||
Pagination limit = default)
|
||||
{
|
||||
return ApplyFilters(_database.LibraryItems, where, sort, limit);
|
||||
return (await ApplyFilters(_database.LibraryItems, where, sort, limit))
|
||||
.Select(SetBackingImageSelf).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<int> GetCount(Expression<Func<LibraryItem, bool>> where = null)
|
||||
public override Task<int> GetCount(Expression<Func<ILibraryItem, bool>> where = null)
|
||||
{
|
||||
IQueryable<LibraryItem> query = _database.LibraryItems;
|
||||
IQueryable<ILibraryItem> query = _database.LibraryItems;
|
||||
if (where != null)
|
||||
query = query.Where(where);
|
||||
return query.CountAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<LibraryItem>> Search(string query)
|
||||
public override async Task<ICollection<ILibraryItem>> Search(string query)
|
||||
{
|
||||
return await Sort(
|
||||
return (await Sort(
|
||||
_database.LibraryItems
|
||||
.Where(_database.Like<LibraryItem>(x => x.Title, $"%{query}%"))
|
||||
.Where(_database.Like<LibraryItem>(x => x.Name, $"%{query}%"))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
.ToListAsync())
|
||||
.Select(SetBackingImageSelf)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<LibraryItem> Create(LibraryItem obj)
|
||||
public override Task<ILibraryItem> Create(ILibraryItem obj)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<LibraryItem> CreateIfNotExists(LibraryItem obj)
|
||||
public override Task<ILibraryItem> CreateIfNotExists(ILibraryItem obj)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<LibraryItem> Edit(LibraryItem obj, bool resetOld)
|
||||
public override Task<ILibraryItem> Edit(ILibraryItem edited)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<ILibraryItem> Patch(int id, Func<ILibraryItem, Task<bool>> patch)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -121,58 +127,7 @@ namespace Kyoo.Core.Controllers
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task Delete(LibraryItem obj)
|
||||
public override Task Delete(ILibraryItem obj)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <summary>
|
||||
/// Get a basic queryable for a library with the right mapping from shows and collections.
|
||||
/// Shows contained in a collection are excluded.
|
||||
/// </summary>
|
||||
/// <param name="selector">Only items that are part of a library that match this predicate will be returned.</param>
|
||||
/// <returns>A queryable containing items that are part of a library matching the selector.</returns>
|
||||
private IQueryable<LibraryItem> _LibraryRelatedQuery(Expression<Func<Library, bool>> selector)
|
||||
=> _database.Libraries
|
||||
.Where(selector)
|
||||
.SelectMany(x => x.Shows)
|
||||
.Where(x => !x.Collections.Any())
|
||||
.Select(LibraryItem.FromShow)
|
||||
.Concat(_database.Libraries
|
||||
.Where(selector)
|
||||
.SelectMany(x => x.Collections)
|
||||
.Select(LibraryItem.FromCollection));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ICollection<LibraryItem>> GetFromLibrary(int id,
|
||||
Expression<Func<LibraryItem, bool>> where = null,
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default)
|
||||
{
|
||||
ICollection<LibraryItem> items = await ApplyFilters(
|
||||
_LibraryRelatedQuery(x => x.ID == id),
|
||||
where,
|
||||
sort,
|
||||
limit
|
||||
);
|
||||
if (!items.Any() && await _libraries.Value.GetOrDefault(id) == null)
|
||||
throw new ItemNotFoundException();
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
|
||||
Expression<Func<LibraryItem, bool>> where = null,
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default)
|
||||
{
|
||||
ICollection<LibraryItem> items = await ApplyFilters(
|
||||
_LibraryRelatedQuery(x => x.Slug == slug),
|
||||
where,
|
||||
sort,
|
||||
limit
|
||||
);
|
||||
if (!items.Any() && await _libraries.Value.GetOrDefault(slug) == null)
|
||||
throw new ItemNotFoundException();
|
||||
return items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,129 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Postgresql;
|
||||
using Kyoo.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A local repository to handle libraries.
|
||||
/// </summary>
|
||||
public class LibraryRepository : LocalRepository<Library>, ILibraryRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The database handle
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sort<Library> DefaultSort => new Sort<Library>.By(x => x.ID);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="LibraryRepository"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle</param>
|
||||
/// <param name="providers">The provider repository</param>
|
||||
public LibraryRepository(DatabaseContext database, IProviderRepository providers)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<Library>> Search(string query)
|
||||
{
|
||||
return await Sort(
|
||||
_database.Libraries
|
||||
.Where(_database.Like<Library>(x => x.Name + " " + x.Slug, $"%{query}%"))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Library> Create(Library obj)
|
||||
{
|
||||
await base.Create(obj);
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
await _database.SaveChangesAsync(() => Get(obj.Slug));
|
||||
OnResourceCreated(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task Validate(Library resource)
|
||||
{
|
||||
await base.Validate(resource);
|
||||
|
||||
if (string.IsNullOrEmpty(resource.Slug))
|
||||
throw new ArgumentException("The library's slug must be set and not empty");
|
||||
if (string.IsNullOrEmpty(resource.Name))
|
||||
throw new ArgumentException("The library's name must be set and not empty");
|
||||
if (resource.Paths == null || !resource.Paths.Any())
|
||||
throw new ArgumentException("The library should have a least one path.");
|
||||
|
||||
if (resource.Providers != null)
|
||||
{
|
||||
resource.Providers = await resource.Providers
|
||||
.SelectAsync(async x =>
|
||||
_database.LocalEntity<Provider>(x.Slug)
|
||||
?? await _providers.CreateIfNotExists(x)
|
||||
)
|
||||
.ToListAsync();
|
||||
_database.AttachRange(resource.Providers);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task EditRelations(Library resource, Library changed, bool resetOld)
|
||||
{
|
||||
await Validate(changed);
|
||||
|
||||
if (changed.Providers != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.Providers).LoadAsync();
|
||||
resource.Providers = changed.Providers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Library obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
}
|
||||
}
|
||||
}
|
@ -45,6 +45,11 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
protected DbContext Database { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The thumbnail manager used to store images.
|
||||
/// </summary>
|
||||
private readonly IThumbnailsManager _thumbs;
|
||||
|
||||
/// <summary>
|
||||
/// The default sort order that will be used for this resource's type.
|
||||
/// </summary>
|
||||
@ -54,9 +59,11 @@ namespace Kyoo.Core.Controllers
|
||||
/// 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 <typeparamref name="T"/></param>
|
||||
protected LocalRepository(DbContext database)
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
protected LocalRepository(DbContext database, IThumbnailsManager thumbs)
|
||||
{
|
||||
Database = database;
|
||||
_thumbs = thumbs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -112,7 +119,7 @@ namespace Kyoo.Core.Controllers
|
||||
throw new SwitchExpressionException();
|
||||
}
|
||||
}
|
||||
return _Sort(query, sortBy, false).ThenBy(x => x.ID);
|
||||
return _Sort(query, sortBy, false).ThenBy(x => x.Id);
|
||||
}
|
||||
|
||||
private static Func<Expression, Expression, BinaryExpression> _GetComparisonExpression(
|
||||
@ -144,7 +151,7 @@ namespace Kyoo.Core.Controllers
|
||||
/// <param name="reference">The reference item (the AfterID query)</param>
|
||||
/// <param name="next">True if the following page should be returned, false for the previous.</param>
|
||||
/// <returns>An expression ready to be added to a Where close of a sorted query to handle the AfterID</returns>
|
||||
protected Expression<Func<T, bool>> KeysetPaginatate(
|
||||
protected Expression<Func<T, bool>> KeysetPaginate(
|
||||
Sort<T> sort,
|
||||
T reference,
|
||||
bool next = true)
|
||||
@ -155,20 +162,20 @@ namespace Kyoo.Core.Controllers
|
||||
ParameterExpression x = Expression.Parameter(typeof(T), "x");
|
||||
ConstantExpression referenceC = Expression.Constant(reference, typeof(T));
|
||||
|
||||
IEnumerable<Sort<T>.By> _GetSortsBy(Sort<T> sort)
|
||||
IEnumerable<Sort<T>.By> GetSortsBy(Sort<T> sort)
|
||||
{
|
||||
return sort switch
|
||||
{
|
||||
Sort<T>.Default => _GetSortsBy(DefaultSort),
|
||||
Sort<T>.Default => GetSortsBy(DefaultSort),
|
||||
Sort<T>.By @sortBy => new[] { sortBy },
|
||||
Sort<T>.Conglomerate(var list) => list.SelectMany(_GetSortsBy),
|
||||
Sort<T>.Conglomerate(var list) => list.SelectMany(GetSortsBy),
|
||||
_ => Array.Empty<Sort<T>.By>(),
|
||||
};
|
||||
}
|
||||
|
||||
// Don't forget that every sorts must end with a ID sort (to differenciate equalities).
|
||||
Sort<T>.By id = new(x => x.ID);
|
||||
IEnumerable<Sort<T>.By> sorts = _GetSortsBy(sort).Append(id);
|
||||
// Don't forget that every sorts must end with a ID sort (to differentiate equalities).
|
||||
Sort<T>.By id = new(x => x.Id);
|
||||
IEnumerable<Sort<T>.By> sorts = GetSortsBy(sort).Append(id);
|
||||
|
||||
BinaryExpression filter = null;
|
||||
List<Sort<T>.By> previousSteps = new();
|
||||
@ -180,9 +187,9 @@ namespace Kyoo.Core.Controllers
|
||||
PropertyInfo property = typeof(T).GetProperty(key);
|
||||
|
||||
// Comparing a value with null always return false so we short opt < > comparisons with null.
|
||||
if (property.GetValue(reference) == null)
|
||||
if (property!.GetValue(reference) == null)
|
||||
{
|
||||
previousSteps.Add(new(key, desc));
|
||||
previousSteps.Add(new Sort<T>.By(key, desc));
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -206,7 +213,7 @@ namespace Kyoo.Core.Controllers
|
||||
|
||||
// Comparing a value with null always return false for nulls so we must add nulls to the results manually.
|
||||
// Postgres sorts them after values so we will do the same
|
||||
// We only add this condition if the collumn type is nullable
|
||||
// We only add this condition if the column type is nullable
|
||||
if (Nullable.GetUnderlyingType(property.PropertyType) != null)
|
||||
{
|
||||
BinaryExpression equalNull = Expression.Equal(xkey, Expression.Constant(null));
|
||||
@ -223,7 +230,29 @@ namespace Kyoo.Core.Controllers
|
||||
|
||||
previousSteps.Add(new(key, desc));
|
||||
}
|
||||
return Expression.Lambda<Func<T, bool>>(filter, x);
|
||||
return Expression.Lambda<Func<T, bool>>(filter!, x);
|
||||
}
|
||||
|
||||
protected void SetBackingImage(T obj)
|
||||
{
|
||||
if (obj is not IThumbnails thumbs)
|
||||
return;
|
||||
string type = obj is ILibraryItem item
|
||||
? item.Kind.ToString().ToLowerInvariant()
|
||||
: typeof(T).Name.ToLowerInvariant();
|
||||
|
||||
if (thumbs.Poster != null)
|
||||
thumbs.Poster.Path = $"/{type}/{obj.Slug}/poster";
|
||||
if (thumbs.Thumbnail != null)
|
||||
thumbs.Thumbnail.Path = $"/{type}/{obj.Slug}/thumbnail";
|
||||
if (thumbs.Logo != null)
|
||||
thumbs.Logo.Path = $"/{type}/{obj.Slug}/logo";
|
||||
}
|
||||
|
||||
protected T SetBackingImageSelf(T obj)
|
||||
{
|
||||
SetBackingImage(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -234,7 +263,8 @@ namespace Kyoo.Core.Controllers
|
||||
/// <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);
|
||||
T ret = await Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.Id == id);
|
||||
SetBackingImage(ret);
|
||||
if (ret == null)
|
||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the id {id}");
|
||||
return ret;
|
||||
@ -270,30 +300,31 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc />
|
||||
public virtual Task<T> GetOrDefault(int id)
|
||||
{
|
||||
return Database.Set<T>().FirstOrDefaultAsync(x => x.ID == id);
|
||||
return Database.Set<T>().FirstOrDefaultAsync(x => x.Id == id).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Task<T> GetOrDefault(string slug)
|
||||
{
|
||||
return Database.Set<T>().FirstOrDefaultAsync(x => x.Slug == slug);
|
||||
return Database.Set<T>().FirstOrDefaultAsync(x => x.Slug == slug).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
|
||||
{
|
||||
return Sort(Database.Set<T>(), sortBy).FirstOrDefaultAsync(where);
|
||||
return Sort(Database.Set<T>(), sortBy).FirstOrDefaultAsync(where).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract Task<ICollection<T>> Search(string query);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
|
||||
public virtual async Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
|
||||
Sort<T> sort = default,
|
||||
Pagination limit = default)
|
||||
{
|
||||
return ApplyFilters(Database.Set<T>(), where, sort, limit);
|
||||
return (await ApplyFilters(Database.Set<T>(), where, sort, limit))
|
||||
.Select(SetBackingImageSelf).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -316,7 +347,7 @@ namespace Kyoo.Core.Controllers
|
||||
if (limit?.AfterID != null)
|
||||
{
|
||||
T reference = await Get(limit.AfterID.Value);
|
||||
query = query.Where(KeysetPaginatate(sort, reference, !limit.Reverse));
|
||||
query = query.Where(KeysetPaginate(sort, reference, !limit.Reverse));
|
||||
}
|
||||
if (limit?.Reverse == true)
|
||||
query = query.Reverse();
|
||||
@ -338,9 +369,18 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> Create(T obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
await Validate(obj);
|
||||
if (obj is IThumbnails thumbs)
|
||||
{
|
||||
await _thumbs.DownloadImages(thumbs);
|
||||
if (thumbs.Poster != null)
|
||||
Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Added;
|
||||
if (thumbs.Thumbnail != null)
|
||||
Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Added;
|
||||
if (thumbs.Logo != null)
|
||||
Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Added;
|
||||
}
|
||||
SetBackingImage(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@ -367,9 +407,6 @@ namespace Kyoo.Core.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
T old = await GetOrDefault(obj.Slug);
|
||||
if (old != null)
|
||||
return old;
|
||||
@ -383,23 +420,19 @@ namespace Kyoo.Core.Controllers
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> Edit(T edited, bool resetOld)
|
||||
public virtual async Task<T> Edit(T edited)
|
||||
{
|
||||
if (edited == null)
|
||||
throw new ArgumentNullException(nameof(edited));
|
||||
|
||||
bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled;
|
||||
Database.ChangeTracker.LazyLoadingEnabled = false;
|
||||
try
|
||||
{
|
||||
T old = await GetWithTracking(edited.ID);
|
||||
T old = await GetWithTracking(edited.Id);
|
||||
|
||||
if (resetOld)
|
||||
old = Merger.Nullify(old);
|
||||
Merger.Complete(old, edited, x => x.GetCustomAttribute<LoadableRelationAttribute>() == null);
|
||||
await EditRelations(old, edited, resetOld);
|
||||
await EditRelations(old, edited);
|
||||
await Database.SaveChangesAsync();
|
||||
OnEdited?.Invoke(old);
|
||||
SetBackingImage(old);
|
||||
return old;
|
||||
}
|
||||
finally
|
||||
@ -409,6 +442,30 @@ namespace Kyoo.Core.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> Patch(int id, Func<T, Task<bool>> patch)
|
||||
{
|
||||
bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled;
|
||||
Database.ChangeTracker.LazyLoadingEnabled = false;
|
||||
try
|
||||
{
|
||||
T resource = await GetWithTracking(id);
|
||||
|
||||
if (!await patch(resource))
|
||||
throw new ArgumentException("Could not patch resource");
|
||||
|
||||
await Database.SaveChangesAsync();
|
||||
OnEdited?.Invoke(resource);
|
||||
SetBackingImage(resource);
|
||||
return resource;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Database.ChangeTracker.LazyLoadingEnabled = lazyLoading;
|
||||
Database.ChangeTracker.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An overridable method to edit relation of a resource.
|
||||
/// </summary>
|
||||
@ -419,12 +476,15 @@ namespace Kyoo.Core.Controllers
|
||||
/// The new version of <paramref name="resource"/>.
|
||||
/// This item will be saved on the database and replace <paramref name="resource"/>
|
||||
/// </param>
|
||||
/// <param name="resetOld">
|
||||
/// A boolean to indicate if all values of resource should be discarded or not.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
protected virtual Task EditRelations(T resource, T changed, bool resetOld)
|
||||
protected virtual Task EditRelations(T resource, T changed)
|
||||
{
|
||||
if (resource is IThumbnails thumbs && changed is IThumbnails chng)
|
||||
{
|
||||
Database.Entry(thumbs).Reference(x => x.Poster).IsModified = thumbs.Poster != chng.Poster;
|
||||
Database.Entry(thumbs).Reference(x => x.Thumbnail).IsModified = thumbs.Thumbnail != chng.Thumbnail;
|
||||
Database.Entry(thumbs).Reference(x => x.Logo).IsModified = thumbs.Logo != chng.Logo;
|
||||
}
|
||||
return Validate(resource);
|
||||
}
|
||||
|
||||
|
142
back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs
Normal file
142
back/src/Kyoo.Core/Controllers/Repositories/MovieRepository.cs
Normal file
@ -0,0 +1,142 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Postgresql;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A local repository to handle shows
|
||||
/// </summary>
|
||||
public class MovieRepository : LocalRepository<Movie>, IMovieRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The database handle
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A studio repository to handle creation/validation of related studios.
|
||||
/// </summary>
|
||||
private readonly IStudioRepository _studios;
|
||||
|
||||
/// <summary>
|
||||
/// A people repository to handle creation/validation of related people.
|
||||
/// </summary>
|
||||
private readonly IPeopleRepository _people;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sort<Movie> DefaultSort => new Sort<Movie>.By(x => x.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="MovieRepository"/>.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle to use</param>
|
||||
/// <param name="studios">A studio repository</param>
|
||||
/// <param name="people">A people repository</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
public MovieRepository(DatabaseContext database,
|
||||
IStudioRepository studios,
|
||||
IPeopleRepository people,
|
||||
IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
{
|
||||
_database = database;
|
||||
_studios = studios;
|
||||
_people = people;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<Movie>> Search(string query)
|
||||
{
|
||||
query = $"%{query}%";
|
||||
return (await Sort(
|
||||
_database.Movies
|
||||
.Where(_database.Like<Movie>(x => x.Name + " " + x.Slug, query))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync())
|
||||
.Select(SetBackingImageSelf)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Movie> Create(Movie obj)
|
||||
{
|
||||
await base.Create(obj);
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
await _database.SaveChangesAsync(() => Get(obj.Slug));
|
||||
OnResourceCreated(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task Validate(Movie resource)
|
||||
{
|
||||
await base.Validate(resource);
|
||||
if (resource.Studio != null)
|
||||
{
|
||||
resource.Studio = await _studios.CreateIfNotExists(resource.Studio);
|
||||
resource.StudioID = resource.Studio.Id;
|
||||
}
|
||||
|
||||
if (resource.People != null)
|
||||
{
|
||||
foreach (PeopleRole role in resource.People)
|
||||
{
|
||||
role.People = _database.LocalEntity<People>(role.People.Slug)
|
||||
?? await _people.CreateIfNotExists(role.People);
|
||||
role.PeopleID = role.People.Id;
|
||||
_database.Entry(role).State = EntityState.Added;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task EditRelations(Movie resource, Movie changed)
|
||||
{
|
||||
await Validate(changed);
|
||||
|
||||
if (changed.Studio != null || changed.StudioID == null)
|
||||
{
|
||||
await Database.Entry(resource).Reference(x => x.Studio).LoadAsync();
|
||||
resource.Studio = changed.Studio;
|
||||
}
|
||||
|
||||
if (changed.People != null)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.People).LoadAsync();
|
||||
resource.People = changed.People;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Movie obj)
|
||||
{
|
||||
_database.Remove(obj);
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
}
|
||||
}
|
||||
}
|
@ -39,11 +39,6 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
/// <summary>
|
||||
/// A lazy loaded show repository to validate requests from shows.
|
||||
/// </summary>
|
||||
@ -56,27 +51,28 @@ namespace Kyoo.Core.Controllers
|
||||
/// Create a new <see cref="PeopleRepository"/>
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="shows">A lazy loaded show repository</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
public PeopleRepository(DatabaseContext database,
|
||||
IProviderRepository providers,
|
||||
Lazy<IShowRepository> shows)
|
||||
: base(database)
|
||||
Lazy<IShowRepository> shows,
|
||||
IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
_shows = shows;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<People>> Search(string query)
|
||||
{
|
||||
return await Sort(
|
||||
return (await Sort(
|
||||
_database.People
|
||||
.Where(_database.Like<People>(x => x.Name, $"%{query}%"))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
.ToListAsync())
|
||||
.Select(SetBackingImageSelf)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -94,55 +90,34 @@ namespace Kyoo.Core.Controllers
|
||||
{
|
||||
await base.Validate(resource);
|
||||
|
||||
if (resource.ExternalIDs != null)
|
||||
{
|
||||
foreach (MetadataID id in resource.ExternalIDs)
|
||||
{
|
||||
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||
?? await _providers.CreateIfNotExists(id.Provider);
|
||||
id.ProviderID = id.Provider.ID;
|
||||
}
|
||||
_database.MetadataIds<People>().AttachRange(resource.ExternalIDs);
|
||||
}
|
||||
|
||||
if (resource.Roles != null)
|
||||
{
|
||||
foreach (PeopleRole role in resource.Roles)
|
||||
{
|
||||
role.Show = _database.LocalEntity<Show>(role.Show.Slug)
|
||||
?? await _shows.Value.CreateIfNotExists(role.Show);
|
||||
role.ShowID = role.Show.ID;
|
||||
role.ShowID = role.Show.Id;
|
||||
_database.Entry(role).State = EntityState.Added;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task EditRelations(People resource, People changed, bool resetOld)
|
||||
protected override async Task EditRelations(People resource, People changed)
|
||||
{
|
||||
await Validate(changed);
|
||||
|
||||
if (changed.Roles != null || resetOld)
|
||||
if (changed.Roles != null)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.Roles).LoadAsync();
|
||||
resource.Roles = changed.Roles;
|
||||
}
|
||||
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||
resource.ExternalIDs = changed.ExternalIDs;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(People obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted);
|
||||
obj.Roles.ForEach(x => _database.Entry(x).State = EntityState.Deleted);
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
|
@ -1,98 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Postgresql;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A local repository to handle providers.
|
||||
/// </summary>
|
||||
public class ProviderRepository : LocalRepository<Provider>, IProviderRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The database handle
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ProviderRepository" />.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle</param>
|
||||
public ProviderRepository(DatabaseContext database)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sort<Provider> DefaultSort => new Sort<Provider>.By(x => x.Slug);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<Provider>> Search(string query)
|
||||
{
|
||||
return await Sort(
|
||||
_database.Providers
|
||||
.Where(_database.Like<Provider>(x => x.Name, $"%{query}%"))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Provider> Create(Provider obj)
|
||||
{
|
||||
await base.Create(obj);
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
await _database.SaveChangesAsync(() => Get(obj.Slug));
|
||||
OnResourceCreated(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Provider obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ICollection<MetadataID>> GetMetadataID<T>(Expression<Func<MetadataID, bool>> where = null,
|
||||
Sort<MetadataID> sort = default,
|
||||
Pagination limit = default)
|
||||
where T : class, IMetadata
|
||||
{
|
||||
return await _database.MetadataIds<T>()
|
||||
.Include(y => y.Provider)
|
||||
.Where(where)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
using Kyoo.Postgresql;
|
||||
using Kyoo.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
@ -38,11 +39,6 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Sort<Season> DefaultSort => new Sort<Season>.By(x => x.SeasonNumber);
|
||||
|
||||
@ -51,19 +47,18 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle that will be used</param>
|
||||
/// <param name="shows">A shows repository</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
public SeasonRepository(DatabaseContext database,
|
||||
IShowRepository shows,
|
||||
IProviderRepository providers)
|
||||
: base(database)
|
||||
IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
|
||||
// Edit seasons slugs when the show's slug changes.
|
||||
shows.OnEdited += (show) =>
|
||||
{
|
||||
List<Season> seasons = _database.Seasons.AsTracking().Where(x => x.ShowID == show.ID).ToList();
|
||||
List<Season> seasons = _database.Seasons.AsTracking().Where(x => x.ShowId == show.Id).ToList();
|
||||
foreach (Season season in seasons)
|
||||
{
|
||||
season.ShowSlug = show.Slug;
|
||||
@ -94,35 +89,37 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc/>
|
||||
public Task<Season> GetOrDefault(int showID, int seasonNumber)
|
||||
{
|
||||
return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID
|
||||
&& x.SeasonNumber == seasonNumber);
|
||||
return _database.Seasons.FirstOrDefaultAsync(x => x.ShowId == showID
|
||||
&& x.SeasonNumber == seasonNumber).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<Season> GetOrDefault(string showSlug, int seasonNumber)
|
||||
{
|
||||
return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
|
||||
&& x.SeasonNumber == seasonNumber);
|
||||
&& x.SeasonNumber == seasonNumber).Then(SetBackingImage);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<ICollection<Season>> Search(string query)
|
||||
{
|
||||
return await Sort(
|
||||
return (await Sort(
|
||||
_database.Seasons
|
||||
.Where(_database.Like<Season>(x => x.Title, $"%{query}%"))
|
||||
.Where(_database.Like<Season>(x => x.Name, $"%{query}%"))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
.ToListAsync())
|
||||
.Select(SetBackingImageSelf)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<Season> Create(Season obj)
|
||||
{
|
||||
await base.Create(obj);
|
||||
obj.ShowSlug = _database.Shows.First(x => x.ID == obj.ShowID).Slug;
|
||||
obj.ShowSlug = _database.Shows.First(x => x.Id == obj.ShowId).Slug;
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
await _database.SaveChangesAsync(() => Get(obj.ShowID, obj.SeasonNumber));
|
||||
await _database.SaveChangesAsync(() => Get(obj.ShowId, obj.SeasonNumber));
|
||||
OnResourceCreated(obj);
|
||||
return obj;
|
||||
}
|
||||
@ -131,46 +128,20 @@ namespace Kyoo.Core.Controllers
|
||||
protected override async Task Validate(Season resource)
|
||||
{
|
||||
await base.Validate(resource);
|
||||
if (resource.ShowID <= 0)
|
||||
if (resource.ShowId <= 0)
|
||||
{
|
||||
if (resource.Show == null)
|
||||
{
|
||||
throw new ArgumentException($"Can't store a season not related to any show " +
|
||||
$"(showID: {resource.ShowID}).");
|
||||
$"(showID: {resource.ShowId}).");
|
||||
}
|
||||
resource.ShowID = resource.Show.ID;
|
||||
}
|
||||
|
||||
if (resource.ExternalIDs != null)
|
||||
{
|
||||
foreach (MetadataID id in resource.ExternalIDs)
|
||||
{
|
||||
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||
?? await _providers.CreateIfNotExists(id.Provider);
|
||||
id.ProviderID = id.Provider.ID;
|
||||
}
|
||||
_database.MetadataIds<Season>().AttachRange(resource.ExternalIDs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task EditRelations(Season resource, Season changed, bool resetOld)
|
||||
{
|
||||
await Validate(changed);
|
||||
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||
resource.ExternalIDs = changed.ExternalIDs;
|
||||
resource.ShowId = resource.Show.Id;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task Delete(Season obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Remove(obj);
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
|
@ -47,18 +47,8 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
private readonly IPeopleRepository _people;
|
||||
|
||||
/// <summary>
|
||||
/// A genres repository to handle creation/validation of related genres.
|
||||
/// </summary>
|
||||
private readonly IGenreRepository _genres;
|
||||
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sort<Show> DefaultSort => new Sort<Show>.By(x => x.Title);
|
||||
protected override Sort<Show> DefaultSort => new Sort<Show>.By(x => x.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ShowRepository"/>.
|
||||
@ -66,32 +56,30 @@ namespace Kyoo.Core.Controllers
|
||||
/// <param name="database">The database handle to use</param>
|
||||
/// <param name="studios">A studio repository</param>
|
||||
/// <param name="people">A people repository</param>
|
||||
/// <param name="genres">A genres repository</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
public ShowRepository(DatabaseContext database,
|
||||
IStudioRepository studios,
|
||||
IPeopleRepository people,
|
||||
IGenreRepository genres,
|
||||
IProviderRepository providers)
|
||||
: base(database)
|
||||
IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
{
|
||||
_database = database;
|
||||
_studios = studios;
|
||||
_people = people;
|
||||
_genres = genres;
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<Show>> Search(string query)
|
||||
{
|
||||
query = $"%{query}%";
|
||||
return await Sort(
|
||||
return (await Sort(
|
||||
_database.Shows
|
||||
.Where(_database.Like<Show>(x => x.Title + " " + x.Slug, query))
|
||||
.Where(_database.Like<Show>(x => x.Name + " " + x.Slug, query))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
.ToListAsync())
|
||||
.Select(SetBackingImageSelf)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -107,32 +95,13 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc />
|
||||
protected override async Task Validate(Show resource)
|
||||
{
|
||||
resource.Slug ??= Utility.ToSlug(resource.Title);
|
||||
resource.Slug ??= Utility.ToSlug(resource.Name);
|
||||
|
||||
await base.Validate(resource);
|
||||
if (resource.Studio != null)
|
||||
{
|
||||
resource.Studio = await _studios.CreateIfNotExists(resource.Studio);
|
||||
resource.StudioID = resource.Studio.ID;
|
||||
}
|
||||
|
||||
if (resource.Genres != null)
|
||||
{
|
||||
resource.Genres = await resource.Genres
|
||||
.SelectAsync(x => _genres.CreateIfNotExists(x))
|
||||
.ToListAsync();
|
||||
_database.AttachRange(resource.Genres);
|
||||
}
|
||||
|
||||
if (resource.ExternalIDs != null)
|
||||
{
|
||||
foreach (MetadataID id in resource.ExternalIDs)
|
||||
{
|
||||
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||
?? await _providers.CreateIfNotExists(id.Provider);
|
||||
id.ProviderID = id.Provider.ID;
|
||||
}
|
||||
_database.MetadataIds<Show>().AttachRange(resource.ExternalIDs);
|
||||
resource.StudioId = resource.Studio.Id;
|
||||
}
|
||||
|
||||
if (resource.People != null)
|
||||
@ -141,70 +110,34 @@ namespace Kyoo.Core.Controllers
|
||||
{
|
||||
role.People = _database.LocalEntity<People>(role.People.Slug)
|
||||
?? await _people.CreateIfNotExists(role.People);
|
||||
role.PeopleID = role.People.ID;
|
||||
role.PeopleID = role.People.Id;
|
||||
_database.Entry(role).State = EntityState.Added;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task EditRelations(Show resource, Show changed, bool resetOld)
|
||||
protected override async Task EditRelations(Show resource, Show changed)
|
||||
{
|
||||
await Validate(changed);
|
||||
|
||||
if (changed.Aliases != null || resetOld)
|
||||
resource.Aliases = changed.Aliases;
|
||||
|
||||
if (changed.Studio != null || resetOld)
|
||||
if (changed.Studio != null || changed.StudioId == null)
|
||||
{
|
||||
await Database.Entry(resource).Reference(x => x.Studio).LoadAsync();
|
||||
resource.Studio = changed.Studio;
|
||||
}
|
||||
|
||||
if (changed.Genres != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.Genres).LoadAsync();
|
||||
resource.Genres = changed.Genres;
|
||||
}
|
||||
|
||||
if (changed.People != null || resetOld)
|
||||
if (changed.People != null)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.People).LoadAsync();
|
||||
resource.People = changed.People;
|
||||
}
|
||||
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||
resource.ExternalIDs = changed.ExternalIDs;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AddShowLink(int showID, int? libraryID, int? collectionID)
|
||||
{
|
||||
if (collectionID != null)
|
||||
{
|
||||
await _database.AddLinks<Collection, Show>(collectionID.Value, showID);
|
||||
await _database.SaveIfNoDuplicates();
|
||||
|
||||
if (libraryID != null)
|
||||
{
|
||||
await _database.AddLinks<Library, Collection>(libraryID.Value, collectionID.Value);
|
||||
await _database.SaveIfNoDuplicates();
|
||||
}
|
||||
}
|
||||
if (libraryID != null)
|
||||
{
|
||||
await _database.AddLinks<Library, Show>(libraryID.Value, showID);
|
||||
await _database.SaveIfNoDuplicates();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> GetSlug(int showID)
|
||||
{
|
||||
return _database.Shows.Where(x => x.ID == showID)
|
||||
return _database.Shows.Where(x => x.Id == showID)
|
||||
.Select(x => x.Slug)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -38,11 +37,6 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sort<Studio> DefaultSort => new Sort<Studio>.By(x => x.Name);
|
||||
|
||||
@ -50,23 +44,24 @@ namespace Kyoo.Core.Controllers
|
||||
/// Create a new <see cref="StudioRepository"/>.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
public StudioRepository(DatabaseContext database, IProviderRepository providers)
|
||||
: base(database)
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
public StudioRepository(DatabaseContext database, IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<Studio>> Search(string query)
|
||||
{
|
||||
return await Sort(
|
||||
return (await Sort(
|
||||
_database.Studios
|
||||
.Where(_database.Like<Studio>(x => x.Name, $"%{query}%"))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
.ToListAsync())
|
||||
.Select(SetBackingImageSelf)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -83,38 +78,12 @@ namespace Kyoo.Core.Controllers
|
||||
protected override async Task Validate(Studio resource)
|
||||
{
|
||||
resource.Slug ??= Utility.ToSlug(resource.Name);
|
||||
|
||||
await base.Validate(resource);
|
||||
if (resource.ExternalIDs != null)
|
||||
{
|
||||
foreach (MetadataID id in resource.ExternalIDs)
|
||||
{
|
||||
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||
?? await _providers.CreateIfNotExists(id.Provider);
|
||||
id.ProviderID = id.Provider.ID;
|
||||
}
|
||||
_database.MetadataIds<Studio>().AttachRange(resource.ExternalIDs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task EditRelations(Studio resource, Studio changed, bool resetOld)
|
||||
{
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||
resource.ExternalIDs = changed.ExternalIDs;
|
||||
}
|
||||
|
||||
await base.EditRelations(resource, changed, resetOld);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Studio obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
|
@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -44,8 +43,9 @@ namespace Kyoo.Core.Controllers
|
||||
/// Create a new <see cref="UserRepository"/>
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle to use</param>
|
||||
public UserRepository(DatabaseContext database)
|
||||
: base(database)
|
||||
/// <param name="thumbs">The thumbnail manager used to store images.</param>
|
||||
public UserRepository(DatabaseContext database, IThumbnailsManager thumbs)
|
||||
: base(database, thumbs)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
@ -53,12 +53,14 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<User>> Search(string query)
|
||||
{
|
||||
return await Sort(
|
||||
return (await Sort(
|
||||
_database.Users
|
||||
.Where(_database.Like<User>(x => x.Username, $"%{query}%"))
|
||||
)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
.ToListAsync())
|
||||
.Select(SetBackingImageSelf)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -66,6 +68,8 @@ namespace Kyoo.Core.Controllers
|
||||
{
|
||||
await base.Create(obj);
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
if (obj.Logo != null)
|
||||
_database.Entry(obj).Reference(x => x.Logo).TargetEntry.State = EntityState.Added;
|
||||
await _database.SaveChangesAsync(() => Get(obj.Slug));
|
||||
OnResourceCreated(obj);
|
||||
return obj;
|
||||
@ -74,9 +78,6 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(User obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
|
@ -18,13 +18,13 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Blurhash.SkiaSharp;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SkiaSharp;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@ -54,105 +54,77 @@ namespace Kyoo.Core.Controllers
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An helper function to download an image.
|
||||
/// </summary>
|
||||
/// <param name="url">The distant url of the image</param>
|
||||
/// <param name="localPath">The local path of the image</param>
|
||||
/// <param name="what">What is currently downloaded (used for errors)</param>
|
||||
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
||||
private async Task<bool> _DownloadImage(string url, string localPath, string what)
|
||||
private static async Task _WriteTo(SKBitmap bitmap, string path, int quality)
|
||||
{
|
||||
if (url == localPath)
|
||||
return false;
|
||||
SKData data = bitmap.Encode(SKEncodedImageFormat.Webp, quality);
|
||||
await using Stream reader = data.AsStream();
|
||||
await using Stream file = File.Create(path);
|
||||
await reader.CopyToAsync(file);
|
||||
}
|
||||
|
||||
private async Task _DownloadImage(Image? image, string localPath, string what)
|
||||
{
|
||||
if (image == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Downloading image {What}", what);
|
||||
|
||||
HttpClient client = _clientFactory.CreateClient();
|
||||
HttpResponseMessage response = await client.GetAsync(url);
|
||||
HttpResponseMessage response = await client.GetAsync(image.Source);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string mime = response.Content.Headers.ContentType?.MediaType!;
|
||||
await using Stream reader = await response.Content.ReadAsStreamAsync();
|
||||
using SKCodec codec = SKCodec.Create(reader);
|
||||
SKImageInfo info = codec.Info;
|
||||
info.ColorType = SKColorType.Rgba8888;
|
||||
using SKBitmap original = SKBitmap.Decode(codec, info);
|
||||
|
||||
string extension = new FileExtensionContentTypeProvider()
|
||||
.Mappings.FirstOrDefault(x => x.Value == mime)
|
||||
.Key;
|
||||
await using Stream local = File.Create(localPath + extension);
|
||||
await reader.CopyToAsync(local);
|
||||
return true;
|
||||
using SKBitmap high = original.Resize(new SKSizeI(original.Width, original.Height), SKFilterQuality.High);
|
||||
await _WriteTo(original, $"{localPath}.{ImageQuality.High.ToString().ToLowerInvariant()}.webp", 80);
|
||||
|
||||
using SKBitmap medium = high.Resize(new SKSizeI((int)(high.Width / 1.5), (int)(high.Height / 1.5)), SKFilterQuality.Medium);
|
||||
await _WriteTo(medium, $"{localPath}.{ImageQuality.Medium.ToString().ToLowerInvariant()}.webp", 55);
|
||||
|
||||
using SKBitmap low = medium.Resize(new SKSizeI(original.Width / 2, original.Height / 2), SKFilterQuality.Low);
|
||||
await _WriteTo(low, $"{localPath}.{ImageQuality.Low.ToString().ToLowerInvariant()}.webp", 25);
|
||||
|
||||
image.Blurhash = Blurhasher.Encode(low, 4, 3);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{What} could not be downloaded", what);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> DownloadImages<T>(T item, bool alwaysDownload = false)
|
||||
public async Task DownloadImages<T>(T item)
|
||||
where T : IThumbnails
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (item.Images == null)
|
||||
return false;
|
||||
|
||||
string name = item is IResource res ? res.Slug : "???";
|
||||
bool ret = false;
|
||||
|
||||
foreach ((int id, string image) in item.Images.Where(x => x.Value != null))
|
||||
{
|
||||
string localPath = _GetPrivateImagePath(item, id);
|
||||
if (alwaysDownload || !Path.Exists(localPath))
|
||||
ret |= await _DownloadImage(image, localPath, $"The image n {id} of {name}");
|
||||
await _DownloadImage(item.Poster, _GetBaseImagePath(item, "poster"), $"The poster of {name}");
|
||||
await _DownloadImage(item.Thumbnail, _GetBaseImagePath(item, "thumbnail"), $"The poster of {name}");
|
||||
await _DownloadImage(item.Logo, _GetBaseImagePath(item, "logo"), $"The poster of {name}");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the local path of an image of the given item <b>without an extension</b>.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to retrieve the poster from.</param>
|
||||
/// <param name="imageId">The ID of the image. See <see cref="Images"/> for values.</param>
|
||||
/// <typeparam name="T">The type of the item</typeparam>
|
||||
/// <returns>The path of the image for the given resource, <b>even if it does not exists</b></returns>
|
||||
private static string _GetPrivateImagePath<T>(T item, int imageId)
|
||||
private static string _GetBaseImagePath<T>(T item, string image)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
string directory = item switch
|
||||
{
|
||||
IResource res => Path.Combine("./metadata", typeof(T).Name.ToLowerInvariant(), res.Slug),
|
||||
IResource res => Path.Combine("./metadata", item.GetType().Name.ToLowerInvariant(), res.Slug),
|
||||
_ => Path.Combine("./metadata", typeof(T).Name.ToLowerInvariant())
|
||||
};
|
||||
Directory.CreateDirectory(directory);
|
||||
string imageName = imageId switch
|
||||
{
|
||||
Images.Poster => "poster",
|
||||
Images.Logo => "logo",
|
||||
Images.Thumbnail => "thumbnail",
|
||||
Images.Trailer => "trailer",
|
||||
_ => $"{imageId}"
|
||||
};
|
||||
return Path.Combine(directory, imageName);
|
||||
return Path.Combine(directory, image);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetImagePath<T>(T item, int imageId)
|
||||
public string GetImagePath<T>(T item, string image, ImageQuality quality)
|
||||
where T : IThumbnails
|
||||
{
|
||||
string basePath = _GetPrivateImagePath(item, imageId);
|
||||
string directory = Path.GetDirectoryName(basePath)!;
|
||||
string baseFile = Path.GetFileName(basePath);
|
||||
if (!Directory.Exists(directory))
|
||||
return null;
|
||||
return Directory.GetFiles(directory, "*", SearchOption.TopDirectoryOnly)
|
||||
.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == baseFile);
|
||||
return $"{_GetBaseImagePath(item, image)}.{quality.ToString().ToLowerInvariant()}.webp";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using JsonOptions = Kyoo.Core.Api.JsonOptions;
|
||||
|
||||
namespace Kyoo.Core
|
||||
@ -48,16 +49,14 @@ namespace Kyoo.Core
|
||||
builder.RegisterType<ThumbnailsManager>().As<IThumbnailsManager>().InstancePerLifetimeScope();
|
||||
builder.RegisterType<LibraryManager>().As<ILibraryManager>().InstancePerLifetimeScope();
|
||||
|
||||
builder.RegisterRepository<ILibraryRepository, LibraryRepository>();
|
||||
builder.RegisterRepository<ILibraryItemRepository, LibraryItemRepository>();
|
||||
builder.RegisterRepository<ICollectionRepository, CollectionRepository>();
|
||||
builder.RegisterRepository<IMovieRepository, MovieRepository>();
|
||||
builder.RegisterRepository<IShowRepository, ShowRepository>();
|
||||
builder.RegisterRepository<ISeasonRepository, SeasonRepository>();
|
||||
builder.RegisterRepository<IEpisodeRepository, EpisodeRepository>();
|
||||
builder.RegisterRepository<IPeopleRepository, PeopleRepository>();
|
||||
builder.RegisterRepository<IStudioRepository, StudioRepository>();
|
||||
builder.RegisterRepository<IGenreRepository, GenreRepository>();
|
||||
builder.RegisterRepository<IProviderRepository, ProviderRepository>();
|
||||
builder.RegisterRepository<IUserRepository, UserRepository>();
|
||||
}
|
||||
|
||||
@ -74,6 +73,7 @@ namespace Kyoo.Core
|
||||
.AddNewtonsoftJson(x =>
|
||||
{
|
||||
x.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
|
||||
x.SerializerSettings.Converters.Add(new StringEnumConverter());
|
||||
})
|
||||
.AddDataAnnotations()
|
||||
.AddControllersAsServices()
|
||||
|
@ -6,9 +6,12 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCore.Proxy" Version="4.4.0" />
|
||||
<PackageReference Include="Blurhash.SkiaSharp" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.9" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -16,12 +16,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@ -161,12 +163,12 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<T>> Edit([FromBody] T resource)
|
||||
{
|
||||
if (resource.ID > 0)
|
||||
return await Repository.Edit(resource, true);
|
||||
if (resource.Id > 0)
|
||||
return await Repository.Edit(resource);
|
||||
|
||||
T old = await Repository.Get(resource.Slug);
|
||||
resource.ID = old.ID;
|
||||
return await Repository.Edit(resource, true);
|
||||
resource.Id = old.Id;
|
||||
return await Repository.Edit(resource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -185,14 +187,15 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<T>> Patch([FromBody] T resource)
|
||||
public async Task<ActionResult<T>> Patch([FromBody] PartialResource resource)
|
||||
{
|
||||
if (resource.ID > 0)
|
||||
return await Repository.Edit(resource, false);
|
||||
if (resource.Id.HasValue)
|
||||
return await Repository.Patch(resource.Id.Value, TryUpdateModelAsync);
|
||||
if (resource.Slug == null)
|
||||
throw new ArgumentException("Either the Id or the slug of the resource has to be defined to edit it.");
|
||||
|
||||
T old = await Repository.Get(resource.Slug);
|
||||
resource.ID = old.ID;
|
||||
return await Repository.Edit(resource, false);
|
||||
return await Repository.Patch(old.Id, TryUpdateModelAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
@ -25,7 +24,6 @@ using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
@ -59,37 +57,7 @@ namespace Kyoo.Core.Api
|
||||
_thumbs = thumbs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the content type of a file using it's extension.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file</param>
|
||||
/// <exception cref="NotImplementedException">The extension of the file is not known.</exception>
|
||||
/// <returns>The content type of the file</returns>
|
||||
private static string _GetContentType(string path)
|
||||
{
|
||||
FileExtensionContentTypeProvider provider = new();
|
||||
if (provider.TryGetContentType(path, out string contentType))
|
||||
return contentType;
|
||||
throw new NotImplementedException($"Can't get the content type of the file at: {path}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get image
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get an image for the specified item.
|
||||
/// List of commonly available images:<br/>
|
||||
/// - Poster: Image 0, also available at /poster<br/>
|
||||
/// - Thumbnail: Image 1, also available at /thumbnail<br/>
|
||||
/// - Logo: Image 3, also available at /logo<br/>
|
||||
/// <br/>
|
||||
/// Other images can be arbitrarily added by plugins so any image number can be specified from this endpoint.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the resource to get the image for.</param>
|
||||
/// <param name="image">The number of the image to retrieve.</param>
|
||||
/// <returns>The image asked.</returns>
|
||||
/// <response code="404">No item exist with the specific identifier or the image does not exists on kyoo.</response>
|
||||
private async Task<IActionResult> _GetImage(Identifier identifier, int image)
|
||||
private async Task<IActionResult> _GetImage(Identifier identifier, string image, ImageQuality? quality)
|
||||
{
|
||||
T resource = await identifier.Match(
|
||||
id => Repository.GetOrDefault(id),
|
||||
@ -97,10 +65,10 @@ namespace Kyoo.Core.Api
|
||||
);
|
||||
if (resource == null)
|
||||
return NotFound();
|
||||
string path = _thumbs.GetImagePath(resource, image);
|
||||
string path = _thumbs.GetImagePath(resource, image, quality ?? ImageQuality.High);
|
||||
if (path == null || !System.IO.File.Exists(path))
|
||||
return NotFound();
|
||||
return PhysicalFile(Path.GetFullPath(path), _GetContentType(path), true);
|
||||
return PhysicalFile(Path.GetFullPath(path), "image/webp", true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -110,17 +78,18 @@ namespace Kyoo.Core.Api
|
||||
/// Get the poster for the specified item.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the resource to get the image for.</param>
|
||||
/// <param name="quality">The quality of the image to retrieve.</param>
|
||||
/// <returns>The image asked.</returns>
|
||||
/// <response code="404">
|
||||
/// No item exist with the specific identifier or the image does not exists on kyoo.
|
||||
/// </response>
|
||||
[HttpGet("{identifier:id}/poster", Order = AlternativeRoute)]
|
||||
[HttpGet("{identifier:id}/poster")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public Task<IActionResult> GetPoster(Identifier identifier)
|
||||
public Task<IActionResult> GetPoster(Identifier identifier, [FromQuery] ImageQuality? quality)
|
||||
{
|
||||
return _GetImage(identifier, Images.Poster);
|
||||
return _GetImage(identifier, "poster", quality);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -130,17 +99,18 @@ namespace Kyoo.Core.Api
|
||||
/// Get the logo for the specified item.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the resource to get the image for.</param>
|
||||
/// <param name="quality">The quality of the image to retrieve.</param>
|
||||
/// <returns>The image asked.</returns>
|
||||
/// <response code="404">
|
||||
/// No item exist with the specific identifier or the image does not exists on kyoo.
|
||||
/// </response>
|
||||
[HttpGet("{identifier:id}/logo", Order = AlternativeRoute)]
|
||||
[HttpGet("{identifier:id}/logo")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public Task<IActionResult> GetLogo(Identifier identifier)
|
||||
public Task<IActionResult> GetLogo(Identifier identifier, [FromQuery] ImageQuality? quality)
|
||||
{
|
||||
return _GetImage(identifier, Images.Logo);
|
||||
return _GetImage(identifier, "logo", quality);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -150,24 +120,16 @@ namespace Kyoo.Core.Api
|
||||
/// Get the thumbnail for the specified item.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the resource to get the image for.</param>
|
||||
/// <param name="quality">The quality of the image to retrieve.</param>
|
||||
/// <returns>The image asked.</returns>
|
||||
/// <response code="404">
|
||||
/// No item exist with the specific identifier or the image does not exists on kyoo.
|
||||
/// </response>
|
||||
[HttpGet("{identifier:id}/thumbnail")]
|
||||
[HttpGet("{identifier:id}/backdrop", Order = AlternativeRoute)]
|
||||
[HttpGet("{identifier:id}/thumbnail", Order = AlternativeRoute)]
|
||||
public Task<IActionResult> GetBackdrop(Identifier identifier)
|
||||
public Task<IActionResult> GetBackdrop(Identifier identifier, [FromQuery] ImageQuality? quality)
|
||||
{
|
||||
return _GetImage(identifier, Images.Thumbnail);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<ActionResult<T>> Create([FromBody] T resource)
|
||||
{
|
||||
// TODO: Remove this method and use a websocket API to do that.
|
||||
ActionResult<T> ret = await base.Create(resource);
|
||||
await _thumbs.DownloadImages(ret.Value);
|
||||
return ret;
|
||||
return _GetImage(identifier, "thumbnail", quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
@ -74,78 +72,5 @@ namespace Kyoo.Core.Api
|
||||
property.ShouldDeserialize = _ => false;
|
||||
return property;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
|
||||
{
|
||||
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
|
||||
if (!type.IsAssignableTo(typeof(IThumbnails)))
|
||||
return properties;
|
||||
foreach ((int id, string image) in Images.ImageName)
|
||||
{
|
||||
properties.Add(new JsonProperty
|
||||
{
|
||||
DeclaringType = type,
|
||||
PropertyName = image.ToLower(),
|
||||
UnderlyingName = image,
|
||||
PropertyType = typeof(string),
|
||||
Readable = true,
|
||||
Writable = false,
|
||||
ItemIsReference = false,
|
||||
TypeNameHandling = TypeNameHandling.None,
|
||||
ShouldSerialize = x =>
|
||||
{
|
||||
IThumbnails thumb = (IThumbnails)x;
|
||||
return thumb?.Images?.ContainsKey(id) == true;
|
||||
},
|
||||
ValueProvider = new ThumbnailProvider(id)
|
||||
});
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom <see cref="IValueProvider"/> that uses the
|
||||
/// <see cref="IThumbnails"/>.<see cref="IThumbnails.Images"/> as a value.
|
||||
/// </summary>
|
||||
private class ThumbnailProvider : IValueProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The index/ID of the image to retrieve/set.
|
||||
/// </summary>
|
||||
private readonly int _imageIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ThumbnailProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="imageIndex">The index/ID of the image to retrieve/set.</param>
|
||||
public ThumbnailProvider(int imageIndex)
|
||||
{
|
||||
_imageIndex = imageIndex;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetValue(object target, object value)
|
||||
{
|
||||
if (target is not IThumbnails thumb)
|
||||
throw new ArgumentException($"The given object is not an Thumbnail.");
|
||||
thumb.Images[_imageIndex] = value as string;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(object target)
|
||||
{
|
||||
string slug = (target as IResource)?.Slug ?? (target as ICustomTypeDescriptor)?.GetComponentName();
|
||||
if (target is not IThumbnails thumb
|
||||
|| slug == null
|
||||
|| string.IsNullOrEmpty(thumb.Images?.GetValueOrDefault(_imageIndex)))
|
||||
return null;
|
||||
string type = target is ICustomTypeDescriptor descriptor
|
||||
? descriptor.GetClassName()
|
||||
: target.GetType().Name;
|
||||
return $"/{type}/{slug}/{Images.ImageName[_imageIndex]}".ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,95 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about one or multiple <see cref="Genre"/>.
|
||||
/// </summary>
|
||||
[Route("genres")]
|
||||
[Route("genre", Order = AlternativeRoute)]
|
||||
[ApiController]
|
||||
[PartialPermission(nameof(Genre))]
|
||||
[ApiDefinition("Genres", Group = MetadataGroup)]
|
||||
public class GenreApi : CrudApi<Genre>
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager used to modify or retrieve information about the data store.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="GenreApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information about the data store.
|
||||
/// </param>
|
||||
public GenreApi(ILibraryManager libraryManager)
|
||||
: base(libraryManager.GenreRepository)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get shows with genre
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Lists the shows that have the selected genre.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Genre"/>.</param>
|
||||
/// <param name="sortBy">A key to sort shows by.</param>
|
||||
/// <param name="where">An optional list of filters.</param>
|
||||
/// <param name="pagination">The number of shows to return and where to start.</param>
|
||||
/// <returns>A page of shows.</returns>
|
||||
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
/// <response code="404">No genre with the given ID could be found.</response>
|
||||
[HttpGet("{identifier:id}/shows")]
|
||||
[HttpGet("{identifier:id}/show", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Genre>(x => x.Genres)),
|
||||
Sort<Show>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Genre>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
@ -83,7 +83,7 @@ namespace Kyoo.Core.Api
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioID, x => x.Studio.Slug)),
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioId, x => x.Studio.Slug)),
|
||||
Sort<Show>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
@ -93,40 +93,5 @@ namespace Kyoo.Core.Api
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get libraries containing this collection
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Lists the libraries that contain the collection with the given id or slug.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Collection"/>.</param>
|
||||
/// <param name="sortBy">A key to sort libraries by.</param>
|
||||
/// <param name="where">An optional list of filters.</param>
|
||||
/// <param name="pagination">The number of libraries to return.</param>
|
||||
/// <returns>A page of libraries.</returns>
|
||||
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
/// <response code="404">No collection with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/libraries")]
|
||||
[HttpGet("{identifier:id}/library", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Library>>> GetLibraries(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Library> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Collection>(x => x.Collections)),
|
||||
Sort<Library>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Collection>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,171 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about one or multiple <see cref="Library"/>.
|
||||
/// </summary>
|
||||
[Route("libraries")]
|
||||
[Route("library", Order = AlternativeRoute)]
|
||||
[ApiController]
|
||||
[ResourceView]
|
||||
[PartialPermission(nameof(Library), Group = Group.Admin)]
|
||||
[ApiDefinition("Library", Group = ResourcesGroup)]
|
||||
public class LibraryApi : CrudApi<Library>
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="EpisodeApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </param>
|
||||
public LibraryApi(ILibraryManager libraryManager)
|
||||
: base(libraryManager.LibraryRepository)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get shows
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// List the shows that are part of this library.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Library"/>.</param>
|
||||
/// <param name="sortBy">A key to sort shows by.</param>
|
||||
/// <param name="where">An optional list of filters.</param>
|
||||
/// <param name="pagination">The number of shows to return.</param>
|
||||
/// <returns>A page of shows.</returns>
|
||||
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
/// <response code="404">No library with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/shows")]
|
||||
[HttpGet("{identifier:id}/show", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Library>(x => x.Libraries)),
|
||||
Sort<Show>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Library>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get collections
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// List the collections that are part of this library.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Library"/>.</param>
|
||||
/// <param name="sortBy">A key to sort collections by.</param>
|
||||
/// <param name="where">An optional list of filters.</param>
|
||||
/// <param name="pagination">The number of collections to return.</param>
|
||||
/// <returns>A page of collections.</returns>
|
||||
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
/// <response code="404">No library with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/collections")]
|
||||
[HttpGet("{identifier:id}/collection", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Collection> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Library>(x => x.Libraries)),
|
||||
Sort<Collection>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Library>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get items
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// List all items of this library.
|
||||
/// An item can ether represent a collection or a show.
|
||||
/// This endpoint allow one to retrieve all collections and shows that are not contained in a collection.
|
||||
/// This is what is displayed on the /browse/library page of the webapp.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Library"/>.</param>
|
||||
/// <param name="sortBy">A key to sort items by.</param>
|
||||
/// <param name="where">An optional list of filters.</param>
|
||||
/// <param name="pagination">The number of items to return.</param>
|
||||
/// <returns>A page of items.</returns>
|
||||
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
/// <response code="404">No library with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/items")]
|
||||
[HttpGet("{identifier:id}/item", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<LibraryItem>>> GetItems(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
Expression<Func<LibraryItem, bool>> whereQuery = ApiHelper.ParseWhere<LibraryItem>(where);
|
||||
Sort<LibraryItem> sort = Sort<LibraryItem>.From(sortBy);
|
||||
|
||||
ICollection<LibraryItem> resources = await identifier.Match(
|
||||
id => _libraryManager.GetItemsFromLibrary(id, whereQuery, sort, pagination),
|
||||
slug => _libraryManager.GetItemsFromLibrary(slug, whereQuery, sort, pagination)
|
||||
);
|
||||
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
@ -38,7 +37,7 @@ namespace Kyoo.Core.Api
|
||||
[Route("item", Order = AlternativeRoute)]
|
||||
[ApiController]
|
||||
[ResourceView]
|
||||
[PartialPermission(nameof(LibraryItem))]
|
||||
[PartialPermission("LibraryItem")]
|
||||
[ApiDefinition("Items", Group = ResourcesGroup)]
|
||||
public class LibraryItemApi : BaseApi
|
||||
{
|
||||
@ -78,14 +77,14 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<LibraryItem>>> GetAll(
|
||||
public async Task<ActionResult<Page<ILibraryItem>>> GetAll(
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<LibraryItem> resources = await _libraryItems.GetAll(
|
||||
ApiHelper.ParseWhere<LibraryItem>(where),
|
||||
Sort<LibraryItem>.From(sortBy),
|
||||
ICollection<ILibraryItem> resources = await _libraryItems.GetAll(
|
||||
ApiHelper.ParseWhere<ILibraryItem>(where),
|
||||
Sort<ILibraryItem>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
||||
|
149
back/src/Kyoo.Core/Views/Resources/MovieApi.cs
Normal file
149
back/src/Kyoo.Core/Views/Resources/MovieApi.cs
Normal file
@ -0,0 +1,149 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about one or multiple <see cref="Movie"/>.
|
||||
/// </summary>
|
||||
[Route("movies")]
|
||||
[Route("movie", Order = AlternativeRoute)]
|
||||
[ApiController]
|
||||
[PartialPermission(nameof(Show))]
|
||||
[ApiDefinition("Shows", Group = ResourcesGroup)]
|
||||
public class MovieApi : CrudThumbsApi<Movie>
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ShowApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information about the data store.
|
||||
/// </param>
|
||||
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||
public MovieApi(ILibraryManager libraryManager,
|
||||
IThumbnailsManager thumbs)
|
||||
: base(libraryManager.MovieRepository, thumbs)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Get staff
|
||||
// /// </summary>
|
||||
// /// <remarks>
|
||||
// /// List staff members that made this show.
|
||||
// /// </remarks>
|
||||
// /// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||
// /// <param name="sortBy">A key to sort staff members by.</param>
|
||||
// /// <param name="where">An optional list of filters.</param>
|
||||
// /// <param name="pagination">The number of people to return.</param>
|
||||
// /// <returns>A page of people.</returns>
|
||||
// /// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
// /// <response code="404">No show with the given ID or slug could be found.</response>
|
||||
// [HttpGet("{identifier:id}/staff")]
|
||||
// [HttpGet("{identifier:id}/people", Order = AlternativeRoute)]
|
||||
// [PartialPermission(Kind.Read)]
|
||||
// [ProducesResponseType(StatusCodes.Status200OK)]
|
||||
// [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
// [ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
// public async Task<ActionResult<Page<PeopleRole>>> GetPeople(Identifier identifier,
|
||||
// [FromQuery] string sortBy,
|
||||
// [FromQuery] Dictionary<string, string> where,
|
||||
// [FromQuery] Pagination pagination)
|
||||
// {
|
||||
// Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
|
||||
// Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
|
||||
//
|
||||
// ICollection<PeopleRole> resources = await identifier.Match(
|
||||
// id => _libraryManager.GetPeopleFromShow(id, whereQuery, sort, pagination),
|
||||
// slug => _libraryManager.GetPeopleFromShow(slug, whereQuery, sort, pagination)
|
||||
// );
|
||||
// return Page(resources, pagination.Limit);
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Get studio that made the show
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get the studio that made the show.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||
/// <returns>The studio that made the show.</returns>
|
||||
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/studio")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Studio>> GetStudio(Identifier identifier)
|
||||
{
|
||||
return await _libraryManager.Get(identifier.IsContainedIn<Studio, Movie>(x => x.Movies));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get collections containing this show
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// List the collections that contain this show.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||
/// <param name="sortBy">A key to sort collections by.</param>
|
||||
/// <param name="where">An optional list of filters.</param>
|
||||
/// <param name="pagination">The number of collections to return.</param>
|
||||
/// <returns>A page of collections.</returns>
|
||||
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/collections")]
|
||||
[HttpGet("{identifier:id}/collection", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Collection> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Movie>(x => x.Movies)),
|
||||
Sort<Collection>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Movie>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
@ -76,10 +76,11 @@ namespace Kyoo.Core.Api
|
||||
{
|
||||
Query = query,
|
||||
Collections = await _libraryManager.Search<Collection>(query),
|
||||
Items = await _libraryManager.Search<ILibraryItem>(query),
|
||||
Movies = await _libraryManager.Search<Movie>(query),
|
||||
Shows = await _libraryManager.Search<Show>(query),
|
||||
Episodes = await _libraryManager.Search<Episode>(query),
|
||||
People = await _libraryManager.Search<People>(query),
|
||||
Genres = await _libraryManager.Search<Genre>(query),
|
||||
Studios = await _libraryManager.Search<Studio>(query)
|
||||
};
|
||||
}
|
||||
@ -133,9 +134,9 @@ namespace Kyoo.Core.Api
|
||||
[Permission(nameof(Show), Kind.Read)]
|
||||
[ApiDefinition("Items")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public Task<ICollection<LibraryItem>> SearchItems(string query)
|
||||
public Task<ICollection<ILibraryItem>> SearchItems(string query)
|
||||
{
|
||||
return _libraryManager.Search<LibraryItem>(query);
|
||||
return _libraryManager.Search<ILibraryItem>(query);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -175,24 +176,6 @@ namespace Kyoo.Core.Api
|
||||
return _libraryManager.Search<People>(query);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search genres
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Search for genres
|
||||
/// </remarks>
|
||||
/// <param name="query">The query to search for.</param>
|
||||
/// <returns>A list of genres found for the specified query.</returns>
|
||||
[HttpGet("genres")]
|
||||
[HttpGet("genre", Order = AlternativeRoute)]
|
||||
[Permission(nameof(Genre), Kind.Read)]
|
||||
[ApiDefinition("Genres")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public Task<ICollection<Genre>> SearchGenres(string query)
|
||||
{
|
||||
return _libraryManager.Search<Genre>(query);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search studios
|
||||
/// </summary>
|
||||
|
@ -84,7 +84,7 @@ namespace Kyoo.Core.Api
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Episode> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonID, x => x.Season.Slug)),
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonId, x => x.Season.Slug)),
|
||||
Sort<Episode>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
@ -37,8 +37,6 @@ namespace Kyoo.Core.Api
|
||||
/// </summary>
|
||||
[Route("shows")]
|
||||
[Route("show", Order = AlternativeRoute)]
|
||||
[Route("movie", Order = AlternativeRoute)]
|
||||
[Route("movies", Order = AlternativeRoute)]
|
||||
[ApiController]
|
||||
[PartialPermission(nameof(Show))]
|
||||
[ApiDefinition("Shows", Group = ResourcesGroup)]
|
||||
@ -63,24 +61,6 @@ namespace Kyoo.Core.Api
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<ActionResult<Show>> Create([FromBody] Show resource)
|
||||
{
|
||||
ActionResult<Show> ret = await base.Create(resource);
|
||||
if (ret.Value.IsMovie)
|
||||
{
|
||||
Episode episode = new()
|
||||
{
|
||||
Show = ret.Value,
|
||||
Title = ret.Value.Title,
|
||||
Path = ret.Value.Path
|
||||
};
|
||||
|
||||
await _libraryManager.Create(episode);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get seasons of this show
|
||||
/// </summary>
|
||||
@ -106,7 +86,7 @@ namespace Kyoo.Core.Api
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Season> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowID, x => x.Show.Slug)),
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowId, x => x.Show.Slug)),
|
||||
Sort<Season>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
@ -141,7 +121,7 @@ namespace Kyoo.Core.Api
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Episode> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowID, x => x.Show.Slug)),
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowId, x => x.Show.Slug)),
|
||||
Sort<Episode>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
@ -185,41 +165,6 @@ namespace Kyoo.Core.Api
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get genres of this show
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// List the genres that represent this show.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||
/// <param name="sortBy">A key to sort genres by.</param>
|
||||
/// <param name="where">An optional list of filters.</param>
|
||||
/// <param name="pagination">The number of genres to return.</param>
|
||||
/// <returns>A page of genres.</returns>
|
||||
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/genres")]
|
||||
[HttpGet("{identifier:id}/genre", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Genre>>> GetGenres(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Genre> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Genre, Show>(x => x.Shows)),
|
||||
Sort<Genre>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get studio that made the show
|
||||
/// </summary>
|
||||
@ -238,42 +183,6 @@ namespace Kyoo.Core.Api
|
||||
return await _libraryManager.Get(identifier.IsContainedIn<Studio, Show>(x => x.Shows));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get libraries containing this show
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// List the libraries that contain this show. If this show is contained in a collection that is contained in
|
||||
/// a library, this library will be returned too.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||
/// <param name="sortBy">A key to sort libraries by.</param>
|
||||
/// <param name="where">An optional list of filters.</param>
|
||||
/// <param name="pagination">The number of libraries to return.</param>
|
||||
/// <returns>A page of libraries.</returns>
|
||||
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/libraries")]
|
||||
[HttpGet("{identifier:id}/library", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Library>>> GetLibraries(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Library> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Show>(x => x.Shows)),
|
||||
Sort<Library>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get collections containing this show
|
||||
/// </summary>
|
||||
|
@ -1,91 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve information of an <see cref="Episode"/> as a <see cref="WatchItem"/>.
|
||||
/// A watch item is another representation of an episode in a form easier to read and display for playback.
|
||||
/// It contains streams (video, audio, subtitles) information, chapters, next and previous episodes and a bit of
|
||||
/// information of the show.
|
||||
/// </summary>
|
||||
[Route("watch")]
|
||||
[Route("watchitem", Order = AlternativeRoute)]
|
||||
[ApiController]
|
||||
[ApiDefinition("Watch Items", Group = WatchGroup)]
|
||||
public class WatchApi : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The http client to reach transcoder.
|
||||
/// </summary>
|
||||
private readonly HttpClient _client;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="WatchApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </param>
|
||||
/// <param name="client">The http client to reach transcoder.</param>
|
||||
public WatchApi(ILibraryManager libraryManager, HttpClient client)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a watch item
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retrieve a watch item of an episode.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
||||
/// <returns>A page of items.</returns>
|
||||
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}")]
|
||||
[Permission("watch", Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<WatchItem>> GetWatchItem(Identifier identifier)
|
||||
{
|
||||
Episode item = await identifier.Match(
|
||||
id => _libraryManager.GetOrDefault<Episode>(id),
|
||||
slug => _libraryManager.GetOrDefault<Episode>(slug)
|
||||
);
|
||||
if (item == null)
|
||||
return NotFound();
|
||||
return await WatchItem.FromEpisode(item, _libraryManager, _client);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
@ -40,16 +41,16 @@ namespace Kyoo.Postgresql
|
||||
/// </remarks>
|
||||
public abstract class DatabaseContext : DbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// All libraries of Kyoo. See <see cref="Library"/>.
|
||||
/// </summary>
|
||||
public DbSet<Library> Libraries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All collections of Kyoo. See <see cref="Collection"/>.
|
||||
/// </summary>
|
||||
public DbSet<Collection> Collections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All movies of Kyoo. See <see cref="Movie"/>.
|
||||
/// </summary>
|
||||
public DbSet<Movie> Movies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All shows of Kyoo. See <see cref="Show"/>.
|
||||
/// </summary>
|
||||
@ -65,11 +66,6 @@ namespace Kyoo.Postgresql
|
||||
/// </summary>
|
||||
public DbSet<Episode> Episodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All genres of Kyoo. See <see cref="Genres"/>.
|
||||
/// </summary>
|
||||
public DbSet<Genre> Genres { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All people of Kyoo. See <see cref="People"/>.
|
||||
/// </summary>
|
||||
@ -80,11 +76,6 @@ namespace Kyoo.Postgresql
|
||||
/// </summary>
|
||||
public DbSet<Studio> Studios { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All providers of Kyoo. See <see cref="Provider"/>.
|
||||
/// </summary>
|
||||
public DbSet<Provider> Providers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of registered users.
|
||||
/// </summary>
|
||||
@ -95,11 +86,6 @@ namespace Kyoo.Postgresql
|
||||
/// </summary>
|
||||
public DbSet<PeopleRole> PeopleRoles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Episodes with a watch percentage. See <see cref="WatchedEpisode"/>.
|
||||
/// </summary>
|
||||
public DbSet<WatchedEpisode> WatchedEpisodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of library items (shows and collections that are part of a library - or the global one).
|
||||
/// </summary>
|
||||
@ -108,17 +94,6 @@ namespace Kyoo.Postgresql
|
||||
/// </remarks>
|
||||
public DbSet<LibraryItem> LibraryItems { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get all metadataIDs (ExternalIDs) of a given resource. See <see cref="MetadataID"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The metadata of this type will be returned.</typeparam>
|
||||
/// <returns>A queryable of metadata ids for a type.</returns>
|
||||
public DbSet<MetadataID> MetadataIds<T>()
|
||||
where T : class, IMetadata
|
||||
{
|
||||
return Set<MetadataID>(MetadataName<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a many to many link between two resources.
|
||||
/// </summary>
|
||||
@ -153,14 +128,6 @@ namespace Kyoo.Postgresql
|
||||
: base(options)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Get the name of the metadata table of the given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type related to the metadata</typeparam>
|
||||
/// <returns>The name of the table containing the metadata.</returns>
|
||||
protected abstract string MetadataName<T>()
|
||||
where T : IMetadata;
|
||||
|
||||
/// <summary>
|
||||
/// Get the name of the link table of the two given types.
|
||||
/// </summary>
|
||||
@ -194,17 +161,33 @@ namespace Kyoo.Postgresql
|
||||
/// </summary>
|
||||
/// <param name="modelBuilder">The database model builder</param>
|
||||
/// <typeparam name="T">The type to add metadata to.</typeparam>
|
||||
private void _HasMetadata<T>(ModelBuilder modelBuilder)
|
||||
private static void _HasMetadata<T>(ModelBuilder modelBuilder)
|
||||
where T : class, IMetadata
|
||||
{
|
||||
modelBuilder.SharedTypeEntity<MetadataID>(MetadataName<T>())
|
||||
.HasKey(MetadataID.PrimaryKey);
|
||||
// TODO: Waiting for https://github.com/dotnet/efcore/issues/29825
|
||||
// modelBuilder.Entity<T>()
|
||||
// .OwnsOne(x => x.ExternalIDs, x =>
|
||||
// {
|
||||
// x.ToJson();
|
||||
// });
|
||||
modelBuilder.Entity<T>()
|
||||
.Property(x => x.ExternalId)
|
||||
.HasConversion(
|
||||
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
|
||||
v => JsonSerializer.Deserialize<Dictionary<string, MetadataId>>(v, (JsonSerializerOptions)null)
|
||||
)
|
||||
.HasColumnType("json");
|
||||
}
|
||||
|
||||
modelBuilder.SharedTypeEntity<MetadataID>(MetadataName<T>())
|
||||
.HasOne<T>()
|
||||
.WithMany(x => x.ExternalIDs)
|
||||
.HasForeignKey(x => x.ResourceID)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
private static void _HasImages<T>(ModelBuilder modelBuilder)
|
||||
where T : class, IThumbnails
|
||||
{
|
||||
modelBuilder.Entity<T>()
|
||||
.OwnsOne(x => x.Poster);
|
||||
modelBuilder.Entity<T>()
|
||||
.OwnsOne(x => x.Thumbnail);
|
||||
modelBuilder.Entity<T>()
|
||||
.OwnsOne(x => x.Logo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -248,6 +231,10 @@ namespace Kyoo.Postgresql
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<Episode>()
|
||||
.Ignore(x => x.PreviousEpisode)
|
||||
.Ignore(x => x.NextEpisode);
|
||||
|
||||
modelBuilder.Entity<PeopleRole>()
|
||||
.Ignore(x => x.ForPeople);
|
||||
|
||||
@ -264,72 +251,68 @@ namespace Kyoo.Postgresql
|
||||
.WithOne(x => x.Season)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<Movie>()
|
||||
.HasOne(x => x.Studio)
|
||||
.WithMany(x => x.Movies)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
modelBuilder.Entity<Show>()
|
||||
.HasOne(x => x.Studio)
|
||||
.WithMany(x => x.Shows)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
_HasManyToMany<Library, Provider>(modelBuilder, x => x.Providers, x => x.Libraries);
|
||||
_HasManyToMany<Library, Collection>(modelBuilder, x => x.Collections, x => x.Libraries);
|
||||
_HasManyToMany<Library, Show>(modelBuilder, x => x.Shows, x => x.Libraries);
|
||||
_HasManyToMany<Collection, Movie>(modelBuilder, x => x.Movies, x => x.Collections);
|
||||
_HasManyToMany<Collection, Show>(modelBuilder, x => x.Shows, x => x.Collections);
|
||||
_HasManyToMany<Show, Genre>(modelBuilder, x => x.Genres, x => x.Shows);
|
||||
|
||||
modelBuilder.Entity<User>()
|
||||
.HasMany(x => x.Watched)
|
||||
.WithMany("Users")
|
||||
.UsingEntity(x => x.ToTable(LinkName<User, Show>()));
|
||||
|
||||
_HasMetadata<LibraryItem>(modelBuilder);
|
||||
_HasMetadata<Collection>(modelBuilder);
|
||||
_HasMetadata<Movie>(modelBuilder);
|
||||
_HasMetadata<Show>(modelBuilder);
|
||||
_HasMetadata<Season>(modelBuilder);
|
||||
_HasMetadata<Episode>(modelBuilder);
|
||||
_HasMetadata<People>(modelBuilder);
|
||||
_HasMetadata<Studio>(modelBuilder);
|
||||
|
||||
_HasImages<LibraryItem>(modelBuilder);
|
||||
_HasImages<Collection>(modelBuilder);
|
||||
_HasImages<Movie>(modelBuilder);
|
||||
_HasImages<Show>(modelBuilder);
|
||||
_HasImages<Season>(modelBuilder);
|
||||
_HasImages<Episode>(modelBuilder);
|
||||
_HasImages<People>(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<User>().OwnsOne(x => x.Logo);
|
||||
|
||||
modelBuilder.Entity<WatchedEpisode>()
|
||||
.HasKey(x => new { User = x.UserID, Episode = x.EpisodeID });
|
||||
|
||||
modelBuilder.Entity<Collection>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Genre>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Library>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<People>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Provider>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Show>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Season>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Episode>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Studio>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<User>().Property(x => x.Slug).IsRequired();
|
||||
|
||||
modelBuilder.Entity<Collection>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Genre>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Library>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<People>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Movie>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Show>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Studio>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Provider>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Season>()
|
||||
.HasIndex(x => new { x.ShowID, x.SeasonNumber })
|
||||
.HasIndex(x => new { ShowID = x.ShowId, x.SeasonNumber })
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Season>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Episode>()
|
||||
.HasIndex(x => new { x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber })
|
||||
.HasIndex(x => new { ShowID = x.ShowId, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber })
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Episode>()
|
||||
.HasIndex(x => x.Slug)
|
||||
@ -338,8 +321,10 @@ namespace Kyoo.Postgresql
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<Movie>()
|
||||
.Ignore(x => x.Links);
|
||||
modelBuilder.Entity<LibraryItem>()
|
||||
.ToView("library_items");
|
||||
.Ignore(x => x.Links);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -352,7 +337,7 @@ namespace Kyoo.Postgresql
|
||||
public T GetTemporaryObject<T>(T model)
|
||||
where T : class, IResource
|
||||
{
|
||||
T tmp = Set<T>().Local.FirstOrDefault(x => x.ID == model.ID);
|
||||
T tmp = Set<T>().Local.FirstOrDefault(x => x.Id == model.Id);
|
||||
if (tmp != null)
|
||||
return tmp;
|
||||
Entry(model).State = EntityState.Unchanged;
|
||||
|
@ -18,4 +18,8 @@
|
||||
<ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,872 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// The initial migration that build most of the database.
|
||||
/// </summary>
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20210801171613_Initial")]
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:Enum:item_type", "show,movie,collection")
|
||||
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
|
||||
.Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,attachment");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "collections",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: true),
|
||||
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
|
||||
overview = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_collections", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "genres",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_genres", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "libraries",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: true),
|
||||
paths = table.Column<string[]>(type: "text[]", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_libraries", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "people",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: true),
|
||||
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_people", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "providers",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: true),
|
||||
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_providers", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "studios",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_studios", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "users",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: false),
|
||||
username = table.Column<string>(type: "text", nullable: true),
|
||||
email = table.Column<string>(type: "text", nullable: true),
|
||||
password = table.Column<string>(type: "text", nullable: true),
|
||||
permissions = table.Column<string[]>(type: "text[]", nullable: true),
|
||||
extra_data = table.Column<Dictionary<string, string>>(type: "jsonb", nullable: true),
|
||||
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_users", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "link_library_collection",
|
||||
columns: table => new
|
||||
{
|
||||
collection_id = table.Column<int>(type: "integer", nullable: false),
|
||||
library_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_link_library_collection", x => new { x.collection_id, x.library_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_link_library_collection_collections_collection_id",
|
||||
column: x => x.collection_id,
|
||||
principalTable: "collections",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_link_library_collection_libraries_library_id",
|
||||
column: x => x.library_id,
|
||||
principalTable: "libraries",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "collection_metadata_id",
|
||||
columns: table => new
|
||||
{
|
||||
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||
data_id = table.Column<string>(type: "text", nullable: true),
|
||||
link = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_collection_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_collection_metadata_id_collections_collection_id",
|
||||
column: x => x.resource_id,
|
||||
principalTable: "collections",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_collection_metadata_id_providers_provider_id",
|
||||
column: x => x.provider_id,
|
||||
principalTable: "providers",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "link_library_provider",
|
||||
columns: table => new
|
||||
{
|
||||
library_id = table.Column<int>(type: "integer", nullable: false),
|
||||
provider_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_link_library_provider", x => new { x.library_id, x.provider_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_link_library_provider_libraries_library_id",
|
||||
column: x => x.library_id,
|
||||
principalTable: "libraries",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_link_library_provider_providers_provider_id",
|
||||
column: x => x.provider_id,
|
||||
principalTable: "providers",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "people_metadata_id",
|
||||
columns: table => new
|
||||
{
|
||||
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||
data_id = table.Column<string>(type: "text", nullable: true),
|
||||
link = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_people_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_people_metadata_id_people_people_id",
|
||||
column: x => x.resource_id,
|
||||
principalTable: "people",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_people_metadata_id_providers_provider_id",
|
||||
column: x => x.provider_id,
|
||||
principalTable: "providers",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "shows",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: false),
|
||||
title = table.Column<string>(type: "text", nullable: true),
|
||||
aliases = table.Column<string[]>(type: "text[]", nullable: true),
|
||||
path = table.Column<string>(type: "text", nullable: true),
|
||||
overview = table.Column<string>(type: "text", nullable: true),
|
||||
status = table.Column<Status>(type: "status", nullable: false),
|
||||
start_air = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
end_air = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
|
||||
is_movie = table.Column<bool>(type: "boolean", nullable: false),
|
||||
studio_id = table.Column<int>(type: "integer", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_shows", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_shows_studios_studio_id",
|
||||
column: x => x.studio_id,
|
||||
principalTable: "studios",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "studio_metadata_id",
|
||||
columns: table => new
|
||||
{
|
||||
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||
data_id = table.Column<string>(type: "text", nullable: true),
|
||||
link = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_studio_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_studio_metadata_id_providers_provider_id",
|
||||
column: x => x.provider_id,
|
||||
principalTable: "providers",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_studio_metadata_id_studios_studio_id",
|
||||
column: x => x.resource_id,
|
||||
principalTable: "studios",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "link_collection_show",
|
||||
columns: table => new
|
||||
{
|
||||
collection_id = table.Column<int>(type: "integer", nullable: false),
|
||||
show_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_link_collection_show_collections_collection_id",
|
||||
column: x => x.collection_id,
|
||||
principalTable: "collections",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_link_collection_show_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "link_library_show",
|
||||
columns: table => new
|
||||
{
|
||||
library_id = table.Column<int>(type: "integer", nullable: false),
|
||||
show_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_link_library_show", x => new { x.library_id, x.show_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_link_library_show_libraries_library_id",
|
||||
column: x => x.library_id,
|
||||
principalTable: "libraries",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_link_library_show_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "link_show_genre",
|
||||
columns: table => new
|
||||
{
|
||||
genre_id = table.Column<int>(type: "integer", nullable: false),
|
||||
show_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_link_show_genre", x => new { x.genre_id, x.show_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_link_show_genre_genres_genre_id",
|
||||
column: x => x.genre_id,
|
||||
principalTable: "genres",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_link_show_genre_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "link_user_show",
|
||||
columns: table => new
|
||||
{
|
||||
users_id = table.Column<int>(type: "integer", nullable: false),
|
||||
watched_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_link_user_show_shows_watched_id",
|
||||
column: x => x.watched_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_link_user_show_users_users_id",
|
||||
column: x => x.users_id,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "people_roles",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
people_id = table.Column<int>(type: "integer", nullable: false),
|
||||
show_id = table.Column<int>(type: "integer", nullable: false),
|
||||
type = table.Column<string>(type: "text", nullable: true),
|
||||
role = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_people_roles", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_people_roles_people_people_id",
|
||||
column: x => x.people_id,
|
||||
principalTable: "people",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_people_roles_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "seasons",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: true),
|
||||
show_id = table.Column<int>(type: "integer", nullable: false),
|
||||
season_number = table.Column<int>(type: "integer", nullable: false),
|
||||
title = table.Column<string>(type: "text", nullable: true),
|
||||
overview = table.Column<string>(type: "text", nullable: true),
|
||||
start_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
end_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_seasons", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_seasons_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "show_metadata_id",
|
||||
columns: table => new
|
||||
{
|
||||
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||
data_id = table.Column<string>(type: "text", nullable: true),
|
||||
link = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_show_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_show_metadata_id_providers_provider_id",
|
||||
column: x => x.provider_id,
|
||||
principalTable: "providers",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_show_metadata_id_shows_show_id",
|
||||
column: x => x.resource_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "episodes",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: true),
|
||||
show_id = table.Column<int>(type: "integer", nullable: false),
|
||||
season_id = table.Column<int>(type: "integer", nullable: true),
|
||||
season_number = table.Column<int>(type: "integer", nullable: true),
|
||||
episode_number = table.Column<int>(type: "integer", nullable: true),
|
||||
absolute_number = table.Column<int>(type: "integer", nullable: true),
|
||||
path = table.Column<string>(type: "text", nullable: true),
|
||||
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
|
||||
title = table.Column<string>(type: "text", nullable: true),
|
||||
overview = table.Column<string>(type: "text", nullable: true),
|
||||
release_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_episodes", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_episodes_seasons_season_id",
|
||||
column: x => x.season_id,
|
||||
principalTable: "seasons",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_episodes_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "season_metadata_id",
|
||||
columns: table => new
|
||||
{
|
||||
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||
data_id = table.Column<string>(type: "text", nullable: true),
|
||||
link = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_season_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_season_metadata_id_providers_provider_id",
|
||||
column: x => x.provider_id,
|
||||
principalTable: "providers",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_season_metadata_id_seasons_season_id",
|
||||
column: x => x.resource_id,
|
||||
principalTable: "seasons",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "episode_metadata_id",
|
||||
columns: table => new
|
||||
{
|
||||
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||
data_id = table.Column<string>(type: "text", nullable: true),
|
||||
link = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_episode_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_episode_metadata_id_episodes_episode_id",
|
||||
column: x => x.resource_id,
|
||||
principalTable: "episodes",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_episode_metadata_id_providers_provider_id",
|
||||
column: x => x.provider_id,
|
||||
principalTable: "providers",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "tracks",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "text", nullable: true),
|
||||
title = table.Column<string>(type: "text", nullable: true),
|
||||
language = table.Column<string>(type: "text", nullable: true),
|
||||
codec = table.Column<string>(type: "text", nullable: true),
|
||||
is_default = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_forced = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_external = table.Column<bool>(type: "boolean", nullable: false),
|
||||
path = table.Column<string>(type: "text", nullable: true),
|
||||
type = table.Column<object>(type: "stream_type", nullable: false),
|
||||
episode_id = table.Column<int>(type: "integer", nullable: false),
|
||||
track_index = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_tracks", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_tracks_episodes_episode_id",
|
||||
column: x => x.episode_id,
|
||||
principalTable: "episodes",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "watched_episodes",
|
||||
columns: table => new
|
||||
{
|
||||
user_id = table.Column<int>(type: "integer", nullable: false),
|
||||
episode_id = table.Column<int>(type: "integer", nullable: false),
|
||||
watched_percentage = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_watched_episodes", x => new { x.user_id, x.episode_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_watched_episodes_episodes_episode_id",
|
||||
column: x => x.episode_id,
|
||||
principalTable: "episodes",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_watched_episodes_users_user_id",
|
||||
column: x => x.user_id,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_collection_metadata_id_provider_id",
|
||||
table: "collection_metadata_id",
|
||||
column: "provider_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_collections_slug",
|
||||
table: "collections",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_episode_metadata_id_provider_id",
|
||||
table: "episode_metadata_id",
|
||||
column: "provider_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_episodes_season_id",
|
||||
table: "episodes",
|
||||
column: "season_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_episodes_show_id_season_number_episode_number_absolute_numb",
|
||||
table: "episodes",
|
||||
columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_episodes_slug",
|
||||
table: "episodes",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_genres_slug",
|
||||
table: "genres",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_libraries_slug",
|
||||
table: "libraries",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_link_collection_show_show_id",
|
||||
table: "link_collection_show",
|
||||
column: "show_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_link_library_collection_library_id",
|
||||
table: "link_library_collection",
|
||||
column: "library_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_link_library_provider_provider_id",
|
||||
table: "link_library_provider",
|
||||
column: "provider_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_link_library_show_show_id",
|
||||
table: "link_library_show",
|
||||
column: "show_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_link_show_genre_show_id",
|
||||
table: "link_show_genre",
|
||||
column: "show_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_link_user_show_watched_id",
|
||||
table: "link_user_show",
|
||||
column: "watched_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_people_slug",
|
||||
table: "people",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_people_metadata_id_provider_id",
|
||||
table: "people_metadata_id",
|
||||
column: "provider_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_people_roles_people_id",
|
||||
table: "people_roles",
|
||||
column: "people_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_people_roles_show_id",
|
||||
table: "people_roles",
|
||||
column: "show_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_providers_slug",
|
||||
table: "providers",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_season_metadata_id_provider_id",
|
||||
table: "season_metadata_id",
|
||||
column: "provider_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_seasons_show_id_season_number",
|
||||
table: "seasons",
|
||||
columns: new[] { "show_id", "season_number" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_seasons_slug",
|
||||
table: "seasons",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_show_metadata_id_provider_id",
|
||||
table: "show_metadata_id",
|
||||
column: "provider_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_shows_slug",
|
||||
table: "shows",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_shows_studio_id",
|
||||
table: "shows",
|
||||
column: "studio_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_studio_metadata_id_provider_id",
|
||||
table: "studio_metadata_id",
|
||||
column: "provider_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_studios_slug",
|
||||
table: "studios",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tracks_episode_id_type_language_track_index_is_forced",
|
||||
table: "tracks",
|
||||
columns: new[] { "episode_id", "type", "language", "track_index", "is_forced" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tracks_slug",
|
||||
table: "tracks",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_users_slug",
|
||||
table: "users",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_watched_episodes_episode_id",
|
||||
table: "watched_episodes",
|
||||
column: "episode_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "collection_metadata_id");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "episode_metadata_id");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "link_collection_show");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "link_library_collection");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "link_library_provider");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "link_library_show");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "link_show_genre");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "link_user_show");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "people_metadata_id");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "people_roles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "season_metadata_id");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "show_metadata_id");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "studio_metadata_id");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "tracks");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "watched_episodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "collections");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "libraries");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "genres");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "people");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "providers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "episodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "seasons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "shows");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "studios");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// A migration that adds postgres triggers to update slugs.
|
||||
/// </summary>
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20210801171641_Triggers")]
|
||||
public partial class Triggers : Migration
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE FUNCTION season_slug_update()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.slug := CONCAT(
|
||||
(SELECT slug FROM shows WHERE id = NEW.show_id),
|
||||
'-s',
|
||||
NEW.season_number
|
||||
);
|
||||
RETURN NEW;
|
||||
END
|
||||
$$;");
|
||||
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE TRIGGER season_slug_trigger BEFORE INSERT OR UPDATE OF season_number, show_id ON seasons
|
||||
FOR EACH ROW EXECUTE PROCEDURE season_slug_update();");
|
||||
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE FUNCTION episode_slug_update()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.slug := CONCAT(
|
||||
(SELECT slug FROM shows WHERE id = NEW.show_id),
|
||||
CASE
|
||||
WHEN NEW.season_number IS NULL AND NEW.episode_number IS NULL THEN NULL
|
||||
WHEN NEW.season_number IS NULL THEN CONCAT('-', NEW.absolute_number)
|
||||
ELSE CONCAT('-s', NEW.season_number, 'e', NEW.episode_number)
|
||||
END
|
||||
);
|
||||
RETURN NEW;
|
||||
END
|
||||
$$;");
|
||||
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE TRIGGER episode_slug_trigger
|
||||
BEFORE INSERT OR UPDATE OF absolute_number, episode_number, season_number, show_id ON episodes
|
||||
FOR EACH ROW EXECUTE PROCEDURE episode_slug_update();");
|
||||
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE FUNCTION show_slug_update()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
UPDATE seasons SET slug = CONCAT(NEW.slug, '-s', season_number) WHERE show_id = NEW.id;
|
||||
UPDATE episodes SET slug = CASE
|
||||
WHEN season_number IS NULL AND episode_number IS NULL THEN NEW.slug
|
||||
WHEN season_number IS NULL THEN CONCAT(NEW.slug, '-', absolute_number)
|
||||
ELSE CONCAT(NEW.slug, '-s', season_number, 'e', episode_number)
|
||||
END WHERE show_id = NEW.id;
|
||||
RETURN NEW;
|
||||
END
|
||||
$$;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE TRIGGER show_slug_trigger AFTER UPDATE OF slug ON shows
|
||||
FOR EACH ROW EXECUTE PROCEDURE show_slug_update();");
|
||||
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE FUNCTION episode_update_tracks_slug()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
UPDATE tracks SET slug = CONCAT(
|
||||
NEW.slug,
|
||||
'.', language,
|
||||
CASE (track_index)
|
||||
WHEN 0 THEN ''
|
||||
ELSE CONCAT('-', track_index)
|
||||
END,
|
||||
CASE (is_forced)
|
||||
WHEN false THEN ''
|
||||
ELSE '.forced'
|
||||
END,
|
||||
'.', type
|
||||
) WHERE episode_id = NEW.id;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE TRIGGER episode_track_slug_trigger AFTER UPDATE OF slug ON episodes
|
||||
FOR EACH ROW EXECUTE PROCEDURE episode_update_tracks_slug();");
|
||||
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE FUNCTION track_slug_update()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW.track_index = 0 THEN
|
||||
NEW.track_index := (SELECT COUNT(*) FROM tracks
|
||||
WHERE episode_id = NEW.episode_id AND type = NEW.type
|
||||
AND language = NEW.language AND is_forced = NEW.is_forced);
|
||||
END IF;
|
||||
NEW.slug := CONCAT(
|
||||
(SELECT slug FROM episodes WHERE id = NEW.episode_id),
|
||||
'.', COALESCE(NEW.language, 'und'),
|
||||
CASE (NEW.track_index)
|
||||
WHEN 0 THEN ''
|
||||
ELSE CONCAT('-', NEW.track_index)
|
||||
END,
|
||||
CASE (NEW.is_forced)
|
||||
WHEN false THEN ''
|
||||
ELSE '.forced'
|
||||
END,
|
||||
'.', NEW.type
|
||||
);
|
||||
RETURN NEW;
|
||||
END
|
||||
$$;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE TRIGGER track_slug_trigger
|
||||
BEFORE INSERT OR UPDATE OF episode_id, is_forced, language, track_index, type ON tracks
|
||||
FOR EACH ROW EXECUTE PROCEDURE track_slug_update();");
|
||||
|
||||
MigrationHelper.CreateLibraryItemsView(migrationBuilder);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql("DROP TRIGGER show_slug_trigger ON shows;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION show_slug_update;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP TRIGGER season_slug_trigger ON seasons;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION season_slug_update;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql("DROP TRIGGER episode_slug_trigger ON episodes;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION episode_slug_update;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql("DROP TRIGGER track_slug_trigger ON tracks;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION track_slug_update;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql("DROP TRIGGER episode_track_slug_trigger ON episodes;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION episode_update_tracks_slug;");
|
||||
MigrationHelper.DropLibraryItemsView(migrationBuilder);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Remove triggers
|
||||
/// </summary>
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20230724144449_RemoveTriggers")]
|
||||
public partial class RemoveTriggers : Migration
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql("DROP TRIGGER show_slug_trigger ON shows;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION show_slug_update;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP TRIGGER season_slug_trigger ON seasons;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION season_slug_update;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql("DROP TRIGGER episode_slug_trigger ON episodes;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION episode_slug_update;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql("DROP TRIGGER track_slug_trigger ON tracks;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION track_slug_update;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql("DROP TRIGGER episode_track_slug_trigger ON episodes;");
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP FUNCTION episode_update_tracks_slug;");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "slug",
|
||||
table: "tracks",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: string.Empty,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "slug",
|
||||
table: "seasons",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: string.Empty,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "slug",
|
||||
table: "episodes",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: string.Empty,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text",
|
||||
oldNullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "slug",
|
||||
table: "tracks",
|
||||
type: "text",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "slug",
|
||||
table: "seasons",
|
||||
type: "text",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "slug",
|
||||
table: "episodes",
|
||||
type: "text",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,138 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20230726100747_Timestamp")]
|
||||
public partial class Timestamp : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
MigrationHelper.DropLibraryItemsView(migrationBuilder);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_air",
|
||||
table: "shows",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp without time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "end_air",
|
||||
table: "shows",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp without time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "seasons",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp without time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "end_date",
|
||||
table: "seasons",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp without time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "release_date",
|
||||
table: "episodes",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp without time zone",
|
||||
oldNullable: true);
|
||||
|
||||
MigrationHelper.CreateLibraryItemsView(migrationBuilder);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
MigrationHelper.DropLibraryItemsView(migrationBuilder);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_air",
|
||||
table: "shows",
|
||||
type: "timestamp without time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "end_air",
|
||||
table: "shows",
|
||||
type: "timestamp without time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "seasons",
|
||||
type: "timestamp without time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "end_date",
|
||||
table: "seasons",
|
||||
type: "timestamp without time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "release_date",
|
||||
table: "episodes",
|
||||
type: "timestamp without time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
|
||||
MigrationHelper.CreateLibraryItemsView(migrationBuilder);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,95 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveTracks : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "tracks");
|
||||
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:Enum:item_type", "show,movie,collection")
|
||||
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
|
||||
.OldAnnotation("Npgsql:Enum:item_type", "show,movie,collection")
|
||||
.OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
|
||||
.OldAnnotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:Enum:item_type", "show,movie,collection")
|
||||
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
|
||||
.Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle")
|
||||
.OldAnnotation("Npgsql:Enum:item_type", "show,movie,collection")
|
||||
.OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "tracks",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
episode_id = table.Column<int>(type: "integer", nullable: false),
|
||||
codec = table.Column<string>(type: "text", nullable: true),
|
||||
is_default = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_external = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_forced = table.Column<bool>(type: "boolean", nullable: false),
|
||||
language = table.Column<string>(type: "text", nullable: true),
|
||||
path = table.Column<string>(type: "text", nullable: true),
|
||||
slug = table.Column<string>(type: "text", nullable: false),
|
||||
title = table.Column<string>(type: "text", nullable: true),
|
||||
track_index = table.Column<int>(type: "integer", nullable: false),
|
||||
type = table.Column<string>(type: "stream_type", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_tracks", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_tracks_episodes_episode_id",
|
||||
column: x => x.episode_id,
|
||||
principalTable: "episodes",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tracks_episode_id_type_language_track_index_is_forced",
|
||||
table: "tracks",
|
||||
columns: new[] { "episode_id", "type", "language", "track_index", "is_forced" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_tracks_slug",
|
||||
table: "tracks",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
1502
back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.Designer.cs
generated
Normal file
1502
back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
547
back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs
Normal file
547
back/src/Kyoo.Postgresql/Migrations/20230806025737_initial.cs
Normal file
@ -0,0 +1,547 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:Enum:genre", "action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western")
|
||||
.Annotation("Npgsql:Enum:item_kind", "show,movie,collection")
|
||||
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "collections",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: false),
|
||||
overview = table.Column<string>(type: "text", nullable: true),
|
||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
external_id = table.Column<string>(type: "json", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_collections", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "people",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: false),
|
||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
external_id = table.Column<string>(type: "json", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_people", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "studios",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: false),
|
||||
external_id = table.Column<string>(type: "json", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_studios", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "users",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
username = table.Column<string>(type: "text", nullable: false),
|
||||
email = table.Column<string>(type: "text", nullable: false),
|
||||
password = table.Column<string>(type: "text", nullable: false),
|
||||
permissions = table.Column<string[]>(type: "text[]", nullable: false),
|
||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_users", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "movies",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: false),
|
||||
tagline = table.Column<string>(type: "text", nullable: true),
|
||||
aliases = table.Column<string[]>(type: "text[]", nullable: false),
|
||||
path = table.Column<string>(type: "text", nullable: false),
|
||||
overview = table.Column<string>(type: "text", nullable: true),
|
||||
tags = table.Column<string[]>(type: "text[]", nullable: false),
|
||||
genres = table.Column<Genre[]>(type: "genre[]", nullable: false),
|
||||
status = table.Column<Status>(type: "status", nullable: false),
|
||||
air_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
trailer = table.Column<string>(type: "text", nullable: true),
|
||||
external_id = table.Column<string>(type: "json", nullable: false),
|
||||
studio_id = table.Column<int>(type: "integer", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_movies", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_movies_studios_studio_id",
|
||||
column: x => x.studio_id,
|
||||
principalTable: "studios",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "shows",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: false),
|
||||
tagline = table.Column<string>(type: "text", nullable: true),
|
||||
aliases = table.Column<string[]>(type: "text[]", nullable: false),
|
||||
overview = table.Column<string>(type: "text", nullable: true),
|
||||
tags = table.Column<string[]>(type: "text[]", nullable: false),
|
||||
genres = table.Column<Genre[]>(type: "genre[]", nullable: false),
|
||||
status = table.Column<Status>(type: "status", nullable: false),
|
||||
start_air = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
end_air = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
trailer = table.Column<string>(type: "text", nullable: true),
|
||||
external_id = table.Column<string>(type: "json", nullable: false),
|
||||
studio_id = table.Column<int>(type: "integer", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_shows", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_shows_studios_studio_id",
|
||||
column: x => x.studio_id,
|
||||
principalTable: "studios",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "link_collection_movie",
|
||||
columns: table => new
|
||||
{
|
||||
collection_id = table.Column<int>(type: "integer", nullable: false),
|
||||
movie_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_link_collection_movie", x => new { x.collection_id, x.movie_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_link_collection_movie_collections_collection_id",
|
||||
column: x => x.collection_id,
|
||||
principalTable: "collections",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_link_collection_movie_movies_movie_id",
|
||||
column: x => x.movie_id,
|
||||
principalTable: "movies",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "link_collection_show",
|
||||
columns: table => new
|
||||
{
|
||||
collection_id = table.Column<int>(type: "integer", nullable: false),
|
||||
show_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_link_collection_show_collections_collection_id",
|
||||
column: x => x.collection_id,
|
||||
principalTable: "collections",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_link_collection_show_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "link_user_show",
|
||||
columns: table => new
|
||||
{
|
||||
users_id = table.Column<int>(type: "integer", nullable: false),
|
||||
watched_id = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_link_user_show_shows_watched_id",
|
||||
column: x => x.watched_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_link_user_show_users_users_id",
|
||||
column: x => x.users_id,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "people_roles",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
people_id = table.Column<int>(type: "integer", nullable: false),
|
||||
show_id = table.Column<int>(type: "integer", nullable: true),
|
||||
movie_id = table.Column<int>(type: "integer", nullable: true),
|
||||
type = table.Column<string>(type: "text", nullable: false),
|
||||
role = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_people_roles", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_people_roles_movies_movie_id",
|
||||
column: x => x.movie_id,
|
||||
principalTable: "movies",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_people_roles_people_people_id",
|
||||
column: x => x.people_id,
|
||||
principalTable: "people",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_people_roles_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "seasons",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
show_id = table.Column<int>(type: "integer", nullable: false),
|
||||
season_number = table.Column<int>(type: "integer", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: true),
|
||||
overview = table.Column<string>(type: "text", nullable: true),
|
||||
start_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
end_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
external_id = table.Column<string>(type: "json", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_seasons", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_seasons_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "episodes",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
show_id = table.Column<int>(type: "integer", nullable: false),
|
||||
season_id = table.Column<int>(type: "integer", nullable: true),
|
||||
season_number = table.Column<int>(type: "integer", nullable: true),
|
||||
episode_number = table.Column<int>(type: "integer", nullable: true),
|
||||
absolute_number = table.Column<int>(type: "integer", nullable: true),
|
||||
path = table.Column<string>(type: "text", nullable: false),
|
||||
name = table.Column<string>(type: "text", nullable: true),
|
||||
overview = table.Column<string>(type: "text", nullable: true),
|
||||
release_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
external_id = table.Column<string>(type: "json", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_episodes", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_episodes_seasons_season_id",
|
||||
column: x => x.season_id,
|
||||
principalTable: "seasons",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_episodes_shows_show_id",
|
||||
column: x => x.show_id,
|
||||
principalTable: "shows",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "watched_episode",
|
||||
columns: table => new
|
||||
{
|
||||
user_id = table.Column<int>(type: "integer", nullable: false),
|
||||
episode_id = table.Column<int>(type: "integer", nullable: false),
|
||||
watched_percentage = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_watched_episode", x => new { x.user_id, x.episode_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_watched_episode_episodes_episode_id",
|
||||
column: x => x.episode_id,
|
||||
principalTable: "episodes",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_watched_episode_users_user_id",
|
||||
column: x => x.user_id,
|
||||
principalTable: "users",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_collections_slug",
|
||||
table: "collections",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_episodes_season_id",
|
||||
table: "episodes",
|
||||
column: "season_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_episodes_show_id_season_number_episode_number_absolute_numb",
|
||||
table: "episodes",
|
||||
columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_episodes_slug",
|
||||
table: "episodes",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_link_collection_movie_movie_id",
|
||||
table: "link_collection_movie",
|
||||
column: "movie_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_link_collection_show_show_id",
|
||||
table: "link_collection_show",
|
||||
column: "show_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_link_user_show_watched_id",
|
||||
table: "link_user_show",
|
||||
column: "watched_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_movies_slug",
|
||||
table: "movies",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_movies_studio_id",
|
||||
table: "movies",
|
||||
column: "studio_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_people_slug",
|
||||
table: "people",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_people_roles_movie_id",
|
||||
table: "people_roles",
|
||||
column: "movie_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_people_roles_people_id",
|
||||
table: "people_roles",
|
||||
column: "people_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_people_roles_show_id",
|
||||
table: "people_roles",
|
||||
column: "show_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_seasons_show_id_season_number",
|
||||
table: "seasons",
|
||||
columns: new[] { "show_id", "season_number" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_seasons_slug",
|
||||
table: "seasons",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_shows_slug",
|
||||
table: "shows",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_shows_studio_id",
|
||||
table: "shows",
|
||||
column: "studio_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_studios_slug",
|
||||
table: "studios",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_users_slug",
|
||||
table: "users",
|
||||
column: "slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_watched_episode_episode_id",
|
||||
table: "watched_episode",
|
||||
column: "episode_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "link_collection_movie");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "link_collection_show");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "link_user_show");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "people_roles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "watched_episode");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "collections");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "movies");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "people");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "episodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "seasons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "shows");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "studios");
|
||||
}
|
||||
}
|
||||
}
|
1502
back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs
generated
Normal file
1502
back/src/Kyoo.Postgresql/Migrations/20230806025743_items.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
64
back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs
Normal file
64
back/src/Kyoo.Postgresql/Migrations/20230806025743_items.cs
Normal file
@ -0,0 +1,64 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Items : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"
|
||||
CREATE VIEW library_items AS
|
||||
SELECT
|
||||
s.id, s.slug, s.name, s.tagline, s.aliases, s.overview, s.tags, s.genres, s.status,
|
||||
s.start_air, s.end_air, s.poster_source, s.poster_blurhash, s.thumbnail_source, s.thumbnail_blurhash,
|
||||
s.logo_source, s.logo_blurhash, s.trailer, s.external_id, s.start_air AS air_date, NULL as path,
|
||||
'show'::item_kind AS kind
|
||||
FROM shows AS s
|
||||
UNION ALL
|
||||
SELECT
|
||||
-m.id, m.slug, m.name, m.tagline, m.aliases, m.overview, m.tags, m.genres, m.status,
|
||||
m.air_date as start_air, m.air_date as end_air, m.poster_source, m.poster_blurhash, m.thumbnail_source,
|
||||
m.thumbnail_blurhash, m.logo_source, m.logo_blurhash, m.trailer, m.external_id, m.air_date, m.path,
|
||||
'movie'::item_kind AS kind
|
||||
FROM movies AS m
|
||||
UNION ALL
|
||||
SELECT
|
||||
c.id + 10000 AS id, c.slug, c.name, NULL as tagline, NULL as alises, c.overview, NULL AS tags, NULL AS genres, 'unknown'::status AS status,
|
||||
NULL AS start_air, NULL AS end_air, c.poster_source, c.poster_blurhash, c.thumbnail_source,
|
||||
c.thumbnail_blurhash, c.logo_source, c.logo_blurhash, NULL as trailer, c.external_id, NULL AS air_date, NULL as path,
|
||||
'collection'::item_kind AS kind
|
||||
FROM collections AS c
|
||||
");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// language=PostgreSQL
|
||||
migrationBuilder.Sql(@"DROP VIEW library_items");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -33,11 +33,6 @@ namespace Kyoo.Postgresql
|
||||
/// </summary>
|
||||
public class PostgresContext : DatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The connection string to use.
|
||||
/// </summary>
|
||||
private readonly string _connection;
|
||||
|
||||
/// <summary>
|
||||
/// Is this instance in debug mode?
|
||||
/// </summary>
|
||||
@ -48,12 +43,13 @@ namespace Kyoo.Postgresql
|
||||
/// </summary>
|
||||
private readonly bool _skipConfigure;
|
||||
|
||||
// TOOD: This needs ot be updated but ef-core still does not offer a way to use this.
|
||||
// TODO: This needs ot be updated but ef-core still does not offer a way to use this.
|
||||
[Obsolete]
|
||||
static PostgresContext()
|
||||
{
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemType>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Genre>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemKind>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -78,7 +74,6 @@ namespace Kyoo.Postgresql
|
||||
/// <param name="debugMode">Is this instance in debug mode?</param>
|
||||
public PostgresContext(string connection, bool debugMode)
|
||||
{
|
||||
_connection = connection;
|
||||
_debugMode = debugMode;
|
||||
}
|
||||
|
||||
@ -106,51 +101,12 @@ namespace Kyoo.Postgresql
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasPostgresEnum<Status>();
|
||||
modelBuilder.HasPostgresEnum<ItemType>();
|
||||
|
||||
modelBuilder.Entity<LibraryItem>()
|
||||
.ToView("library_items")
|
||||
.HasKey(x => x.ID);
|
||||
|
||||
modelBuilder.Entity<User>()
|
||||
.Property(x => x.ExtraData)
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
modelBuilder.Entity<LibraryItem>()
|
||||
.Property(x => x.Images)
|
||||
.HasColumnType("jsonb");
|
||||
modelBuilder.Entity<Collection>()
|
||||
.Property(x => x.Images)
|
||||
.HasColumnType("jsonb");
|
||||
modelBuilder.Entity<Show>()
|
||||
.Property(x => x.Images)
|
||||
.HasColumnType("jsonb");
|
||||
modelBuilder.Entity<Season>()
|
||||
.Property(x => x.Images)
|
||||
.HasColumnType("jsonb");
|
||||
modelBuilder.Entity<Episode>()
|
||||
.Property(x => x.Images)
|
||||
.HasColumnType("jsonb");
|
||||
modelBuilder.Entity<People>()
|
||||
.Property(x => x.Images)
|
||||
.HasColumnType("jsonb");
|
||||
modelBuilder.Entity<Provider>()
|
||||
.Property(x => x.Images)
|
||||
.HasColumnType("jsonb");
|
||||
modelBuilder.Entity<User>()
|
||||
.Property(x => x.Images)
|
||||
.HasColumnType("jsonb");
|
||||
modelBuilder.HasPostgresEnum<Genre>();
|
||||
modelBuilder.HasPostgresEnum<ItemKind>();
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string MetadataName<T>()
|
||||
{
|
||||
SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture);
|
||||
return rewriter.RewriteName(typeof(T).Name + nameof(MetadataID));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string LinkName<T, T2>()
|
||||
{
|
||||
|
@ -38,7 +38,7 @@ namespace Kyoo.Swagger
|
||||
{
|
||||
context.OperationDescription.Operation.Security ??= new List<OpenApiSecurityRequirement>();
|
||||
OpenApiSecurityRequirement perms = context.MethodInfo.GetCustomAttributes<UserOnlyAttribute>()
|
||||
.Aggregate(new OpenApiSecurityRequirement(), (agg, cur) =>
|
||||
.Aggregate(new OpenApiSecurityRequirement(), (agg, _) =>
|
||||
{
|
||||
agg[nameof(Kyoo)] = Array.Empty<string>();
|
||||
return agg;
|
||||
@ -60,15 +60,15 @@ namespace Kyoo.Swagger
|
||||
perms = context.MethodInfo.GetCustomAttributes<PartialPermissionAttribute>()
|
||||
.Aggregate(perms, (agg, cur) =>
|
||||
{
|
||||
Group group = controller.Group != Group.Overall
|
||||
Group? group = controller.Group != Group.Overall
|
||||
? controller.Group
|
||||
: cur.Group;
|
||||
string type = controller.Type ?? cur.Type;
|
||||
Kind kind = controller.Type == null
|
||||
Kind? kind = controller.Type == null
|
||||
? controller.Kind
|
||||
: cur.Kind;
|
||||
ICollection<string> permissions = _GetPermissionsList(agg, group);
|
||||
permissions.Add($"{type}.{kind.ToString().ToLower()}");
|
||||
ICollection<string> permissions = _GetPermissionsList(agg, group ?? Group.Overall);
|
||||
permissions.Add($"{type}.{kind!.Value.ToString().ToLower()}");
|
||||
agg[nameof(Kyoo)] = permissions;
|
||||
return agg;
|
||||
});
|
||||
|
@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
@ -87,7 +86,6 @@ namespace Kyoo.Swagger
|
||||
x.IsNullableRaw = false;
|
||||
x.Type = JsonObjectType.String | JsonObjectType.Integer;
|
||||
}));
|
||||
document.SchemaProcessors.Add(new ThumbnailProcessor());
|
||||
|
||||
document.AddSecurity(nameof(Kyoo), new OpenApiSecurityScheme
|
||||
{
|
||||
|
@ -1,49 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Kyoo.Abstractions.Models;
|
||||
using NJsonSchema;
|
||||
using NJsonSchema.Generation;
|
||||
|
||||
namespace Kyoo.Swagger
|
||||
{
|
||||
/// <summary>
|
||||
/// An operation processor to add computed fields of <see cref="IThumbnails"/>.
|
||||
/// </summary>
|
||||
public class ThumbnailProcessor : ISchemaProcessor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Process(SchemaProcessorContext context)
|
||||
{
|
||||
if (!context.ContextualType.OriginalType.IsAssignableTo(typeof(IThumbnails)))
|
||||
return;
|
||||
foreach ((int _, string imageP) in Images.ImageName)
|
||||
{
|
||||
string image = imageP.ToLower()[0] + imageP[1..];
|
||||
context.Schema.Properties.Add(image, new JsonSchemaProperty
|
||||
{
|
||||
Type = JsonObjectType.String,
|
||||
IsNullableRaw = true,
|
||||
Description = $"An url to the {image} of this resource. If this resource does not have an image, " +
|
||||
$"the link will be null. If the kyoo's instance is not capable of handling this kind of image " +
|
||||
$"for the specific resource, this field won't be present."
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Core.Controllers;
|
||||
using Kyoo.Postgresql;
|
||||
using Moq;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Kyoo.Tests.Database
|
||||
@ -37,31 +38,28 @@ namespace Kyoo.Tests.Database
|
||||
{
|
||||
Context = new PostgresTestContext(postgres, output);
|
||||
|
||||
ProviderRepository provider = new(_NewContext());
|
||||
LibraryRepository library = new(_NewContext(), provider);
|
||||
CollectionRepository collection = new(_NewContext(), provider);
|
||||
GenreRepository genre = new(_NewContext());
|
||||
StudioRepository studio = new(_NewContext(), provider);
|
||||
PeopleRepository people = new(_NewContext(), provider,
|
||||
new Lazy<IShowRepository>(() => LibraryManager.ShowRepository));
|
||||
ShowRepository show = new(_NewContext(), studio, people, genre, provider);
|
||||
SeasonRepository season = new(_NewContext(), show, provider);
|
||||
LibraryItemRepository libraryItem = new(_NewContext(),
|
||||
new Lazy<ILibraryRepository>(() => LibraryManager.LibraryRepository));
|
||||
EpisodeRepository episode = new(_NewContext(), show, provider);
|
||||
UserRepository user = new(_NewContext());
|
||||
Mock<IThumbnailsManager> thumbs = new();
|
||||
CollectionRepository collection = new(_NewContext(), thumbs.Object);
|
||||
StudioRepository studio = new(_NewContext(), thumbs.Object);
|
||||
PeopleRepository people = new(_NewContext(),
|
||||
new Lazy<IShowRepository>(() => LibraryManager.ShowRepository),
|
||||
thumbs.Object);
|
||||
MovieRepository movies = new(_NewContext(), studio, people, thumbs.Object);
|
||||
ShowRepository show = new(_NewContext(), studio, people, thumbs.Object);
|
||||
SeasonRepository season = new(_NewContext(), show, thumbs.Object);
|
||||
LibraryItemRepository libraryItem = new(_NewContext(), thumbs.Object);
|
||||
EpisodeRepository episode = new(_NewContext(), show, thumbs.Object);
|
||||
UserRepository user = new(_NewContext(), thumbs.Object);
|
||||
|
||||
LibraryManager = new LibraryManager(new IBaseRepository[] {
|
||||
provider,
|
||||
library,
|
||||
libraryItem,
|
||||
collection,
|
||||
movies,
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
people,
|
||||
studio,
|
||||
genre,
|
||||
user
|
||||
});
|
||||
}
|
||||
|
@ -19,7 +19,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
@ -30,7 +29,7 @@ using Xunit;
|
||||
namespace Kyoo.Tests.Database
|
||||
{
|
||||
public abstract class RepositoryTests<T> : IDisposable, IAsyncDisposable
|
||||
where T : class, IResource, new()
|
||||
where T : class, IResource
|
||||
{
|
||||
protected readonly RepositoryActivator Repositories;
|
||||
private readonly IRepository<T> _repository;
|
||||
@ -63,7 +62,7 @@ namespace Kyoo.Tests.Database
|
||||
[Fact]
|
||||
public async Task GetByIdTest()
|
||||
{
|
||||
T value = await _repository.Get(TestSample.Get<T>().ID);
|
||||
T value = await _repository.Get(TestSample.Get<T>().Id);
|
||||
KAssert.DeepEqual(TestSample.Get<T>(), value);
|
||||
}
|
||||
|
||||
@ -89,7 +88,7 @@ namespace Kyoo.Tests.Database
|
||||
[Fact]
|
||||
public async Task DeleteByIdTest()
|
||||
{
|
||||
await _repository.Delete(TestSample.Get<T>().ID);
|
||||
await _repository.Delete(TestSample.Get<T>().Id);
|
||||
Assert.Equal(0, await _repository.GetCount());
|
||||
}
|
||||
|
||||
@ -114,23 +113,11 @@ namespace Kyoo.Tests.Database
|
||||
await _repository.Delete(TestSample.Get<T>());
|
||||
|
||||
T expected = TestSample.Get<T>();
|
||||
expected.ID = 0;
|
||||
expected.Id = 0;
|
||||
await _repository.Create(expected);
|
||||
KAssert.DeepEqual(expected, await _repository.Get(expected.Slug));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateNullTest()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => _repository.Create(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateIfNotExistNullTest()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => _repository.CreateIfNotExists(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public virtual async Task CreateIfNotExistTest()
|
||||
{
|
||||
@ -140,22 +127,16 @@ namespace Kyoo.Tests.Database
|
||||
KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get<T>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EditNullTest()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => _repository.Edit(null!, false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EditNonExistingTest()
|
||||
{
|
||||
await Assert.ThrowsAsync<ItemNotFoundException>(() => _repository.Edit(new T { ID = 56 }, false));
|
||||
}
|
||||
// [Fact]
|
||||
// public async Task EditNonExistingTest()
|
||||
// {
|
||||
// await Assert.ThrowsAsync<ItemNotFoundException>(() => _repository.Edit(new T { Id = 56 }));
|
||||
// }
|
||||
|
||||
[Fact]
|
||||
public async Task GetExpressionIDTest()
|
||||
{
|
||||
KAssert.DeepEqual(TestSample.Get<T>(), await _repository.Get(x => x.ID == TestSample.Get<T>().ID));
|
||||
KAssert.DeepEqual(TestSample.Get<T>(), await _repository.Get(x => x.Id == TestSample.Get<T>().Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -170,12 +151,6 @@ namespace Kyoo.Tests.Database
|
||||
await Assert.ThrowsAsync<ItemNotFoundException>(() => _repository.Get(x => x.Slug == "non-existing"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetExpressionNullTest()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() => _repository.Get((Expression<Func<T, bool>>)null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOrDefaultTest()
|
||||
{
|
||||
|
@ -66,40 +66,29 @@ namespace Kyoo.Tests.Database
|
||||
Assert.Equal("2!", ret.Slug);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateWithoutNameTest()
|
||||
{
|
||||
Collection collection = TestSample.GetNew<Collection>();
|
||||
collection.Name = null;
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(collection));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateWithExternalIdTest()
|
||||
{
|
||||
Collection collection = TestSample.GetNew<Collection>();
|
||||
collection.ExternalIDs = new[]
|
||||
collection.ExternalId = new Dictionary<string, MetadataId>
|
||||
{
|
||||
new MetadataID
|
||||
["1"] = new()
|
||||
{
|
||||
Provider = TestSample.Get<Provider>(),
|
||||
Link = "link",
|
||||
DataID = "id"
|
||||
DataId = "id"
|
||||
},
|
||||
new MetadataID
|
||||
["2"] = new()
|
||||
{
|
||||
Provider = TestSample.GetNew<Provider>(),
|
||||
Link = "new-provider-link",
|
||||
DataID = "new-id"
|
||||
DataId = "new-id"
|
||||
}
|
||||
};
|
||||
await _repository.Create(collection);
|
||||
|
||||
Collection retrieved = await _repository.Get(2);
|
||||
await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs);
|
||||
Assert.Equal(2, retrieved.ExternalIDs.Count);
|
||||
KAssert.DeepEqual(collection.ExternalIDs.First(), retrieved.ExternalIDs.First());
|
||||
KAssert.DeepEqual(collection.ExternalIDs.Last(), retrieved.ExternalIDs.Last());
|
||||
Assert.Equal(2, retrieved.ExternalId.Count);
|
||||
KAssert.DeepEqual(collection.ExternalId.First(), retrieved.ExternalId.First());
|
||||
KAssert.DeepEqual(collection.ExternalId.Last(), retrieved.ExternalId.Last());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -107,11 +96,8 @@ namespace Kyoo.Tests.Database
|
||||
{
|
||||
Collection value = await _repository.Get(TestSample.Get<Collection>().Slug);
|
||||
value.Name = "New Title";
|
||||
value.Images = new Dictionary<int, string>
|
||||
{
|
||||
[Images.Poster] = "new-poster"
|
||||
};
|
||||
await _repository.Edit(value, false);
|
||||
value.Poster = new Image("new-poster");
|
||||
await _repository.Edit(value);
|
||||
|
||||
await using DatabaseContext database = Repositories.Context.New();
|
||||
Collection retrieved = await database.Collections.FirstAsync();
|
||||
@ -123,22 +109,18 @@ namespace Kyoo.Tests.Database
|
||||
public async Task EditMetadataTest()
|
||||
{
|
||||
Collection value = await _repository.Get(TestSample.Get<Collection>().Slug);
|
||||
value.ExternalIDs = new[]
|
||||
value.ExternalId = new Dictionary<string, MetadataId>
|
||||
{
|
||||
new MetadataID
|
||||
["test"] = new()
|
||||
{
|
||||
Provider = TestSample.Get<Provider>(),
|
||||
Link = "link",
|
||||
DataID = "id"
|
||||
DataId = "id"
|
||||
},
|
||||
};
|
||||
await _repository.Edit(value, false);
|
||||
await _repository.Edit(value);
|
||||
|
||||
await using DatabaseContext database = Repositories.Context.New();
|
||||
Collection retrieved = await database.Collections
|
||||
.Include(x => x.ExternalIDs)
|
||||
.ThenInclude(x => x.Provider)
|
||||
.FirstAsync();
|
||||
Collection retrieved = await database.Collections.FirstAsync();
|
||||
|
||||
KAssert.DeepEqual(value, retrieved);
|
||||
}
|
||||
@ -147,41 +129,33 @@ namespace Kyoo.Tests.Database
|
||||
public async Task AddMetadataTest()
|
||||
{
|
||||
Collection value = await _repository.Get(TestSample.Get<Collection>().Slug);
|
||||
value.ExternalIDs = new List<MetadataID>
|
||||
value.ExternalId = new Dictionary<string, MetadataId>
|
||||
{
|
||||
new()
|
||||
["toto"] = new()
|
||||
{
|
||||
Provider = TestSample.Get<Provider>(),
|
||||
Link = "link",
|
||||
DataID = "id"
|
||||
DataId = "id"
|
||||
},
|
||||
};
|
||||
await _repository.Edit(value, false);
|
||||
await _repository.Edit(value);
|
||||
|
||||
{
|
||||
await using DatabaseContext database = Repositories.Context.New();
|
||||
Collection retrieved = await database.Collections
|
||||
.Include(x => x.ExternalIDs)
|
||||
.ThenInclude(x => x.Provider)
|
||||
.FirstAsync();
|
||||
Collection retrieved = await database.Collections.FirstAsync();
|
||||
|
||||
KAssert.DeepEqual(value, retrieved);
|
||||
}
|
||||
|
||||
value.ExternalIDs.Add(new MetadataID
|
||||
value.ExternalId.Add("test", new MetadataId
|
||||
{
|
||||
Provider = TestSample.GetNew<Provider>(),
|
||||
Link = "link",
|
||||
DataID = "id"
|
||||
DataId = "id"
|
||||
});
|
||||
await _repository.Edit(value, false);
|
||||
await _repository.Edit(value);
|
||||
|
||||
{
|
||||
await using DatabaseContext database = Repositories.Context.New();
|
||||
Collection retrieved = await database.Collections
|
||||
.Include(x => x.ExternalIDs)
|
||||
.ThenInclude(x => x.Provider)
|
||||
.FirstAsync();
|
||||
Collection retrieved = await database.Collections.FirstAsync();
|
||||
|
||||
KAssert.DeepEqual(value, retrieved);
|
||||
}
|
||||
|
@ -55,12 +55,11 @@ namespace Kyoo.Tests.Database
|
||||
{
|
||||
Episode episode = await _repository.Get(1);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
|
||||
Show show = new()
|
||||
await Repositories.LibraryManager.ShowRepository.Patch(episode.ShowId, (x) =>
|
||||
{
|
||||
ID = episode.ShowID,
|
||||
Slug = "new-slug"
|
||||
};
|
||||
await Repositories.LibraryManager.ShowRepository.Edit(show, false);
|
||||
x.Slug = "new-slug";
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
episode = await _repository.Get(1);
|
||||
Assert.Equal("new-slug-s1e1", episode.Slug);
|
||||
}
|
||||
@ -70,12 +69,11 @@ namespace Kyoo.Tests.Database
|
||||
{
|
||||
Episode episode = await _repository.Get(1);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
|
||||
episode = await _repository.Edit(new Episode
|
||||
episode = await _repository.Patch(1, (x) =>
|
||||
{
|
||||
ID = 1,
|
||||
SeasonNumber = 2,
|
||||
ShowID = 1
|
||||
}, false);
|
||||
x.SeasonNumber = 2;
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e1", episode.Slug);
|
||||
episode = await _repository.Get(1);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e1", episode.Slug);
|
||||
@ -86,12 +84,11 @@ namespace Kyoo.Tests.Database
|
||||
{
|
||||
Episode episode = await _repository.Get(1);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
|
||||
episode = await _repository.Edit(new Episode
|
||||
episode = await Repositories.LibraryManager.Patch<Episode>(episode.Id, (x) =>
|
||||
{
|
||||
ID = 1,
|
||||
EpisodeNumber = 2,
|
||||
ShowID = 1
|
||||
}, false);
|
||||
x.EpisodeNumber = 2;
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
||||
episode = await _repository.Get(1);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
||||
@ -100,12 +97,12 @@ namespace Kyoo.Tests.Database
|
||||
[Fact]
|
||||
public async Task EpisodeCreationSlugTest()
|
||||
{
|
||||
Episode episode = await _repository.Create(new Episode
|
||||
{
|
||||
ShowID = TestSample.Get<Show>().ID,
|
||||
SeasonNumber = 2,
|
||||
EpisodeNumber = 4
|
||||
});
|
||||
Episode model = TestSample.Get<Episode>();
|
||||
model.Id = 0;
|
||||
model.ShowId = TestSample.Get<Show>().Id;
|
||||
model.SeasonNumber = 2;
|
||||
model.EpisodeNumber = 4;
|
||||
Episode episode = await _repository.Create(model);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e4", episode.Slug);
|
||||
}
|
||||
|
||||
@ -127,12 +124,11 @@ namespace Kyoo.Tests.Database
|
||||
public async Task SlugEditAbsoluteTest()
|
||||
{
|
||||
Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode());
|
||||
Show show = new()
|
||||
await Repositories.LibraryManager.ShowRepository.Patch(episode.ShowId, (x) =>
|
||||
{
|
||||
ID = episode.ShowID,
|
||||
Slug = "new-slug"
|
||||
};
|
||||
await Repositories.LibraryManager.ShowRepository.Edit(show, false);
|
||||
x.Slug = "new-slug";
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
episode = await _repository.Get(2);
|
||||
Assert.Equal($"new-slug-3", episode.Slug);
|
||||
}
|
||||
@ -141,12 +137,11 @@ namespace Kyoo.Tests.Database
|
||||
public async Task AbsoluteNumberEditTest()
|
||||
{
|
||||
await _repository.Create(TestSample.GetAbsoluteEpisode());
|
||||
Episode episode = await _repository.Edit(new Episode
|
||||
Episode episode = await _repository.Patch(2, (x) =>
|
||||
{
|
||||
ID = 2,
|
||||
AbsoluteNumber = 56,
|
||||
ShowID = 1
|
||||
}, false);
|
||||
x.AbsoluteNumber = 56;
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-56", episode.Slug);
|
||||
episode = await _repository.Get(2);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-56", episode.Slug);
|
||||
@ -156,13 +151,12 @@ namespace Kyoo.Tests.Database
|
||||
public async Task AbsoluteToNormalEditTest()
|
||||
{
|
||||
await _repository.Create(TestSample.GetAbsoluteEpisode());
|
||||
Episode episode = await _repository.Edit(new Episode
|
||||
Episode episode = await _repository.Patch(2, (x) =>
|
||||
{
|
||||
ID = 2,
|
||||
SeasonNumber = 1,
|
||||
EpisodeNumber = 2,
|
||||
ShowID = 1
|
||||
}, false);
|
||||
x.SeasonNumber = 1;
|
||||
x.EpisodeNumber = 2;
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
||||
episode = await _repository.Get(2);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
||||
@ -174,72 +168,44 @@ namespace Kyoo.Tests.Database
|
||||
Episode episode = await _repository.Get(1);
|
||||
episode.SeasonNumber = null;
|
||||
episode.AbsoluteNumber = 12;
|
||||
episode = await _repository.Edit(episode, true);
|
||||
episode = await _repository.Edit(episode);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-12", episode.Slug);
|
||||
episode = await _repository.Get(1);
|
||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-12", episode.Slug);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MovieEpisodeTest()
|
||||
{
|
||||
Episode episode = await _repository.Create(TestSample.GetMovieEpisode());
|
||||
Assert.Equal(TestSample.Get<Show>().Slug, episode.Slug);
|
||||
episode = await _repository.Get(3);
|
||||
Assert.Equal(TestSample.Get<Show>().Slug, episode.Slug);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MovieEpisodeEditTest()
|
||||
{
|
||||
await _repository.Create(TestSample.GetMovieEpisode());
|
||||
await Repositories.LibraryManager.Edit(new Show
|
||||
{
|
||||
ID = 1,
|
||||
Slug = "john-wick"
|
||||
}, false);
|
||||
Episode episode = await _repository.Get(3);
|
||||
Assert.Equal("john-wick", episode.Slug);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateWithExternalIdTest()
|
||||
{
|
||||
Episode value = TestSample.GetNew<Episode>();
|
||||
value.ExternalIDs = new[]
|
||||
value.ExternalId = new Dictionary<string, MetadataId>
|
||||
{
|
||||
new MetadataID
|
||||
["2"] = new()
|
||||
{
|
||||
Provider = TestSample.Get<Provider>(),
|
||||
Link = "link",
|
||||
DataID = "id"
|
||||
DataId = "id"
|
||||
},
|
||||
new MetadataID
|
||||
["3"] = new()
|
||||
{
|
||||
Provider = TestSample.GetNew<Provider>(),
|
||||
Link = "new-provider-link",
|
||||
DataID = "new-id"
|
||||
DataId = "new-id"
|
||||
}
|
||||
};
|
||||
await _repository.Create(value);
|
||||
|
||||
Episode retrieved = await _repository.Get(2);
|
||||
await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs);
|
||||
Assert.Equal(2, retrieved.ExternalIDs.Count);
|
||||
KAssert.DeepEqual(value.ExternalIDs.First(), retrieved.ExternalIDs.First());
|
||||
KAssert.DeepEqual(value.ExternalIDs.Last(), retrieved.ExternalIDs.Last());
|
||||
Assert.Equal(2, retrieved.ExternalId.Count);
|
||||
KAssert.DeepEqual(value.ExternalId.First(), retrieved.ExternalId.First());
|
||||
KAssert.DeepEqual(value.ExternalId.Last(), retrieved.ExternalId.Last());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EditTest()
|
||||
{
|
||||
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
|
||||
value.Title = "New Title";
|
||||
value.Images = new Dictionary<int, string>
|
||||
{
|
||||
[Images.Poster] = "new-poster"
|
||||
};
|
||||
await _repository.Edit(value, false);
|
||||
value.Name = "New Title";
|
||||
value.Poster = new Image("poster");
|
||||
await _repository.Edit(value);
|
||||
|
||||
await using DatabaseContext database = Repositories.Context.New();
|
||||
Episode retrieved = await database.Episodes.FirstAsync();
|
||||
@ -251,22 +217,18 @@ namespace Kyoo.Tests.Database
|
||||
public async Task EditMetadataTest()
|
||||
{
|
||||
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
|
||||
value.ExternalIDs = new[]
|
||||
value.ExternalId = new Dictionary<string, MetadataId>
|
||||
{
|
||||
new MetadataID
|
||||
["1"] = new()
|
||||
{
|
||||
Provider = TestSample.Get<Provider>(),
|
||||
Link = "link",
|
||||
DataID = "id"
|
||||
DataId = "id"
|
||||
},
|
||||
};
|
||||
await _repository.Edit(value, false);
|
||||
await _repository.Edit(value);
|
||||
|
||||
await using DatabaseContext database = Repositories.Context.New();
|
||||
Episode retrieved = await database.Episodes
|
||||
.Include(x => x.ExternalIDs)
|
||||
.ThenInclude(x => x.Provider)
|
||||
.FirstAsync();
|
||||
Episode retrieved = await database.Episodes.FirstAsync();
|
||||
|
||||
KAssert.DeepEqual(value, retrieved);
|
||||
}
|
||||
@ -275,41 +237,33 @@ namespace Kyoo.Tests.Database
|
||||
public async Task AddMetadataTest()
|
||||
{
|
||||
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
|
||||
value.ExternalIDs = new List<MetadataID>
|
||||
value.ExternalId = new Dictionary<string, MetadataId>
|
||||
{
|
||||
new()
|
||||
["toto"] = new()
|
||||
{
|
||||
Provider = TestSample.Get<Provider>(),
|
||||
Link = "link",
|
||||
DataID = "id"
|
||||
DataId = "id"
|
||||
},
|
||||
};
|
||||
await _repository.Edit(value, false);
|
||||
await _repository.Edit(value);
|
||||
|
||||
{
|
||||
await using DatabaseContext database = Repositories.Context.New();
|
||||
Episode retrieved = await database.Episodes
|
||||
.Include(x => x.ExternalIDs)
|
||||
.ThenInclude(x => x.Provider)
|
||||
.FirstAsync();
|
||||
Episode retrieved = await database.Episodes.FirstAsync();
|
||||
|
||||
KAssert.DeepEqual(value, retrieved);
|
||||
}
|
||||
|
||||
value.ExternalIDs.Add(new MetadataID
|
||||
value.ExternalId.Add("test", new MetadataId
|
||||
{
|
||||
Provider = TestSample.GetNew<Provider>(),
|
||||
Link = "link",
|
||||
DataID = "id"
|
||||
DataId = "id"
|
||||
});
|
||||
await _repository.Edit(value, false);
|
||||
await _repository.Edit(value);
|
||||
|
||||
{
|
||||
await using DatabaseContext database = Repositories.Context.New();
|
||||
Episode retrieved = await database.Episodes
|
||||
.Include(x => x.ExternalIDs)
|
||||
.ThenInclude(x => x.Provider)
|
||||
.FirstAsync();
|
||||
Episode retrieved = await database.Episodes.FirstAsync();
|
||||
|
||||
KAssert.DeepEqual(value, retrieved);
|
||||
}
|
||||
@ -323,12 +277,10 @@ namespace Kyoo.Tests.Database
|
||||
[InlineData("SuPeR")]
|
||||
public async Task SearchTest(string query)
|
||||
{
|
||||
Episode value = new()
|
||||
{
|
||||
Title = "This is a test super title",
|
||||
ShowID = 1,
|
||||
AbsoluteNumber = 2
|
||||
};
|
||||
Episode value = TestSample.Get<Episode>();
|
||||
value.Id = 0;
|
||||
value.Name = "This is a test super title";
|
||||
value.EpisodeNumber = 56;
|
||||
await _repository.Create(value);
|
||||
ICollection<Episode> ret = await _repository.Search(query);
|
||||
value.Show = TestSample.Get<Show>();
|
||||
@ -342,9 +294,9 @@ namespace Kyoo.Tests.Database
|
||||
await _repository.Delete(TestSample.Get<Episode>());
|
||||
|
||||
Episode expected = TestSample.Get<Episode>();
|
||||
expected.ID = 0;
|
||||
expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get<Show>())).ID;
|
||||
expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get<Season>())).ID;
|
||||
expected.Id = 0;
|
||||
expected.ShowId = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get<Show>())).Id;
|
||||
expected.SeasonId = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get<Season>())).Id;
|
||||
await _repository.Create(expected);
|
||||
KAssert.DeepEqual(expected, await _repository.Get(expected.Slug));
|
||||
}
|
||||
@ -355,8 +307,8 @@ namespace Kyoo.Tests.Database
|
||||
Episode expected = TestSample.Get<Episode>();
|
||||
KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(TestSample.Get<Episode>()));
|
||||
await _repository.Delete(TestSample.Get<Episode>());
|
||||
expected.ShowID = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get<Show>())).ID;
|
||||
expected.SeasonID = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get<Season>())).ID;
|
||||
expected.ShowId = (await Repositories.LibraryManager.ShowRepository.Create(TestSample.Get<Show>())).Id;
|
||||
expected.SeasonId = (await Repositories.LibraryManager.SeasonRepository.Create(TestSample.Get<Season>())).Id;
|
||||
KAssert.DeepEqual(expected, await _repository.CreateIfNotExists(expected));
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user