Răsfoiți Sursa

make more classes portable

Luke Pulverenti 8 ani în urmă
părinte
comite
5d55b36487
27 a modificat fișierele cu 118 adăugiri și 1606 ștergeri
  1. 1 1
      Emby.Common.Implementations/BaseApplicationHost.cs
  2. 4 3
      Emby.Server.Core/ApplicationHost.cs
  3. 16 0
      Emby.Server.Core/Logging/ConsoleLogger.cs
  4. 9 5
      Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
  5. 1 2
      Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
  6. 1 1
      Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
  7. 3 3
      Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
  8. 0 36
      Emby.Server.Implementations/Connect/ConnectData.cs
  9. 0 218
      Emby.Server.Implementations/Connect/ConnectEntryPoint.cs
  10. 0 1193
      Emby.Server.Implementations/Connect/ConnectManager.cs
  11. 0 85
      Emby.Server.Implementations/Connect/Responses.cs
  12. 0 29
      Emby.Server.Implementations/Connect/Validator.cs
  13. 3 3
      Emby.Server.Implementations/Dto/DtoService.cs
  14. 6 5
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  15. 12 7
      Emby.Server.Implementations/Logging/UnhandledExceptionWriter.cs
  16. 6 5
      Emby.Server.Implementations/ServerApplicationPaths.cs
  17. 7 0
      MediaBrowser.Model/Logging/IConsoleLogger.cs
  18. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  19. 3 0
      MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
  20. 8 1
      MediaBrowser.Server.Mono/MonoAppHost.cs
  21. 10 2
      MediaBrowser.Server.Mono/Program.cs
  22. 12 3
      MediaBrowser.ServerApplication/MainStartup.cs
  23. 3 0
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  24. 8 0
      MediaBrowser.ServerApplication/WindowsAppHost.cs
  25. 1 1
      Nuget/MediaBrowser.Common.Internal.nuspec
  26. 1 1
      Nuget/MediaBrowser.Common.nuspec
  27. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 1 - 1
Emby.Common.Implementations/BaseApplicationHost.cs

@@ -147,7 +147,7 @@ namespace Emby.Common.Implementations
         /// <value>The configuration manager.</value>
         protected IConfigurationManager ConfigurationManager { get; private set; }
 
-        protected IFileSystem FileSystemManager { get; private set; }
+        public IFileSystem FileSystemManager { get; private set; }
 
         protected IIsoManager IsoManager { get; private set; }
 

+ 4 - 3
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();
+
         /// <summary>
         /// Registers resources that classes will depend on
         /// </summary>
@@ -635,7 +636,7 @@ namespace Emby.Server.Core
             var encryptionManager = new EncryptionManager();
             RegisterSingleInstance<IEncryptionManager>(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);

+ 16 - 0
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);
+        }
+    }
+}

+ 9 - 5
Emby.Common.Implementations/BaseApplicationPaths.cs → Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs

@@ -1,7 +1,8 @@
-using System.IO;
+using System;
+using System.IO;
 using MediaBrowser.Common.Configuration;
 
