From 0ee1a0d7bd827e53351ee5e4ad21c4dda258362d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 18 Feb 2017 22:46:09 -0500 Subject: fix mapping multiple tuner channels to same epg channel --- MediaBrowser.ServerApplication/WindowsAppHost.cs | 106 +---------------------- 1 file changed, 1 insertion(+), 105 deletions(-) (limited to 'MediaBrowser.ServerApplication/WindowsAppHost.cs') diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index ec66923aa..9d19525b4 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -27,38 +27,6 @@ namespace MediaBrowser.ServerApplication get { return MainStartup.IsRunningAsService; } } - protected override FFMpegInstallInfo GetFfmpegInstallInfo() - { - var info = new FFMpegInstallInfo(); - - info.FFMpegFilename = "ffmpeg.exe"; - info.FFProbeFilename = "ffprobe.exe"; - info.Version = "20160410"; - info.ArchiveType = "7z"; - info.DownloadUrls = GetDownloadUrls(); - - return info; - } - - private string[] GetDownloadUrls() - { - switch (EnvironmentInfo.SystemArchitecture) - { - case Architecture.X64: - return new[] - { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win64.7z" - }; - case Architecture.X86: - return new[] - { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win32.7z" - }; - } - - return new string[] { }; - } - protected override void RestartInternal() { MainStartup.Restart(); @@ -94,7 +62,7 @@ namespace MediaBrowser.ServerApplication protected override void ConfigureAutoRunInternal(bool autorun) { - var startupPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Startup); + var startupPath = Environment.GetFolderPath(System.Environment.SpecialFolder.Startup); if (autorun && !MainStartup.IsRunningAsService) { @@ -121,14 +89,6 @@ namespace MediaBrowser.ServerApplication } } - protected override bool SupportsDualModeSockets - { - get - { - return true; - } - } - protected override void EnableLoopbackInternal(string appName) { LoopUtil.Run(appName); @@ -150,14 +110,6 @@ namespace MediaBrowser.ServerApplication } } - public override bool SupportsAutoRunAtStartup - { - get - { - return true; - } - } - public override bool CanSelfUpdate { get @@ -165,61 +117,5 @@ namespace MediaBrowser.ServerApplication return MainStartup.CanSelfUpdate; } } - - public bool PortsRequireAuthorization(string applicationPath) - { - var appNameSrch = Path.GetFileName(applicationPath); - - var startInfo = new ProcessStartInfo - { - FileName = "netsh", - - Arguments = "advfirewall firewall show rule \"" + appNameSrch + "\"", - - CreateNoWindow = true, - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false, - RedirectStandardOutput = true - }; - - using (var process = Process.Start(startInfo)) - { - process.Start(); - - try - { - var data = process.StandardOutput.ReadToEnd() ?? string.Empty; - - if (data.IndexOf("Block", StringComparison.OrdinalIgnoreCase) != -1) - { - Logger.Info("Found potential windows firewall rule blocking Emby Server: " + data); - } - - //var parts = data.Split('\n'); - - //return parts.Length > 4; - //return Confirm(); - return false; - } - catch (Exception ex) - { - Logger.ErrorException("Error querying windows firewall", ex); - - // Hate having to do this - try - { - process.Kill(); - } - catch (Exception ex1) - { - Logger.ErrorException("Error killing process", ex1); - } - - throw; - } - } - } - } } -- cgit v1.2.3 From 5d55b36487b25b2efaf6923a3c069f4b0b59a449 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 20 Feb 2017 15:50:58 -0500 Subject: make more classes portable --- Emby.Common.Implementations/BaseApplicationHost.cs | 2 +- .../BaseApplicationPaths.cs | 174 --- .../Configuration/BaseConfigurationManager.cs | 329 ------ .../Configuration/ConfigurationHelper.cs | 60 - Emby.Server.Core/ApplicationHost.cs | 7 +- .../Configuration/ServerConfigurationManager.cs | 245 ---- Emby.Server.Core/Logging/ConsoleLogger.cs | 16 + Emby.Server.Core/ServerApplicationPaths.cs | 233 ---- Emby.Server.Core/UnhandledExceptionWriter.cs | 38 - .../AppBase/BaseApplicationPaths.cs | 178 +++ .../AppBase/BaseConfigurationManager.cs | 328 ++++++ .../AppBase/ConfigurationHelper.cs | 60 + .../Configuration/ServerConfigurationManager.cs | 245 ++++ Emby.Server.Implementations/Connect/ConnectData.cs | 36 - .../Connect/ConnectEntryPoint.cs | 218 ---- .../Connect/ConnectManager.cs | 1193 -------------------- Emby.Server.Implementations/Connect/Responses.cs | 85 -- Emby.Server.Implementations/Connect/Validator.cs | 29 - Emby.Server.Implementations/Dto/DtoService.cs | 6 +- .../Emby.Server.Implementations.csproj | 11 +- .../Logging/UnhandledExceptionWriter.cs | 43 + .../ServerApplicationPaths.cs | 234 ++++ MediaBrowser.Model/Logging/IConsoleLogger.cs | 7 + MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + .../MediaBrowser.Server.Mono.csproj | 3 + MediaBrowser.Server.Mono/MonoAppHost.cs | 9 +- MediaBrowser.Server.Mono/Program.cs | 12 +- MediaBrowser.ServerApplication/MainStartup.cs | 15 +- .../MediaBrowser.ServerApplication.csproj | 3 + MediaBrowser.ServerApplication/WindowsAppHost.cs | 8 + Nuget/MediaBrowser.Common.Internal.nuspec | 2 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 33 files changed, 1174 insertions(+), 2662 deletions(-) delete mode 100644 Emby.Common.Implementations/BaseApplicationPaths.cs delete mode 100644 Emby.Common.Implementations/Configuration/BaseConfigurationManager.cs delete mode 100644 Emby.Common.Implementations/Configuration/ConfigurationHelper.cs delete mode 100644 Emby.Server.Core/Configuration/ServerConfigurationManager.cs create mode 100644 Emby.Server.Core/Logging/ConsoleLogger.cs delete mode 100644 Emby.Server.Core/ServerApplicationPaths.cs delete mode 100644 Emby.Server.Core/UnhandledExceptionWriter.cs create mode 100644 Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs create mode 100644 Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs create mode 100644 Emby.Server.Implementations/AppBase/ConfigurationHelper.cs create mode 100644 Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs delete mode 100644 Emby.Server.Implementations/Connect/ConnectData.cs delete mode 100644 Emby.Server.Implementations/Connect/ConnectEntryPoint.cs delete mode 100644 Emby.Server.Implementations/Connect/ConnectManager.cs delete mode 100644 Emby.Server.Implementations/Connect/Responses.cs delete mode 100644 Emby.Server.Implementations/Connect/Validator.cs create mode 100644 Emby.Server.Implementations/Logging/UnhandledExceptionWriter.cs create mode 100644 Emby.Server.Implementations/ServerApplicationPaths.cs create mode 100644 MediaBrowser.Model/Logging/IConsoleLogger.cs (limited to 'MediaBrowser.ServerApplication/WindowsAppHost.cs') diff --git a/Emby.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs index f53433511..13bb85087 100644 --- a/Emby.Common.Implementations/BaseApplicationHost.cs +++ b/Emby.Common.Implementations/BaseApplicationHost.cs @@ -147,7 +147,7 @@ namespace Emby.Common.Implementations /// The configuration manager. protected IConfigurationManager ConfigurationManager { get; private set; } - protected IFileSystem FileSystemManager { get; private set; } + public IFileSystem FileSystemManager { get; private set; } protected IIsoManager IsoManager { get; private set; } diff --git a/Emby.Common.Implementations/BaseApplicationPaths.cs b/Emby.Common.Implementations/BaseApplicationPaths.cs deleted file mode 100644 index 8792778ba..000000000 --- a/Emby.Common.Implementations/BaseApplicationPaths.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System.IO; -using MediaBrowser.Common.Configuration; - -namespace Emby.Common.Implementations -{ - /// - /// 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. - /// - public abstract class BaseApplicationPaths : IApplicationPaths - { - /// - /// Initializes a new instance of the class. - /// - protected BaseApplicationPaths(string programDataPath, string appFolderPath) - { - ProgramDataPath = programDataPath; - ProgramSystemPath = appFolderPath; - } - - public string ProgramDataPath { get; private set; } - - /// - /// Gets the path to the system folder - /// - public string ProgramSystemPath { get; private set; } - - /// - /// The _data directory - /// - private string _dataDirectory; - /// - /// Gets the folder path to the data directory - /// - /// The data directory. - public string DataPath - { - get - { - if (_dataDirectory == null) - { - _dataDirectory = Path.Combine(ProgramDataPath, "data"); - - Directory.CreateDirectory(_dataDirectory); - } - - return _dataDirectory; - } - } - - /// - /// Gets the image cache path. - /// - /// The image cache path. - public string ImageCachePath - { - get - { - return Path.Combine(CachePath, "images"); - } - } - - /// - /// Gets the path to the plugin directory - /// - /// The plugins path. - public string PluginsPath - { - get - { - return Path.Combine(ProgramDataPath, "plugins"); - } - } - - /// - /// Gets the path to the plugin configurations directory - /// - /// The plugin configurations path. - public string PluginConfigurationsPath - { - get - { - return Path.Combine(PluginsPath, "configurations"); - } - } - - /// - /// Gets the path to where temporary update files will be stored - /// - /// The plugin configurations path. - public string TempUpdatePath - { - get - { - return Path.Combine(ProgramDataPath, "updates"); - } - } - - /// - /// Gets the path to the log directory - /// - /// The log directory path. - public string LogDirectoryPath - { - get - { - return Path.Combine(ProgramDataPath, "logs"); - } - } - - /// - /// Gets the path to the application configuration root directory - /// - /// The configuration directory path. - public string ConfigurationDirectoryPath - { - get - { - return Path.Combine(ProgramDataPath, "config"); - } - } - - /// - /// Gets the path to the system configuration file - /// - /// The system configuration file path. - public string SystemConfigurationFilePath - { - get - { - return Path.Combine(ConfigurationDirectoryPath, "system.xml"); - } - } - - /// - /// The _cache directory - /// - private string _cachePath; - /// - /// Gets the folder path to the cache directory - /// - /// The cache directory. - public string CachePath - { - get - { - if (string.IsNullOrEmpty(_cachePath)) - { - _cachePath = Path.Combine(ProgramDataPath, "cache"); - - Directory.CreateDirectory(_cachePath); - } - - return _cachePath; - } - set - { - _cachePath = value; - } - } - - /// - /// Gets the folder path to the temp directory within the cache folder - /// - /// The temp directory. - public string TempDirectory - { - get - { - return Path.Combine(CachePath, "temp"); - } - } - } -} diff --git a/Emby.Common.Implementations/Configuration/BaseConfigurationManager.cs b/Emby.Common.Implementations/Configuration/BaseConfigurationManager.cs deleted file mode 100644 index 27c9fe615..000000000 --- a/Emby.Common.Implementations/Configuration/BaseConfigurationManager.cs +++ /dev/null @@ -1,329 +0,0 @@ -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 Emby.Common.Implementations; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; - -namespace Emby.Common.Implementations.Configuration -{ - /// - /// Class BaseConfigurationManager - /// - public abstract class BaseConfigurationManager : IConfigurationManager - { - /// - /// Gets the type of the configuration. - /// - /// The type of the configuration. - protected abstract Type ConfigurationType { get; } - - /// - /// Occurs when [configuration updated]. - /// - public event EventHandler ConfigurationUpdated; - - /// - /// Occurs when [configuration updating]. - /// - public event EventHandler NamedConfigurationUpdating; - - /// - /// Occurs when [named configuration updated]. - /// - public event EventHandler NamedConfigurationUpdated; - - /// - /// Gets the logger. - /// - /// The logger. - protected ILogger Logger { get; private set; } - /// - /// Gets the XML serializer. - /// - /// The XML serializer. - protected IXmlSerializer XmlSerializer { get; private set; } - - /// - /// Gets or sets the application paths. - /// - /// The application paths. - public IApplicationPaths CommonApplicationPaths { get; private set; } - public readonly IFileSystem FileSystem; - - /// - /// The _configuration loaded - /// - private bool _configurationLoaded; - /// - /// The _configuration sync lock - /// - private object _configurationSyncLock = new object(); - /// - /// The _configuration - /// - private BaseApplicationConfiguration _configuration; - /// - /// Gets the system configuration - /// - /// The configuration. - 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 = { }; - - /// - /// Initializes a new instance of the class. - /// - /// The application paths. - /// The log manager. - /// The XML serializer. - 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 factories) - { - _configurationFactories = factories.ToArray(); - - _configurationStores = _configurationFactories - .SelectMany(i => i.GetConfigurations()) - .ToArray(); - } - - /// - /// Saves the configuration. - /// - public void SaveConfiguration() - { - Logger.Info("Saving system configuration"); - var path = CommonApplicationPaths.SystemConfigurationFilePath; - - FileSystem.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_configurationSyncLock) - { - XmlSerializer.SerializeToFile(CommonConfiguration, path); - } - - OnConfigurationUpdated(); - } - - /// - /// Called when [configuration updated]. - /// - protected virtual void OnConfigurationUpdated() - { - UpdateCachePath(); - - EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger); - } - - /// - /// Replaces the configuration. - /// - /// The new configuration. - /// newConfiguration - public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration) - { - if (newConfiguration == null) - { - throw new ArgumentNullException("newConfiguration"); - } - - ValidateCachePath(newConfiguration); - - CommonConfiguration = newConfiguration; - SaveConfiguration(); - } - - /// - /// Updates the items by name path. - /// - private void UpdateCachePath() - { - string cachePath; - - if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath)) - { - cachePath = null; - } - else - { - cachePath = Path.Combine(CommonConfiguration.CachePath, "cache"); - } - - ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath; - } - - /// - /// Replaces the cache path. - /// - /// The new configuration. - /// - 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 _configurations = new ConcurrentDictionary(); - - 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.Common.Implementations/Configuration/ConfigurationHelper.cs b/Emby.Common.Implementations/Configuration/ConfigurationHelper.cs deleted file mode 100644 index 0d43a651e..000000000 --- a/Emby.Common.Implementations/Configuration/ConfigurationHelper.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; - -namespace Emby.Common.Implementations.Configuration -{ - /// - /// Class ConfigurationHelper - /// - public static class ConfigurationHelper - { - /// - /// 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 - /// - /// The type. - /// The path. - /// The XML serializer. - /// System.Object. - 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.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index c3d88eeab..a8866fc97 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -83,7 +83,6 @@ using Emby.Dlna.MediaReceiverRegistrar; using Emby.Dlna.Ssdp; using Emby.Server.Core; using Emby.Server.Implementations.Activity; -using Emby.Server.Core.Configuration; using Emby.Server.Implementations.Devices; using Emby.Server.Implementations.FFMpeg; using Emby.Server.Core.IO; @@ -94,7 +93,6 @@ using Emby.Server.Implementations.Social; using Emby.Server.Implementations.Sync; using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; -using Emby.Server.Implementations.Connect; using Emby.Server.Implementations.Dto; using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.FileOrganization; @@ -134,6 +132,7 @@ using Emby.Drawing; using Emby.Server.Implementations.Migrations; using MediaBrowser.Model.Diagnostics; using Emby.Common.Implementations.Diagnostics; +using Emby.Server.Implementations.Configuration; namespace Emby.Server.Core { @@ -526,6 +525,8 @@ namespace Emby.Server.Core } } + protected abstract IConnectManager CreateConnectManager(); + /// /// Registers resources that classes will depend on /// @@ -635,7 +636,7 @@ namespace Emby.Server.Core var encryptionManager = new EncryptionManager(); RegisterSingleInstance(encryptionManager); - ConnectManager = new ConnectManager(LogManager.GetLogger("ConnectManager"), ApplicationPaths, JsonSerializer, encryptionManager, HttpClient, this, ServerConfigurationManager, UserManager, ProviderManager, SecurityManager, FileSystemManager); + ConnectManager = CreateConnectManager(); RegisterSingleInstance(ConnectManager); DeviceManager = new DeviceManager(new DeviceRepository(ApplicationPaths, JsonSerializer, LogManager.GetLogger("DeviceManager"), FileSystemManager), UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LogManager.GetLogger("DeviceManager"), NetworkManager); diff --git a/Emby.Server.Core/Configuration/ServerConfigurationManager.cs b/Emby.Server.Core/Configuration/ServerConfigurationManager.cs deleted file mode 100644 index eb3d8b9f9..000000000 --- a/Emby.Server.Core/Configuration/ServerConfigurationManager.cs +++ /dev/null @@ -1,245 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Emby.Common.Implementations.Configuration; -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.Core.Configuration -{ - /// - /// Class ServerConfigurationManager - /// - public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager - { - - /// - /// Initializes a new instance of the class. - /// - /// The application paths. - /// The log manager. - /// The XML serializer. - /// The file system. - public ServerConfigurationManager(IApplicationPaths applicationPaths, ILogManager logManager, IXmlSerializer xmlSerializer, IFileSystem fileSystem) - : base(applicationPaths, logManager, xmlSerializer, fileSystem) - { - UpdateMetadataPath(); - } - - public event EventHandler> ConfigurationUpdating; - - /// - /// Gets the type of the configuration. - /// - /// The type of the configuration. - protected override Type ConfigurationType - { - get { return typeof(ServerConfiguration); } - } - - /// - /// Gets the application paths. - /// - /// The application paths. - public IServerApplicationPaths ApplicationPaths - { - get { return (IServerApplicationPaths)CommonApplicationPaths; } - } - - /// - /// Gets the configuration. - /// - /// The configuration. - public ServerConfiguration Configuration - { - get { return (ServerConfiguration)CommonConfiguration; } - } - - /// - /// Called when [configuration updated]. - /// - protected override void OnConfigurationUpdated() - { - UpdateMetadataPath(); - - base.OnConfigurationUpdated(); - } - - public override void AddParts(IEnumerable factories) - { - base.AddParts(factories); - - UpdateTranscodingTempPath(); - } - - /// - /// Updates the metadata path. - /// - 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"); - } - - /// - /// Updates the transcoding temporary path. - /// - private void UpdateTranscodingTempPath() - { - var encodingConfig = this.GetConfiguration("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(); - } - } - - /// - /// Replaces the configuration. - /// - /// The new configuration. - /// - public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration) - { - var newConfig = (ServerConfiguration)newConfiguration; - - ValidateMetadataPath(newConfig); - ValidateSslCertificate(newConfig); - - EventHelper.FireEventIfNotNull(ConfigurationUpdating, this, new GenericEventArgs { Argument = newConfig }, Logger); - - base.ReplaceConfiguration(newConfiguration); - } - - - /// - /// Validates the SSL certificate. - /// - /// The new configuration. - /// - 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)); - } - } - } - - /// - /// Validates the metadata path. - /// - /// The new configuration. - /// - 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 DirectoryNotFoundException(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.Core/Logging/ConsoleLogger.cs b/Emby.Server.Core/Logging/ConsoleLogger.cs new file mode 100644 index 000000000..01eca7b7e --- /dev/null +++ b/Emby.Server.Core/Logging/ConsoleLogger.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Model.Logging; + +namespace Emby.Server.Core.Logging +{ + public class ConsoleLogger : IConsoleLogger + { + public void WriteLine(string message) + { + Console.WriteLine(message); + } + } +} diff --git a/Emby.Server.Core/ServerApplicationPaths.cs b/Emby.Server.Core/ServerApplicationPaths.cs deleted file mode 100644 index dc80b773c..000000000 --- a/Emby.Server.Core/ServerApplicationPaths.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System.IO; -using Emby.Common.Implementations; -using MediaBrowser.Controller; - -namespace Emby.Server.Core -{ - /// - /// Extends BaseApplicationPaths to add paths that are only applicable on the server - /// - public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths - { - /// - /// Initializes a new instance of the class. - /// - public ServerApplicationPaths(string programDataPath, string appFolderPath, string applicationResourcesPath) - : base(programDataPath, appFolderPath) - { - ApplicationResourcesPath = applicationResourcesPath; - } - - public string ApplicationResourcesPath { get; private set; } - - /// - /// Gets the path to the base root media directory - /// - /// The root folder path. - public string RootFolderPath - { - get - { - return Path.Combine(ProgramDataPath, "root"); - } - } - - /// - /// Gets the path to the default user view directory. Used if no specific user view is defined. - /// - /// The default user views path. - public string DefaultUserViewsPath - { - get - { - return Path.Combine(RootFolderPath, "default"); - } - } - - /// - /// Gets the path to localization data. - /// - /// The localization path. - public string LocalizationPath - { - get - { - return Path.Combine(ProgramDataPath, "localization"); - } - } - - /// - /// The _ibn path - /// - private string _ibnPath; - /// - /// Gets the path to the Images By Name directory - /// - /// The images by name path. - public string ItemsByNamePath - { - get - { - return _ibnPath ?? (_ibnPath = Path.Combine(ProgramDataPath, "ImagesByName")); - } - set - { - _ibnPath = value; - } - } - - /// - /// Gets the path to the People directory - /// - /// The people path. - public string PeoplePath - { - get - { - return Path.Combine(ItemsByNamePath, "People"); - } - } - - public string ArtistsPath - { - get - { - return Path.Combine(ItemsByNamePath, "artists"); - } - } - - /// - /// Gets the path to the Genre directory - /// - /// The genre path. - public string GenrePath - { - get - { - return Path.Combine(ItemsByNamePath, "Genre"); - } - } - - /// - /// Gets the path to the Genre directory - /// - /// The genre path. - public string MusicGenrePath - { - get - { - return Path.Combine(ItemsByNamePath, "MusicGenre"); - } - } - - /// - /// Gets the path to the Studio directory - /// - /// The studio path. - public string StudioPath - { - get - { - return Path.Combine(ItemsByNamePath, "Studio"); - } - } - - /// - /// Gets the path to the Year directory - /// - /// The year path. - public string YearPath - { - get - { - return Path.Combine(ItemsByNamePath, "Year"); - } - } - - /// - /// Gets the path to the General IBN directory - /// - /// The general path. - public string GeneralPath - { - get - { - return Path.Combine(ItemsByNamePath, "general"); - } - } - - /// - /// Gets the path to the Ratings IBN directory - /// - /// The ratings path. - public string RatingsPath - { - get - { - return Path.Combine(ItemsByNamePath, "ratings"); - } - } - - /// - /// Gets the media info images path. - /// - /// The media info images path. - public string MediaInfoImagesPath - { - get - { - return Path.Combine(ItemsByNamePath, "mediainfo"); - } - } - - /// - /// Gets the path to the user configuration directory - /// - /// The user configuration directory path. - 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; - } - } - - /// - /// Gets the game genre path. - /// - /// The game genre path. - 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; - } - } - } -} diff --git a/Emby.Server.Core/UnhandledExceptionWriter.cs b/Emby.Server.Core/UnhandledExceptionWriter.cs deleted file mode 100644 index 5147be9e7..000000000 --- a/Emby.Server.Core/UnhandledExceptionWriter.cs +++ /dev/null @@ -1,38 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Logging; -using System; -using System.IO; - -namespace Emby.Server.Core -{ - public class UnhandledExceptionWriter - { - private readonly IApplicationPaths _appPaths; - private readonly ILogger _logger; - private readonly ILogManager _logManager; - - public UnhandledExceptionWriter(IApplicationPaths appPaths, ILogger logger, ILogManager logManager) - { - _appPaths = appPaths; - _logger = logger; - _logManager = logManager; - } - - public void Log(Exception ex) - { - _logger.ErrorException("UnhandledException", ex); - _logManager.Flush(); - - var path = Path.Combine(_appPaths.LogDirectoryPath, "unhandled_" + Guid.NewGuid() + ".txt"); - Directory.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()); - - File.WriteAllText(path, builder.ToString()); - } - } -} 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 +{ + /// + /// 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. + /// + public abstract class BaseApplicationPaths : IApplicationPaths + { + /// + /// Initializes a new instance of the class. + /// + protected BaseApplicationPaths(string programDataPath, string appFolderPath, Action createDirectoryFn) + { + ProgramDataPath = programDataPath; + ProgramSystemPath = appFolderPath; + CreateDirectoryFn = createDirectoryFn; + } + + protected Action CreateDirectoryFn; + + public string ProgramDataPath { get; private set; } + + /// + /// Gets the path to the system folder + /// + public string ProgramSystemPath { get; private set; } + + /// + /// The _data directory + /// + private string _dataDirectory; + /// + /// Gets the folder path to the data directory + /// + /// The data directory. + public string DataPath + { + get + { + if (_dataDirectory == null) + { + _dataDirectory = Path.Combine(ProgramDataPath, "data"); + + CreateDirectoryFn(_dataDirectory); + } + + return _dataDirectory; + } + } + + /// + /// Gets the image cache path. + /// + /// The image cache path. + public string ImageCachePath + { + get + { + return Path.Combine(CachePath, "images"); + } + } + + /// + /// Gets the path to the plugin directory + /// + /// The plugins path. + public string PluginsPath + { + get + { + return Path.Combine(ProgramDataPath, "plugins"); + } + } + + /// + /// Gets the path to the plugin configurations directory + /// + /// The plugin configurations path. + public string PluginConfigurationsPath + { + get + { + return Path.Combine(PluginsPath, "configurations"); + } + } + + /// + /// Gets the path to where temporary update files will be stored + /// + /// The plugin configurations path. + public string TempUpdatePath + { + get + { + return Path.Combine(ProgramDataPath, "updates"); + } + } + + /// + /// Gets the path to the log directory + /// + /// The log directory path. + public string LogDirectoryPath + { + get + { + return Path.Combine(ProgramDataPath, "logs"); + } + } + + /// + /// Gets the path to the application configuration root directory + /// + /// The configuration directory path. + public string ConfigurationDirectoryPath + { + get + { + return Path.Combine(ProgramDataPath, "config"); + } + } + + /// + /// Gets the path to the system configuration file + /// + /// The system configuration file path. + public string SystemConfigurationFilePath + { + get + { + return Path.Combine(ConfigurationDirectoryPath, "system.xml"); + } + } + + /// + /// The _cache directory + /// + private string _cachePath; + /// + /// Gets the folder path to the cache directory + /// + /// The cache directory. + public string CachePath + { + get + { + if (string.IsNullOrEmpty(_cachePath)) + { + _cachePath = Path.Combine(ProgramDataPath, "cache"); + + CreateDirectoryFn(_cachePath); + } + + return _cachePath; + } + set + { + _cachePath = value; + } + } + + /// + /// Gets the folder path to the temp directory within the cache folder + /// + /// The temp directory. + 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 +{ + /// + /// Class BaseConfigurationManager + /// + public abstract class BaseConfigurationManager : IConfigurationManager + { + /// + /// Gets the type of the configuration. + /// + /// The type of the configuration. + protected abstract Type ConfigurationType { get; } + + /// + /// Occurs when [configuration updated]. + /// + public event EventHandler ConfigurationUpdated; + + /// + /// Occurs when [configuration updating]. + /// + public event EventHandler NamedConfigurationUpdating; + + /// + /// Occurs when [named configuration updated]. + /// + public event EventHandler NamedConfigurationUpdated; + + /// + /// Gets the logger. + /// + /// The logger. + protected ILogger Logger { get; private set; } + /// + /// Gets the XML serializer. + /// + /// The XML serializer. + protected IXmlSerializer XmlSerializer { get; private set; } + + /// + /// Gets or sets the application paths. + /// + /// The application paths. + public IApplicationPaths CommonApplicationPaths { get; private set; } + public readonly IFileSystem FileSystem; + + /// + /// The _configuration loaded + /// + private bool _configurationLoaded; + /// + /// The _configuration sync lock + /// + private object _configurationSyncLock = new object(); + /// + /// The _configuration + /// + private BaseApplicationConfiguration _configuration; + /// + /// Gets the system configuration + /// + /// The configuration. + 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 = { }; + + /// + /// Initializes a new instance of the class. + /// + /// The application paths. + /// The log manager. + /// The XML serializer. + 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 factories) + { + _configurationFactories = factories.ToArray(); + + _configurationStores = _configurationFactories + .SelectMany(i => i.GetConfigurations()) + .ToArray(); + } + + /// + /// Saves the configuration. + /// + public void SaveConfiguration() + { + Logger.Info("Saving system configuration"); + var path = CommonApplicationPaths.SystemConfigurationFilePath; + + FileSystem.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_configurationSyncLock) + { + XmlSerializer.SerializeToFile(CommonConfiguration, path); + } + + OnConfigurationUpdated(); + } + + /// + /// Called when [configuration updated]. + /// + protected virtual void OnConfigurationUpdated() + { + UpdateCachePath(); + + EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger); + } + + /// + /// Replaces the configuration. + /// + /// The new configuration. + /// newConfiguration + public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration) + { + if (newConfiguration == null) + { + throw new ArgumentNullException("newConfiguration"); + } + + ValidateCachePath(newConfiguration); + + CommonConfiguration = newConfiguration; + SaveConfiguration(); + } + + /// + /// Updates the items by name path. + /// + private void UpdateCachePath() + { + string cachePath; + + if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath)) + { + cachePath = null; + } + else + { + cachePath = Path.Combine(CommonConfiguration.CachePath, "cache"); + } + + ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath; + } + + /// + /// Replaces the cache path. + /// + /// The new configuration. + /// + 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 _configurations = new ConcurrentDictionary(); + + 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 +{ + /// + /// Class ConfigurationHelper + /// + public static class ConfigurationHelper + { + /// + /// 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 + /// + /// The type. + /// The path. + /// The XML serializer. + /// System.Object. + 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 +{ + /// + /// Class ServerConfigurationManager + /// + public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager + { + + /// + /// Initializes a new instance of the class. + /// + /// The application paths. + /// The log manager. + /// The XML serializer. + /// The file system. + public ServerConfigurationManager(IApplicationPaths applicationPaths, ILogManager logManager, IXmlSerializer xmlSerializer, IFileSystem fileSystem) + : base(applicationPaths, logManager, xmlSerializer, fileSystem) + { + UpdateMetadataPath(); + } + + public event EventHandler> ConfigurationUpdating; + + /// + /// Gets the type of the configuration. + /// + /// The type of the configuration. + protected override Type ConfigurationType + { + get { return typeof(ServerConfiguration); } + } + + /// + /// Gets the application paths. + /// + /// The application paths. + public IServerApplicationPaths ApplicationPaths + { + get { return (IServerApplicationPaths)CommonApplicationPaths; } + } + + /// + /// Gets the configuration. + /// + /// The configuration. + public ServerConfiguration Configuration + { + get { return (ServerConfiguration)CommonConfiguration; } + } + + /// + /// Called when [configuration updated]. + /// + protected override void OnConfigurationUpdated() + { + UpdateMetadataPath(); + + base.OnConfigurationUpdated(); + } + + public override void AddParts(IEnumerable factories) + { + base.AddParts(factories); + + UpdateTranscodingTempPath(); + } + + /// + /// Updates the metadata path. + /// + 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"); + } + + /// + /// Updates the transcoding temporary path. + /// + private void UpdateTranscodingTempPath() + { + var encodingConfig = this.GetConfiguration("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(); + } + } + + /// + /// Replaces the configuration. + /// + /// The new configuration. + /// + public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration) + { + var newConfig = (ServerConfiguration)newConfiguration; + + ValidateMetadataPath(newConfig); + ValidateSslCertificate(newConfig); + + EventHelper.FireEventIfNotNull(ConfigurationUpdating, this, new GenericEventArgs { Argument = newConfig }, Logger); + + base.ReplaceConfiguration(newConfiguration); + } + + + /// + /// Validates the SSL certificate. + /// + /// The new configuration. + /// + 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)); + } + } + } + + /// + /// Validates the metadata path. + /// + /// The new configuration. + /// + 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 - { - /// - /// Gets or sets the server identifier. - /// - /// The server identifier. - public string ServerId { get; set; } - /// - /// Gets or sets the access key. - /// - /// The access key. - public string AccessKey { get; set; } - - /// - /// Gets or sets the authorizations. - /// - /// The authorizations. - public List PendingAuthorizations { get; set; } - - /// - /// Gets or sets the last authorizations refresh. - /// - /// The last authorizations refresh. - public DateTime LastAuthorizationsRefresh { get; set; } - - public ConnectData() - { - PendingAuthorizations = new List(); - } - } -} 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 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 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 - { - {"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(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 - { - {"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(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 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 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 - { - {"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(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 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 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 - { - {"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(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 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 - { - {"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 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(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>(stream); - - await RefreshAuthorizations(list, refreshImages).ConfigureAwait(false); - } - } - catch (Exception ex) - { - _logger.ErrorException("Error refreshing server authorizations.", ex); - } - } - - private async Task RefreshAuthorizations(List 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(); - - 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 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> 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 - { - {"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 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 - { - {"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(response); - } - } - - public async Task 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 - { - 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(); - - /// - /// Taken from http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx - /// - /// - private static Regex CreateValidEmailRegex() - { - const string validEmailPattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" - + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(? + + + @@ -46,11 +49,7 @@ - - - - - + @@ -179,6 +178,7 @@ + @@ -213,6 +213,7 @@ + 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 +{ + /// + /// Extends BaseApplicationPaths to add paths that are only applicable on the server + /// + public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths + { + /// + /// Initializes a new instance of the class. + /// + public ServerApplicationPaths(string programDataPath, string appFolderPath, string applicationResourcesPath, Action createDirectoryFn) + : base(programDataPath, appFolderPath, createDirectoryFn) + { + ApplicationResourcesPath = applicationResourcesPath; + } + + public string ApplicationResourcesPath { get; private set; } + + /// + /// Gets the path to the base root media directory + /// + /// The root folder path. + public string RootFolderPath + { + get + { + return Path.Combine(ProgramDataPath, "root"); + } + } + + /// + /// Gets the path to the default user view directory. Used if no specific user view is defined. + /// + /// The default user views path. + public string DefaultUserViewsPath + { + get + { + return Path.Combine(RootFolderPath, "default"); + } + } + + /// + /// Gets the path to localization data. + /// + /// The localization path. + public string LocalizationPath + { + get + { + return Path.Combine(ProgramDataPath, "localization"); + } + } + + /// + /// The _ibn path + /// + private string _ibnPath; + /// + /// Gets the path to the Images By Name directory + /// + /// The images by name path. + public string ItemsByNamePath + { + get + { + return _ibnPath ?? (_ibnPath = Path.Combine(ProgramDataPath, "ImagesByName")); + } + set + { + _ibnPath = value; + } + } + + /// + /// Gets the path to the People directory + /// + /// The people path. + public string PeoplePath + { + get + { + return Path.Combine(ItemsByNamePath, "People"); + } + } + + public string ArtistsPath + { + get + { + return Path.Combine(ItemsByNamePath, "artists"); + } + } + + /// + /// Gets the path to the Genre directory + /// + /// The genre path. + public string GenrePath + { + get + { + return Path.Combine(ItemsByNamePath, "Genre"); + } + } + + /// + /// Gets the path to the Genre directory + /// + /// The genre path. + public string MusicGenrePath + { + get + { + return Path.Combine(ItemsByNamePath, "MusicGenre"); + } + } + + /// + /// Gets the path to the Studio directory + /// + /// The studio path. + public string StudioPath + { + get + { + return Path.Combine(ItemsByNamePath, "Studio"); + } + } + + /// + /// Gets the path to the Year directory + /// + /// The year path. + public string YearPath + { + get + { + return Path.Combine(ItemsByNamePath, "Year"); + } + } + + /// + /// Gets the path to the General IBN directory + /// + /// The general path. + public string GeneralPath + { + get + { + return Path.Combine(ItemsByNamePath, "general"); + } + } + + /// + /// Gets the path to the Ratings IBN directory + /// + /// The ratings path. + public string RatingsPath + { + get + { + return Path.Combine(ItemsByNamePath, "ratings"); + } + } + + /// + /// Gets the media info images path. + /// + /// The media info images path. + public string MediaInfoImagesPath + { + get + { + return Path.Combine(ItemsByNamePath, "mediainfo"); + } + } + + /// + /// Gets the path to the user configuration directory + /// + /// The user configuration directory path. + 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; + } + } + + /// + /// Gets the game genre path. + /// + /// The game genre path. + 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; + } + } + } +} diff --git a/MediaBrowser.Model/Logging/IConsoleLogger.cs b/MediaBrowser.Model/Logging/IConsoleLogger.cs new file mode 100644 index 000000000..a8c282d65 --- /dev/null +++ b/MediaBrowser.Model/Logging/IConsoleLogger.cs @@ -0,0 +1,7 @@ +namespace MediaBrowser.Model.Logging +{ + public interface IConsoleLogger + { + void WriteLine(string message); + } +} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index c50ee984e..b796effa1 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -135,6 +135,7 @@ + diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index 27001d596..7dd92b5e8 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -61,6 +61,9 @@ ..\ThirdParty\emby\Emby.Common.Implementations.dll + + ..\ThirdParty\emby\Emby.Server.Connect.dll + ..\ThirdParty\emby\Emby.Server.Core.dll diff --git a/MediaBrowser.Server.Mono/MonoAppHost.cs b/MediaBrowser.Server.Mono/MonoAppHost.cs index 93ced1186..32f6b74ce 100644 --- a/MediaBrowser.Server.Mono/MonoAppHost.cs +++ b/MediaBrowser.Server.Mono/MonoAppHost.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; using System.Reflection; +using Emby.Server.Connect; using Emby.Server.Core; using Emby.Server.Implementations; -using Emby.Server.Implementations.FFMpeg; +using MediaBrowser.Controller.Connect; using MediaBrowser.IsoMounter; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -26,6 +27,11 @@ namespace MediaBrowser.Server.Mono } } + protected override IConnectManager CreateConnectManager() + { + return new ConnectManager(); + } + protected override void RestartInternal() { MainClass.Restart(StartupOptions); @@ -46,6 +52,7 @@ namespace MediaBrowser.Server.Mono var list = new List(); list.Add(typeof(LinuxIsoManager).Assembly); + list.Add(typeof(ConnectManager).Assembly); return list; } diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index 4790378a9..649283410 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -16,8 +16,11 @@ using Emby.Common.Implementations.Logging; using Emby.Common.Implementations.Networking; using Emby.Common.Implementations.Security; using Emby.Server.Core; +using Emby.Server.Core.Logging; using Emby.Server.Implementations; using Emby.Server.Implementations.IO; +using Emby.Server.Implementations.Logging; +using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using MediaBrowser.Server.Startup.Common.IO; using Mono.Unix.Native; @@ -32,6 +35,7 @@ namespace MediaBrowser.Server.Mono private static ApplicationHost _appHost; private static ILogger _logger; + private static IFileSystem FileSystem; public static void Main(string[] args) { @@ -98,7 +102,9 @@ namespace MediaBrowser.Server.Mono var appFolderPath = Path.GetDirectoryName(applicationPath); - return new ServerApplicationPaths(programDataPath, appFolderPath, Path.GetDirectoryName(applicationPath)); + Action createDirectoryFn = s => Directory.CreateDirectory(s); + + return new ServerApplicationPaths(programDataPath, appFolderPath, Path.GetDirectoryName(applicationPath), createDirectoryFn); } private static readonly TaskCompletionSource ApplicationTaskCompletionSource = new TaskCompletionSource(); @@ -111,6 +117,8 @@ namespace MediaBrowser.Server.Mono var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), false, false, appPaths.TempDirectory); fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + FileSystem = fileSystem; + var environmentInfo = GetEnvironmentInfo(); var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); @@ -247,7 +255,7 @@ namespace MediaBrowser.Server.Mono { var exception = (Exception)e.ExceptionObject; - new UnhandledExceptionWriter(_appHost.ServerConfigurationManager.ApplicationPaths, _logger, _appHost.LogManager).Log(exception); + new UnhandledExceptionWriter(_appHost.ServerConfigurationManager.ApplicationPaths, _logger, _appHost.LogManager, FileSystem, new ConsoleLogger()).Log(exception); if (!Debugger.IsAttached) { diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index c42cd0396..b41e7607c 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -23,11 +23,14 @@ using Emby.Common.Implementations.Logging; using Emby.Common.Implementations.Networking; using Emby.Common.Implementations.Security; using Emby.Server.Core; +using Emby.Server.Core.Logging; using Emby.Server.Implementations; using Emby.Server.Implementations.Browser; using Emby.Server.Implementations.IO; +using Emby.Server.Implementations.Logging; using ImageMagickSharp; using MediaBrowser.Common.Net; +using MediaBrowser.Model.IO; using MediaBrowser.Server.Startup.Common.IO; namespace MediaBrowser.ServerApplication @@ -47,6 +50,8 @@ namespace MediaBrowser.ServerApplication public static string ApplicationPath; + private static IFileSystem FileSystem; + public static bool TryGetLocalFromUncDirectory(string local, out string unc) { if ((local == null) || (local == "")) @@ -259,16 +264,18 @@ namespace MediaBrowser.ServerApplication var resourcesPath = Path.GetDirectoryName(applicationPath); + Action createDirectoryFn = s => Directory.CreateDirectory(s); + if (runAsService) { var systemPath = Path.GetDirectoryName(applicationPath); var programDataPath = Path.GetDirectoryName(systemPath); - return new ServerApplicationPaths(programDataPath, appFolderPath, resourcesPath); + return new ServerApplicationPaths(programDataPath, appFolderPath, resourcesPath, createDirectoryFn); } - return new ServerApplicationPaths(ApplicationPathHelper.GetProgramDataPath(applicationPath), appFolderPath, resourcesPath); + return new ServerApplicationPaths(ApplicationPathHelper.GetProgramDataPath(applicationPath), appFolderPath, resourcesPath, createDirectoryFn); } /// @@ -330,6 +337,8 @@ namespace MediaBrowser.ServerApplication var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); + FileSystem = fileSystem; + _appHost = new WindowsAppHost(appPaths, logManager, options, @@ -580,7 +589,7 @@ namespace MediaBrowser.ServerApplication { var exception = (Exception)e.ExceptionObject; - new UnhandledExceptionWriter(_appHost.ServerConfigurationManager.ApplicationPaths, _logger, _appHost.LogManager).Log(exception); + new UnhandledExceptionWriter(_appHost.ServerConfigurationManager.ApplicationPaths, _logger, _appHost.LogManager, FileSystem, new ConsoleLogger()).Log(exception); if (!IsRunningAsService) { diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 8a75bf67a..656b295c2 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -67,6 +67,9 @@ ..\ThirdParty\emby\Emby.Common.Implementations.dll + + ..\ThirdParty\emby\Emby.Server.Connect.dll + ..\ThirdParty\emby\Emby.Server.Core.dll diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index 9d19525b4..d4753f57a 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -4,10 +4,12 @@ using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.InteropServices.ComTypes; +using Emby.Server.Connect; using Emby.Server.Core; using Emby.Server.Implementations; using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.FFMpeg; +using MediaBrowser.Controller.Connect; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.System; @@ -27,6 +29,11 @@ namespace MediaBrowser.ServerApplication get { return MainStartup.IsRunningAsService; } } + protected override IConnectManager CreateConnectManager() + { + return new ConnectManager(); + } + protected override void RestartInternal() { MainStartup.Restart(); @@ -41,6 +48,7 @@ namespace MediaBrowser.ServerApplication //list.Add(typeof(PismoIsoManager).Assembly); } + list.Add(typeof(ConnectManager).Assembly); list.Add(GetType().Assembly); return list; diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 292c80a7c..fdc2b9f7d 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.680 + 3.0.681 Emby.Common.Internal Luke ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 4bb58cd73..95cee8be1 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.694 + 3.0.695 Emby.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index c475a4c91..f703690a9 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.694 + 3.0.695 Emby.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + -- cgit v1.2.3 From e391ee1b1724c44c3a12bca24f3dff9389de4b8b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 23 Feb 2017 14:13:07 -0500 Subject: update components --- Emby.Server.Core/ApplicationHost.cs | 17 +- .../Emby.Server.Implementations.csproj | 19 +- .../EntryPoints/UsageReporter.cs | 3 +- .../Security/PluginSecurityManager.cs | 3 +- .../Sync/AppSyncProvider.cs | 118 -- .../Sync/CloudSyncProfile.cs | 288 ---- .../Sync/IHasSyncQuality.cs | 31 - Emby.Server.Implementations/Sync/MediaSync.cs | 500 ------- .../Sync/MultiProviderSync.cs | 79 -- .../Sync/ServerSyncScheduledTask.cs | 95 -- Emby.Server.Implementations/Sync/SyncConfig.cs | 29 - .../Sync/SyncConvertScheduledTask.cs | 89 -- Emby.Server.Implementations/Sync/SyncHelper.cs | 24 - Emby.Server.Implementations/Sync/SyncJobOptions.cs | 18 - .../Sync/SyncJobProcessor.cs | 998 -------------- Emby.Server.Implementations/Sync/SyncManager.cs | 1372 -------------------- .../Sync/SyncNotificationEntryPoint.cs | 60 - .../Sync/SyncRegistrationInfo.cs | 31 - Emby.Server.Implementations/Sync/SyncRepository.cs | 847 ------------ .../Sync/SyncedMediaSourceProvider.cs | 158 --- .../Sync/TargetDataProvider.cs | 208 --- Emby.Server.Implementations/packages.config | 2 +- MediaBrowser.Api/ConnectService.cs | 177 --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 2 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 - MediaBrowser.Controller/LiveTv/ITunerHost.cs | 5 +- .../MediaBrowser.Server.Mono.csproj | 13 +- MediaBrowser.Server.Mono/MonoAppHost.cs | 8 + MediaBrowser.Server.Mono/app.config | 4 + MediaBrowser.Server.Mono/packages.config | 8 +- MediaBrowser.ServerApplication/App.config | 2 +- .../MediaBrowser.ServerApplication.csproj | 13 +- MediaBrowser.ServerApplication/WindowsAppHost.cs | 8 + MediaBrowser.ServerApplication/packages.config | 8 +- 34 files changed, 54 insertions(+), 5184 deletions(-) delete mode 100644 Emby.Server.Implementations/Sync/AppSyncProvider.cs delete mode 100644 Emby.Server.Implementations/Sync/CloudSyncProfile.cs delete mode 100644 Emby.Server.Implementations/Sync/IHasSyncQuality.cs delete mode 100644 Emby.Server.Implementations/Sync/MediaSync.cs delete mode 100644 Emby.Server.Implementations/Sync/MultiProviderSync.cs delete mode 100644 Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncConfig.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncConvertScheduledTask.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncHelper.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncJobOptions.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncJobProcessor.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncManager.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncNotificationEntryPoint.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncRegistrationInfo.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncRepository.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncedMediaSourceProvider.cs delete mode 100644 Emby.Server.Implementations/Sync/TargetDataProvider.cs delete mode 100644 MediaBrowser.Api/ConnectService.cs (limited to 'MediaBrowser.ServerApplication/WindowsAppHost.cs') diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 416155751..4425d1a0b 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -90,7 +90,6 @@ using Emby.Server.Core.Localization; using Emby.Server.Implementations.Migrations; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Social; -using Emby.Server.Implementations.Sync; using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Dto; @@ -109,7 +108,6 @@ using Emby.Server.Implementations; using Emby.Server.Implementations.ServerManager; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.Social; -using Emby.Server.Implementations.Sync; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using MediaBrowser.Model.Activity; @@ -526,6 +524,7 @@ namespace Emby.Server.Core } protected abstract IConnectManager CreateConnectManager(); + protected abstract ISyncManager CreateSyncManager(); /// /// Registers resources that classes will depend on @@ -587,9 +586,6 @@ namespace Emby.Server.Core AuthenticationRepository = await GetAuthenticationRepository().ConfigureAwait(false); RegisterSingleInstance(AuthenticationRepository); - SyncRepository = GetSyncRepository(); - RegisterSingleInstance(SyncRepository); - UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, () => ConnectManager, this, JsonSerializer, FileSystemManager, CryptographyProvider, _defaultUserNameFactory()); RegisterSingleInstance(UserManager); @@ -627,7 +623,7 @@ namespace Emby.Server.Core TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); RegisterSingleInstance(TVSeriesManager); - SyncManager = new SyncManager(LibraryManager, SyncRepository, ImageProcessor, LogManager.GetLogger("SyncManager"), UserManager, () => DtoService, this, TVSeriesManager, () => MediaEncoder, FileSystemManager, () => SubtitleEncoder, ServerConfigurationManager, UserDataManager, () => MediaSourceManager, JsonSerializer, TaskManager, MemoryStreamFactory); + SyncManager = CreateSyncManager(); RegisterSingleInstance(SyncManager); DtoService = new DtoService(LogManager.GetLogger("DtoService"), LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, SyncManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager); @@ -944,15 +940,6 @@ namespace Emby.Server.Core return repo; } - private ISyncRepository GetSyncRepository() - { - var repo = new SyncRepository(LogManager.GetLogger("SyncRepository"), JsonSerializer, ServerConfigurationManager.ApplicationPaths); - - repo.Initialize(); - - return repo; - } - /// /// Configures the repositories. /// diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 5514bb1b6..13efb7bf9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -264,23 +264,6 @@ - - - - - - - - - - - - - - - - - @@ -329,7 +312,7 @@ True - ..\packages\SQLitePCLRaw.core.1.1.1\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll True diff --git a/Emby.Server.Implementations/EntryPoints/UsageReporter.cs b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs index 31254c6c2..778c8a6ce 100644 --- a/Emby.Server.Implementations/EntryPoints/UsageReporter.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs @@ -40,8 +40,7 @@ namespace Emby.Server.Implementations.EntryPoints { "serverid", _applicationHost.SystemId }, { "deviceid", _applicationHost.SystemId }, { "ver", _applicationHost.ApplicationVersion.ToString() }, - { "platform", _applicationHost.OperatingSystemDisplayName }, - { "isservice", _applicationHost.IsRunningAsService.ToString().ToLower()} + { "platform", _applicationHost.OperatingSystemDisplayName } }; var users = _userManager.Users.ToList(); diff --git a/Emby.Server.Implementations/Security/PluginSecurityManager.cs b/Emby.Server.Implementations/Security/PluginSecurityManager.cs index f21259137..a26df7625 100644 --- a/Emby.Server.Implementations/Security/PluginSecurityManager.cs +++ b/Emby.Server.Implementations/Security/PluginSecurityManager.cs @@ -277,8 +277,7 @@ namespace Emby.Server.Implementations.Security { "systemid", _appHost.SystemId }, { "mb2equiv", mb2Equivalent }, { "ver", version }, - { "platform", _appHost.OperatingSystemDisplayName }, - { "isservice", _appHost.IsRunningAsService.ToString().ToLower() } + { "platform", _appHost.OperatingSystemDisplayName } }; try diff --git a/Emby.Server.Implementations/Sync/AppSyncProvider.cs b/Emby.Server.Implementations/Sync/AppSyncProvider.cs deleted file mode 100644 index d405a0ff9..000000000 --- a/Emby.Server.Implementations/Sync/AppSyncProvider.cs +++ /dev/null @@ -1,118 +0,0 @@ -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Emby.Server.Implementations.Sync -{ - public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds, IHasSyncQuality, IHasDuplicateCheck - { - private readonly IDeviceManager _deviceManager; - - public AppSyncProvider(IDeviceManager deviceManager) - { - _deviceManager = deviceManager; - } - - public IEnumerable GetSyncTargets(string userId) - { - return _deviceManager.GetDevices(new DeviceQuery - { - SupportsSync = true, - UserId = userId - - }).Items.Select(i => new SyncTarget - { - Id = i.Id, - Name = i.Name - }); - } - - public DeviceProfile GetDeviceProfile(SyncTarget target, string profile, string quality) - { - var caps = _deviceManager.GetCapabilities(target.Id); - - var deviceProfile = caps == null || caps.DeviceProfile == null ? new DeviceProfile() : caps.DeviceProfile; - deviceProfile.MaxStaticBitrate = SyncHelper.AdjustBitrate(deviceProfile.MaxStaticBitrate, quality); - - return deviceProfile; - } - - public string Name - { - get { return "Mobile Sync"; } - } - - public IEnumerable GetAllSyncTargets() - { - return _deviceManager.GetDevices(new DeviceQuery - { - SupportsSync = true - - }).Items.Select(i => new SyncTarget - { - Id = i.Id, - Name = i.Name - }); - } - - public IEnumerable GetQualityOptions(SyncTarget target) - { - return new List - { - new SyncQualityOption - { - Name = "Original", - Id = "original", - Description = "Syncs original files as-is, regardless of whether the device is capable of playing them or not." - }, - new SyncQualityOption - { - Name = "High", - Id = "high", - IsDefault = true - }, - new SyncQualityOption - { - Name = "Medium", - Id = "medium" - }, - new SyncQualityOption - { - Name = "Low", - Id = "low" - }, - new SyncQualityOption - { - Name = "Custom", - Id = "custom" - } - }; - } - - public IEnumerable GetProfileOptions(SyncTarget target) - { - return new List(); - } - - public SyncJobOptions GetSyncJobOptions(SyncTarget target, string profile, string quality) - { - var isConverting = !string.Equals(quality, "original", StringComparison.OrdinalIgnoreCase); - - return new SyncJobOptions - { - DeviceProfile = GetDeviceProfile(target, profile, quality), - IsConverting = isConverting - }; - } - - public bool AllowDuplicateJobItem(SyncJobItem original, SyncJobItem duplicate) - { - return false; - } - } -} diff --git a/Emby.Server.Implementations/Sync/CloudSyncProfile.cs b/Emby.Server.Implementations/Sync/CloudSyncProfile.cs deleted file mode 100644 index c0675df81..000000000 --- a/Emby.Server.Implementations/Sync/CloudSyncProfile.cs +++ /dev/null @@ -1,288 +0,0 @@ -using MediaBrowser.Model.Dlna; -using System.Collections.Generic; - -namespace Emby.Server.Implementations.Sync -{ - public class CloudSyncProfile : DeviceProfile - { - public CloudSyncProfile(bool supportsAc3, bool supportsDca) - { - Name = "Cloud Sync"; - - MaxStreamingBitrate = 20000000; - MaxStaticBitrate = 20000000; - - var mkvAudio = "aac,mp3"; - var mp4Audio = "aac"; - - if (supportsAc3) - { - mkvAudio += ",ac3"; - mp4Audio += ",ac3"; - } - - if (supportsDca) - { - mkvAudio += ",dca,dts"; - } - - var videoProfile = "high|main|baseline|constrained baseline"; - var videoLevel = "40"; - - DirectPlayProfiles = new[] - { - //new DirectPlayProfile - //{ - // Container = "mkv", - // VideoCodec = "h264,mpeg4", - // AudioCodec = mkvAudio, - // Type = DlnaProfileType.Video - //}, - new DirectPlayProfile - { - Container = "mp4,mov,m4v", - VideoCodec = "h264,mpeg4", - AudioCodec = mp4Audio, - Type = DlnaProfileType.Video - }, - new DirectPlayProfile - { - Container = "mp3", - Type = DlnaProfileType.Audio - } - }; - - ContainerProfiles = new[] - { - new ContainerProfile - { - Type = DlnaProfileType.Video, - Conditions = new [] - { - new ProfileCondition - { - Condition = ProfileConditionType.NotEquals, - Property = ProfileConditionValue.NumAudioStreams, - Value = "0", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.NumVideoStreams, - Value = "1", - IsRequired = false - } - } - } - }; - - var codecProfiles = new List - { - new CodecProfile - { - Type = CodecType.Video, - Codec = "h264", - Conditions = new [] - { - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoBitDepth, - Value = "8", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.Width, - Value = "1920", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.Height, - Value = "1080", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.RefFrames, - Value = "4", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoFramerate, - Value = "30", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.IsAnamorphic, - Value = "false", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoLevel, - Value = videoLevel, - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.EqualsAny, - Property = ProfileConditionValue.VideoProfile, - Value = videoProfile, - IsRequired = false - } - } - }, - new CodecProfile - { - Type = CodecType.Video, - Codec = "mpeg4", - Conditions = new [] - { - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoBitDepth, - Value = "8", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.Width, - Value = "1920", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.Height, - Value = "1080", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.RefFrames, - Value = "4", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoFramerate, - Value = "30", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.IsAnamorphic, - Value = "false", - IsRequired = false - } - } - } - }; - - codecProfiles.Add(new CodecProfile - { - Type = CodecType.VideoAudio, - Codec = "ac3", - Conditions = new[] - { - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.AudioChannels, - Value = "6", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.AudioBitrate, - Value = "320000", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.IsSecondaryAudio, - Value = "false", - IsRequired = false - } - } - }); - codecProfiles.Add(new CodecProfile - { - Type = CodecType.VideoAudio, - Codec = "aac,mp3", - Conditions = new[] - { - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.IsSecondaryAudio, - Value = "false", - IsRequired = false - } - } - }); - - CodecProfiles = codecProfiles.ToArray(); - - SubtitleProfiles = new[] - { - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.External - }, - new SubtitleProfile - { - Format = "vtt", - Method = SubtitleDeliveryMethod.External - } - }; - - TranscodingProfiles = new[] - { - new TranscodingProfile - { - Container = "mp3", - AudioCodec = "mp3", - Type = DlnaProfileType.Audio, - Context = EncodingContext.Static - }, - - new TranscodingProfile - { - Container = "mp4", - Type = DlnaProfileType.Video, - AudioCodec = "aac", - VideoCodec = "h264", - Context = EncodingContext.Static - }, - - new TranscodingProfile - { - Container = "jpeg", - Type = DlnaProfileType.Photo, - Context = EncodingContext.Static - } - }; - - } - } -} diff --git a/Emby.Server.Implementations/Sync/IHasSyncQuality.cs b/Emby.Server.Implementations/Sync/IHasSyncQuality.cs deleted file mode 100644 index bec8b37a7..000000000 --- a/Emby.Server.Implementations/Sync/IHasSyncQuality.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Model.Sync; -using System.Collections.Generic; - -namespace Emby.Server.Implementations.Sync -{ - public interface IHasSyncQuality - { - /// - /// Gets the device profile. - /// - /// The target. - /// The profile. - /// The quality. - /// DeviceProfile. - SyncJobOptions GetSyncJobOptions(SyncTarget target, string profile, string quality); - - /// - /// Gets the quality options. - /// - /// The target. - /// IEnumerable<SyncQualityOption>. - IEnumerable GetQualityOptions(SyncTarget target); - - /// - /// Gets the profile options. - /// - /// The target. - /// IEnumerable<SyncQualityOption>. - IEnumerable GetProfileOptions(SyncTarget target); - } -} diff --git a/Emby.Server.Implementations/Sync/MediaSync.cs b/Emby.Server.Implementations/Sync/MediaSync.cs deleted file mode 100644 index fa8388b6c..000000000 --- a/Emby.Server.Implementations/Sync/MediaSync.cs +++ /dev/null @@ -1,500 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.IO; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Sync -{ - public class MediaSync - { - private readonly ISyncManager _syncManager; - private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IConfigurationManager _config; - private readonly ICryptoProvider _cryptographyProvider; - - public const string PathSeparatorString = "/"; - public const char PathSeparatorChar = '/'; - - public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost, IFileSystem fileSystem, IConfigurationManager config, ICryptoProvider cryptographyProvider) - { - _logger = logger; - _syncManager = syncManager; - _appHost = appHost; - _fileSystem = fileSystem; - _config = config; - _cryptographyProvider = cryptographyProvider; - } - - public async Task Sync(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - SyncTarget target, - IProgress progress, - CancellationToken cancellationToken) - { - var serverId = _appHost.SystemId; - var serverName = _appHost.FriendlyName; - - await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); - progress.Report(3); - - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => - { - var totalProgress = pct * .97; - totalProgress += 1; - progress.Report(totalProgress); - }); - await GetNewMedia(provider, dataProvider, target, serverId, serverName, innerProgress, cancellationToken); - - // Do the data sync twice so the server knows what was removed from the device - await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); - - progress.Report(100); - } - - private async Task SyncData(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - string serverId, - SyncTarget target, - CancellationToken cancellationToken) - { - var localItems = await dataProvider.GetLocalItems(target, serverId).ConfigureAwait(false); - var remoteFiles = await provider.GetFiles(target, cancellationToken).ConfigureAwait(false); - var remoteIds = remoteFiles.Items.Select(i => i.FullName).ToList(); - - var jobItemIds = new List(); - - foreach (var localItem in localItems) - { - if (remoteIds.Contains(localItem.FileId, StringComparer.OrdinalIgnoreCase)) - { - jobItemIds.Add(localItem.SyncJobItemId); - } - } - - var result = await _syncManager.SyncData(new SyncDataRequest - { - TargetId = target.Id, - SyncJobItemIds = jobItemIds - - }).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - foreach (var itemIdToRemove in result.ItemIdsToRemove) - { - try - { - await RemoveItem(provider, dataProvider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting item from device. Id: {0}", ex, itemIdToRemove); - } - } - } - - private async Task GetNewMedia(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - SyncTarget target, - string serverId, - string serverName, - IProgress progress, - CancellationToken cancellationToken) - { - var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false); - - var numComplete = 0; - double startingPercent = 0; - double percentPerItem = 1; - if (jobItems.Count > 0) - { - percentPerItem /= jobItems.Count; - } - - foreach (var jobItem in jobItems) - { - cancellationToken.ThrowIfCancellationRequested(); - - var currentPercent = startingPercent; - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => - { - var totalProgress = pct * percentPerItem; - totalProgress += currentPercent; - progress.Report(totalProgress); - }); - - try - { - await GetItem(provider, dataProvider, target, serverId, serverName, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error syncing item", ex); - } - - numComplete++; - startingPercent = numComplete; - startingPercent /= jobItems.Count; - startingPercent *= 100; - progress.Report(startingPercent); - } - } - - private async Task GetItem(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - SyncTarget target, - string serverId, - string serverName, - SyncedItem jobItem, - IProgress progress, - CancellationToken cancellationToken) - { - var libraryItem = jobItem.Item; - var internalSyncJobItem = _syncManager.GetJobItem(jobItem.SyncJobItemId); - var internalSyncJob = _syncManager.GetJob(jobItem.SyncJobId); - - var localItem = CreateLocalItem(provider, jobItem, internalSyncJob, target, libraryItem, serverId, serverName, jobItem.OriginalFileName); - - await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id); - - var transferSuccess = false; - Exception transferException = null; - - var options = _config.GetSyncOptions(); - - try - { - var fileTransferProgress = new ActionableProgress(); - fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92)); - - var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem.LocalPath.Split(PathSeparatorChar), target, options, fileTransferProgress, cancellationToken).ConfigureAwait(false); - - if (localItem.Item.MediaSources != null) - { - var mediaSource = localItem.Item.MediaSources.FirstOrDefault(); - if (mediaSource != null) - { - mediaSource.Path = sendFileResult.Path; - mediaSource.Protocol = sendFileResult.Protocol; - mediaSource.RequiredHttpHeaders = sendFileResult.RequiredHttpHeaders; - mediaSource.SupportsTranscoding = false; - } - } - - localItem.FileId = sendFileResult.Id; - - // Create db record - await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false); - - if (localItem.Item.MediaSources != null) - { - var mediaSource = localItem.Item.MediaSources.FirstOrDefault(); - if (mediaSource != null) - { - await SendSubtitles(localItem, mediaSource, provider, dataProvider, target, options, cancellationToken).ConfigureAwait(false); - } - } - - progress.Report(92); - - transferSuccess = true; - - progress.Report(99); - } - catch (Exception ex) - { - _logger.ErrorException("Error transferring sync job file", ex); - transferException = ex; - } - - if (transferSuccess) - { - await _syncManager.ReportSyncJobItemTransferred(jobItem.SyncJobItemId).ConfigureAwait(false); - } - else - { - await _syncManager.ReportSyncJobItemTransferFailed(jobItem.SyncJobItemId).ConfigureAwait(false); - - throw transferException; - } - } - - private async Task SendSubtitles(LocalItem localItem, MediaSourceInfo mediaSource, IServerSyncProvider provider, ISyncDataProvider dataProvider, SyncTarget target, SyncOptions options, CancellationToken cancellationToken) - { - var failedSubtitles = new List(); - var requiresSave = false; - - foreach (var mediaStream in mediaSource.MediaStreams - .Where(i => i.Type == MediaStreamType.Subtitle && i.IsExternal) - .ToList()) - { - try - { - var remotePath = GetRemoteSubtitlePath(localItem, mediaStream, provider, target); - var sendFileResult = await SendFile(provider, mediaStream.Path, remotePath, target, options, new Progress(), cancellationToken).ConfigureAwait(false); - - // This is the path that will be used when talking to the provider - mediaStream.ExternalId = sendFileResult.Id; - - // Keep track of all additional files for cleanup later. - localItem.AdditionalFiles.Add(sendFileResult.Id); - - // This is the public path clients will use - mediaStream.Path = sendFileResult.Path; - requiresSave = true; - } - catch (Exception ex) - { - _logger.ErrorException("Error sending subtitle stream", ex); - failedSubtitles.Add(mediaStream); - } - } - - if (failedSubtitles.Count > 0) - { - mediaSource.MediaStreams = mediaSource.MediaStreams.Except(failedSubtitles).ToList(); - requiresSave = true; - } - - if (requiresSave) - { - await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false); - } - } - - private string[] GetRemoteSubtitlePath(LocalItem item, MediaStream stream, IServerSyncProvider provider, SyncTarget target) - { - var filename = GetSubtitleSaveFileName(item, stream.Language, stream.IsForced) + "." + stream.Codec.ToLower(); - - var pathParts = item.LocalPath.Split(PathSeparatorChar); - var list = pathParts.Take(pathParts.Length - 1).ToList(); - list.Add(filename); - - return list.ToArray(); - } - - private string GetSubtitleSaveFileName(LocalItem item, string language, bool isForced) - { - var path = item.LocalPath; - - var name = Path.GetFileNameWithoutExtension(path); - - if (!string.IsNullOrWhiteSpace(language)) - { - name += "." + language.ToLower(); - } - - if (isForced) - { - name += ".foreign"; - } - - return name; - } - - private async Task RemoveItem(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - string serverId, - string syncJobItemId, - SyncTarget target, - CancellationToken cancellationToken) - { - var localItems = await dataProvider.GetItemsBySyncJobItemId(target, serverId, syncJobItemId); - - foreach (var localItem in localItems) - { - var files = localItem.AdditionalFiles.ToList(); - - foreach (var file in files) - { - _logger.Debug("Removing {0} from {1}.", file, target.Name); - await provider.DeleteFile(file, target, cancellationToken).ConfigureAwait(false); - } - - _logger.Debug("Removing {0} from {1}.", localItem.FileId, target.Name); - await provider.DeleteFile(localItem.FileId, target, cancellationToken).ConfigureAwait(false); - - await dataProvider.Delete(target, localItem.Id).ConfigureAwait(false); - } - } - - private async Task SendFile(IServerSyncProvider provider, string inputPath, string[] pathParts, SyncTarget target, SyncOptions options, IProgress progress, CancellationToken cancellationToken) - { - _logger.Debug("Sending {0} to {1}. Remote path: {2}", inputPath, provider.Name, string.Join("/", pathParts)); - var supportsDirectCopy = provider as ISupportsDirectCopy; - if (supportsDirectCopy != null) - { - return await supportsDirectCopy.SendFile(inputPath, pathParts, target, progress, cancellationToken).ConfigureAwait(false); - } - - using (var fileStream = _fileSystem.GetFileStream(inputPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true)) - { - Stream stream = fileStream; - - if (options.UploadSpeedLimitBytes > 0 && provider is IRemoteSyncProvider) - { - stream = new ThrottledStream(stream, options.UploadSpeedLimitBytes); - } - - return await provider.SendFile(stream, pathParts, target, progress, cancellationToken).ConfigureAwait(false); - } - } - - private string GetLocalId(string jobItemId, string itemId) - { - var bytes = Encoding.UTF8.GetBytes(jobItemId + itemId); - bytes = CreateMd5(bytes); - return BitConverter.ToString(bytes, 0, bytes.Length).Replace("-", string.Empty); - } - - private byte[] CreateMd5(byte[] value) - { - return _cryptographyProvider.ComputeMD5(value); - } - - public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncJob job, SyncTarget target, BaseItemDto libraryItem, string serverId, string serverName, string originalFileName) - { - var path = GetDirectoryPath(provider, job, syncedItem, libraryItem, serverName); - path.Add(GetLocalFileName(provider, libraryItem, originalFileName)); - - var localPath = string.Join(PathSeparatorString, path.ToArray()); - - foreach (var mediaSource in libraryItem.MediaSources) - { - mediaSource.Path = localPath; - mediaSource.Protocol = MediaProtocol.File; - } - - return new LocalItem - { - Item = libraryItem, - ItemId = libraryItem.Id, - ServerId = serverId, - LocalPath = localPath, - Id = GetLocalId(syncedItem.SyncJobItemId, libraryItem.Id), - SyncJobItemId = syncedItem.SyncJobItemId - }; - } - - private List GetDirectoryPath(IServerSyncProvider provider, SyncJob job, SyncedItem syncedItem, BaseItemDto item, string serverName) - { - var parts = new List - { - serverName - }; - - var profileOption = _syncManager.GetProfileOptions(job.TargetId) - .FirstOrDefault(i => string.Equals(i.Id, job.Profile, StringComparison.OrdinalIgnoreCase)); - - string name; - - if (profileOption != null && !string.IsNullOrWhiteSpace(profileOption.Name)) - { - name = profileOption.Name; - - if (job.Bitrate.HasValue) - { - name += "-" + job.Bitrate.Value.ToString(CultureInfo.InvariantCulture); - } - else - { - var qualityOption = _syncManager.GetQualityOptions(job.TargetId) - .FirstOrDefault(i => string.Equals(i.Id, job.Quality, StringComparison.OrdinalIgnoreCase)); - - if (qualityOption != null && !string.IsNullOrWhiteSpace(qualityOption.Name)) - { - name += "-" + qualityOption.Name; - } - } - } - else - { - name = syncedItem.SyncJobName + "-" + syncedItem.SyncJobDateCreated - .ToLocalTime() - .ToString("g") - .Replace(" ", "-"); - } - - name = GetValidFilename(provider, name); - parts.Add(name); - - if (item.IsType("episode")) - { - parts.Add("TV"); - if (!string.IsNullOrWhiteSpace(item.SeriesName)) - { - parts.Add(item.SeriesName); - } - } - else if (item.IsVideo) - { - parts.Add("Videos"); - parts.Add(item.Name); - } - else if (item.IsAudio) - { - parts.Add("Music"); - - if (!string.IsNullOrWhiteSpace(item.AlbumArtist)) - { - parts.Add(item.AlbumArtist); - } - - if (!string.IsNullOrWhiteSpace(item.Album)) - { - parts.Add(item.Album); - } - } - else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) - { - parts.Add("Photos"); - - if (!string.IsNullOrWhiteSpace(item.Album)) - { - parts.Add(item.Album); - } - } - - return parts.Select(i => GetValidFilename(provider, i)).ToList(); - } - - private string GetLocalFileName(IServerSyncProvider provider, BaseItemDto item, string originalFileName) - { - var filename = originalFileName; - - if (string.IsNullOrWhiteSpace(filename)) - { - filename = item.Name; - } - - return GetValidFilename(provider, filename); - } - - private string GetValidFilename(IServerSyncProvider provider, string filename) - { - // We can always add this method to the sync provider if it's really needed - return _fileSystem.GetValidFilename(filename); - } - } -} diff --git a/Emby.Server.Implementations/Sync/MultiProviderSync.cs b/Emby.Server.Implementations/Sync/MultiProviderSync.cs deleted file mode 100644 index 8189b8550..000000000 --- a/Emby.Server.Implementations/Sync/MultiProviderSync.cs +++ /dev/null @@ -1,79 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Sync -{ - public class MultiProviderSync - { - private readonly SyncManager _syncManager; - private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IConfigurationManager _config; - private readonly ICryptoProvider _cryptographyProvider; - - public MultiProviderSync(SyncManager syncManager, IServerApplicationHost appHost, ILogger logger, IFileSystem fileSystem, IConfigurationManager config, ICryptoProvider cryptographyProvider) - { - _syncManager = syncManager; - _appHost = appHost; - _logger = logger; - _fileSystem = fileSystem; - _config = config; - _cryptographyProvider = cryptographyProvider; - } - - public async Task Sync(IEnumerable providers, IProgress progress, CancellationToken cancellationToken) - { - var targets = providers - .SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple(i, t))) - .ToList(); - - var numComplete = 0; - double startingPercent = 0; - double percentPerItem = 1; - if (targets.Count > 0) - { - percentPerItem /= targets.Count; - } - - foreach (var target in targets) - { - cancellationToken.ThrowIfCancellationRequested(); - - var currentPercent = startingPercent; - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => - { - var totalProgress = pct * percentPerItem; - totalProgress += currentPercent; - progress.Report(totalProgress); - }); - - var dataProvider = _syncManager.GetDataProvider(target.Item1, target.Item2); - - await new MediaSync(_logger, _syncManager, _appHost, _fileSystem, _config, _cryptographyProvider) - .Sync(target.Item1, dataProvider, target.Item2, innerProgress, cancellationToken) - .ConfigureAwait(false); - - numComplete++; - startingPercent = numComplete; - startingPercent /= targets.Count; - startingPercent *= 100; - progress.Report(startingPercent); - } - } - } -} diff --git a/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs b/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs deleted file mode 100644 index 09a0bfde4..000000000 --- a/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs +++ /dev/null @@ -1,95 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - class ServerSyncScheduledTask : IScheduledTask, IConfigurableScheduledTask - { - private readonly ISyncManager _syncManager; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IServerApplicationHost _appHost; - private readonly IConfigurationManager _config; - private readonly ICryptoProvider _cryptographyProvider; - - public ServerSyncScheduledTask(ISyncManager syncManager, ILogger logger, IFileSystem fileSystem, IServerApplicationHost appHost, IConfigurationManager config, ICryptoProvider cryptographyProvider) - { - _syncManager = syncManager; - _logger = logger; - _fileSystem = fileSystem; - _appHost = appHost; - _config = config; - _cryptographyProvider = cryptographyProvider; - } - - public string Name - { - get { return "Cloud & Folder Sync"; } - } - - public string Description - { - get { return "Sync media to the cloud"; } - } - - public string Category - { - get - { - return "Sync"; - } - } - - public Task Execute(CancellationToken cancellationToken, IProgress progress) - { - return new MultiProviderSync((SyncManager)_syncManager, _appHost, _logger, _fileSystem, _config, _cryptographyProvider) - .Sync(ServerSyncProviders, progress, cancellationToken); - } - - public IEnumerable ServerSyncProviders - { - get { return ((SyncManager)_syncManager).ServerSyncProviders; } - } - - /// - /// Creates the triggers that define when the task will run - /// - public IEnumerable GetDefaultTriggers() - { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(3).Ticks} - }; - } - public bool IsHidden - { - get { return !IsEnabled; } - } - - public bool IsEnabled - { - get { return ServerSyncProviders.Any(); } - } - - public bool IsLogged - { - get { return true; } - } - - public string Key - { - get { return "ServerSync"; } - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncConfig.cs b/Emby.Server.Implementations/Sync/SyncConfig.cs deleted file mode 100644 index 8a97326bd..000000000 --- a/Emby.Server.Implementations/Sync/SyncConfig.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Sync; -using System.Collections.Generic; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncConfigurationFactory : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new List - { - new ConfigurationStore - { - ConfigurationType = typeof(SyncOptions), - Key = "sync" - } - }; - } - } - - public static class SyncExtensions - { - public static SyncOptions GetSyncOptions(this IConfigurationManager config) - { - return config.GetConfiguration("sync"); - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncConvertScheduledTask.cs b/Emby.Server.Implementations/Sync/SyncConvertScheduledTask.cs deleted file mode 100644 index 8dafac7e1..000000000 --- a/Emby.Server.Implementations/Sync/SyncConvertScheduledTask.cs +++ /dev/null @@ -1,89 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncConvertScheduledTask : IScheduledTask - { - private readonly ILibraryManager _libraryManager; - private readonly ISyncRepository _syncRepo; - private readonly ISyncManager _syncManager; - private readonly ILogger _logger; - private readonly IUserManager _userManager; - private readonly ITVSeriesManager _tvSeriesManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly ISubtitleEncoder _subtitleEncoder; - private readonly IConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IMediaSourceManager _mediaSourceManager; - - public SyncConvertScheduledTask(ILibraryManager libraryManager, ISyncRepository syncRepo, ISyncManager syncManager, ILogger logger, IUserManager userManager, ITVSeriesManager tvSeriesManager, IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder, IConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager) - { - _libraryManager = libraryManager; - _syncRepo = syncRepo; - _syncManager = syncManager; - _logger = logger; - _userManager = userManager; - _tvSeriesManager = tvSeriesManager; - _mediaEncoder = mediaEncoder; - _subtitleEncoder = subtitleEncoder; - _config = config; - _fileSystem = fileSystem; - _mediaSourceManager = mediaSourceManager; - } - - public string Name - { - get { return "Convert media"; } - } - - public string Description - { - get { return "Runs scheduled sync jobs"; } - } - - public string Category - { - get - { - return "Sync"; - } - } - - public Task Execute(CancellationToken cancellationToken, IProgress progress) - { - return new SyncJobProcessor(_libraryManager, _syncRepo, (SyncManager)_syncManager, _logger, _userManager, _tvSeriesManager, _mediaEncoder, _subtitleEncoder, _config, _fileSystem, _mediaSourceManager) - .Sync(progress, cancellationToken); - } - - /// - /// Creates the triggers that define when the task will run - /// - /// IEnumerable{BaseTaskTrigger}. - public IEnumerable GetDefaultTriggers() - { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(3).Ticks} - }; - } - - public string Key - { - get { return "SyncPrepare"; } - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncHelper.cs b/Emby.Server.Implementations/Sync/SyncHelper.cs deleted file mode 100644 index 7fe703796..000000000 --- a/Emby.Server.Implementations/Sync/SyncHelper.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncHelper - { - public static long? AdjustBitrate(long? profileBitrate, string quality) - { - if (profileBitrate.HasValue) - { - if (string.Equals(quality, "medium", StringComparison.OrdinalIgnoreCase)) - { - profileBitrate = Math.Min(profileBitrate.Value, 4000000); - } - else if (string.Equals(quality, "low", StringComparison.OrdinalIgnoreCase)) - { - profileBitrate = Math.Min(profileBitrate.Value, 1500000); - } - } - - return profileBitrate; - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncJobOptions.cs b/Emby.Server.Implementations/Sync/SyncJobOptions.cs deleted file mode 100644 index 8e4d8e2ed..000000000 --- a/Emby.Server.Implementations/Sync/SyncJobOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using MediaBrowser.Model.Dlna; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncJobOptions - { - /// - /// Gets or sets the conversion options. - /// - /// The conversion options. - public DeviceProfile DeviceProfile { get; set; } - /// - /// Gets or sets a value indicating whether this instance is converting. - /// - /// true if this instance is converting; otherwise, false. - public bool IsConverting { get; set; } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncJobProcessor.cs b/Emby.Server.Implementations/Sync/SyncJobProcessor.cs deleted file mode 100644 index 17cdef5fc..000000000 --- a/Emby.Server.Implementations/Sync/SyncJobProcessor.cs +++ /dev/null @@ -1,998 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Session; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncJobProcessor - { - private readonly ILibraryManager _libraryManager; - private readonly ISyncRepository _syncRepo; - private readonly SyncManager _syncManager; - private readonly ILogger _logger; - private readonly IUserManager _userManager; - private readonly ITVSeriesManager _tvSeriesManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly ISubtitleEncoder _subtitleEncoder; - private readonly IConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IMediaSourceManager _mediaSourceManager; - - public SyncJobProcessor(ILibraryManager libraryManager, ISyncRepository syncRepo, SyncManager syncManager, ILogger logger, IUserManager userManager, ITVSeriesManager tvSeriesManager, IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder, IConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager) - { - _libraryManager = libraryManager; - _syncRepo = syncRepo; - _syncManager = syncManager; - _logger = logger; - _userManager = userManager; - _tvSeriesManager = tvSeriesManager; - _mediaEncoder = mediaEncoder; - _subtitleEncoder = subtitleEncoder; - _config = config; - _fileSystem = fileSystem; - _mediaSourceManager = mediaSourceManager; - } - - public async Task EnsureJobItems(SyncJob job, User user) - { - var items = (await GetItemsForSync(job.Category, job.ParentId, job.RequestedItemIds, user, job.UnwatchedOnly).ConfigureAwait(false)) - .ToList(); - - var jobItems = _syncManager.GetJobItems(new SyncJobItemQuery - { - JobId = job.Id, - AddMetadata = false - - }).Items.ToList(); - - foreach (var item in items) - { - // Respect ItemLimit, if set - if (job.ItemLimit.HasValue) - { - if (jobItems.Count(j => j.Status != SyncJobItemStatus.RemovedFromDevice && j.Status != SyncJobItemStatus.Failed) >= job.ItemLimit.Value) - { - break; - } - } - - var itemId = item.Id.ToString("N"); - - var jobItem = jobItems.FirstOrDefault(i => string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)); - - if (jobItem != null) - { - continue; - } - - var index = jobItems.Count == 0 ? - 0 : - jobItems.Select(i => i.JobItemIndex).Max() + 1; - - jobItem = new SyncJobItem - { - Id = Guid.NewGuid().ToString("N"), - ItemId = itemId, - ItemName = GetSyncJobItemName(item), - JobId = job.Id, - TargetId = job.TargetId, - DateCreated = DateTime.UtcNow, - JobItemIndex = index - }; - - await _syncRepo.Create(jobItem).ConfigureAwait(false); - _syncManager.OnSyncJobItemCreated(jobItem); - - jobItems.Add(jobItem); - } - - jobItems = jobItems - .OrderBy(i => i.DateCreated) - .ToList(); - - await UpdateJobStatus(job, jobItems).ConfigureAwait(false); - } - - private string GetSyncJobItemName(BaseItem item) - { - var name = item.Name; - var episode = item as Episode; - - if (episode != null) - { - if (episode.IndexNumber.HasValue) - { - name = "E" + episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture) + " - " + name; - } - - if (episode.ParentIndexNumber.HasValue) - { - name = "S" + episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture) + ", " + name; - } - } - - return name; - } - - public Task UpdateJobStatus(string id) - { - var job = _syncRepo.GetJob(id); - - if (job == null) - { - return Task.FromResult(true); - } - - var result = _syncManager.GetJobItems(new SyncJobItemQuery - { - JobId = job.Id, - AddMetadata = false - }); - - return UpdateJobStatus(job, result.Items.ToList()); - } - - private async Task UpdateJobStatus(SyncJob job, List jobItems) - { - job.ItemCount = jobItems.Count; - - double pct = 0; - - foreach (var item in jobItems) - { - if (item.Status == SyncJobItemStatus.Failed || item.Status == SyncJobItemStatus.Synced || item.Status == SyncJobItemStatus.RemovedFromDevice || item.Status == SyncJobItemStatus.Cancelled) - { - pct += 100; - } - else - { - pct += item.Progress ?? 0; - } - } - - if (job.ItemCount > 0) - { - pct /= job.ItemCount; - job.Progress = pct; - } - else - { - job.Progress = null; - } - - if (jobItems.Any(i => i.Status == SyncJobItemStatus.Transferring)) - { - job.Status = SyncJobStatus.Transferring; - } - else if (jobItems.Any(i => i.Status == SyncJobItemStatus.Converting)) - { - job.Status = SyncJobStatus.Converting; - } - else if (jobItems.All(i => i.Status == SyncJobItemStatus.Failed)) - { - job.Status = SyncJobStatus.Failed; - } - else if (jobItems.All(i => i.Status == SyncJobItemStatus.Cancelled)) - { - job.Status = SyncJobStatus.Cancelled; - } - else if (jobItems.All(i => i.Status == SyncJobItemStatus.ReadyToTransfer)) - { - job.Status = SyncJobStatus.ReadyToTransfer; - } - else if (jobItems.All(i => i.Status == SyncJobItemStatus.Cancelled || i.Status == SyncJobItemStatus.Failed || i.Status == SyncJobItemStatus.Synced || i.Status == SyncJobItemStatus.RemovedFromDevice)) - { - if (jobItems.Any(i => i.Status == SyncJobItemStatus.Failed)) - { - job.Status = SyncJobStatus.CompletedWithError; - } - else - { - job.Status = SyncJobStatus.Completed; - } - } - else - { - job.Status = SyncJobStatus.Queued; - } - - await _syncRepo.Update(job).ConfigureAwait(false); - - _syncManager.OnSyncJobUpdated(job); - } - - public async Task> GetItemsForSync(SyncCategory? category, string parentId, IEnumerable itemIds, User user, bool unwatchedOnly) - { - var list = new List(); - - if (category.HasValue) - { - list = (await GetItemsForSync(category.Value, parentId, user).ConfigureAwait(false)).ToList(); - } - else - { - foreach (var itemId in itemIds) - { - var subList = await GetItemsForSync(itemId, user).ConfigureAwait(false); - list.AddRange(subList); - } - } - - IEnumerable items = list; - items = items.Where(_syncManager.SupportsSync); - - if (unwatchedOnly) - { - // Avoid implicitly captured closure - var currentUser = user; - - items = items.Where(i => - { - var video = i as Video; - - if (video != null) - { - return !video.IsPlayed(currentUser); - } - - return true; - }); - } - - return items.DistinctBy(i => i.Id); - } - - private async Task> GetItemsForSync(SyncCategory category, string parentId, User user) - { - var parent = string.IsNullOrWhiteSpace(parentId) - ? user.RootFolder - : (Folder)_libraryManager.GetItemById(parentId); - - InternalItemsQuery query; - - switch (category) - { - case SyncCategory.Latest: - query = new InternalItemsQuery - { - IsFolder = false, - SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }, - SortOrder = SortOrder.Descending, - Recursive = true - }; - break; - case SyncCategory.Resume: - query = new InternalItemsQuery - { - IsFolder = false, - SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }, - SortOrder = SortOrder.Descending, - Recursive = true, - IsResumable = true, - MediaTypes = new[] { MediaType.Video } - }; - break; - - case SyncCategory.NextUp: - return _tvSeriesManager.GetNextUp(new NextUpQuery - { - ParentId = parentId, - UserId = user.Id.ToString("N") - }).Items; - - default: - throw new ArgumentException("Unrecognized category: " + category); - } - - if (parent == null) - { - return new List(); - } - - query.User = user; - - var result = await parent.GetItems(query).ConfigureAwait(false); - return result.Items; - } - - private async Task> GetItemsForSync(string id, User user) - { - var item = _libraryManager.GetItemById(id); - - if (item == null) - { - return new List(); - } - - var itemByName = item as IItemByName; - if (itemByName != null) - { - return itemByName.GetTaggedItems(new InternalItemsQuery(user) - { - IsFolder = false, - Recursive = true - }).ToList(); - } - - if (item.IsFolder) - { - var folder = (Folder)item; - var itemsResult = await folder.GetItems(new InternalItemsQuery(user) - { - Recursive = true, - IsFolder = false - - }).ConfigureAwait(false); - - var items = itemsResult.Items; - - if (!folder.IsPreSorted) - { - items = _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending) - .ToArray(); - } - - return items.ToList(); - } - - return new List { item }; - } - - private async Task EnsureSyncJobItems(string targetId, CancellationToken cancellationToken) - { - var jobResult = _syncRepo.GetJobs(new SyncJobQuery - { - SyncNewContent = true, - TargetId = targetId - }); - - foreach (var job in jobResult.Items) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (job.SyncNewContent) - { - var user = _userManager.GetUserById(job.UserId); - - if (user == null) - { - await _syncManager.CancelJob(job.Id).ConfigureAwait(false); - } - else - { - await EnsureJobItems(job, user).ConfigureAwait(false); - } - } - } - } - - public async Task Sync(IProgress progress, CancellationToken cancellationToken) - { - await EnsureSyncJobItems(null, cancellationToken).ConfigureAwait(false); - - // Look job items that are supposedly transfering, but need to be requeued because the synced files have been deleted somehow - await HandleDeletedSyncFiles(cancellationToken).ConfigureAwait(false); - - // If it already has a converting status then is must have been aborted during conversion - var result = _syncManager.GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - AddMetadata = false - }); - - await SyncJobItems(result.Items, true, progress, cancellationToken).ConfigureAwait(false); - - CleanDeadSyncFiles(); - } - - private async Task HandleDeletedSyncFiles(CancellationToken cancellationToken) - { - // Look job items that are supposedly transfering, but need to be requeued because the synced files have been deleted somehow - var result = _syncManager.GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Transferring }, - AddMetadata = false - }); - - foreach (var item in result.Items) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrWhiteSpace(item.OutputPath) || !_fileSystem.FileExists(item.OutputPath)) - { - item.Status = SyncJobItemStatus.Queued; - await _syncManager.UpdateSyncJobItemInternal(item).ConfigureAwait(false); - await UpdateJobStatus(item.JobId).ConfigureAwait(false); - } - } - } - - private void CleanDeadSyncFiles() - { - // TODO - // Clean files in sync temp folder that are not linked to any sync jobs - } - - public async Task SyncJobItems(string targetId, bool enableConversion, IProgress progress, - CancellationToken cancellationToken) - { - await EnsureSyncJobItems(targetId, cancellationToken).ConfigureAwait(false); - - // If it already has a converting status then is must have been aborted during conversion - var result = _syncManager.GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - TargetId = targetId, - AddMetadata = false - }); - - await SyncJobItems(result.Items, enableConversion, progress, cancellationToken).ConfigureAwait(false); - } - - public async Task SyncJobItems(SyncJobItem[] items, bool enableConversion, IProgress progress, CancellationToken cancellationToken) - { - if (items.Length > 0) - { - if (!SyncRegistrationInfo.Instance.IsRegistered) - { - _logger.Debug("Cancelling sync job processing. Please obtain a supporter membership."); - return; - } - } - - var numComplete = 0; - - foreach (var item in items) - { - cancellationToken.ThrowIfCancellationRequested(); - - double percentPerItem = 1; - percentPerItem /= items.Length; - var startingPercent = numComplete * percentPerItem * 100; - - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report(startingPercent + percentPerItem * p)); - - // Pull it fresh from the db just to make sure it wasn't deleted or cancelled while another item was converting - var jobItem = enableConversion ? _syncRepo.GetJobItem(item.Id) : item; - - if (jobItem != null) - { - if (jobItem.Status != SyncJobItemStatus.Cancelled) - { - await ProcessJobItem(jobItem, enableConversion, innerProgress, cancellationToken).ConfigureAwait(false); - } - - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - numComplete++; - double percent = numComplete; - percent /= items.Length; - progress.Report(100 * percent); - } - } - - private async Task ProcessJobItem(SyncJobItem jobItem, bool enableConversion, IProgress progress, CancellationToken cancellationToken) - { - if (jobItem == null) - { - throw new ArgumentNullException("jobItem"); - } - - var item = _libraryManager.GetItemById(jobItem.ItemId); - if (item == null) - { - jobItem.Status = SyncJobItemStatus.Failed; - _logger.Error("Unable to locate library item for JobItem {0}, ItemId {1}", jobItem.Id, jobItem.ItemId); - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - - jobItem.Progress = 0; - - var job = _syncManager.GetJob(jobItem.JobId); - if (job == null) - { - _logger.Error("Job not found. Cannot complete the sync job."); - await _syncManager.CancelJobItem(jobItem.Id).ConfigureAwait(false); - return; - } - - var user = _userManager.GetUserById(job.UserId); - if (user == null) - { - jobItem.Status = SyncJobItemStatus.Failed; - _logger.Error("User not found. Cannot complete the sync job."); - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - - // See if there's already another active job item for the same target - var existingJobItems = _syncManager.GetJobItems(new SyncJobItemQuery - { - AddMetadata = false, - ItemId = jobItem.ItemId, - TargetId = jobItem.TargetId, - Statuses = new[] { SyncJobItemStatus.Converting, SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Synced, SyncJobItemStatus.Transferring } - }); - - var duplicateJobItems = existingJobItems.Items - .Where(i => !string.Equals(i.Id, jobItem.Id, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (duplicateJobItems.Count > 0) - { - var syncProvider = _syncManager.GetSyncProvider(jobItem) as IHasDuplicateCheck; - - if (!duplicateJobItems.Any(i => AllowDuplicateJobItem(syncProvider, i, jobItem))) - { - _logger.Debug("Cancelling sync job item because there is already another active job for the same target."); - jobItem.Status = SyncJobItemStatus.Cancelled; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - } - - var syncOptions = _config.GetSyncOptions(); - - var video = item as Video; - if (video != null) - { - await Sync(jobItem, video, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false); - } - - else if (item is Audio) - { - await Sync(jobItem, (Audio)item, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false); - } - - else if (item is Photo) - { - await Sync(jobItem, (Photo)item, cancellationToken).ConfigureAwait(false); - } - - else - { - await SyncGeneric(jobItem, item, cancellationToken).ConfigureAwait(false); - } - } - - private bool AllowDuplicateJobItem(IHasDuplicateCheck provider, SyncJobItem original, SyncJobItem duplicate) - { - if (provider != null) - { - return provider.AllowDuplicateJobItem(original, duplicate); - } - - return true; - } - - private async Task Sync(SyncJobItem jobItem, Video item, User user, bool enableConversion, SyncOptions syncOptions, IProgress progress, CancellationToken cancellationToken) - { - var job = _syncManager.GetJob(jobItem.JobId); - var jobOptions = _syncManager.GetVideoOptions(jobItem, job); - var conversionOptions = new VideoOptions - { - Profile = jobOptions.DeviceProfile - }; - - conversionOptions.DeviceId = jobItem.TargetId; - conversionOptions.Context = EncodingContext.Static; - conversionOptions.ItemId = item.Id.ToString("N"); - conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList(); - - var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(conversionOptions); - var mediaSource = streamInfo.MediaSource; - - // No sense creating external subs if we're already burning one into the video - var externalSubs = streamInfo.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode ? - new List() : - streamInfo.GetExternalSubtitles(false, true, null, null); - - // Mark as requiring conversion if transcoding the video, or if any subtitles need to be extracted - var requiresVideoTranscoding = streamInfo.PlayMethod == PlayMethod.Transcode && jobOptions.IsConverting; - var requiresConversion = requiresVideoTranscoding || externalSubs.Any(i => RequiresExtraction(i, mediaSource)); - - if (requiresConversion && !enableConversion) - { - return; - } - - jobItem.MediaSourceId = streamInfo.MediaSourceId; - jobItem.TemporaryPath = GetTemporaryPath(jobItem); - - if (requiresConversion) - { - jobItem.Status = SyncJobItemStatus.Converting; - } - - if (requiresVideoTranscoding) - { - // Save the job item now since conversion could take a while - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - - try - { - var lastJobUpdate = DateTime.MinValue; - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(async pct => - { - progress.Report(pct); - - if ((DateTime.UtcNow - lastJobUpdate).TotalSeconds >= DatabaseProgressUpdateIntervalSeconds) - { - jobItem.Progress = pct / 2; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - }); - - jobItem.OutputPath = await _mediaEncoder.EncodeVideo(new EncodingJobOptions(streamInfo, conversionOptions.Profile) - { - OutputDirectory = jobItem.TemporaryPath, - CpuCoreLimit = syncOptions.TranscodingCpuCoreLimit, - ReadInputAtNativeFramerate = !syncOptions.EnableFullSpeedTranscoding - - }, innerProgress, cancellationToken); - - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - _syncManager.OnConversionComplete(jobItem); - } - catch (OperationCanceledException) - { - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - } - catch (Exception ex) - { - jobItem.Status = SyncJobItemStatus.Failed; - _logger.ErrorException("Error during sync transcoding", ex); - } - - if (jobItem.Status == SyncJobItemStatus.Failed || jobItem.Status == SyncJobItemStatus.Queued) - { - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - - jobItem.MediaSource = await GetEncodedMediaSource(jobItem.OutputPath, user, true).ConfigureAwait(false); - } - else - { - if (mediaSource.Protocol == MediaProtocol.File) - { - jobItem.OutputPath = mediaSource.Path; - } - else if (mediaSource.Protocol == MediaProtocol.Http) - { - jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false); - } - else - { - throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol)); - } - - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - jobItem.MediaSource = mediaSource; - } - - jobItem.MediaSource.SupportsTranscoding = false; - - if (externalSubs.Count > 0) - { - // Save the job item now since conversion could take a while - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - await ConvertSubtitles(jobItem, externalSubs, streamInfo, cancellationToken).ConfigureAwait(false); - } - - jobItem.Progress = 50; - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - - private bool RequiresExtraction(SubtitleStreamInfo stream, MediaSourceInfo mediaSource) - { - var originalStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Subtitle && i.Index == stream.Index); - - return originalStream != null && !originalStream.IsExternal; - } - - private async Task ConvertSubtitles(SyncJobItem jobItem, - IEnumerable subtitles, - StreamInfo streamInfo, - CancellationToken cancellationToken) - { - var files = new List(); - - var mediaStreams = jobItem.MediaSource.MediaStreams - .Where(i => i.Type != MediaStreamType.Subtitle || !i.IsExternal) - .ToList(); - - var startingIndex = mediaStreams.Count == 0 ? - 0 : - mediaStreams.Select(i => i.Index).Max() + 1; - - foreach (var subtitle in subtitles) - { - var fileInfo = await ConvertSubtitles(jobItem.TemporaryPath, streamInfo, subtitle, cancellationToken).ConfigureAwait(false); - - // Reset this to a value that will be based on the output media - fileInfo.Index = startingIndex; - files.Add(fileInfo); - - mediaStreams.Add(new MediaStream - { - Index = startingIndex, - Codec = subtitle.Format, - IsForced = subtitle.IsForced, - IsExternal = true, - Language = subtitle.Language, - Path = fileInfo.Path, - SupportsExternalStream = true, - Type = MediaStreamType.Subtitle - }); - - startingIndex++; - } - - jobItem.AdditionalFiles.AddRange(files); - - jobItem.MediaSource.MediaStreams = mediaStreams; - } - - private async Task ConvertSubtitles(string temporaryPath, StreamInfo streamInfo, SubtitleStreamInfo subtitleStreamInfo, CancellationToken cancellationToken) - { - var subtitleStreamIndex = subtitleStreamInfo.Index; - - var filename = Guid.NewGuid() + "." + subtitleStreamInfo.Format.ToLower(); - - var path = Path.Combine(temporaryPath, filename); - - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); - - using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, false, cancellationToken).ConfigureAwait(false)) - { - using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) - { - await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - - return new ItemFileInfo - { - Name = Path.GetFileName(path), - Path = path, - Type = ItemFileType.Subtitles, - Index = subtitleStreamIndex - }; - } - - private const int DatabaseProgressUpdateIntervalSeconds = 2; - - private async Task Sync(SyncJobItem jobItem, Audio item, User user, bool enableConversion, SyncOptions syncOptions, IProgress progress, CancellationToken cancellationToken) - { - var job = _syncManager.GetJob(jobItem.JobId); - var jobOptions = _syncManager.GetAudioOptions(jobItem, job); - var conversionOptions = new AudioOptions - { - Profile = jobOptions.DeviceProfile - }; - - conversionOptions.DeviceId = jobItem.TargetId; - conversionOptions.Context = EncodingContext.Static; - conversionOptions.ItemId = item.Id.ToString("N"); - conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList(); - - var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(conversionOptions); - var mediaSource = streamInfo.MediaSource; - - jobItem.MediaSourceId = streamInfo.MediaSourceId; - jobItem.TemporaryPath = GetTemporaryPath(jobItem); - - if (streamInfo.PlayMethod == PlayMethod.Transcode && jobOptions.IsConverting) - { - if (!enableConversion) - { - return; - } - - jobItem.Status = SyncJobItemStatus.Converting; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - - try - { - var lastJobUpdate = DateTime.MinValue; - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(async pct => - { - progress.Report(pct); - - if ((DateTime.UtcNow - lastJobUpdate).TotalSeconds >= DatabaseProgressUpdateIntervalSeconds) - { - jobItem.Progress = pct / 2; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - }); - - jobItem.OutputPath = await _mediaEncoder.EncodeAudio(new EncodingJobOptions(streamInfo, conversionOptions.Profile) - { - OutputDirectory = jobItem.TemporaryPath, - CpuCoreLimit = syncOptions.TranscodingCpuCoreLimit - - }, innerProgress, cancellationToken); - - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - _syncManager.OnConversionComplete(jobItem); - } - catch (OperationCanceledException) - { - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - } - catch (Exception ex) - { - jobItem.Status = SyncJobItemStatus.Failed; - _logger.ErrorException("Error during sync transcoding", ex); - } - - if (jobItem.Status == SyncJobItemStatus.Failed || jobItem.Status == SyncJobItemStatus.Queued) - { - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - - jobItem.MediaSource = await GetEncodedMediaSource(jobItem.OutputPath, user, false).ConfigureAwait(false); - } - else - { - if (mediaSource.Protocol == MediaProtocol.File) - { - jobItem.OutputPath = mediaSource.Path; - } - else if (mediaSource.Protocol == MediaProtocol.Http) - { - jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false); - } - else - { - throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol)); - } - - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - jobItem.MediaSource = mediaSource; - } - - jobItem.MediaSource.SupportsTranscoding = false; - - jobItem.Progress = 50; - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - - private async Task Sync(SyncJobItem jobItem, Photo item, CancellationToken cancellationToken) - { - jobItem.OutputPath = item.Path; - - jobItem.Progress = 50; - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - - private async Task SyncGeneric(SyncJobItem jobItem, BaseItem item, CancellationToken cancellationToken) - { - jobItem.OutputPath = item.Path; - - jobItem.Progress = 50; - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - - private async Task DownloadFile(SyncJobItem jobItem, MediaSourceInfo mediaSource, CancellationToken cancellationToken) - { - // TODO: Download - return mediaSource.Path; - } - - public string GetTemporaryPath(SyncJob job) - { - return GetTemporaryPath(job.Id); - } - - public string GetTemporaryPath(string jobId) - { - var basePath = _config.GetSyncOptions().TemporaryPath; - - if (string.IsNullOrWhiteSpace(basePath)) - { - basePath = Path.Combine(_config.CommonApplicationPaths.ProgramDataPath, "sync"); - } - - return Path.Combine(basePath, jobId); - } - - public string GetTemporaryPath(SyncJobItem jobItem) - { - return Path.Combine(GetTemporaryPath(jobItem.JobId), jobItem.Id); - } - - private async Task GetEncodedMediaSource(string path, User user, bool isVideo) - { - var item = _libraryManager.ResolvePath(_fileSystem.GetFileSystemInfo(path)); - - await item.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); - - var hasMediaSources = item as IHasMediaSources; - - var mediaSources = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false).ToList(); - - var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) - ? new string[] { } - : new[] { user.Configuration.AudioLanguagePreference }; - - var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference) - ? new List() : new List { user.Configuration.SubtitleLanguagePreference }; - - foreach (var source in mediaSources) - { - if (isVideo) - { - source.DefaultAudioStreamIndex = - MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack); - - var defaultAudioIndex = source.DefaultAudioStreamIndex; - var audioLangage = defaultAudioIndex == null - ? null - : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); - - source.DefaultAudioStreamIndex = - MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams, preferredSubs, user.Configuration.SubtitleMode, audioLangage); - } - else - { - var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - - if (audio != null) - { - source.DefaultAudioStreamIndex = audio.Index; - } - - } - } - - return mediaSources.FirstOrDefault(); - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncManager.cs b/Emby.Server.Implementations/Sync/SyncManager.cs deleted file mode 100644 index 418d42c9a..000000000 --- a/Emby.Server.Implementations/Sync/SyncManager.cs +++ /dev/null @@ -1,1372 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Playlists; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Sync; -using MediaBrowser.Model.Users; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncManager : ISyncManager - { - private readonly ILibraryManager _libraryManager; - private readonly ISyncRepository _repo; - private readonly IImageProcessor _imageProcessor; - private readonly ILogger _logger; - private readonly IUserManager _userManager; - private readonly Func _dtoService; - private readonly IServerApplicationHost _appHost; - private readonly ITVSeriesManager _tvSeriesManager; - private readonly Func _mediaEncoder; - private readonly IFileSystem _fileSystem; - private readonly Func _subtitleEncoder; - private readonly IConfigurationManager _config; - private readonly IUserDataManager _userDataManager; - private readonly Func _mediaSourceManager; - private readonly IJsonSerializer _json; - private readonly ITaskManager _taskManager; - private readonly IMemoryStreamFactory _memoryStreamProvider; - - private ISyncProvider[] _providers = { }; - - public event EventHandler> SyncJobCreated; - public event EventHandler> SyncJobCancelled; - public event EventHandler> SyncJobUpdated; - public event EventHandler> SyncJobItemUpdated; - public event EventHandler> SyncJobItemCreated; - - public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func mediaEncoder, IFileSystem fileSystem, Func subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func mediaSourceManager, IJsonSerializer json, ITaskManager taskManager, IMemoryStreamFactory memoryStreamProvider) - { - _libraryManager = libraryManager; - _repo = repo; - _imageProcessor = imageProcessor; - _logger = logger; - _userManager = userManager; - _dtoService = dtoService; - _appHost = appHost; - _tvSeriesManager = tvSeriesManager; - _mediaEncoder = mediaEncoder; - _fileSystem = fileSystem; - _subtitleEncoder = subtitleEncoder; - _config = config; - _userDataManager = userDataManager; - _mediaSourceManager = mediaSourceManager; - _json = json; - _taskManager = taskManager; - _memoryStreamProvider = memoryStreamProvider; - } - - public void AddParts(IEnumerable providers) - { - _providers = providers.ToArray(); - } - - public IEnumerable ServerSyncProviders - { - get { return _providers.OfType(); } - } - - private readonly ConcurrentDictionary _dataProviders = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - public ISyncDataProvider GetDataProvider(IServerSyncProvider provider, SyncTarget target) - { - return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths, _memoryStreamProvider)); - } - - public async Task CreateJob(SyncJobRequest request) - { - var processor = GetSyncJobProcessor(); - - var user = _userManager.GetUserById(request.UserId); - - var items = (await processor - .GetItemsForSync(request.Category, request.ParentId, request.ItemIds, user, request.UnwatchedOnly).ConfigureAwait(false)) - .ToList(); - - if (items.Any(i => !SupportsSync(i))) - { - throw new ArgumentException("Item does not support sync."); - } - - if (string.IsNullOrWhiteSpace(request.Name)) - { - if (request.ItemIds.Count == 1) - { - request.Name = GetDefaultName(_libraryManager.GetItemById(request.ItemIds[0])); - } - } - - if (string.IsNullOrWhiteSpace(request.Name)) - { - request.Name = DateTime.Now.ToString("f1", CultureInfo.CurrentCulture); - } - - var target = GetSyncTargets(request.UserId) - .FirstOrDefault(i => string.Equals(request.TargetId, i.Id)); - - if (target == null) - { - throw new ArgumentException("Sync target not found."); - } - - var jobId = Guid.NewGuid().ToString("N"); - - if (string.IsNullOrWhiteSpace(request.Quality)) - { - request.Quality = GetQualityOptions(request.TargetId) - .Where(i => i.IsDefault) - .Select(i => i.Id) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); - } - - var job = new SyncJob - { - Id = jobId, - Name = request.Name, - TargetId = target.Id, - UserId = request.UserId, - UnwatchedOnly = request.UnwatchedOnly, - ItemLimit = request.ItemLimit, - RequestedItemIds = request.ItemIds ?? new List(), - DateCreated = DateTime.UtcNow, - DateLastModified = DateTime.UtcNow, - SyncNewContent = request.SyncNewContent, - ItemCount = items.Count, - Category = request.Category, - ParentId = request.ParentId, - Quality = request.Quality, - Profile = request.Profile, - Bitrate = request.Bitrate - }; - - if (!request.Category.HasValue && request.ItemIds != null) - { - var requestedItems = request.ItemIds - .Select(_libraryManager.GetItemById) - .Where(i => i != null); - - // It's just a static list - if (!requestedItems.Any(i => i.IsFolder || i is IItemByName)) - { - job.SyncNewContent = false; - } - } - - await _repo.Create(job).ConfigureAwait(false); - - await processor.EnsureJobItems(job, user).ConfigureAwait(false); - - // If it already has a converting status then is must have been aborted during conversion - var jobItemsResult = GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - JobId = jobId, - AddMetadata = false - }); - - await processor.SyncJobItems(jobItemsResult.Items, false, new Progress(), CancellationToken.None) - .ConfigureAwait(false); - - jobItemsResult = GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - JobId = jobId, - AddMetadata = false - }); - - var returnResult = new SyncJobCreationResult - { - Job = GetJob(jobId), - JobItems = jobItemsResult.Items.ToList() - }; - - if (SyncJobCreated != null) - { - EventHelper.FireEventIfNotNull(SyncJobCreated, this, new GenericEventArgs - { - Argument = returnResult - - }, _logger); - } - - if (returnResult.JobItems.Any(i => i.Status == SyncJobItemStatus.Queued || i.Status == SyncJobItemStatus.Converting)) - { - _taskManager.QueueScheduledTask(); - } - - return returnResult; - } - - public async Task UpdateJob(SyncJob job) - { - // Get fresh from the db and only update the fields that are supported to be changed. - var instance = _repo.GetJob(job.Id); - - instance.Name = job.Name; - instance.Quality = job.Quality; - instance.Profile = job.Profile; - instance.UnwatchedOnly = job.UnwatchedOnly; - instance.SyncNewContent = job.SyncNewContent; - instance.ItemLimit = job.ItemLimit; - - await _repo.Update(instance).ConfigureAwait(false); - - OnSyncJobUpdated(instance); - } - - internal void OnSyncJobUpdated(SyncJob job) - { - if (SyncJobUpdated != null) - { - EventHelper.FireEventIfNotNull(SyncJobUpdated, this, new GenericEventArgs - { - Argument = job - - }, _logger); - } - } - - internal async Task UpdateSyncJobItemInternal(SyncJobItem jobItem) - { - await _repo.Update(jobItem).ConfigureAwait(false); - - if (SyncJobUpdated != null) - { - EventHelper.FireEventIfNotNull(SyncJobItemUpdated, this, new GenericEventArgs - { - Argument = jobItem - - }, _logger); - } - } - - internal void OnSyncJobItemCreated(SyncJobItem job) - { - if (SyncJobUpdated != null) - { - EventHelper.FireEventIfNotNull(SyncJobItemCreated, this, new GenericEventArgs - { - Argument = job - - }, _logger); - } - } - - public async Task> GetJobs(SyncJobQuery query) - { - var result = _repo.GetJobs(query); - - foreach (var item in result.Items) - { - await FillMetadata(item).ConfigureAwait(false); - } - - return result; - } - - private async Task FillMetadata(SyncJob job) - { - var user = _userManager.GetUserById(job.UserId); - - if (user == null) - { - return; - } - - var target = GetSyncTargets(job.UserId) - .FirstOrDefault(i => string.Equals(i.Id, job.TargetId, StringComparison.OrdinalIgnoreCase)); - - if (target != null) - { - job.TargetName = target.Name; - } - - var item = job.RequestedItemIds - .Select(_libraryManager.GetItemById) - .FirstOrDefault(i => i != null); - - if (item == null) - { - var processor = GetSyncJobProcessor(); - - item = (await processor - .GetItemsForSync(job.Category, job.ParentId, job.RequestedItemIds, user, job.UnwatchedOnly).ConfigureAwait(false)) - .FirstOrDefault(); - } - - if (item != null) - { - var hasSeries = item as IHasSeries; - if (hasSeries != null) - { - job.ParentName = hasSeries.SeriesName; - } - - var hasAlbumArtist = item as IHasAlbumArtist; - if (hasAlbumArtist != null) - { - job.ParentName = hasAlbumArtist.AlbumArtists.FirstOrDefault(); - } - - var primaryImage = item.GetImageInfo(ImageType.Primary, 0); - var itemWithImage = item; - - if (primaryImage == null) - { - var parentWithImage = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary)); - - if (parentWithImage != null) - { - itemWithImage = parentWithImage; - primaryImage = parentWithImage.GetImageInfo(ImageType.Primary, 0); - } - } - - if (primaryImage != null) - { - try - { - job.PrimaryImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Primary); - job.PrimaryImageItemId = itemWithImage.Id.ToString("N"); - - } - catch (Exception ex) - { - _logger.ErrorException("Error getting image info", ex); - } - } - } - } - - private void FillMetadata(SyncJobItem jobItem) - { - var item = _libraryManager.GetItemById(jobItem.ItemId); - - if (item == null) - { - return; - } - - var primaryImage = item.GetImageInfo(ImageType.Primary, 0); - var itemWithImage = item; - - if (primaryImage == null) - { - var parentWithImage = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary)); - - if (parentWithImage != null) - { - itemWithImage = parentWithImage; - primaryImage = parentWithImage.GetImageInfo(ImageType.Primary, 0); - } - } - - if (primaryImage != null) - { - try - { - jobItem.PrimaryImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Primary); - jobItem.PrimaryImageItemId = itemWithImage.Id.ToString("N"); - - } - catch (Exception ex) - { - _logger.ErrorException("Error getting image info", ex); - } - } - } - - public async Task CancelJob(string id) - { - var job = GetJob(id); - - if (job == null) - { - throw new ArgumentException("Job not found."); - } - - await _repo.DeleteJob(id).ConfigureAwait(false); - - var path = GetSyncJobProcessor().GetTemporaryPath(id); - - try - { - _fileSystem.DeleteDirectory(path, true); - } - catch (IOException) - { - - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting directory {0}", ex, path); - } - - if (SyncJobCancelled != null) - { - EventHelper.FireEventIfNotNull(SyncJobCancelled, this, new GenericEventArgs - { - Argument = job - - }, _logger); - } - } - - public SyncJob GetJob(string id) - { - return _repo.GetJob(id); - } - - public IEnumerable GetSyncTargets(string userId) - { - return _providers - .SelectMany(i => GetSyncTargets(i, userId)) - .OrderBy(i => i.Name); - } - - private IEnumerable GetSyncTargets(ISyncProvider provider) - { - return provider.GetAllSyncTargets().Select(i => new SyncTarget - { - Name = i.Name, - Id = GetSyncTargetId(provider, i) - }); - } - - private IEnumerable GetSyncTargets(ISyncProvider provider, string userId) - { - return provider.GetSyncTargets(userId).Select(i => new SyncTarget - { - Name = i.Name, - Id = GetSyncTargetId(provider, i) - }); - } - - private string GetSyncTargetId(ISyncProvider provider, SyncTarget target) - { - var hasUniqueId = provider as IHasUniqueTargetIds; - - if (hasUniqueId != null) - { - return target.Id; - } - - return target.Id; - //var providerId = GetSyncProviderId(provider); - //return (providerId + "-" + target.Id).GetMD5().ToString("N"); - } - - private string GetSyncProviderId(ISyncProvider provider) - { - return provider.GetType().Name.GetMD5().ToString("N"); - } - - public bool SupportsSync(BaseItem item) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - if (item is Playlist) - { - return true; - } - - if (item is Person) - { - return false; - } - - if (item is Year) - { - return false; - } - - if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.MediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.MediaType, MediaType.Book, StringComparison.OrdinalIgnoreCase)) - { - if (item.LocationType == LocationType.Virtual) - { - return false; - } - - var video = item as Video; - if (video != null) - { - if (video.IsPlaceHolder) - { - return false; - } - - if (video.IsShortcut) - { - return false; - } - } - - if (item.SourceType != SourceType.Library) - { - return false; - } - - return true; - } - - if (item.SourceType == SourceType.Channel) - { - return BaseItem.ChannelManager.SupportsSync(item.ChannelId); - } - - return item.LocationType == LocationType.FileSystem || item is Season; - } - - private string GetDefaultName(BaseItem item) - { - return item.Name; - } - - public async Task ReportSyncJobItemTransferred(string id) - { - var jobItem = _repo.GetJobItem(id); - - if (jobItem == null) - { - _logger.Debug("ReportSyncJobItemTransferred: SyncJobItem {0} doesn't exist anymore", id); - return; - } - - jobItem.Status = SyncJobItemStatus.Synced; - jobItem.Progress = 100; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - - if (!string.IsNullOrWhiteSpace(jobItem.TemporaryPath)) - { - try - { - _fileSystem.DeleteDirectory(jobItem.TemporaryPath, true); - } - catch (IOException) - { - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting temporary job file: {0}", ex, jobItem.OutputPath); - } - } - } - - private SyncJobProcessor GetSyncJobProcessor() - { - return new SyncJobProcessor(_libraryManager, _repo, this, _logger, _userManager, _tvSeriesManager, _mediaEncoder(), _subtitleEncoder(), _config, _fileSystem, _mediaSourceManager()); - } - - public SyncJobItem GetJobItem(string id) - { - return _repo.GetJobItem(id); - } - - public QueryResult GetJobItems(SyncJobItemQuery query) - { - var result = _repo.GetJobItems(query); - - if (query.AddMetadata) - { - foreach (var item in result.Items) - { - FillMetadata(item); - } - } - - return result; - } - - private SyncedItem GetJobItemInfo(SyncJobItem jobItem) - { - var job = _repo.GetJob(jobItem.JobId); - - if (job == null) - { - _logger.Error("GetJobItemInfo job id {0} no longer exists", jobItem.JobId); - return null; - } - - var libraryItem = _libraryManager.GetItemById(jobItem.ItemId); - - if (libraryItem == null) - { - _logger.Error("GetJobItemInfo library item with id {0} no longer exists", jobItem.ItemId); - return null; - } - - var syncedItem = new SyncedItem - { - SyncJobId = jobItem.JobId, - SyncJobItemId = jobItem.Id, - ServerId = _appHost.SystemId, - UserId = job.UserId, - SyncJobName = job.Name, - SyncJobDateCreated = job.DateCreated, - AdditionalFiles = jobItem.AdditionalFiles.Select(i => new ItemFileInfo - { - ImageType = i.ImageType, - Name = i.Name, - Type = i.Type, - Index = i.Index - - }).ToList() - }; - - var dtoOptions = new DtoOptions(); - - // Remove some bloat - dtoOptions.Fields.Remove(ItemFields.MediaStreams); - dtoOptions.Fields.Remove(ItemFields.IndexOptions); - dtoOptions.Fields.Remove(ItemFields.MediaSourceCount); - dtoOptions.Fields.Remove(ItemFields.Path); - dtoOptions.Fields.Remove(ItemFields.SeriesGenres); - dtoOptions.Fields.Remove(ItemFields.Settings); - dtoOptions.Fields.Remove(ItemFields.SyncInfo); - dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo); - - syncedItem.Item = _dtoService().GetBaseItemDto(libraryItem, dtoOptions); - - var mediaSource = jobItem.MediaSource; - - syncedItem.Item.MediaSources = new List(); - - syncedItem.OriginalFileName = Path.GetFileName(libraryItem.Path); - if (string.IsNullOrWhiteSpace(syncedItem.OriginalFileName)) - { - syncedItem.OriginalFileName = Path.GetFileName(mediaSource.Path); - } - - // This will be null for items that are not audio/video - if (mediaSource != null) - { - syncedItem.OriginalFileName = Path.ChangeExtension(syncedItem.OriginalFileName, Path.GetExtension(mediaSource.Path)); - syncedItem.Item.MediaSources.Add(mediaSource); - } - if (string.IsNullOrWhiteSpace(syncedItem.OriginalFileName)) - { - syncedItem.OriginalFileName = libraryItem.Name; - } - - return syncedItem; - } - - public Task ReportOfflineAction(UserAction action) - { - switch (action.Type) - { - case UserActionType.PlayedItem: - return ReportOfflinePlayedItem(action); - default: - throw new ArgumentException("Unexpected action type"); - } - } - - private Task ReportOfflinePlayedItem(UserAction action) - { - var item = _libraryManager.GetItemById(action.ItemId); - var userData = _userDataManager.GetUserData(action.UserId, item); - - userData.LastPlayedDate = action.Date; - _userDataManager.UpdatePlayState(item, userData, action.PositionTicks); - - return _userDataManager.SaveUserData(new Guid(action.UserId), item, userData, UserDataSaveReason.Import, CancellationToken.None); - } - - public async Task> GetReadySyncItems(string targetId) - { - var processor = GetSyncJobProcessor(); - - await processor.SyncJobItems(targetId, false, new Progress(), CancellationToken.None).ConfigureAwait(false); - - var jobItemResult = GetJobItems(new SyncJobItemQuery - { - TargetId = targetId, - Statuses = new[] - { - SyncJobItemStatus.ReadyToTransfer, - SyncJobItemStatus.Transferring - } - }); - - var readyItems = jobItemResult.Items - .Select(GetJobItemInfo) - .Where(i => i != null) - .ToList(); - - _logger.Debug("Returning {0} ready sync items for targetId {1}", readyItems.Count, targetId); - - return readyItems; - } - - public async Task SyncData(SyncDataRequest request) - { - if (request.SyncJobItemIds != null) - { - return await SyncDataUsingSyncJobItemIds(request).ConfigureAwait(false); - } - - var jobItemResult = GetJobItems(new SyncJobItemQuery - { - TargetId = request.TargetId, - Statuses = new[] { SyncJobItemStatus.Synced } - }); - - var response = new SyncDataResponse(); - - foreach (var jobItem in jobItemResult.Items) - { - var requiresSaving = false; - var removeFromDevice = false; - - if (request.LocalItemIds.Contains(jobItem.ItemId, StringComparer.OrdinalIgnoreCase)) - { - var libraryItem = _libraryManager.GetItemById(jobItem.ItemId); - - var job = _repo.GetJob(jobItem.JobId); - var user = _userManager.GetUserById(job.UserId); - - if (jobItem.IsMarkedForRemoval) - { - // Tell the device to remove it since it has been marked for removal - _logger.Info("Adding ItemIdsToRemove {0} because IsMarkedForRemoval is set.", jobItem.ItemId); - removeFromDevice = true; - } - else if (user == null) - { - // Tell the device to remove it since the user is gone now - _logger.Info("Adding ItemIdsToRemove {0} because the user is no longer valid.", jobItem.ItemId); - removeFromDevice = true; - } - else if (!IsLibraryItemAvailable(libraryItem)) - { - // Tell the device to remove it since it's no longer available - _logger.Info("Adding ItemIdsToRemove {0} because it is no longer available.", jobItem.ItemId); - removeFromDevice = true; - } - else if (job.UnwatchedOnly) - { - if (libraryItem is Video && libraryItem.IsPlayed(user)) - { - // Tell the device to remove it since it has been played - _logger.Info("Adding ItemIdsToRemove {0} because it has been marked played.", jobItem.ItemId); - removeFromDevice = true; - } - } - else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0) - { - _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId); - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - requiresSaving = true; - } - } - else - { - // Content is no longer on the device - if (jobItem.IsMarkedForRemoval) - { - jobItem.Status = SyncJobItemStatus.RemovedFromDevice; - } - else - { - _logger.Info("Setting status to Queued for {0} because it is no longer on the device.", jobItem.ItemId); - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - } - requiresSaving = true; - } - - if (removeFromDevice) - { - response.ItemIdsToRemove.Add(jobItem.ItemId); - jobItem.IsMarkedForRemoval = true; - requiresSaving = true; - } - - if (requiresSaving) - { - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - } - - // Now check each item that's on the device - foreach (var itemId in request.LocalItemIds) - { - // See if it's already marked for removal - if (response.ItemIdsToRemove.Contains(itemId, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - - // If there isn't a sync job for this item, mark it for removal - if (!jobItemResult.Items.Any(i => string.Equals(itemId, i.ItemId, StringComparison.OrdinalIgnoreCase))) - { - response.ItemIdsToRemove.Add(itemId); - } - } - - response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); - - var itemsOnDevice = request.LocalItemIds - .Except(response.ItemIdsToRemove) - .ToList(); - - SetUserAccess(request, response, itemsOnDevice); - - return response; - } - - private async Task SyncDataUsingSyncJobItemIds(SyncDataRequest request) - { - var jobItemResult = GetJobItems(new SyncJobItemQuery - { - TargetId = request.TargetId, - Statuses = new[] { SyncJobItemStatus.Synced } - }); - - var response = new SyncDataResponse(); - - foreach (var jobItem in jobItemResult.Items) - { - var requiresSaving = false; - var removeFromDevice = false; - - if (request.SyncJobItemIds.Contains(jobItem.Id, StringComparer.OrdinalIgnoreCase)) - { - var libraryItem = _libraryManager.GetItemById(jobItem.ItemId); - - var job = _repo.GetJob(jobItem.JobId); - var user = _userManager.GetUserById(job.UserId); - - if (jobItem.IsMarkedForRemoval) - { - // Tell the device to remove it since it has been marked for removal - _logger.Info("Adding ItemIdsToRemove {0} because IsMarkedForRemoval is set.", jobItem.Id); - removeFromDevice = true; - } - else if (user == null) - { - // Tell the device to remove it since the user is gone now - _logger.Info("Adding ItemIdsToRemove {0} because the user is no longer valid.", jobItem.Id); - removeFromDevice = true; - } - else if (!IsLibraryItemAvailable(libraryItem)) - { - // Tell the device to remove it since it's no longer available - _logger.Info("Adding ItemIdsToRemove {0} because it is no longer available.", jobItem.Id); - removeFromDevice = true; - } - else if (job.UnwatchedOnly) - { - if (libraryItem is Video && libraryItem.IsPlayed(user)) - { - // Tell the device to remove it since it has been played - _logger.Info("Adding ItemIdsToRemove {0} because it has been marked played.", jobItem.Id); - removeFromDevice = true; - } - } - else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0) - { - _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId); - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - requiresSaving = true; - } - } - else - { - // Content is no longer on the device - if (jobItem.IsMarkedForRemoval) - { - jobItem.Status = SyncJobItemStatus.RemovedFromDevice; - } - else - { - _logger.Info("Setting status to Queued for {0} because it is no longer on the device.", jobItem.Id); - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - } - requiresSaving = true; - } - - if (removeFromDevice) - { - response.ItemIdsToRemove.Add(jobItem.Id); - jobItem.IsMarkedForRemoval = true; - requiresSaving = true; - } - - if (requiresSaving) - { - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - } - - // Now check each item that's on the device - foreach (var syncJobItemId in request.SyncJobItemIds) - { - // See if it's already marked for removal - if (response.ItemIdsToRemove.Contains(syncJobItemId, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - - // If there isn't a sync job for this item, mark it for removal - if (!jobItemResult.Items.Any(i => string.Equals(syncJobItemId, i.Id, StringComparison.OrdinalIgnoreCase))) - { - response.ItemIdsToRemove.Add(syncJobItemId); - } - } - - response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); - - return response; - } - - private void SetUserAccess(SyncDataRequest request, SyncDataResponse response, List itemIds) - { - var users = request.OfflineUserIds - .Select(_userManager.GetUserById) - .Where(i => i != null) - .ToList(); - - foreach (var itemId in itemIds) - { - var item = _libraryManager.GetItemById(itemId); - - if (item != null) - { - response.ItemUserAccess[itemId] = users - .Where(i => IsUserVisible(item, i)) - .Select(i => i.Id.ToString("N")) - .OrderBy(i => i) - .ToList(); - } - } - } - - private bool IsUserVisible(BaseItem item, User user) - { - return item.IsVisibleStandalone(user); - } - - private bool IsLibraryItemAvailable(BaseItem item) - { - if (item == null) - { - return false; - } - - return true; - } - - public async Task ReEnableJobItem(string id) - { - var jobItem = _repo.GetJobItem(id); - - if (jobItem.Status != SyncJobItemStatus.Failed && jobItem.Status != SyncJobItemStatus.Cancelled) - { - throw new ArgumentException("Operation is not valid for this job item"); - } - - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - jobItem.IsMarkedForRemoval = false; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - public async Task CancelItems(string targetId, IEnumerable itemIds) - { - foreach (var item in itemIds) - { - var syncJobItemResult = GetJobItems(new SyncJobItemQuery - { - AddMetadata = false, - ItemId = item, - TargetId = targetId, - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Converting, SyncJobItemStatus.Synced, SyncJobItemStatus.Failed } - }); - - foreach (var jobItem in syncJobItemResult.Items) - { - await CancelJobItem(jobItem.Id).ConfigureAwait(false); - } - - var syncJobResult = await GetJobs(new SyncJobQuery - { - ItemId = item, - TargetId = targetId - - }).ConfigureAwait(false); - - foreach (var job in syncJobResult.Items) - { - await CancelJob(job.Id).ConfigureAwait(false); - } - } - } - - public async Task CancelJobItem(string id) - { - var jobItem = _repo.GetJobItem(id); - - jobItem.Status = SyncJobItemStatus.Cancelled; - - jobItem.Progress = 0; - jobItem.IsMarkedForRemoval = true; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - - var path = processor.GetTemporaryPath(jobItem); - - try - { - _fileSystem.DeleteDirectory(path, true); - } - catch (IOException) - { - - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting directory {0}", ex, path); - } - - var jobItemsResult = GetJobItems(new SyncJobItemQuery - { - AddMetadata = false, - JobId = jobItem.JobId, - Limit = 0, - Statuses = new[] { SyncJobItemStatus.Converting, SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Synced, SyncJobItemStatus.Transferring } - }); - - if (jobItemsResult.TotalRecordCount == 0) - { - await CancelJob(jobItem.JobId).ConfigureAwait(false); - } - } - - public Task MarkJobItemForRemoval(string id) - { - return CancelJobItem(id); - } - - public async Task UnmarkJobItemForRemoval(string id) - { - var jobItem = _repo.GetJobItem(id); - - if (jobItem.Status != SyncJobItemStatus.Synced) - { - throw new ArgumentException("Operation is not valid for this job item"); - } - - jobItem.IsMarkedForRemoval = false; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - public async Task ReportSyncJobItemTransferBeginning(string id) - { - var jobItem = _repo.GetJobItem(id); - - jobItem.Status = SyncJobItemStatus.Transferring; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - public async Task ReportSyncJobItemTransferFailed(string id) - { - var jobItem = _repo.GetJobItem(id); - - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - public Dictionary GetSyncedItemProgresses(SyncJobItemQuery query) - { - return _repo.GetSyncedItemProgresses(query); - } - - public SyncJobOptions GetAudioOptions(SyncJobItem jobItem, SyncJob job) - { - var options = GetSyncJobOptions(jobItem.TargetId, null, null); - - if (job.Bitrate.HasValue) - { - options.DeviceProfile.MaxStaticBitrate = job.Bitrate.Value; - } - - return options; - } - - public ISyncProvider GetSyncProvider(SyncJobItem jobItem) - { - foreach (var provider in _providers) - { - foreach (var target in GetSyncTargets(provider)) - { - if (string.Equals(target.Id, jobItem.TargetId, StringComparison.OrdinalIgnoreCase)) - { - return provider; - } - } - } - return null; - } - - public SyncJobOptions GetVideoOptions(SyncJobItem jobItem, SyncJob job) - { - var options = GetSyncJobOptions(jobItem.TargetId, job.Profile, job.Quality); - - if (job.Bitrate.HasValue) - { - options.DeviceProfile.MaxStaticBitrate = job.Bitrate.Value; - } - - return options; - } - - private SyncJobOptions GetSyncJobOptions(string targetId, string profile, string quality) - { - foreach (var provider in _providers) - { - foreach (var target in GetSyncTargets(provider)) - { - if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase)) - { - return GetSyncJobOptions(provider, target, profile, quality); - } - } - } - - return GetDefaultSyncJobOptions(profile, quality); - } - - private SyncJobOptions GetSyncJobOptions(ISyncProvider provider, SyncTarget target, string profile, string quality) - { - var hasProfile = provider as IHasSyncQuality; - - if (hasProfile != null) - { - return hasProfile.GetSyncJobOptions(target, profile, quality); - } - - return GetDefaultSyncJobOptions(profile, quality); - } - - private SyncJobOptions GetDefaultSyncJobOptions(string profile, string quality) - { - var supportsAc3 = string.Equals(profile, "general", StringComparison.OrdinalIgnoreCase); - - var deviceProfile = new CloudSyncProfile(supportsAc3, false); - deviceProfile.MaxStaticBitrate = SyncHelper.AdjustBitrate(deviceProfile.MaxStaticBitrate, quality); - - return new SyncJobOptions - { - DeviceProfile = deviceProfile, - IsConverting = IsConverting(profile, quality) - }; - } - - private bool IsConverting(string profile, string quality) - { - return !string.Equals(profile, "original", StringComparison.OrdinalIgnoreCase); - } - - public IEnumerable GetQualityOptions(string targetId) - { - return GetQualityOptions(targetId, null); - } - - public IEnumerable GetQualityOptions(string targetId, User user) - { - foreach (var provider in _providers) - { - foreach (var target in GetSyncTargets(provider)) - { - if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase)) - { - return GetQualityOptions(provider, target, user); - } - } - } - - return new List(); - } - - private IEnumerable GetQualityOptions(ISyncProvider provider, SyncTarget target, User user) - { - var hasQuality = provider as IHasSyncQuality; - if (hasQuality != null) - { - var options = hasQuality.GetQualityOptions(target); - - if (user != null && !user.Policy.EnableSyncTranscoding) - { - options = options.Where(i => i.IsOriginalQuality); - } - - return options; - } - - // Default options for providers that don't override - return new List - { - new SyncQualityOption - { - Name = "High", - Id = "high", - IsDefault = true - }, - new SyncQualityOption - { - Name = "Medium", - Id = "medium" - }, - new SyncQualityOption - { - Name = "Low", - Id = "low" - }, - new SyncQualityOption - { - Name = "Custom", - Id = "custom" - } - }; - } - - public IEnumerable GetProfileOptions(string targetId, User user) - { - foreach (var provider in _providers) - { - foreach (var target in GetSyncTargets(provider)) - { - if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase)) - { - return GetProfileOptions(provider, target, user); - } - } - } - - return new List(); - } - - public IEnumerable GetProfileOptions(string targetId) - { - return GetProfileOptions(targetId, null); - } - - private IEnumerable GetProfileOptions(ISyncProvider provider, SyncTarget target, User user) - { - var hasQuality = provider as IHasSyncQuality; - if (hasQuality != null) - { - return hasQuality.GetProfileOptions(target); - } - - var list = new List(); - - list.Add(new SyncProfileOption - { - Name = "Original", - Id = "Original", - Description = "Syncs original files as-is.", - EnableQualityOptions = false - }); - - if (user == null || user.Policy.EnableSyncTranscoding) - { - list.Add(new SyncProfileOption - { - Name = "Baseline", - Id = "baseline", - Description = "Designed for compatibility with all devices, including web browsers. Targets H264/AAC video and MP3 audio." - }); - - list.Add(new SyncProfileOption - { - Name = "General", - Id = "general", - Description = "Designed for compatibility with Chromecast, Roku, Smart TV's, and other similar devices. Targets H264/AAC/AC3 video and MP3 audio.", - IsDefault = true - }); - } - - return list; - } - - protected internal void OnConversionComplete(SyncJobItem item) - { - var syncProvider = GetSyncProvider(item); - if (syncProvider is AppSyncProvider) - { - return; - } - - _taskManager.QueueIfNotRunning(); - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncNotificationEntryPoint.cs b/Emby.Server.Implementations/Sync/SyncNotificationEntryPoint.cs deleted file mode 100644 index 06e0e66a9..000000000 --- a/Emby.Server.Implementations/Sync/SyncNotificationEntryPoint.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Threading; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Sync; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncNotificationEntryPoint : IServerEntryPoint - { - private readonly ISessionManager _sessionManager; - private readonly ISyncManager _syncManager; - - public SyncNotificationEntryPoint(ISyncManager syncManager, ISessionManager sessionManager) - { - _syncManager = syncManager; - _sessionManager = sessionManager; - } - - public void Run() - { - _syncManager.SyncJobItemUpdated += _syncManager_SyncJobItemUpdated; - } - - private async void _syncManager_SyncJobItemUpdated(object sender, GenericEventArgs e) - { - var item = e.Argument; - - if (item.Status == SyncJobItemStatus.ReadyToTransfer) - { - try - { - await _sessionManager.SendMessageToUserDeviceSessions(item.TargetId, "SyncJobItemReady", item, CancellationToken.None).ConfigureAwait(false); - } - catch - { - - } - } - - if (item.Status == SyncJobItemStatus.Cancelled) - { - try - { - await _sessionManager.SendMessageToUserDeviceSessions(item.TargetId, "SyncJobItemCancelled", item, CancellationToken.None).ConfigureAwait(false); - } - catch - { - - } - } - } - - public void Dispose() - { - _syncManager.SyncJobItemUpdated -= _syncManager_SyncJobItemUpdated; - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncRegistrationInfo.cs b/Emby.Server.Implementations/Sync/SyncRegistrationInfo.cs deleted file mode 100644 index c2658c5c5..000000000 --- a/Emby.Server.Implementations/Sync/SyncRegistrationInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Common.Security; -using System.Threading.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncRegistrationInfo : IRequiresRegistration - { - private readonly ISecurityManager _securityManager; - - public static SyncRegistrationInfo Instance; - - public SyncRegistrationInfo(ISecurityManager securityManager) - { - _securityManager = securityManager; - Instance = this; - } - - private bool _registered; - public bool IsRegistered - { - get { return _registered; } - } - - public async Task LoadRegistrationInfoAsync() - { - var info = await _securityManager.GetRegistrationStatus("sync").ConfigureAwait(false); - - _registered = info.IsValid; - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncRepository.cs b/Emby.Server.Implementations/Sync/SyncRepository.cs deleted file mode 100644 index aafce3500..000000000 --- a/Emby.Server.Implementations/Sync/SyncRepository.cs +++ /dev/null @@ -1,847 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Emby.Server.Implementations.Data; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Sync; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncRepository : BaseSqliteRepository, ISyncRepository - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - private readonly IJsonSerializer _json; - - public SyncRepository(ILogger logger, IJsonSerializer json, IServerApplicationPaths appPaths) - : base(logger) - { - _json = json; - DbFilePath = Path.Combine(appPaths.DataPath, "sync14.db"); - } - - private class SyncSummary - { - public Dictionary Items { get; set; } - - public SyncSummary() - { - Items = new Dictionary(); - } - } - - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - string[] queries = { - - "create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Profile TEXT, Quality TEXT, Bitrate INT, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, Category TEXT, ParentId TEXT, UnwatchedOnly BIT, ItemLimit INT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)", - - "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT, ItemDateModifiedTicks BIGINT)", - - "drop index if exists idx_SyncJobItems2", - "drop index if exists idx_SyncJobItems3", - "drop index if exists idx_SyncJobs1", - "drop index if exists idx_SyncJobs", - "drop index if exists idx_SyncJobItems1", - "create index if not exists idx_SyncJobItems4 on SyncJobItems(TargetId,ItemId,Status,Progress,DateCreated)", - "create index if not exists idx_SyncJobItems5 on SyncJobItems(TargetId,Status,ItemId,Progress)", - - "create index if not exists idx_SyncJobs2 on SyncJobs(TargetId,Status,ItemIds,Progress)", - - "pragma shrink_memory" - }; - - connection.RunQueries(queries); - - connection.RunInTransaction(db => - { - var existingColumnNames = GetColumnNames(db, "SyncJobs"); - AddColumn(db, "SyncJobs", "Profile", "TEXT", existingColumnNames); - AddColumn(db, "SyncJobs", "Bitrate", "INT", existingColumnNames); - - existingColumnNames = GetColumnNames(db, "SyncJobItems"); - AddColumn(db, "SyncJobItems", "ItemDateModifiedTicks", "BIGINT", existingColumnNames); - }, TransactionMode); - } - } - - protected override bool EnableTempStoreMemory - { - get - { - return true; - } - } - - private const string BaseJobSelectText = "select Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs"; - private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks from SyncJobItems"; - - public SyncJob GetJob(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - CheckDisposed(); - - var guid = new Guid(id); - - if (guid == Guid.Empty) - { - throw new ArgumentNullException("id"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = BaseJobSelectText + " where Id=?"; - var paramList = new List(); - - paramList.Add(guid.ToGuidParamValue()); - - foreach (var row in connection.Query(commandText, paramList.ToArray())) - { - return GetJob(row); - } - - return null; - } - } - } - - private SyncJob GetJob(IReadOnlyList reader) - { - var info = new SyncJob - { - Id = reader[0].ReadGuid().ToString("N"), - TargetId = reader[1].ToString(), - Name = reader[2].ToString() - }; - - if (reader[3].SQLiteType != SQLiteType.Null) - { - info.Profile = reader[3].ToString(); - } - - if (reader[4].SQLiteType != SQLiteType.Null) - { - info.Quality = reader[4].ToString(); - } - - if (reader[5].SQLiteType != SQLiteType.Null) - { - info.Bitrate = reader[5].ToInt(); - } - - if (reader[6].SQLiteType != SQLiteType.Null) - { - info.Status = (SyncJobStatus)Enum.Parse(typeof(SyncJobStatus), reader[6].ToString(), true); - } - - if (reader[7].SQLiteType != SQLiteType.Null) - { - info.Progress = reader[7].ToDouble(); - } - - if (reader[8].SQLiteType != SQLiteType.Null) - { - info.UserId = reader[8].ToString(); - } - - if (reader[9].SQLiteType != SQLiteType.Null) - { - info.RequestedItemIds = reader[9].ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); - } - - if (reader[10].SQLiteType != SQLiteType.Null) - { - info.Category = (SyncCategory)Enum.Parse(typeof(SyncCategory), reader[10].ToString(), true); - } - - if (reader[11].SQLiteType != SQLiteType.Null) - { - info.ParentId = reader[11].ToString(); - } - - if (reader[12].SQLiteType != SQLiteType.Null) - { - info.UnwatchedOnly = reader[12].ToBool(); - } - - if (reader[13].SQLiteType != SQLiteType.Null) - { - info.ItemLimit = reader[13].ToInt(); - } - - info.SyncNewContent = reader[14].ToBool(); - - info.DateCreated = reader[15].ReadDateTime(); - info.DateLastModified = reader[16].ReadDateTime(); - info.ItemCount = reader[17].ToInt(); - - return info; - } - - public Task Create(SyncJob job) - { - return InsertOrUpdate(job, true); - } - - public Task Update(SyncJob job) - { - return InsertOrUpdate(job, false); - } - - private async Task InsertOrUpdate(SyncJob job, bool insert) - { - if (job == null) - { - throw new ArgumentNullException("job"); - } - - CheckDisposed(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - string commandText; - - if (insert) - { - commandText = "insert into SyncJobs (Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Profile, @Quality, @Bitrate, @Status, @Progress, @UserId, @ItemIds, @Category, @ParentId, @UnwatchedOnly, @ItemLimit, @SyncNewContent, @DateCreated, @DateLastModified, @ItemCount)"; - } - else - { - commandText = "update SyncJobs set TargetId=@TargetId,Name=@Name,Profile=@Profile,Quality=@Quality,Bitrate=@Bitrate,Status=@Status,Progress=@Progress,UserId=@UserId,ItemIds=@ItemIds,Category=@Category,ParentId=@ParentId,UnwatchedOnly=@UnwatchedOnly,ItemLimit=@ItemLimit,SyncNewContent=@SyncNewContent,DateCreated=@DateCreated,DateLastModified=@DateLastModified,ItemCount=@ItemCount where Id=@Id"; - } - - connection.RunInTransaction(conn => - { - using (var statement = PrepareStatementSafe(connection, commandText)) - { - statement.TryBind("@TargetId", job.TargetId); - statement.TryBind("@Name", job.Name); - statement.TryBind("@Profile", job.Profile); - statement.TryBind("@Quality", job.Quality); - statement.TryBind("@Bitrate", job.Bitrate); - statement.TryBind("@Status", job.Status.ToString()); - statement.TryBind("@Progress", job.Progress); - statement.TryBind("@UserId", job.UserId); - - statement.TryBind("@ItemIds", string.Join(",", job.RequestedItemIds.ToArray())); - - if (job.Category.HasValue) - { - statement.TryBind("@Category", job.Category.Value.ToString()); - } - else - { - statement.TryBindNull("@Category"); - } - - if (!string.IsNullOrWhiteSpace(job.ParentId)) - { - statement.TryBind("@ParentId", job.ParentId); - } - else - { - statement.TryBindNull("@ParentId"); - } - - statement.TryBind("@UnwatchedOnly", job.UnwatchedOnly); - - if (job.ItemLimit.HasValue) - { - statement.TryBind("@ItemLimit", job.ItemLimit); - } - else - { - statement.TryBindNull("@ItemLimit"); - } - - statement.TryBind("@SyncNewContent", job.SyncNewContent); - - statement.TryBind("@DateCreated", job.DateCreated.ToDateTimeParamValue()); - statement.TryBind("@DateLastModified", job.DateLastModified.ToDateTimeParamValue()); - - statement.TryBind("@ItemCount", job.ItemCount); - statement.TryBind("@Id", job.Id.ToGuidParamValue()); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - public async Task DeleteJob(string id) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - CheckDisposed(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(conn => - { - conn.Execute("delete from SyncJobs where Id=?", id.ToGuidParamValue()); - conn.Execute("delete from SyncJobItems where JobId=?", id); - }, TransactionMode); - } - } - } - - public QueryResult GetJobs(SyncJobQuery query) - { - if (query == null) - { - throw new ArgumentNullException("query"); - } - - CheckDisposed(); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = BaseJobSelectText; - var paramList = new List(); - - var whereClauses = new List(); - - if (query.Statuses.Length > 0) - { - var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray()); - - whereClauses.Add(string.Format("Status in ({0})", statuses)); - } - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - whereClauses.Add("TargetId=?"); - paramList.Add(query.TargetId); - } - if (!string.IsNullOrWhiteSpace(query.ExcludeTargetIds)) - { - var excludeIds = (query.ExcludeTargetIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - if (excludeIds.Length == 1) - { - whereClauses.Add("TargetId<>?"); - paramList.Add(excludeIds[0]); - } - else if (excludeIds.Length > 1) - { - whereClauses.Add("TargetId<>?"); - paramList.Add(excludeIds[0]); - } - } - if (!string.IsNullOrWhiteSpace(query.UserId)) - { - whereClauses.Add("UserId=?"); - paramList.Add(query.UserId); - } - if (!string.IsNullOrWhiteSpace(query.ItemId)) - { - whereClauses.Add("ItemIds like ?"); - paramList.Add("%" + query.ItemId + "%"); - } - if (query.SyncNewContent.HasValue) - { - whereClauses.Add("SyncNewContent=?"); - paramList.Add(query.SyncNewContent.Value); - } - - commandText += " mainTable"; - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - var startIndex = query.StartIndex ?? 0; - if (startIndex > 0) - { - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobs ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC LIMIT {0})", - startIndex.ToString(_usCulture))); - } - - if (whereClauses.Count > 0) - { - commandText += " where " + string.Join(" AND ", whereClauses.ToArray()); - } - - commandText += " ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC"; - - if (query.Limit.HasValue) - { - commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); - } - - var list = new List(); - var count = connection.Query("select count (Id) from SyncJobs" + whereTextWithoutPaging, paramList.ToArray()) - .SelectScalarInt() - .First(); - - foreach (var row in connection.Query(commandText, paramList.ToArray())) - { - list.Add(GetJob(row)); - } - - return new QueryResult() - { - Items = list.ToArray(), - TotalRecordCount = count - }; - } - } - } - - public SyncJobItem GetJobItem(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - CheckDisposed(); - - var guid = new Guid(id); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = BaseJobItemSelectText + " where Id=?"; - var paramList = new List(); - - paramList.Add(guid.ToGuidParamValue()); - - foreach (var row in connection.Query(commandText, paramList.ToArray())) - { - return GetJobItem(row); - } - - return null; - } - } - } - - private QueryResult GetJobItemReader(SyncJobItemQuery query, string baseSelectText, Func, T> itemFactory) - { - if (query == null) - { - throw new ArgumentNullException("query"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = baseSelectText; - var paramList = new List(); - - var whereClauses = new List(); - - if (!string.IsNullOrWhiteSpace(query.JobId)) - { - whereClauses.Add("JobId=?"); - paramList.Add(query.JobId); - } - if (!string.IsNullOrWhiteSpace(query.ItemId)) - { - whereClauses.Add("ItemId=?"); - paramList.Add(query.ItemId); - } - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - whereClauses.Add("TargetId=?"); - paramList.Add(query.TargetId); - } - - if (query.Statuses.Length > 0) - { - var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray()); - - whereClauses.Add(string.Format("Status in ({0})", statuses)); - } - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - var startIndex = query.StartIndex ?? 0; - if (startIndex > 0) - { - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobItems ORDER BY JobItemIndex, DateCreated LIMIT {0})", - startIndex.ToString(_usCulture))); - } - - if (whereClauses.Count > 0) - { - commandText += " where " + string.Join(" AND ", whereClauses.ToArray()); - } - - commandText += " ORDER BY JobItemIndex, DateCreated"; - - if (query.Limit.HasValue) - { - commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); - } - - var list = new List(); - var count = connection.Query("select count (Id) from SyncJobItems" + whereTextWithoutPaging, paramList.ToArray()) - .SelectScalarInt() - .First(); - - foreach (var row in connection.Query(commandText, paramList.ToArray())) - { - list.Add(itemFactory(row)); - } - - return new QueryResult() - { - Items = list.ToArray(), - TotalRecordCount = count - }; - } - } - } - - public Dictionary GetSyncedItemProgresses(SyncJobItemQuery query) - { - var result = new Dictionary(); - - var now = DateTime.UtcNow; - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = "select ItemId,Status,Progress from SyncJobItems"; - var whereClauses = new List(); - - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - whereClauses.Add("TargetId=@TargetId"); - } - - if (query.Statuses.Length > 0) - { - var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray()); - - whereClauses.Add(string.Format("Status in ({0})", statuses)); - } - - if (whereClauses.Count > 0) - { - commandText += " where " + string.Join(" AND ", whereClauses.ToArray()); - } - - var statementTexts = new List - { - commandText - }; - - commandText = commandText - .Replace("select ItemId,Status,Progress from SyncJobItems", "select ItemIds,Status,Progress from SyncJobs") - .Replace("'Synced'", "'Completed','CompletedWithError'"); - - statementTexts.Add(commandText); - - var statements = connection.PrepareAll(string.Join(";", statementTexts.ToArray())) - .ToList(); - - using (var statement = statements[0]) - { - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - statement.TryBind("@TargetId", query.TargetId); - } - - foreach (var row in statement.ExecuteQuery()) - { - AddStatusResult(row, result, false); - } - LogQueryTime("GetSyncedItemProgresses", commandText, now); - } - - now = DateTime.UtcNow; - - using (var statement = statements[1]) - { - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - statement.TryBind("@TargetId", query.TargetId); - } - - foreach (var row in statement.ExecuteQuery()) - { - AddStatusResult(row, result, true); - } - LogQueryTime("GetSyncedItemProgresses", commandText, now); - } - } - } - - return result; - } - - private void LogQueryTime(string methodName, string commandText, DateTime startDate) - { - var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds; - - var slowThreshold = 1000; - -#if DEBUG - slowThreshold = 50; -#endif - - if (elapsed >= slowThreshold) - { - Logger.Debug("{2} query time (slow): {0}ms. Query: {1}", - Convert.ToInt32(elapsed), - commandText, - methodName); - } - else - { - //Logger.Debug("{2} query time: {0}ms. Query: {1}", - // Convert.ToInt32(elapsed), - // cmd.CommandText, - // methodName); - } - } - - private void AddStatusResult(IReadOnlyList reader, Dictionary result, bool multipleIds) - { - if (reader[0].SQLiteType == SQLiteType.Null) - { - return; - } - - var itemIds = new List(); - - var ids = reader[0].ToString(); - - if (multipleIds) - { - itemIds = ids.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); - } - else - { - itemIds.Add(ids); - } - - if (reader[1].SQLiteType != SQLiteType.Null) - { - SyncJobItemStatus status; - var statusString = reader[1].ToString(); - if (string.Equals(statusString, "Completed", StringComparison.OrdinalIgnoreCase) || - string.Equals(statusString, "CompletedWithError", StringComparison.OrdinalIgnoreCase)) - { - status = SyncJobItemStatus.Synced; - } - else - { - status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), statusString, true); - } - - if (status == SyncJobItemStatus.Synced) - { - foreach (var itemId in itemIds) - { - result[itemId] = new SyncedItemProgress - { - Status = SyncJobItemStatus.Synced - }; - } - } - else - { - double progress = reader[2].SQLiteType == SQLiteType.Null ? 0.0 : reader[2].ToDouble(); - - foreach (var itemId in itemIds) - { - SyncedItemProgress currentStatus; - if (!result.TryGetValue(itemId, out currentStatus) || (currentStatus.Status != SyncJobItemStatus.Synced && progress >= currentStatus.Progress)) - { - result[itemId] = new SyncedItemProgress - { - Status = status, - Progress = progress - }; - } - } - } - } - } - - public QueryResult GetJobItems(SyncJobItemQuery query) - { - return GetJobItemReader(query, BaseJobItemSelectText, GetJobItem); - } - - public Task Create(SyncJobItem jobItem) - { - return InsertOrUpdate(jobItem, true); - } - - public Task Update(SyncJobItem jobItem) - { - return InsertOrUpdate(jobItem, false); - } - - private async Task InsertOrUpdate(SyncJobItem jobItem, bool insert) - { - if (jobItem == null) - { - throw new ArgumentNullException("jobItem"); - } - - CheckDisposed(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - string commandText; - - if (insert) - { - commandText = "insert into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - } - else - { - // cmd - commandText = "update SyncJobItems set ItemId=?,ItemName=?,MediaSourceId=?,JobId=?,TemporaryPath=?,OutputPath=?,Status=?,TargetId=?,DateCreated=?,Progress=?,AdditionalFiles=?,MediaSource=?,IsMarkedForRemoval=?,JobItemIndex=?,ItemDateModifiedTicks=? where Id=?"; - } - - var paramList = new List(); - paramList.Add(jobItem.ItemId); - paramList.Add(jobItem.ItemName); - paramList.Add(jobItem.MediaSourceId); - paramList.Add(jobItem.JobId); - paramList.Add(jobItem.TemporaryPath); - paramList.Add(jobItem.OutputPath); - paramList.Add(jobItem.Status.ToString()); - - paramList.Add(jobItem.TargetId); - paramList.Add(jobItem.DateCreated.ToDateTimeParamValue()); - paramList.Add(jobItem.Progress); - paramList.Add(_json.SerializeToString(jobItem.AdditionalFiles)); - paramList.Add(jobItem.MediaSource == null ? null : _json.SerializeToString(jobItem.MediaSource)); - paramList.Add(jobItem.IsMarkedForRemoval); - paramList.Add(jobItem.JobItemIndex); - paramList.Add(jobItem.ItemDateModifiedTicks); - - if (insert) - { - paramList.Insert(0, jobItem.Id.ToGuidParamValue()); - } - else - { - paramList.Add(jobItem.Id.ToGuidParamValue()); - } - - connection.RunInTransaction(conn => - { - conn.Execute(commandText, paramList.ToArray()); - }, TransactionMode); - } - } - } - - private SyncJobItem GetJobItem(IReadOnlyList reader) - { - var info = new SyncJobItem - { - Id = reader[0].ReadGuid().ToString("N"), - ItemId = reader[1].ToString() - }; - - if (reader[2].SQLiteType != SQLiteType.Null) - { - info.ItemName = reader[2].ToString(); - } - - if (reader[3].SQLiteType != SQLiteType.Null) - { - info.MediaSourceId = reader[3].ToString(); - } - - info.JobId = reader[4].ToString(); - - if (reader[5].SQLiteType != SQLiteType.Null) - { - info.TemporaryPath = reader[5].ToString(); - } - if (reader[6].SQLiteType != SQLiteType.Null) - { - info.OutputPath = reader[6].ToString(); - } - - if (reader[7].SQLiteType != SQLiteType.Null) - { - info.Status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), reader[7].ToString(), true); - } - - info.TargetId = reader[8].ToString(); - - info.DateCreated = reader[9].ReadDateTime(); - - if (reader[10].SQLiteType != SQLiteType.Null) - { - info.Progress = reader[10].ToDouble(); - } - - if (reader[11].SQLiteType != SQLiteType.Null) - { - var json = reader[11].ToString(); - - if (!string.IsNullOrWhiteSpace(json)) - { - info.AdditionalFiles = _json.DeserializeFromString>(json); - } - } - - if (reader[12].SQLiteType != SQLiteType.Null) - { - var json = reader[12].ToString(); - - if (!string.IsNullOrWhiteSpace(json)) - { - info.MediaSource = _json.DeserializeFromString(json); - } - } - - info.IsMarkedForRemoval = reader[13].ToBool(); - info.JobItemIndex = reader[14].ToInt(); - - if (reader[15].SQLiteType != SQLiteType.Null) - { - info.ItemDateModifiedTicks = reader[15].ToInt64(); - } - - return info; - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/Emby.Server.Implementations/Sync/SyncedMediaSourceProvider.cs deleted file mode 100644 index 1e54885e6..000000000 --- a/Emby.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ /dev/null @@ -1,158 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncedMediaSourceProvider : IMediaSourceProvider - { - private readonly SyncManager _syncManager; - private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; - - public SyncedMediaSourceProvider(ISyncManager syncManager, IServerApplicationHost appHost, ILogger logger) - { - _appHost = appHost; - _logger = logger; - _syncManager = (SyncManager)syncManager; - } - - public async Task> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken) - { - var jobItemResult = _syncManager.GetJobItems(new SyncJobItemQuery - { - AddMetadata = false, - Statuses = new[] { SyncJobItemStatus.Synced }, - ItemId = item.Id.ToString("N") - }); - - var list = new List(); - - if (jobItemResult.Items.Length > 0) - { - var targets = _syncManager.ServerSyncProviders - .SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple(i, t))) - .ToList(); - - var serverId = _appHost.SystemId; - - foreach (var jobItem in jobItemResult.Items) - { - var targetTuple = targets.FirstOrDefault(i => string.Equals(i.Item2.Id, jobItem.TargetId, StringComparison.OrdinalIgnoreCase)); - - if (targetTuple != null) - { - var syncTarget = targetTuple.Item2; - var syncProvider = targetTuple.Item1; - var dataProvider = _syncManager.GetDataProvider(targetTuple.Item1, syncTarget); - - var localItems = await dataProvider.GetItems(syncTarget, serverId, item.Id.ToString("N")).ConfigureAwait(false); - - foreach (var localItem in localItems) - { - foreach (var mediaSource in localItem.Item.MediaSources) - { - AddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget); - } - } - } - } - } - - return list; - } - - private void AddMediaSource(List list, - LocalItem item, - MediaSourceInfo mediaSource, - IServerSyncProvider provider, - SyncTarget target) - { - SetStaticMediaSourceInfo(item, mediaSource); - - var requiresDynamicAccess = provider as IHasDynamicAccess; - - if (requiresDynamicAccess != null) - { - mediaSource.RequiresOpening = true; - - var keyList = new List(); - keyList.Add(provider.GetType().FullName.GetMD5().ToString("N")); - keyList.Add(target.Id.GetMD5().ToString("N")); - keyList.Add(item.Id); - mediaSource.OpenToken = string.Join(StreamIdDelimeterString, keyList.ToArray()); - } - - list.Add(mediaSource); - } - - // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message. - private const string StreamIdDelimeterString = "_"; - - public async Task> OpenMediaSource(string openToken, CancellationToken cancellationToken) - { - var openKeys = openToken.Split(new[] { StreamIdDelimeterString[0] }, 3); - - var provider = _syncManager.ServerSyncProviders - .FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); - - var target = provider.GetAllSyncTargets() - .FirstOrDefault(i => string.Equals(openKeys[1], i.Id.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); - - var dataProvider = _syncManager.GetDataProvider(provider, target); - var localItem = await dataProvider.Get(target, openKeys[2]).ConfigureAwait(false); - - var fileId = localItem.FileId; - if (string.IsNullOrWhiteSpace(fileId)) - { - } - - var requiresDynamicAccess = (IHasDynamicAccess)provider; - var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(fileId, target, cancellationToken).ConfigureAwait(false); - - var mediaSource = localItem.Item.MediaSources.First(); - mediaSource.LiveStreamId = Guid.NewGuid().ToString(); - SetStaticMediaSourceInfo(localItem, mediaSource); - - foreach (var stream in mediaSource.MediaStreams) - { - if (!string.IsNullOrWhiteSpace(stream.ExternalId)) - { - var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); - stream.Path = dynamicStreamInfo.Path; - } - } - - mediaSource.Path = dynamicInfo.Path; - mediaSource.Protocol = dynamicInfo.Protocol; - mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders; - - return new Tuple(mediaSource, null); - } - - private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource) - { - mediaSource.Id = item.Id; - mediaSource.SupportsTranscoding = false; - if (mediaSource.Protocol == MediaBrowser.Model.MediaInfo.MediaProtocol.File) - { - mediaSource.ETag = item.Id; - } - } - - public Task CloseMediaSource(string liveStreamId) - { - throw new NotImplementedException(); - } - } -} diff --git a/Emby.Server.Implementations/Sync/TargetDataProvider.cs b/Emby.Server.Implementations/Sync/TargetDataProvider.cs deleted file mode 100644 index cac8f0cd8..000000000 --- a/Emby.Server.Implementations/Sync/TargetDataProvider.cs +++ /dev/null @@ -1,208 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Sync -{ - public class TargetDataProvider : ISyncDataProvider - { - private readonly SyncTarget _target; - private readonly IServerSyncProvider _provider; - - private readonly SemaphoreSlim _dataLock = new SemaphoreSlim(1, 1); - private readonly SemaphoreSlim _remoteDataLock = new SemaphoreSlim(1, 1); - private List _items; - - private readonly ILogger _logger; - private readonly IJsonSerializer _json; - private readonly IFileSystem _fileSystem; - private readonly IApplicationPaths _appPaths; - private readonly IServerApplicationHost _appHost; - private readonly IMemoryStreamFactory _memoryStreamProvider; - - public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths, IMemoryStreamFactory memoryStreamProvider) - { - _logger = logger; - _json = json; - _provider = provider; - _target = target; - _fileSystem = fileSystem; - _appPaths = appPaths; - _memoryStreamProvider = memoryStreamProvider; - _appHost = appHost; - } - - private string[] GetRemotePath() - { - var parts = new List - { - _appHost.FriendlyName, - "data.json" - }; - - parts = parts.Select(i => GetValidFilename(_provider, i)).ToList(); - - return parts.ToArray(); - } - - private string GetValidFilename(IServerSyncProvider provider, string filename) - { - // We can always add this method to the sync provider if it's really needed - return _fileSystem.GetValidFilename(filename); - } - - private async Task> RetrieveItems(CancellationToken cancellationToken) - { - _logger.Debug("Getting {0} from {1}", string.Join(MediaSync.PathSeparatorString, GetRemotePath().ToArray()), _provider.Name); - - await _remoteDataLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var fileResult = await _provider.GetFiles(GetRemotePath().ToArray(), _target, cancellationToken).ConfigureAwait(false); - - if (fileResult.Items.Length > 0) - { - using (var stream = await _provider.GetFile(fileResult.Items[0].FullName, _target, new Progress(), cancellationToken)) - { - return _json.DeserializeFromStream>(stream); - } - } - } - finally - { - _remoteDataLock.Release(); - } - - return new List(); - } - - private async Task EnsureData(CancellationToken cancellationToken) - { - if (_items == null) - { - _items = await RetrieveItems(cancellationToken).ConfigureAwait(false); - } - } - - private async Task SaveData(List items, CancellationToken cancellationToken) - { - using (var stream = _memoryStreamProvider.CreateNew()) - { - _json.SerializeToStream(items, stream); - - // Save to sync provider - stream.Position = 0; - var remotePath = GetRemotePath(); - - await _remoteDataLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - _logger.Debug("Saving data.json to {0}. Remote path: {1}", _provider.Name, string.Join("/", remotePath)); - - await _provider.SendFile(stream, remotePath, _target, new Progress(), cancellationToken).ConfigureAwait(false); - } - finally - { - _remoteDataLock.Release(); - } - } - } - - private async Task GetData(bool enableCache, Func, T> dataFactory) - { - if (!enableCache) - { - var items = await RetrieveItems(CancellationToken.None).ConfigureAwait(false); - var newCache = items.ToList(); - var result = dataFactory(items); - await UpdateCache(newCache).ConfigureAwait(false); - return result; - } - - await _dataLock.WaitAsync().ConfigureAwait(false); - - try - { - await EnsureData(CancellationToken.None).ConfigureAwait(false); - - return dataFactory(_items); - } - finally - { - _dataLock.Release(); - } - } - - private async Task UpdateData(Func, List> action) - { - var items = await RetrieveItems(CancellationToken.None).ConfigureAwait(false); - items = action(items); - await SaveData(items.ToList(), CancellationToken.None).ConfigureAwait(false); - - await UpdateCache(null).ConfigureAwait(false); - } - - private async Task UpdateCache(List list) - { - await _dataLock.WaitAsync().ConfigureAwait(false); - - try - { - _items = list; - } - finally - { - _dataLock.Release(); - } - } - - public Task> GetLocalItems(SyncTarget target, string serverId) - { - return GetData(false, items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)).ToList()); - } - - public Task AddOrUpdate(SyncTarget target, LocalItem item) - { - return UpdateData(items => - { - var list = items.Where(i => !string.Equals(i.Id, item.Id, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - list.Add(item); - - return list; - }); - } - - public Task Delete(SyncTarget target, string id) - { - return UpdateData(items => items.Where(i => !string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)).ToList()); - } - - public Task Get(SyncTarget target, string id) - { - return GetData(true, items => items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase))); - } - - public Task> GetItems(SyncTarget target, string serverId, string itemId) - { - return GetData(true, items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)).ToList()); - } - - public Task> GetItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId) - { - return GetData(false, items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.SyncJobItemId, syncJobItemId, StringComparison.OrdinalIgnoreCase)).ToList()); - } - } -} diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 88627957b..7e638e171 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -3,6 +3,6 @@ - + \ No newline at end of file diff --git a/MediaBrowser.Api/ConnectService.cs b/MediaBrowser.Api/ConnectService.cs deleted file mode 100644 index 304dc366b..000000000 --- a/MediaBrowser.Api/ConnectService.cs +++ /dev/null @@ -1,177 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Connect; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Connect; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api -{ - [Route("/Users/{Id}/Connect/Link", "POST", Summary = "Creates a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class CreateConnectLink : IReturn - { - [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "ConnectUsername", Description = "Connect username", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ConnectUsername { get; set; } - } - - [Route("/Users/{Id}/Connect/Link", "DELETE", Summary = "Removes a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class DeleteConnectLink : IReturnVoid - { - [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Connect/Invite", "POST", Summary = "Creates a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class CreateConnectInvite : IReturn - { - [ApiMember(Name = "ConnectUsername", Description = "Connect username", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string ConnectUsername { get; set; } - - [ApiMember(Name = "SendingUserId", Description = "Sending User Id", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string SendingUserId { get; set; } - - [ApiMember(Name = "EnabledLibraries", Description = "EnabledLibraries", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string EnabledLibraries { get; set; } - - [ApiMember(Name = "EnabledChannels", Description = "EnabledChannels", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string EnabledChannels { get; set; } - - [ApiMember(Name = "EnableLiveTv", Description = "EnableLiveTv", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public bool EnableLiveTv { get; set; } - } - - - [Route("/Connect/Pending", "GET", Summary = "Creates a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class GetPendingGuests : IReturn> - { - } - - - [Route("/Connect/Pending", "DELETE", Summary = "Deletes a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class DeleteAuthorization : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Authorization Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Connect/Exchange", "GET", Summary = "Gets the corresponding local user from a connect user id")] - [Authenticated] - public class GetLocalUser : IReturn - { - [ApiMember(Name = "ConnectUserId", Description = "ConnectUserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ConnectUserId { get; set; } - } - - public class ConnectService : BaseApiService - { - private readonly IConnectManager _connectManager; - private readonly ISessionManager _sessionManager; - private readonly IAuthorizationContext _authContext; - - public ConnectService(IConnectManager connectManager, ISessionManager sessionManager, IAuthorizationContext authContext) - { - _connectManager = connectManager; - _sessionManager = sessionManager; - _authContext = authContext; - } - - public object Post(CreateConnectLink request) - { - return _connectManager.LinkUser(request.Id, request.ConnectUsername); - } - - public object Post(CreateConnectInvite request) - { - var enabledLibraries = (request.EnabledLibraries ?? string.Empty) - .Split(',') - .Where(i => !string.IsNullOrWhiteSpace(i)) - .ToArray(); - - var enabledChannels = (request.EnabledChannels ?? string.Empty) - .Split(',') - .Where(i => !string.IsNullOrWhiteSpace(i)) - .ToArray(); - - return _connectManager.InviteUser(new ConnectAuthorizationRequest - { - ConnectUserName = request.ConnectUsername, - SendingUserId = request.SendingUserId, - EnabledLibraries = enabledLibraries, - EnabledChannels = enabledChannels, - EnableLiveTv = request.EnableLiveTv - }); - } - - public void Delete(DeleteConnectLink request) - { - var task = _connectManager.RemoveConnect(request.Id); - - Task.WaitAll(task); - } - - public async Task Get(GetPendingGuests request) - { - var result = await _connectManager.GetPendingGuests().ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public void Delete(DeleteAuthorization request) - { - var task = _connectManager.CancelAuthorization(request.Id); - - Task.WaitAll(task); - } - - public async Task Get(GetLocalUser request) - { - var user = await _connectManager.GetLocalUser(request.ConnectUserId).ConfigureAwait(false); - - if (user == null) - { - throw new ResourceNotFoundException(); - } - - var auth = _authContext.GetAuthorizationInfo(Request); - - if (string.IsNullOrWhiteSpace(auth.Client)) - { - return ToOptimizedResult(new ConnectAuthenticationExchangeResult - { - AccessToken = user.ConnectAccessKey, - LocalUserId = user.Id.ToString("N") - }); - } - - var session = await _sessionManager.CreateNewSession(new AuthenticationRequest - { - App = auth.Client, - AppVersion = auth.Version, - DeviceId = auth.DeviceId, - DeviceName = auth.Device, - RemoteEndPoint = Request.RemoteIp, - Username = user.Name, - UserId = user.Id.ToString("N") - - }).ConfigureAwait(false); - - return ToOptimizedResult(new ConnectAuthenticationExchangeResult - { - AccessToken = session.AccessToken, - LocalUserId = session.User.Id - }); - } - } -} diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 909fc0623..dc4e57155 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -791,7 +791,7 @@ namespace MediaBrowser.Api.LiveTv ProviderChannels = providerChannels.Select(i => new NameIdPair { Name = i.Name, - Id = string.IsNullOrWhiteSpace(i.TunerChannelId) ? i.Id : i.TunerChannelId + Id = i.Id }).ToList(), diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 6c8c6b2ab..55ef65311 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -51,7 +51,6 @@ - diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 89d035649..5615649c2 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -1,5 +1,4 @@ -using System; -using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.LiveTv; using System.Collections.Generic; using System.Threading; @@ -23,7 +22,7 @@ namespace MediaBrowser.Controller.LiveTv /// Gets the channels. /// /// Task<IEnumerable<ChannelInfo>>. - Task> GetChannels(bool enableCache, CancellationToken cancellationToken); + Task> GetChannels(bool enableCache, CancellationToken cancellationToken); /// /// Gets the tuner infos. /// diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index 7dd92b5e8..62c3e00a8 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -67,12 +67,15 @@ ..\ThirdParty\emby\Emby.Server.Core.dll + + ..\ThirdParty\emby\Emby.Server.Sync.dll + False ..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll - ..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll + ..\packages\NLog.4.4.3\lib\net45\NLog.dll True @@ -83,16 +86,16 @@ ..\packages\SharpCompress.0.14.0\lib\net45\SharpCompress.dll True - - ..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll + + ..\packages\SimpleInjector.3.3.2\lib\net45\SimpleInjector.dll True - ..\packages\SQLitePCLRaw.core.1.1.1\lib\net45\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll True - ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.1\lib\net45\SQLitePCLRaw.provider.sqlite3.dll + ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll True diff --git a/MediaBrowser.Server.Mono/MonoAppHost.cs b/MediaBrowser.Server.Mono/MonoAppHost.cs index 32f6b74ce..e9ded5cd5 100644 --- a/MediaBrowser.Server.Mono/MonoAppHost.cs +++ b/MediaBrowser.Server.Mono/MonoAppHost.cs @@ -4,7 +4,9 @@ using System.Reflection; using Emby.Server.Connect; using Emby.Server.Core; using Emby.Server.Implementations; +using Emby.Server.Sync; using MediaBrowser.Controller.Connect; +using MediaBrowser.Controller.Sync; using MediaBrowser.IsoMounter; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -32,6 +34,11 @@ namespace MediaBrowser.Server.Mono return new ConnectManager(); } + protected override ISyncManager CreateSyncManager() + { + return new SyncManager(); + } + protected override void RestartInternal() { MainClass.Restart(StartupOptions); @@ -53,6 +60,7 @@ namespace MediaBrowser.Server.Mono list.Add(typeof(LinuxIsoManager).Assembly); list.Add(typeof(ConnectManager).Assembly); + list.Add(typeof(SyncManager).Assembly); return list; } diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config index a77a3a506..07c113f3e 100644 --- a/MediaBrowser.Server.Mono/app.config +++ b/MediaBrowser.Server.Mono/app.config @@ -20,6 +20,10 @@ + + + + diff --git a/MediaBrowser.Server.Mono/packages.config b/MediaBrowser.Server.Mono/packages.config index 465a05c08..81da308f5 100644 --- a/MediaBrowser.Server.Mono/packages.config +++ b/MediaBrowser.Server.Mono/packages.config @@ -1,10 +1,10 @@  - + - - - + + + \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config index 4bac6bb70..fae013d6e 100644 --- a/MediaBrowser.ServerApplication/App.config +++ b/MediaBrowser.ServerApplication/App.config @@ -49,7 +49,7 @@ - + diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 656b295c2..50b0aa21f 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -73,12 +73,15 @@ ..\ThirdParty\emby\Emby.Server.Core.dll + + ..\ThirdParty\emby\Emby.Server.Sync.dll + False ..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll - ..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll + ..\packages\NLog.4.4.3\lib\net45\NLog.dll True @@ -89,16 +92,16 @@ ..\packages\SharpCompress.0.14.0\lib\net45\SharpCompress.dll True - - ..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll + + ..\packages\SimpleInjector.3.3.2\lib\net45\SimpleInjector.dll True - ..\packages\SQLitePCLRaw.core.1.1.1\lib\net45\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll True - ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.1\lib\net45\SQLitePCLRaw.provider.sqlite3.dll + ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll True diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index d4753f57a..9f11dc322 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -9,7 +9,9 @@ using Emby.Server.Core; using Emby.Server.Implementations; using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.FFMpeg; +using Emby.Server.Sync; using MediaBrowser.Controller.Connect; +using MediaBrowser.Controller.Sync; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.System; @@ -34,6 +36,11 @@ namespace MediaBrowser.ServerApplication return new ConnectManager(); } + protected override ISyncManager CreateSyncManager() + { + return new SyncManager(); + } + protected override void RestartInternal() { MainStartup.Restart(); @@ -49,6 +56,7 @@ namespace MediaBrowser.ServerApplication } list.Add(typeof(ConnectManager).Assembly); + list.Add(typeof(SyncManager).Assembly); list.Add(GetType().Assembly); return list; diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index 2cebb9aea..68d0a7fda 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -1,10 +1,10 @@  - + - - - + + + \ No newline at end of file -- cgit v1.2.3