diff --git a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs index 3622f77d..3eafb90c 100644 --- a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs +++ b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs @@ -7,4 +7,15 @@ namespace Kyoo.Models.Attributes [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class DeserializeIgnoreAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class SerializeAsAttribute : Attribute + { + public string Format { get; } + + public SerializeAsAttribute(string format) + { + Format = format; + } + } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 936620c9..b1274278 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -8,7 +8,7 @@ namespace Kyoo.Models public int ID { get; set; } public string Slug { 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; } [LoadableRelation] public virtual ICollection Shows { get; set; } [LoadableRelation] public virtual ICollection Libraries { get; set; } diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 4d45c397..0515da46 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -19,14 +19,14 @@ namespace Kyoo.Models public int EpisodeNumber { get; set; } = -1; public int AbsoluteNumber { get; set; } = -1; [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 Overview { get; set; } public DateTime? ReleaseDate { get; set; } public int Runtime { get; set; } //This runtime variable should be in minutes - [SerializeIgnore] public string Poster { get; set; } [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } [LoadableRelation] public virtual ICollection Tracks { get; set; } @@ -41,7 +41,7 @@ namespace Kyoo.Models string overview, DateTime? releaseDate, int runtime, - string poster, + string thumb, IEnumerable externalIDs) { SeasonNumber = seasonNumber; @@ -51,7 +51,7 @@ namespace Kyoo.Models Overview = overview; ReleaseDate = releaseDate; Runtime = runtime; - Poster = poster; + Thumb = thumb; ExternalIDs = externalIDs?.ToArray(); } diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 8691e5f3..827b24f7 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -18,8 +18,7 @@ namespace Kyoo.Models public string Overview { get; set; } public int? Year { get; set; } - [SerializeIgnore] public string Poster { get; set; } - public string Thumb => $"/api/seasons/{Slug}/thumb"; + [SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; } [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } [LoadableRelation] public virtual ICollection Episodes { get; set; } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index a3cda2dd..7b948b55 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -18,9 +18,9 @@ namespace Kyoo.Models public int? StartYear { get; set; } public int? EndYear { get; set; } - public string Poster { get; set; } - public string Logo { get; set; } - public string Backdrop { get; set; } + [SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; } + [SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; } + [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; } public bool IsMovie { get; set; } diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 57eb45d9..8702e66b 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -1,7 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using Kyoo.Models; using Kyoo.Models.Attributes; using Newtonsoft.Json; @@ -13,7 +15,13 @@ namespace Kyoo.Controllers public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver { private int _depth = -1; - + private string _host; + + public JsonPropertyIgnorer(string host) + { + _host = host; + } + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty property = base.CreateProperty(member, memberSerialization); @@ -38,6 +46,10 @@ namespace Kyoo.Controllers property.ShouldSerialize = _ => false; if (member?.GetCustomAttribute() != null) property.ShouldDeserialize = _ => false; + + SerializeAsAttribute serializeAs = member?.GetCustomAttribute(); + if (serializeAs != null) + property.ValueProvider = new SerializeAsProvider(serializeAs.Format, _host); return property; } @@ -86,4 +98,40 @@ namespace Kyoo.Controllers 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, @"(? + { + 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(); + } + } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/ResourceViewAttribute.cs b/Kyoo.CommonAPI/ResourceViewAttribute.cs index a05a8fec..fa177342 100644 --- a/Kyoo.CommonAPI/ResourceViewAttribute.cs +++ b/Kyoo.CommonAPI/ResourceViewAttribute.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Kyoo.CommonApi { @@ -24,9 +25,15 @@ namespace Kyoo.CommonApi where.Remove(key); } - string[] fields = context.HttpContext.Request.Query["fields"] + List fields = context.HttpContext.Request.Query["fields"] .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) { Type type = descriptor.MethodInfo.ReturnType; @@ -50,7 +57,7 @@ namespace Kyoo.CommonApi }); return null; }) - .ToArray(); + .ToList(); if (context.Result != null) return; } @@ -71,7 +78,7 @@ namespace Kyoo.CommonApi return; await using ILibraryManager library = context.HttpContext.RequestServices.GetService(); - string[] fields = (string[])context.HttpContext.Items["fields"]; + ICollection fields = (ICollection)context.HttpContext.Items["fields"]; Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>)); diff --git a/Kyoo/Controllers/ProviderManager.cs b/Kyoo/Controllers/ProviderManager.cs index b2795c85..d025c31c 100644 --- a/Kyoo/Controllers/ProviderManager.cs +++ b/Kyoo/Controllers/ProviderManager.cs @@ -44,7 +44,7 @@ namespace Kyoo.Controllers Library library, string what) { - List ret = new List(); + List ret = new(); IEnumerable providers = library?.Providers .Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug)) @@ -121,6 +121,7 @@ namespace Kyoo.Controllers $"the season {seasonNumber} of {show.Title}"); season.Show = show; season.ShowID = show.ID; + season.ShowSlug = show.Slug; season.SeasonNumber = season.SeasonNumber == -1 ? seasonNumber : season.SeasonNumber; season.Title ??= $"Season {season.SeasonNumber}"; return season; @@ -139,6 +140,7 @@ namespace Kyoo.Controllers "an episode"); episode.Show = show; episode.ShowID = show.ID; + episode.ShowSlug = show.Slug; episode.Path = episodePath; episode.SeasonNumber = episode.SeasonNumber != -1 ? episode.SeasonNumber : seasonNumber; episode.EpisodeNumber = episode.EpisodeNumber != -1 ? episode.EpisodeNumber : episodeNumber; diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 07a1d709..ac4aeeec 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -96,11 +96,11 @@ namespace Kyoo.Controllers if (episode?.Path == null) return default; - if (episode.Poster != null) + if (episode.Thumb != null) { string localPath = Path.ChangeExtension(episode.Path, "jpg"); 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; } diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs index 904bb2c2..8817046e 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20210306181259_Initial")] + [Migration("20210312234147_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -71,9 +71,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Path") .HasColumnType("text"); - b.Property("Poster") - .HasColumnType("text"); - b.Property("ReleaseDate") .HasColumnType("timestamp without time zone"); @@ -89,6 +86,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("ShowID") .HasColumnType("integer"); + b.Property("Thumb") + .HasColumnType("text"); + b.Property("Title") .HasColumnType("text"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs index c9e95676..4b007b7f 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20210306181259_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20210312234147_Initial.cs @@ -318,11 +318,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal EpisodeNumber = table.Column(type: "integer", nullable: false), AbsoluteNumber = table.Column(type: "integer", nullable: false), Path = table.Column(type: "text", nullable: true), + Thumb = table.Column(type: "text", nullable: true), Title = table.Column(type: "text", nullable: true), Overview = table.Column(type: "text", nullable: true), ReleaseDate = table.Column(type: "timestamp without time zone", nullable: true), - Runtime = table.Column(type: "integer", nullable: false), - Poster = table.Column(type: "text", nullable: true) + Runtime = table.Column(type: "integer", nullable: false) }, constraints: table => { diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 5f5a7395..fb3f4599 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -69,9 +69,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Path") .HasColumnType("text"); - b.Property("Poster") - .HasColumnType("text"); - b.Property("ReleaseDate") .HasColumnType("timestamp without time zone"); @@ -87,6 +84,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("ShowID") .HasColumnType("integer"); + b.Property("Thumb") + .HasColumnType("text"); + b.Property("Title") .HasColumnType("text"); diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index eae316f2..5659843d 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -11,6 +11,7 @@ namespace Kyoo { public static async Task Main(string[] args) { + if (args.Length > 0) FileSystem.CurrentDirectory = args[0]; if (!File.Exists("./appsettings.json")) diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index c31c51e6..72666c57 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -37,6 +37,8 @@ namespace Kyoo public void ConfigureServices(IServiceCollection services) { + string publicUrl = _configuration.GetValue("public_url"); + services.AddSpaStaticFiles(configuration => { configuration.RootPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"); @@ -45,7 +47,7 @@ namespace Kyoo services.AddControllers() .AddNewtonsoftJson(x => { - x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(); + x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(publicUrl); x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); }); services.AddHttpClient(); @@ -63,7 +65,6 @@ namespace Kyoo }); string assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; - string publicUrl = _configuration.GetValue("public_url"); services.AddIdentityCore(o => { diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 2d65987d..0cbf431a 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -351,7 +351,8 @@ namespace Kyoo.Controllers Title = show.Title, Path = episodePath, Show = show, - ShowID = show.ID + ShowID = show.ID, + ShowSlug = show.Slug }; episode.Tracks = await GetTracks(episode); return episode; diff --git a/Kyoo/Views/API/ThumbnailAPI.cs b/Kyoo/Views/API/ThumbnailAPI.cs deleted file mode 100644 index 72c13ae0..00000000 --- a/Kyoo/Views/API/ThumbnailAPI.cs +++ /dev/null @@ -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("peoplePath"); - } - - - [HttpGet("poster/{showSlug}")] - [Authorize(Policy="Read")] - public async Task 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 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 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 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(); - } - } -}