aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/AppBase
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/AppBase')
-rw-r--r--Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs178
-rw-r--r--Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs328
-rw-r--r--Emby.Server.Implementations/AppBase/ConfigurationHelper.cs60
3 files changed, 566 insertions, 0 deletions
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
new file mode 100644
index 000000000..54d1d5302
--- /dev/null
+++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
@@ -0,0 +1,178 @@
+using System;
+using System.IO;
+using MediaBrowser.Common.Configuration;
+
+namespace Emby.Server.Implementations.AppBase
+{
+ /// <summary>
+ /// Provides a base class to hold common application paths used by both the Ui and Server.
+ /// This can be subclassed to add application-specific paths.
+ /// </summary>
+ public abstract class BaseApplicationPaths : IApplicationPaths
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
+ /// </summary>
+ protected BaseApplicationPaths(string programDataPath, string appFolderPath, Action<string> createDirectoryFn)
+ {
+ ProgramDataPath = programDataPath;
+ ProgramSystemPath = appFolderPath;
+ CreateDirectoryFn = createDirectoryFn;
+ }
+
+ protected Action<string> CreateDirectoryFn;
+
+ public string ProgramDataPath { get; private set; }
+
+ /// <summary>
+ /// Gets the path to the system folder
+ /// </summary>
+ public string ProgramSystemPath { get; private set; }
+
+ /// <summary>
+ /// The _data directory
+ /// </summary>
+ private string _dataDirectory;
+ /// <summary>
+ /// Gets the folder path to the data directory
+ /// </summary>
+ /// <value>The data directory.</value>
+ public string DataPath
+ {
+ get
+ {
+ if (_dataDirectory == null)
+ {
+ _dataDirectory = Path.Combine(ProgramDataPath, "data");
+
+ CreateDirectoryFn(_dataDirectory);
+ }
+
+ return _dataDirectory;
+ }
+ }
+
+ /// <summary>
+ /// Gets the image cache path.
+ /// </summary>
+ /// <value>The image cache path.</value>
+ public string ImageCachePath
+ {
+ get
+ {
+ return Path.Combine(CachePath, "images");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the plugin directory
+ /// </summary>
+ /// <value>The plugins path.</value>
+ public string PluginsPath
+ {
+ get
+ {
+ return Path.Combine(ProgramDataPath, "plugins");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the plugin configurations directory
+ /// </summary>
+ /// <value>The plugin configurations path.</value>
+ public string PluginConfigurationsPath
+ {
+ get
+ {
+ return Path.Combine(PluginsPath, "configurations");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to where temporary update files will be stored
+ /// </summary>
+ /// <value>The plugin configurations path.</value>
+ public string TempUpdatePath
+ {
+ get
+ {
+ return Path.Combine(ProgramDataPath, "updates");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the log directory
+ /// </summary>
+ /// <value>The log directory path.</value>
+ public string LogDirectoryPath
+ {
+ get
+ {
+ return Path.Combine(ProgramDataPath, "logs");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the application configuration root directory
+ /// </summary>
+ /// <value>The configuration directory path.</value>
+ public string ConfigurationDirectoryPath
+ {
+ get
+ {
+ return Path.Combine(ProgramDataPath, "config");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the system configuration file
+ /// </summary>
+ /// <value>The system configuration file path.</value>
+ public string SystemConfigurationFilePath
+ {
+ get
+ {
+ return Path.Combine(ConfigurationDirectoryPath, "system.xml");
+ }
+ }
+
+ /// <summary>
+ /// The _cache directory
+ /// </summary>
+ private string _cachePath;
+ /// <summary>
+ /// Gets the folder path to the cache directory
+ /// </summary>
+ /// <value>The cache directory.</value>
+ public string CachePath
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(_cachePath))
+ {
+ _cachePath = Path.Combine(ProgramDataPath, "cache");
+
+ CreateDirectoryFn(_cachePath);
+ }
+
+ return _cachePath;
+ }
+ set
+ {
+ _cachePath = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the folder path to the temp directory within the cache folder
+ /// </summary>
+ /// <value>The temp directory.</value>
+ public string TempDirectory
+ {
+ get
+ {
+ return Path.Combine(CachePath, "temp");
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
new file mode 100644
index 000000000..13874223c
--- /dev/null
+++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
@@ -0,0 +1,328 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
+
+namespace Emby.Server.Implementations.AppBase
+{
+ /// <summary>
+ /// Class BaseConfigurationManager
+ /// </summary>
+ public abstract class BaseConfigurationManager : IConfigurationManager
+ {
+ /// <summary>
+ /// Gets the type of the configuration.
+ /// </summary>
+ /// <value>The type of the configuration.</value>
+ protected abstract Type ConfigurationType { get; }
+
+ /// <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 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 or sets the application paths.
+ /// </summary>
+ /// <value>The application paths.</value>
+ public IApplicationPaths CommonApplicationPaths { get; private set; }
+ public readonly IFileSystem FileSystem;
+
+ /// <summary>
+ /// The _configuration loaded
+ /// </summary>
+ private bool _configurationLoaded;
+ /// <summary>
+ /// The _configuration sync lock
+ /// </summary>
+ private object _configurationSyncLock = new object();
+ /// <summary>
+ /// The _configuration
+ /// </summary>
+ private BaseApplicationConfiguration _configuration;
+ /// <summary>
+ /// Gets the system configuration
+ /// </summary>
+ /// <value>The configuration.</value>
+ public BaseApplicationConfiguration CommonConfiguration
+ {
+ get
+ {
+ // Lazy load
+ LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem));
+ return _configuration;
+ }
+ protected set
+ {
+ _configuration = value;
+
+ _configurationLoaded = value != null;
+ }
+ }
+
+ private ConfigurationStore[] _configurationStores = { };
+ private IConfigurationFactory[] _configurationFactories = { };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseConfigurationManager" /> class.
+ /// </summary>
+ /// <param name="applicationPaths">The application paths.</param>
+ /// <param name="logManager">The log manager.</param>
+ /// <param name="xmlSerializer">The XML serializer.</param>
+ protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILogManager logManager, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
+ {
+ CommonApplicationPaths = applicationPaths;
+ XmlSerializer = xmlSerializer;
+ FileSystem = fileSystem;
+ Logger = logManager.GetLogger(GetType().Name);
+
+ UpdateCachePath();
+ }
+
+ 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.Info("Saving system configuration");
+ var path = CommonApplicationPaths.SystemConfigurationFilePath;
+
+ FileSystem.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="System.ArgumentNullException">newConfiguration</exception>
+ public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
+ {
+ if (newConfiguration == null)
+ {
+ throw new ArgumentNullException("newConfiguration");
+ }
+
+ ValidateCachePath(newConfiguration);
+
+ CommonConfiguration = newConfiguration;
+ SaveConfiguration();
+ }
+
+ /// <summary>
+ /// Updates the items by name path.
+ /// </summary>
+ private void UpdateCachePath()
+ {
+ string cachePath;
+
+ if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath))
+ {
+ cachePath = null;
+ }
+ else
+ {
+ cachePath = Path.Combine(CommonConfiguration.CachePath, "cache");
+ }
+
+ ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
+ }
+
+ /// <summary>
+ /// Replaces the cache path.
+ /// </summary>
+ /// <param name="newConfig">The new configuration.</param>
+ /// <exception cref="System.IO.DirectoryNotFoundException"></exception>
+ private void ValidateCachePath(BaseApplicationConfiguration newConfig)
+ {
+ var newPath = newConfig.CachePath;
+
+ if (!string.IsNullOrWhiteSpace(newPath)
+ && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
+ {
+ // Validate
+ if (!FileSystem.DirectoryExists(newPath))
+ {
+ throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
+ }
+
+ EnsureWriteAccess(newPath);
+ }
+ }
+
+ protected void EnsureWriteAccess(string path)
+ {
+ var file = Path.Combine(path, Guid.NewGuid().ToString());
+
+ FileSystem.WriteAllText(file, string.Empty);
+ FileSystem.DeleteFile(file);
+ }
+
+ private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
+
+ private string GetConfigurationFile(string key)
+ {
+ return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLower() + ".xml");
+ }
+
+ 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)
+ {
+ try
+ {
+ return XmlSerializer.DeserializeFromFile(configurationType, path);
+ }
+ catch (FileNotFoundException)
+ {
+ return Activator.CreateInstance(configurationType);
+ }
+ catch (IOException)
+ {
+ return Activator.CreateInstance(configurationType);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error loading configuration file: {0}", ex, path);
+
+ return Activator.CreateInstance(configurationType);
+ }
+ }
+
+ 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);
+ }
+
+ var validatingStore = configurationStore as IValidatingConfiguration;
+ if (validatingStore != null)
+ {
+ var currentConfiguration = GetConfiguration(key);
+
+ validatingStore.Validate(currentConfiguration, configuration);
+ }
+
+ EventHelper.FireEventIfNotNull(NamedConfigurationUpdating, this, new ConfigurationUpdateEventArgs
+ {
+ Key = key,
+ NewConfiguration = configuration
+
+ }, Logger);
+
+ _configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
+
+ var path = GetConfigurationFile(key);
+ FileSystem.CreateDirectory(Path.GetDirectoryName(path));
+
+ lock (_configurationSyncLock)
+ {
+ XmlSerializer.SerializeToFile(configuration, path);
+ }
+
+ OnNamedConfigurationUpdated(key, configuration);
+ }
+
+ protected virtual void OnNamedConfigurationUpdated(string key, object configuration)
+ {
+ EventHelper.FireEventIfNotNull(NamedConfigurationUpdated, this, new ConfigurationUpdateEventArgs
+ {
+ Key = key,
+ NewConfiguration = configuration
+
+ }, Logger);
+ }
+
+ 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));
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
new file mode 100644
index 000000000..ad2f45945
--- /dev/null
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -0,0 +1,60 @@
+using System;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Serialization;
+
+namespace Emby.Server.Implementations.AppBase
+{
+ /// <summary>
+ /// Class ConfigurationHelper
+ /// </summary>
+ public static class ConfigurationHelper
+ {
+ /// <summary>
+ /// Reads an xml configuration file from the file system
+ /// It will immediately re-serialize and save if new serialization data is available due to property changes
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <param name="path">The path.</param>
+ /// <param name="xmlSerializer">The XML serializer.</param>
+ /// <returns>System.Object.</returns>
+ public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
+ {
+ object configuration;
+
+ byte[] buffer = null;
+
+ // Use try/catch to avoid the extra file system lookup using File.Exists
+ try
+ {
+ buffer = fileSystem.ReadAllBytes(path);
+
+ configuration = xmlSerializer.DeserializeFromBytes(type, buffer);
+ }
+ catch (Exception)
+ {
+ configuration = Activator.CreateInstance(type);
+ }
+
+ using (var stream = new MemoryStream())
+ {
+ xmlSerializer.SerializeToStream(configuration, stream);
+
+ // Take the object we just got and serialize it back to bytes
+ var newBytes = stream.ToArray();
+
+ // If the file didn't exist before, or if something has changed, re-save
+ if (buffer == null || !buffer.SequenceEqual(newBytes))
+ {
+ fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+
+ // Save it after load in case we got new items
+ fileSystem.WriteAllBytes(path, newBytes);
+ }
+
+ return configuration;
+ }
+ }
+ }
+}