Rework sort system to allow multiples keys

This commit is contained in:
Zoe Roux 2023-03-13 01:34:16 +09:00
parent e32c09f48f
commit c0c263c4d7
30 changed files with 464 additions and 489 deletions

View File

@ -16,6 +16,7 @@ dotnet_diagnostic.IDE0058.severity = none
dotnet_diagnostic.IDE0046.severity = none
dotnet_diagnostic.CA1848.severity = none
dotnet_diagnostic.CA2007.severity = none
dotnet_diagnostic.CA1716.severity = none
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
csharp_using_directive_placement = outside_namespace:warning

View File

@ -19,7 +19,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Abstractions.Models;
@ -256,7 +255,7 @@ namespace Kyoo.Abstractions.Controllers
/// <typeparam name="T">The type of the source object</typeparam>
/// <typeparam name="T2">The related resource's type</typeparam>
/// <returns>The param <paramref name="obj"/></returns>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}}, bool)"/>
/// <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)
@ -274,7 +273,7 @@ namespace Kyoo.Abstractions.Controllers
/// <typeparam name="T">The type of the source object</typeparam>
/// <typeparam name="T2">The related resource's type</typeparam>
/// <returns>The param <paramref name="obj"/></returns>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,T2}}, bool)"/>
/// <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)
@ -291,8 +290,8 @@ namespace Kyoo.Abstractions.Controllers
/// </param>
/// <typeparam name="T">The type of the source object</typeparam>
/// <returns>The param <paramref name="obj"/></returns>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,T2}}, bool)"/>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}}, bool)"/>
/// <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)
where T : class, IResource;
@ -305,8 +304,8 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="force">
/// <c>true</c> if you want to load the relation even if it is not null, <c>false</c> otherwise.
/// </param>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,T2}}, bool)"/>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}}, bool)"/>
/// <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{T}(T, string, bool)"/>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task Load([NotNull] IResource obj, string memberName, bool force = false);
@ -325,21 +324,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<LibraryItem> sort = default,
Pagination limit = default);
/// <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">A sort by method</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,
[Optional] Expression<Func<LibraryItem, bool>> where,
Expression<Func<LibraryItem, object>> sort,
Pagination limit = default
) => GetItemsFromLibrary(id, where, new Sort<LibraryItem>(sort), limit);
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
@ -354,21 +338,6 @@ namespace Kyoo.Abstractions.Controllers
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">A sort by method</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,
[Optional] Expression<Func<LibraryItem, bool>> where,
Expression<Func<LibraryItem, object>> sort,
Pagination limit = default
) => GetItemsFromLibrary(slug, where, new Sort<LibraryItem>(sort), limit);
/// <summary>
/// Get people's roles from a show.
/// </summary>
@ -383,21 +352,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default);
/// <summary>
/// Get people's roles from a show.
/// </summary>
/// <param name="showID">The ID of the show</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <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,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetPeopleFromShow(showID, where, new Sort<PeopleRole>(sort), limit);
/// <summary>
/// Get people's roles from a show.
/// </summary>
@ -412,21 +366,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default);
/// <summary>
/// Get people's roles from a show.
/// </summary>
/// <param name="showSlug">The slug of the show</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <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,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetPeopleFromShow(showSlug, where, new Sort<PeopleRole>(sort), limit);
/// <summary>
/// Get people's roles from a person.
/// </summary>
@ -441,21 +380,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default);
/// <summary>
/// Get people's roles from a person.
/// </summary>
/// <param name="id">The id of the person</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <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,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetRolesFromPeople(id, where, new Sort<PeopleRole>(sort), limit);
/// <summary>
/// Get people's roles from a person.
/// </summary>
@ -470,21 +394,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default);
/// <summary>
/// Get people's roles from a person.
/// </summary>
/// <param name="slug">The slug of the person</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <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,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetRolesFromPeople(slug, where, new Sort<PeopleRole>(sort), limit);
/// <summary>
/// Setup relations between a show, a library and a collection
/// </summary>
@ -516,22 +425,6 @@ namespace Kyoo.Abstractions.Controllers
Pagination limit = default)
where T : class, IResource;
/// <summary>
/// Get all resources with filters
/// </summary>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by function</param>
/// <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>([Optional] Expression<Func<T, bool>> where,
Expression<Func<T, object>> sort,
Pagination limit = default)
where T : class, IResource
{
return GetAll(where, new Sort<T>(sort), limit);
}
/// <summary>
/// Get the count of resources that match the filter
/// </summary>

View File

