API: documenting watch items

This commit is contained in:
Zoe Roux 2021-09-24 14:17:53 +02:00
parent 7e4fab1ee1
commit 049f545d51
16 changed files with 134 additions and 88 deletions

View File

@ -200,10 +200,11 @@ namespace Kyoo.Abstractions.Controllers
/// Get the resource by a filter function or null if it is not found. /// Get the resource by a filter function or null if it is not found.
/// </summary> /// </summary>
/// <param name="where">The filter function.</param> /// <param name="where">The filter function.</param>
/// <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> /// <typeparam name="T">The type of the resource</typeparam>
/// <returns>The first resource found that match the where function</returns> /// <returns>The first resource found that match the where function</returns>
[ItemCanBeNull] [ItemCanBeNull]
Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where) Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>

View File

@ -81,9 +81,10 @@ namespace Kyoo.Abstractions.Controllers
/// Get the first resource that match the predicate or null if it is not found. /// Get the first resource that match the predicate or null if it is not found.
/// </summary> /// </summary>
/// <param name="where">A predicate to filter the resource.</param> /// <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> /// <returns>The resource found</returns>
[ItemCanBeNull] [ItemCanBeNull]
Task<T> GetOrDefault(Expression<Func<T, bool>> where); Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default);
/// <summary> /// <summary>
/// Search for resources. /// Search for resources.
@ -263,6 +264,8 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
public interface IEpisodeRepository : IRepository<Episode> public interface IEpisodeRepository : IRepository<Episode>
{ {
// TODO replace the next methods with extension methods.
/// <summary> /// <summary>
/// Get a episode from it's showID, it's seasonNumber and it's episode number. /// Get a episode from it's showID, it's seasonNumber and it's episode number.
/// </summary> /// </summary>

View File

@ -158,36 +158,50 @@ namespace Kyoo.Abstractions.Models
/// <returns>A new WatchItem representing the given episode.</returns> /// <returns>A new WatchItem representing the given episode.</returns>
public static async Task<WatchItem> FromEpisode(Episode ep, ILibraryManager library) public static async Task<WatchItem> FromEpisode(Episode ep, ILibraryManager library)
{ {
Episode previous = null;
Episode next = null;
await library.Load(ep, x => x.Show); await library.Load(ep, x => x.Show);
await library.Load(ep, x => x.Tracks); await library.Load(ep, x => x.Tracks);
if (!ep.Show.IsMovie && ep.SeasonNumber != null && ep.EpisodeNumber != null) Episode previous = null;
Episode next = null;
if (!ep.Show.IsMovie)
{ {
if (ep.EpisodeNumber > 1) if (ep.AbsoluteNumber != null)
previous = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value, ep.EpisodeNumber.Value - 1);
else if (ep.SeasonNumber > 1)
{ {
previous = (await library.GetAll(x => x.ShowID == ep.ShowID previous = await library.GetOrDefault(
&& x.SeasonNumber == ep.SeasonNumber.Value - 1, x => x.ShowID == ep.ShowID && x.AbsoluteNumber <= ep.AbsoluteNumber,
limit: 1, new Sort<Episode>(x => x.AbsoluteNumber, true)
sort: new Sort<Episode>(x => x.EpisodeNumber, true)) );
).FirstOrDefault(); 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)
);
if (ep.EpisodeNumber >= await library.GetCount<Episode>(x => x.SeasonID == ep.SeasonID)) next = await library.GetOrDefault(
next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value + 1, 1); x => x.ShowID == ep.ShowID
else && x.SeasonNumber == ep.SeasonNumber
next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value, ep.EpisodeNumber.Value + 1); && x.EpisodeNumber > ep.EpisodeNumber,
} new Sort<Episode>(x => x.EpisodeNumber)
else if (!ep.Show.IsMovie && ep.AbsoluteNumber != null) );
{ next ??= await library.GetOrDefault(
previous = await library.GetOrDefault<Episode>(x => x.ShowID == ep.ShowID x => x.ShowID == ep.ShowID
&& x.AbsoluteNumber == ep.EpisodeNumber + 1); && x.SeasonNumber == ep.SeasonNumber + 1,
next = await library.GetOrDefault<Episode>(x => x.ShowID == ep.ShowID new Sort<Episode>(x => x.EpisodeNumber)
&& x.AbsoluteNumber == ep.AbsoluteNumber + 1); );
}
} }
return new WatchItem return new WatchItem

View File

@ -163,10 +163,10 @@ namespace Kyoo.Core.Controllers
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where) public async Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T> sortBy)
where T : class, IResource where T : class, IResource
{ {
return await GetRepository<T>().GetOrDefault(where); return await GetRepository<T>().GetOrDefault(where, sortBy);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -115,9 +115,12 @@ namespace Kyoo.Core.Controllers
} }
/// <inheritdoc /> /// <inheritdoc />
public virtual Task<T> GetOrDefault(Expression<Func<T, bool>> where) public virtual Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
{ {
return Database.Set<T>().FirstOrDefaultAsync(where); 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);
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@ -23,10 +23,6 @@ using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Core.Models.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Kyoo.Core.Api namespace Kyoo.Core.Api
{ {

View File

@ -29,8 +29,8 @@ namespace Kyoo.Core.Api
{ {
public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver
{ {
private int _depth = -1;
private readonly Uri _host; private readonly Uri _host;
private int _depth = -1;
public JsonPropertyIgnorer(Uri host) public JsonPropertyIgnorer(Uri host)
{ {

View File

@ -0,0 +1,83 @@
// 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.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("api/watch")]
[Route("api/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>
/// Create a new <see cref="WatchApi"/>.
/// </summary>
/// <param name="libraryManager">
/// The library manager used to modify or retrieve information in the data store.
/// </param>
public WatchApi(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
/// <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);
}
}
}

View File

@ -1,54 +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.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Permissions;
using Microsoft.AspNetCore.Mvc;
namespace Kyoo.Core.Api
{
[Route("api/watch")]
[ApiController]
public class WatchApi : ControllerBase
{
private readonly ILibraryManager _libraryManager;
public WatchApi(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
[HttpGet("{slug}")]
[Permission("video", Kind.Read)]
public async Task<ActionResult<WatchItem>> GetWatchItem(string slug)
{
try
{
Episode item = await _libraryManager.Get<Episode>(slug);
return await WatchItem.FromEpisode(item, _libraryManager);
}
catch (ItemNotFoundException)
{
return NotFound();
}
}
}
}