-namespace Emby.Common.Implementations
+namespace Emby.Server.Implementations.AppBase
 {
     /// <summary>
     /// Provides a base class to hold common application paths used by both the Ui and Server.
@@ -12,12 +13,15 @@ namespace Emby.Common.Implementations
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
         /// </summary>
-        protected BaseApplicationPaths(string programDataPath, string appFolderPath)
+        protected BaseApplicationPaths(string programDataPath, string appFolderPath, Action<string> createDirectoryFn)
         {
             ProgramDataPath = programDataPath;
             ProgramSystemPath = appFolderPath;
+            CreateDirectoryFn = createDirectoryFn;
         }
 
+        protected Action<string> CreateDirectoryFn;
+
         public string ProgramDataPath { get; private set; }
 
         /// <summary>
@@ -41,7 +45,7 @@ namespace Emby.Common.Implementations
                 {
                     _dataDirectory = Path.Combine(ProgramDataPath, "data");
 
-                    Directory.CreateDirectory(_dataDirectory);
+                    CreateDirectoryFn(_dataDirectory);
                 }
 
                 return _dataDirectory;
@@ -148,7 +152,7 @@ namespace Emby.Common.Implementations
                 {
                     _cachePath = Path.Combine(ProgramDataPath, "cache");
 
-                    Directory.CreateDirectory(_cachePath);
+                    CreateDirectoryFn(_cachePath);
                 }
 
                 return _cachePath;

+ 1 - 2
Emby.Common.Implementations/Configuration/BaseConfigurationManager.cs → Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs

@@ -7,13 +7,12 @@ 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
+namespace Emby.Server.Implementations.AppBase
 {
     /// <summary>
     /// Class BaseConfigurationManager

+ 1 - 1
Emby.Common.Implementations/Configuration/ConfigurationHelper.cs → Emby.Server.Implementations/AppBase/ConfigurationHelper.cs

@@ -4,7 +4,7 @@ using System.Linq;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 
-namespace Emby.Common.Implementations.Configuration
+namespace Emby.Server.Implementations.AppBase
 {
     /// <summary>
     /// Class ConfigurationHelper

+ 3 - 3
Emby.Server.Core/Configuration/ServerConfigurationManager.cs → Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs

@@ -2,7 +2,7 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using Emby.Common.Implementations.Configuration;
+using Emby.Server.Implementations.AppBase;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Controller;
@@ -17,7 +17,7 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 
-namespace Emby.Server.Core.Configuration
+namespace Emby.Server.Implementations.Configuration
 {
     /// <summary>
     /// Class ServerConfigurationManager
@@ -187,7 +187,7 @@ namespace Emby.Server.Core.Configuration
                 // Validate
                 if (!FileSystem.DirectoryExists(newPath))
                 {
-                    throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
+                    throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
                 }
 
                 EnsureWriteAccess(newPath);

+ 0 - 36
Emby.Server.Implementations/Connect/ConnectData.cs

@@ -1,36 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Emby.Server.Implementations.Connect
-{
-    public class ConnectData
-    {
-        /// <summary>
-        /// Gets or sets the server identifier.
-        /// </summary>
-        /// <value>The server identifier.</value>
-        public string ServerId { get; set; }
-        /// <summary>
-        /// Gets or sets the access key.
-        /// </summary>
-        /// <value>The access key.</value>
-        public string AccessKey { get; set; }
-
-        /// <summary>
-        /// Gets or sets the authorizations.
-        /// </summary>
-        /// <value>The authorizations.</value>
-        public List<ConnectAuthorizationInternal> PendingAuthorizations { get; set; }
-
-        /// <summary>
-        /// Gets or sets the last authorizations refresh.
-        /// </summary>
-        /// <value>The last authorizations refresh.</value>
-        public DateTime LastAuthorizationsRefresh { get; set; }
-
-        public ConnectData()
-        {
-            PendingAuthorizations = new List<ConnectAuthorizationInternal>();
-        }
-    }
-}

+ 0 - 218
Emby.Server.Implementations/Connect/ConnectEntryPoint.cs

@@ -1,218 +0,0 @@
-using MediaBrowser.Common;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Connect;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
-using System;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Threading;
-
-namespace Emby.Server.Implementations.Connect
-{
-    public class ConnectEntryPoint : IServerEntryPoint
-    {
-        private ITimer _timer;
-        private IpAddressInfo _cachedIpAddress;
-        private readonly IHttpClient _httpClient;
-        private readonly IApplicationPaths _appPaths;
-        private readonly ILogger _logger;
-        private readonly IConnectManager _connectManager;
-
-        private readonly INetworkManager _networkManager;
-        private readonly IApplicationHost _appHost;
-        private readonly IFileSystem _fileSystem;
-        private readonly ITimerFactory _timerFactory;
-        private readonly IEncryptionManager _encryption;
-
-        public ConnectEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, INetworkManager networkManager, IConnectManager connectManager, IApplicationHost appHost, IFileSystem fileSystem, ITimerFactory timerFactory, IEncryptionManager encryption)
-        {
-            _httpClient = httpClient;
-            _appPaths = appPaths;
-            _logger = logger;
-            _networkManager = networkManager;
-            _connectManager = connectManager;
-            _appHost = appHost;
-            _fileSystem = fileSystem;
-            _timerFactory = timerFactory;
-            _encryption = encryption;
-        }
-
-        public void Run()
-        {
-            LoadCachedAddress();
-
-            _timer = _timerFactory.Create(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(1));
-            ((ConnectManager)_connectManager).Start();
-        }
-
-        private readonly string[] _ipLookups =
-        {
-            "http://bot.whatismyipaddress.com",
-            "https://connect.emby.media/service/ip"
-        };
-
-        private async void TimerCallback(object state)
-        {
-            IpAddressInfo validIpAddress = null;
-
-            foreach (var ipLookupUrl in _ipLookups)
-            {
-                try
-                {
-                    validIpAddress = await GetIpAddress(ipLookupUrl).ConfigureAwait(false);
-
-                    // Try to find the ipv4 address, if present
-                    if (validIpAddress.AddressFamily != IpAddressFamily.InterNetworkV6)
-                    {
-                        break;
-                    }
-                }
-                catch (HttpException)
-                {
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error getting connection info", ex);
-                }
-            }
-           
-            // If this produced an ipv6 address, try again
-            if (validIpAddress != null && validIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
-            {
-                foreach (var ipLookupUrl in _ipLookups)
-                {
-                    try
-                    {
-                        var newAddress = await GetIpAddress(ipLookupUrl, true).ConfigureAwait(false);
-
-                        // Try to find the ipv4 address, if present
-                        if (newAddress.AddressFamily != IpAddressFamily.InterNetworkV6)
-                        {
-                            validIpAddress = newAddress;
-                            break;
-                        }
-                    }
-                    catch (HttpException)
-                    {
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.ErrorException("Error getting connection info", ex);
-                    }
-                }
-            }
-
-            if (validIpAddress != null)
-            {
-                ((ConnectManager)_connectManager).OnWanAddressResolved(validIpAddress);
-                CacheAddress(validIpAddress);
-            }
-        }
-
-        private async Task<IpAddressInfo> GetIpAddress(string lookupUrl, bool preferIpv4 = false)
-        {
-            // Sometimes whatismyipaddress might fail, but it won't do us any good having users raise alarms over it.
-            var logErrors = false;
-
-#if DEBUG
-            logErrors = true;
-#endif
-            using (var stream = await _httpClient.Get(new HttpRequestOptions
-            {
-                Url = lookupUrl,
-                UserAgent = "Emby/" + _appHost.ApplicationVersion,
-                LogErrors = logErrors,
-
-                // Seeing block length errors with our server
-                EnableHttpCompression = false,
-                PreferIpv4 = preferIpv4,
-                BufferContent = false
-
-            }).ConfigureAwait(false))
-            {
-                using (var reader = new StreamReader(stream))
-                {
-                    var addressString = await reader.ReadToEndAsync().ConfigureAwait(false);
-
-                    return _networkManager.ParseIpAddress(addressString);
-                }
-            }
-        }
-
-        private string CacheFilePath
-        {
-            get { return Path.Combine(_appPaths.DataPath, "wan.dat"); }
-        }
-
-        private void CacheAddress(IpAddressInfo address)
-        {
-            if (_cachedIpAddress != null && _cachedIpAddress.Equals(address))
-            {
-                // no need to update the file if the address has not changed
-                return;
-            }
-
-            var path = CacheFilePath;
-
-            try
-            {
-                _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
-            }
-            catch (Exception ex)
-            {
-            }
-
-            try
-            {
-                _fileSystem.WriteAllText(path, _encryption.EncryptString(address.ToString()), Encoding.UTF8);
-                _cachedIpAddress = address;
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error saving data", ex);
-            }
-        }
-
-        private void LoadCachedAddress()
-        {
-            var path = CacheFilePath;
-
-            _logger.Info("Loading data from {0}", path);
-
-            try
-            {
-                var endpoint = _encryption.DecryptString(_fileSystem.ReadAllText(path, Encoding.UTF8));
-                IpAddressInfo ipAddress;
-
-                if (_networkManager.TryParseIpAddress(endpoint, out ipAddress))
-                {
-                    _cachedIpAddress = ipAddress;
-                    ((ConnectManager)_connectManager).OnWanAddressResolved(ipAddress);
-                }
-            }
-            catch (IOException)
-            {
-                // File isn't there. no biggie
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error loading data", ex);
-            }
-        }
-
-        public void Dispose()
-        {
-            if (_timer != null)
-            {
-                _timer.Dispose();
-                _timer = null;
-            }
-        }
-    }
-}

+ 0 - 1193
Emby.Server.Implementations/Connect/ConnectManager.cs

@@ -1,1193 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Common.Security;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Connect;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.Connect;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Common.Extensions;
-
-namespace Emby.Server.Implementations.Connect
-{
-    public class ConnectManager : IConnectManager
-    {
-        private readonly SemaphoreSlim _operationLock = new SemaphoreSlim(1, 1);
-
-        private readonly ILogger _logger;
-        private readonly IApplicationPaths _appPaths;
-        private readonly IJsonSerializer _json;
-        private readonly IEncryptionManager _encryption;
-        private readonly IHttpClient _httpClient;
-        private readonly IServerApplicationHost _appHost;
-        private readonly IServerConfigurationManager _config;
-        private readonly IUserManager _userManager;
-        private readonly IProviderManager _providerManager;
-        private readonly ISecurityManager _securityManager;
-        private readonly IFileSystem _fileSystem;
-
-        private ConnectData _data = new ConnectData();
-
-        public string ConnectServerId
-        {
-            get { return _data.ServerId; }
-        }
-        public string ConnectAccessKey
-        {
-            get { return _data.AccessKey; }
-        }
-
-        private IpAddressInfo DiscoveredWanIpAddress { get; set; }
-
-        public string WanIpAddress
-        {
-            get
-            {
-                var address = _config.Configuration.WanDdns;
-
-                if (!string.IsNullOrWhiteSpace(address))
-                {
-                    Uri newUri;
-
-                    if (Uri.TryCreate(address, UriKind.Absolute, out newUri))
-                    {
-                        address = newUri.Host;
-                    }
-                }
-
-                if (string.IsNullOrWhiteSpace(address) && DiscoveredWanIpAddress != null)
-                {
-                    if (DiscoveredWanIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6)
-                    {
-                        address = "[" + DiscoveredWanIpAddress + "]";
-                    }
-                    else
-                    {
-                        address = DiscoveredWanIpAddress.ToString();
-                    }
-                }
-
-                return address;
-            }
-        }
-
-        public string WanApiAddress
-        {
-            get
-            {
-                var ip = WanIpAddress;
-
-                if (!string.IsNullOrEmpty(ip))
-                {
-                    if (!ip.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
-                        !ip.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
-                    {
-                        ip = (_appHost.EnableHttps ? "https://" : "http://") + ip;
-                    }
-
-                    ip += ":";
-                    ip += _appHost.EnableHttps ? _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture) : _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture);
-
-                    return ip;
-                }
-
-                return null;
-            }
-        }
-
-        private string XApplicationValue
-        {
-            get { return _appHost.Name + "/" + _appHost.ApplicationVersion; }
-        }
-
-        public ConnectManager(ILogger logger,
-            IApplicationPaths appPaths,
-            IJsonSerializer json,
-            IEncryptionManager encryption,
-            IHttpClient httpClient,
-            IServerApplicationHost appHost,
-            IServerConfigurationManager config, IUserManager userManager, IProviderManager providerManager, ISecurityManager securityManager, IFileSystem fileSystem)
-        {
-            _logger = logger;
-            _appPaths = appPaths;
-            _json = json;
-            _encryption = encryption;
-            _httpClient = httpClient;
-            _appHost = appHost;
-            _config = config;
-            _userManager = userManager;
-            _providerManager = providerManager;
-            _securityManager = securityManager;
-            _fileSystem = fileSystem;
-
-            LoadCachedData();
-        }
-
-        internal void Start()
-        {
-            _config.ConfigurationUpdated += _config_ConfigurationUpdated;
-        }
-
-        internal void OnWanAddressResolved(IpAddressInfo address)
-        {
-            DiscoveredWanIpAddress = address;
-
-            var task = UpdateConnectInfo();
-        }
-
-        private async Task UpdateConnectInfo()
-        {
-            await _operationLock.WaitAsync().ConfigureAwait(false);
-
-            try
-            {
-                await UpdateConnectInfoInternal().ConfigureAwait(false);
-            }
-            finally
-            {
-                _operationLock.Release();
-            }
-        }
-
-        private async Task UpdateConnectInfoInternal()
-        {
-            var wanApiAddress = WanApiAddress;
-
-            if (string.IsNullOrWhiteSpace(wanApiAddress))
-            {
-                _logger.Warn("Cannot update Emby Connect information without a WanApiAddress");
-                return;
-            }
-
-            try
-            {
-                var localAddress = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
-
-                var hasExistingRecord = !string.IsNullOrWhiteSpace(ConnectServerId) &&
-                                  !string.IsNullOrWhiteSpace(ConnectAccessKey);
-
-                var createNewRegistration = !hasExistingRecord;
-
-                if (hasExistingRecord)
-                {
-                    try
-                    {
-                        await UpdateServerRegistration(wanApiAddress, localAddress).ConfigureAwait(false);
-                    }
-                    catch (HttpException ex)
-                    {
-                        if (!ex.StatusCode.HasValue || !new[] { HttpStatusCode.NotFound, HttpStatusCode.Unauthorized }.Contains(ex.StatusCode.Value))
-                        {
-                            throw;
-                        }
-
-                        createNewRegistration = true;
-                    }
-                }
-
-                if (createNewRegistration)
-                {
-                    await CreateServerRegistration(wanApiAddress, localAddress).ConfigureAwait(false);
-                }
-
-                _lastReportedIdentifier = GetConnectReportingIdentifier(localAddress, wanApiAddress);
-
-                await RefreshAuthorizationsInternal(true, CancellationToken.None).ConfigureAwait(false);
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error registering with Connect", ex);
-            }
-        }
-
-        private string _lastReportedIdentifier;
-        private async Task<string> GetConnectReportingIdentifier()
-        {
-            var url = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
-            return GetConnectReportingIdentifier(url, WanApiAddress);
-        }
-        private string GetConnectReportingIdentifier(string localAddress, string remoteAddress)
-        {
-            return (remoteAddress ?? string.Empty) + (localAddress ?? string.Empty);
-        }
-
-        async void _config_ConfigurationUpdated(object sender, EventArgs e)
-        {
-            // If info hasn't changed, don't report anything
-            var connectIdentifier = await GetConnectReportingIdentifier().ConfigureAwait(false);
-            if (string.Equals(_lastReportedIdentifier, connectIdentifier, StringComparison.OrdinalIgnoreCase))
-            {
-                return;
-            }
-
-            await UpdateConnectInfo().ConfigureAwait(false);
-        }
-
-        private async Task CreateServerRegistration(string wanApiAddress, string localAddress)
-        {
-            if (string.IsNullOrWhiteSpace(wanApiAddress))
-            {
-                throw new ArgumentNullException("wanApiAddress");
-            }
-
-            var url = "Servers";
-            url = GetConnectUrl(url);
-
-            var postData = new Dictionary<string, string>
-            {
-                {"name", _appHost.FriendlyName},
-                {"url", wanApiAddress},
-                {"systemId", _appHost.SystemId}
-            };
-
-            if (!string.IsNullOrWhiteSpace(localAddress))
-            {
-                postData["localAddress"] = localAddress;
-            }
-
-            var options = new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = CancellationToken.None,
-                BufferContent = false
-            };
-
-            options.SetPostData(postData);
-            SetApplicationHeader(options);
-
-            using (var response = await _httpClient.Post(options).ConfigureAwait(false))
-            {
-                var data = _json.DeserializeFromStream<ServerRegistrationResponse>(response.Content);
-
-                _data.ServerId = data.Id;
-                _data.AccessKey = data.AccessKey;
-
-                CacheData();
-            }
-        }
-
-        private async Task UpdateServerRegistration(string wanApiAddress, string localAddress)
-        {
-            if (string.IsNullOrWhiteSpace(wanApiAddress))
-            {
-                throw new ArgumentNullException("wanApiAddress");
-            }
-
-            if (string.IsNullOrWhiteSpace(ConnectServerId))
-            {
-                throw new ArgumentNullException("ConnectServerId");
-            }
-
-            var url = "Servers";
-            url = GetConnectUrl(url);
-            url += "?id=" + ConnectServerId;
-
-            var postData = new Dictionary<string, string>
-            {
-                {"name", _appHost.FriendlyName},
-                {"url", wanApiAddress},
-                {"systemId", _appHost.SystemId}
-            };
-
-            if (!string.IsNullOrWhiteSpace(localAddress))
-            {
-                postData["localAddress"] = localAddress;
-            }
-
-            var options = new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = CancellationToken.None,
-                BufferContent = false
-            };
-
-            options.SetPostData(postData);
-
-            SetServerAccessToken(options);
-            SetApplicationHeader(options);
-
-            // No need to examine the response
-            using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
-            {
-            }
-        }
-
-        private readonly object _dataFileLock = new object();
-        private string CacheFilePath
-        {
-            get { return Path.Combine(_appPaths.DataPath, "connect.txt"); }
-        }
-
-        private void CacheData()
-        {
-            var path = CacheFilePath;
-
-            try
-            {
-                _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
-
-                var json = _json.SerializeToString(_data);
-
-                var encrypted = _encryption.EncryptString(json);
-
-                lock (_dataFileLock)
-                {
-                    _fileSystem.WriteAllText(path, encrypted, Encoding.UTF8);
-                }
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error saving data", ex);
-            }
-        }
-
-        private void LoadCachedData()
-        {
-            var path = CacheFilePath;
-
-            _logger.Info("Loading data from {0}", path);
-
-            try
-            {
-                lock (_dataFileLock)
-                {
-                    var encrypted = _fileSystem.ReadAllText(path, Encoding.UTF8);
-
-                    var json = _encryption.DecryptString(encrypted);
-
-                    _data = _json.DeserializeFromString<ConnectData>(json);
-                }
-            }
-            catch (IOException)
-            {
-                // File isn't there. no biggie
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error loading data", ex);
-            }
-        }
-
-        private User GetUser(string id)
-        {
-            var user = _userManager.GetUserById(id);
-
-            if (user == null)
-            {
-                throw new ArgumentException("User not found.");
-            }
-
-            return user;
-        }
-
-        private string GetConnectUrl(string handler)
-        {
-            return "https://connect.emby.media/service/" + handler;
-        }
-
-        public async Task<UserLinkResult> LinkUser(string userId, string connectUsername)
-        {
-            if (string.IsNullOrWhiteSpace(userId))
-            {
-                throw new ArgumentNullException("userId");
-            }
-            if (string.IsNullOrWhiteSpace(connectUsername))
-            {
-                throw new ArgumentNullException("connectUsername");
-            }
-            if (string.IsNullOrWhiteSpace(ConnectServerId))
-            {
-                await UpdateConnectInfo().ConfigureAwait(false);
-            }
-
-            await _operationLock.WaitAsync().ConfigureAwait(false);
-
-            try
-            {
-                return await LinkUserInternal(userId, connectUsername).ConfigureAwait(false);
-            }
-            finally
-            {
-                _operationLock.Release();
-            }
-        }
-
-        private async Task<UserLinkResult> LinkUserInternal(string userId, string connectUsername)
-        {
-            if (string.IsNullOrWhiteSpace(ConnectServerId))
-            {
-                throw new ArgumentNullException("ConnectServerId");
-            }
-
-            var connectUser = await GetConnectUser(new ConnectUserQuery
-            {
-                NameOrEmail = connectUsername
-
-            }, CancellationToken.None).ConfigureAwait(false);
-
-            if (!connectUser.IsActive)
-            {
-                throw new ArgumentException("The Emby account has been disabled.");
-            }
-
-            var existingUser = _userManager.Users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectUser.Id) && !string.IsNullOrWhiteSpace(i.ConnectAccessKey));
-            if (existingUser != null)
-            {
-                throw new InvalidOperationException("This connect user is already linked to local user " + existingUser.Name);
-            }
-
-            var user = GetUser(userId);
-
-            if (!string.IsNullOrWhiteSpace(user.ConnectUserId))
-            {
-                await RemoveConnect(user, user.ConnectUserId).ConfigureAwait(false);
-            }
-
-            var url = GetConnectUrl("ServerAuthorizations");
-
-            var options = new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = CancellationToken.None,
-                BufferContent = false
-            };
-
-            var accessToken = Guid.NewGuid().ToString("N");
-
-            var postData = new Dictionary<string, string>
-            {
-                {"serverId", ConnectServerId},
-                {"userId", connectUser.Id},
-                {"userType", "Linked"},
-                {"accessToken", accessToken}
-            };
-
-            options.SetPostData(postData);
-
-            SetServerAccessToken(options);
-            SetApplicationHeader(options);
-
-            var result = new UserLinkResult();
-
-            // No need to examine the response
-            using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
-            {
-                var response = _json.DeserializeFromStream<ServerUserAuthorizationResponse>(stream);
-
-                result.IsPending = string.Equals(response.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase);
-            }
-
-            user.ConnectAccessKey = accessToken;
-            user.ConnectUserName = connectUser.Name;
-            user.ConnectUserId = connectUser.Id;
-            user.ConnectLinkType = UserLinkType.LinkedUser;
-
-            await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
-
-            await _userManager.UpdateConfiguration(user.Id.ToString("N"), user.Configuration);
-
-            await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
-
-            return result;
-        }
-
-        public async Task<UserLinkResult> InviteUser(ConnectAuthorizationRequest request)
-        {
-            if (string.IsNullOrWhiteSpace(ConnectServerId))
-            {
-                await UpdateConnectInfo().ConfigureAwait(false);
-            }
-
-            await _operationLock.WaitAsync().ConfigureAwait(false);
-
-            try
-            {
-                return await InviteUserInternal(request).ConfigureAwait(false);
-            }
-            finally
-            {
-                _operationLock.Release();
-            }
-        }
-
-        private async Task<UserLinkResult> InviteUserInternal(ConnectAuthorizationRequest request)
-        {
-            var connectUsername = request.ConnectUserName;
-            var sendingUserId = request.SendingUserId;
-
-            if (string.IsNullOrWhiteSpace(connectUsername))
-            {
-                throw new ArgumentNullException("connectUsername");
-            }
-            if (string.IsNullOrWhiteSpace(ConnectServerId))
-            {
-                throw new ArgumentNullException("ConnectServerId");
-            }
-
-            var sendingUser = GetUser(sendingUserId);
-            var requesterUserName = sendingUser.ConnectUserName;
-
-            if (string.IsNullOrWhiteSpace(requesterUserName))
-            {
-                throw new ArgumentException("A Connect account is required in order to send invitations.");
-            }
-
-            string connectUserId = null;
-            var result = new UserLinkResult();
-
-            try
-            {
-                var connectUser = await GetConnectUser(new ConnectUserQuery
-                {
-                    NameOrEmail = connectUsername
-
-                }, CancellationToken.None).ConfigureAwait(false);
-
-                if (!connectUser.IsActive)
-                {
-                    throw new ArgumentException("The Emby account is not active. Please ensure the account has been activated by following the instructions within the email confirmation.");
-                }
-
-                connectUserId = connectUser.Id;
-                result.GuestDisplayName = connectUser.Name;
-            }
-            catch (HttpException ex)
-            {
-                if (!ex.StatusCode.HasValue || ex.IsTimedOut)
-                {
-                    throw new Exception("Unable to invite guest, " + ex.Message, ex);
-                }
-
-                // If they entered a username, then whatever the error is just throw it, for example, user not found
-                if (!Validator.EmailIsValid(connectUsername))
-                {
-                    if (ex.StatusCode.Value == HttpStatusCode.NotFound)
-                    {
-                        throw new ResourceNotFoundException();
-                    }
-                    throw;
-                }
-
-                if (ex.StatusCode.Value != HttpStatusCode.NotFound)
-                {
-                    throw;
-                }
-            }
-
-            if (string.IsNullOrWhiteSpace(connectUserId))
-            {
-                return await SendNewUserInvitation(requesterUserName, connectUsername).ConfigureAwait(false);
-            }
-
-            var url = GetConnectUrl("ServerAuthorizations");
-
-            var options = new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = CancellationToken.None,
-                BufferContent = false
-            };
-
-            var accessToken = Guid.NewGuid().ToString("N");
-
-            var postData = new Dictionary<string, string>
-            {
-                {"serverId", ConnectServerId},
-                {"userId", connectUserId},
-                {"userType", "Guest"},
-                {"accessToken", accessToken},
-                {"requesterUserName", requesterUserName}
-            };
-
-            options.SetPostData(postData);
-
-            SetServerAccessToken(options);
-            SetApplicationHeader(options);
-
-            // No need to examine the response
-            using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
-            {
-                var response = _json.DeserializeFromStream<ServerUserAuthorizationResponse>(stream);
-
-                result.IsPending = string.Equals(response.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase);
-
-                _data.PendingAuthorizations.Add(new ConnectAuthorizationInternal
-                {
-                    ConnectUserId = response.UserId,
-                    Id = response.Id,
-                    ImageUrl = response.UserImageUrl,
-                    UserName = response.UserName,
-                    EnabledLibraries = request.EnabledLibraries,
-                    EnabledChannels = request.EnabledChannels,
-                    EnableLiveTv = request.EnableLiveTv,
-                    AccessToken = accessToken
-                });
-
-                CacheData();
-            }
-
-            await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
-
-            return result;
-        }
-
-        private async Task<UserLinkResult> SendNewUserInvitation(string fromName, string email)
-        {
-            var url = GetConnectUrl("users/invite");
-
-            var options = new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = CancellationToken.None,
-                BufferContent = false
-            };
-
-            var postData = new Dictionary<string, string>
-            {
-                {"email", email},
-                {"requesterUserName", fromName}
-            };
-
-            options.SetPostData(postData);
-            SetApplicationHeader(options);
-
-            // No need to examine the response
-            using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
-            {
-            }
-
-            return new UserLinkResult
-            {
-                IsNewUserInvitation = true,
-                GuestDisplayName = email
-            };
-        }
-
-        public Task RemoveConnect(string userId)
-        {
-            var user = GetUser(userId);
-
-            return RemoveConnect(user, user.ConnectUserId);
-        }
-
-        private async Task RemoveConnect(User user, string connectUserId)
-        {
-            if (!string.IsNullOrWhiteSpace(connectUserId))
-            {
-                await CancelAuthorizationByConnectUserId(connectUserId).ConfigureAwait(false);
-            }
-
-            user.ConnectAccessKey = null;
-            user.ConnectUserName = null;
-            user.ConnectUserId = null;
-            user.ConnectLinkType = null;
-
-            await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
-        }
-
-        private async Task<ConnectUser> GetConnectUser(ConnectUserQuery query, CancellationToken cancellationToken)
-        {
-            var url = GetConnectUrl("user");
-
-            if (!string.IsNullOrWhiteSpace(query.Id))
-            {
-                url = url + "?id=" + WebUtility.UrlEncode(query.Id);
-            }
-            else if (!string.IsNullOrWhiteSpace(query.NameOrEmail))
-            {
-                url = url + "?nameOrEmail=" + WebUtility.UrlEncode(query.NameOrEmail);
-            }
-            else if (!string.IsNullOrWhiteSpace(query.Name))
-            {
-                url = url + "?name=" + WebUtility.UrlEncode(query.Name);
-            }
-            else if (!string.IsNullOrWhiteSpace(query.Email))
-            {
-                url = url + "?name=" + WebUtility.UrlEncode(query.Email);
-            }
-            else
-            {
-                throw new ArgumentException("Empty ConnectUserQuery supplied");
-            }
-
-            var options = new HttpRequestOptions
-            {
-                CancellationToken = cancellationToken,
-                Url = url,
-                BufferContent = false
-            };
-
-            SetServerAccessToken(options);
-            SetApplicationHeader(options);
-
-            using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
-            {
-                var response = _json.DeserializeFromStream<GetConnectUserResponse>(stream);
-
-                return new ConnectUser
-                {
-                    Email = response.Email,
-                    Id = response.Id,
-                    Name = response.Name,
-                    IsActive = response.IsActive,
-                    ImageUrl = response.ImageUrl
-                };
-            }
-        }
-
-        private void SetApplicationHeader(HttpRequestOptions options)
-        {
-            options.RequestHeaders.Add("X-Application", XApplicationValue);
-        }
-
-        private void SetServerAccessToken(HttpRequestOptions options)
-        {
-            if (string.IsNullOrWhiteSpace(ConnectAccessKey))
-            {
-                throw new ArgumentNullException("ConnectAccessKey");
-            }
-
-            options.RequestHeaders.Add("X-Connect-Token", ConnectAccessKey);
-        }
-
-        public async Task RefreshAuthorizations(CancellationToken cancellationToken)
-        {
-            await _operationLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                await RefreshAuthorizationsInternal(true, cancellationToken).ConfigureAwait(false);
-            }
-            finally
-            {
-                _operationLock.Release();
-            }
-        }
-
-        private async Task RefreshAuthorizationsInternal(bool refreshImages, CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrWhiteSpace(ConnectServerId))
-            {
-                throw new ArgumentNullException("ConnectServerId");
-            }
-
-            var url = GetConnectUrl("ServerAuthorizations");
-
-            url += "?serverId=" + ConnectServerId;
-
-            var options = new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = cancellationToken,
-                BufferContent = false
-            };
-
-            SetServerAccessToken(options);
-            SetApplicationHeader(options);
-
-            try
-            {
-                using (var stream = (await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)).Content)
-                {
-                    var list = _json.DeserializeFromStream<List<ServerUserAuthorizationResponse>>(stream);
-
-                    await RefreshAuthorizations(list, refreshImages).ConfigureAwait(false);
-                }
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error refreshing server authorizations.", ex);
-            }
-        }
-
-        private async Task RefreshAuthorizations(List<ServerUserAuthorizationResponse> list, bool refreshImages)
-        {
-            var users = _userManager.Users.ToList();
-
-            // Handle existing authorizations that were removed by the Connect server
-            // Handle existing authorizations whose status may have been updated
-            foreach (var user in users)
-            {
-                if (!string.IsNullOrWhiteSpace(user.ConnectUserId))
-                {
-                    var connectEntry = list.FirstOrDefault(i => string.Equals(i.UserId, user.ConnectUserId, StringComparison.OrdinalIgnoreCase));
-
-                    if (connectEntry == null)
-                    {
-                        var deleteUser = user.ConnectLinkType.HasValue &&
-                                         user.ConnectLinkType.Value == UserLinkType.Guest;
-
-                        user.ConnectUserId = null;
-                        user.ConnectAccessKey = null;
-                        user.ConnectUserName = null;
-                        user.ConnectLinkType = null;
-
-                        await _userManager.UpdateUser(user).ConfigureAwait(false);
-
-                        if (deleteUser)
-                        {
-                            _logger.Debug("Deleting guest user {0}", user.Name);
-                            await _userManager.DeleteUser(user).ConfigureAwait(false);
-                        }
-                    }
-                    else
-                    {
-                        var changed = !string.Equals(user.ConnectAccessKey, connectEntry.AccessToken, StringComparison.OrdinalIgnoreCase);
-
-                        if (changed)
-                        {
-                            user.ConnectUserId = connectEntry.UserId;
-                            user.ConnectAccessKey = connectEntry.AccessToken;
-
-                            await _userManager.UpdateUser(user).ConfigureAwait(false);
-                        }
-                    }
-                }
-            }
-
-            var currentPendingList = _data.PendingAuthorizations.ToList();
-            var newPendingList = new List<ConnectAuthorizationInternal>();
-
-            foreach (var connectEntry in list)
-            {
-                if (string.Equals(connectEntry.UserType, "guest", StringComparison.OrdinalIgnoreCase))
-                {
-                    var currentPendingEntry = currentPendingList.FirstOrDefault(i => string.Equals(i.Id, connectEntry.Id, StringComparison.OrdinalIgnoreCase));
-
-                    if (string.Equals(connectEntry.AcceptStatus, "accepted", StringComparison.OrdinalIgnoreCase))
-                    {
-                        var user = _userManager.Users
-                            .FirstOrDefault(i => string.Equals(i.ConnectUserId, connectEntry.UserId, StringComparison.OrdinalIgnoreCase));
-
-                        if (user == null)
-                        {
-                            // Add user
-                            user = await _userManager.CreateUser(_userManager.MakeValidUsername(connectEntry.UserName)).ConfigureAwait(false);
-
-                            user.ConnectUserName = connectEntry.UserName;
-                            user.ConnectUserId = connectEntry.UserId;
-                            user.ConnectLinkType = UserLinkType.Guest;
-                            user.ConnectAccessKey = connectEntry.AccessToken;
-
-                            await _userManager.UpdateUser(user).ConfigureAwait(false);
-
-                            user.Policy.IsHidden = true;
-                            user.Policy.EnableLiveTvManagement = false;
-                            user.Policy.EnableContentDeletion = false;
-                            user.Policy.EnableRemoteControlOfOtherUsers = false;
-                            user.Policy.EnableSharedDeviceControl = false;
-                            user.Policy.IsAdministrator = false;
-
-                            if (currentPendingEntry != null)
-                            {
-                                user.Policy.EnabledFolders = currentPendingEntry.EnabledLibraries;
-                                user.Policy.EnableAllFolders = false;
-
-                                user.Policy.EnabledChannels = currentPendingEntry.EnabledChannels;
-                                user.Policy.EnableAllChannels = false;
-
-                                user.Policy.EnableLiveTvAccess = currentPendingEntry.EnableLiveTv;
-                            }
-
-                            await _userManager.UpdateConfiguration(user.Id.ToString("N"), user.Configuration);
-                        }
-                    }
-                    else if (string.Equals(connectEntry.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase))
-                    {
-                        currentPendingEntry = currentPendingEntry ?? new ConnectAuthorizationInternal();
-
-                        currentPendingEntry.ConnectUserId = connectEntry.UserId;
-                        currentPendingEntry.ImageUrl = connectEntry.UserImageUrl;
-                        currentPendingEntry.UserName = connectEntry.UserName;
-                        currentPendingEntry.Id = connectEntry.Id;
-                        currentPendingEntry.AccessToken = connectEntry.AccessToken;
-
-                        newPendingList.Add(currentPendingEntry);
-                    }
-                }
-            }
-
-            _data.PendingAuthorizations = newPendingList;
-
-            if (!newPendingList.Select(i => i.Id).SequenceEqual(currentPendingList.Select(i => i.Id), StringComparer.Ordinal))
-            {
-                CacheData();
-            }
-
-            await RefreshGuestNames(list, refreshImages).ConfigureAwait(false);
-        }
-
-        private async Task RefreshGuestNames(List<ServerUserAuthorizationResponse> list, bool refreshImages)
-        {
-            var users = _userManager.Users
-                .Where(i => !string.IsNullOrEmpty(i.ConnectUserId) && i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest)
-                    .ToList();
-
-            foreach (var user in users)
-            {
-                var authorization = list.FirstOrDefault(i => string.Equals(i.UserId, user.ConnectUserId, StringComparison.Ordinal));
-
-                if (authorization == null)
-                {
-                    _logger.Warn("Unable to find connect authorization record for user {0}", user.Name);
-                    continue;
-                }
-
-                var syncConnectName = true;
-                var syncConnectImage = true;
-
-                if (syncConnectName)
-                {
-                    var changed = !string.Equals(authorization.UserName, user.Name, StringComparison.OrdinalIgnoreCase);
-
-                    if (changed)
-                    {
-                        await user.Rename(authorization.UserName).ConfigureAwait(false);
-                    }
-                }
-
-                if (syncConnectImage)
-                {
-                    var imageUrl = authorization.UserImageUrl;
-
-                    if (!string.IsNullOrWhiteSpace(imageUrl))
-                    {
-                        var changed = false;
-
-                        if (!user.HasImage(ImageType.Primary))
-                        {
-                            changed = true;
-                        }
-                        else if (refreshImages)
-                        {
-                            using (var response = await _httpClient.SendAsync(new HttpRequestOptions
-                            {
-                                Url = imageUrl,
-                                BufferContent = false
-
-                            }, "HEAD").ConfigureAwait(false))
-                            {
-                                var length = response.ContentLength;
-
-                                if (length != _fileSystem.GetFileInfo(user.GetImageInfo(ImageType.Primary, 0).Path).Length)
-                                {
-                                    changed = true;
-                                }
-                            }
-                        }
-
-                        if (changed)
-                        {
-                            await _providerManager.SaveImage(user, imageUrl, ImageType.Primary, null, CancellationToken.None).ConfigureAwait(false);
-
-                            await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
-                            {
-                                ForceSave = true,
-
-                            }, CancellationToken.None).ConfigureAwait(false);
-                        }
-                    }
-                }
-            }
-        }
-
-        public async Task<List<ConnectAuthorization>> GetPendingGuests()
-        {
-            var time = DateTime.UtcNow - _data.LastAuthorizationsRefresh;
-
-            if (time.TotalMinutes >= 5)
-            {
-                await _operationLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
-
-                try
-                {
-                    await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
-
-                    _data.LastAuthorizationsRefresh = DateTime.UtcNow;
-                    CacheData();
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error refreshing authorization", ex);
-                }
-                finally
-                {
-                    _operationLock.Release();
-                }
-            }
-
-            return _data.PendingAuthorizations.Select(i => new ConnectAuthorization
-            {
-                ConnectUserId = i.ConnectUserId,
-                EnableLiveTv = i.EnableLiveTv,
-                EnabledChannels = i.EnabledChannels,
-                EnabledLibraries = i.EnabledLibraries,
-                Id = i.Id,
-                ImageUrl = i.ImageUrl,
-                UserName = i.UserName
-
-            }).ToList();
-        }
-
-        public async Task CancelAuthorization(string id)
-        {
-            await _operationLock.WaitAsync().ConfigureAwait(false);
-
-            try
-            {
-                await CancelAuthorizationInternal(id).ConfigureAwait(false);
-            }
-            finally
-            {
-                _operationLock.Release();
-            }
-        }
-
-        private async Task CancelAuthorizationInternal(string id)
-        {
-            var connectUserId = _data.PendingAuthorizations
-                .First(i => string.Equals(i.Id, id, StringComparison.Ordinal))
-                .ConnectUserId;
-
-            await CancelAuthorizationByConnectUserId(connectUserId).ConfigureAwait(false);
-
-            await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
-        }
-
-        private async Task CancelAuthorizationByConnectUserId(string connectUserId)
-        {
-            if (string.IsNullOrWhiteSpace(connectUserId))
-            {
-                throw new ArgumentNullException("connectUserId");
-            }
-            if (string.IsNullOrWhiteSpace(ConnectServerId))
-            {
-                throw new ArgumentNullException("ConnectServerId");
-            }
-
-            var url = GetConnectUrl("ServerAuthorizations");
-
-            var options = new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = CancellationToken.None,
-                BufferContent = false
-            };
-
-            var postData = new Dictionary<string, string>
-                {
-                    {"serverId", ConnectServerId},
-                    {"userId", connectUserId}
-                };
-
-            options.SetPostData(postData);
-
-            SetServerAccessToken(options);
-            SetApplicationHeader(options);
-
-            try
-            {
-                // No need to examine the response
-                using (var stream = (await _httpClient.SendAsync(options, "DELETE").ConfigureAwait(false)).Content)
-                {
-                }
-            }
-            catch (HttpException ex)
-            {
-                // If connect says the auth doesn't exist, we can handle that gracefully since this is a remove operation
-
-                if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
-                {
-                    throw;
-                }
-
-                _logger.Debug("Connect returned a 404 when removing a user auth link. Handling it.");
-            }
-        }
-
-        public async Task<ConnectAuthenticationResult> Authenticate(string username, string passwordMd5)
-        {
-            if (string.IsNullOrWhiteSpace(username))
-            {
-                throw new ArgumentNullException("username");
-            }
-
-            if (string.IsNullOrWhiteSpace(passwordMd5))
-            {
-                throw new ArgumentNullException("passwordMd5");
-            }
-
-            var options = new HttpRequestOptions
-            {
-                Url = GetConnectUrl("user/authenticate"),
-                BufferContent = false
-            };
-
-            options.SetPostData(new Dictionary<string, string>
-                {
-                    {"userName",username},
-                    {"password",passwordMd5}
-                });
-
-            SetApplicationHeader(options);
-
-            // No need to examine the response
-            using (var response = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
-            {
-                return _json.DeserializeFromStream<ConnectAuthenticationResult>(response);
-            }
-        }
-
-        public async Task<User> GetLocalUser(string connectUserId)
-        {
-            var user = _userManager.Users
-                .FirstOrDefault(i => string.Equals(i.ConnectUserId, connectUserId, StringComparison.OrdinalIgnoreCase));
-
-            if (user == null)
-            {
-                await RefreshAuthorizations(CancellationToken.None).ConfigureAwait(false);
-            }
-
-            return _userManager.Users
-                .FirstOrDefault(i => string.Equals(i.ConnectUserId, connectUserId, StringComparison.OrdinalIgnoreCase));
-        }
-
-        public User GetUserFromExchangeToken(string token)
-        {
-            if (string.IsNullOrWhiteSpace(token))
-            {
-                throw new ArgumentNullException("token");
-            }
-
-            return _userManager.Users.FirstOrDefault(u => string.Equals(token, u.ConnectAccessKey, StringComparison.OrdinalIgnoreCase));
-        }
-
-        public bool IsAuthorizationTokenValid(string token)
-        {
-            if (string.IsNullOrWhiteSpace(token))
-            {
-                throw new ArgumentNullException("token");
-            }
-
-            return _userManager.Users.Any(u => string.Equals(token, u.ConnectAccessKey, StringComparison.OrdinalIgnoreCase)) ||
-                _data.PendingAuthorizations.Select(i => i.AccessToken).Contains(token, StringComparer.OrdinalIgnoreCase);
-        }
-    }
-}

