From 9ae846f88a4bfe8835417d608b25381d08b5c7c7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 13 May 2021 23:01:11 +0200 Subject: [PATCH 1/9] Starting a config apio --- .../Models/Attributes/PermissionAttribute.cs | 7 +++- Kyoo.Common/Module.cs | 8 ++++ Kyoo/Program.cs | 2 + Kyoo/Views/ConfigurationApi.cs | 41 +++++++++++++++++++ Kyoo/settings.json | 4 +- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 Kyoo/Views/ConfigurationApi.cs diff --git a/Kyoo.Common/Models/Attributes/PermissionAttribute.cs b/Kyoo.Common/Models/Attributes/PermissionAttribute.cs index b34fb48b..40228782 100644 --- a/Kyoo.Common/Models/Attributes/PermissionAttribute.cs +++ b/Kyoo.Common/Models/Attributes/PermissionAttribute.cs @@ -7,12 +7,17 @@ namespace Kyoo.Models.Permissions /// /// The kind of permission needed. /// + /// + /// The admin kind is used for configuration or security sensitive permissions to allow one + /// to use an overall permission without compromising security. + /// public enum Kind { Read, Write, Create, - Delete + Delete, + Admin } /// diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs index c1c09165..ea87b604 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -1,5 +1,6 @@ using System; using Kyoo.Controllers; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Kyoo @@ -62,5 +63,12 @@ namespace Kyoo services.Add(ServiceDescriptor.Describe(typeof(T), typeof(T2), lifetime)); return services.AddRepository(lifetime); } + + public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration config, string path) + { + services.Configure(config.GetSection(path)); + services.AddSingleton(new ConfigReference(path, typeof(T))); + return services; + } } } \ No newline at end of file diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index 9fecc61b..16b6d098 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; @@ -18,6 +19,7 @@ namespace Kyoo /// Main function of the program /// /// Command line arguments + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse")] public static async Task Main(string[] args) { if (!File.Exists("./settings.json")) diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs new file mode 100644 index 00000000..2dfbc463 --- /dev/null +++ b/Kyoo/Views/ConfigurationApi.cs @@ -0,0 +1,41 @@ +using Kyoo.Models.Permissions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + /// + /// An API to retrieve or edit configuration settings + /// + [Route("api/config")] + [Route("api/configuration")] + [ApiController] + public class ConfigurationApi : Controller + { + /// + /// The configuration to retrieve and edit. + /// + private readonly IConfiguration _config; + + /// + /// Create a new using the given configuration. + /// + /// The configuration to use. + public ConfigurationApi(IConfiguration config) + { + _config = config; + } + + /// + /// Get a permission from it's slug. + /// + /// The permission to retrieve. You can use __ to get a child value. + /// The associate value or list of values. + [HttpGet("{slug}")] + [Permission(nameof(ConfigurationApi), Kind.Admin)] + public ActionResult GetConfiguration(string slug) + { + return _config[slug]; + } + } +} \ No newline at end of file diff --git a/Kyoo/settings.json b/Kyoo/settings.json index fed2bd58..114636fe 100644 --- a/Kyoo/settings.json +++ b/Kyoo/settings.json @@ -32,8 +32,8 @@ "password": "passphrase" }, "permissions": { - "default": ["overall.read", "overall.write", "overall.create", "overall.delete"], - "newUser": ["overall.read", "overall.write", "overall.create", "overall.delete"] + "default": ["overall.read", "overall.write", "overall.create", "overall.delete", "overall.admin"], + "newUser": ["overall.read", "overall.write", "overall.create", "overall.delete", "overall.admin"] }, "profilePicturePath": "users/", "clients": [] From 1929994128ee462ad6e7a291ae73c79760412450 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 15 May 2021 23:18:08 +0200 Subject: [PATCH 2/9] Handling strongly typed options and adding a config get --- Kyoo.Authentication/AuthenticationModule.cs | 1 + Kyoo.Common/Models/ConfigurationReference.cs | 91 ++++++++++++++++++++ Kyoo.Common/Module.cs | 18 +++- Kyoo/Views/ConfigurationApi.cs | 30 +++++-- 4 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 Kyoo.Common/Models/ConfigurationReference.cs diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index 9c7c3687..66cdf2fd 100644 --- a/Kyoo.Authentication/AuthenticationModule.cs +++ b/Kyoo.Authentication/AuthenticationModule.cs @@ -98,6 +98,7 @@ namespace Kyoo.Authentication services.Configure(_configuration.GetSection(PermissionOption.Path)); services.Configure(_configuration.GetSection(CertificateOption.Path)); services.Configure(_configuration.GetSection(AuthenticationOption.Path)); + services.AddConfiguration(AuthenticationOption.Path); List clients = new(); diff --git a/Kyoo.Common/Models/ConfigurationReference.cs b/Kyoo.Common/Models/ConfigurationReference.cs new file mode 100644 index 00000000..fab6f9d3 --- /dev/null +++ b/Kyoo.Common/Models/ConfigurationReference.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using JetBrains.Annotations; + +namespace Kyoo.Models +{ + /// + /// A class given information about a strongly typed configuration. + /// + public class ConfigurationReference + { + /// + /// The path of the resource (separated by ':') + /// + public string Path { get; } + + /// + /// The type of the resource. + /// + public Type Type { get; } + + + /// + /// Create a new using a given path and type. + /// This method does not create sub configuration resources. Please see + /// + /// The path of the resource (separated by ':' or "__") + /// The type of the resource + /// + public ConfigurationReference(string path, Type type) + { + Path = path; + Type = type; + } + + /// + /// Return the list of configuration reference a type has. + /// + /// + /// The base path of the type (separated by ':' or "__". If empty, it will start at root) + /// + /// The type of the object + /// The list of configuration reference a type has. + public static IEnumerable CreateReference(string path, [NotNull] Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + List ret = new() + { + new ConfigurationReference(path, type) + }; + + + if (!type.IsClass || type.AssemblyQualifiedName?.StartsWith("System") == true) + return ret; + + Type enumerable = Utility.GetGenericDefinition(type, typeof(IEnumerable<>)); + Type dictionary = Utility.GetGenericDefinition(type, typeof(IDictionary<,>)); + Type dictionaryKey = dictionary?.GetGenericArguments()[0]; + + if (dictionary != null && dictionaryKey == typeof(string)) + ret.AddRange(CreateReference($"{path}:{type.Name}:*", dictionary.GetGenericArguments()[1])); + else if (dictionary != null && dictionaryKey == typeof(int)) + ret.AddRange(CreateReference($"{path}:{type.Name}:", dictionary.GetGenericArguments()[1])); + else if (enumerable != null) + ret.AddRange(CreateReference($"{path}:{type.Name}:", enumerable.GetGenericArguments()[0])); + else + { + foreach (PropertyInfo child in type.GetProperties()) + ret.AddRange(CreateReference($"{path}:{child.Name}", child.PropertyType)); + } + + return ret; + } + + /// + /// Return the list of configuration reference a type has. + /// + /// + /// The base path of the type (separated by ':' or "__". If empty, it will start at root) + /// + /// The type of the object + /// The list of configuration reference a type has. + public static IEnumerable CreateReference(string path) + { + return CreateReference(path, typeof(T)); + } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs index ea87b604..e8e23035 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -1,5 +1,7 @@ using System; +using System.Linq; using Kyoo.Controllers; +using Kyoo.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -64,10 +66,20 @@ namespace Kyoo return services.AddRepository(lifetime); } - public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration config, string path) + /// + /// Add an editable configuration to the editable configuration list + /// + /// The service collection to edit + /// The root path of the editable configuration. It should not be a nested type. + /// The type of the configuration + /// The given service collection is returned. + public static IServiceCollection AddConfiguration(this IServiceCollection services, string path) + where T : class { - services.Configure(config.GetSection(path)); - services.AddSingleton(new ConfigReference(path, typeof(T))); + if (services.Any(x => x.ServiceType == typeof(T))) + return services; + foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference(path)) + services.AddSingleton(confRef); return services; } } diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs index 2dfbc463..ef346979 100644 --- a/Kyoo/Views/ConfigurationApi.cs +++ b/Kyoo/Views/ConfigurationApi.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -15,15 +19,22 @@ namespace Kyoo.Api /// /// The configuration to retrieve and edit. /// - private readonly IConfiguration _config; + private readonly IConfiguration _configuration; + + /// + /// The strongly typed list of options + /// + private readonly Dictionary _references; /// /// Create a new using the given configuration. /// - /// The configuration to use. - public ConfigurationApi(IConfiguration config) + /// The configuration to use. + /// The strongly typed option list. + public ConfigurationApi(IConfiguration configuration, IEnumerable references) { - _config = config; + _configuration = configuration; + _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } /// @@ -35,7 +46,16 @@ namespace Kyoo.Api [Permission(nameof(ConfigurationApi), Kind.Admin)] public ActionResult GetConfiguration(string slug) { - return _config[slug]; + slug = slug.Replace("__", ":"); + // TODO handle lists and dictionaries. + if (!_references.TryGetValue(slug, out Type type)) + return NotFound(); + object ret = _configuration.GetValue(type, slug); + if (ret != null) + return ret; + object option = Activator.CreateInstance(type); + _configuration.Bind(slug, option); + return option; } } } \ No newline at end of file From 0f00c22f7f9433083c8877a63278e87688646780 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 17 May 2021 23:41:54 +0200 Subject: [PATCH 3/9] Cleaning up the startup --- Kyoo.Common/Module.cs | 1 - Kyoo/CoreModule.cs | 47 ++++++++++++++++++++++++++++++++++ Kyoo/Startup.cs | 40 +++++------------------------ Kyoo/Views/ConfigurationApi.cs | 27 ++++++++++++++++++- 4 files changed, 79 insertions(+), 36 deletions(-) diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs index e8e23035..e8fcf1b6 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using Kyoo.Controllers; using Kyoo.Models; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Kyoo diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs index 3111177e..3c637fce 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -1,10 +1,15 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Kyoo.Controllers; using Kyoo.Models.Permissions; using Kyoo.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; namespace Kyoo { @@ -66,9 +71,34 @@ namespace Kyoo typeof(IProviderRepository) }; + + /// + /// The configuration to use. + /// + private readonly IConfiguration _configuration; + + + /// + /// Create a new core module instance and use the given configuration. + /// + /// The configuration to use + public CoreModule(IConfiguration configuration) + { + _configuration = configuration; + } + /// public void Configure(IServiceCollection services, ICollection availableTypes) { + string publicUrl = _configuration.GetValue("publicUrl"); + + services.AddControllers() + .AddNewtonsoftJson(x => + { + x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(publicUrl); + x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); + }); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -99,5 +129,22 @@ namespace Kyoo if (services.All(x => x.ServiceType != typeof(IPermissionValidator))) services.AddSingleton(); } + + /// + public void ConfigureAspNet(IApplicationBuilder app) + { + FileExtensionContentTypeProvider contentTypeProvider = new(); + contentTypeProvider.Mappings[".data"] = "application/octet-stream"; + app.UseStaticFiles(new StaticFileOptions + { + ContentTypeProvider = contentTypeProvider, + FileProvider = new PhysicalFileProvider(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot")) + }); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } } } \ No newline at end of file diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index e30f495e..361c1a27 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -8,10 +8,8 @@ using Kyoo.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.SpaServices.AngularCli; -using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -22,10 +20,6 @@ namespace Kyoo /// public class Startup { - /// - /// The configuration context - /// - private readonly IConfiguration _configuration; /// /// A plugin manager used to load plugins and allow them to configure services / asp net. /// @@ -44,11 +38,11 @@ namespace Kyoo /// A logger factory used to create a logger for the plugin manager. public Startup(IServiceProvider hostProvider, IConfiguration configuration, ILoggerFactory loggerFactory, IWebHostEnvironment host) { - _configuration = configuration; - _plugins = new PluginManager(hostProvider, _configuration, loggerFactory.CreateLogger()); + _plugins = new PluginManager(hostProvider, configuration, loggerFactory.CreateLogger()); // TODO remove postgres from here and load it like a normal plugin. - _plugins.LoadPlugins(new IPlugin[] {new CoreModule(), + _plugins.LoadPlugins(new IPlugin[] { + new CoreModule(configuration), new PostgresModule(configuration, host), new AuthenticationModule(configuration, loggerFactory, host) }); @@ -60,8 +54,6 @@ namespace Kyoo /// The service collection to fill. public void ConfigureServices(IServiceCollection services) { - string publicUrl = _configuration.GetValue("publicUrl"); - services.AddMvc().AddControllersAsServices(); services.AddSpaStaticFiles(configuration => @@ -72,13 +64,7 @@ namespace Kyoo { x.EnableForHttps = true; }); - - services.AddControllers() - .AddNewtonsoftJson(x => - { - x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(publicUrl); - x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); - }); + services.AddHttpClient(); services.AddTransient(typeof(Lazy<>), typeof(LazyDi<>)); @@ -99,22 +85,14 @@ namespace Kyoo app.UseDeveloperExceptionPage(); else { - app.UseExceptionHandler("/Error"); + app.UseExceptionHandler("/error"); app.UseHsts(); } - - FileExtensionContentTypeProvider contentTypeProvider = new(); - contentTypeProvider.Mappings[".data"] = "application/octet-stream"; - app.UseStaticFiles(new StaticFileOptions - { - ContentTypeProvider = contentTypeProvider, - FileProvider = new PhysicalFileProvider(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot")) - }); + if (!env.IsDevelopment()) app.UseSpaStaticFiles(); app.UseRouting(); - app.Use((ctx, next) => { ctx.Response.Headers.Remove("X-Powered-By"); @@ -131,12 +109,6 @@ namespace Kyoo _plugins.ConfigureAspnet(app); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - - app.UseSpa(spa => { spa.Options.SourcePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Kyoo.WebApp"); diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs index ef346979..83cd80d5 100644 --- a/Kyoo/Views/ConfigurationApi.cs +++ b/Kyoo/Views/ConfigurationApi.cs @@ -40,8 +40,10 @@ namespace Kyoo.Api /// /// Get a permission from it's slug. /// - /// The permission to retrieve. You can use __ to get a child value. + /// The permission to retrieve. You can use ':' or "__" to get a child value. /// The associate value or list of values. + /// Return the configuration value or the list of configurations + /// No configuration exists for the given slug [HttpGet("{slug}")] [Permission(nameof(ConfigurationApi), Kind.Admin)] public ActionResult GetConfiguration(string slug) @@ -57,5 +59,28 @@ namespace Kyoo.Api _configuration.Bind(slug, option); return option; } + + /// + /// Edit a permission from it's slug. + /// + /// The permission to edit. You can use ':' or "__" to get a child value. + /// The new value of the configuration + /// The edited value. + /// Return the edited value + /// No configuration exists for the given slug + [HttpPut("{slug}")] + [Permission(nameof(ConfigurationApi), Kind.Admin)] + public ActionResult EditConfiguration(string slug, [FromBody] object newValue) + { + slug = slug.Replace("__", ":"); + if (!_references.TryGetValue(slug, out Type type)) + return NotFound(); + // object ret = _configuration.(type, slug); + // if (ret != null) + // return ret; + // object option = Activator.CreateInstance(type); + // _configuration.Bind(slug, option); + return newValue; + } } } \ No newline at end of file From d115797dd79529decf88f23061b0bfde1b15d481 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 18 May 2021 17:55:48 +0200 Subject: [PATCH 4/9] Creating a configuration manager with a start of edit --- .../Controllers/IConfigurationManager.cs | 33 +++++++++ Kyoo.Common/Models/Account.cs | 9 --- Kyoo.Common/Models/ImageTypes.cs | 4 - Kyoo.Common/Utility.cs | 40 ++-------- Kyoo/Controllers/ConfigurationManager.cs | 73 +++++++++++++++++++ Kyoo/CoreModule.cs | 1 + Kyoo/Views/ConfigurationApi.cs | 16 ++-- Kyoo/Views/LibraryApi.cs | 6 +- Kyoo/Views/StudioApi.cs | 4 +- 9 files changed, 128 insertions(+), 58 deletions(-) create mode 100644 Kyoo.Common/Controllers/IConfigurationManager.cs delete mode 100644 Kyoo.Common/Models/Account.cs delete mode 100644 Kyoo.Common/Models/ImageTypes.cs create mode 100644 Kyoo/Controllers/ConfigurationManager.cs diff --git a/Kyoo.Common/Controllers/IConfigurationManager.cs b/Kyoo.Common/Controllers/IConfigurationManager.cs new file mode 100644 index 00000000..ccd2bd05 --- /dev/null +++ b/Kyoo.Common/Controllers/IConfigurationManager.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; +using Kyoo.Models; +using Kyoo.Models.Exceptions; + +namespace Kyoo.Controllers +{ + /// + /// A class to ease configuration management. This work WITH Microsoft's package, you can still use IOptions patterns + /// to access your options, this manager ease dynamic work and editing. + /// It works with . + /// + public interface IConfigurationManager + { + /// + /// Edit the value of a setting using it's path. Save it to the json file. + /// + /// The path of the resource (can be separated by ':' or '__' + /// The new value of the resource + /// The type of the resource + /// No setting found at the given path. + Task EditValue(string path, T value); + + /// + /// Edit the value of a setting using it's path. Save it to the json file. + /// + /// The path of the resource (can be separated by ':' or '__' + /// The new value of the resource + /// The type of the resource + /// No setting found at the given path. + Task EditValue(string path, object value, Type type); + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Account.cs b/Kyoo.Common/Models/Account.cs deleted file mode 100644 index a3f51c61..00000000 --- a/Kyoo.Common/Models/Account.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Kyoo.Models -{ - public class Account - { - public string Username { get; set; } - public string Email { get; set; } - public string Picture { get; set; } - } -} \ No newline at end of file diff --git a/Kyoo.Common/Models/ImageTypes.cs b/Kyoo.Common/Models/ImageTypes.cs deleted file mode 100644 index 6dea10d4..00000000 --- a/Kyoo.Common/Models/ImageTypes.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Kyoo.Models -{ - public enum ImageType { Poster, Background, Logo } -} \ No newline at end of file diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 6583f704..743cf545 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -10,7 +10,6 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using JetBrains.Annotations; -using Kyoo.Models; using Kyoo.Models.Attributes; namespace Kyoo @@ -33,7 +32,7 @@ namespace Kyoo } /// - /// Get the name of a property. Usfull for selectors as members ex: Load(x => x.Shows) + /// Get the name of a property. Useful for selectors as members ex: Load(x => x.Shows) /// /// The expression /// The name of the expression @@ -71,10 +70,10 @@ namespace Kyoo } /// - /// Slugify a string (Replace spaces by -, Uniformise accents é -> e) + /// Slugify a string (Replace spaces by -, Uniformize accents é -> e) /// /// The string to slugify - /// The slugified string + /// The slug version of the given string public static string ToSlug(string str) { if (str == null) @@ -98,37 +97,12 @@ namespace Kyoo str = Regex.Replace(str, @"([-_]){2,}", "$1", RegexOptions.Compiled); return str; } - - - /// - /// Set the image of a show using the type. - /// - /// The owner of the image - /// The url of the image - /// The type of the image - public static void SetImage(Show show, string imgUrl, ImageType type) - { - switch(type) - { - case ImageType.Poster: - show.Poster = imgUrl; - break; - case ImageType.Logo: - show.Logo = imgUrl; - break; - case ImageType.Background: - show.Backdrop = imgUrl; - break; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - } /// /// Merge two lists, can keep duplicates or remove them. /// - /// The first enumarble to merge - /// The second enumerable to merge, if items from this list are equals to one from the first, they are not keeped + /// The first enumerable to merge + /// The second enumerable to merge, if items from this list are equals to one from the first, they are not kept /// Equality function to compare items. If this is null, duplicated elements are kept /// The two list merged as an array public static T[] MergeLists(IEnumerable first, @@ -150,7 +124,7 @@ namespace Kyoo /// At the end, the OnMerge method of first will be called if first is a /// /// The object to assign - /// The object containg new values + /// The object containing new values /// Fields of T will be used /// public static T Assign(T first, T second) @@ -339,7 +313,7 @@ namespace Kyoo /// /// The type to check /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). - /// The generic definition of genericType that type inherit or null if type does not implement the genric type. + /// The generic definition of genericType that type inherit or null if type does not implement the generic type. /// and can't be null /// must be a generic type public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType) diff --git a/Kyoo/Controllers/ConfigurationManager.cs b/Kyoo/Controllers/ConfigurationManager.cs new file mode 100644 index 00000000..3d9ddda8 --- /dev/null +++ b/Kyoo/Controllers/ConfigurationManager.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.Api; +using Kyoo.Models; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json.Linq; + +namespace Kyoo.Controllers +{ + public class ConfigurationManager : IConfigurationManager + { + /// + /// The configuration to retrieve and edit. + /// + private readonly IConfiguration _configuration; + + /// + /// The strongly typed list of options + /// + private readonly Dictionary _references; + + /// + /// Create a new using the given configuration. + /// + /// The configuration to use. + /// The strongly typed option list. + public ConfigurationManager(IConfiguration configuration, IEnumerable references) + { + _configuration = configuration; + _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); + } + + + /// + public Task EditValue(string path, T value) + { + return EditValue(path, value, typeof(T)); + } + + /// + public async Task EditValue(string path, object value, Type type) + { + JObject obj = JObject.FromObject(ToObject(_configuration)); + // TODO allow path to change + await using StreamWriter writer = new("settings.json"); + await writer.WriteAsync(obj.ToString()); + } + + /// + /// Transform a configuration to a strongly typed object (the root configuration is an + /// but child elements are using strong types. + /// + /// The configuration to transform + /// A strongly typed representation of the configuration. + private object ToObject(IConfiguration config) + { + ExpandoObject obj = new(); + + foreach (IConfigurationSection section in config.GetChildren()) + { + if (!_references.TryGetValue(section.Path, out Type type)) + continue; + obj.TryAdd(section.Key, section.Get(type)); + } + + return obj; + } + } +} \ No newline at end of file diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs index 3c637fce..cbd6cbd9 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -99,6 +99,7 @@ namespace Kyoo x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); }); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs index 83cd80d5..dd6dbc9b 100644 --- a/Kyoo/Views/ConfigurationApi.cs +++ b/Kyoo/Views/ConfigurationApi.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; @@ -16,6 +18,8 @@ namespace Kyoo.Api [ApiController] public class ConfigurationApi : Controller { + private readonly ConfigurationManager _manager; + /// /// The configuration to retrieve and edit. /// @@ -31,8 +35,9 @@ namespace Kyoo.Api /// /// The configuration to use. /// The strongly typed option list. - public ConfigurationApi(IConfiguration configuration, IEnumerable references) + public ConfigurationApi(ConfigurationManager manager, IConfiguration configuration, IEnumerable references) { + _manager = manager; _configuration = configuration; _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } @@ -70,16 +75,13 @@ namespace Kyoo.Api /// No configuration exists for the given slug [HttpPut("{slug}")] [Permission(nameof(ConfigurationApi), Kind.Admin)] - public ActionResult EditConfiguration(string slug, [FromBody] object newValue) + public async Task> EditConfiguration(string slug, [FromBody] object newValue) { slug = slug.Replace("__", ":"); if (!_references.TryGetValue(slug, out Type type)) return NotFound(); - // object ret = _configuration.(type, slug); - // if (ret != null) - // return ret; - // object option = Activator.CreateInstance(type); - // _configuration.Bind(slug, option); + await _manager.EditValue(slug, newValue, type); + // await _configuration.SetValue(slug, newValue, type); return newValue; } } diff --git a/Kyoo/Views/LibraryApi.cs b/Kyoo/Views/LibraryApi.cs index 239dcd8e..bbcd7a90 100644 --- a/Kyoo/Views/LibraryApi.cs +++ b/Kyoo/Views/LibraryApi.cs @@ -14,13 +14,13 @@ namespace Kyoo.Api [Route("api/library")] [Route("api/libraries")] [ApiController] - [PartialPermission(nameof(LibraryAPI))] - public class LibraryAPI : CrudApi + [PartialPermission(nameof(LibraryApi))] + public class LibraryApi : CrudApi { private readonly ILibraryManager _libraryManager; private readonly ITaskManager _taskManager; - public LibraryAPI(ILibraryManager libraryManager, ITaskManager taskManager, IConfiguration configuration) + public LibraryApi(ILibraryManager libraryManager, ITaskManager taskManager, IConfiguration configuration) : base(libraryManager.LibraryRepository, configuration) { _libraryManager = libraryManager; diff --git a/Kyoo/Views/StudioApi.cs b/Kyoo/Views/StudioApi.cs index 66b6ca5d..657988bb 100644 --- a/Kyoo/Views/StudioApi.cs +++ b/Kyoo/Views/StudioApi.cs @@ -15,11 +15,11 @@ namespace Kyoo.Api [Route("api/studios")] [ApiController] [PartialPermission(nameof(ShowApi))] - public class StudioAPI : CrudApi + public class StudioApi : CrudApi { private readonly ILibraryManager _libraryManager; - public StudioAPI(ILibraryManager libraryManager, IConfiguration config) + public StudioApi(ILibraryManager libraryManager, IConfiguration config) : base(libraryManager.StudioRepository, config) { _libraryManager = libraryManager; From f3e415a0505d62f300529fa02fcc444114b4d5ca Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 18 May 2021 22:41:58 +0200 Subject: [PATCH 5/9] Cleaning up the configuration edit --- .../Controllers/IConfigurationManager.cs | 15 ++----------- Kyoo/Controllers/ConfigurationManager.cs | 22 ++++++++++--------- Kyoo/Views/ConfigurationApi.cs | 18 +++++++++------ 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/Kyoo.Common/Controllers/IConfigurationManager.cs b/Kyoo.Common/Controllers/IConfigurationManager.cs index ccd2bd05..089f7312 100644 --- a/Kyoo.Common/Controllers/IConfigurationManager.cs +++ b/Kyoo.Common/Controllers/IConfigurationManager.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -15,19 +14,9 @@ namespace Kyoo.Controllers /// /// Edit the value of a setting using it's path. Save it to the json file. /// - /// The path of the resource (can be separated by ':' or '__' + /// The path of the resource (can be separated by ':' or '__') /// The new value of the resource - /// The type of the resource /// No setting found at the given path. - Task EditValue(string path, T value); - - /// - /// Edit the value of a setting using it's path. Save it to the json file. - /// - /// The path of the resource (can be separated by ':' or '__' - /// The new value of the resource - /// The type of the resource - /// No setting found at the given path. - Task EditValue(string path, object value, Type type); + Task EditValue(string path, object value); } } \ No newline at end of file diff --git a/Kyoo/Controllers/ConfigurationManager.cs b/Kyoo/Controllers/ConfigurationManager.cs index 3d9ddda8..38710017 100644 --- a/Kyoo/Controllers/ConfigurationManager.cs +++ b/Kyoo/Controllers/ConfigurationManager.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Kyoo.Api; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Microsoft.Extensions.Configuration; using Newtonsoft.Json.Linq; @@ -34,17 +35,18 @@ namespace Kyoo.Controllers _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } - /// - public Task EditValue(string path, T value) + public async Task EditValue(string path, object value) { - return EditValue(path, value, typeof(T)); - } - - /// - public async Task EditValue(string path, object value, Type type) - { - JObject obj = JObject.FromObject(ToObject(_configuration)); + path = path.Replace("__", ":"); + if (!_references.TryGetValue(path, out Type type)) + throw new ItemNotFoundException($"No configuration exists for the name: {path}"); + + ExpandoObject config = ToObject(_configuration); + IDictionary configDic = config; + // TODO validate the type + configDic[path] = value; + JObject obj = JObject.FromObject(config); // TODO allow path to change await using StreamWriter writer = new("settings.json"); await writer.WriteAsync(obj.ToString()); @@ -56,7 +58,7 @@ namespace Kyoo.Controllers /// /// The configuration to transform /// A strongly typed representation of the configuration. - private object ToObject(IConfiguration config) + private ExpandoObject ToObject(IConfiguration config) { ExpandoObject obj = new(); diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs index dd6dbc9b..71697551 100644 --- a/Kyoo/Views/ConfigurationApi.cs +++ b/Kyoo/Views/ConfigurationApi.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -18,7 +19,7 @@ namespace Kyoo.Api [ApiController] public class ConfigurationApi : Controller { - private readonly ConfigurationManager _manager; + private readonly IConfigurationManager _manager; /// /// The configuration to retrieve and edit. @@ -35,7 +36,7 @@ namespace Kyoo.Api /// /// The configuration to use. /// The strongly typed option list. - public ConfigurationApi(ConfigurationManager manager, IConfiguration configuration, IEnumerable references) + public ConfigurationApi(IConfigurationManager manager, IConfiguration configuration, IEnumerable references) { _manager = manager; _configuration = configuration; @@ -77,12 +78,15 @@ namespace Kyoo.Api [Permission(nameof(ConfigurationApi), Kind.Admin)] public async Task> EditConfiguration(string slug, [FromBody] object newValue) { - slug = slug.Replace("__", ":"); - if (!_references.TryGetValue(slug, out Type type)) + try + { + await _manager.EditValue(slug, newValue); + return newValue; + } + catch (ItemNotFoundException) + { return NotFound(); - await _manager.EditValue(slug, newValue, type); - // await _configuration.SetValue(slug, newValue, type); - return newValue; + } } } } \ No newline at end of file From fcd419993a2fd1616f4720937db127589c4c5afb Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 19 May 2021 23:09:43 +0200 Subject: [PATCH 6/9] Handling type validation on config edits --- Kyoo/Controllers/ConfigurationManager.cs | 7 ++++--- Kyoo/Program.cs | 7 ++++++- Kyoo/Views/ConfigurationApi.cs | 4 ++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Kyoo/Controllers/ConfigurationManager.cs b/Kyoo/Controllers/ConfigurationManager.cs index 38710017..42bdf433 100644 --- a/Kyoo/Controllers/ConfigurationManager.cs +++ b/Kyoo/Controllers/ConfigurationManager.cs @@ -41,14 +41,15 @@ namespace Kyoo.Controllers path = path.Replace("__", ":"); if (!_references.TryGetValue(path, out Type type)) throw new ItemNotFoundException($"No configuration exists for the name: {path}"); + value = JObject.FromObject(value).ToObject(type); + if (value == null) + throw new ArgumentException("Invalid value format."); ExpandoObject config = ToObject(_configuration); IDictionary configDic = config; - // TODO validate the type configDic[path] = value; JObject obj = JObject.FromObject(config); - // TODO allow path to change - await using StreamWriter writer = new("settings.json"); + await using StreamWriter writer = new(Program.JsonConfigPath); await writer.WriteAsync(obj.ToString()); } diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index 16b6d098..32d763fd 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -15,6 +15,11 @@ namespace Kyoo /// public static class Program { + /// + /// The path of the json configuration of the application. + /// + public const string JsonConfigPath = "./settings.json"; + /// /// Main function of the program /// @@ -66,7 +71,7 @@ namespace Kyoo /// The modified configuration builder private static IConfigurationBuilder SetupConfig(IConfigurationBuilder builder, string[] args) { - return builder.AddJsonFile("./settings.json", false, true) + return builder.AddJsonFile(JsonConfigPath, false, true) .AddEnvironmentVariables() .AddCommandLine(args); } diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs index 71697551..e8829e3b 100644 --- a/Kyoo/Views/ConfigurationApi.cs +++ b/Kyoo/Views/ConfigurationApi.cs @@ -87,6 +87,10 @@ namespace Kyoo.Api { return NotFound(); } + catch (ArgumentException) + { + return BadRequest(); + } } } } \ No newline at end of file From dcfb1e538cab09832808f638364d71d1b4f1052e Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 20 May 2021 00:22:35 +0200 Subject: [PATCH 7/9] Adding strongly typed options --- Kyoo.Authentication/AuthenticationModule.cs | 4 +- .../Controllers/IConfigurationManager.cs | 20 ++++++++ Kyoo.Common/Module.cs | 11 +++++ Kyoo.CommonAPI/CrudApi.cs | 5 +- Kyoo.Postgresql/PostgresModule.cs | 4 -- Kyoo/Controllers/ConfigurationManager.cs | 28 +++++++++++ Kyoo/Controllers/PluginManager.cs | 13 ++--- Kyoo/Controllers/TaskManager.cs | 18 +++---- Kyoo/Controllers/ThumbnailsManager.cs | 25 +++++----- Kyoo/Controllers/Transcoder.cs | 15 +++--- Kyoo/CoreModule.cs | 10 +++- Kyoo/Models/Options/BasicOptions.cs | 48 +++++++++++++++++++ Kyoo/Models/Options/MediaOptions.cs | 23 +++++++++ Kyoo/Models/Options/TaskOptions.cs | 28 +++++++++++ Kyoo/Program.cs | 7 ++- Kyoo/Startup.cs | 5 +- Kyoo/Views/CollectionApi.cs | 7 +-- Kyoo/Views/ConfigurationApi.cs | 40 +++++----------- Kyoo/Views/EpisodeApi.cs | 7 +-- Kyoo/Views/GenreApi.cs | 7 +-- Kyoo/Views/LibraryApi.cs | 7 +-- Kyoo/Views/LibraryItemApi.cs | 7 +-- Kyoo/Views/PeopleApi.cs | 7 +-- Kyoo/Views/ProviderApi.cs | 7 +-- Kyoo/Views/SeasonApi.cs | 7 +-- Kyoo/Views/ShowApi.cs | 7 +-- Kyoo/Views/StudioApi.cs | 7 +-- Kyoo/Views/TrackApi.cs | 7 +-- Kyoo/Views/VideoApi.cs | 15 +++--- Kyoo/settings.json | 42 ++++++++-------- 30 files changed, 302 insertions(+), 136 deletions(-) create mode 100644 Kyoo/Models/Options/BasicOptions.cs create mode 100644 Kyoo/Models/Options/MediaOptions.cs create mode 100644 Kyoo/Models/Options/TaskOptions.cs diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index 66cdf2fd..8e2c78c4 100644 --- a/Kyoo.Authentication/AuthenticationModule.cs +++ b/Kyoo.Authentication/AuthenticationModule.cs @@ -84,7 +84,7 @@ namespace Kyoo.Authentication /// public void Configure(IServiceCollection services, ICollection availableTypes) { - string publicUrl = _configuration.GetValue("publicUrl").TrimEnd('/'); + string publicUrl = _configuration.GetPublicUrl(); if (_environment.IsDevelopment()) IdentityModelEventSource.ShowPII = true; @@ -146,7 +146,7 @@ namespace Kyoo.Authentication app.UseAuthentication(); app.Use((ctx, next) => { - ctx.SetIdentityServerOrigin(_configuration.GetValue("publicUrl").TrimEnd('/')); + ctx.SetIdentityServerOrigin(_configuration.GetPublicUrl()); return next(); }); app.UseIdentityServer(); diff --git a/Kyoo.Common/Controllers/IConfigurationManager.cs b/Kyoo.Common/Controllers/IConfigurationManager.cs index 089f7312..9159d92c 100644 --- a/Kyoo.Common/Controllers/IConfigurationManager.cs +++ b/Kyoo.Common/Controllers/IConfigurationManager.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -11,6 +12,25 @@ namespace Kyoo.Controllers /// public interface IConfigurationManager { + /// + /// Get the value of a setting using it's path. + /// + /// The path of the resource (can be separated by ':' or '__') + /// No setting found at the given path. + /// The value of the settings (if it's a strongly typed one, the given type is instantiated + object GetValue(string path); + + /// + /// Get the value of a setting using it's path. + /// If your don't need a strongly typed value, see . + /// + /// The path of the resource (can be separated by ':' or '__') + /// A type to strongly type your option. + /// If your type is not the same as the registered type + /// No setting found at the given path. + /// The value of the settings (if it's a strongly typed one, the given type is instantiated + T GetValue(string path); + /// /// Edit the value of a setting using it's path. Save it to the json file. /// diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs index e8fcf1b6..0b5e0d04 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using Kyoo.Controllers; using Kyoo.Models; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Kyoo @@ -81,5 +82,15 @@ namespace Kyoo services.AddSingleton(confRef); return services; } + + /// + /// Get the public URL of kyoo using the given configuration instance. + /// + /// The configuration instance + /// The public URl of kyoo (without a slash at the end) + public static string GetPublicUrl(this IConfiguration configuration) + { + return configuration["basics:publicUrl"]?.TrimEnd('/') ?? "http://localhost:5000"; + } } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index 75a2758c..b6d03580 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -7,7 +7,6 @@ using Kyoo.Models; using Kyoo.Models.Exceptions; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; namespace Kyoo.CommonApi { @@ -18,10 +17,10 @@ namespace Kyoo.CommonApi private readonly IRepository _repository; protected readonly string BaseURL; - public CrudApi(IRepository repository, IConfiguration configuration) + public CrudApi(IRepository repository, string baseURL) { _repository = repository; - BaseURL = configuration.GetValue("publicUrl").TrimEnd('/'); + BaseURL = baseURL; } diff --git a/Kyoo.Postgresql/PostgresModule.cs b/Kyoo.Postgresql/PostgresModule.cs index 7a818296..f0c8f23c 100644 --- a/Kyoo.Postgresql/PostgresModule.cs +++ b/Kyoo.Postgresql/PostgresModule.cs @@ -66,10 +66,6 @@ namespace Kyoo.Postgresql if (_environment.IsDevelopment()) x.EnableDetailedErrors().EnableSensitiveDataLogging(); }); - // services.AddScoped(_ => new PostgresContext( - // _configuration.GetDatabaseConnection("postgres"), - // _environment.IsDevelopment())); - // services.AddScoped(x => x.GetRequiredService()); } /// diff --git a/Kyoo/Controllers/ConfigurationManager.cs b/Kyoo/Controllers/ConfigurationManager.cs index 42bdf433..c0a1340b 100644 --- a/Kyoo/Controllers/ConfigurationManager.cs +++ b/Kyoo/Controllers/ConfigurationManager.cs @@ -35,6 +35,34 @@ namespace Kyoo.Controllers _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } + /// + public object GetValue(string path) + { + path = path.Replace("__", ":"); + // TODO handle lists and dictionaries. + if (!_references.TryGetValue(path, out Type type)) + throw new ItemNotFoundException($"No configuration exists for the name: {path}"); + object ret = _configuration.GetValue(type, path); + if (ret != null) + return ret; + object option = Activator.CreateInstance(type); + _configuration.Bind(path, option); + return option; + } + + /// + public T GetValue(string path) + { + path = path.Replace("__", ":"); + // TODO handle lists and dictionaries. + if (!_references.TryGetValue(path, out Type type)) + throw new ItemNotFoundException($"No configuration exists for the name: {path}"); + if (typeof(T).IsAssignableFrom(type)) + throw new InvalidCastException($"The type {typeof(T).Name} is not valid for " + + $"a resource of type {type.Name}."); + return (T)GetValue(path); + } + /// public async Task EditValue(string path, object value) { diff --git a/Kyoo/Controllers/PluginManager.cs b/Kyoo/Controllers/PluginManager.cs index 321d2f57..7d0c399b 100644 --- a/Kyoo/Controllers/PluginManager.cs +++ b/Kyoo/Controllers/PluginManager.cs @@ -4,10 +4,11 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Loader; +using Kyoo.Models.Options; using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -24,7 +25,7 @@ namespace Kyoo.Controllers /// /// The configuration to get the plugin's directory. /// - private readonly IConfiguration _config; + private readonly IOptionsMonitor _options; /// /// The logger used by this class. /// @@ -39,14 +40,14 @@ namespace Kyoo.Controllers /// Create a new instance. /// /// A service container to allow initialization of plugins - /// The configuration instance, to get the plugin's directory path. + /// The configuration instance, to get the plugin's directory path. /// The logger used by this class. public PluginManager(IServiceProvider provider, - IConfiguration config, + IOptionsMonitor options, ILogger logger) { _provider = provider; - _config = config; + _options = options; _logger = logger; } @@ -97,7 +98,7 @@ namespace Kyoo.Controllers /// public void LoadPlugins(ICollection plugins) { - string pluginFolder = _config.GetValue("plugins"); + string pluginFolder = _options.CurrentValue.PluginPath; if (!Directory.Exists(pluginFolder)) Directory.CreateDirectory(pluginFolder); diff --git a/Kyoo/Controllers/TaskManager.cs b/Kyoo/Controllers/TaskManager.cs index 5f281ba3..caa7a01d 100644 --- a/Kyoo/Controllers/TaskManager.cs +++ b/Kyoo/Controllers/TaskManager.cs @@ -7,10 +7,11 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.Models.Attributes; using Kyoo.Models.Exceptions; -using Microsoft.Extensions.Configuration; +using Kyoo.Models.Options; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -27,7 +28,7 @@ namespace Kyoo.Controllers /// /// The configuration instance used to get schedule information /// - private readonly IConfiguration _configuration; + private readonly IOptionsMonitor _options; /// /// The logger instance. /// @@ -56,15 +57,15 @@ namespace Kyoo.Controllers /// /// The list of tasks to manage /// The service provider to request services for tasks - /// The configuration to load schedule information. + /// The configuration to load schedule information. /// The logger. public TaskManager(IEnumerable tasks, IServiceProvider provider, - IConfiguration configuration, + IOptionsMonitor options, ILogger logger) { _provider = provider; - _configuration = configuration.GetSection("scheduledTasks"); + _options = options; _logger = logger; _tasks = tasks.Select(x => (x, GetNextTaskDate(x.Slug))).ToList(); @@ -224,10 +225,9 @@ namespace Kyoo.Controllers /// The next date. private DateTime GetNextTaskDate(string taskSlug) { - TimeSpan delay = _configuration.GetValue(taskSlug); - if (delay == default) - return DateTime.MaxValue; - return DateTime.Now + delay; + if (_options.CurrentValue.Scheduled.TryGetValue(taskSlug, out TimeSpan delay)) + return DateTime.Now + delay; + return DateTime.MaxValue; } /// diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index b75e3231..1e84cef6 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -1,26 +1,25 @@ using Kyoo.Models; -using Microsoft.Extensions.Configuration; using System; using System.IO; using System.Net; using System.Threading.Tasks; using JetBrains.Annotations; +using Kyoo.Models.Options; +using Microsoft.Extensions.Options; namespace Kyoo.Controllers { public class ThumbnailsManager : IThumbnailsManager { private readonly IFileManager _files; - private readonly string _peoplePath; - private readonly string _providerPath; + private readonly IOptionsMonitor _options; - public ThumbnailsManager(IConfiguration configuration, IFileManager files) + public ThumbnailsManager(IFileManager files, IOptionsMonitor options) { _files = files; - _peoplePath = Path.GetFullPath(configuration.GetValue("peoplePath")); - _providerPath = Path.GetFullPath(configuration.GetValue("providerPath")); - Directory.CreateDirectory(_peoplePath); - Directory.CreateDirectory(_providerPath); + _options = options; + Directory.CreateDirectory(_options.CurrentValue.PeoplePath); + Directory.CreateDirectory(_options.CurrentValue.ProviderPath); } private static async Task DownloadImage(string url, string localPath, string what) @@ -141,16 +140,18 @@ namespace Kyoo.Controllers { if (people == null) throw new ArgumentNullException(nameof(people)); - string thumbPath = Path.GetFullPath(Path.Combine(_peoplePath, $"{people.Slug}.jpg")); - return Task.FromResult(thumbPath.StartsWith(_peoplePath) ? thumbPath : null); + string peoplePath = _options.CurrentValue.PeoplePath; + string thumbPath = Path.GetFullPath(Path.Combine(peoplePath, $"{people.Slug}.jpg")); + return Task.FromResult(thumbPath.StartsWith(peoplePath) ? thumbPath : null); } public Task GetProviderLogo(Provider provider) { if (provider == null) throw new ArgumentNullException(nameof(provider)); - string thumbPath = Path.GetFullPath(Path.Combine(_providerPath, $"{provider.Slug}.{provider.LogoExtension}")); - return Task.FromResult(thumbPath.StartsWith(_providerPath) ? thumbPath : null); + string providerPath = _options.CurrentValue.ProviderPath; + string thumbPath = Path.GetFullPath(Path.Combine(providerPath, $"{provider.Slug}.{provider.LogoExtension}")); + return Task.FromResult(thumbPath.StartsWith(providerPath) ? thumbPath : null); } } } diff --git a/Kyoo/Controllers/Transcoder.cs b/Kyoo/Controllers/Transcoder.cs index 54cb5bfc..823aa9d2 100644 --- a/Kyoo/Controllers/Transcoder.cs +++ b/Kyoo/Controllers/Transcoder.cs @@ -3,7 +3,8 @@ using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; using Kyoo.Models; -using Microsoft.Extensions.Configuration; +using Kyoo.Models.Options; +using Microsoft.Extensions.Options; using Stream = Kyoo.Models.Watch.Stream; // We use threads so tasks are not always awaited. @@ -66,20 +67,18 @@ namespace Kyoo.Controllers tracks = Array.Empty(); if (ptr != IntPtr.Zero) - free(ptr); // free_streams is not necesarry since the Marshal free the unmanaged pointers. + free(ptr); // free_streams is not necessary since the Marshal free the unmanaged pointers. return tracks; } } private readonly IFileManager _files; - private readonly string _transmuxPath; - private readonly string _transcodePath; + private readonly IOptions _options; - public Transcoder(IConfiguration config, IFileManager files) + public Transcoder(IFileManager files, IOptions options) { _files = files; - _transmuxPath = Path.GetFullPath(config.GetValue("transmuxTempPath")); - _transcodePath = Path.GetFullPath(config.GetValue("transcodeTempPath")); + _options = options; if (TranscoderAPI.init() != Marshal.SizeOf()) throw new BadTranscoderException(); @@ -100,7 +99,7 @@ namespace Kyoo.Controllers if (!File.Exists(episode.Path)) throw new ArgumentException("Path does not exists. Can't transcode."); - string folder = Path.Combine(_transmuxPath, episode.Slug); + string folder = Path.Combine(_options.Value.TransmuxPath, episode.Slug); string manifest = Path.Combine(folder, episode.Slug + ".m3u8"); float playableDuration = 0; bool transmuxFailed = false; diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs index cbd6cbd9..726ed36a 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Kyoo.Controllers; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Kyoo.Tasks; using Microsoft.AspNetCore.Builder; @@ -90,8 +91,15 @@ namespace Kyoo /// public void Configure(IServiceCollection services, ICollection availableTypes) { - string publicUrl = _configuration.GetValue("publicUrl"); + string publicUrl = _configuration.GetPublicUrl(); + services.Configure(_configuration.GetSection(BasicOptions.Path)); + services.AddConfiguration(BasicOptions.Path); + services.Configure(_configuration.GetSection(TaskOptions.Path)); + services.AddConfiguration(TaskOptions.Path); + services.Configure(_configuration.GetSection(MediaOptions.Path)); + services.AddConfiguration(MediaOptions.Path); + services.AddControllers() .AddNewtonsoftJson(x => { diff --git a/Kyoo/Models/Options/BasicOptions.cs b/Kyoo/Models/Options/BasicOptions.cs new file mode 100644 index 00000000..95d4873a --- /dev/null +++ b/Kyoo/Models/Options/BasicOptions.cs @@ -0,0 +1,48 @@ +namespace Kyoo.Models.Options +{ + /// + /// The typed list of basic/global options for Kyoo + /// + public class BasicOptions + { + /// + /// The path of this list of options + /// + public const string Path = "Basics"; + + /// + /// The internal url where the server will listen + /// + public string Url { get; set; } = "http://*:5000"; + + /// + /// The public url that will be used in items response and in authentication server host. + /// + public string PublicUrl { get; set; } = "http://localhost:5000/"; + + /// + /// The path of the plugin directory. + /// + public string PluginPath { get; set; } = "plugins/"; + + /// + /// The path of the people pictures. + /// + public string PeoplePath { get; set; } = "people/"; + + /// + /// The path of providers icons. + /// + public string ProviderPath { get; set; } = "providers/"; + + /// + /// The temporary folder to cache transmuxed file. + /// + public string TransmuxPath { get; set; } = "cached/transmux"; + + /// + /// The temporary folder to cache transcoded file. + /// + public string TranscodePath { get; set; } = "cached/transcode"; + } +} \ No newline at end of file diff --git a/Kyoo/Models/Options/MediaOptions.cs b/Kyoo/Models/Options/MediaOptions.cs new file mode 100644 index 00000000..d53b13d0 --- /dev/null +++ b/Kyoo/Models/Options/MediaOptions.cs @@ -0,0 +1,23 @@ +namespace Kyoo.Models.Options +{ + /// + /// Options for media registering. + /// + public class MediaOptions + { + /// + /// The path of this options + /// + public const string Path = "Media"; + + /// + /// A regex for episodes + /// + public string Regex { get; set; } + + /// + /// A regex for subtitles + /// + public string SubtitleRegex { get; set; } + } +} \ No newline at end of file diff --git a/Kyoo/Models/Options/TaskOptions.cs b/Kyoo/Models/Options/TaskOptions.cs new file mode 100644 index 00000000..922a9ff4 --- /dev/null +++ b/Kyoo/Models/Options/TaskOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Kyoo.Models.Options +{ + /// + /// Options related to tasks + /// + public class TaskOptions + { + /// + /// The path of this options + /// + public const string Path = "Tasks"; + + /// + /// The number of tasks that can be run concurrently. + /// + public int Parallels { get; set; } + + /// + /// The delay of tasks that should be automatically started at fixed times. + /// + [UsedImplicitly] + public Dictionary Scheduled { get; set; } = new(); + } +} \ No newline at end of file diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index 32d763fd..db83cfd9 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -83,14 +83,16 @@ namespace Kyoo /// A new web host instance private static IWebHostBuilder CreateWebHostBuilder(string[] args) { + IConfiguration configuration = SetupConfig(new ConfigurationBuilder(), args).Build(); + return new WebHostBuilder() .UseContentRoot(AppDomain.CurrentDomain.BaseDirectory) - .UseConfiguration(SetupConfig(new ConfigurationBuilder(), args).Build()) + .UseConfiguration(configuration) .ConfigureAppConfiguration(x => SetupConfig(x, args)) .ConfigureLogging((context, builder) => { builder.AddConfiguration(context.Configuration.GetSection("logging")) - .AddSimpleConsole(x => + .AddSimpleConsole(x => { x.TimestampFormat = "[hh:mm:ss] "; }) @@ -107,6 +109,7 @@ namespace Kyoo .UseKestrel(options => { options.AddServerHeader = false; }) .UseIIS() .UseIISIntegration() + .UseUrls(configuration.GetValue("basics:urls")) .UseStartup(); } } diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 361c1a27..8bd517cc 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -3,6 +3,7 @@ using System.IO; using Kyoo.Authentication; using Kyoo.Controllers; using Kyoo.Models; +using Kyoo.Models.Options; using Kyoo.Postgresql; using Kyoo.Tasks; using Microsoft.AspNetCore.Builder; @@ -12,6 +13,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Kyoo { @@ -38,7 +40,8 @@ namespace Kyoo /// A logger factory used to create a logger for the plugin manager. public Startup(IServiceProvider hostProvider, IConfiguration configuration, ILoggerFactory loggerFactory, IWebHostEnvironment host) { - _plugins = new PluginManager(hostProvider, configuration, loggerFactory.CreateLogger()); + IOptionsMonitor options = hostProvider.GetService>(); + _plugins = new PluginManager(hostProvider, options, loggerFactory.CreateLogger()); // TODO remove postgres from here and load it like a normal plugin. _plugins.LoadPlugins(new IPlugin[] { diff --git a/Kyoo/Views/CollectionApi.cs b/Kyoo/Views/CollectionApi.cs index 214cb60d..783bb67b 100644 --- a/Kyoo/Views/CollectionApi.cs +++ b/Kyoo/Views/CollectionApi.cs @@ -6,8 +6,9 @@ using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Kyoo.CommonApi; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -19,8 +20,8 @@ namespace Kyoo.Api { private readonly ILibraryManager _libraryManager; - public CollectionApi(ILibraryManager libraryManager, IConfiguration configuration) - : base(libraryManager.CollectionRepository, configuration) + public CollectionApi(ILibraryManager libraryManager, IOptions options) + : base(libraryManager.CollectionRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; } diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs index e8829e3b..833d5993 100644 --- a/Kyoo/Views/ConfigurationApi.cs +++ b/Kyoo/Views/ConfigurationApi.cs @@ -1,13 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; -using Kyoo.Models; using Kyoo.Models.Exceptions; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; namespace Kyoo.Api { @@ -19,28 +15,18 @@ namespace Kyoo.Api [ApiController] public class ConfigurationApi : Controller { + /// + /// The configuration manager used to retrieve and edit configuration values (while being type safe). + /// private readonly IConfigurationManager _manager; - /// - /// The configuration to retrieve and edit. - /// - private readonly IConfiguration _configuration; - - /// - /// The strongly typed list of options - /// - private readonly Dictionary _references; - /// /// Create a new using the given configuration. /// - /// The configuration to use. - /// The strongly typed option list. - public ConfigurationApi(IConfigurationManager manager, IConfiguration configuration, IEnumerable references) + /// The configuration manager used to retrieve and edit configuration values + public ConfigurationApi(IConfigurationManager manager) { _manager = manager; - _configuration = configuration; - _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } /// @@ -54,16 +40,14 @@ namespace Kyoo.Api [Permission(nameof(ConfigurationApi), Kind.Admin)] public ActionResult GetConfiguration(string slug) { - slug = slug.Replace("__", ":"); - // TODO handle lists and dictionaries. - if (!_references.TryGetValue(slug, out Type type)) + try + { + return _manager.GetValue(slug); + } + catch (ItemNotFoundException) + { return NotFound(); - object ret = _configuration.GetValue(type, slug); - if (ret != null) - return ret; - object option = Activator.CreateInstance(type); - _configuration.Bind(slug, option); - return option; + } } /// diff --git a/Kyoo/Views/EpisodeApi.cs b/Kyoo/Views/EpisodeApi.cs index c87a2704..490d0b34 100644 --- a/Kyoo/Views/EpisodeApi.cs +++ b/Kyoo/Views/EpisodeApi.cs @@ -7,8 +7,9 @@ using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models.Exceptions; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -23,10 +24,10 @@ namespace Kyoo.Api private readonly IFileManager _files; public EpisodeApi(ILibraryManager libraryManager, - IConfiguration configuration, + IOptions options, IFileManager files, IThumbnailsManager thumbnails) - : base(libraryManager.EpisodeRepository, configuration) + : base(libraryManager.EpisodeRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; _files = files; diff --git a/Kyoo/Views/GenreApi.cs b/Kyoo/Views/GenreApi.cs index 83ebb38d..e494665d 100644 --- a/Kyoo/Views/GenreApi.cs +++ b/Kyoo/Views/GenreApi.cs @@ -5,9 +5,10 @@ using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -19,8 +20,8 @@ namespace Kyoo.Api { private readonly ILibraryManager _libraryManager; - public GenreApi(ILibraryManager libraryManager, IConfiguration config) - : base(libraryManager.GenreRepository, config) + public GenreApi(ILibraryManager libraryManager, IOptions options) + : base(libraryManager.GenreRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; } diff --git a/Kyoo/Views/LibraryApi.cs b/Kyoo/Views/LibraryApi.cs index bbcd7a90..93cd57d3 100644 --- a/Kyoo/Views/LibraryApi.cs +++ b/Kyoo/Views/LibraryApi.cs @@ -6,8 +6,9 @@ using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using Kyoo.CommonApi; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -20,8 +21,8 @@ namespace Kyoo.Api private readonly ILibraryManager _libraryManager; private readonly ITaskManager _taskManager; - public LibraryApi(ILibraryManager libraryManager, ITaskManager taskManager, IConfiguration configuration) - : base(libraryManager.LibraryRepository, configuration) + public LibraryApi(ILibraryManager libraryManager, ITaskManager taskManager, IOptions options) + : base(libraryManager.LibraryRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; _taskManager = taskManager; diff --git a/Kyoo/Views/LibraryItemApi.cs b/Kyoo/Views/LibraryItemApi.cs index 6cd81b05..5fd6b7e3 100644 --- a/Kyoo/Views/LibraryItemApi.cs +++ b/Kyoo/Views/LibraryItemApi.cs @@ -6,9 +6,10 @@ using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Exceptions; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -22,10 +23,10 @@ namespace Kyoo.Api private readonly string _baseURL; - public LibraryItemApi(ILibraryItemRepository libraryItems, IConfiguration configuration) + public LibraryItemApi(ILibraryItemRepository libraryItems, IOptions options) { _libraryItems = libraryItems; - _baseURL = configuration.GetValue("publicUrl").TrimEnd('/'); + _baseURL = options.Value.PublicUrl; } [HttpGet] diff --git a/Kyoo/Views/PeopleApi.cs b/Kyoo/Views/PeopleApi.cs index 724198e5..c421fc16 100644 --- a/Kyoo/Views/PeopleApi.cs +++ b/Kyoo/Views/PeopleApi.cs @@ -5,9 +5,10 @@ using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Exceptions; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -21,10 +22,10 @@ namespace Kyoo.Api private readonly IThumbnailsManager _thumbs; public PeopleApi(ILibraryManager libraryManager, - IConfiguration configuration, + IOptions options, IFileManager files, IThumbnailsManager thumbs) - : base(libraryManager.PeopleRepository, configuration) + : base(libraryManager.PeopleRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; _files = files; diff --git a/Kyoo/Views/ProviderApi.cs b/Kyoo/Views/ProviderApi.cs index 133c15fd..eac22675 100644 --- a/Kyoo/Views/ProviderApi.cs +++ b/Kyoo/Views/ProviderApi.cs @@ -2,9 +2,10 @@ using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -19,10 +20,10 @@ namespace Kyoo.Api private readonly IFileManager _files; public ProviderApi(ILibraryManager libraryManager, - IConfiguration config, + IOptions options, IFileManager files, IThumbnailsManager thumbnails) - : base(libraryManager.ProviderRepository, config) + : base(libraryManager.ProviderRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; _files = files; diff --git a/Kyoo/Views/SeasonApi.cs b/Kyoo/Views/SeasonApi.cs index 1987e7c5..a32b0b1c 100644 --- a/Kyoo/Views/SeasonApi.cs +++ b/Kyoo/Views/SeasonApi.cs @@ -6,8 +6,9 @@ using Kyoo.Controllers; using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Linq; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -22,10 +23,10 @@ namespace Kyoo.Api private readonly IFileManager _files; public SeasonApi(ILibraryManager libraryManager, - IConfiguration configuration, + IOptions options, IThumbnailsManager thumbs, IFileManager files) - : base(libraryManager.SeasonRepository, configuration) + : base(libraryManager.SeasonRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; _thumbs = thumbs; diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index 8b73afd2..4e121d75 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -8,8 +8,9 @@ using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models.Exceptions; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -26,8 +27,8 @@ namespace Kyoo.Api public ShowApi(ILibraryManager libraryManager, IFileManager files, IThumbnailsManager thumbs, - IConfiguration configuration) - : base(libraryManager.ShowRepository, configuration) + IOptions options) + : base(libraryManager.ShowRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; _files = files; diff --git a/Kyoo/Views/StudioApi.cs b/Kyoo/Views/StudioApi.cs index 657988bb..a6957e33 100644 --- a/Kyoo/Views/StudioApi.cs +++ b/Kyoo/Views/StudioApi.cs @@ -5,9 +5,10 @@ using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -19,8 +20,8 @@ namespace Kyoo.Api { private readonly ILibraryManager _libraryManager; - public StudioApi(ILibraryManager libraryManager, IConfiguration config) - : base(libraryManager.StudioRepository, config) + public StudioApi(ILibraryManager libraryManager, IOptions options) + : base(libraryManager.StudioRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; } diff --git a/Kyoo/Views/TrackApi.cs b/Kyoo/Views/TrackApi.cs index 77f8669e..0eadaf3b 100644 --- a/Kyoo/Views/TrackApi.cs +++ b/Kyoo/Views/TrackApi.cs @@ -4,9 +4,10 @@ using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Exceptions; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -18,8 +19,8 @@ namespace Kyoo.Api { private readonly ILibraryManager _libraryManager; - public TrackApi(ILibraryManager libraryManager, IConfiguration configuration) - : base(libraryManager.TrackRepository, configuration) + public TrackApi(ILibraryManager libraryManager, IOptions options) + : base(libraryManager.TrackRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; } diff --git a/Kyoo/Views/VideoApi.cs b/Kyoo/Views/VideoApi.cs index 050b82b4..5c48f7bc 100644 --- a/Kyoo/Views/VideoApi.cs +++ b/Kyoo/Views/VideoApi.cs @@ -2,11 +2,12 @@ using Kyoo.Controllers; using Kyoo.Models; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using System.Threading.Tasks; using Kyoo.Models.Exceptions; +using Kyoo.Models.Options; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; namespace Kyoo.Api { @@ -16,20 +17,18 @@ namespace Kyoo.Api { private readonly ILibraryManager _libraryManager; private readonly ITranscoder _transcoder; + private readonly IOptions _options; private readonly IFileManager _files; - private readonly string _transmuxPath; - private readonly string _transcodePath; public VideoApi(ILibraryManager libraryManager, ITranscoder transcoder, - IConfiguration config, + IOptions options, IFileManager files) { _libraryManager = libraryManager; _transcoder = transcoder; + _options = options; _files = files; - _transmuxPath = config.GetValue("transmuxTempPath"); - _transcodePath = config.GetValue("transcodeTempPath"); } public override void OnActionExecuted(ActionExecutedContext ctx) @@ -101,7 +100,7 @@ namespace Kyoo.Api [Permission("video", Kind.Read)] public IActionResult GetTransmuxedChunk(string episodeLink, string chunk) { - string path = Path.GetFullPath(Path.Combine(_transmuxPath, episodeLink)); + string path = Path.GetFullPath(Path.Combine(_options.Value.TransmuxPath, episodeLink)); path = Path.Combine(path, "segments", chunk); return PhysicalFile(path, "video/MP2T"); } @@ -110,7 +109,7 @@ namespace Kyoo.Api [Permission("video", Kind.Read)] public IActionResult GetTranscodedChunk(string episodeLink, string chunk) { - string path = Path.GetFullPath(Path.Combine(_transcodePath, episodeLink)); + string path = Path.GetFullPath(Path.Combine(_options.Value.TranscodePath, episodeLink)); path = Path.Combine(path, "segments", chunk); return PhysicalFile(path, "video/MP2T"); } diff --git a/Kyoo/settings.json b/Kyoo/settings.json index 114636fe..71e2574d 100644 --- a/Kyoo/settings.json +++ b/Kyoo/settings.json @@ -1,7 +1,14 @@ { - "server.urls": "http://*:5000", - "publicUrl": "http://localhost:5000/", - + "basics": { + "url": "http://*:5000", + "publicUrl": "http://localhost:5000/", + "pluginsPath": "plugins/", + "peoplePath": "people/", + "providerPath": "providers/", + "transmuxPath": "cached/transmux", + "transcodePath": "cached/transcode" + }, + "database": { "postgres": { "server": "127.0.0.1", @@ -24,6 +31,18 @@ "Kyoo": "Trace" } }, + + "tasks": { + "parallels": "1", + "scheduled": { + "scan": "24:00:00" + } + }, + + "media": { + "regex": "(?:\\/(?.*?))?\\/(?.*?)(?: \\(\\d+\\))?\\/\\k(?: \\(\\d+\\))?(?:(?: S(?\\d+)E(?\\d+))| (?\\d+))?.*$", + "subtitleRegex": "^(?.*)\\.(?\\w{1,3})\\.(?default\\.)?(?forced\\.)?.*$" + }, "authentication": { "certificate": { @@ -37,20 +56,5 @@ }, "profilePicturePath": "users/", "clients": [] - }, - - - "parallelTasks": "1", - - "scheduledTasks": { - "scan": "24:00:00" - }, - - "transmuxTempPath": "cached/kyoo/transmux", - "transcodeTempPath": "cached/kyoo/transcode", - "peoplePath": "people", - "providerPath": "providers", - "plugins": "plugins/", - "regex": "(?:\\/(?.*?))?\\/(?.*?)(?: \\(\\d+\\))?\\/\\k(?: \\(\\d+\\))?(?:(?: S(?\\d+)E(?\\d+))| (?\\d+))?.*$", - "subtitleRegex": "^(?.*)\\.(?\\w{1,3})\\.(?default\\.)?(?forced\\.)?.*$" + } } From 5f7604a563b53eba60ea0fe2433c8c8bc07c9964 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 20 May 2021 00:29:25 +0200 Subject: [PATCH 8/9] Adding permission groups --- .../Controllers/PremissionValidator.cs | 13 +++++++--- .../Models/Attributes/PermissionAttribute.cs | 25 ++++++++++++++----- Kyoo/Views/ConfigurationApi.cs | 4 +-- Kyoo/settings.json | 4 +-- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Kyoo.Authentication/Controllers/PremissionValidator.cs b/Kyoo.Authentication/Controllers/PremissionValidator.cs index dc60faa7..62a4843b 100644 --- a/Kyoo.Authentication/Controllers/PremissionValidator.cs +++ b/Kyoo.Authentication/Controllers/PremissionValidator.cs @@ -36,7 +36,7 @@ namespace Kyoo.Authentication /// public IFilterMetadata Create(PermissionAttribute attribute) { - return new PermissionValidator(attribute.Type, attribute.Kind, _options); + return new PermissionValidator(attribute.Type, attribute.Kind, attribute.Group, _options); } /// @@ -58,6 +58,11 @@ namespace Kyoo.Authentication /// The kind of permission needed /// private readonly Kind? _kind; + + /// + /// The group of he permission + /// + private readonly Group _group = Group.Overall; /// /// The permissions options to retrieve default permissions. /// @@ -68,11 +73,13 @@ namespace Kyoo.Authentication /// /// The permission to validate /// The kind of permission needed + /// The group of the permission /// The option containing default values. - public PermissionValidator(string permission, Kind kind, IOptionsMonitor options) + public PermissionValidator(string permission, Kind kind, Group group, IOptionsMonitor options) { _permission = permission; _kind = kind; + _group = group; _options = options; } @@ -125,7 +132,7 @@ namespace Kyoo.Authentication } string permStr = $"{permission.ToLower()}.{kind.ToString()!.ToLower()}"; - string overallStr = $"overall.{kind.ToString()!.ToLower()}"; + string overallStr = $"{_group.ToString()}.{kind.ToString()!.ToLower()}"; AuthenticateResult res = await context.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); if (res.Succeeded) { diff --git a/Kyoo.Common/Models/Attributes/PermissionAttribute.cs b/Kyoo.Common/Models/Attributes/PermissionAttribute.cs index 40228782..24de7950 100644 --- a/Kyoo.Common/Models/Attributes/PermissionAttribute.cs +++ b/Kyoo.Common/Models/Attributes/PermissionAttribute.cs @@ -7,16 +7,20 @@ namespace Kyoo.Models.Permissions /// /// The kind of permission needed. /// - /// - /// The admin kind is used for configuration or security sensitive permissions to allow one - /// to use an overall permission without compromising security. - /// public enum Kind { Read, Write, Create, - Delete, + Delete + } + + /// + /// The group of the permission. + /// + public enum Group + { + Overall, Admin } @@ -34,6 +38,10 @@ namespace Kyoo.Models.Permissions /// The needed permission kind. /// public Kind Kind { get; } + /// + /// The group of this permission + /// + public Group Group { get; } /// /// Ask a permission to run an action. @@ -43,12 +51,17 @@ namespace Kyoo.Models.Permissions /// (if the type ends with api, it will be removed. This allow you to use nameof(YourApi)). /// /// The kind of permission needed - public PermissionAttribute(string type, Kind permission) + /// + /// The group of this permission (allow grouped permission like overall.read + /// for all read permissions of this group) + /// + public PermissionAttribute(string type, Kind permission, Group group = Group.Overall) { if (type.EndsWith("API", StringComparison.OrdinalIgnoreCase)) type = type[..^3]; Type = type.ToLower(); Kind = permission; + Group = group; } /// diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs index 833d5993..13d7f5ca 100644 --- a/Kyoo/Views/ConfigurationApi.cs +++ b/Kyoo/Views/ConfigurationApi.cs @@ -37,7 +37,7 @@ namespace Kyoo.Api /// Return the configuration value or the list of configurations /// No configuration exists for the given slug [HttpGet("{slug}")] - [Permission(nameof(ConfigurationApi), Kind.Admin)] + [Permission(nameof(ConfigurationApi), Kind.Read, Group.Admin)] public ActionResult GetConfiguration(string slug) { try @@ -59,7 +59,7 @@ namespace Kyoo.Api /// Return the edited value /// No configuration exists for the given slug [HttpPut("{slug}")] - [Permission(nameof(ConfigurationApi), Kind.Admin)] + [Permission(nameof(ConfigurationApi), Kind.Write, Group.Admin)] public async Task> EditConfiguration(string slug, [FromBody] object newValue) { try diff --git a/Kyoo/settings.json b/Kyoo/settings.json index 71e2574d..ff7dff63 100644 --- a/Kyoo/settings.json +++ b/Kyoo/settings.json @@ -51,8 +51,8 @@ "password": "passphrase" }, "permissions": { - "default": ["overall.read", "overall.write", "overall.create", "overall.delete", "overall.admin"], - "newUser": ["overall.read", "overall.write", "overall.create", "overall.delete", "overall.admin"] + "default": ["overall.read", "overall.write", "overall.create", "overall.delete", "admin.read", "admin.write"], + "newUser": ["overall.read", "overall.write", "overall.create", "overall.delete", "admin.read", "admin.write"] }, "profilePicturePath": "users/", "clients": [] From 52e7093c268d20c73fba5ce75ceb78916617bb42 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 20 May 2021 01:14:48 +0200 Subject: [PATCH 9/9] Allowing untyped sections in the config --- .../Controllers/PremissionValidator.cs | 2 +- Kyoo.Common/Models/ConfigurationReference.cs | 6 ++ Kyoo.Common/Module.cs | 14 +++++ Kyoo/Controllers/ConfigurationManager.cs | 62 ++++++++++++++++--- Kyoo/CoreModule.cs | 2 + Kyoo/Program.cs | 2 +- Kyoo/Views/ConfigurationApi.cs | 4 +- 7 files changed, 80 insertions(+), 12 deletions(-) diff --git a/Kyoo.Authentication/Controllers/PremissionValidator.cs b/Kyoo.Authentication/Controllers/PremissionValidator.cs index 62a4843b..ca3102ed 100644 --- a/Kyoo.Authentication/Controllers/PremissionValidator.cs +++ b/Kyoo.Authentication/Controllers/PremissionValidator.cs @@ -132,7 +132,7 @@ namespace Kyoo.Authentication } string permStr = $"{permission.ToLower()}.{kind.ToString()!.ToLower()}"; - string overallStr = $"{_group.ToString()}.{kind.ToString()!.ToLower()}"; + string overallStr = $"{_group.ToString().ToLower()}.{kind.ToString()!.ToLower()}"; AuthenticateResult res = await context.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); if (res.Succeeded) { diff --git a/Kyoo.Common/Models/ConfigurationReference.cs b/Kyoo.Common/Models/ConfigurationReference.cs index fab6f9d3..00d20b4c 100644 --- a/Kyoo.Common/Models/ConfigurationReference.cs +++ b/Kyoo.Common/Models/ConfigurationReference.cs @@ -87,5 +87,11 @@ namespace Kyoo.Models { return CreateReference(path, typeof(T)); } + + + public static ConfigurationReference CreateUntyped(string path) + { + return new(path, null); + } } } \ No newline at end of file diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs index 0b5e0d04..a8a81b88 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -82,6 +82,20 @@ namespace Kyoo services.AddSingleton(confRef); return services; } + + /// + /// Add an editable configuration to the editable configuration list. + /// WARNING: this method allow you to add an unmanaged type. This type won't be editable. This can be used + /// for external libraries or variable arguments. + /// + /// The service collection to edit + /// The root path of the editable configuration. It should not be a nested type. + /// The given service collection is returned. + public static IServiceCollection AddUntypedConfiguration(this IServiceCollection services, string path) + { + services.AddSingleton(ConfigurationReference.CreateUntyped(path)); + return services; + } /// /// Get the public URL of kyoo using the given configuration instance. diff --git a/Kyoo/Controllers/ConfigurationManager.cs b/Kyoo/Controllers/ConfigurationManager.cs index c0a1340b..c0a63895 100644 --- a/Kyoo/Controllers/ConfigurationManager.cs +++ b/Kyoo/Controllers/ConfigurationManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.IO; using System.Linq; @@ -35,13 +36,30 @@ namespace Kyoo.Controllers _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } + private Type GetType(string path) + { + path = path.Replace("__", ":"); + + // TODO handle lists and dictionaries. + if (_references.TryGetValue(path, out Type type)) + { + if (type != null) + return type; + throw new ArgumentException($"The configuration at {path} is not editable or readable."); + } + + string parent = path.Contains(':') ? path[..path.IndexOf(':')] : null; + if (parent != null && _references.TryGetValue(parent, out type) && type == null) + throw new ArgumentException($"The configuration at {path} is not editable or readable."); + throw new ItemNotFoundException($"No configuration exists for the name: {path}"); + } + /// public object GetValue(string path) { path = path.Replace("__", ":"); // TODO handle lists and dictionaries. - if (!_references.TryGetValue(path, out Type type)) - throw new ItemNotFoundException($"No configuration exists for the name: {path}"); + Type type = GetType(path); object ret = _configuration.GetValue(type, path); if (ret != null) return ret; @@ -55,8 +73,7 @@ namespace Kyoo.Controllers { path = path.Replace("__", ":"); // TODO handle lists and dictionaries. - if (!_references.TryGetValue(path, out Type type)) - throw new ItemNotFoundException($"No configuration exists for the name: {path}"); + Type type = GetType(path); if (typeof(T).IsAssignableFrom(type)) throw new InvalidCastException($"The type {typeof(T).Name} is not valid for " + $"a resource of type {type.Name}."); @@ -67,8 +84,7 @@ namespace Kyoo.Controllers public async Task EditValue(string path, object value) { path = path.Replace("__", ":"); - if (!_references.TryGetValue(path, out Type type)) - throw new ItemNotFoundException($"No configuration exists for the name: {path}"); + Type type = GetType(path); value = JObject.FromObject(value).ToObject(type); if (value == null) throw new ArgumentException("Invalid value format."); @@ -87,18 +103,48 @@ namespace Kyoo.Controllers /// /// The configuration to transform /// A strongly typed representation of the configuration. + [SuppressMessage("ReSharper", "RedundantJumpStatement")] private ExpandoObject ToObject(IConfiguration config) { ExpandoObject obj = new(); foreach (IConfigurationSection section in config.GetChildren()) { - if (!_references.TryGetValue(section.Path, out Type type)) + try + { + Type type = GetType(section.Path); + obj.TryAdd(section.Key, section.Get(type)); + } + catch (ArgumentException) + { + obj.TryAdd(section.Key, ToUntyped(section)); + } + catch + { continue; - obj.TryAdd(section.Key, section.Get(type)); + } } return obj; } + + /// + /// Transform the configuration section in nested expando objects. + /// + /// The section to convert + /// The converted section + private static object ToUntyped(IConfigurationSection config) + { + ExpandoObject obj = new(); + + foreach (IConfigurationSection section in config.GetChildren()) + { + obj.TryAdd(section.Key, ToUntyped(section)); + } + + if (!obj.Any()) + return config.Value; + return obj; + } } } \ No newline at end of file diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs index 726ed36a..34e24e75 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -99,6 +99,8 @@ namespace Kyoo services.AddConfiguration(TaskOptions.Path); services.Configure(_configuration.GetSection(MediaOptions.Path)); services.AddConfiguration(MediaOptions.Path); + services.AddUntypedConfiguration("database"); + services.AddUntypedConfiguration("logging"); services.AddControllers() .AddNewtonsoftJson(x => diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs index db83cfd9..12227266 100644 --- a/Kyoo/Program.cs +++ b/Kyoo/Program.cs @@ -109,7 +109,7 @@ namespace Kyoo .UseKestrel(options => { options.AddServerHeader = false; }) .UseIIS() .UseIISIntegration() - .UseUrls(configuration.GetValue("basics:urls")) + .UseUrls(configuration.GetValue("basics:url")) .UseStartup(); } } diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs index 13d7f5ca..7830d786 100644 --- a/Kyoo/Views/ConfigurationApi.cs +++ b/Kyoo/Views/ConfigurationApi.cs @@ -71,9 +71,9 @@ namespace Kyoo.Api { return NotFound(); } - catch (ArgumentException) + catch (ArgumentException ex) { - return BadRequest(); + return BadRequest(ex.Message); } } }