Only serializing useful informations for each query. Cleaning up the rest API

This commit is contained in:
Zoe Roux 2020-02-11 22:26:38 +01:00
parent aca93acc86
commit 84f8d545a6
17 changed files with 183 additions and 104 deletions

View File

@ -25,7 +25,7 @@ namespace Kyoo.Models
[JsonIgnore] public string ImgPrimary { get; set; }
public string ExternalIDs { get; set; }
public virtual IEnumerable<Track> Tracks { get; set; }
[JsonIgnore] public virtual IEnumerable<Track> Tracks { get; set; }
public string ShowTitle; //Used in the API response only
public string Link; //Used in the API response only

View File

@ -29,15 +29,15 @@ namespace Kyoo.Models
public bool IsCollection;
public virtual IEnumerable<Genre> Genres
[JsonIgnore] public virtual IEnumerable<Genre> Genres
{
get { return GenreLinks?.Select(x => x.Genre).OrderBy(x => x.Name); }
set { GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList(); }
}
[JsonIgnore] public virtual List<GenreLink> GenreLinks { get; set; }
public virtual Studio Studio { get; set; }
public virtual IEnumerable<PeopleLink> People { get; set; }
public virtual IEnumerable<Season> Seasons { get; set; }
[JsonIgnore] public virtual Studio Studio { get; set; }
[JsonIgnore] public virtual IEnumerable<PeopleLink> People { get; set; }
[JsonIgnore] public virtual IEnumerable<Season> Seasons { get; set; }
[JsonIgnore] public virtual IEnumerable<Episode> Episodes { get; set; }

View File

@ -67,6 +67,7 @@ namespace Kyoo.Controllers
public IEnumerable<Show> GetShows(string searchQuery)
{
// TODO use case insensitive queries.
return (from show in _database.Shows from l in _database.CollectionLinks.DefaultIfEmpty()
where l.CollectionID == null select show).AsEnumerable().Union(
from collection in _database.Collections select collection.AsShow())

View File

@ -1,7 +1,8 @@
using Microsoft.AspNetCore.Mvc;
using System.Threading;
using Kyoo.Controllers;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("api/[controller]")]
[ApiController]

View File

@ -3,7 +3,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
public class AuthentificationAPI : Controller
{

View File

@ -3,23 +3,23 @@ using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("api/[controller]")]
[ApiController]
public class CollectionController : ControllerBase
{
private readonly ILibraryManager libraryManager;
private readonly ILibraryManager _libraryManager;
public CollectionController(ILibraryManager libraryManager)
{
this.libraryManager = libraryManager;
_libraryManager = libraryManager;
}
[HttpGet("{collectionSlug}")]
public ActionResult<Collection> GetShows(string collectionSlug)
{
Collection collection = libraryManager.GetCollection(collectionSlug);
Collection collection = _libraryManager.GetCollection(collectionSlug);
if (collection == null)
return NotFound();

View File

@ -2,8 +2,9 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using Kyoo.Controllers;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("api/[controller]")]
[ApiController]
@ -28,6 +29,7 @@ namespace Kyoo.Controllers
}
[HttpGet("{showSlug}/season/{seasonNumber}/episode/{episodeNumber}")]
[JsonDetailed]
public ActionResult<Episode> GetEpisode(string showSlug, long seasonNumber, long episodeNumber)
{
Episode episode = _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);

View File

@ -0,0 +1,89 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Reflection;
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Kyoo.Controllers
{
public class JsonPropertySelector : DefaultContractResolver
{
private readonly Dictionary<Type, HashSet<string>> _ignored;
private readonly Dictionary<Type, HashSet<string>> _forceSerialize;
public JsonPropertySelector()
{
_ignored = new Dictionary<Type, HashSet<string>>();
_forceSerialize = new Dictionary<Type, HashSet<string>>();
}
public JsonPropertySelector(Dictionary<Type, HashSet<string>> ignored, Dictionary<Type, HashSet<string>> forceSerialize)
{
_ignored = ignored ?? new Dictionary<Type, HashSet<string>>();
_forceSerialize = forceSerialize ?? new Dictionary<Type, HashSet<string>>();
}
private bool IsIgnored(Type type, string propertyName)
{
if (_ignored.ContainsKey(type) && _ignored[type].Contains(propertyName))
return true;
if (type.BaseType == null)
return false;
return _ignored.ContainsKey(type.BaseType) && _ignored[type.BaseType].Contains(propertyName);
}
private bool IsSerializationForced(Type type, string propertyName)
{
if (_forceSerialize.ContainsKey(type) && _forceSerialize[type].Contains(propertyName))
return true;
if (type.BaseType == null)
return false;
return _forceSerialize.ContainsKey(type.BaseType) && _forceSerialize[type.BaseType].Contains(propertyName);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (IsIgnored(property.DeclaringType, property.PropertyName))
{
property.ShouldSerialize = i => false;
property.Ignored = true;
}
else if (IsSerializationForced(property.DeclaringType, property.PropertyName))
{
property.ShouldSerialize = i => true;
property.Ignored = false;
}
return property;
}
}
public class JsonDetailed : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is ObjectResult result)
{
result.Formatters.Add(new NewtonsoftJsonOutputFormatter(
new JsonSerializerSettings
{
ContractResolver = new JsonPropertySelector(null, new Dictionary<Type, HashSet<string>>()
{
{typeof(Show), new HashSet<string> {"Genres", "Studio", "People", "Seasons"}},
{typeof(Episode), new HashSet<string> {"Tracks"}}
})
},
context.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>(),
context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value));
}
}
}
}