+ 0 - 85
Emby.Server.Implementations/Connect/Responses.cs

@@ -1,85 +0,0 @@
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Connect;
-
-namespace Emby.Server.Implementations.Connect
-{
-    public class ServerRegistrationResponse
-    {
-        public string Id { get; set; }
-        public string Url { get; set; }
-        public string Name { get; set; }
-        public string AccessKey { get; set; }
-    }
-
-    public class UpdateServerRegistrationResponse
-    {
-        public string Id { get; set; }
-        public string Url { get; set; }
-        public string Name { get; set; }
-    }
-
-    public class GetConnectUserResponse
-    {
-        public string Id { get; set; }
-        public string Name { get; set; }
-        public string DisplayName { get; set; }
-        public string Email { get; set; }
-        public bool IsActive { get; set; }
-        public string ImageUrl { get; set; }
-    }
-
-    public class ServerUserAuthorizationResponse
-    {
-        public string Id { get; set; }
-        public string ServerId { get; set; }
-        public string UserId { get; set; }
-        public string AccessToken { get; set; }
-        public string DateCreated { get; set; }
-        public bool IsActive { get; set; }
-        public string AcceptStatus { get; set; }
-        public string UserType { get; set; }
-        public string UserImageUrl { get; set; }
-        public string UserName { get; set; }
-    }
-
-    public class ConnectUserPreferences
-    {
-        public string[] PreferredAudioLanguages { get; set; }
-        public bool PlayDefaultAudioTrack { get; set; }
-        public string[] PreferredSubtitleLanguages { get; set; }
-        public SubtitlePlaybackMode SubtitleMode { get; set; }
-        public bool GroupMoviesIntoBoxSets { get; set; }
-
-        public ConnectUserPreferences()
-        {
-            PreferredAudioLanguages = new string[] { };
-            PreferredSubtitleLanguages = new string[] { };
-        }
-
-        public static ConnectUserPreferences FromUserConfiguration(UserConfiguration config)
-        {
-            return new ConnectUserPreferences
-            {
-                PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
-                SubtitleMode = config.SubtitleMode,
-                PreferredAudioLanguages = string.IsNullOrWhiteSpace(config.AudioLanguagePreference) ? new string[] { } : new[] { config.AudioLanguagePreference },
-                PreferredSubtitleLanguages = string.IsNullOrWhiteSpace(config.SubtitleLanguagePreference) ? new string[] { } : new[] { config.SubtitleLanguagePreference }
-            };
-        }
-
-        public void MergeInto(UserConfiguration config)
-        {
-
-        }
-    }
-
-    public class UserPreferencesDto<T>
-    {
-        public T data { get; set; }
-    }
-
-    public class ConnectAuthorizationInternal : ConnectAuthorization
-    {
-        public string AccessToken { get; set; }
-    }
-}

