Serializing images paths

This commit is contained in:
Zoe Roux 2021-03-13 00:49:12 +01:00
parent 356b8a5472
commit 779702f969
16 changed files with 100 additions and 124 deletions

View File

@ -7,4 +7,15 @@ namespace Kyoo.Models.Attributes
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class DeserializeIgnoreAttribute : Attribute {} public class DeserializeIgnoreAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SerializeAsAttribute : Attribute
{
public string Format { get; }
public SerializeAsAttribute(string format)
{
Format = format;
}
}
} }

View File

@ -8,7 +8,7 @@ namespace Kyoo.Models
public int ID { get; set; } public int ID { get; set; }
public string Slug { get; set; } public string Slug { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Poster { get; set; } [SerializeAs("{HOST}/api/library/{Slug}/poster")] public string Poster { get; set; }
public string Overview { get; set; } public string Overview { get; set; }
[LoadableRelation] public virtual ICollection<Show> Shows { get; set; } [LoadableRelation] public virtual ICollection<Show> Shows { get; set; }
[LoadableRelation] public virtual ICollection<Library> Libraries { get; set; } [LoadableRelation] public virtual ICollection<Library> Libraries { get; set; }

View File

@ -19,14 +19,14 @@ namespace Kyoo.Models
public int EpisodeNumber { get; set; } = -1; public int EpisodeNumber { get; set; } = -1;
public int AbsoluteNumber { get; set; } = -1; public int AbsoluteNumber { get; set; } = -1;
[SerializeIgnore] public string Path { get; set; } [SerializeIgnore] public string Path { get; set; }
public string Thumb => $"/api/episodes/{Slug}/thumb";
[SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] public string Thumb { get; set; }
public string Title { get; set; } public string Title { get; set; }
public string Overview { get; set; } public string Overview { get; set; }
public DateTime? ReleaseDate { get; set; } public DateTime? ReleaseDate { get; set; }
public int Runtime { get; set; } //This runtime variable should be in minutes public int Runtime { get; set; } //This runtime variable should be in minutes
[SerializeIgnore] public string Poster { get; set; }
[LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; } [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[LoadableRelation] public virtual ICollection<Track> Tracks { get; set; } [LoadableRelation] public virtual ICollection<Track> Tracks { get; set; }
@ -41,7 +41,7 @@ namespace Kyoo.Models
string overview, string overview,
DateTime? releaseDate, DateTime? releaseDate,
int runtime, int runtime,
string poster, string thumb,
IEnumerable<MetadataID> externalIDs) IEnumerable<MetadataID> externalIDs)
{ {
SeasonNumber = seasonNumber; SeasonNumber = seasonNumber;
@ -51,7 +51,7 @@ namespace Kyoo.Models
Overview = overview; Overview = overview;
ReleaseDate = releaseDate; ReleaseDate = releaseDate;
Runtime = runtime; Runtime = runtime;
Poster = poster; Thumb = thumb;
ExternalIDs = externalIDs?.ToArray(); ExternalIDs = externalIDs?.ToArray();
} }

View File