@ -19,7 +19,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Abstractions.Models;
@ -106,19 +105,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<T> sort = default,
Pagination limit = default);
/// <summary>
/// Get every resources that match all filters
/// </summary>
/// <param name="where">A filter predicate</param>
/// <param name="sort">A sort by predicate. The order is ascending.</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([Optional] Expression<Func<T, bool>> where,
Expression<Func<T, object>> sort,
Pagination limit = default
) => GetAll(where, new Sort<T>(sort), limit);
/// <summary>
/// Get the number of resources that match the filter's predicate.
/// </summary>
@ -352,21 +338,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<LibraryItem> sort = default,
Pagination limit = default);
/// <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">A sort by method</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,
[Optional] Expression<Func<LibraryItem, bool>> where,
Expression<Func<LibraryItem, object>> sort,
Pagination limit = default
) => GetFromLibrary(id, where, new Sort<LibraryItem>(sort), limit);
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
@ -380,21 +351,6 @@ namespace Kyoo.Abstractions.Controllers
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">A sort by method</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,
[Optional] Expression<Func<LibraryItem, bool>> where,
Expression<Func<LibraryItem, object>> sort,
Pagination limit = default
) => GetFromLibrary(slug, where, new Sort<LibraryItem>(sort), limit);
}
/// <summary>
@ -431,21 +387,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default);
/// <summary>
/// Get people's roles from a show.
/// </summary>
/// <param name="showID">The ID of the show</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <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,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetFromShow(showID, where, new Sort<PeopleRole>(sort), limit);
/// <summary>
/// Get people's roles from a show.
/// </summary>
@ -460,21 +401,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default);
/// <summary>
/// Get people's roles from a show.
/// </summary>
/// <param name="showSlug">The slug of the show</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <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,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetFromShow(showSlug, where, new Sort<PeopleRole>(sort), limit);
/// <summary>
/// Get people's roles from a person.
/// </summary>
@ -489,21 +415,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default);
/// <summary>
/// Get people's roles from a person.
/// </summary>
/// <param name="id">The id of the person</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <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,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetFromPeople(id, where, new Sort<PeopleRole>(sort), limit);
/// <summary>
/// Get people's roles from a person.
/// </summary>
@ -517,21 +428,6 @@ namespace Kyoo.Abstractions.Controllers
Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default,
Pagination limit = default);
/// <summary>
/// Get people's roles from a person.
/// </summary>
/// <param name="slug">The slug of the person</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <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,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetFromPeople(slug, where, new Sort<PeopleRole>(sort), limit);
}
/// <summary>
@ -551,21 +447,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<MetadataID> sort = default,
Pagination limit = default)
where T : class, IMetadata;
/// <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">A sort by expression</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>([Optional] Expression<Func<MetadataID, bool>> where,
Expression<Func<MetadataID, object>> sort,
Pagination limit = default
)
where T : class, IMetadata
=> GetMetadataID<T>(where, new Sort<MetadataID>(sort), limit);
}
/// <summary>

View File

@ -113,7 +113,7 @@ namespace Kyoo.Abstractions.Models.Utils
/// <summary>
/// A matcher overload for nullable IDs. See
/// <see cref="Matcher{T}(System.Linq.Expressions.Expression{System.Func{T,int}},System.Linq.Expressions.Expression{System.Func{T,string}})"/>
/// <see cref="Matcher{T}(Expression{Func{T,int}},Expression{Func{T,string}})"/>
/// for more details.
/// </summary>
/// <param name="idGetter">An expression to retrieve an ID from the type <typeparamref name="T"/>.</param>

View File

@ -33,15 +33,22 @@ namespace Kyoo.Abstractions.Controllers
/// </summary>
public int? AfterID { get; }
/// <summary>
/// Should the previous page be returned instead of the next?
/// </summary>
public bool Reverse { get; }
/// <summary>
/// Create a new <see cref="Pagination"/> instance.
/// </summary>
/// <param name="count">Set the <see cref="Count"/> value</param>
/// <param name="afterID">Set the <see cref="AfterID"/> value. If not specified, it will start from the start</param>
public Pagination(int count, int? afterID = null)
/// <param name="reverse">Should the previous page be returned instead of the next?</param>
public Pagination(int count, int? afterID = null, bool reverse = false)
{
Count = count;
AfterID = afterID;
Reverse = reverse;
}
/// <summary>

View File

@ -17,7 +17,9 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Controllers
@ -26,63 +28,73 @@ namespace Kyoo.Abstractions.Controllers
/// Information about how a query should be sorted. What factor should decide the sort and in which order.
/// </summary>
/// <typeparam name="T">For witch type this sort applies</typeparam>
public readonly struct Sort<T>
public record Sort<T>
{
/// <summary>
/// The sort key. This member will be used to sort the results.
/// Sort by a specific key
/// </summary>
public Expression<Func<T, object>> Key { get; }
/// <summary>
/// <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.
/// </summary>
public bool Descendant { get; }
/// <summary>
/// Create a new <see cref="Sort{T}"/> instance.
/// </summary>
/// <param name="key">The sort key given. It is assigned to <see cref="Key"/>.</param>
/// <param name="descendant">Should this be in descendant order? The default is false.</param>
/// <exception cref="ArgumentException">If the given key is not a member.</exception>
public Sort(Expression<Func<T, object>> key, bool descendant = false)
/// </param>
public record By(string key, bool desendant = false) : Sort<T>
{
Key = key;
Descendant = descendant;
if (!Utility.IsPropertyExpression(Key))
throw new ArgumentException("The given sort key is not valid.");
}
/// <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">
/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order.
/// </param>
public By(Expression<Func<T, object>> key, bool desendant = false)
: this(Utility.GetPropertyName(key), desendant) { }
/// <summary>
/// Create a new <see cref="Sort{T}"/> instance from a key's name (case insensitive).
/// </summary>
/// <param name="sortBy">A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key".</param>
/// <exception cref="ArgumentException">An invalid key or sort specifier as been given.</exception>
public Sort(string sortBy)
/// <returns>A <see cref="Sort{T}"/> for the given string</returns>
public static new By From(string sortBy)
{
if (string.IsNullOrEmpty(sortBy))
{
Key = null;
Descendant = false;
return;
}
string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy;
string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null;
ParameterExpression param = Expression.Parameter(typeof(T), "x");
MemberExpression property = Expression.Property(param, key);
Key = property.Type.IsValueType
? Expression.Lambda<Func<T, object>>(Expression.Convert(property, typeof(object)), param)
: Expression.Lambda<Func<T, object>>(property, param);
Descendant = order switch
bool desendant = order switch
{
"desc" => true,
"asc" => false,
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);
if (property == null)
throw new ArgumentException("The given sort key is not valid.");
return new By(property.Name, desendant);
}
}
/// <summary>
/// Sort by multiple keys.
/// </summary>
/// <param name="list">The list of keys to sort by.</param>
public record Conglomerate(params By[] list) : Sort<T>;
/// <summary>The default sort method for the given type.</summary>
public record Default : Sort<T>;
/// <summary>
/// Create a new <see cref="Sort{T}"/> instance from a key's name (case insensitive).
/// </summary>
/// <param name="sortBy">A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key".</param>
/// <exception cref="ArgumentException">An invalid key or sort specifier as been given.</exception>
/// <returns>A <see cref="Sort{T}"/> for the given string</returns>
public static Sort<T> From(string sortBy)
{
if (string.IsNullOrEmpty(sortBy))
return new Default();
if (sortBy.Contains(','))
return new Conglomerate(sortBy.Split(',').Select(By.From).ToArray());
return By.From(sortBy);
}
}
}