+ 0 - 29
Emby.Server.Implementations/Connect/Validator.cs

@@ -1,29 +0,0 @@
-using System.Text.RegularExpressions;
-
-namespace Emby.Server.Implementations.Connect
-{
-    public static class Validator
-    {
-        static readonly Regex ValidEmailRegex = CreateValidEmailRegex();
-
-        /// <summary>
-        /// Taken from http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx
-        /// </summary>
-        /// <returns></returns>
-        private static Regex CreateValidEmailRegex()
-        {
-            const string validEmailPattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|"
-                                             + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(?<!\.)\.)*)(?<!\.)"
-                                             + @"@[a-z0-9][\w\.-]*[a-z0-9]\.[a-z][a-z\.]*[a-z]$";
-
-            return new Regex(validEmailPattern, RegexOptions.IgnoreCase);
-        }
-
-        internal static bool EmailIsValid(string emailAddress)
-        {
-            bool isValid = ValidEmailRegex.IsMatch(emailAddress);
-
-            return isValid;
-        }
-    }
-}

+ 3 - 3
Emby.Server.Implementations/Dto/DtoService.cs

@@ -1031,7 +1031,7 @@ namespace Emby.Server.Implementations.Dto
 
             if (fields.Contains(ItemFields.Path))
             {
-                dto.Path = GetMappedPath(item);
+                dto.Path = GetMappedPath(item, owner);
             }
 
             dto.PremiereDate = item.PremiereDate;