@ -18,8 +18,7 @@ namespace Kyoo.Models
public string Overview { get; set; } public string Overview { get; set; }
public int? Year { get; set; } public int? Year { get; set; }
[SerializeIgnore] public string Poster { get; set; } [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; }
public string Thumb => $"/api/seasons/{Slug}/thumb";
[EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; } [EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[LoadableRelation] public virtual ICollection<Episode> Episodes { get; set; } [LoadableRelation] public virtual ICollection<Episode> Episodes { get; set; }

View File

@ -18,9 +18,9 @@ namespace Kyoo.Models
public int? StartYear { get; set; } public int? StartYear { get; set; }
public int? EndYear { get; set; } public int? EndYear { get; set; }
public string Poster { get; set; } [SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; }
public string Logo { get; set; } [SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; }
public string Backdrop { get; set; } [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; }
public bool IsMovie { get; set; } public bool IsMovie { get; set; }

View File

@ -1,7 +1,9 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -13,6 +15,12 @@ namespace Kyoo.Controllers
public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver
{ {
private int _depth = -1; private int _depth = -1;
private string _host;
public JsonPropertyIgnorer(string host)
{
_host = host;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{ {
@ -38,6 +46,10 @@ namespace Kyoo.Controllers
property.ShouldSerialize = _ => false; property.ShouldSerialize = _ => false;
if (member?.GetCustomAttribute<DeserializeIgnoreAttribute>() != null) if (member?.GetCustomAttribute<DeserializeIgnoreAttribute>() != null)
property.ShouldDeserialize = _ => false; property.ShouldDeserialize = _ => false;
SerializeAsAttribute serializeAs = member?.GetCustomAttribute<SerializeAsAttribute>();
if (serializeAs != null)
property.ValueProvider = new SerializeAsProvider(serializeAs.Format, _host);
return property; return property;
} }
@ -86,4 +98,40 @@ namespace Kyoo.Controllers
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
public class SerializeAsProvider : IValueProvider
{
private string _format;
private string _host;
public SerializeAsProvider(string format, string host)
{
_format = format;
_host = host.TrimEnd('/');
}
public object GetValue(object target)
{
return Regex.Replace(_format, @"(?<!{){(\w+)}", x =>
{
string value = x.Groups[1].Value;
if (value == "HOST")
return _host;
PropertyInfo properties = target.GetType().GetProperties()
.FirstOrDefault(y => y.Name == value);
if (properties == null)
return null;
if (properties.GetValue(target) is string ret)
return ret;
throw new ArgumentException($"Invalid serializer replacement {value}");
});
}
public void SetValue(object target, object value)
{
throw new NotImplementedException();
}
}
} }

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Kyoo.CommonApi namespace Kyoo.CommonApi
{ {
@ -24,9 +25,15 @@ namespace Kyoo.CommonApi
where.Remove(key); where.Remove(key);
} }
string[] fields = context.HttpContext.Request.Query["fields"] List<string> fields = context.HttpContext.Request.Query["fields"]
.SelectMany(x => x.Split(',')) .SelectMany(x => x.Split(','))
.ToArray(); .ToList();
if (fields.Contains("internal"))
{
fields.Remove("internal");
context.HttpContext.Items["internal"] = true;
// TODO disable SerializeAs attributes when this is true.
}
if (context.ActionDescriptor is ControllerActionDescriptor descriptor) if (context.ActionDescriptor is ControllerActionDescriptor descriptor)
{ {
Type type = descriptor.MethodInfo.ReturnType; Type type = descriptor.MethodInfo.ReturnType;
@ -50,7 +57,7 @@ namespace Kyoo.CommonApi
}); });
return null; return null;
}) })
.ToArray(); .ToList();
if (context.Result != null) if (context.Result != null)
return; return;
} }
@ -71,7 +78,7 @@ namespace Kyoo.CommonApi
return; return;
await using ILibraryManager library = context.HttpContext.RequestServices.GetService<ILibraryManager>(); await using ILibraryManager library = context.HttpContext.RequestServices.GetService<ILibraryManager>();
string[] fields = (string[])context.HttpContext.Items["fields"]; ICollection<string> fields = (ICollection<string>)context.HttpContext.Items["fields"];
Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>)); Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>));

View File