View File

@ -163,48 +163,46 @@ namespace Kyoo.Abstractions.Models
await library.Load(ep, x => x.Show);
await library.Load(ep, x => x.Tracks);
Episode previous = null;
Episode next = null;
if (!ep.Show.IsMovie)
{
if (ep.AbsoluteNumber != null)
{
previous = await library.GetOrDefault(
x => x.ShowID == ep.ShowID && x.AbsoluteNumber < ep.AbsoluteNumber,
new Sort<Episode>(x => x.AbsoluteNumber, true)
);
next = await library.GetOrDefault(
x => x.ShowID == ep.ShowID && x.AbsoluteNumber > ep.AbsoluteNumber,
new Sort<Episode>(x => x.AbsoluteNumber)
);
}
else if (ep.SeasonNumber != null && ep.EpisodeNumber != null)
{
previous = await library.GetOrDefault(
x => x.ShowID == ep.ShowID
&& x.SeasonNumber == ep.SeasonNumber
&& x.EpisodeNumber < ep.EpisodeNumber,
new Sort<Episode>(x => x.EpisodeNumber, true)
);
previous ??= await library.GetOrDefault(
x => x.ShowID == ep.ShowID
&& x.SeasonNumber == ep.SeasonNumber - 1,
new Sort<Episode>(x => x.EpisodeNumber, true)
);
next = await library.GetOrDefault(
x => x.ShowID == ep.ShowID
&& x.SeasonNumber == ep.SeasonNumber
&& x.EpisodeNumber > ep.EpisodeNumber,
new Sort<Episode>(x => x.EpisodeNumber)
);
next ??= await library.GetOrDefault(
x => x.ShowID == ep.ShowID
&& x.SeasonNumber == ep.SeasonNumber + 1,
new Sort<Episode>(x => x.EpisodeNumber)
);
}
}
// if (!ep.Show.IsMovie)
// {
// if (ep.AbsoluteNumber != null)
// {
// previous = await library.GetOrDefault(
// x => x.ShowID == ep.ShowID && x.AbsoluteNumber < ep.AbsoluteNumber,
// new Sort<Episode>(x => x.AbsoluteNumber, true)
// );
// next = await library.GetOrDefault(
// x => x.ShowID == ep.ShowID && x.AbsoluteNumber > ep.AbsoluteNumber,
// new Sort<Episode>(x => x.AbsoluteNumber)
// );
// }
// else if (ep.SeasonNumber != null && ep.EpisodeNumber != null)
// {
// previous = await library.GetOrDefault(
// x => x.ShowID == ep.ShowID
// && x.SeasonNumber == ep.SeasonNumber
// && x.EpisodeNumber < ep.EpisodeNumber,
// new Sort<Episode>(x => x.EpisodeNumber, true)
// );
// previous ??= await library.GetOrDefault(
// x => x.ShowID == ep.ShowID
// && x.SeasonNumber == ep.SeasonNumber - 1,
// new Sort<Episode>(x => x.EpisodeNumber, true)
// );
//
// next = await library.GetOrDefault(
// x => x.ShowID == ep.ShowID
// && x.SeasonNumber == ep.SeasonNumber
// && x.EpisodeNumber > ep.EpisodeNumber,
// new Sort<Episode>(x => x.EpisodeNumber)
// );
// next ??= await library.GetOrDefault(
// x => x.ShowID == ep.ShowID
// && x.SeasonNumber == ep.SeasonNumber + 1,
// new Sort<Episode>(x => x.EpisodeNumber)
// );
// }
// }
return new WatchItem
{
@ -225,8 +223,12 @@ namespace Kyoo.Abstractions.Models
Audios = ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(),
Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(),
Fonts = await transcoder.ListFonts(ep),
PreviousEpisode = previous,
NextEpisode = next,
PreviousEpisode = ep.Show.IsMovie
? null
: (await library.GetAll<Episode>(limit: new Pagination(1, ep.ID, true))).FirstOrDefault(),
NextEpisode = ep.Show.IsMovie
? null
: (await library.GetAll<Episode>(limit: new Pagination(1, ep.ID))).FirstOrDefault(),
Chapters = await _GetChapters(ep, fs),
IsMovie = ep.Show.IsMovie
};