@@ -1566,7 +1566,7 @@ namespace Emby.Server.Implementations.Dto
             }
         }
 
-        private string GetMappedPath(BaseItem item)
+        private string GetMappedPath(BaseItem item, BaseItem ownerItem)
         {
             var path = item.Path;
 
@@ -1574,7 +1574,7 @@ namespace Emby.Server.Implementations.Dto
 
             if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
             {
-                path = _libraryManager.GetPathAfterNetworkSubstitution(path, item);
+                path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item);
             }
 
             return path;

+ 6 - 5
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -35,6 +35,9 @@
     <Compile Include="Activity\ActivityLogEntryPoint.cs" />
     <Compile Include="Activity\ActivityManager.cs" />
     <Compile Include="Activity\ActivityRepository.cs" />
+    <Compile Include="AppBase\BaseApplicationPaths.cs" />
+    <Compile Include="AppBase\BaseConfigurationManager.cs" />
+    <Compile Include="AppBase\ConfigurationHelper.cs" />
     <Compile Include="Branding\BrandingConfigurationFactory.cs" />
     <Compile Include="Browser\BrowserLauncher.cs" />
     <Compile Include="Channels\ChannelConfigurations.cs" />
@@ -46,11 +49,7 @@
     <Compile Include="Collections\CollectionImageProvider.cs" />
     <Compile Include="Collections\CollectionManager.cs" />
     <Compile Include="Collections\CollectionsDynamicFolder.cs" />
