123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- #nullable disable
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Globalization;
- using System.IO;
- using System.Linq;
- using MediaBrowser.Common.Configuration;
- using MediaBrowser.Common.Events;
- using MediaBrowser.Common.Extensions;
- using MediaBrowser.Model.Configuration;
- using MediaBrowser.Model.IO;
- using MediaBrowser.Model.Serialization;
- using Microsoft.Extensions.Logging;
- namespace Emby.Server.Implementations.AppBase
- {
- /// <summary>
- /// Class BaseConfigurationManager.
- /// </summary>
- public abstract class BaseConfigurationManager : IConfigurationManager
- {
- private readonly IFileSystem _fileSystem;
- private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
- /// <summary>
- /// The _configuration sync lock.
- /// </summary>
- private readonly object _configurationSyncLock = new object();
- private ConfigurationStore[] _configurationStores = Array.Empty<ConfigurationStore>();
- private IConfigurationFactory[] _configurationFactories = Array.Empty<IConfigurationFactory>();
- /// <summary>
- /// The _configuration loaded.
- /// </summary>
- private bool _configurationLoaded;
- /// <summary>
- /// The _configuration.
- /// </summary>
- private BaseApplicationConfiguration _configuration;
- /// <summary>
- /// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
- /// </summary>
- /// <param name="applicationPaths">The application paths.</param>
- /// <param name="loggerFactory">The logger factory.</param>
- /// <param name="xmlSerializer">The XML serializer.</param>
- /// <param name="fileSystem">The file system.</param>
- protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
- {
- CommonApplicationPaths = applicationPaths;
- XmlSerializer = xmlSerializer;
- _fileSystem = fileSystem;
- Logger = loggerFactory.CreateLogger<BaseConfigurationManager>();
- UpdateCachePath();
- }
- /// <summary>
- /// Occurs when [configuration updated].
- /// </summary>
- public event EventHandler<EventArgs> ConfigurationUpdated;
- /// <summary>
- /// Occurs when [configuration updating].
- /// </summary>
- public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdating;
- /// <summary>
- /// Occurs when [named configuration updated].
- /// </summary>
- public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated;
- /// <summary>
- /// Gets the type of the configuration.
- /// </summary>
- /// <value>The type of the configuration.</value>
- protected abstract Type ConfigurationType { get; }
- /// <summary>
- /// Gets the logger.
- /// </summary>
- /// <value>The logger.</value>
- protected ILogger<BaseConfigurationManager> Logger { get; private set; }
- /// <summary>
- /// Gets the XML serializer.
- /// </summary>
- /// <value>The XML serializer.</value>
- protected IXmlSerializer XmlSerializer { get; private set; }
- /// <summary>
- /// Gets the application paths.
- /// </summary>
- /// <value>The application paths.</value>
- public IApplicationPaths CommonApplicationPaths { get; private set; }
- /// <summary>
- /// Gets or sets the system configuration.
- /// </summary>
- /// <value>The configuration.</value>
- public BaseApplicationConfiguration CommonConfiguration
- {
- get
- {
- if (_configurationLoaded)
- {
- return _configuration;
- }
- lock (_configurationSyncLock)
- {
- if (_configurationLoaded)
- {
- return _configuration;
- }
- _configuration = (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer);
- _configurationLoaded = true;
- return _configuration;
- }
- }
- protected set
- {
- _configuration = value;
- _configurationLoaded = value is not null;
- }
- }
- /// <summary>
- /// Manually pre-loads a factory so that it is available pre system initialisation.
- /// </summary>
- /// <typeparam name="T">Class to register.</typeparam>
- public virtual void RegisterConfiguration<T>()
- where T : IConfigurationFactory
- {
- IConfigurationFactory factory = Activator.CreateInstance<T>();
- if (_configurationFactories is null)
- {
- _configurationFactories = new[] { factory };
- }
- else
- {
- var oldLen = _configurationFactories.Length;
- var arr = new IConfigurationFactory[oldLen + 1];
- _configurationFactories.CopyTo(arr, 0);
- arr[oldLen] = factory;
- _configurationFactories = arr;
- }
- _configurationStores = _configurationFactories
- .SelectMany(i => i.GetConfigurations())
- .ToArray();
- }
- /// <summary>
- /// Adds parts.
- /// </summary>
- /// <param name="factories">The configuration factories.</param>
- public virtual void AddParts(IEnumerable<IConfigurationFactory> factories)
- {
- _configurationFactories = factories.ToArray();
- _configurationStores = _configurationFactories
- .SelectMany(i => i.GetConfigurations())
- .ToArray();
- }
- /// <summary>
- /// Saves the configuration.
- /// </summary>
- public void SaveConfiguration()
- {
- Logger.LogInformation("Saving system configuration");
- var path = CommonApplicationPaths.SystemConfigurationFilePath;
- Directory.CreateDirectory(Path.GetDirectoryName(path));
- lock (_configurationSyncLock)
- {
- XmlSerializer.SerializeToFile(CommonConfiguration, path);
- }
- OnConfigurationUpdated();
- }
- /// <summary>
- /// Called when [configuration updated].
- /// </summary>
- protected virtual void OnConfigurationUpdated()
- {
- UpdateCachePath();
- EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger);
- }
- /// <summary>
- /// Replaces the configuration.
- /// </summary>
- /// <param name="newConfiguration">The new configuration.</param>
- /// <exception cref="ArgumentNullException"><c>newConfiguration</c> is <c>null</c>.</exception>
- public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
- {
- ArgumentNullException.ThrowIfNull(newConfiguration);
- ValidateCachePath(newConfiguration);
- CommonConfiguration = newConfiguration;
- SaveConfiguration();
- }
- /// <summary>
- /// Updates the items by name path.
- /// </summary>
- private void UpdateCachePath()
- {
- string cachePath;
- // If the configuration file has no entry (i.e. not set in UI)
- if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
- {
- // If the current live configuration has no entry (i.e. not set on CLI/envvars, during startup)
- if (string.IsNullOrWhiteSpace(((BaseApplicationPaths)CommonApplicationPaths).CachePath))
- {
- // Set cachePath to a default value under ProgramDataPath
- cachePath = Path.Combine(((BaseApplicationPaths)CommonApplicationPaths).ProgramDataPath, "cache");
- }
- else
- {
- // Set cachePath to the existing live value; will require restart if UI value is removed (but not replaced)
- // TODO: Figure out how to re-grab this from the CLI/envvars while running
- cachePath = ((BaseApplicationPaths)CommonApplicationPaths).CachePath;
- }
- }
- else
- {
- // Set cachePath to the new UI-set value
- cachePath = CommonConfiguration.CachePath;
- }
- Logger.LogInformation("Setting cache path: {Path}", cachePath);
- ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
- }
- /// <summary>
- /// Replaces the cache path.
- /// </summary>
- /// <param name="newConfig">The new configuration.</param>
- /// <exception cref="DirectoryNotFoundException">The new cache path doesn't exist.</exception>
- private void ValidateCachePath(BaseApplicationConfiguration newConfig)
- {
- var newPath = newConfig.CachePath;
- if (!string.IsNullOrWhiteSpace(newPath)
- && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath, StringComparison.Ordinal))
- {
- // Validate
- if (!Directory.Exists(newPath))
- {
- throw new DirectoryNotFoundException(
- string.Format(
- CultureInfo.InvariantCulture,
- "{0} does not exist.",
- newPath));
- }
- EnsureWriteAccess(newPath);
- }
- }
- /// <summary>
- /// Ensures that we have write access to the path.
- /// </summary>
- /// <param name="path">The path.</param>
- protected void EnsureWriteAccess(string path)
- {
- var file = Path.Combine(path, Guid.NewGuid().ToString());
- File.WriteAllText(file, string.Empty);
- _fileSystem.DeleteFile(file);
- }
- private string GetConfigurationFile(string key)
- {
- return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml");
- }
- /// <inheritdoc />
- public object GetConfiguration(string key)
- {
- return _configurations.GetOrAdd(
- key,
- static (k, configurationManager) =>
- {
- var file = configurationManager.GetConfigurationFile(k);
- var configurationInfo = Array.Find(
- configurationManager._configurationStores,
- i => string.Equals(i.Key, k, StringComparison.OrdinalIgnoreCase));
- if (configurationInfo is null)
- {
- throw new ResourceNotFoundException("Configuration with key " + k + " not found.");
- }
- var configurationType = configurationInfo.ConfigurationType;
- lock (configurationManager._configurationSyncLock)
- {
- return configurationManager.LoadConfiguration(file, configurationType);
- }
- },
- this);
- }
- private object LoadConfiguration(string path, Type configurationType)
- {
- if (!File.Exists(path))
- {
- return Activator.CreateInstance(configurationType);
- }
- try
- {
- return XmlSerializer.DeserializeFromFile(configurationType, path);
- }
- catch (IOException)
- {
- return Activator.CreateInstance(configurationType);
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error loading configuration file: {Path}", path);
- return Activator.CreateInstance(configurationType);
- }
- }
- /// <inheritdoc />
- public void SaveConfiguration(string key, object configuration)
- {
- var configurationStore = GetConfigurationStore(key);
- var configurationType = configurationStore.ConfigurationType;
- if (configuration.GetType() != configurationType)
- {
- throw new ArgumentException("Expected configuration type is " + configurationType.Name);
- }
- if (configurationStore is IValidatingConfiguration validatingStore)
- {
- var currentConfiguration = GetConfiguration(key);
- validatingStore.Validate(currentConfiguration, configuration);
- }
- NamedConfigurationUpdating?.Invoke(this, new ConfigurationUpdateEventArgs(key, configuration));
- _configurations.AddOrUpdate(key, configuration, (_, _) => configuration);
- var path = GetConfigurationFile(key);
- Directory.CreateDirectory(Path.GetDirectoryName(path));
- lock (_configurationSyncLock)
- {
- XmlSerializer.SerializeToFile(configuration, path);
- }
- OnNamedConfigurationUpdated(key, configuration);
- }
- /// <summary>
- /// Event handler for when a named configuration has been updated.
- /// </summary>
- /// <param name="key">The key of the configuration.</param>
- /// <param name="configuration">The old configuration.</param>
- protected virtual void OnNamedConfigurationUpdated(string key, object configuration)
- {
- NamedConfigurationUpdated?.Invoke(this, new ConfigurationUpdateEventArgs(key, configuration));
- }
- /// <inheritdoc />
- public ConfigurationStore[] GetConfigurationStores()
- {
- return _configurationStores;
- }
- /// <inheritdoc />
- public Type GetConfigurationType(string key)
- {
- return GetConfigurationStore(key)
- .ConfigurationType;
- }
- private ConfigurationStore GetConfigurationStore(string key)
- {
- return _configurationStores
- .First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase));
- }
- }
- }
|