View File

@ -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;
@ -44,7 +43,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers;
/// <inheritdoc />
protected override Expression<Func<Collection, object>> DefaultSort => x => x.Name;
protected override Sort<Collection> DefaultSort => new Sort<Collection>.By(nameof(Collection.Name));
/// <summary>
/// Create a new <see cref="CollectionRepository"/>.
@ -61,11 +60,11 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Collection>> Search(string query)
{
return await _database.Collections
return await Sort(
_database.Collections
.Where(_database.Like<Collection>(x => x.Name + " " + x.Slug, $"%{query}%"))
.OrderBy(DefaultSort)
.Take(20)
.ToListAsync();
).ToListAsync();
}
/// <inheritdoc />

View File

@ -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;
@ -51,7 +50,12 @@ namespace Kyoo.Core.Controllers
private readonly ITrackRepository _tracks;
/// <inheritdoc />
protected override Expression<Func<Episode, object>> DefaultSort => x => x.EpisodeNumber;
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
protected override Sort<Episode> DefaultSort => new Sort<Episode>.Conglomerate(
new Sort<Episode>.By(x => x.AbsoluteNumber),
new Sort<Episode>.By(x => x.SeasonNumber),
new Sort<Episode>.By(x => x.EpisodeNumber)
);
/// <summary>
/// Create a new <see cref="EpisodeRepository"/>.
@ -120,11 +124,12 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Episode>> Search(string query)
{
List<Episode> ret = await _database.Episodes
List<Episode> ret = await Sort(
_database.Episodes
.Include(x => x.Show)
.Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null)
.Where(_database.Like<Episode>(x => x.Title, $"%{query}%"))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
foreach (Episode ep in ret)

View File

@ -39,7 +39,7 @@ namespace Kyoo.Core.Controllers
private readonly DatabaseContext _database;
/// <inheritdoc />
protected override Expression<Func<Genre, object>> DefaultSort => x => x.Slug;
protected override Sort<Genre> DefaultSort => new Sort<Genre>.By(x => x.Slug);
/// <summary>
/// Create a new <see cref="GenreRepository"/>.
@ -54,9 +54,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Genre>> Search(string query)
{
return await _database.Genres
return await Sort(
_database.Genres
.Where(_database.Like<Genre>(x => x.Name, $"%{query}%"))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
}

View File

@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers
private readonly Lazy<ILibraryRepository> _libraries;
/// <inheritdoc />
protected override Expression<Func<LibraryItem, object>> DefaultSort => x => x.Title;
protected override Sort<LibraryItem> DefaultSort => new Sort<LibraryItem>.By(x => x.Title);
/// <summary>
/// Create a new <see cref="LibraryItemRepository"/>.
@ -92,9 +92,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<LibraryItem>> Search(string query)
{
return await _database.LibraryItems
return await Sort(
_database.LibraryItems
.Where(_database.Like<LibraryItem>(x => x.Title, $"%{query}%"))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
}

View File

@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers;
/// <inheritdoc />
protected override Expression<Func<Library, object>> DefaultSort => x => x.ID;
protected override Sort<Library> DefaultSort => new Sort<Library>.By(x => x.ID);
/// <summary>
/// Create a new <see cref="LibraryRepository"/> instance.
@ -62,9 +62,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Library>> Search(string query)
{
return await _database.Libraries
return await Sort(
_database.Libraries
.Where(_database.Like<Library>(x => x.Name + " " + x.Slug, $"%{query}%"))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
}

View File

@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
@ -47,7 +48,7 @@ namespace Kyoo.Core.Controllers
/// <summary>
/// The default sort order that will be used for this resource's type.
/// </summary>
protected abstract Expression<Func<T, object>> DefaultSort { get; }
protected abstract Sort<T> DefaultSort { get; }
/// <summary>
/// Create a new base <see cref="LocalRepository{T}"/> with the given database handle.
@ -61,6 +62,206 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public Type RepositoryType => typeof(T);
/// <summary>
/// Sort the given query.
/// </summary>
/// <param name="query">The query to sort.</param>
/// <param name="sortBy">How to sort the query</param>
/// <returns>The newly sorted query.</returns>
protected IOrderedQueryable<T> Sort(IQueryable<T> query, Sort<T> sortBy = null)
{
sortBy ??= DefaultSort;
switch (sortBy)
{
case Sort<T>.Default:
return Sort(query, DefaultSort);
case Sort<T>.By(var key, var desc):
return desc
? query.OrderByDescending(x => EF.Property<object>(x, key))
: query.OrderBy(x => EF.Property<T>(x, key));
case Sort<T>.Conglomerate(var keys):
IOrderedQueryable<T> nQuery = Sort(query, keys[0]);
foreach ((string key, bool desc) in keys.Skip(1))
{
nQuery = desc
? nQuery.ThenByDescending(x => EF.Property<object>(x, key))
: nQuery.ThenBy(x => EF.Property<object>(x, key));
}
return nQuery;
default:
// The language should not require me to do this...
throw new SwitchExpressionException();
}
}
private static Func<Expression, Expression, BinaryExpression> GetComparisonExpression(
bool desc,
bool next,
bool orEqual)
{
bool greaterThan = desc ^ next;
return orEqual
? (greaterThan ? Expression.GreaterThanOrEqual : Expression.LessThanOrEqual)
: (greaterThan ? Expression.GreaterThan : Expression.LessThan);
}
/// <summary>
/// Create a filter (where) expression on the query to skip everything before/after the referenceID.
/// The generalized expression for this in pseudocode is:
/// (x > a) OR
/// (x = a AND y > b) OR
/// (x = a AND y = b AND z > c) OR...
///
/// Of course, this will be a bit more complex when ASC and DESC are mixed.
/// Assume x is ASC, y is DESC, and z is ASC:
/// (x > a) OR
/// (x = a AND y &lt; b) OR
/// (x = a AND y = b AND z > c) OR...
/// </summary>
protected Expression<Func<T, bool>> KeysetPaginatate(
Sort<T> sort,
T reference,
bool next = true)
{
if (sort is Sort<T>.Default)
sort = DefaultSort;
// x =>
ParameterExpression x = Expression.Parameter(typeof(T), "x");
ConstantExpression referenceC = Expression.Constant(reference, typeof(T));
if (sort is Sort<T>.By(var key, var desc))
{
Func<Expression, Expression, BinaryExpression> comparer = GetComparisonExpression(desc, next, false);
MemberExpression xkey = Expression.Property(x, key);
MemberExpression rkey = Expression.Property(referenceC, key);
BinaryExpression compare = ApiHelper.StringCompatibleExpression(comparer, xkey, rkey);
return Expression.Lambda<Func<T, bool>>(compare, x);
}
if (sort is Sort<T>.Conglomerate(var list))
{
throw new NotImplementedException();
// BinaryExpression orExpression;
//
// foreach ((string key, bool desc) in list)
// {
// query.Where(x =>
// }
}
throw new SwitchExpressionException();
// Shamlessly stollen from https://github.com/mrahhal/MR.EntityFrameworkCore.KeysetPagination/blob/main/src/MR.EntityFrameworkCore.KeysetPagination/KeysetPaginationExtensions.cs#L191
// // A composite keyset pagination in sql looks something like this:
// // (x, y, ...) > (a, b, ...)
// // Where, x/y/... represent the column and a/b/... represent the reference's respective values.
// //
// // In sql standard this syntax is called "row value". Check here: https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-row-values
// // Unfortunately, not all databases support this properly.
// // Further, if we were to use this we would somehow need EF Core to recognise it and translate it
// // perhaps by using a new DbFunction (https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbfunctions).
// // There's an ongoing issue for this here: https://github.com/dotnet/efcore/issues/26822
// //
// // In addition, row value won't work for mixed ordered columns. i.e if x > a but y < b.
// // So even if we can use it we'll still have to fallback to this logic in these cases.
// //
// // The generalized expression for this in pseudocode is:
// // (x > a) OR
// // (x = a AND y > b) OR
// // (x = a AND y = b AND z > c) OR...
// //
// // Of course, this will be a bit more complex when ASC and DESC are mixed.
// // Assume x is ASC, y is DESC, and z is ASC:
// // (x > a) OR
// // (x = a AND y < b) OR
// // (x = a AND y = b AND z > c) OR...
// //
// // An optimization is to include an additional redundant wrapping clause for the 1st column when there are
// // more than one column we're acting on, which would allow the db to use it as an access predicate on the 1st column.
// // See here: https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic
//
// var referenceValues = GetValues(columns, reference);
//
// MemberExpression firstMemberAccessExpression;
// Expression firstReferenceValueExpression;
//
// // entity =>
// ParameterExpression param = Expression.Parameter(typeof(T), "entity");
//
// BinaryExpression orExpression;
// int innerLimit = 1;
// // This loop compounds the outer OR expressions.
// for (int i = 0; i < sort.list.Length; i++)
// {
// BinaryExpression andExpression;
//
// // This loop compounds the inner AND expressions.
// // innerLimit implicitly grows from 1 to items.Count by each iteration.
// for (int j = 0; j < innerLimit; j++)
// {
// bool isInnerLastOperation = j + 1 == innerLimit;
// var column = columns[j];
// var memberAccess = column.MakeMemberAccessExpression(param);
// var referenceValue = referenceValues[j];
// Expression<Func<object>> referenceValueFunc = () => referenceValue;
// var referenceValueExpression = referenceValueFunc.Body;
//
// if (firstMemberAccessExpression == null)
// {
// // This might be used later on in an optimization.
// firstMemberAccessExpression = memberAccess;
// firstReferenceValueExpression = referenceValueExpression;
// }
//
// BinaryExpression innerExpression;
// if (!isInnerLastOperation)
// {
// innerExpression = Expression.Equal(
// memberAccess,
// EnsureMatchingType(memberAccess, referenceValueExpression));
// }
// else
// {
// var compare = GetComparisonExpressionToApply(direction, column, orEqual: false);
// innerExpression = MakeComparisonExpression(
// column,
// memberAccess, referenceValueExpression,
// compare);
// }
//
// andExpression = andExpression == null ? innerExpression : Expression.And(andExpression, innerExpression);
// }
//
// orExpression = orExpression == null ? andExpression : Expression.Or(orExpression, andExpression);
//
// innerLimit++;
// }
//
// var finalExpression = orExpression;
// if (columns.Count > 1)
// {
// // Implement the optimization that allows an access predicate on the 1st column.
// // This is done by generating the following expression:
// // (x >=|<= a) AND (previous generated expression)
// //
// // This effectively adds a redundant clause on the 1st column, but it's a clause all dbs
// // understand and can use as an access predicate (most commonly when the column is indexed).
//
// var firstColumn = columns[0];
// var compare = GetComparisonExpressionToApply(direction, firstColumn, orEqual: true);
// var accessPredicateClause = MakeComparisonExpression(
// firstColumn,
// firstMemberAccessExpression!, firstReferenceValueExpression!,
// compare);
// finalExpression = Expression.And(accessPredicateClause, finalExpression);
// }
//
// return Expression.Lambda<Func<T, bool>>(finalExpression, param);
}
/// <summary>
/// Get a resource from it's ID and make the <see cref="Database"/> instance track it.
/// </summary>
@ -117,10 +318,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public virtual Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
{
IQueryable<T> query = Database.Set<T>();
Expression<Func<T, object>> sortKey = sortBy.Key ?? DefaultSort;
query = sortBy.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
return query.FirstOrDefaultAsync(where);
return Sort(Database.Set<T>(), sortBy).FirstOrDefaultAsync(where);
}
/// <inheritdoc/>
@ -142,54 +340,19 @@ namespace Kyoo.Core.Controllers
/// <param name="sort">The sort settings (sort order and sort by)</param>
/// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <returns>The filtered query</returns>
protected Task<ICollection<T>> ApplyFilters(IQueryable<T> query,
protected async Task<ICollection<T>> ApplyFilters(IQueryable<T> query,
Expression<Func<T, bool>> where = null,
Sort<T> sort = default,
Pagination limit = default)
{
return ApplyFilters(query, GetOrDefault, DefaultSort, where, sort, limit);
}
/// <summary>
/// Apply filters to a query to ease sort, pagination and where queries for any resources types.
/// For resources of type <typeparamref name="T"/>, see <see cref="ApplyFilters"/>
/// </summary>
/// <param name="query">The base query to filter.</param>
/// <param name="get">A function to asynchronously get a resource from the database using it's ID.</param>
/// <param name="defaultSort">The default sort order of this resource's type.</param>
/// <param name="where">An expression to filter based on arbitrary conditions</param>
/// <param name="sort">The sort settings (sort order and sort by)</param>
/// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <typeparam name="TValue">The type of items to query.</typeparam>
/// <returns>The filtered query</returns>
protected async Task<ICollection<TValue>> ApplyFilters<TValue>(IQueryable<TValue> query,
Func<int, Task<TValue>> get,
Expression<Func<TValue, object>> defaultSort,
Expression<Func<TValue, bool>> where = null,
Sort<TValue> sort = default,
Pagination limit = default)
{
query = Sort(query, sort);
if (where != null)
query = query.Where(where);
Expression<Func<TValue, object>> sortKey = sort.Key ?? defaultSort;
Expression sortExpression = sortKey.Body.NodeType == ExpressionType.Convert
? ((UnaryExpression)sortKey.Body).Operand
: sortKey.Body;
if (typeof(Enum).IsAssignableFrom(sortExpression.Type))
throw new ArgumentException("Invalid sort key.");
query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
if (limit.AfterID != null)
{
TValue after = await get(limit.AfterID.Value);
Expression key = Expression.Constant(sortKey.Compile()(after), sortExpression.Type);
query = query.Where(Expression.Lambda<Func<TValue, bool>>(
ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortExpression, key),
sortKey.Parameters.First()
));
T reference = await Get(limit.AfterID.Value);
query = query.Where(KeysetPaginatate(sort, reference));
}
if (limit.Count > 0)
query = query.Take(limit.Count);

View File

@ -51,7 +51,7 @@ namespace Kyoo.Core.Controllers
private readonly Lazy<IShowRepository> _shows;
/// <inheritdoc />
protected override Expression<Func<People, object>> DefaultSort => x => x.Name;
protected override Sort<People> DefaultSort => new Sort<People>.By(x => x.Name);
/// <summary>
/// Create a new <see cref="PeopleRepository"/>
@ -72,9 +72,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<People>> Search(string query)
{
return await _database.People
return await Sort(
_database.People
.Where(_database.Like<People>(x => x.Name, $"%{query}%"))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
}
@ -152,19 +153,19 @@ namespace Kyoo.Core.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default)
{
ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles
.Where(x => x.ShowID == showID)
.Include(x => x.People),
id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id),
x => x.People.Name,
where,
sort,
limit);
if (!people.Any() && await _shows.Value.GetOrDefault(showID) == null)
throw new ItemNotFoundException();
foreach (PeopleRole role in people)
role.ForPeople = true;
return people;
throw new NotImplementedException();
// ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles
// .Where(x => x.ShowID == showID)
// .Include(x => x.People),
// x => x.People.Name,
// where,
// sort,
// limit);
// if (!people.Any() && await _shows.Value.GetOrDefault(showID) == null)
// throw new ItemNotFoundException();
// foreach (PeopleRole role in people)
// role.ForPeople = true;
// return people;
}
/// <inheritdoc />
@ -173,20 +174,21 @@ namespace Kyoo.Core.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default)
{
ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles
.Where(x => x.Show.Slug == showSlug)
.Include(x => x.People)
.Include(x => x.Show),
id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id),
x => x.People.Name,
where,
sort,
limit);
if (!people.Any() && await _shows.Value.GetOrDefault(showSlug) == null)
throw new ItemNotFoundException();
foreach (PeopleRole role in people)
role.ForPeople = true;
return people;
throw new NotImplementedException();
// ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles
// .Where(x => x.Show.Slug == showSlug)
// .Include(x => x.People)
// .Include(x => x.Show),
// id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id),
// x => x.People.Name,
// where,
// sort,
// limit);
// if (!people.Any() && await _shows.Value.GetOrDefault(showSlug) == null)
// throw new ItemNotFoundException();
// foreach (PeopleRole role in people)
// role.ForPeople = true;
// return people;
}
/// <inheritdoc />
@ -195,17 +197,18 @@ namespace Kyoo.Core.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default)
{
ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles
.Where(x => x.PeopleID == id)
.Include(x => x.Show),
y => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == y),
x => x.Show.Title,
where,
sort,
limit);
if (!roles.Any() && await GetOrDefault(id) == null)
throw new ItemNotFoundException();
return roles;
throw new NotImplementedException();
// ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles
// .Where(x => x.PeopleID == id)
// .Include(x => x.Show),
// y => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == y),
// x => x.Show.Title,
// where,
// sort,
// limit);
// if (!roles.Any() && await GetOrDefault(id) == null)
// throw new ItemNotFoundException();
// return roles;
}
/// <inheritdoc />
@ -214,17 +217,18 @@ namespace Kyoo.Core.Controllers
Sort<PeopleRole> sort = default,
Pagination limit = default)
{
ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles
.Where(x => x.People.Slug == slug)
.Include(x => x.Show),
id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id),
x => x.Show.Title,
where,
sort,
limit);
if (!roles.Any() && await GetOrDefault(slug) == null)
throw new ItemNotFoundException();
return roles;
throw new NotImplementedException();
// ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles
// .Where(x => x.People.Slug == slug)
// .Include(x => x.Show),
// id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id),
// x => x.Show.Title,
// where,
// sort,
// limit);
// if (!roles.Any() && await GetOrDefault(slug) == null)
// throw new ItemNotFoundException();
// return roles;
}
}
}