-    <Compile Include="Connect\ConnectData.cs" />
-    <Compile Include="Connect\ConnectEntryPoint.cs" />
-    <Compile Include="Connect\ConnectManager.cs" />
-    <Compile Include="Connect\Responses.cs" />
-    <Compile Include="Connect\Validator.cs" />
+    <Compile Include="Configuration\ServerConfigurationManager.cs" />
     <Compile Include="Data\ManagedConnection.cs" />
     <Compile Include="Data\SqliteDisplayPreferencesRepository.cs" />
     <Compile Include="Data\SqliteFileOrganizationRepository.cs" />
@@ -179,6 +178,7 @@
     <Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
     <Compile Include="LiveTv\TunerHosts\QueueStream.cs" />
     <Compile Include="Localization\LocalizationManager.cs" />
+    <Compile Include="Logging\UnhandledExceptionWriter.cs" />
     <Compile Include="MediaEncoder\EncodingManager.cs" />
     <Compile Include="Migrations\IVersionMigration.cs" />
     <Compile Include="Migrations\LibraryScanMigration.cs" />
@@ -213,6 +213,7 @@
     <Compile Include="Security\MBLicenseFile.cs" />
     <Compile Include="Security\PluginSecurityManager.cs" />
     <Compile Include="Security\RegRecord.cs" />
