aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2017-02-20 15:50:58 -0500
committerLuke Pulverenti <luke.pulverenti@gmail.com>2017-02-20 15:50:58 -0500
commit5d55b36487b25b2efaf6923a3c069f4b0b59a449 (patch)
treedacf7607a28bf65a67004f65fdb1f1e33f3c075f /Emby.Server.Implementations
parentde3ca87a763506badcf1518ae288a5736605445c (diff)
make more classes portable
Diffstat (limited to 'Emby.Server.Implementations')
-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
-rw-r--r--Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs245
-rw-r--r--Emby.Server.Implementations/Connect/ConnectData.cs36
-rw-r--r--Emby.Server.Implementations/Connect/ConnectEntryPoint.cs218
-rw-r--r--Emby.Server.Implementations/Connect/ConnectManager.cs1193
-rw-r--r--Emby.Server.Implementations/Connect/Responses.cs85
-rw-r--r--Emby.Server.Implementations/Connect/Validator.cs29
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs6
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj11
-rw-r--r--Emby.Server.Implementations/Logging/UnhandledExceptionWriter.cs43
-rw-r--r--Emby.Server.Implementations/ServerApplicationPaths.cs234
13 files changed, 1097 insertions, 1569 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;
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
new file mode 100644
index 000000000..2241e9377
--- /dev/null
+++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -0,0 +1,245 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Emby.Server.Implementations.AppBase;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
+
+namespace Emby.Server.Implementations.Configuration
+{
+ /// <summary>
+ /// Class ServerConfigurationManager
+ /// </summary>
+ public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager
+ {
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServerConfigurationManager" /> class.
+ /// </summary>
+ /// <param name="applicationPaths">The application paths.</param>
+ /// <param name="logManager">The log manager.</param>
+ /// <param name="xmlSerializer">The XML serializer.</param>
+ /// <param name="fileSystem">The file system.</param>
+ public ServerConfigurationManager(IApplicationPaths applicationPaths, ILogManager logManager, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
+ : base(applicationPaths, logManager, xmlSerializer, fileSystem)
+ {
+ UpdateMetadataPath();
+ }
+
+ public event EventHandler<GenericEventArgs<ServerConfiguration>> ConfigurationUpdating;
+
+ /// <summary>
+ /// Gets the type of the configuration.
+ /// </summary>
+ /// <value>The type of the configuration.</value>
+ protected override Type ConfigurationType
+ {
+ get { return typeof(ServerConfiguration); }
+ }
+
+ /// <summary>
+ /// Gets the application paths.
+ /// </summary>
+ /// <value>The application paths.</value>
+ public IServerApplicationPaths ApplicationPaths
+ {
+ get { return (IServerApplicationPaths)CommonApplicationPaths; }
+ }
+
+ /// <summary>
+ /// Gets the configuration.
+ /// </summary>
+ /// <value>The configuration.</value>
+ public ServerConfiguration Configuration
+ {
+ get { return (ServerConfiguration)CommonConfiguration; }
+ }
+
+ /// <summary>
+ /// Called when [configuration updated].
+ /// </summary>
+ protected override void OnConfigurationUpdated()
+ {
+ UpdateMetadataPath();
+
+ base.OnConfigurationUpdated();
+ }
+
+ public override void AddParts(IEnumerable<IConfigurationFactory> factories)
+ {
+ base.AddParts(factories);
+
+ UpdateTranscodingTempPath();
+ }
+
+ /// <summary>
+ /// Updates the metadata path.
+ /// </summary>
+ private void UpdateMetadataPath()
+ {
+ string metadataPath;
+
+ if (string.IsNullOrWhiteSpace(Configuration.MetadataPath))
+ {
+ metadataPath = GetInternalMetadataPath();
+ }
+ else
+ {
+ metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
+ }
+
+ ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
+
+ ((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath;
+ }
+
+ private string GetInternalMetadataPath()
+ {
+ return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
+ }
+
+ /// <summary>
+ /// Updates the transcoding temporary path.
+ /// </summary>
+ private void UpdateTranscodingTempPath()
+ {
+ var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding");
+
+ ((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ?
+ null :
+ Path.Combine(encodingConfig.TranscodingTempPath, "transcoding-temp");
+ }
+
+ protected override void OnNamedConfigurationUpdated(string key, object configuration)
+ {
+ base.OnNamedConfigurationUpdated(key, configuration);
+
+ if (string.Equals(key, "encoding", StringComparison.OrdinalIgnoreCase))
+ {
+ UpdateTranscodingTempPath();
+ }
+ }
+
+ /// <summary>
+ /// Replaces the configuration.
+ /// </summary>
+ /// <param name="newConfiguration">The new configuration.</param>
+ /// <exception cref="System.IO.DirectoryNotFoundException"></exception>
+ public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
+ {
+ var newConfig = (ServerConfiguration)newConfiguration;
+
+ ValidateMetadataPath(newConfig);
+ ValidateSslCertificate(newConfig);
+
+ EventHelper.FireEventIfNotNull(ConfigurationUpdating, this, new GenericEventArgs<ServerConfiguration> { Argument = newConfig }, Logger);
+
+ base.ReplaceConfiguration(newConfiguration);
+ }
+
+
+ /// <summary>
+ /// Validates the SSL certificate.
+ /// </summary>
+ /// <param name="newConfig">The new configuration.</param>
+ /// <exception cref="System.IO.DirectoryNotFoundException"></exception>
+ private void ValidateSslCertificate(BaseApplicationConfiguration newConfig)
+ {
+ var serverConfig = (ServerConfiguration)newConfig;
+
+ var newPath = serverConfig.CertificatePath;
+
+ if (!string.IsNullOrWhiteSpace(newPath)
+ && !string.Equals(Configuration.CertificatePath ?? string.Empty, newPath))
+ {
+ // Validate
+ if (!FileSystem.FileExists(newPath))
+ {
+ throw new FileNotFoundException(string.Format("Certificate file '{0}' does not exist.", newPath));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Validates the metadata path.
+ /// </summary>
+ /// <param name="newConfig">The new configuration.</param>
+ /// <exception cref="System.IO.DirectoryNotFoundException"></exception>
+ private void ValidateMetadataPath(ServerConfiguration newConfig)
+ {
+ var newPath = newConfig.MetadataPath;
+
+ if (!string.IsNullOrWhiteSpace(newPath)
+ && !string.Equals(Configuration.MetadataPath ?? string.Empty, newPath))
+ {
+ // Validate
+ if (!FileSystem.DirectoryExists(newPath))
+ {
+ throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
+ }
+
+ EnsureWriteAccess(newPath);
+ }
+ }
+
+ public void DisableMetadataService(string service)
+ {
+ DisableMetadataService(typeof(Movie), Configuration, service);
+ DisableMetadataService(typeof(Episode), Configuration, service);
+ DisableMetadataService(typeof(Series), Configuration, service);
+ DisableMetadataService(typeof(Season), Configuration, service);
+ DisableMetadataService(typeof(MusicArtist), Configuration, service);
+ DisableMetadataService(typeof(MusicAlbum), Configuration, service);
+ DisableMetadataService(typeof(MusicVideo), Configuration, service);
+ DisableMetadataService(typeof(Video), Configuration, service);
+ }
+
+ private void DisableMetadataService(Type type, ServerConfiguration config, string service)
+ {
+ var options = GetMetadataOptions(type, config);
+
+ if (!options.DisabledMetadataSavers.Contains(service, StringComparer.OrdinalIgnoreCase))
+ {
+ var list = options.DisabledMetadataSavers.ToList();
+
+ list.Add(service);
+
+ options.DisabledMetadataSavers = list.ToArray();
+ }
+ }
+
+ private MetadataOptions GetMetadataOptions(Type type, ServerConfiguration config)
+ {
+ var options = config.MetadataOptions
+ .FirstOrDefault(i => string.Equals(i.ItemType, type.Name, StringComparison.OrdinalIgnoreCase));
+
+ if (options == null)
+ {
+ var list = config.MetadataOptions.ToList();
+
+ options = new MetadataOptions
+ {
+ ItemType = type.Name
+ };
+
+ list.Add(options);
+
+ config.MetadataOptions = list.ToArray();
+ }
+
+ return options;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Connect/ConnectData.cs b/Emby.Server.Implementations/Connect/ConnectData.cs
deleted file mode 100644
index 41b89ce52..000000000
--- a/Emby.Server.Implementations/Connect/ConnectData.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Emby.Server.Implementations.Connect
-{
- public class ConnectData
- {
- /// <summary>
- /// Gets or sets the server identifier.
- /// </summary>
- /// <value>The server identifier.</value>
- public string ServerId { get; set; }
- /// <summary>
- /// Gets or sets the access key.
- /// </summary>
- /// <value>The access key.</value>
- public string AccessKey { get; set; }
-
- /// <summary>
- /// Gets or sets the authorizations.
- /// </summary>
- /// <value>The authorizations.</value>
- public List<ConnectAuthorizationInternal> PendingAuthorizations { get; set; }
-
- /// <summary>
- /// Gets or sets the last authorizations refresh.
- /// </summary>
- /// <value>The last authorizations refresh.</value>
- public DateTime LastAuthorizationsRefresh { get; set; }
-
- public ConnectData()
- {
- PendingAuthorizations = new List<ConnectAuthorizationInternal>();
- }
- }
-}
diff --git a/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs b/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs
deleted file mode 100644
index b5639773b..000000000
--- a/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs
+++ /dev/null
@@ -1,218 +0,0 @@
-using MediaBrowser.Common;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Connect;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
-using System;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Threading;
-
-namespace Emby.Server.Implementations.Connect
-{
- public class ConnectEntryPoint : IServerEntryPoint
- {
- private ITimer _timer;
- private IpAddressInfo _cachedIpAddress;
- private readonly IHttpClient _httpClient;
- private readonly IApplicationPaths _appPaths;
- private readonly ILogger _logger;
- private readonly IConnectManager _connectManager;
-
- private readonly INetworkManager _networkManager;
- private readonly IApplicationHost _appHost;
- private readonly IFileSystem _fileSystem;
- private readonly ITimerFactory _timerFactory;
- private readonly IEncryptionManager _encryption;
-
- public ConnectEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, INetworkManager networkManager, IConnectManager connectManager, IApplicationHost appHost, IFileSystem fileSystem, ITimerFactory timerFactory, IEncryptionManager encryption)
- {
- _httpClient = httpClient;
- _appPaths = appPaths;
- _logger = logger;
- _networkManager = networkManager;
- _connectManager = connectManager;
- _appHost = appHost;
- _fileSystem = fileSystem;
- _timerFactory = timerFactory;
- _encryption = encryption;
- }
-
- public void Run()
- {
- LoadCachedAddress();
-
- _timer = _timerFactory.Create(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(1));
- ((ConnectManager)_connectManager).Start();
- }
-
- private readonly string[] _ipLookups =
- {
- "http://bot.whatismyipaddress.com",
- "https://connect.emby.media/service/ip"
- };
-
- private async void TimerCallback(object state)
- {
- IpAddressInfo validIpAddress = null;
-
- foreach (var ipLookupUrl in _ipLookups)
- {
- try
- {
- validIpAddress = await GetIpAddress(ipLookupUrl).ConfigureAwait(false);
-
- // Try to find the ipv4 address, if present
- if (validIpAddress.AddressFamily != IpAddressFamily.InterNetworkV6)
- {
- break;
- }
- }
- catch (HttpException)
- {
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting connection info", ex);
- }
- }
-
- // If this produced an ipv6 address, try again
- if (validIpAddress != null && validIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
- {
- foreach (var ipLookupUrl in _ipLookups)
- {
- try
- {
- var newAddress = await GetIpAddress(ipLookupUrl, true).ConfigureAwait(false);
-
- // Try to find the ipv4 address, if present
- if (newAddress.AddressFamily != IpAddressFamily.InterNetworkV6)
- {
- validIpAddress = newAddress;
- break;
- }
- }
- catch (HttpException)
- {
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting connection info", ex);
- }
- }
- }
-
- if (validIpAddress != null)
- {
- ((ConnectManager)_connectManager).OnWanAddressResolved(validIpAddress);
- CacheAddress(validIpAddress);
- }
- }
-
- private async Task<IpAddressInfo> GetIpAddress(string lookupUrl, bool preferIpv4 = false)
- {
- // Sometimes whatismyipaddress might fail, but it won't do us any good having users raise alarms over it.
- var logErrors = false;
-
-#if DEBUG
- logErrors = true;
-#endif
- using (var stream = await _httpClient.Get(new HttpRequestOptions
- {
- Url = lookupUrl,
- UserAgent = "Emby/" + _appHost.ApplicationVersion,
- LogErrors = logErrors,
-
- // Seeing block length errors with our server
- EnableHttpCompression = false,
- PreferIpv4 = preferIpv4,
- BufferContent = false
-
- }).ConfigureAwait(false))
- {
- using (var reader = new StreamReader(stream))
- {
- var addressString = await reader.ReadToEndAsync().ConfigureAwait(false);
-
- return _networkManager.ParseIpAddress(addressString);
- }
- }
- }
-
- private string CacheFilePath
- {
- get { return Path.Combine(_appPaths.DataPath, "wan.dat"); }
- }
-
- private void CacheAddress(IpAddressInfo address)
- {
- if (_cachedIpAddress != null && _cachedIpAddress.Equals(address))
- {
- // no need to update the file if the address has not changed
- return;
- }
-
- var path = CacheFilePath;
-
- try
- {
- _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
- }
- catch (Exception ex)
- {
- }
-
- try
- {
- _fileSystem.WriteAllText(path, _encryption.EncryptString(address.ToString()), Encoding.UTF8);
- _cachedIpAddress = address;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving data", ex);
- }
- }
-
- private void LoadCachedAddress()
- {
- var path = CacheFilePath;
-
- _logger.Info("Loading data from {0}", path);
-
- try
- {
- var endpoint = _encryption.DecryptString(_fileSystem.ReadAllText(path, Encoding.UTF8));
- IpAddressInfo ipAddress;
-
- if (_networkManager.TryParseIpAddress(endpoint, out ipAddress))
- {
- _cachedIpAddress = ipAddress;
- ((ConnectManager)_connectManager).OnWanAddressResolved(ipAddress);
- }
- }
- catch (IOException)
- {
- // File isn't there. no biggie
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error loading data", ex);
- }
- }
-
- public void Dispose()
- {
- if (_timer != null)
- {
- _timer.Dispose();
- _timer = null;
- }
- }
- }
-}
diff --git a/Emby.Server.Implementations/Connect/ConnectManager.cs b/Emby.Server.Implementations/Connect/ConnectManager.cs
deleted file mode 100644
index 8aac2a8c4..000000000
--- a/Emby.Server.Implementations/Connect/ConnectManager.cs
+++ /dev/null
@@ -1,1193 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Common.Security;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Connect;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.Connect;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Common.Extensions;
-
-namespace Emby.Server.Implementations.Connect
-{
- public class ConnectManager : IConnectManager
- {
- private readonly SemaphoreSlim _operationLock = new SemaphoreSlim(1, 1);
-
- private readonly ILogger _logger;
- private readonly IApplicationPaths _appPaths;
- private readonly IJsonSerializer _json;
- private readonly IEncryptionManager _encryption;
- private readonly IHttpClient _httpClient;
- private readonly IServerApplicationHost _appHost;
- private readonly IServerConfigurationManager _config;
- private readonly IUserManager _userManager;
- private readonly IProviderManager _providerManager;
- private readonly ISecurityManager _securityManager;
- private readonly IFileSystem _fileSystem;
-
- private ConnectData _data = new ConnectData();
-
- public string ConnectServerId
- {
- get { return _data.ServerId; }
- }
- public string ConnectAccessKey
- {
- get { return _data.AccessKey; }
- }
-
- private IpAddressInfo DiscoveredWanIpAddress { get; set; }
-
- public string WanIpAddress
- {
- get
- {
- var address = _config.Configuration.WanDdns;
-
- if (!string.IsNullOrWhiteSpace(address))
- {
- Uri newUri;
-
- if (Uri.TryCreate(address, UriKind.Absolute, out newUri))
- {
- address = newUri.Host;
- }
- }
-
- if (string.IsNullOrWhiteSpace(address) && DiscoveredWanIpAddress != null)
- {
- if (DiscoveredWanIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
- {
- address = "[" + DiscoveredWanIpAddress + "]";
- }
- else
- {
- address = DiscoveredWanIpAddress.ToString();
- }
- }
-
- return address;
- }
- }
-
- public string WanApiAddress
- {
- get
- {
- var ip = WanIpAddress;
-
- if (!string.IsNullOrEmpty(ip))
- {
- if (!ip.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
- !ip.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
- {
- ip = (_appHost.EnableHttps ? "https://" : "http://") + ip;
- }
-
- ip += ":";
- ip += _appHost.EnableHttps ? _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture) : _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture);
-
- return ip;
- }
-
- return null;
- }
- }
-
- private string XApplicationValue
- {
- get { return _appHost.Name + "/" + _appHost.ApplicationVersion; }
- }
-
- public ConnectManager(ILogger logger,
- IApplicationPaths appPaths,
- IJsonSerializer json,
- IEncryptionManager encryption,
- IHttpClient httpClient,
- IServerApplicationHost appHost,
- IServerConfigurationManager config, IUserManager userManager, IProviderManager providerManager, ISecurityManager securityManager, IFileSystem fileSystem)
- {
- _logger = logger;
- _appPaths = appPaths;
- _json = json;
- _encryption = encryption;
- _httpClient = httpClient;
- _appHost = appHost;
- _config = config;
- _userManager = userManager;
- _providerManager = providerManager;
- _securityManager = securityManager;
- _fileSystem = fileSystem;
-
- LoadCachedData();
- }
-
- internal void Start()
- {
- _config.ConfigurationUpdated += _config_ConfigurationUpdated;
- }
-
- internal void OnWanAddressResolved(IpAddressInfo address)
- {
- DiscoveredWanIpAddress = address;
-
- var task = UpdateConnectInfo();
- }
-
- private async Task UpdateConnectInfo()
- {
- await _operationLock.WaitAsync().ConfigureAwait(false);
-
- try
- {
- await UpdateConnectInfoInternal().ConfigureAwait(false);
- }
- finally
- {
- _operationLock.Release();
- }
- }
-
- private async Task UpdateConnectInfoInternal()
- {
- var wanApiAddress = WanApiAddress;
-
- if (string.IsNullOrWhiteSpace(wanApiAddress))
- {
- _logger.Warn("Cannot update Emby Connect information without a WanApiAddress");
- return;
- }
-
- try
- {
- var localAddress = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
-
- var hasExistingRecord = !string.IsNullOrWhiteSpace(ConnectServerId) &&
- !string.IsNullOrWhiteSpace(ConnectAccessKey);
-
- var createNewRegistration = !hasExistingRecord;
-
- if (hasExistingRecord)
- {
- try
- {
- await UpdateServerRegistration(wanApiAddress, localAddress).ConfigureAwait(false);
- }
- catch (HttpException ex)
- {
- if (!ex.StatusCode.HasValue || !new[] { HttpStatusCode.NotFound, HttpStatusCode.Unauthorized }.Contains(ex.StatusCode.Value))
- {
- throw;
- }
-
- createNewRegistration = true;
- }
- }
-
- if (createNewRegistration)
- {
- await CreateServerRegistration(wanApiAddress, localAddress).ConfigureAwait(false);
- }
-
- _lastReportedIdentifier = GetConnectReportingIdentifier(localAddress, wanApiAddress);
-
- await RefreshAuthorizationsInternal(true, CancellationToken.None).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error registering with Connect", ex);
- }
- }
-
- private string _lastReportedIdentifier;
- private async Task<string> GetConnectReportingIdentifier()
- {
- var url = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
- return GetConnectReportingIdentifier(url, WanApiAddress);
- }
- private string GetConnectReportingIdentifier(string localAddress, string remoteAddress)
- {
- return (remoteAddress ?? string.Empty) + (localAddress ?? string.Empty);
- }
-
- async void _config_ConfigurationUpdated(object sender, EventArgs e)
- {
- // If info hasn't changed, don't report anything
- var connectIdentifier = await GetConnectReportingIdentifier().ConfigureAwait(false);
- if (string.Equals(_lastReportedIdentifier, connectIdentifier, StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
-
- await UpdateConnectInfo().ConfigureAwait(false);
- }
-
- private async Task CreateServerRegistration(string wanApiAddress, string localAddress)
- {
- if (string.IsNullOrWhiteSpace(wanApiAddress))
- {
- throw new ArgumentNullException("wanApiAddress");
- }
-
- var url = "Servers";
- url = GetConnectUrl(url);
-
- var postData = new Dictionary<string, string>
- {
- {"name", _appHost.FriendlyName},
- {"url", wanApiAddress},
- {"systemId", _appHost.SystemId}
- };
-
- if (!string.IsNullOrWhiteSpace(localAddress))
- {
- postData["localAddress"] = localAddress;
- }
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None,
- BufferContent = false
- };
-
- options.SetPostData(postData);
- SetApplicationHeader(options);
-
- using (var response = await _httpClient.Post(options).ConfigureAwait(false))
- {
- var data = _json.DeserializeFromStream<ServerRegistrationResponse>(response.Content);
-
- _data.ServerId = data.Id;
- _data.AccessKey = data.AccessKey;
-
- CacheData();
- }
- }
-
- private async Task UpdateServerRegistration(string wanApiAddress, string localAddress)
- {
- if (string.IsNullOrWhiteSpace(wanApiAddress))
- {
- throw new ArgumentNullException("wanApiAddress");
- }
-
- if (string.IsNullOrWhiteSpace(ConnectServerId))
- {
- throw new ArgumentNullException("ConnectServerId");
- }
-
- var url = "Servers";
- url = GetConnectUrl(url);
- url += "?id=" + ConnectServerId;
-
- var postData = new Dictionary<string, string>
- {
- {"name", _appHost.FriendlyName},
- {"url", wanApiAddress},
- {"systemId", _appHost.SystemId}
- };
-
- if (!string.IsNullOrWhiteSpace(localAddress))
- {
- postData["localAddress"] = localAddress;
- }
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None,
- BufferContent = false
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
- {
- }
- }
-
- private readonly object _dataFileLock = new object();
- private string CacheFilePath
- {
- get { return Path.Combine(_appPaths.DataPath, "connect.txt"); }
- }
-
- private void CacheData()
- {
- var path = CacheFilePath;
-
- try
- {
- _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
-
- var json = _json.SerializeToString(_data);
-
- var encrypted = _encryption.EncryptString(json);
-
- lock (_dataFileLock)
- {
- _fileSystem.WriteAllText(path, encrypted, Encoding.UTF8);
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving data", ex);
- }
- }
-
- private void LoadCachedData()
- {
- var path = CacheFilePath;
-
- _logger.Info("Loading data from {0}", path);
-
- try
- {
- lock (_dataFileLock)
- {
- var encrypted = _fileSystem.ReadAllText(path, Encoding.UTF8);
-
- var json = _encryption.DecryptString(encrypted);
-
- _data = _json.DeserializeFromString<ConnectData>(json);
- }
- }
- catch (IOException)
- {
- // File isn't there. no biggie
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error loading data", ex);
- }
- }
-
- private User GetUser(string id)
- {
- var user = _userManager.GetUserById(id);
-
- if (user == null)
- {
- throw new ArgumentException("User not found.");
- }
-
- return user;
- }
-
- private string GetConnectUrl(string handler)
- {
- return "https://connect.emby.media/service/" + handler;
- }
-
- public async Task<UserLinkResult> LinkUser(string userId, string connectUsername)
- {
- if (string.IsNullOrWhiteSpace(userId))
- {
- throw new ArgumentNullException("userId");
- }
- if (string.IsNullOrWhiteSpace(connectUsername))
- {
- throw new ArgumentNullException("connectUsername");
- }
- if (string.IsNullOrWhiteSpace(ConnectServerId))
- {
- await UpdateConnectInfo().ConfigureAwait(false);
- }
-
- await _operationLock.WaitAsync().ConfigureAwait(false);
-
- try
- {
- return await LinkUserInternal(userId, connectUsername).ConfigureAwait(false);
- }
- finally
- {
- _operationLock.Release();
- }
- }
-
- private async Task<UserLinkResult> LinkUserInternal(string userId, string connectUsername)
- {
- if (string.IsNullOrWhiteSpace(ConnectServerId))
- {
- throw new ArgumentNullException("ConnectServerId");
- }
-
- var connectUser = await GetConnectUser(new ConnectUserQuery
- {
- NameOrEmail = connectUsername
-
- }, CancellationToken.None).ConfigureAwait(false);
-
- if (!connectUser.IsActive)
- {
- throw new ArgumentException("The Emby account has been disabled.");
- }
-
- var existingUser = _userManager.Users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectUser.Id) && !string.IsNullOrWhiteSpace(i.ConnectAccessKey));
- if (existingUser != null)
- {
- throw new InvalidOperationException("This connect user is already linked to local user " + existingUser.Name);
- }
-
- var user = GetUser(userId);
-
- if (!string.IsNullOrWhiteSpace(user.ConnectUserId))
- {
- await RemoveConnect(user, user.ConnectUserId).ConfigureAwait(false);
- }
-
- var url = GetConnectUrl("ServerAuthorizations");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None,
- BufferContent = false
- };
-
- var accessToken = Guid.NewGuid().ToString("N");
-
- var postData = new Dictionary<string, string>
- {
- {"serverId", ConnectServerId},
- {"userId", connectUser.Id},
- {"userType", "Linked"},
- {"accessToken", accessToken}
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- var result = new UserLinkResult();
-
- // No need to examine the response
- using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
- {
- var response = _json.DeserializeFromStream<ServerUserAuthorizationResponse>(stream);
-
- result.IsPending = string.Equals(response.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase);
- }
-
- user.ConnectAccessKey = accessToken;
- user.ConnectUserName = connectUser.Name;
- user.ConnectUserId = connectUser.Id;
- user.ConnectLinkType = UserLinkType.LinkedUser;
-
- await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
-
- await _userManager.UpdateConfiguration(user.Id.ToString("N"), user.Configuration);
-
- await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
-
- return result;
- }
-
- public async Task<UserLinkResult> InviteUser(ConnectAuthorizationRequest request)
- {
- if (string.IsNullOrWhiteSpace(ConnectServerId))
- {
- await UpdateConnectInfo().ConfigureAwait(false);
- }
-
- await _operationLock.WaitAsync().ConfigureAwait(false);
-
- try
- {
- return await InviteUserInternal(request).ConfigureAwait(false);
- }
- finally
- {
- _operationLock.Release();
- }
- }
-
- private async Task<UserLinkResult> InviteUserInternal(ConnectAuthorizationRequest request)
- {
- var connectUsername = request.ConnectUserName;
- var sendingUserId = request.SendingUserId;
-
- if (string.IsNullOrWhiteSpace(connectUsername))
- {
- throw new ArgumentNullException("connectUsername");
- }
- if (string.IsNullOrWhiteSpace(ConnectServerId))
- {
- throw new ArgumentNullException("ConnectServerId");
- }
-
- var sendingUser = GetUser(sendingUserId);
- var requesterUserName = sendingUser.ConnectUserName;
-
- if (string.IsNullOrWhiteSpace(requesterUserName))
- {
- throw new ArgumentException("A Connect account is required in order to send invitations.");
- }
-
- string connectUserId = null;
- var result = new UserLinkResult();
-
- try
- {
- var connectUser = await GetConnectUser(new ConnectUserQuery
- {
- NameOrEmail = connectUsername
-
- }, CancellationToken.None).ConfigureAwait(false);
-
- if (!connectUser.IsActive)
- {
- throw new ArgumentException("The Emby account is not active. Please ensure the account has been activated by following the instructions within the email confirmation.");
- }
-
- connectUserId = connectUser.Id;
- result.GuestDisplayName = connectUser.Name;
- }
- catch (HttpException ex)
- {
- if (!ex.StatusCode.HasValue || ex.IsTimedOut)
- {
- throw new Exception("Unable to invite guest, " + ex.Message, ex);
- }
-
- // If they entered a username, then whatever the error is just throw it, for example, user not found
- if (!Validator.EmailIsValid(connectUsername))
- {
- if (ex.StatusCode.Value == HttpStatusCode.NotFound)
- {
- throw new ResourceNotFoundException();
- }
- throw;
- }
-
- if (ex.StatusCode.Value != HttpStatusCode.NotFound)
- {
- throw;
- }
- }
-
- if (string.IsNullOrWhiteSpace(connectUserId))
- {
- return await SendNewUserInvitation(requesterUserName, connectUsername).ConfigureAwait(false);
- }
-
- var url = GetConnectUrl("ServerAuthorizations");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None,
- BufferContent = false
- };
-
- var accessToken = Guid.NewGuid().ToString("N");
-
- var postData = new Dictionary<string, string>
- {
- {"serverId", ConnectServerId},
- {"userId", connectUserId},
- {"userType", "Guest"},
- {"accessToken", accessToken},
- {"requesterUserName", requesterUserName}
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
- {
- var response = _json.DeserializeFromStream<ServerUserAuthorizationResponse>(stream);
-
- result.IsPending = string.Equals(response.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase);
-
- _data.PendingAuthorizations.Add(new ConnectAuthorizationInternal
- {
- ConnectUserId = response.UserId,
- Id = response.Id,
- ImageUrl = response.UserImageUrl,
- UserName = response.UserName,
- EnabledLibraries = request.EnabledLibraries,
- EnabledChannels = request.EnabledChannels,
- EnableLiveTv = request.EnableLiveTv,
- AccessToken = accessToken
- });
-
- CacheData();
- }
-
- await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
-
- return result;
- }
-
- private async Task<UserLinkResult> SendNewUserInvitation(string fromName, string email)
- {
- var url = GetConnectUrl("users/invite");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None,
- BufferContent = false
- };
-
- var postData = new Dictionary<string, string>
- {
- {"email", email},
- {"requesterUserName", fromName}
- };
-
- options.SetPostData(postData);
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
- {
- }
-
- return new UserLinkResult
- {
- IsNewUserInvitation = true,
- GuestDisplayName = email
- };
- }
-
- public Task RemoveConnect(string userId)
- {
- var user = GetUser(userId);
-
- return RemoveConnect(user, user.ConnectUserId);
- }
-
- private async Task RemoveConnect(User user, string connectUserId)
- {
- if (!string.IsNullOrWhiteSpace(connectUserId))
- {
- await CancelAuthorizationByConnectUserId(connectUserId).ConfigureAwait(false);
- }
-
- user.ConnectAccessKey = null;
- user.ConnectUserName = null;
- user.ConnectUserId = null;
- user.ConnectLinkType = null;
-
- await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
- }
-
- private async Task<ConnectUser> GetConnectUser(ConnectUserQuery query, CancellationToken cancellationToken)
- {
- var url = GetConnectUrl("user");
-
- if (!string.IsNullOrWhiteSpace(query.Id))
- {
- url = url + "?id=" + WebUtility.UrlEncode(query.Id);
- }
- else if (!string.IsNullOrWhiteSpace(query.NameOrEmail))
- {
- url = url + "?nameOrEmail=" + WebUtility.UrlEncode(query.NameOrEmail);
- }
- else if (!string.IsNullOrWhiteSpace(query.Name))
- {
- url = url + "?name=" + WebUtility.UrlEncode(query.Name);
- }
- else if (!string.IsNullOrWhiteSpace(query.Email))
- {
- url = url + "?name=" + WebUtility.UrlEncode(query.Email);
- }
- else
- {
- throw new ArgumentException("Empty ConnectUserQuery supplied");
- }
-
- var options = new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url,
- BufferContent = false
- };
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
- {
- var response = _json.DeserializeFromStream<GetConnectUserResponse>(stream);
-
- return new ConnectUser
- {
- Email = response.Email,
- Id = response.Id,
- Name = response.Name,
- IsActive = response.IsActive,
- ImageUrl = response.ImageUrl
- };
- }
- }
-
- private void SetApplicationHeader(HttpRequestOptions options)
- {
- options.RequestHeaders.Add("X-Application", XApplicationValue);
- }
-
- private void SetServerAccessToken(HttpRequestOptions options)
- {
- if (string.IsNullOrWhiteSpace(ConnectAccessKey))
- {
- throw new ArgumentNullException("ConnectAccessKey");
- }
-
- options.RequestHeaders.Add("X-Connect-Token", ConnectAccessKey);
- }
-
- public async Task RefreshAuthorizations(CancellationToken cancellationToken)
- {
- await _operationLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- await RefreshAuthorizationsInternal(true, cancellationToken).ConfigureAwait(false);
- }
- finally
- {
- _operationLock.Release();
- }
- }
-
- private async Task RefreshAuthorizationsInternal(bool refreshImages, CancellationToken cancellationToken)
- {
- if (string.IsNullOrWhiteSpace(ConnectServerId))
- {
- throw new ArgumentNullException("ConnectServerId");
- }
-
- var url = GetConnectUrl("ServerAuthorizations");
-
- url += "?serverId=" + ConnectServerId;
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = false
- };
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- try
- {
- using (var stream = (await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)).Content)
- {
- var list = _json.DeserializeFromStream<List<ServerUserAuthorizationResponse>>(stream);
-
- await RefreshAuthorizations(list, refreshImages).ConfigureAwait(false);
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error refreshing server authorizations.", ex);
- }
- }
-
- private async Task RefreshAuthorizations(List<ServerUserAuthorizationResponse> list, bool refreshImages)
- {
- var users = _userManager.Users.ToList();
-
- // Handle existing authorizations that were removed by the Connect server
- // Handle existing authorizations whose status may have been updated
- foreach (var user in users)
- {
- if (!string.IsNullOrWhiteSpace(user.ConnectUserId))
- {
- var connectEntry = list.FirstOrDefault(i => string.Equals(i.UserId, user.ConnectUserId, StringComparison.OrdinalIgnoreCase));
-
- if (connectEntry == null)
- {
- var deleteUser = user.ConnectLinkType.HasValue &&
- user.ConnectLinkType.Value == UserLinkType.Guest;
-
- user.ConnectUserId = null;
- user.ConnectAccessKey = null;
- user.ConnectUserName = null;
- user.ConnectLinkType = null;
-
- await _userManager.UpdateUser(user).ConfigureAwait(false);
-
- if (deleteUser)
- {
- _logger.Debug("Deleting guest user {0}", user.Name);
- await _userManager.DeleteUser(user).ConfigureAwait(false);
- }
- }
- else
- {
- var changed = !string.Equals(user.ConnectAccessKey, connectEntry.AccessToken, StringComparison.OrdinalIgnoreCase);
-
- if (changed)
- {
- user.ConnectUserId = connectEntry.UserId;
- user.ConnectAccessKey = connectEntry.AccessToken;
-
- await _userManager.UpdateUser(user).ConfigureAwait(false);
- }
- }
- }
- }
-
- var currentPendingList = _data.PendingAuthorizations.ToList();
- var newPendingList = new List<ConnectAuthorizationInternal>();
-
- foreach (var connectEntry in list)
- {
- if (string.Equals(connectEntry.UserType, "guest", StringComparison.OrdinalIgnoreCase))
- {
- var currentPendingEntry = currentPendingList.FirstOrDefault(i => string.Equals(i.Id, connectEntry.Id, StringComparison.OrdinalIgnoreCase));
-
- if (string.Equals(connectEntry.AcceptStatus, "accepted", StringComparison.OrdinalIgnoreCase))
- {
- var user = _userManager.Users
- .FirstOrDefault(i => string.Equals(i.ConnectUserId, connectEntry.UserId, StringComparison.OrdinalIgnoreCase));
-
- if (user == null)
- {
- // Add user
- user = await _userManager.CreateUser(_userManager.MakeValidUsername(connectEntry.UserName)).ConfigureAwait(false);
-
- user.ConnectUserName = connectEntry.UserName;
- user.ConnectUserId = connectEntry.UserId;
- user.ConnectLinkType = UserLinkType.Guest;
- user.ConnectAccessKey = connectEntry.AccessToken;
-
- await _userManager.UpdateUser(user).ConfigureAwait(false);
-
- user.Policy.IsHidden = true;
- user.Policy.EnableLiveTvManagement = false;
- user.Policy.EnableContentDeletion = false;
- user.Policy.EnableRemoteControlOfOtherUsers = false;
- user.Policy.EnableSharedDeviceControl = false;
- user.Policy.IsAdministrator = false;
-
- if (currentPendingEntry != null)
- {
- user.Policy.EnabledFolders = currentPendingEntry.EnabledLibraries;
- user.Policy.EnableAllFolders = false;
-
- user.Policy.EnabledChannels = currentPendingEntry.EnabledChannels;
- user.Policy.EnableAllChannels = false;
-
- user.Policy.EnableLiveTvAccess = currentPendingEntry.EnableLiveTv;
- }
-
- await _userManager.UpdateConfiguration(user.Id.ToString("N"), user.Configuration);
- }
- }
- else if (string.Equals(connectEntry.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase))
- {
- currentPendingEntry = currentPendingEntry ?? new ConnectAuthorizationInternal();
-
- currentPendingEntry.ConnectUserId = connectEntry.UserId;
- currentPendingEntry.ImageUrl = connectEntry.UserImageUrl;
- currentPendingEntry.UserName = connectEntry.UserName;
- currentPendingEntry.Id = connectEntry.Id;
- currentPendingEntry.AccessToken = connectEntry.AccessToken;
-
- newPendingList.Add(currentPendingEntry);
- }
- }
- }
-
- _data.PendingAuthorizations = newPendingList;
-
- if (!newPendingList.Select(i => i.Id).SequenceEqual(currentPendingList.Select(i => i.Id), StringComparer.Ordinal))
- {
- CacheData();
- }
-
- await RefreshGuestNames(list, refreshImages).ConfigureAwait(false);
- }
-
- private async Task RefreshGuestNames(List<ServerUserAuthorizationResponse> list, bool refreshImages)
- {
- var users = _userManager.Users
- .Where(i => !string.IsNullOrEmpty(i.ConnectUserId) && i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest)
- .ToList();
-
- foreach (var user in users)
- {
- var authorization = list.FirstOrDefault(i => string.Equals(i.UserId, user.ConnectUserId, StringComparison.Ordinal));
-
- if (authorization == null)
- {
- _logger.Warn("Unable to find connect authorization record for user {0}", user.Name);
- continue;
- }
-
- var syncConnectName = true;
- var syncConnectImage = true;
-
- if (syncConnectName)
- {
- var changed = !string.Equals(authorization.UserName, user.Name, StringComparison.OrdinalIgnoreCase);
-
- if (changed)
- {
- await user.Rename(authorization.UserName).ConfigureAwait(false);
- }
- }
-
- if (syncConnectImage)
- {
- var imageUrl = authorization.UserImageUrl;
-
- if (!string.IsNullOrWhiteSpace(imageUrl))
- {
- var changed = false;
-
- if (!user.HasImage(ImageType.Primary))
- {
- changed = true;
- }
- else if (refreshImages)
- {
- using (var response = await _httpClient.SendAsync(new HttpRequestOptions
- {
- Url = imageUrl,
- BufferContent = false
-
- }, "HEAD").ConfigureAwait(false))
- {
- var length = response.ContentLength;
-
- if (length != _fileSystem.GetFileInfo(user.GetImageInfo(ImageType.Primary, 0).Path).Length)
- {
- changed = true;
- }
- }
- }
-
- if (changed)
- {
- await _providerManager.SaveImage(user, imageUrl, ImageType.Primary, null, CancellationToken.None).ConfigureAwait(false);
-
- await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
- {
- ForceSave = true,
-
- }, CancellationToken.None).ConfigureAwait(false);
- }
- }
- }
- }
- }
-
- public async Task<List<ConnectAuthorization>> GetPendingGuests()
- {
- var time = DateTime.UtcNow - _data.LastAuthorizationsRefresh;
-
- if (time.TotalMinutes >= 5)
- {
- await _operationLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
-
- try
- {
- await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
-
- _data.LastAuthorizationsRefresh = DateTime.UtcNow;
- CacheData();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error refreshing authorization", ex);
- }
- finally
- {
- _operationLock.Release();
- }
- }
-
- return _data.PendingAuthorizations.Select(i => new ConnectAuthorization
- {
- ConnectUserId = i.ConnectUserId,
- EnableLiveTv = i.EnableLiveTv,
- EnabledChannels = i.EnabledChannels,
- EnabledLibraries = i.EnabledLibraries,
- Id = i.Id,
- ImageUrl = i.ImageUrl,
- UserName = i.UserName
-
- }).ToList();
- }
-
- public async Task CancelAuthorization(string id)
- {
- await _operationLock.WaitAsync().ConfigureAwait(false);
-
- try
- {
- await CancelAuthorizationInternal(id).ConfigureAwait(false);
- }
- finally
- {
- _operationLock.Release();
- }
- }
-
- private async Task CancelAuthorizationInternal(string id)
- {
- var connectUserId = _data.PendingAuthorizations
- .First(i => string.Equals(i.Id, id, StringComparison.Ordinal))
- .ConnectUserId;
-
- await CancelAuthorizationByConnectUserId(connectUserId).ConfigureAwait(false);
-
- await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
- }
-
- private async Task CancelAuthorizationByConnectUserId(string connectUserId)
- {
- if (string.IsNullOrWhiteSpace(connectUserId))
- {
- throw new ArgumentNullException("connectUserId");
- }
- if (string.IsNullOrWhiteSpace(ConnectServerId))
- {
- throw new ArgumentNullException("ConnectServerId");
- }
-
- var url = GetConnectUrl("ServerAuthorizations");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None,
- BufferContent = false
- };
-
- var postData = new Dictionary<string, string>
- {
- {"serverId", ConnectServerId},
- {"userId", connectUserId}
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- try
- {
- // No need to examine the response
- using (var stream = (await _httpClient.SendAsync(options, "DELETE").ConfigureAwait(false)).Content)
- {
- }
- }
- catch (HttpException ex)
- {
- // If connect says the auth doesn't exist, we can handle that gracefully since this is a remove operation
-
- if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
- {
- throw;
- }
-
- _logger.Debug("Connect returned a 404 when removing a user auth link. Handling it.");
- }
- }
-
- public async Task<ConnectAuthenticationResult> Authenticate(string username, string passwordMd5)
- {
- if (string.IsNullOrWhiteSpace(username))
- {
- throw new ArgumentNullException("username");
- }
-
- if (string.IsNullOrWhiteSpace(passwordMd5))
- {
- throw new ArgumentNullException("passwordMd5");
- }
-
- var options = new HttpRequestOptions
- {
- Url = GetConnectUrl("user/authenticate"),
- BufferContent = false
- };
-
- options.SetPostData(new Dictionary<string, string>
- {
- {"userName",username},
- {"password",passwordMd5}
- });
-
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var response = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
- {
- return _json.DeserializeFromStream<ConnectAuthenticationResult>(response);
- }
- }
-
- public async Task<User> GetLocalUser(string connectUserId)
- {
- var user = _userManager.Users
- .FirstOrDefault(i => string.Equals(i.ConnectUserId, connectUserId, StringComparison.OrdinalIgnoreCase));
-
- if (user == null)
- {
- await RefreshAuthorizations(CancellationToken.None).ConfigureAwait(false);
- }
-
- return _userManager.Users
- .FirstOrDefault(i => string.Equals(i.ConnectUserId, connectUserId, StringComparison.OrdinalIgnoreCase));
- }
-
- public User GetUserFromExchangeToken(string token)
- {
- if (string.IsNullOrWhiteSpace(token))
- {
- throw new ArgumentNullException("token");
- }
-
- return _userManager.Users.FirstOrDefault(u => string.Equals(token, u.ConnectAccessKey, StringComparison.OrdinalIgnoreCase));
- }
-
- public bool IsAuthorizationTokenValid(string token)
- {
- if (string.IsNullOrWhiteSpace(token))
- {
- throw new ArgumentNullException("token");
- }
-
- return _userManager.Users.Any(u => string.Equals(token, u.ConnectAccessKey, StringComparison.OrdinalIgnoreCase)) ||
- _data.PendingAuthorizations.Select(i => i.AccessToken).Contains(token, StringComparer.OrdinalIgnoreCase);
- }
- }
-}
diff --git a/Emby.Server.Implementations/Connect/Responses.cs b/Emby.Server.Implementations/Connect/Responses.cs
deleted file mode 100644
index 87cb6cdf9..000000000
--- a/Emby.Server.Implementations/Connect/Responses.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Connect;
-
-namespace Emby.Server.Implementations.Connect
-{
- public class ServerRegistrationResponse
- {
- public string Id { get; set; }
- public string Url { get; set; }
- public string Name { get; set; }
- public string AccessKey { get; set; }
- }
-
- public class UpdateServerRegistrationResponse
- {
- public string Id { get; set; }
- public string Url { get; set; }
- public string Name { get; set; }
- }
-
- public class GetConnectUserResponse
- {
- public string Id { get; set; }
- public string Name { get; set; }
- public string DisplayName { get; set; }
- public string Email { get; set; }
- public bool IsActive { get; set; }
- public string ImageUrl { get; set; }
- }
-
- public class ServerUserAuthorizationResponse
- {
- public string Id { get; set; }
- public string ServerId { get; set; }
- public string UserId { get; set; }
- public string AccessToken { get; set; }
- public string DateCreated { get; set; }
- public bool IsActive { get; set; }
- public string AcceptStatus { get; set; }
- public string UserType { get; set; }
- public string UserImageUrl { get; set; }
- public string UserName { get; set; }
- }
-
- public class ConnectUserPreferences
- {
- public string[] PreferredAudioLanguages { get; set; }
- public bool PlayDefaultAudioTrack { get; set; }
- public string[] PreferredSubtitleLanguages { get; set; }
- public SubtitlePlaybackMode SubtitleMode { get; set; }
- public bool GroupMoviesIntoBoxSets { get; set; }
-
- public ConnectUserPreferences()
- {
- PreferredAudioLanguages = new string[] { };
- PreferredSubtitleLanguages = new string[] { };
- }
-
- public static ConnectUserPreferences FromUserConfiguration(UserConfiguration config)
- {
- return new ConnectUserPreferences
- {
- PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
- SubtitleMode = config.SubtitleMode,
- PreferredAudioLanguages = string.IsNullOrWhiteSpace(config.AudioLanguagePreference) ? new string[] { } : new[] { config.AudioLanguagePreference },
- PreferredSubtitleLanguages = string.IsNullOrWhiteSpace(config.SubtitleLanguagePreference) ? new string[] { } : new[] { config.SubtitleLanguagePreference }
- };
- }
-
- public void MergeInto(UserConfiguration config)
- {
-
- }
- }
-
- public class UserPreferencesDto<T>
- {
- public T data { get; set; }
- }
-
- public class ConnectAuthorizationInternal : ConnectAuthorization
- {
- public string AccessToken { get; set; }
- }
-}
diff --git a/Emby.Server.Implementations/Connect/Validator.cs b/Emby.Server.Implementations/Connect/Validator.cs
deleted file mode 100644
index 5c94fa71c..000000000
--- a/Emby.Server.Implementations/Connect/Validator.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System.Text.RegularExpressions;
-
-namespace Emby.Server.Implementations.Connect
-{
- public static class Validator
- {
- static readonly Regex ValidEmailRegex = CreateValidEmailRegex();
-
- /// <summary>
- /// Taken from http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx
- /// </summary>
- /// <returns></returns>
- private static Regex CreateValidEmailRegex()
- {
- const string validEmailPattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|"
- + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(?<!\.)\.)*)(?<!\.)"
- + @"@[a-z0-9][\w\.-]*[a-z0-9]\.[a-z][a-z\.]*[a-z]$";
-
- return new Regex(validEmailPattern, RegexOptions.IgnoreCase);
- }
-
- internal static bool EmailIsValid(string emailAddress)
- {
- bool isValid = ValidEmailRegex.IsMatch(emailAddress);
-
- return isValid;
- }
- }
-}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 696be80ed..4ee3df7f5 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -1031,7 +1031,7 @@ namespace Emby.Server.Implementations.Dto
if (fields.Contains(ItemFields.Path))
{
- dto.Path = GetMappedPath(item);
+ dto.Path = GetMappedPath(item, owner);
}
dto.PremiereDate = item.PremiereDate;
@@ -1566,7 +1566,7 @@ namespace Emby.Server.Implementations.Dto
}
}
- private string GetMappedPath(BaseItem item)
+ private string GetMappedPath(BaseItem item, BaseItem ownerItem)
{
var path = item.Path;
@@ -1574,7 +1574,7 @@ namespace Emby.Server.Implementations.Dto
if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
{
- path = _libraryManager.GetPathAfterNetworkSubstitution(path, item);
+ path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item);
}
return path;
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index fec0c2294..5514bb1b6 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -35,6 +35,9 @@
<Compile Include="Activity\ActivityLogEntryPoint.cs" />
<Compile Include="Activity\ActivityManager.cs" />
<Compile Include="Activity\ActivityRepository.cs" />
+ <Compile Include="AppBase\BaseApplicationPaths.cs" />
+ <Compile Include="AppBase\BaseConfigurationManager.cs" />
+ <Compile Include="AppBase\ConfigurationHelper.cs" />
<Compile Include="Branding\BrandingConfigurationFactory.cs" />
<Compile Include="Browser\BrowserLauncher.cs" />
<Compile Include="Channels\ChannelConfigurations.cs" />
@@ -46,11 +49,7 @@
<Compile Include="Collections\CollectionImageProvider.cs" />
<Compile Include="Collections\CollectionManager.cs" />
<Compile Include="Collections\CollectionsDynamicFolder.cs" />
- <Compile Include="Connect\ConnectData.cs" />
- <Compile Include="Connect\ConnectEntryPoint.cs" />
- <Compile Include="Connect\ConnectManager.cs" />
- <Compile Include="Connect\Responses.cs" />
- <Compile Include="Connect\Validator.cs" />
+ <Compile Include="Configuration\ServerConfigurationManager.cs" />
<Compile Include="Data\ManagedConnection.cs" />
<Compile Include="Data\SqliteDisplayPreferencesRepository.cs" />
<Compile Include="Data\SqliteFileOrganizationRepository.cs" />
@@ -179,6 +178,7 @@
<Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
<Compile Include="LiveTv\TunerHosts\QueueStream.cs" />
<Compile Include="Localization\LocalizationManager.cs" />
+ <Compile Include="Logging\UnhandledExceptionWriter.cs" />
<Compile Include="MediaEncoder\EncodingManager.cs" />
<Compile Include="Migrations\IVersionMigration.cs" />
<Compile Include="Migrations\LibraryScanMigration.cs" />
@@ -213,6 +213,7 @@
<Compile Include="Security\MBLicenseFile.cs" />
<Compile Include="Security\PluginSecurityManager.cs" />
<Compile Include="Security\RegRecord.cs" />
+ <Compile Include="ServerApplicationPaths.cs" />
<Compile Include="ServerManager\ServerManager.cs" />
<Compile Include="ServerManager\WebSocketConnection.cs" />
<Compile Include="Services\ServicePath.cs" />
diff --git a/Emby.Server.Implementations/Logging/UnhandledExceptionWriter.cs b/Emby.Server.Implementations/Logging/UnhandledExceptionWriter.cs
new file mode 100644
index 000000000..5183f3a0b
--- /dev/null
+++ b/Emby.Server.Implementations/Logging/UnhandledExceptionWriter.cs
@@ -0,0 +1,43 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Logging;
+using System;
+using System.IO;
+using MediaBrowser.Model.IO;
+
+namespace Emby.Server.Implementations.Logging
+{
+ public class UnhandledExceptionWriter
+ {
+ private readonly IApplicationPaths _appPaths;
+ private readonly ILogger _logger;
+ private readonly ILogManager _logManager;
+ private readonly IFileSystem _fileSystem;
+ private readonly IConsoleLogger _console;
+
+ public UnhandledExceptionWriter(IApplicationPaths appPaths, ILogger logger, ILogManager logManager, IFileSystem fileSystem, IConsoleLogger console)
+ {
+ _appPaths = appPaths;
+ _logger = logger;
+ _logManager = logManager;
+ _fileSystem = fileSystem;
+ _console = console;
+ }
+
+ public void Log(Exception ex)
+ {
+ _logger.ErrorException("UnhandledException", ex);
+ _logManager.Flush();
+
+ var path = Path.Combine(_appPaths.LogDirectoryPath, "unhandled_" + Guid.NewGuid() + ".txt");
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+
+ var builder = LogHelper.GetLogMessage(ex);
+
+ // Write to console just in case file logging fails
+ _console.WriteLine("UnhandledException");
+ _console.WriteLine(builder.ToString());
+
+ _fileSystem.WriteAllText(path, builder.ToString());
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs
new file mode 100644
index 000000000..b4b2bb139
--- /dev/null
+++ b/Emby.Server.Implementations/ServerApplicationPaths.cs
@@ -0,0 +1,234 @@
+using System;
+using System.IO;
+using Emby.Server.Implementations.AppBase;
+using MediaBrowser.Controller;
+
+namespace Emby.Server.Implementations
+{
+ /// <summary>
+ /// Extends BaseApplicationPaths to add paths that are only applicable on the server
+ /// </summary>
+ public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseApplicationPaths" /> class.
+ /// </summary>
+ public ServerApplicationPaths(string programDataPath, string appFolderPath, string applicationResourcesPath, Action<string> createDirectoryFn)
+ : base(programDataPath, appFolderPath, createDirectoryFn)
+ {
+ ApplicationResourcesPath = applicationResourcesPath;
+ }
+
+ public string ApplicationResourcesPath { get; private set; }
+
+ /// <summary>
+ /// Gets the path to the base root media directory
+ /// </summary>
+ /// <value>The root folder path.</value>
+ public string RootFolderPath
+ {
+ get
+ {
+ return Path.Combine(ProgramDataPath, "root");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the default user view directory. Used if no specific user view is defined.
+ /// </summary>
+ /// <value>The default user views path.</value>
+ public string DefaultUserViewsPath
+ {
+ get
+ {
+ return Path.Combine(RootFolderPath, "default");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to localization data.
+ /// </summary>
+ /// <value>The localization path.</value>
+ public string LocalizationPath
+ {
+ get
+ {
+ return Path.Combine(ProgramDataPath, "localization");
+ }
+ }
+
+ /// <summary>
+ /// The _ibn path
+ /// </summary>
+ private string _ibnPath;
+ /// <summary>
+ /// Gets the path to the Images By Name directory
+ /// </summary>
+ /// <value>The images by name path.</value>
+ public string ItemsByNamePath
+ {
+ get
+ {
+ return _ibnPath ?? (_ibnPath = Path.Combine(ProgramDataPath, "ImagesByName"));
+ }
+ set
+ {
+ _ibnPath = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the People directory
+ /// </summary>
+ /// <value>The people path.</value>
+ public string PeoplePath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "People");
+ }
+ }
+
+ public string ArtistsPath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "artists");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the Genre directory
+ /// </summary>
+ /// <value>The genre path.</value>
+ public string GenrePath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "Genre");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the Genre directory
+ /// </summary>
+ /// <value>The genre path.</value>
+ public string MusicGenrePath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "MusicGenre");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the Studio directory
+ /// </summary>
+ /// <value>The studio path.</value>
+ public string StudioPath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "Studio");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the Year directory
+ /// </summary>
+ /// <value>The year path.</value>
+ public string YearPath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "Year");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the General IBN directory
+ /// </summary>
+ /// <value>The general path.</value>
+ public string GeneralPath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "general");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the Ratings IBN directory
+ /// </summary>
+ /// <value>The ratings path.</value>
+ public string RatingsPath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "ratings");
+ }
+ }
+
+ /// <summary>
+ /// Gets the media info images path.
+ /// </summary>
+ /// <value>The media info images path.</value>
+ public string MediaInfoImagesPath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "mediainfo");
+ }
+ }
+
+ /// <summary>
+ /// Gets the path to the user configuration directory
+ /// </summary>
+ /// <value>The user configuration directory path.</value>
+ public string UserConfigurationDirectoryPath
+ {
+ get
+ {
+ return Path.Combine(ConfigurationDirectoryPath, "users");
+ }
+ }
+
+ private string _transcodingTempPath;
+ public string TranscodingTempPath
+ {
+ get
+ {
+ return _transcodingTempPath ?? (_transcodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp"));
+ }
+ set
+ {
+ _transcodingTempPath = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the game genre path.
+ /// </summary>
+ /// <value>The game genre path.</value>
+ public string GameGenrePath
+ {
+ get
+ {
+ return Path.Combine(ItemsByNamePath, "GameGenre");
+ }
+ }
+
+ private string _internalMetadataPath;
+ public string InternalMetadataPath
+ {
+ get
+ {
+ return _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
+ }
+ set
+ {
+ _internalMetadataPath = value;
+ }
+ }
+ }
+}