| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 | 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>();        private ConfigurationStore[] _configurationStores = Array.Empty<ConfigurationStore>();        private IConfigurationFactory[] _configurationFactories = Array.Empty<IConfigurationFactory>();        /// <summary>        /// The _configuration loaded.        /// </summary>        private bool _configurationLoaded;        /// <summary>        /// The _configuration sync lock.        /// </summary>        private readonly object _configurationSyncLock = new object();        /// <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(GetType().Name);            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 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 != null;            }        }        /// <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)        {            if (newConfiguration == null)            {                throw new ArgumentNullException(nameof(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, k =>            {                var file = GetConfigurationFile(key);                var configurationInfo = _configurationStores                    .FirstOrDefault(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase));                if (configurationInfo == null)                {                    throw new ResourceNotFoundException("Configuration with key " + key + " not found.");                }                var configurationType = configurationInfo.ConfigurationType;                lock (_configurationSyncLock)                {                    return LoadConfiguration(file, configurationType);                }            });        }        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 = key,                NewConfiguration = configuration            });            _configurations.AddOrUpdate(key, configuration, (k, v) => 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 = key,                NewConfiguration = configuration            });        }        /// <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));        }    }}
 |