View File

@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("api/libraries")]
[ApiController]

View File

@ -1,31 +1,30 @@
using Kyoo.Controllers;
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("api/[controller]")]
[ApiController]
public class PeopleController : ControllerBase
{
private readonly ILibraryManager libraryManager;
private readonly ILibraryManager _libraryManager;
public PeopleController(ILibraryManager libraryManager)
{
this.libraryManager = libraryManager;
_libraryManager = libraryManager;
}
[HttpGet("{peopleSlug}")]
public ActionResult<Collection> GetPeople(string peopleSlug)
{
People people = libraryManager.GetPeopleBySlug(peopleSlug);
People people = _libraryManager.GetPeopleBySlug(peopleSlug);
if (people == null)
return NotFound();
Collection collection = new Collection(people.Slug, people.Name, null, null)
{
Shows = libraryManager.GetShowsByPeople(people.ID),
Shows = _libraryManager.GetShowsByPeople(people.ID),
Poster = "peopleimg/" + people.Slug
};
return collection;

View File

@ -2,7 +2,7 @@
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("api/[controller]")]
[ApiController]

View File

@ -1,31 +1,32 @@
using Kyoo.Controllers;
using Kyoo.Models;
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using Kyoo.Controllers;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("api/shows")]
[ApiController]
public class ShowsController : ControllerBase
{
private readonly ILibraryManager libraryManager;
private readonly ILibraryManager _libraryManager;
public ShowsController(ILibraryManager libraryManager)
{
this.libraryManager = libraryManager;
_libraryManager = libraryManager;
}
[HttpGet]
public IEnumerable<Show> GetShows()
{
return libraryManager.GetShows();
return _libraryManager.GetShows();
}
[HttpGet("{slug}")]
[JsonDetailed]
public ActionResult<Show> GetShow(string slug)
{
Show show = libraryManager.GetShowBySlug(slug);
Show show = _libraryManager.GetShowBySlug(slug);
if (show == null)
return NotFound();

View File

@ -3,20 +3,21 @@ using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Kyoo.Controllers;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("[controller]")]
[ApiController]
public class SubtitleController : ControllerBase
{
private readonly ILibraryManager libraryManager;
private readonly ITranscoder transcoder;
private readonly ILibraryManager _libraryManager;
private readonly ITranscoder _transcoder;
public SubtitleController(ILibraryManager libraryManager, ITranscoder transcoder)
{
this.libraryManager = libraryManager;
this.transcoder = transcoder;
_libraryManager = libraryManager;
_transcoder = transcoder;
}
[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}.{identifier}.{extension?}")]
@ -25,7 +26,7 @@ namespace Kyoo.Controllers
string languageTag = identifier.Substring(0, 3);
bool forced = identifier.Length > 3 && identifier.Substring(4) == "forced";
Track subtitle = libraryManager.GetSubtitle(showSlug, seasonNumber, episodeNumber, languageTag, forced);
Track subtitle = _libraryManager.GetSubtitle(showSlug, seasonNumber, episodeNumber, languageTag, forced);
if (subtitle == null)
return NotFound();
@ -49,14 +50,14 @@ namespace Kyoo.Controllers
[HttpGet("extract/{showSlug}-s{seasonNumber}e{episodeNumber}")]
public async Task<string> ExtractSubtitle(string showSlug, long seasonNumber, long episodeNumber)
{
Episode episode = libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);
libraryManager.ClearSubtitles(episode.ID);
Episode episode = _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);
_libraryManager.ClearSubtitles(episode.ID);
Track[] tracks = await transcoder.ExtractSubtitles(episode.Path);
Track[] tracks = await _transcoder.ExtractSubtitles(episode.Path);
foreach (Track track in tracks)
{
track.EpisodeID = episode.ID;
libraryManager.RegisterTrack(track);
_libraryManager.RegisterTrack(track);
}
return "Done. " + tracks.Length + " track(s) extracted.";
@ -65,16 +66,16 @@ namespace Kyoo.Controllers
[HttpGet("extract/{showSlug}")]
public async Task<string> ExtractSubtitle(string showSlug)
{
IEnumerable<Episode> episodes = libraryManager.GetEpisodes(showSlug);
IEnumerable<Episode> episodes = _libraryManager.GetEpisodes(showSlug);
foreach (Episode episode in episodes)
{
libraryManager.ClearSubtitles(episode.ID);
_libraryManager.ClearSubtitles(episode.ID);
Track[] tracks = await transcoder.ExtractSubtitles(episode.Path);
Track[] tracks = await _transcoder.ExtractSubtitles(episode.Path);
foreach (Track track in tracks)
{
track.EpisodeID = episode.ID;
libraryManager.RegisterTrack(track);
_libraryManager.RegisterTrack(track);
}
}
@ -85,11 +86,11 @@ namespace Kyoo.Controllers
public class ConvertSubripToVtt : IActionResult
{
private readonly string path;
private readonly string _path;
public ConvertSubripToVtt(string subtitlePath)
{
path = subtitlePath;
_path = subtitlePath;
}
public async Task ExecuteResultAsync(ActionContext context)
@ -100,13 +101,13 @@ namespace Kyoo.Controllers
context.HttpContext.Response.StatusCode = 200;
context.HttpContext.Response.Headers.Add("Content-Type", "text/vtt");
using (StreamWriter writer = new StreamWriter(context.HttpContext.Response.Body))
await using (StreamWriter writer = new StreamWriter(context.HttpContext.Response.Body))
{
await writer.WriteLineAsync("WEBVTT");
await writer.WriteLineAsync("");
await writer.WriteLineAsync("");
using (StreamReader reader = new StreamReader(path))
using (StreamReader reader = new StreamReader(_path))
{
while ((line = await reader.ReadLineAsync()) != null)
{
@ -127,7 +128,7 @@ namespace Kyoo.Controllers
await context.HttpContext.Response.Body.FlushAsync();
}
public List<string> ConvertBlock(List<string> lines)
private static List<string> ConvertBlock(List<string> lines)
{
lines[1] = lines[1].Replace(',', '.');
if (lines[2].Length > 5)

View File

@ -1,25 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System.IO;
using Kyoo.Controllers;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
public class ThumbnailController : ControllerBase
{
private readonly ILibraryManager libraryManager;
private readonly string peoplePath;
private readonly ILibraryManager _libraryManager;
private readonly string _peoplePath;
public ThumbnailController(ILibraryManager libraryManager, IConfiguration config)
{
this.libraryManager = libraryManager;
peoplePath = config.GetValue<string>("peoplePath");
_libraryManager = libraryManager;
_peoplePath = config.GetValue<string>("peoplePath");
}
[HttpGet("poster/{showSlug}")]
public IActionResult GetShowThumb(string showSlug)
{
string path = libraryManager.GetShowBySlug(showSlug)?.Path;
string path = _libraryManager.GetShowBySlug(showSlug)?.Path;
if (path == null)
return NotFound();
@ -27,14 +28,13 @@ namespace Kyoo.Controllers
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(thumb, "image/jpg");
else
return NotFound();
return NotFound();
}
[HttpGet("logo/{showSlug}")]
public IActionResult GetShowLogo(string showSlug)
{
string path = libraryManager.GetShowBySlug(showSlug)?.Path;
string path = _libraryManager.GetShowBySlug(showSlug)?.Path;
if (path == null)
return NotFound();
@ -42,14 +42,13 @@ namespace Kyoo.Controllers
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(thumb, "image/jpg");
else
return NotFound();
return NotFound();
}
[HttpGet("backdrop/{showSlug}")]
public IActionResult GetShowBackdrop(string showSlug)
{
string path = libraryManager.GetShowBySlug(showSlug)?.Path;
string path = _libraryManager.GetShowBySlug(showSlug)?.Path;
if (path == null)
return NotFound();
@ -57,14 +56,13 @@ namespace Kyoo.Controllers
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(thumb, "image/jpg");
else
return NotFound();
return NotFound();
}
[HttpGet("peopleimg/{peopleSlug}")]
public IActionResult GetPeopleIcon(string peopleSlug)
{
string thumbPath = Path.Combine(peoplePath, peopleSlug + ".jpg");
string thumbPath = Path.Combine(_peoplePath, peopleSlug + ".jpg");
if (!System.IO.File.Exists(thumbPath))
return NotFound();
@ -74,7 +72,7 @@ namespace Kyoo.Controllers
[HttpGet("thumb/{showSlug}-s{seasonNumber}e{episodeNumber}")]
public IActionResult GetEpisodeThumb(string showSlug, long seasonNumber, long episodeNumber)
{
string path = libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber)?.Path;
string path = _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber)?.Path;
if (path == null)
return NotFound();

View File

@ -5,55 +5,50 @@ using Microsoft.Extensions.Configuration;
using System.IO;
using System.Threading.Tasks;
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("[controller]")]
[ApiController]
public class VideoController : ControllerBase
{
private readonly ILibraryManager libraryManager;
private readonly ITranscoder transcoder;
private readonly string transmuxPath;
private readonly ILibraryManager _libraryManager;
private readonly ITranscoder _transcoder;
private readonly string _transmuxPath;
public VideoController(ILibraryManager libraryManager, ITranscoder transcoder, IConfiguration config)
{
this.libraryManager = libraryManager;
this.transcoder = transcoder;
transmuxPath = config.GetValue<string>("transmuxTempPath");
_libraryManager = libraryManager;
_transcoder = transcoder;
_transmuxPath = config.GetValue<string>("transmuxTempPath");
}
[HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}")]
public IActionResult Index(string showSlug, long seasonNumber, long episodeNumber)
{
WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
WatchItem episode = _libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
if (episode != null && System.IO.File.Exists(episode.Path))
return PhysicalFile(episode.Path, "video/x-matroska", true);
else
return NotFound();
return NotFound();
}
[HttpGet("transmux/{showSlug}-s{seasonNumber}e{episodeNumber}")]
public async Task<IActionResult> Transmux(string showSlug, long seasonNumber, long episodeNumber)
{
WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
WatchItem episode = _libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
if (episode != null && System.IO.File.Exists(episode.Path))
{
string path = await transcoder.Transmux(episode);
if (path != null)
return PhysicalFile(path, "application/x-mpegURL ", true);
else
return StatusCode(500);
}
else
return NotFound();
if (episode == null || !System.IO.File.Exists(episode.Path))
return NotFound();
string path = await _transcoder.Transmux(episode);
if (path != null)
return PhysicalFile(path, "application/x-mpegURL ", true);
return StatusCode(500);
}
[HttpGet("transmux/{episodeLink}/segment/{chunk}")]
public IActionResult GetTransmuxedChunk(string episodeLink, string chunk)
{
string path = Path.Combine(transmuxPath, episodeLink);
string path = Path.Combine(_transmuxPath, episodeLink);
path = Path.Combine(path, "segments" + Path.DirectorySeparatorChar + chunk);
return PhysicalFile(path, "video/MP2T");
@ -62,18 +57,14 @@ namespace Kyoo.Controllers
[HttpGet("transcode/{showSlug}-s{seasonNumber}e{episodeNumber}")]
public async Task<IActionResult> Transcode(string showSlug, long seasonNumber, long episodeNumber)
{
WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
WatchItem episode = _libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
if (episode != null && System.IO.File.Exists(episode.Path))
{
string path = await transcoder.Transcode(episode);
if (path != null)
return PhysicalFile(path, "application/x-mpegURL ", true);
else
return StatusCode(500);
}
else
return NotFound();
if (episode == null || !System.IO.File.Exists(episode.Path))
return NotFound();
string path = await _transcoder.Transcode(episode);
if (path != null)
return PhysicalFile(path, "application/x-mpegURL ", true);
return StatusCode(500);
}
}
}

View File

@ -1,27 +1,24 @@
using Kyoo.Controllers;
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace Kyoo.Controllers
namespace Kyoo.Api
{
[Route("api/[controller]")]
[ApiController]
public class WatchController : ControllerBase
{
private readonly ILibraryManager libraryManager;
private readonly ILibraryManager _libraryManager;
public WatchController(ILibraryManager libraryManager)
{
this.libraryManager = libraryManager;
_libraryManager = libraryManager;
}
[HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}")]
public ActionResult<WatchItem> Index(string showSlug, long seasonNumber, long episodeNumber)
{
WatchItem item = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
WatchItem item = _libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
if(item == null)
return NotFound();

View File

@ -19,7 +19,6 @@
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="3.0.2" />
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="3.0.2" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="3.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.0.0" />