+    <Compile Include="ServerApplicationPaths.cs" />
     <Compile Include="ServerManager\ServerManager.cs" />
     <Compile Include="ServerManager\WebSocketConnection.cs" />
     <Compile Include="Services\ServicePath.cs" />

+ 12 - 7
Emby.Server.Core/UnhandledExceptionWriter.cs → Emby.Server.Implementations/Logging/UnhandledExceptionWriter.cs

@@ -2,20 +2,25 @@
 using MediaBrowser.Model.Logging;
 using System;
 using System.IO;
+using MediaBrowser.Model.IO;
 
-namespace Emby.Server.Core
+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)
+        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)
@@ -24,15 +29,15 @@ namespace Emby.Server.Core
             _logManager.Flush();
 
             var path = Path.Combine(_appPaths.LogDirectoryPath, "unhandled_" + Guid.NewGuid() + ".txt");
-			Directory.CreateDirectory(Path.GetDirectoryName(path));
+            _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());
-            
-			File.WriteAllText(path, builder.ToString());
+            _console.WriteLine("UnhandledException");
+            _console.WriteLine(builder.ToString());
+
+            _fileSystem.WriteAllText(path, builder.ToString());
         }
     }
 }

+ 6 - 5
Emby.Server.Core/ServerApplicationPaths.cs → Emby.Server.Implementations/ServerApplicationPaths.cs

