diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index 9c7c3687..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; @@ -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(); @@ -145,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.Authentication/Controllers/PremissionValidator.cs b/Kyoo.Authentication/Controllers/PremissionValidator.cs index dc60faa7..ca3102ed 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().ToLower()}.{kind.ToString()!.ToLower()}"; AuthenticateResult res = await context.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); if (res.Succeeded) { diff --git a/Kyoo.Common/Controllers/IConfigurationManager.cs b/Kyoo.Common/Controllers/IConfigurationManager.cs new file mode 100644 index 00000000..9159d92c --- /dev/null +++ b/Kyoo.Common/Controllers/IConfigurationManager.cs @@ -0,0 +1,42 @@ +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 + { + /// + /// 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. + /// + /// The path of the resource (can be separated by ':' or '__') + /// The new value of the resource + /// No setting found at the given path. + Task EditValue(string path, object value); + } +} \ 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/Attributes/PermissionAttribute.cs b/Kyoo.Common/Models/Attributes/PermissionAttribute.cs index b34fb48b..24de7950 100644 --- a/Kyoo.Common/Models/Attributes/PermissionAttribute.cs +++ b/Kyoo.Common/Models/Attributes/PermissionAttribute.cs @@ -14,6 +14,15 @@ namespace Kyoo.Models.Permissions Create, Delete } + + /// + /// The group of the permission. + /// + public enum Group + { + Overall, + Admin + } /// /// Specify permissions needed for the API. @@ -29,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. @@ -38,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.Common/Models/ConfigurationReference.cs b/Kyoo.Common/Models/ConfigurationReference.cs new file mode 100644 index 00000000..00d20b4c --- /dev/null +++ b/Kyoo.Common/Models/ConfigurationReference.cs @@ -0,0 +1,97 @@ +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)); + } + + + public static ConfigurationReference CreateUntyped(string path) + { + return new(path, null); + } + } +} \ 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/Module.cs b/Kyoo.Common/Module.cs index c1c09165..a8a81b88 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -1,5 +1,8 @@ using System; +using System.Linq; using Kyoo.Controllers; +using Kyoo.Models; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Kyoo @@ -62,5 +65,46 @@ namespace Kyoo services.Add(ServiceDescriptor.Describe(typeof(T), typeof(T2), lifetime)); return services.AddRepository(lifetime); } + + /// + /// 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 + { + if (services.Any(x => x.ServiceType == typeof(T))) + return services; + foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference(path)) + 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. + /// + /// 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.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.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 new file mode 100644 index 00000000..c0a63895 --- /dev/null +++ b/Kyoo/Controllers/ConfigurationManager.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Dynamic; +using System.IO; +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; + +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); + } + + 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. + Type type = GetType(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. + 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}."); + return (T)GetValue(path); + } + + /// + public async Task EditValue(string path, object value) + { + path = path.Replace("__", ":"); + Type type = GetType(path); + value = JObject.FromObject(value).ToObject(type); + if (value == null) + throw new ArgumentException("Invalid value format."); + + ExpandoObject config = ToObject(_configuration); + IDictionary configDic = config; + configDic[path] = value; + JObject obj = JObject.FromObject(config); + await using StreamWriter writer = new(Program.JsonConfigPath); + 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. + [SuppressMessage("ReSharper", "RedundantJumpStatement")] + private ExpandoObject ToObject(IConfiguration config) + { + ExpandoObject obj = new(); + + foreach (IConfigurationSection section in config.GetChildren()) + { + try + { + Type type = GetType(section.Path); + obj.TryAdd(section.Key, section.Get(type)); + } + catch (ArgumentException) + { + obj.TryAdd(section.Key, ToUntyped(section)); + } + catch + { + continue; + } + } + + 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/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 3111177e..34e24e75 100644 --- a/Kyoo/CoreModule.cs +++ b/Kyoo/CoreModule.cs @@ -1,10 +1,16 @@ using System; 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; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; namespace Kyoo { @@ -66,9 +72,44 @@ 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.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.AddUntypedConfiguration("database"); + services.AddUntypedConfiguration("logging"); + + services.AddControllers() + .AddNewtonsoftJson(x => + { + x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(publicUrl); + x.SerializerSettings.Converters.Add(new PeopleRoleConverter()); + }); + + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -99,5 +140,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/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 9fecc61b..12227266 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; @@ -14,10 +15,16 @@ 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 /// /// Command line arguments + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse")] public static async Task Main(string[] args) { if (!File.Exists("./settings.json")) @@ -64,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); } @@ -76,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] "; }) @@ -100,6 +109,7 @@ namespace Kyoo .UseKestrel(options => { options.AddServerHeader = false; }) .UseIIS() .UseIISIntegration() + .UseUrls(configuration.GetValue("basics:url")) .UseStartup(); } } diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index e30f495e..8bd517cc 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -3,17 +3,17 @@ 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; 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; +using Microsoft.Extensions.Options; namespace Kyoo { @@ -22,10 +22,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 +40,12 @@ 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()); + 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[] {new CoreModule(), + _plugins.LoadPlugins(new IPlugin[] { + new CoreModule(configuration), new PostgresModule(configuration, host), new AuthenticationModule(configuration, loggerFactory, host) }); @@ -60,8 +57,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 +67,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 +88,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 +112,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/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 new file mode 100644 index 00000000..7830d786 --- /dev/null +++ b/Kyoo/Views/ConfigurationApi.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading.Tasks; +using Kyoo.Controllers; +using Kyoo.Models.Exceptions; +using Kyoo.Models.Permissions; +using Microsoft.AspNetCore.Mvc; + +namespace Kyoo.Api +{ + /// + /// An API to retrieve or edit configuration settings + /// + [Route("api/config")] + [Route("api/configuration")] + [ApiController] + public class ConfigurationApi : Controller + { + /// + /// The configuration manager used to retrieve and edit configuration values (while being type safe). + /// + private readonly IConfigurationManager _manager; + + /// + /// Create a new using the given configuration. + /// + /// The configuration manager used to retrieve and edit configuration values + public ConfigurationApi(IConfigurationManager manager) + { + _manager = manager; + } + + /// + /// Get a permission from it's slug. + /// + /// 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.Read, Group.Admin)] + public ActionResult GetConfiguration(string slug) + { + try + { + return _manager.GetValue(slug); + } + catch (ItemNotFoundException) + { + return NotFound(); + } + } + + /// + /// 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.Write, Group.Admin)] + public async Task> EditConfiguration(string slug, [FromBody] object newValue) + { + try + { + await _manager.EditValue(slug, newValue); + return newValue; + } + catch (ItemNotFoundException) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(ex.Message); + } + } + } +} \ No newline at end of file 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 239dcd8e..93cd57d3 100644 --- a/Kyoo/Views/LibraryApi.cs +++ b/Kyoo/Views/LibraryApi.cs @@ -6,22 +6,23 @@ 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 { [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) - : 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 66b6ca5d..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 { @@ -15,12 +16,12 @@ 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) - : 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 fed2bd58..ff7dff63 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": { @@ -32,25 +51,10 @@ "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", "admin.read", "admin.write"], + "newUser": ["overall.read", "overall.write", "overall.create", "overall.delete", "admin.read", "admin.write"] }, "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\\.)?.*$" + } }