mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add previous page support
This commit is contained in:
parent
67112a37da
commit
fbe624ca6d
@ -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 Kyoo.Utils;
|
||||
@ -40,6 +39,11 @@ namespace Kyoo.Abstractions.Models
|
||||
/// </summary>
|
||||
public string First { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The link of the previous page.
|
||||
/// </summary>
|
||||
public string Previous { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The link of the next page.
|
||||
/// </summary>
|
||||
@ -60,12 +64,14 @@ namespace Kyoo.Abstractions.Models
|
||||
/// </summary>
|
||||
/// <param name="items">The list of items in the page.</param>
|
||||
/// <param name="this">The link of the current page.</param>
|
||||
/// <param name="previous">The link of the previous page.</param>
|
||||
/// <param name="next">The link of the next page.</param>
|
||||
/// <param name="first">The link of the first page.</param>
|
||||
public Page(ICollection<T> items, string @this, string next, string first)
|
||||
public Page(ICollection<T> items, string @this, string previous, string next, string first)
|
||||
{
|
||||
Items = items;
|
||||
This = @this;
|
||||
Previous = previous;
|
||||
Next = next;
|
||||
First = first;
|
||||
}
|
||||
@ -85,6 +91,13 @@ namespace Kyoo.Abstractions.Models
|
||||
Items = items;
|
||||
This = url + query.ToQueryString();
|
||||
|
||||
if (items.Count > 0 && query.ContainsKey("afterID"))
|
||||
{
|
||||
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();
|
||||
|
@ -21,32 +21,42 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <summary>
|
||||
/// Information about the pagination. How many items should be displayed and where to start.
|
||||
/// </summary>
|
||||
public readonly struct Pagination
|
||||
public class Pagination
|
||||
{
|
||||
/// <summary>
|
||||
/// The count of items to return.
|
||||
/// </summary>
|
||||
public int Count { get; }
|
||||
public int Limit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Where to start? Using the given sort.
|
||||
/// </summary>
|
||||
public int? AfterID { get; }
|
||||
public int? AfterID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should the previous page be returned instead of the next?
|
||||
/// </summary>
|
||||
public bool Reverse { get; }
|
||||
public bool Reverse { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Pagination"/> with default values.
|
||||
/// </summary>
|
||||
public Pagination()
|
||||
{
|
||||
Limit = 20;
|
||||
AfterID = null;
|
||||
Reverse = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Pagination"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="count">Set the <see cref="Count"/> value</param>
|
||||
/// <param name="count">Set the <see cref="Limit"/> value</param>
|
||||
/// <param name="afterID">Set the <see cref="AfterID"/> value. If not specified, it will start from the start</param>
|
||||
/// <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;
|
||||
Limit = count;
|
||||
AfterID = afterID;
|
||||
Reverse = reverse;
|
||||
}
|
||||
@ -54,7 +64,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <summary>
|
||||
/// Implicitly create a new pagination from a limit number.
|
||||
/// </summary>
|
||||
/// <param name="limit">Set the <see cref="Count"/> value</param>
|
||||
/// <param name="limit">Set the <see cref="Limit"/> value</param>
|
||||
/// <returns>A new <see cref="Pagination"/> instance</returns>
|
||||
public static implicit operator Pagination(int limit) => new(limit);
|
||||
}
|
||||
|
@ -48,36 +48,13 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// </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>
|
||||
/// <returns>A <see cref="Sort{T}"/> for the given string</returns>
|
||||
public static new By From(string sortBy)
|
||||
{
|
||||
string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy;
|
||||
string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null;
|
||||
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>;
|
||||
public record Conglomerate(params Sort<T>[] list) : Sort<T>;
|
||||
|
||||
/// <summary>The default sort method for the given type.</summary>
|
||||
public record Default : Sort<T>;
|
||||
@ -90,11 +67,24 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// <returns>A <see cref="Sort{T}"/> for the given string</returns>
|
||||
public static Sort<T> From(string sortBy)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sortBy))
|
||||
if (string.IsNullOrEmpty(sortBy) || sortBy == "default")
|
||||
return new Default();
|
||||
if (sortBy.Contains(','))
|
||||
return new Conglomerate(sortBy.Split(',').Select(By.From).ToArray());
|
||||
return By.From(sortBy);
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,47 +163,6 @@ namespace Kyoo.Abstractions.Models
|
||||
await library.Load(ep, x => x.Show);
|
||||
await library.Load(ep, x => x.Tracks);
|
||||
|
||||
// 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
|
||||
{
|
||||
EpisodeID = ep.ID,
|
||||
|
@ -72,31 +72,38 @@ namespace Kyoo.Core.Controllers
|
||||
{
|
||||
sortBy ??= DefaultSort;
|
||||
|
||||
IOrderedQueryable<T> _Sort(IQueryable<T> query, Sort<T> sortBy)
|
||||
IOrderedQueryable<T> _SortBy(IQueryable<T> qr, Expression<Func<T, object>> sort, bool desc, bool then)
|
||||
{
|
||||
if (then && qr is IOrderedQueryable<T> qro)
|
||||
{
|
||||
return desc
|
||||
? qro.ThenByDescending(sort)
|
||||
: qro.ThenBy(sort);
|
||||
}
|
||||
return desc
|
||||
? qr.OrderByDescending(sort)
|
||||
: qr.OrderBy(sort);
|
||||
}
|
||||
|
||||
IOrderedQueryable<T> _Sort(IQueryable<T> query, Sort<T> sortBy, bool then)
|
||||
{
|
||||
switch (sortBy)
|
||||
{
|
||||
case Sort<T>.Default:
|
||||
return Sort(query, DefaultSort);
|
||||
return _Sort(query, DefaultSort, then);
|
||||
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 _SortBy(query, x => EF.Property<T>(x, key), desc, then);
|
||||
case Sort<T>.Conglomerate(var sorts):
|
||||
IOrderedQueryable<T> nQuery = _Sort(query, sorts.First(), false);
|
||||
foreach (Sort<T> sort in sorts.Skip(1))
|
||||
nQuery = _Sort(nQuery, sort, true);
|
||||
return nQuery;
|
||||
default:
|
||||
// The language should not require me to do this...
|
||||
throw new SwitchExpressionException();
|
||||
}
|
||||
}
|
||||
return _Sort(query, sortBy).ThenBy(x => x.ID);
|
||||
return _Sort(query, sortBy, false).ThenBy(x => x.ID);
|
||||
}
|
||||
|
||||
private static Func<Expression, Expression, BinaryExpression> _GetComparisonExpression(
|
||||
@ -133,22 +140,24 @@ namespace Kyoo.Core.Controllers
|
||||
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));
|
||||
|
||||
IEnumerable<Sort<T>.By> _GetSortsBy(Sort<T> sort)
|
||||
{
|
||||
return sort switch
|
||||
{
|
||||
Sort<T>.Default => _GetSortsBy(DefaultSort),
|
||||
Sort<T>.By @sortBy => new[] { sortBy },
|
||||
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 = (sort switch
|
||||
{
|
||||
Sort<T>.By @sortBy => new[] { sortBy },
|
||||
Sort<T>.Conglomerate(var list) => list,
|
||||
_ => Array.Empty<Sort<T>.By>(),
|
||||
}).Append(id);
|
||||
IEnumerable<Sort<T>.By> sorts = _GetSortsBy(sort).Append(id);
|
||||
|
||||
BinaryExpression filter = null;
|
||||
List<Sort<T>.By> previousSteps = new();
|
||||
@ -278,10 +287,10 @@ namespace Kyoo.Core.Controllers
|
||||
if (limit.AfterID != null)
|
||||
{
|
||||
T reference = await Get(limit.AfterID.Value);
|
||||
query = query.Where(KeysetPaginatate(sort, reference));
|
||||
query = query.Where(KeysetPaginatate(sort, reference, !limit.Reverse));
|
||||
}
|
||||
if (limit.Count > 0)
|
||||
query = query.Take(limit.Count);
|
||||
if (limit.Limit > 0)
|
||||
query = query.Take(limit.Limit);
|
||||
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ namespace Kyoo.Core.Api
|
||||
pagination
|
||||
);
|
||||
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -44,9 +44,14 @@ namespace Kyoo.Core.Api
|
||||
{
|
||||
if (context.ActionArguments.TryGetValue("where", out object dic) && dic is Dictionary<string, string> where)
|
||||
{
|
||||
where.Remove("fields");
|
||||
Dictionary<string, string> nWhere = new(where, StringComparer.InvariantCultureIgnoreCase);
|
||||
nWhere.Remove("fields");
|
||||
nWhere.Remove("afterID");
|
||||
nWhere.Remove("limit");
|
||||
nWhere.Remove("reverse");
|
||||
foreach ((string key, _) in context.ActionArguments)
|
||||
where.Remove(key);
|
||||
nWhere.Remove(key);
|
||||
context.ActionArguments["where"] = nWhere;
|
||||
}
|
||||
|
||||
List<string> fields = context.HttpContext.Request.Query["fields"]
|
||||
|
@ -89,7 +89,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Genre>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ namespace Kyoo.Core.Api
|
||||
slug => _libraryManager.GetRolesFromPeople(slug, whereQuery, sort, pagination)
|
||||
);
|
||||
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Studio>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Collection>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -128,7 +128,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Collection>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Episode>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Library>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -127,7 +127,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Library>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -165,7 +165,7 @@ namespace Kyoo.Core.Api
|
||||
slug => _libraryManager.GetItemsFromLibrary(slug, whereQuery, sort, pagination)
|
||||
);
|
||||
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ namespace Kyoo.Core.Api
|
||||
pagination
|
||||
);
|
||||
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Season>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -97,7 +97,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -132,7 +132,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -166,7 +166,7 @@ namespace Kyoo.Core.Api
|
||||
id => _libraryManager.GetPeopleFromShow(id, whereQuery, sort, pagination),
|
||||
slug => _libraryManager.GetPeopleFromShow(slug, whereQuery, sort, pagination)
|
||||
);
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -201,7 +201,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -255,7 +255,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -290,7 +290,7 @@ namespace Kyoo.Core.Api
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Count);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user