@@ -1,8 +1,9 @@
-using System.IO;
-using Emby.Common.Implementations;
+using System;
+using System.IO;
+using Emby.Server.Implementations.AppBase;
 using MediaBrowser.Controller;
 
-namespace Emby.Server.Core
+namespace Emby.Server.Implementations
 {
     /// <summary>
     /// Extends BaseApplicationPaths to add paths that are only applicable on the server
@@ -12,8 +13,8 @@ namespace Emby.Server.Core
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseApplicationPaths" /> class.
         /// </summary>
-        public ServerApplicationPaths(string programDataPath, string appFolderPath, string applicationResourcesPath)
-            : base(programDataPath, appFolderPath)
+        public ServerApplicationPaths(string programDataPath, string appFolderPath, string applicationResourcesPath, Action<string> createDirectoryFn)
+            : base(programDataPath, appFolderPath, createDirectoryFn)
         {
             ApplicationResourcesPath = applicationResourcesPath;
         }

+ 7 - 0
MediaBrowser.Model/Logging/IConsoleLogger.cs

@@ -0,0 +1,7 @@
+namespace MediaBrowser.Model.Logging
+{
+    public interface IConsoleLogger
+    {
+        void WriteLine(string message);
+    }
+}

+ 1 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -135,6 +135,7 @@
     <Compile Include="Dto\MetadataEditorInfo.cs" />
     <Compile Include="Dto\NameIdPair.cs" />
     <Compile Include="Dto\NameValuePair.cs" />
+    <Compile Include="Logging\IConsoleLogger.cs" />
     <Compile Include="Net\IpEndPointInfo.cs" />
     <Compile Include="Net\ISocket.cs" />
     <Compile Include="Net\ISocketFactory.cs" />

+ 3 - 0
MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj

@@ -61,6 +61,9 @@
     <Reference Include="Emby.Common.Implementations">
       <HintPath>..\ThirdParty\emby\Emby.Common.Implementations.dll</HintPath>
     </Reference>
+    <Reference Include="Emby.Server.Connect">
+      <HintPath>..\ThirdParty\emby\Emby.Server.Connect.dll</HintPath>
+    </Reference>
     <Reference Include="Emby.Server.Core">
       <HintPath>..\ThirdParty\emby\Emby.Server.Core.dll</HintPath>
     </Reference>

+ 8 - 1
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<Assembly>();
 
             list.Add(typeof(LinuxIsoManager).Assembly);
+            list.Add(typeof(ConnectManager).Assembly);
 
             return list;
         }

+ 10 - 2
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<string> createDirectoryFn = s => Directory.CreateDirectory(s);
+
+            return new ServerApplicationPaths(programDataPath, appFolderPath, Path.GetDirectoryName(applicationPath), createDirectoryFn);
         }
 
         private static readonly TaskCompletionSource<bool> ApplicationTaskCompletionSource = new TaskCompletionSource<bool>();
@@ -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)
             {

+ 12 - 3
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<string> 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);
         }
 
         /// <summary>
@@ -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)
             {

+ 3 - 0
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -67,6 +67,9 @@
     <Reference Include="Emby.Common.Implementations">
       <HintPath>..\ThirdParty\emby\Emby.Common.Implementations.dll</HintPath>
     </Reference>
+    <Reference Include="Emby.Server.Connect">
+      <HintPath>..\ThirdParty\emby\Emby.Server.Connect.dll</HintPath>
+    </Reference>
     <Reference Include="Emby.Server.Core">
       <HintPath>..\ThirdParty\emby\Emby.Server.Core.dll</HintPath>
     </Reference>

+ 8 - 0
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;

+ 1 - 1
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.680</version>
+        <version>3.0.681</version>
         <title>Emby.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.694</version>
+        <version>3.0.695</version>
         <title>Emby.Common</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.694</version>
+        <version>3.0.695</version>
         <title>Emby.Server.Core</title>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Emby Server.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.694" />
+            <dependency id="MediaBrowser.Common" version="3.0.695" />
         </dependencies>
     </metadata>
     <files>