// Kyoo - A portable and vast media library solution. // Copyright (c) Kyoo. // // See AUTHORS.md and LICENSE file in the project root for full license information. // // Kyoo is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // Kyoo is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . 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.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Core.Api; using Microsoft.Extensions.Configuration; using Newtonsoft.Json.Linq; namespace Kyoo.Core.Controllers { public class ConfigurationManager : IConfigurationManager { /// /// The configuration to retrieve and edit. /// private readonly IConfiguration _configuration; /// /// The application running Kyoo, it is used to retrieve the configuration file. /// private readonly IApplication _application; /// /// 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. /// The application running Kyoo, it is used to retrieve the configuration file. public ConfigurationManager(IConfiguration configuration, IEnumerable references, IApplication application) { _configuration = configuration; _application = application; _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } /// /// 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; } /// public void AddTyped(string path) { foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference(path)) _references.Add(confRef.Path, confRef.Type); } /// public void AddUntyped(string path) { ConfigurationReference config = ConfigurationReference.CreateUntyped(path); _references.Add(config.Path, config.Type); } /// public void Register(string path, Type type) { if (type == null) { ConfigurationReference config = ConfigurationReference.CreateUntyped(path); _references.Add(config.Path, config.Type); } else { foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference(path, type)) _references.Add(confRef.Path, confRef.Type); } } /// /// Get the type of the resource at the given path /// /// The path of the resource /// The path is not editable or readable /// No configuration exists for the given path /// The type of the resource at the given path 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(_application.GetConfigFile()); 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", Justification = "A catch block should not be empty.")] 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; } } }