View File

@ -49,14 +49,15 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc />
protected override Expression<Func<Provider, object>> DefaultSort => x => x.Slug;
protected override Sort<Provider> DefaultSort => new Sort<Provider>.By(x => x.Slug);
/// <inheritdoc />
public override async Task<ICollection<Provider>> Search(string query)
{
return await _database.Providers
return await Sort(
_database.Providers
.Where(_database.Like<Provider>(x => x.Name, $"%{query}%"))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
}
@ -87,13 +88,14 @@ namespace Kyoo.Core.Controllers
Pagination limit = default)
where T : class, IMetadata
{
return ApplyFilters(_database.MetadataIds<T>()
.Include(y => y.Provider),
x => _database.MetadataIds<T>().FirstOrDefaultAsync(y => y.ResourceID == x),
x => x.ResourceID,
where,
sort,
limit);
throw new NotImplementedException();
// return ApplyFilters(_database.MetadataIds<T>()
// .Include(y => y.Provider),
// x => _database.MetadataIds<T>().FirstOrDefaultAsync(y => y.ResourceID == x),
// x => x.ResourceID,
// where,
// sort,
// limit);
}
}
}

View File

@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers;
/// <inheritdoc/>
protected override Expression<Func<Season, object>> DefaultSort => x => x.SeasonNumber;
protected override Sort<Season> DefaultSort => new Sort<Season>.By(x => x.SeasonNumber);
/// <summary>
/// Create a new <see cref="SeasonRepository"/>.
@ -95,9 +95,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public override async Task<ICollection<Season>> Search(string query)
{
return await _database.Seasons
return await Sort(
_database.Seasons
.Where(_database.Like<Season>(x => x.Title, $"%{query}%"))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
}

View File

@ -60,7 +60,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers;
/// <inheritdoc />
protected override Expression<Func<Show, object>> DefaultSort => x => x.Title;
protected override Sort<Show> DefaultSort => new Sort<Show>.By(x => x.Title);
/// <summary>
/// Create a new <see cref="ShowRepository"/>.
@ -88,9 +88,10 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<Show>> Search(string query)
{
query = $"%{query}%";
return await _database.Shows
return await Sort(
_database.Shows
.Where(_database.Like<Show>(x => x.Title + " " + x.Slug, query))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
}

View File

@ -44,7 +44,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers;
/// <inheritdoc />
protected override Expression<Func<Studio, object>> DefaultSort => x => x.Name;
protected override Sort<Studio> DefaultSort => new Sort<Studio>.By(x => x.Name);
/// <summary>
/// Create a new <see cref="StudioRepository"/>.
@ -61,9 +61,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Studio>> Search(string query)
{
return await _database.Studios
return await Sort(
_database.Studios
.Where(_database.Like<Studio>(x => x.Name, $"%{query}%"))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
}

View File

@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
@ -38,7 +37,7 @@ namespace Kyoo.Core.Controllers
private readonly DatabaseContext _database;
/// <inheritdoc />
protected override Expression<Func<Track, object>> DefaultSort => x => x.TrackIndex;
protected override Sort<Track> DefaultSort => new Sort<Track>.By(x => x.TrackIndex);
/// <summary>
/// Create a new <see cref="TrackRepository"/>.

View File

@ -39,7 +39,7 @@ namespace Kyoo.Core.Controllers
private readonly DatabaseContext _database;
/// <inheritdoc />
protected override Expression<Func<User, object>> DefaultSort => x => x.Username;
protected override Sort<User> DefaultSort => new Sort<User>.By(x => x.Username);
/// <summary>
/// Create a new <see cref="UserRepository"/>
@ -54,9 +54,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<User>> Search(string query)
{
return await _database.Users
return await Sort(
_database.Users
.Where(_database.Like<User>(x => x.Username, $"%{query}%"))
.OrderBy(DefaultSort)
)
.Take(20)
.ToListAsync();
}

View File

@ -129,7 +129,7 @@ namespace Kyoo.Core.Api
{
ICollection<T> resources = await Repository.GetAll(
ApiHelper.ParseWhere<T>(where),
new Sort<T>(sortBy),
Sort<T>.From(sortBy),
new Pagination(limit, afterID)
);

View File

@ -88,7 +88,7 @@ namespace Kyoo.Core.Api
{
ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Genre>(x => x.Genres)),
new Sort<Show>(sortBy),
Sort<Show>.From(sortBy),
new Pagination(limit, afterID)
);

View File

@ -93,7 +93,7 @@ namespace Kyoo.Core.Api
try
{
Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
Sort<PeopleRole> sort = new(sortBy);
Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
Pagination pagination = new(limit, afterID);
ICollection<PeopleRole> resources = await identifier.Match(

View File

@ -88,7 +88,7 @@ namespace Kyoo.Core.Api
{
ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioID, x => x.Studio.Slug)),
new Sort<Show>(sortBy),
Sort<Show>.From(sortBy),
new Pagination(limit, afterID)
);

View File

@ -92,7 +92,7 @@ namespace Kyoo.Core.Api
{
ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Collection>(x => x.Collections)),
new Sort<Show>(sortBy),
Sort<Show>.From(sortBy),
new Pagination(limit, afterID)
);
@ -136,7 +136,7 @@ namespace Kyoo.Core.Api
{
ICollection<Library> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Collection>(x => x.Collections)),
new Sort<Library>(sortBy),
Sort<Library>.From(sortBy),
new Pagination(limit, afterID)
);

View File

@ -160,7 +160,7 @@ namespace Kyoo.Core.Api
{
ICollection<Track> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Track>(x => x.EpisodeID, x => x.Episode.Slug)),
new Sort<Track>(sortBy),
Sort<Track>.From(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Episode>()) == null)