@ -44,7 +44,7 @@ namespace Kyoo.Controllers
Library library, Library library,
string what) string what)
{ {
List<T> ret = new List<T>(); List<T> ret = new();
IEnumerable<IMetadataProvider> providers = library?.Providers IEnumerable<IMetadataProvider> providers = library?.Providers
.Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug)) .Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug))
@ -121,6 +121,7 @@ namespace Kyoo.Controllers
$"the season {seasonNumber} of {show.Title}"); $"the season {seasonNumber} of {show.Title}");
season.Show = show; season.Show = show;
season.ShowID = show.ID; season.ShowID = show.ID;
season.ShowSlug = show.Slug;
season.SeasonNumber = season.SeasonNumber == -1 ? seasonNumber : season.SeasonNumber; season.SeasonNumber = season.SeasonNumber == -1 ? seasonNumber : season.SeasonNumber;
season.Title ??= $"Season {season.SeasonNumber}"; season.Title ??= $"Season {season.SeasonNumber}";
return season; return season;
@ -139,6 +140,7 @@ namespace Kyoo.Controllers
"an episode"); "an episode");
episode.Show = show; episode.Show = show;
episode.ShowID = show.ID; episode.ShowID = show.ID;
episode.ShowSlug = show.Slug;
episode.Path = episodePath; episode.Path = episodePath;
episode.SeasonNumber = episode.SeasonNumber != -1 ? episode.SeasonNumber : seasonNumber; episode.SeasonNumber = episode.SeasonNumber != -1 ? episode.SeasonNumber : seasonNumber;
episode.EpisodeNumber = episode.EpisodeNumber != -1 ? episode.EpisodeNumber : episodeNumber; episode.EpisodeNumber = episode.EpisodeNumber != -1 ? episode.EpisodeNumber : episodeNumber;

View File

@ -96,11 +96,11 @@ namespace Kyoo.Controllers
if (episode?.Path == null) if (episode?.Path == null)
return default; return default;
if (episode.Poster != null) if (episode.Thumb != null)
{ {
string localPath = Path.ChangeExtension(episode.Path, "jpg"); string localPath = Path.ChangeExtension(episode.Path, "jpg");
if (alwaysDownload || !File.Exists(localPath)) if (alwaysDownload || !File.Exists(localPath))
await DownloadImage(episode.Poster, localPath, $"The thumbnail of {episode.Show.Title}"); await DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Show.Title}");
} }
return episode; return episode;
} }

View File

