From 049f545d51c5e026c3c605c8826a674b637cca93 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 24 Sep 2021 14:17:53 +0200 Subject: [PATCH] API: documenting watch items --- .../Controllers/ILibraryManager.cs | 3 +- .../Controllers/IRepository.cs | 5 +- src/Kyoo.Abstractions/Models/WatchItem.cs | 60 +++++++++----- src/Kyoo.Core/Controllers/LibraryManager.cs | 4 +- .../Repositories/LocalRepository.cs | 7 +- src/Kyoo.Core/Views/Helper/ApiHelper.cs | 4 - .../Helper/Serializers/JsonPropertyIgnorer.cs | 2 +- .../Views/{ => Resources}/CollectionApi.cs | 0 .../Views/{ => Resources}/EpisodeApi.cs | 0 .../Views/{ => Resources}/LibraryApi.cs | 0 .../Views/{ => Resources}/LibraryItemApi.cs | 0 .../Views/{ => Resources}/SeasonApi.cs | 0 .../Views/{ => Resources}/ShowApi.cs | 0 src/Kyoo.Core/Views/{ => Watch}/TrackApi.cs | 0 src/Kyoo.Core/Views/Watch/WatchApi.cs | 83 +++++++++++++++++++ src/Kyoo.Core/Views/WatchApi.cs | 54 ------------ 16 files changed, 134 insertions(+), 88 deletions(-) rename src/Kyoo.Core/Views/{ => Resources}/CollectionApi.cs (100%) rename src/Kyoo.Core/Views/{ => Resources}/EpisodeApi.cs (100%) rename src/Kyoo.Core/Views/{ => Resources}/LibraryApi.cs (100%) rename src/Kyoo.Core/Views/{ => Resources}/LibraryItemApi.cs (100%) rename src/Kyoo.Core/Views/{ => Resources}/SeasonApi.cs (100%) rename src/Kyoo.Core/Views/{ => Resources}/ShowApi.cs (100%) rename src/Kyoo.Core/Views/{ => Watch}/TrackApi.cs (100%) create mode 100644 src/Kyoo.Core/Views/Watch/WatchApi.cs delete mode 100644 src/Kyoo.Core/Views/WatchApi.cs diff --git a/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs index f4218760..1bbe147f 100644 --- a/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -200,10 +200,11 @@ namespace Kyoo.Abstractions.Controllers /// Get the resource by a filter function or null if it is not found. /// /// The filter function. + /// A custom sort method to handle cases where multiples items match the filters. /// The type of the resource /// The first resource found that match the where function [ItemCanBeNull] - Task GetOrDefault(Expression> where) + Task GetOrDefault(Expression> where, Sort sortBy = default) where T : class, IResource; /// diff --git a/src/Kyoo.Abstractions/Controllers/IRepository.cs b/src/Kyoo.Abstractions/Controllers/IRepository.cs index 21a65c12..4deae6e8 100644 --- a/src/Kyoo.Abstractions/Controllers/IRepository.cs +++ b/src/Kyoo.Abstractions/Controllers/IRepository.cs @@ -81,9 +81,10 @@ namespace Kyoo.Abstractions.Controllers /// Get the first resource that match the predicate or null if it is not found. /// /// A predicate to filter the resource. + /// A custom sort method to handle cases where multiples items match the filters. /// The resource found [ItemCanBeNull] - Task GetOrDefault(Expression> where); + Task GetOrDefault(Expression> where, Sort sortBy = default); /// /// Search for resources. @@ -263,6 +264,8 @@ namespace Kyoo.Abstractions.Controllers /// public interface IEpisodeRepository : IRepository { + // TODO replace the next methods with extension methods. + /// /// Get a episode from it's showID, it's seasonNumber and it's episode number. /// diff --git a/src/Kyoo.Abstractions/Models/WatchItem.cs b/src/Kyoo.Abstractions/Models/WatchItem.cs index 8c4daab9..5bbf0fb8 100644 --- a/src/Kyoo.Abstractions/Models/WatchItem.cs +++ b/src/Kyoo.Abstractions/Models/WatchItem.cs @@ -158,36 +158,50 @@ namespace Kyoo.Abstractions.Models /// A new WatchItem representing the given episode. public static async Task FromEpisode(Episode ep, ILibraryManager library) { - Episode previous = null; - Episode next = null; - await library.Load(ep, x => x.Show); 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) - previous = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value, ep.EpisodeNumber.Value - 1); - else if (ep.SeasonNumber > 1) + if (ep.AbsoluteNumber != null) { - previous = (await library.GetAll(x => x.ShowID == ep.ShowID - && x.SeasonNumber == ep.SeasonNumber.Value - 1, - limit: 1, - sort: new Sort(x => x.EpisodeNumber, true)) - ).FirstOrDefault(); + previous = await library.GetOrDefault( + x => x.ShowID == ep.ShowID && x.AbsoluteNumber <= ep.AbsoluteNumber, + new Sort(x => x.AbsoluteNumber, true) + ); + next = await library.GetOrDefault( + x => x.ShowID == ep.ShowID && x.AbsoluteNumber >= ep.AbsoluteNumber, + new Sort(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(x => x.EpisodeNumber, true) + ); + previous ??= await library.GetOrDefault( + x => x.ShowID == ep.ShowID + && x.SeasonNumber == ep.SeasonNumber - 1, + new Sort(x => x.EpisodeNumber, true) + ); - if (ep.EpisodeNumber >= await library.GetCount(x => x.SeasonID == ep.SeasonID)) - next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value + 1, 1); - else - next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value, ep.EpisodeNumber.Value + 1); - } - else if (!ep.Show.IsMovie && ep.AbsoluteNumber != null) - { - previous = await library.GetOrDefault(x => x.ShowID == ep.ShowID - && x.AbsoluteNumber == ep.EpisodeNumber + 1); - next = await library.GetOrDefault(x => x.ShowID == ep.ShowID - && x.AbsoluteNumber == ep.AbsoluteNumber + 1); + next = await library.GetOrDefault( + x => x.ShowID == ep.ShowID + && x.SeasonNumber == ep.SeasonNumber + && x.EpisodeNumber > ep.EpisodeNumber, + new Sort(x => x.EpisodeNumber) + ); + next ??= await library.GetOrDefault( + x => x.ShowID == ep.ShowID + && x.SeasonNumber == ep.SeasonNumber + 1, + new Sort(x => x.EpisodeNumber) + ); + } } return new WatchItem diff --git a/src/Kyoo.Core/Controllers/LibraryManager.cs b/src/Kyoo.Core/Controllers/LibraryManager.cs index a6cd9f44..5243ee36 100644 --- a/src/Kyoo.Core/Controllers/LibraryManager.cs +++ b/src/Kyoo.Core/Controllers/LibraryManager.cs @@ -163,10 +163,10 @@ namespace Kyoo.Core.Controllers } /// - public async Task GetOrDefault(Expression> where) + public async Task GetOrDefault(Expression> where, Sort sortBy) where T : class, IResource { - return await GetRepository().GetOrDefault(where); + return await GetRepository().GetOrDefault(where, sortBy); } /// diff --git a/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 3cc80036..59928d02 100644 --- a/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -115,9 +115,12 @@ namespace Kyoo.Core.Controllers } /// - public virtual Task GetOrDefault(Expression> where) + public virtual Task GetOrDefault(Expression> where, Sort sortBy = default) { - return Database.Set().FirstOrDefaultAsync(where); + IQueryable query = Database.Set(); + Expression> sortKey = sortBy.Key ?? DefaultSort; + query = sortBy.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey); + return query.FirstOrDefaultAsync(where); } /// diff --git a/src/Kyoo.Core/Views/Helper/ApiHelper.cs b/src/Kyoo.Core/Views/Helper/ApiHelper.cs index d2e926fa..7365c5a7 100644 --- a/src/Kyoo.Core/Views/Helper/ApiHelper.cs +++ b/src/Kyoo.Core/Views/Helper/ApiHelper.cs @@ -23,10 +23,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; 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 { diff --git a/src/Kyoo.Core/Views/Helper/Serializers/JsonPropertyIgnorer.cs b/src/Kyoo.Core/Views/Helper/Serializers/JsonPropertyIgnorer.cs index 65c00f7f..40a682a9 100644 --- a/src/Kyoo.Core/Views/Helper/Serializers/JsonPropertyIgnorer.cs +++ b/src/Kyoo.Core/Views/Helper/Serializers/JsonPropertyIgnorer.cs @@ -29,8 +29,8 @@ namespace Kyoo.Core.Api { public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver { - private int _depth = -1; private readonly Uri _host; + private int _depth = -1; public JsonPropertyIgnorer(Uri host) { diff --git a/src/Kyoo.Core/Views/CollectionApi.cs b/src/Kyoo.Core/Views/Resources/CollectionApi.cs similarity index 100% rename from src/Kyoo.Core/Views/CollectionApi.cs rename to src/Kyoo.Core/Views/Resources/CollectionApi.cs diff --git a/src/Kyoo.Core/Views/EpisodeApi.cs b/src/Kyoo.Core/Views/Resources/EpisodeApi.cs similarity index 100% rename from src/Kyoo.Core/Views/EpisodeApi.cs rename to src/Kyoo.Core/Views/Resources/EpisodeApi.cs diff --git a/src/Kyoo.Core/Views/LibraryApi.cs b/src/Kyoo.Core/Views/Resources/LibraryApi.cs similarity index 100% rename from src/Kyoo.Core/Views/LibraryApi.cs rename to src/Kyoo.Core/Views/Resources/LibraryApi.cs diff --git a/src/Kyoo.Core/Views/LibraryItemApi.cs b/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs similarity index 100% rename from src/Kyoo.Core/Views/LibraryItemApi.cs rename to src/Kyoo.Core/Views/Resources/LibraryItemApi.cs diff --git a/src/Kyoo.Core/Views/SeasonApi.cs b/src/Kyoo.Core/Views/Resources/SeasonApi.cs similarity index 100% rename from src/Kyoo.Core/Views/SeasonApi.cs rename to src/Kyoo.Core/Views/Resources/SeasonApi.cs diff --git a/src/Kyoo.Core/Views/ShowApi.cs b/src/Kyoo.Core/Views/Resources/ShowApi.cs similarity index 100% rename from src/Kyoo.Core/Views/ShowApi.cs rename to src/Kyoo.Core/Views/Resources/ShowApi.cs diff --git a/src/Kyoo.Core/Views/TrackApi.cs b/src/Kyoo.Core/Views/Watch/TrackApi.cs similarity index 100% rename from src/Kyoo.Core/Views/TrackApi.cs rename to src/Kyoo.Core/Views/Watch/TrackApi.cs diff --git a/src/Kyoo.Core/Views/Watch/WatchApi.cs b/src/Kyoo.Core/Views/Watch/WatchApi.cs new file mode 100644 index 00000000..f72b1901 --- /dev/null +++ b/src/Kyoo.Core/Views/Watch/WatchApi.cs @@ -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 . + +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 +{ + /// + /// Retrieve information of an as a . + /// 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. + /// + [Route("api/watch")] + [Route("api/watchitem", Order = AlternativeRoute)] + [ApiController] + [ApiDefinition("Watch Items", Group = WatchGroup)] + public class WatchApi : ControllerBase + { + /// + /// The library manager used to modify or retrieve information in the data store. + /// + private readonly ILibraryManager _libraryManager; + + /// + /// Create a new . + /// + /// + /// The library manager used to modify or retrieve information in the data store. + /// + public WatchApi(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + /// + /// Get a watch item + /// + /// + /// Retrieve a watch item of an episode. + /// + /// The ID or slug of the . + /// A page of items. + /// No episode with the given ID or slug could be found. + [HttpGet("{identifier:id}")] + [Permission("watch", Kind.Read)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetWatchItem(Identifier identifier) + { + Episode item = await identifier.Match( + id => _libraryManager.GetOrDefault(id), + slug => _libraryManager.GetOrDefault(slug) + ); + if (item == null) + return NotFound(); + return await WatchItem.FromEpisode(item, _libraryManager); + } + } +} diff --git a/src/Kyoo.Core/Views/WatchApi.cs b/src/Kyoo.Core/Views/WatchApi.cs deleted file mode 100644 index 74dba487..00000000 --- a/src/Kyoo.Core/Views/WatchApi.cs +++ /dev/null @@ -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 . - -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> GetWatchItem(string slug) - { - try - { - Episode item = await _libraryManager.Get(slug); - return await WatchItem.FromEpisode(item, _libraryManager); - } - catch (ItemNotFoundException) - { - return NotFound(); - } - } - } -}