View File

@ -91,7 +91,7 @@ namespace Kyoo.Core.Api
{
ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Library>(x => x.Libraries)),
new Sort<Show>(sortBy),
Sort<Show>.From(sortBy),
new Pagination(limit, afterID)
);
@ -135,7 +135,7 @@ namespace Kyoo.Core.Api
{
ICollection<Collection> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Library>(x => x.Libraries)),
new Sort<Collection>(sortBy),
Sort<Collection>.From(sortBy),
new Pagination(limit, afterID)
);
@ -181,7 +181,7 @@ namespace Kyoo.Core.Api
try
{
Expression<Func<LibraryItem, bool>> whereQuery = ApiHelper.ParseWhere<LibraryItem>(where);
Sort<LibraryItem> sort = new(sortBy);
Sort<LibraryItem> sort = Sort<LibraryItem>.From(sortBy);
Pagination pagination = new(limit, afterID);
ICollection<LibraryItem> resources = await identifier.Match(

View File

@ -89,7 +89,7 @@ namespace Kyoo.Core.Api
{
ICollection<LibraryItem> resources = await _libraryItems.GetAll(
ApiHelper.ParseWhere<LibraryItem>(where),
new Sort<LibraryItem>(sortBy),
Sort<LibraryItem>.From(sortBy),
new Pagination(limit, afterID)
);

View File

@ -92,7 +92,7 @@ namespace Kyoo.Core.Api
{
ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonID, x => x.Season.Slug)),
new Sort<Episode>(sortBy),
Sort<Episode>.From(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Season>()) == null)

View File

@ -96,7 +96,7 @@ namespace Kyoo.Core.Api
{
ICollection<Season> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowID, x => x.Show.Slug)),
new Sort<Season>(sortBy),
Sort<Season>.From(sortBy),
new Pagination(limit, afterID)
);
@ -140,7 +140,7 @@ namespace Kyoo.Core.Api
{
ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowID, x => x.Show.Slug)),
new Sort<Episode>(sortBy),
Sort<Episode>.From(sortBy),
new Pagination(limit, afterID)
);
@ -183,7 +183,7 @@ namespace Kyoo.Core.Api
try
{
Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
Sort<PeopleRole> sort = new(sortBy);
Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
Pagination pagination = new(limit, afterID);
ICollection<PeopleRole> resources = await identifier.Match(
@ -232,7 +232,7 @@ namespace Kyoo.Core.Api
{
ICollection<Genre> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Genre, Show>(x => x.Shows)),
new Sort<Genre>(sortBy),
Sort<Genre>.From(sortBy),
new Pagination(limit, afterID)
);
@ -298,7 +298,7 @@ namespace Kyoo.Core.Api
{
ICollection<Library> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Show>(x => x.Shows)),
new Sort<Library>(sortBy),
Sort<Library>.From(sortBy),
new Pagination(limit, afterID)
);
@ -342,7 +342,7 @@ namespace Kyoo.Core.Api
{
ICollection<Collection> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Show>(x => x.Shows)),
new Sort<Collection>(sortBy),
Sort<Collection>.From(sortBy),
new Pagination(limit, afterID)
);