@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Kyoo.Models.DatabaseMigrations.Internal namespace Kyoo.Models.DatabaseMigrations.Internal
{ {
[DbContext(typeof(DatabaseContext))] [DbContext(typeof(DatabaseContext))]
[Migration("20210306181259_Initial")] [Migration("20210312234147_Initial")]
partial class Initial partial class Initial
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -71,9 +71,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
b.Property<string>("Path") b.Property<string>("Path")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("Poster")
.HasColumnType("text");
b.Property<DateTime?>("ReleaseDate") b.Property<DateTime?>("ReleaseDate")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
@ -89,6 +86,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
b.Property<int>("ShowID") b.Property<int>("ShowID")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<string>("Thumb")
.HasColumnType("text");
b.Property<string>("Title") b.Property<string>("Title")
.HasColumnType("text"); .HasColumnType("text");

View File

@ -318,11 +318,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
EpisodeNumber = table.Column<int>(type: "integer", nullable: false), EpisodeNumber = table.Column<int>(type: "integer", nullable: false),
AbsoluteNumber = table.Column<int>(type: "integer", nullable: false), AbsoluteNumber = table.Column<int>(type: "integer", nullable: false),
Path = table.Column<string>(type: "text", nullable: true), Path = table.Column<string>(type: "text", nullable: true),
Thumb = table.Column<string>(type: "text", nullable: true),
Title = table.Column<string>(type: "text", nullable: true), Title = table.Column<string>(type: "text", nullable: true),
Overview = table.Column<string>(type: "text", nullable: true), Overview = table.Column<string>(type: "text", nullable: true),
ReleaseDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true), ReleaseDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
Runtime = table.Column<int>(type: "integer", nullable: false), Runtime = table.Column<int>(type: "integer", nullable: false)
Poster = table.Column<string>(type: "text", nullable: true)
}, },
constraints: table => constraints: table =>
{ {

View File

@ -69,9 +69,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
b.Property<string>("Path") b.Property<string>("Path")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("Poster")
.HasColumnType("text");
b.Property<DateTime?>("ReleaseDate") b.Property<DateTime?>("ReleaseDate")
.HasColumnType("timestamp without time zone"); .HasColumnType("timestamp without time zone");
@ -87,6 +84,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
b.Property<int>("ShowID") b.Property<int>("ShowID")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<string>("Thumb")
.HasColumnType("text");
b.Property<string>("Title") b.Property<string>("Title")
.HasColumnType("text"); .HasColumnType("text");

View File

@ -11,6 +11,7 @@ namespace Kyoo
{ {
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
if (args.Length > 0) if (args.Length > 0)
FileSystem.CurrentDirectory = args[0]; FileSystem.CurrentDirectory = args[0];
if (!File.Exists("./appsettings.json")) if (!File.Exists("./appsettings.json"))

View File

@ -37,6 +37,8 @@ namespace Kyoo
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
string publicUrl = _configuration.GetValue<string>("public_url");
services.AddSpaStaticFiles(configuration => services.AddSpaStaticFiles(configuration =>
{ {
configuration.RootPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"); configuration.RootPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot");
@ -45,7 +47,7 @@ namespace Kyoo
services.AddControllers() services.AddControllers()
.AddNewtonsoftJson(x => .AddNewtonsoftJson(x =>
{ {
x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(); x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(publicUrl);
x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); x.SerializerSettings.Converters.Add(new PeopleRoleConverter());
}); });
services.AddHttpClient(); services.AddHttpClient();
@ -63,7 +65,6 @@ namespace Kyoo
}); });
string assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; string assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
string publicUrl = _configuration.GetValue<string>("public_url");
services.AddIdentityCore<User>(o => services.AddIdentityCore<User>(o =>
{ {

View File

@ -351,7 +351,8 @@ namespace Kyoo.Controllers
Title = show.Title, Title = show.Title,
Path = episodePath, Path = episodePath,
Show = show, Show = show,
ShowID = show.ID ShowID = show.ID,
ShowSlug = show.Slug
}; };
episode.Tracks = await GetTracks(episode); episode.Tracks = await GetTracks(episode);
return episode; return episode;

View File

@ -1,94 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System.IO;
using System.Threading.Tasks;
using Kyoo.Controllers;
using Microsoft.AspNetCore.Authorization;
namespace Kyoo.Api
{
public class ThumbnailController : ControllerBase
{
private readonly ILibraryManager _libraryManager;
private readonly string _peoplePath;
public ThumbnailController(ILibraryManager libraryManager, IConfiguration config)
{
_libraryManager = libraryManager;
_peoplePath = config.GetValue<string>("peoplePath");
}
[HttpGet("poster/{showSlug}")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetShowThumb(string showSlug)
{
string path = (await _libraryManager.GetShow(showSlug))?.Path;
if (path == null)
return NotFound();
string thumb = Path.Combine(path, "poster.jpg");
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg");
return NotFound();
}
[HttpGet("logo/{showSlug}")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetShowLogo(string showSlug)
{
string path = (await _libraryManager.GetShow(showSlug))?.Path;
if (path == null)
return NotFound();
string thumb = Path.Combine(path, "logo.png");
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg");
return NotFound();
}
[HttpGet("backdrop/{showSlug}")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetShowBackdrop(string showSlug)
{
string path = (await _libraryManager.GetShow(showSlug))?.Path;
if (path == null)
return NotFound();
string thumb = Path.Combine(path, "backdrop.jpg");
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg");
return NotFound();
}
[HttpGet("peopleimg/{peopleSlug}")]
[Authorize(Policy="Read")]
public IActionResult GetPeopleIcon(string peopleSlug)
{
string thumbPath = Path.Combine(_peoplePath, peopleSlug + ".jpg");
if (!System.IO.File.Exists(thumbPath))
return NotFound();
return new PhysicalFileResult(Path.GetFullPath(thumbPath), "image/jpg");
}
[HttpGet("thumb/{showSlug}-s{seasonNumber}e{episodeNumber}")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetEpisodeThumb(string showSlug, int seasonNumber, int episodeNumber)
{
string path = (await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber))?.Path;
if (path == null)
return NotFound();
string thumb = Path.ChangeExtension(path, "jpg");
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg");
return NotFound();
}
}
}