From 6d250c4050ceb0bda33aad6a484fd05e508c4e27 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 4 Nov 2016 04:31:05 -0400 Subject: make dlna project portable --- Emby.Common.Implementations/Net/SocketFactory.cs | 113 +++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 Emby.Common.Implementations/Net/SocketFactory.cs (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs new file mode 100644 index 000000000..3a2cea12a --- /dev/null +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using MediaBrowser.Model.Net; + +namespace Emby.Common.Implementations.Net +{ + public class SocketFactory : ISocketFactory + { + // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS + // Be careful to check any changes compile and work for all platform projects it is shared in. + + // Not entirely happy with this. Would have liked to have done something more generic/reusable, + // but that wasn't really the point so kept to YAGNI principal for now, even if the + // interfaces are a bit ugly, specific and make assumptions. + + /// + /// Used by RSSDP components to create implementations of the interface, to perform platform agnostic socket communications. + /// + private IPAddress _LocalIP; + + /// + /// Default constructor. + /// + /// A string containing the IP address of the local network adapter to bind sockets to. Null or empty string will use . + public SocketFactory(string localIP) + { + if (String.IsNullOrEmpty(localIP)) + _LocalIP = IPAddress.Any; + else + _LocalIP = IPAddress.Parse(localIP); + } + + #region ISocketFactory Members + + /// + /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port. + /// + /// An integer specifying the local port to bind the socket to. + /// An implementation of the interface used by RSSDP components to perform socket operations. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] + public IUdpSocket CreateUdpSocket(int localPort) + { + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + try + { + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), _LocalIP)); + return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + + /// + /// Creates a new UDP socket that is a member of the specified multicast IP address, and binds it to the specified local port. + /// + /// The multicast IP address to make the socket a member of. + /// The multicast time to live value for the socket. + /// The number of the local port to bind to. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] + public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort) + { + if (ipAddress == null) throw new ArgumentNullException("ipAddress"); + if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", "ipAddress"); + if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", "multicastTimeToLive"); + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + try + { +#if NETSTANDARD1_3 + // The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket + // See https://github.com/dotnet/corefx/pull/11509 for more details + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + { + retVal.ExclusiveAddressUse = false; + } +#else + retVal.ExclusiveAddressUse = false; +#endif + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP)); + retVal.MulticastLoopback = true; + + return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + + #endregion + } +} -- cgit v1.2.3 From 72aaecb27930e04aa356febf35db2372bc417166 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 4 Nov 2016 14:56:47 -0400 Subject: move classes to new server project --- Emby.Common.Implementations/Net/SocketFactory.cs | 30 +- Emby.Common.Implementations/Net/UdpSocket.cs | 38 +- .../Networking/BaseNetworkManager.cs | 31 + Emby.Server.Implementations/Connect/ConnectData.cs | 36 + .../Connect/ConnectEntryPoint.cs | 199 ++++ .../Connect/ConnectManager.cs | 1189 +++++++++++++++++++ Emby.Server.Implementations/Connect/Responses.cs | 85 ++ Emby.Server.Implementations/Connect/Validator.cs | 29 + .../Emby.Server.Implementations.csproj | 9 + .../EntryPoints/UdpServerEntryPoint.cs | 85 ++ Emby.Server.Implementations/IO/FileRefresher.cs | 323 ++++++ .../Security/EncryptionManager.cs | 51 + Emby.Server.Implementations/Udp/UdpServer.cs | 247 ++++ MediaBrowser.Common/Net/INetworkManager.cs | 4 + MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- MediaBrowser.Model/Net/ISocketFactory.cs | 21 +- MediaBrowser.Model/Net/IUdpSocket.cs | 6 +- MediaBrowser.Model/Net/ReceivedUdpData.cs | 24 - MediaBrowser.Model/Net/SocketReceiveResult.cs | 24 + .../Connect/ConnectData.cs | 36 - .../Connect/ConnectEntryPoint.cs | 203 ---- .../Connect/ConnectManager.cs | 1192 -------------------- .../Connect/Responses.cs | 85 -- .../Connect/Validator.cs | 29 - .../EntryPoints/UdpServerEntryPoint.cs | 92 -- .../SocketSharp/WebSocketSharpListener.cs | 2 +- .../IO/FileRefresher.cs | 323 ------ .../IO/LibraryMonitor.cs | 14 +- .../Logging/PatternsLogger.cs | 63 -- .../MediaBrowser.Server.Implementations.csproj | 11 - .../Security/EncryptionManager.cs | 51 - .../Udp/UdpMessageReceivedEventArgs.cs | 21 - .../Udp/UdpServer.cs | 328 ------ .../ApplicationHost.cs | 7 +- RSSDP/SsdpCommunicationsServer.cs | 6 +- 35 files changed, 2387 insertions(+), 2509 deletions(-) create mode 100644 Emby.Server.Implementations/Connect/ConnectData.cs create mode 100644 Emby.Server.Implementations/Connect/ConnectEntryPoint.cs create mode 100644 Emby.Server.Implementations/Connect/ConnectManager.cs create mode 100644 Emby.Server.Implementations/Connect/Responses.cs create mode 100644 Emby.Server.Implementations/Connect/Validator.cs create mode 100644 Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs create mode 100644 Emby.Server.Implementations/IO/FileRefresher.cs create mode 100644 Emby.Server.Implementations/Security/EncryptionManager.cs create mode 100644 Emby.Server.Implementations/Udp/UdpServer.cs delete mode 100644 MediaBrowser.Model/Net/ReceivedUdpData.cs create mode 100644 MediaBrowser.Model/Net/SocketReceiveResult.cs delete mode 100644 MediaBrowser.Server.Implementations/Connect/ConnectData.cs delete mode 100644 MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs delete mode 100644 MediaBrowser.Server.Implementations/Connect/ConnectManager.cs delete mode 100644 MediaBrowser.Server.Implementations/Connect/Responses.cs delete mode 100644 MediaBrowser.Server.Implementations/Connect/Validator.cs delete mode 100644 MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs delete mode 100644 MediaBrowser.Server.Implementations/IO/FileRefresher.cs delete mode 100644 MediaBrowser.Server.Implementations/Logging/PatternsLogger.cs delete mode 100644 MediaBrowser.Server.Implementations/Security/EncryptionManager.cs delete mode 100644 MediaBrowser.Server.Implementations/Udp/UdpMessageReceivedEventArgs.cs delete mode 100644 MediaBrowser.Server.Implementations/Udp/UdpServer.cs (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 3a2cea12a..6f0ff2996 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -36,13 +36,37 @@ namespace Emby.Common.Implementations.Net #region ISocketFactory Members + /// + /// Creates a new UDP socket and binds it to the specified local port. + /// + /// An integer specifying the local port to bind the socket to. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] + public IUdpSocket CreateUdpSocket(int localPort) + { + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + try + { + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + return new UdpSocket(retVal, localPort, _LocalIP); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + /// /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port. /// /// An integer specifying the local port to bind the socket to. /// An implementation of the interface used by RSSDP components to perform socket operations. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] - public IUdpSocket CreateUdpSocket(int localPort) + public IUdpSocket CreateSsdpUdpSocket(int localPort) { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); @@ -52,7 +76,7 @@ namespace Emby.Common.Implementations.Net retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), _LocalIP)); - return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + return new UdpSocket(retVal, localPort, _LocalIP); } catch { @@ -97,7 +121,7 @@ namespace Emby.Common.Implementations.Net retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP)); retVal.MulticastLoopback = true; - return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + return new UdpSocket(retVal, localPort, _LocalIP); } catch { diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 86ce9c83b..997d3f25f 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -24,19 +24,13 @@ namespace Emby.Common.Implementations.Net #region Constructors - public UdpSocket(System.Net.Sockets.Socket socket, int localPort, string ipAddress) + public UdpSocket(System.Net.Sockets.Socket socket, int localPort, IPAddress ip) { if (socket == null) throw new ArgumentNullException("socket"); _Socket = socket; _LocalPort = localPort; - IPAddress ip = null; - if (String.IsNullOrEmpty(ipAddress)) - ip = IPAddress.Any; - else - ip = IPAddress.Parse(ipAddress); - _Socket.Bind(new IPEndPoint(ip, _LocalPort)); if (_LocalPort == 0) _LocalPort = (_Socket.LocalEndPoint as IPEndPoint).Port; @@ -46,11 +40,11 @@ namespace Emby.Common.Implementations.Net #region IUdpSocket Members - public Task ReceiveAsync() + public Task ReceiveAsync() { ThrowIfDisposed(); - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); System.Net.EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); @@ -74,22 +68,30 @@ namespace Emby.Common.Implementations.Net return tcs.Task; } - public Task SendTo(byte[] messageData, IpEndPointInfo endPoint) + public Task SendAsync(byte[] buffer, int size, IpEndPointInfo endPoint) { ThrowIfDisposed(); - if (messageData == null) throw new ArgumentNullException("messageData"); + if (buffer == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); #if NETSTANDARD1_6 - _Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); + + if (size != buffer.Length) + { + byte[] copy = new byte[size]; + Buffer.BlockCopy(buffer, 0, copy, 0, size); + buffer = copy; + } + + _Socket.SendTo(buffer, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); return Task.FromResult(true); #else var taskSource = new TaskCompletionSource(); try { - _Socket.BeginSendTo(messageData, 0, messageData.Length, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port), result => + _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port), result => { try { @@ -160,11 +162,11 @@ namespace Emby.Common.Implementations.Net var ipEndPoint = state.EndPoint as IPEndPoint; state.TaskCompletionSource.SetResult( - new ReceivedUdpData() + new SocketReceiveResult() { Buffer = state.Buffer, ReceivedBytes = bytesRead, - ReceivedFrom = ToIpEndPointInfo(ipEndPoint) + RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) } ); } @@ -215,11 +217,11 @@ namespace Emby.Common.Implementations.Net var ipEndPoint = state.EndPoint as IPEndPoint; state.TaskCompletionSource.SetResult( - new ReceivedUdpData() + new SocketReceiveResult { Buffer = state.Buffer, ReceivedBytes = bytesRead, - ReceivedFrom = ToIpEndPointInfo(ipEndPoint) + RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) } ); } @@ -258,7 +260,7 @@ namespace Emby.Common.Implementations.Net public System.Net.Sockets.Socket Socket { get; private set; } - public TaskCompletionSource TaskCompletionSource { get; set; } + public TaskCompletionSource TaskCompletionSource { get; set; } } diff --git a/Emby.Common.Implementations/Networking/BaseNetworkManager.cs b/Emby.Common.Implementations/Networking/BaseNetworkManager.cs index bab340e27..d1c299dc9 100644 --- a/Emby.Common.Implementations/Networking/BaseNetworkManager.cs +++ b/Emby.Common.Implementations/Networking/BaseNetworkManager.cs @@ -8,6 +8,7 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading.Tasks; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Net; namespace Emby.Common.Implementations.Networking { @@ -382,5 +383,35 @@ namespace Emby.Common.Implementations.Networking return hosts[0]; } + + public IpAddressInfo ParseIpAddress(string ipAddress) + { + IpAddressInfo info; + if (TryParseIpAddress(ipAddress, out info)) + { + return info; + } + + throw new ArgumentException("Invalid ip address: " + ipAddress); + } + + public bool TryParseIpAddress(string ipAddress, out IpAddressInfo ipAddressInfo) + { + IPAddress address; + if (IPAddress.TryParse(ipAddress, out address)) + { + + ipAddressInfo = new IpAddressInfo + { + Address = address.ToString(), + IsIpv6 = address.AddressFamily == AddressFamily.InterNetworkV6 + }; + + return true; + } + + ipAddressInfo = null; + return false; + } } } diff --git a/Emby.Server.Implementations/Connect/ConnectData.cs b/Emby.Server.Implementations/Connect/ConnectData.cs new file mode 100644 index 000000000..41b89ce52 --- /dev/null +++ b/Emby.Server.Implementations/Connect/ConnectData.cs @@ -0,0 +1,36 @@ +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 new file mode 100644 index 000000000..d7574d466 --- /dev/null +++ b/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs @@ -0,0 +1,199 @@ +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.Model.IO; +using MediaBrowser.Model.Threading; + +namespace Emby.Server.Implementations.Connect +{ + public class ConnectEntryPoint : IServerEntryPoint + { + private ITimer _timer; + 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; + + public ConnectEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, INetworkManager networkManager, IConnectManager connectManager, IApplicationHost appHost, IFileSystem fileSystem, ITimerFactory timerFactory) + { + _httpClient = httpClient; + _appPaths = appPaths; + _logger = logger; + _networkManager = networkManager; + _connectManager = connectManager; + _appHost = appHost; + _fileSystem = fileSystem; + _timerFactory = timerFactory; + } + + 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.IsIpv6) + { + 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.IsIpv6) + { + foreach (var ipLookupUrl in _ipLookups) + { + try + { + var newAddress = await GetIpAddress(ipLookupUrl, true).ConfigureAwait(false); + + // Try to find the ipv4 address, if present + if (!newAddress.IsIpv6) + { + 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.txt"); } + } + + private void CacheAddress(IpAddressInfo address) + { + var path = CacheFilePath; + + try + { + _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + _fileSystem.WriteAllText(path, address.ToString(), Encoding.UTF8); + } + 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 = _fileSystem.ReadAllText(path, Encoding.UTF8); + IpAddressInfo ipAddress; + + if (_networkManager.TryParseIpAddress(endpoint, out 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 new file mode 100644 index 000000000..6c2ac40c3 --- /dev/null +++ b/Emby.Server.Implementations/Connect/ConnectManager.cs @@ -0,0 +1,1189 @@ +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.IsIpv6) + { + 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) + { + throw; + } + + // 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 readonly SemaphoreSlim _connectImageSemaphore = new SemaphoreSlim(5, 5); + 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; + 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, _connectImageSemaphore, 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) + { + } + } + + 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 new file mode 100644 index 000000000..87cb6cdf9 --- /dev/null +++ b/Emby.Server.Implementations/Connect/Responses.cs @@ -0,0 +1,85 @@ +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 new file mode 100644 index 000000000..5c94fa71c --- /dev/null +++ b/Emby.Server.Implementations/Connect/Validator.cs @@ -0,0 +1,29 @@ +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!#$%&'*+/=?^_`{|}~]|(? + + + + + @@ -51,6 +56,7 @@ + @@ -68,6 +74,7 @@ + @@ -159,6 +166,7 @@ + @@ -217,6 +225,7 @@ + diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs new file mode 100644 index 000000000..df5a7c985 --- /dev/null +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -0,0 +1,85 @@ +using System; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using Emby.Server.Implementations.Udp; +using MediaBrowser.Model.Net; + +namespace Emby.Server.Implementations.EntryPoints +{ + /// + /// Class UdpServerEntryPoint + /// + public class UdpServerEntryPoint : IServerEntryPoint + { + /// + /// Gets or sets the UDP server. + /// + /// The UDP server. + private UdpServer UdpServer { get; set; } + + /// + /// The _logger + /// + private readonly ILogger _logger; + private readonly ISocketFactory _socketFactory; + private readonly IServerApplicationHost _appHost; + private readonly IJsonSerializer _json; + + public const int PortNumber = 7359; + + /// + /// Initializes a new instance of the class. + /// + public UdpServerEntryPoint(ILogger logger, IServerApplicationHost appHost, IJsonSerializer json, ISocketFactory socketFactory) + { + _logger = logger; + _appHost = appHost; + _json = json; + _socketFactory = socketFactory; + } + + /// + /// Runs this instance. + /// + public void Run() + { + var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory); + + try + { + udpServer.Start(PortNumber); + + UdpServer = udpServer; + } + catch (Exception ex) + { + _logger.ErrorException("Failed to start UDP Server", ex); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + if (UdpServer != null) + { + UdpServer.Dispose(); + } + } + } + } +} diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs new file mode 100644 index 000000000..295ecc465 --- /dev/null +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -0,0 +1,323 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Threading; + +namespace Emby.Server.Implementations.IO +{ + public class FileRefresher : IDisposable + { + private ILogger Logger { get; set; } + private ITaskManager TaskManager { get; set; } + private ILibraryManager LibraryManager { get; set; } + private IServerConfigurationManager ConfigurationManager { get; set; } + private readonly IFileSystem _fileSystem; + private readonly List _affectedPaths = new List(); + private ITimer _timer; + private readonly ITimerFactory _timerFactory; + private readonly object _timerLock = new object(); + public string Path { get; private set; } + + public event EventHandler Completed; + + public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory) + { + logger.Debug("New file refresher created for {0}", path); + Path = path; + + _fileSystem = fileSystem; + ConfigurationManager = configurationManager; + LibraryManager = libraryManager; + TaskManager = taskManager; + Logger = logger; + _timerFactory = timerFactory; + AddPath(path); + } + + private void AddAffectedPath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException("path"); + } + + if (!_affectedPaths.Contains(path, StringComparer.Ordinal)) + { + _affectedPaths.Add(path); + } + } + + public void AddPath(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException("path"); + } + + lock (_timerLock) + { + AddAffectedPath(path); + } + RestartTimer(); + } + + public void RestartTimer() + { + if (_disposed) + { + return; + } + + lock (_timerLock) + { + if (_disposed) + { + return; + } + + if (_timer == null) + { + _timer = _timerFactory.Create(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + } + else + { + _timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + } + } + } + + public void ResetPath(string path, string affectedFile) + { + lock (_timerLock) + { + Logger.Debug("Resetting file refresher from {0} to {1}", Path, path); + + Path = path; + AddAffectedPath(path); + + if (!string.IsNullOrWhiteSpace(affectedFile)) + { + AddAffectedPath(affectedFile); + } + } + RestartTimer(); + } + + private async void OnTimerCallback(object state) + { + List paths; + + lock (_timerLock) + { + paths = _affectedPaths.ToList(); + } + + // Extend the timer as long as any of the paths are still being written to. + if (paths.Any(IsFileLocked)) + { + Logger.Info("Timer extended."); + RestartTimer(); + return; + } + + Logger.Debug("Timer stopped."); + + DisposeTimer(); + EventHelper.FireEventIfNotNull(Completed, this, EventArgs.Empty, Logger); + + try + { + await ProcessPathChanges(paths.ToList()).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error processing directory changes", ex); + } + } + + private async Task ProcessPathChanges(List paths) + { + var itemsToRefresh = paths + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(GetAffectedBaseItem) + .Where(item => item != null) + .DistinctBy(i => i.Id) + .ToList(); + + foreach (var p in paths) + { + Logger.Info(p + " reports change."); + } + + // If the root folder changed, run the library task so the user can see it + if (itemsToRefresh.Any(i => i is AggregateFolder)) + { + LibraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None); + return; + } + + foreach (var item in itemsToRefresh) + { + Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); + + try + { + await item.ChangedExternally().ConfigureAwait(false); + } + catch (IOException ex) + { + // For now swallow and log. + // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) + // Should we remove it from it's parent? + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + catch (Exception ex) + { + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + } + } + + /// + /// Gets the affected base item. + /// + /// The path. + /// BaseItem. + private BaseItem GetAffectedBaseItem(string path) + { + BaseItem item = null; + + while (item == null && !string.IsNullOrEmpty(path)) + { + item = LibraryManager.FindByPath(path, null); + + path = System.IO.Path.GetDirectoryName(path); + } + + if (item != null) + { + // If the item has been deleted find the first valid parent that still exists + while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) + { + item = item.GetParent(); + + if (item == null) + { + break; + } + } + } + + return item; + } + + private bool IsFileLocked(string path) + { + //if (Environment.OSVersion.Platform != PlatformID.Win32NT) + //{ + // // Causing lockups on linux + // return false; + //} + + try + { + var data = _fileSystem.GetFileSystemInfo(path); + + if (!data.Exists + || data.IsDirectory + + // Opening a writable stream will fail with readonly files + || data.IsReadOnly) + { + return false; + } + } + catch (IOException) + { + return false; + } + catch (Exception ex) + { + Logger.ErrorException("Error getting file system info for: {0}", ex, path); + return false; + } + + // In order to determine if the file is being written to, we have to request write access + // But if the server only has readonly access, this is going to cause this entire algorithm to fail + // So we'll take a best guess about our access level + var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta + ? FileAccessMode.ReadWrite + : FileAccessMode.Read; + + try + { + using (_fileSystem.GetFileStream(path, FileOpenMode.Open, requestedFileAccess, FileShareMode.ReadWrite)) + { + //file is not locked + return false; + } + } + //catch (DirectoryNotFoundException) + //{ + // // File may have been deleted + // return false; + //} + catch (FileNotFoundException) + { + // File may have been deleted + return false; + } + catch (UnauthorizedAccessException) + { + Logger.Debug("No write permission for: {0}.", path); + return false; + } + catch (IOException) + { + //the file is unavailable because it is: + //still being written to + //or being processed by another thread + //or does not exist (has already been processed) + Logger.Debug("{0} is locked.", path); + return true; + } + catch (Exception ex) + { + Logger.ErrorException("Error determining if file is locked: {0}", ex, path); + return false; + } + } + + private void DisposeTimer() + { + lock (_timerLock) + { + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + } + } + + private bool _disposed; + public void Dispose() + { + _disposed = true; + DisposeTimer(); + } + } +} diff --git a/Emby.Server.Implementations/Security/EncryptionManager.cs b/Emby.Server.Implementations/Security/EncryptionManager.cs new file mode 100644 index 000000000..271b0bbdb --- /dev/null +++ b/Emby.Server.Implementations/Security/EncryptionManager.cs @@ -0,0 +1,51 @@ +using MediaBrowser.Controller.Security; +using System; +using System.Text; + +namespace Emby.Server.Implementations.Security +{ + public class EncryptionManager : IEncryptionManager + { + /// + /// Encrypts the string. + /// + /// The value. + /// System.String. + /// value + public string EncryptString(string value) + { + if (value == null) throw new ArgumentNullException("value"); + + return EncryptStringUniversal(value); + } + + /// + /// Decrypts the string. + /// + /// The value. + /// System.String. + /// value + public string DecryptString(string value) + { + if (value == null) throw new ArgumentNullException("value"); + + return DecryptStringUniversal(value); + } + + private string EncryptStringUniversal(string value) + { + // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now + + var bytes = Encoding.UTF8.GetBytes(value); + return Convert.ToBase64String(bytes); + } + + private string DecryptStringUniversal(string value) + { + // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now + + var bytes = Convert.FromBase64String(value); + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + } +} diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs new file mode 100644 index 000000000..c15e0ee41 --- /dev/null +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -0,0 +1,247 @@ +using MediaBrowser.Controller; +using MediaBrowser.Model.ApiClient; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Net; + +namespace Emby.Server.Implementations.Udp +{ + /// + /// Provides a Udp Server + /// + public class UdpServer : IDisposable + { + /// + /// The _logger + /// + private readonly ILogger _logger; + + private bool _isDisposed; + + private readonly List>> _responders = new List>>(); + + private readonly IServerApplicationHost _appHost; + private readonly IJsonSerializer _json; + + /// + /// Initializes a new instance of the class. + /// + public UdpServer(ILogger logger, IServerApplicationHost appHost, IJsonSerializer json, ISocketFactory socketFactory) + { + _logger = logger; + _appHost = appHost; + _json = json; + _socketFactory = socketFactory; + + AddMessageResponder("who is EmbyServer?", true, RespondToV2Message); + AddMessageResponder("who is MediaBrowserServer_v2?", false, RespondToV2Message); + } + + private void AddMessageResponder(string message, bool isSubstring, Func responder) + { + _responders.Add(new Tuple>(message, isSubstring, responder)); + } + + /// + /// Raises the event. + /// + private async void OnMessageReceived(GenericEventArgs e) + { + var message = e.Argument; + + var encoding = Encoding.UTF8; + var responder = GetResponder(message.Buffer, message.ReceivedBytes, encoding); + + if (responder == null) + { + encoding = Encoding.Unicode; + responder = GetResponder(message.Buffer, message.ReceivedBytes, encoding); + } + + if (responder != null) + { + try + { + await responder.Item2.Item3(responder.Item1, message.RemoteEndPoint, encoding).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error in OnMessageReceived", ex); + } + } + } + + private Tuple>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding) + { + var text = encoding.GetString(buffer, 0, bytesReceived); + var responder = _responders.FirstOrDefault(i => + { + if (i.Item2) + { + return text.IndexOf(i.Item1, StringComparison.OrdinalIgnoreCase) != -1; + } + return string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase); + }); + + if (responder == null) + { + return null; + } + return new Tuple>>(text, responder); + } + + private async Task RespondToV2Message(string messageText, IpEndPointInfo endpoint, Encoding encoding) + { + var parts = messageText.Split('|'); + + var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false); + + if (!string.IsNullOrEmpty(localUrl)) + { + var response = new ServerDiscoveryInfo + { + Address = localUrl, + Id = _appHost.SystemId, + Name = _appHost.FriendlyName + }; + + await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint).ConfigureAwait(false); + + if (parts.Length > 1) + { + _appHost.EnableLoopback(parts[1]); + } + } + else + { + _logger.Warn("Unable to respond to udp request because the local ip address could not be determined."); + } + } + + /// + /// The _udp client + /// + private IUdpSocket _udpClient; + private readonly ISocketFactory _socketFactory; + + /// + /// Starts the specified port. + /// + /// The port. + public void Start(int port) + { + _udpClient = _socketFactory.CreateUdpSocket(port); + + Task.Run(() => StartListening()); + } + + private async void StartListening() + { + while (!_isDisposed) + { + try + { + var result = await _udpClient.ReceiveAsync().ConfigureAwait(false); + + OnMessageReceived(result); + } + catch (ObjectDisposedException) + { + } + catch (Exception ex) + { + _logger.ErrorException("Error receiving udp message", ex); + } + } + } + + /// + /// Called when [message received]. + /// + /// The message. + private void OnMessageReceived(SocketReceiveResult message) + { + if (message.RemoteEndPoint.Port == 0) + { + return; + } + + try + { + OnMessageReceived(new GenericEventArgs + { + Argument = message + }); + } + catch (Exception ex) + { + _logger.ErrorException("Error handling UDP message", ex); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Stops this instance. + /// + public void Stop() + { + _isDisposed = true; + + if (_udpClient != null) + { + _udpClient.Dispose(); + } + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + Stop(); + } + } + + public async Task SendAsync(byte[] bytes, IpEndPointInfo remoteEndPoint) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + + if (remoteEndPoint == null) + { + throw new ArgumentNullException("remoteEndPoint"); + } + + try + { + await _udpClient.SendAsync(bytes, bytes.Length, remoteEndPoint).ConfigureAwait(false); + + _logger.Info("Udp message sent to {0}", remoteEndPoint); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending message to {0}", ex, remoteEndPoint); + } + } + } + +} diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 0a565f670..fe60c7ebf 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -46,6 +46,10 @@ namespace MediaBrowser.Common.Net /// true if [is in local network] [the specified endpoint]; otherwise, false. bool IsInLocalNetwork(string endpoint); + IpAddressInfo ParseIpAddress(string ipAddress); + + bool TryParseIpAddress(string ipAddress, out IpAddressInfo ipAddressInfo); + /// /// Generates a self signed certificate at the locatation specified by . /// diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 03bbafe60..c85b215f2 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -140,7 +140,7 @@ - + diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs index c0e0440c2..3f1ddf84f 100644 --- a/MediaBrowser.Model/Net/ISocketFactory.cs +++ b/MediaBrowser.Model/Net/ISocketFactory.cs @@ -14,13 +14,18 @@ namespace MediaBrowser.Model.Net /// A implementation. IUdpSocket CreateUdpSocket(int localPort); - /// - /// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port. - /// - /// The multicast IP address to bind to. - /// The multicast time to live value. Actually a maximum number of network hops for UDP packets. - /// The local port to bind to. - /// A implementation. - IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort); + /// + /// Createa a new unicast socket using the specified local port number. + /// + IUdpSocket CreateSsdpUdpSocket(int localPort); + + /// + /// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port. + /// + /// The multicast IP address to bind to. + /// The multicast time to live value. Actually a maximum number of network hops for UDP packets. + /// The local port to bind to. + /// A implementation. + IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort); } } diff --git a/MediaBrowser.Model/Net/IUdpSocket.cs b/MediaBrowser.Model/Net/IUdpSocket.cs index cbeb8a995..ef090e010 100644 --- a/MediaBrowser.Model/Net/IUdpSocket.cs +++ b/MediaBrowser.Model/Net/IUdpSocket.cs @@ -15,13 +15,11 @@ namespace MediaBrowser.Model.Net /// Waits for and returns the next UDP message sent to this socket (uni or multicast). /// /// - Task ReceiveAsync(); + Task ReceiveAsync(); /// /// Sends a UDP message to a particular end point (uni or multicast). /// - /// The data to send. - /// The providing the address and port to send to. - Task SendTo(byte[] messageData, IpEndPointInfo endPoint); + Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint); } } \ No newline at end of file diff --git a/MediaBrowser.Model/Net/ReceivedUdpData.cs b/MediaBrowser.Model/Net/ReceivedUdpData.cs deleted file mode 100644 index 1fdb22c93..000000000 --- a/MediaBrowser.Model/Net/ReceivedUdpData.cs +++ /dev/null @@ -1,24 +0,0 @@ - -namespace MediaBrowser.Model.Net -{ - /// - /// Used by the sockets wrapper to hold raw data received from a UDP socket. - /// - public sealed class ReceivedUdpData - { - /// - /// The buffer to place received data into. - /// - public byte[] Buffer { get; set; } - - /// - /// The number of bytes received. - /// - public int ReceivedBytes { get; set; } - - /// - /// The the data was received from. - /// - public IpEndPointInfo ReceivedFrom { get; set; } - } -} diff --git a/MediaBrowser.Model/Net/SocketReceiveResult.cs b/MediaBrowser.Model/Net/SocketReceiveResult.cs new file mode 100644 index 000000000..0a2d04ad3 --- /dev/null +++ b/MediaBrowser.Model/Net/SocketReceiveResult.cs @@ -0,0 +1,24 @@ + +namespace MediaBrowser.Model.Net +{ + /// + /// Used by the sockets wrapper to hold raw data received from a UDP socket. + /// + public sealed class SocketReceiveResult + { + /// + /// The buffer to place received data into. + /// + public byte[] Buffer { get; set; } + + /// + /// The number of bytes received. + /// + public int ReceivedBytes { get; set; } + + /// + /// The the data was received from. + /// + public IpEndPointInfo RemoteEndPoint { get; set; } + } +} diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectData.cs b/MediaBrowser.Server.Implementations/Connect/ConnectData.cs deleted file mode 100644 index 5ec0bea22..000000000 --- a/MediaBrowser.Server.Implementations/Connect/ConnectData.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.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/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs deleted file mode 100644 index 565eeb259..000000000 --- a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs +++ /dev/null @@ -1,203 +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.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Threading; - -namespace MediaBrowser.Server.Implementations.Connect -{ - public class ConnectEntryPoint : IServerEntryPoint - { - private ITimer _timer; - 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; - - public ConnectEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, INetworkManager networkManager, IConnectManager connectManager, IApplicationHost appHost, IFileSystem fileSystem, ITimerFactory timerFactory) - { - _httpClient = httpClient; - _appPaths = appPaths; - _logger = logger; - _networkManager = networkManager; - _connectManager = connectManager; - _appHost = appHost; - _fileSystem = fileSystem; - _timerFactory = timerFactory; - } - - 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) - { - IPAddress 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 == AddressFamily.InterNetwork) - { - 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 == AddressFamily.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 == AddressFamily.InterNetwork) - { - 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 IPAddress.Parse(addressString); - } - } - } - - private string CacheFilePath - { - get { return Path.Combine(_appPaths.DataPath, "wan.txt"); } - } - - private void CacheAddress(IPAddress address) - { - var path = CacheFilePath; - - try - { - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); - _fileSystem.WriteAllText(path, address.ToString(), Encoding.UTF8); - } - 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 = _fileSystem.ReadAllText(path, Encoding.UTF8); - IPAddress ipAddress; - - if (IPAddress.TryParse(endpoint, out 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/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs deleted file mode 100644 index 27bbfbe82..000000000 --- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs +++ /dev/null @@ -1,1192 +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.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; - -namespace MediaBrowser.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 IPAddress 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 == AddressFamily.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(IPAddress 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) - { - throw; - } - - // 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 readonly SemaphoreSlim _connectImageSemaphore = new SemaphoreSlim(5, 5); - 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; - 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, _connectImageSemaphore, 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) - { - } - } - - 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/MediaBrowser.Server.Implementations/Connect/Responses.cs b/MediaBrowser.Server.Implementations/Connect/Responses.cs deleted file mode 100644 index f86527829..000000000 --- a/MediaBrowser.Server.Implementations/Connect/Responses.cs +++ /dev/null @@ -1,85 +0,0 @@ -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Connect; - -namespace MediaBrowser.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/MediaBrowser.Server.Implementations/Connect/Validator.cs b/MediaBrowser.Server.Implementations/Connect/Validator.cs deleted file mode 100644 index 8cdfc4a6b..000000000 --- a/MediaBrowser.Server.Implementations/Connect/Validator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Text.RegularExpressions; - -namespace MediaBrowser.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!#$%&'*+/=?^_`{|}~]|(? - /// Class UdpServerEntryPoint - /// - public class UdpServerEntryPoint : IServerEntryPoint - { - /// - /// Gets or sets the UDP server. - /// - /// The UDP server. - private UdpServer UdpServer { get; set; } - - /// - /// The _logger - /// - private readonly ILogger _logger; - /// - /// The _network manager - /// - private readonly INetworkManager _networkManager; - private readonly IServerApplicationHost _appHost; - private readonly IJsonSerializer _json; - - public const int PortNumber = 7359; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The network manager. - /// The application host. - /// The json. - public UdpServerEntryPoint(ILogger logger, INetworkManager networkManager, IServerApplicationHost appHost, IJsonSerializer json) - { - _logger = logger; - _networkManager = networkManager; - _appHost = appHost; - _json = json; - } - - /// - /// Runs this instance. - /// - public void Run() - { - var udpServer = new UdpServer(_logger, _networkManager, _appHost, _json); - - try - { - udpServer.Start(PortNumber); - - UdpServer = udpServer; - } - catch (Exception ex) - { - _logger.ErrorException("Failed to start UDP Server", ex); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - if (UdpServer != null) - { - UdpServer.Dispose(); - } - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index 20d89d2eb..561934854 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -1,13 +1,13 @@ using System.Collections.Specialized; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Logging; -using MediaBrowser.Server.Implementations.Logging; using SocketHttpListener.Net; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; +using Emby.Server.Implementations.Logging; using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs deleted file mode 100644 index 2742e1a26..000000000 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ /dev/null @@ -1,323 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Common.Events; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Threading; - -namespace MediaBrowser.Server.Implementations.IO -{ - public class FileRefresher : IDisposable - { - private ILogger Logger { get; set; } - private ITaskManager TaskManager { get; set; } - private ILibraryManager LibraryManager { get; set; } - private IServerConfigurationManager ConfigurationManager { get; set; } - private readonly IFileSystem _fileSystem; - private readonly List _affectedPaths = new List(); - private ITimer _timer; - private readonly ITimerFactory _timerFactory; - private readonly object _timerLock = new object(); - public string Path { get; private set; } - - public event EventHandler Completed; - - public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory) - { - logger.Debug("New file refresher created for {0}", path); - Path = path; - - _fileSystem = fileSystem; - ConfigurationManager = configurationManager; - LibraryManager = libraryManager; - TaskManager = taskManager; - Logger = logger; - _timerFactory = timerFactory; - AddPath(path); - } - - private void AddAffectedPath(string path) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - if (!_affectedPaths.Contains(path, StringComparer.Ordinal)) - { - _affectedPaths.Add(path); - } - } - - public void AddPath(string path) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - lock (_timerLock) - { - AddAffectedPath(path); - } - RestartTimer(); - } - - public void RestartTimer() - { - if (_disposed) - { - return; - } - - lock (_timerLock) - { - if (_disposed) - { - return; - } - - if (_timer == null) - { - _timer = _timerFactory.Create(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); - } - else - { - _timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); - } - } - } - - public void ResetPath(string path, string affectedFile) - { - lock (_timerLock) - { - Logger.Debug("Resetting file refresher from {0} to {1}", Path, path); - - Path = path; - AddAffectedPath(path); - - if (!string.IsNullOrWhiteSpace(affectedFile)) - { - AddAffectedPath(affectedFile); - } - } - RestartTimer(); - } - - private async void OnTimerCallback(object state) - { - List paths; - - lock (_timerLock) - { - paths = _affectedPaths.ToList(); - } - - // Extend the timer as long as any of the paths are still being written to. - if (paths.Any(IsFileLocked)) - { - Logger.Info("Timer extended."); - RestartTimer(); - return; - } - - Logger.Debug("Timer stopped."); - - DisposeTimer(); - EventHelper.FireEventIfNotNull(Completed, this, EventArgs.Empty, Logger); - - try - { - await ProcessPathChanges(paths.ToList()).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error processing directory changes", ex); - } - } - - private async Task ProcessPathChanges(List paths) - { - var itemsToRefresh = paths - .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(GetAffectedBaseItem) - .Where(item => item != null) - .DistinctBy(i => i.Id) - .ToList(); - - foreach (var p in paths) - { - Logger.Info(p + " reports change."); - } - - // If the root folder changed, run the library task so the user can see it - if (itemsToRefresh.Any(i => i is AggregateFolder)) - { - LibraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None); - return; - } - - foreach (var item in itemsToRefresh) - { - Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); - - try - { - await item.ChangedExternally().ConfigureAwait(false); - } - catch (IOException ex) - { - // For now swallow and log. - // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) - // Should we remove it from it's parent? - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - } - } - - /// - /// Gets the affected base item. - /// - /// The path. - /// BaseItem. - private BaseItem GetAffectedBaseItem(string path) - { - BaseItem item = null; - - while (item == null && !string.IsNullOrEmpty(path)) - { - item = LibraryManager.FindByPath(path, null); - - path = System.IO.Path.GetDirectoryName(path); - } - - if (item != null) - { - // If the item has been deleted find the first valid parent that still exists - while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) - { - item = item.GetParent(); - - if (item == null) - { - break; - } - } - } - - return item; - } - - private bool IsFileLocked(string path) - { - if (Environment.OSVersion.Platform != PlatformID.Win32NT) - { - // Causing lockups on linux - return false; - } - - try - { - var data = _fileSystem.GetFileSystemInfo(path); - - if (!data.Exists - || data.IsDirectory - - // Opening a writable stream will fail with readonly files - || data.IsReadOnly) - { - return false; - } - } - catch (IOException) - { - return false; - } - catch (Exception ex) - { - Logger.ErrorException("Error getting file system info for: {0}", ex, path); - return false; - } - - // In order to determine if the file is being written to, we have to request write access - // But if the server only has readonly access, this is going to cause this entire algorithm to fail - // So we'll take a best guess about our access level - var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta - ? FileAccessMode.ReadWrite - : FileAccessMode.Read; - - try - { - using (_fileSystem.GetFileStream(path, FileOpenMode.Open, requestedFileAccess, FileShareMode.ReadWrite)) - { - //file is not locked - return false; - } - } - //catch (DirectoryNotFoundException) - //{ - // // File may have been deleted - // return false; - //} - catch (FileNotFoundException) - { - // File may have been deleted - return false; - } - catch (UnauthorizedAccessException) - { - Logger.Debug("No write permission for: {0}.", path); - return false; - } - catch (IOException) - { - //the file is unavailable because it is: - //still being written to - //or being processed by another thread - //or does not exist (has already been processed) - Logger.Debug("{0} is locked.", path); - return true; - } - catch (Exception ex) - { - Logger.ErrorException("Error determining if file is locked: {0}", ex, path); - return false; - } - } - - private void DisposeTimer() - { - lock (_timerLock) - { - if (_timer != null) - { - _timer.Dispose(); - _timer = null; - } - } - } - - private bool _disposed; - public void Dispose() - { - _disposed = true; - DisposeTimer(); - } - } -} diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 49cb1e75f..34fc85e7b 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -10,13 +10,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Emby.Server.Implementations.IO; using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.IO; +using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Threading; -using Microsoft.Win32; namespace MediaBrowser.Server.Implementations.IO { @@ -142,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.IO /// /// Initializes a new instance of the class. /// - public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory) + public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory, ISystemEvents systemEvents) { if (taskManager == null) { @@ -156,15 +157,10 @@ namespace MediaBrowser.Server.Implementations.IO _fileSystem = fileSystem; _timerFactory = timerFactory; - SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; + systemEvents.Resume += _systemEvents_Resume; } - /// - /// Handles the PowerModeChanged event of the SystemEvents control. - /// - /// The source of the event. - /// The instance containing the event data. - void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) + private void _systemEvents_Resume(object sender, EventArgs e) { Restart(); } diff --git a/MediaBrowser.Server.Implementations/Logging/PatternsLogger.cs b/MediaBrowser.Server.Implementations/Logging/PatternsLogger.cs deleted file mode 100644 index 00b6cc5a8..000000000 --- a/MediaBrowser.Server.Implementations/Logging/PatternsLogger.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Patterns.Logging; -using System; - -namespace MediaBrowser.Server.Implementations.Logging -{ - public class PatternsLogger : ILogger - { - private readonly Model.Logging.ILogger _logger; - - public PatternsLogger() - : this(new Model.Logging.NullLogger()) - { - } - - public PatternsLogger(Model.Logging.ILogger logger) - { - _logger = logger; - } - - public void Debug(string message, params object[] paramList) - { - _logger.Debug(message, paramList); - } - - public void Error(string message, params object[] paramList) - { - _logger.Error(message, paramList); - } - - public void ErrorException(string message, Exception exception, params object[] paramList) - { - _logger.ErrorException(message, exception, paramList); - } - - public void Fatal(string message, params object[] paramList) - { - _logger.Fatal(message, paramList); - } - - public void FatalException(string message, Exception exception, params object[] paramList) - { - _logger.FatalException(message, exception, paramList); - } - - public void Info(string message, params object[] paramList) - { - _logger.Info(message, paramList); - } - - public void Warn(string message, params object[] paramList) - { - _logger.Warn(message, paramList); - } - - public void Log(LogSeverity severity, string message, params object[] paramList) - { - } - - public void LogMultiline(string message, LogSeverity severity, System.Text.StringBuilder additionalContent) - { - } - } -} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index d6223c465..066ee8e30 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -113,15 +113,9 @@ - - - - - - @@ -140,7 +134,6 @@ - @@ -166,7 +159,6 @@ - @@ -180,15 +172,12 @@ - - - diff --git a/MediaBrowser.Server.Implementations/Security/EncryptionManager.cs b/MediaBrowser.Server.Implementations/Security/EncryptionManager.cs deleted file mode 100644 index cd9b9651e..000000000 --- a/MediaBrowser.Server.Implementations/Security/EncryptionManager.cs +++ /dev/null @@ -1,51 +0,0 @@ -using MediaBrowser.Controller.Security; -using System; -using System.Text; - -namespace MediaBrowser.Server.Implementations.Security -{ - public class EncryptionManager : IEncryptionManager - { - /// - /// Encrypts the string. - /// - /// The value. - /// System.String. - /// value - public string EncryptString(string value) - { - if (value == null) throw new ArgumentNullException("value"); - - return EncryptStringUniversal(value); - } - - /// - /// Decrypts the string. - /// - /// The value. - /// System.String. - /// value - public string DecryptString(string value) - { - if (value == null) throw new ArgumentNullException("value"); - - return DecryptStringUniversal(value); - } - - private string EncryptStringUniversal(string value) - { - // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now - - var bytes = Encoding.UTF8.GetBytes(value); - return Convert.ToBase64String(bytes); - } - - private string DecryptStringUniversal(string value) - { - // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now - - var bytes = Convert.FromBase64String(value); - return Encoding.UTF8.GetString(bytes); - } - } -} diff --git a/MediaBrowser.Server.Implementations/Udp/UdpMessageReceivedEventArgs.cs b/MediaBrowser.Server.Implementations/Udp/UdpMessageReceivedEventArgs.cs deleted file mode 100644 index 5c83a1300..000000000 --- a/MediaBrowser.Server.Implementations/Udp/UdpMessageReceivedEventArgs.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace MediaBrowser.Server.Implementations.Udp -{ - /// - /// Class UdpMessageReceivedEventArgs - /// - public class UdpMessageReceivedEventArgs : EventArgs - { - /// - /// Gets or sets the bytes. - /// - /// The bytes. - public byte[] Bytes { get; set; } - /// - /// Gets or sets the remote end point. - /// - /// The remote end point. - public string RemoteEndPoint { get; set; } - } -} diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs deleted file mode 100644 index c2082f0d2..000000000 --- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs +++ /dev/null @@ -1,328 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading.Tasks; -using Emby.Common.Implementations.Networking; - -namespace MediaBrowser.Server.Implementations.Udp -{ - /// - /// Provides a Udp Server - /// - public class UdpServer : IDisposable - { - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// The _network manager - /// - private readonly INetworkManager _networkManager; - - private bool _isDisposed; - - private readonly List>> _responders = new List>>(); - - private readonly IServerApplicationHost _appHost; - private readonly IJsonSerializer _json; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The network manager. - /// The application host. - /// The json. - public UdpServer(ILogger logger, INetworkManager networkManager, IServerApplicationHost appHost, IJsonSerializer json) - { - _logger = logger; - _networkManager = networkManager; - _appHost = appHost; - _json = json; - - AddMessageResponder("who is EmbyServer?", true, RespondToV2Message); - AddMessageResponder("who is MediaBrowserServer_v2?", false, RespondToV2Message); - } - - private void AddMessageResponder(string message, bool isSubstring, Func responder) - { - _responders.Add(new Tuple>(message, isSubstring, responder)); - } - - /// - /// Raises the event. - /// - /// The instance containing the event data. - private async void OnMessageReceived(UdpMessageReceivedEventArgs e) - { - var encoding = Encoding.UTF8; - var responder = GetResponder(e.Bytes, encoding); - - if (responder == null) - { - encoding = Encoding.Unicode; - responder = GetResponder(e.Bytes, encoding); - } - - if (responder != null) - { - try - { - await responder.Item2.Item3(responder.Item1, e.RemoteEndPoint, encoding).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error in OnMessageReceived", ex); - } - } - } - - private Tuple>> GetResponder(byte[] bytes, Encoding encoding) - { - var text = encoding.GetString(bytes); - var responder = _responders.FirstOrDefault(i => - { - if (i.Item2) - { - return text.IndexOf(i.Item1, StringComparison.OrdinalIgnoreCase) != -1; - } - return string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase); - }); - - if (responder == null) - { - return null; - } - return new Tuple>>(text, responder); - } - - private async Task RespondToV2Message(string messageText, string endpoint, Encoding encoding) - { - var parts = messageText.Split('|'); - - var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false); - - if (!string.IsNullOrEmpty(localUrl)) - { - var response = new ServerDiscoveryInfo - { - Address = localUrl, - Id = _appHost.SystemId, - Name = _appHost.FriendlyName - }; - - await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint).ConfigureAwait(false); - - if (parts.Length > 1) - { - _appHost.EnableLoopback(parts[1]); - } - } - else - { - _logger.Warn("Unable to respond to udp request because the local ip address could not be determined."); - } - } - - /// - /// The _udp client - /// - private UdpClient _udpClient; - - /// - /// Starts the specified port. - /// - /// The port. - public void Start(int port) - { - _udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, port)); - - _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - - Task.Run(() => StartListening()); - } - - private async void StartListening() - { - while (!_isDisposed) - { - try - { - var result = await GetResult().ConfigureAwait(false); - - OnMessageReceived(result); - } - catch (ObjectDisposedException) - { - break; - } - catch (Exception ex) - { - _logger.ErrorException("Error in StartListening", ex); - } - } - } - - private Task GetResult() - { - try - { - return _udpClient.ReceiveAsync(); - } - catch (ObjectDisposedException) - { - return Task.FromResult(new UdpReceiveResult(new byte[] { }, new IPEndPoint(IPAddress.Any, 0))); - } - catch (Exception ex) - { - _logger.ErrorException("Error receiving udp message", ex); - return Task.FromResult(new UdpReceiveResult(new byte[] { }, new IPEndPoint(IPAddress.Any, 0))); - } - } - - /// - /// Called when [message received]. - /// - /// The message. - private void OnMessageReceived(UdpReceiveResult message) - { - if (message.RemoteEndPoint.Port == 0) - { - return; - } - var bytes = message.Buffer; - - try - { - OnMessageReceived(new UdpMessageReceivedEventArgs - { - Bytes = bytes, - RemoteEndPoint = message.RemoteEndPoint.ToString() - }); - } - catch (Exception ex) - { - _logger.ErrorException("Error handling UDP message", ex); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Stops this instance. - /// - public void Stop() - { - _isDisposed = true; - - if (_udpClient != null) - { - _udpClient.Close(); - } - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - Stop(); - } - } - - /// - /// Sends the async. - /// - /// The data. - /// The ip address. - /// The port. - /// Task{System.Int32}. - /// data - public Task SendAsync(string data, string ipAddress, int port) - { - return SendAsync(Encoding.UTF8.GetBytes(data), ipAddress, port); - } - - /// - /// Sends the async. - /// - /// The bytes. - /// The ip address. - /// The port. - /// Task{System.Int32}. - /// bytes - public Task SendAsync(byte[] bytes, string ipAddress, int port) - { - if (bytes == null) - { - throw new ArgumentNullException("bytes"); - } - - if (string.IsNullOrEmpty(ipAddress)) - { - throw new ArgumentNullException("ipAddress"); - } - - return _udpClient.SendAsync(bytes, bytes.Length, ipAddress, port); - } - - /// - /// Sends the async. - /// - /// The bytes. - /// The remote end point. - /// Task. - /// - /// bytes - /// or - /// remoteEndPoint - /// - public async Task SendAsync(byte[] bytes, string remoteEndPoint) - { - if (bytes == null) - { - throw new ArgumentNullException("bytes"); - } - - if (string.IsNullOrEmpty(remoteEndPoint)) - { - throw new ArgumentNullException("remoteEndPoint"); - } - - try - { - // Need to do this until Common will compile with this method - var nativeNetworkManager = (BaseNetworkManager) _networkManager; - - await _udpClient.SendAsync(bytes, bytes.Length, nativeNetworkManager.Parse(remoteEndPoint)).ConfigureAwait(false); - - _logger.Info("Udp message sent to {0}", remoteEndPoint); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending message to {0}", ex, remoteEndPoint); - } - } - } - -} diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 5f609de27..79f7b5f05 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -51,12 +51,9 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.Server.Implementations; using MediaBrowser.Server.Implementations.Activity; using MediaBrowser.Server.Implementations.Configuration; -using MediaBrowser.Server.Implementations.Connect; using MediaBrowser.Server.Implementations.Devices; -using MediaBrowser.Server.Implementations.EntryPoints; using MediaBrowser.Server.Implementations.HttpServer; using MediaBrowser.Server.Implementations.IO; -using MediaBrowser.Server.Implementations.LiveTv; using MediaBrowser.Server.Implementations.Localization; using MediaBrowser.Server.Implementations.Notifications; using MediaBrowser.Server.Implementations.Persistence; @@ -102,8 +99,10 @@ using Emby.Dlna.Ssdp; using Emby.Server.Implementations.Activity; using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; +using Emby.Server.Implementations.Connect; using Emby.Server.Implementations.Devices; using Emby.Server.Implementations.Dto; +using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.FileOrganization; using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.Library; @@ -593,7 +592,7 @@ namespace MediaBrowser.Server.Startup.Common var musicManager = new MusicManager(LibraryManager); RegisterSingleInstance(new MusicManager(LibraryManager)); - LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory); + LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, SystemEvents); RegisterSingleInstance(LibraryMonitor); ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer, MemoryStreamProvider); diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 64278fe4e..dadb1bff4 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -260,7 +260,7 @@ namespace Rssdp.Infrastructure var socket = _SendSocket; if (socket != null) { - await _SendSocket.SendTo(messageData, destination).ConfigureAwait(false); + await _SendSocket.SendAsync(messageData, messageData.Length, destination).ConfigureAwait(false); } else { @@ -290,7 +290,7 @@ namespace Rssdp.Infrastructure private IUdpSocket CreateSocketAndListenForResponsesAsync() { - _SendSocket = _SocketFactory.CreateUdpSocket(_LocalPort); + _SendSocket = _SocketFactory.CreateSsdpUdpSocket(_LocalPort); ListenToSocket(_SendSocket); @@ -316,7 +316,7 @@ namespace Rssdp.Infrastructure // Strange cannot convert compiler error here if I don't explicitly // assign or cast to Action first. Assignment is easier to read, // so went with that. - Action processWork = () => ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.ReceivedFrom); + Action processWork = () => ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint); var processTask = Task.Run(processWork); } } -- cgit v1.2.3 From 67ad1db6b77b2c2cb6d81c22808d99564a5f3ebc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 4 Nov 2016 15:51:59 -0400 Subject: add environment info --- Emby.Common.Implementations/BaseApplicationHost.cs | 17 ++---- .../EnvironmentInfo/EnvironmentInfo.cs | 70 ++++++++++++++++++++++ Emby.Common.Implementations/Net/SocketFactory.cs | 7 +-- Emby.Common.Implementations/Net/UdpSocket.cs | 18 +++--- .../Serialization/XmlSerializer.cs | 17 +++--- Emby.Common.Implementations/project.json | 9 +-- Emby.Dlna/Main/DlnaEntryPoint.cs | 8 ++- Emby.Server.Implementations/IO/FileRefresher.cs | 15 +++-- MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + MediaBrowser.Model/System/IEnvironmentInfo.cs | 22 +++++++ .../IO/LibraryMonitor.cs | 6 +- .../ApplicationHost.cs | 2 +- 12 files changed, 142 insertions(+), 50 deletions(-) create mode 100644 Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs create mode 100644 MediaBrowser.Model/System/IEnvironmentInfo.cs (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/Emby.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs index bdebe894e..9585abb2a 100644 --- a/Emby.Common.Implementations/BaseApplicationHost.cs +++ b/Emby.Common.Implementations/BaseApplicationHost.cs @@ -29,6 +29,7 @@ using MediaBrowser.Common.Extensions; using Emby.Common.Implementations.Cryptography; using Emby.Common.Implementations.Diagnostics; using Emby.Common.Implementations.Net; +using Emby.Common.Implementations.EnvironmentInfo; using Emby.Common.Implementations.Threading; using MediaBrowser.Common; using MediaBrowser.Common.IO; @@ -171,6 +172,8 @@ namespace Emby.Common.Implementations protected ICryptographyProvider CryptographyProvider = new CryptographyProvider(); + protected IEnvironmentInfo EnvironmentInfo = new Emby.Common.Implementations.EnvironmentInfo.EnvironmentInfo(); + private DeviceId _deviceId; public string SystemId { @@ -187,16 +190,7 @@ namespace Emby.Common.Implementations public virtual string OperatingSystemDisplayName { - get - { -#if NET46 - return Environment.OSVersion.VersionString; -#endif -#if NETSTANDARD1_6 - return System.Runtime.InteropServices.RuntimeInformation.OSDescription; -#endif - return "Operating System"; - } + get { return EnvironmentInfo.OperatingSystemName; } } public IMemoryStreamProvider MemoryStreamProvider { get; set; } @@ -216,7 +210,7 @@ namespace Emby.Common.Implementations // hack alert, until common can target .net core BaseExtensions.CryptographyProvider = CryptographyProvider; - XmlSerializer = new XmlSerializer(fileSystem, logManager.GetLogger("XmlSerializer")); + XmlSerializer = new MyXmlSerializer(fileSystem, logManager.GetLogger("XmlSerializer")); FailedAssemblies = new List(); ApplicationPaths = applicationPaths; @@ -534,6 +528,7 @@ return null; RegisterSingleInstance(Logger); RegisterSingleInstance(TaskManager); + RegisterSingleInstance(EnvironmentInfo); RegisterSingleInstance(FileSystemManager); diff --git a/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs new file mode 100644 index 000000000..8cea617ea --- /dev/null +++ b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using MediaBrowser.Model.System; + +namespace Emby.Common.Implementations.EnvironmentInfo +{ + public class EnvironmentInfo : IEnvironmentInfo + { + public MediaBrowser.Model.System.OperatingSystem OperatingSystem + { + get + { +#if NET46 + switch (Environment.OSVersion.Platform) + { + case PlatformID.MacOSX: + return MediaBrowser.Model.System.OperatingSystem.OSX; + case PlatformID.Win32NT: + return MediaBrowser.Model.System.OperatingSystem.Windows; + case PlatformID.Unix: + return MediaBrowser.Model.System.OperatingSystem.Linux; + } +#elif NETSTANDARD1_6 + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperatingSystem.OSX; + } + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperatingSystem.Windows; + } + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperatingSystem.Linux; + } +#endif + return MediaBrowser.Model.System.OperatingSystem.Windows; + } + } + + public string OperatingSystemName + { + get + { +#if NET46 + return Environment.OSVersion.Platform.ToString(); +#elif NETSTANDARD1_6 + return System.Runtime.InteropServices.RuntimeInformation.OSDescription; +#endif + return "Operating System"; + } + } + + public string OperatingSystemVersion + { + get + { +#if NET46 + return Environment.OSVersion.Version.ToString() + " " + Environment.OSVersion.ServicePack.ToString(); +#elif NETSTANDARD1_6 + return System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; +#endif + return "1.0"; + } + } + } +} diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 6f0ff2996..bb38c72da 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -40,12 +40,11 @@ namespace Emby.Common.Implementations.Net /// Creates a new UDP socket and binds it to the specified local port. /// /// An integer specifying the local port to bind the socket to. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] public IUdpSocket CreateUdpSocket(int localPort) { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); @@ -65,12 +64,11 @@ namespace Emby.Common.Implementations.Net /// /// An integer specifying the local port to bind the socket to. /// An implementation of the interface used by RSSDP components to perform socket operations. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] public IUdpSocket CreateSsdpUdpSocket(int localPort) { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); @@ -94,7 +92,6 @@ namespace Emby.Common.Implementations.Net /// The multicast time to live value for the socket. /// The number of the local port to bind to. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort) { if (ipAddress == null) throw new ArgumentNullException("ipAddress"); diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 997d3f25f..6afb071ae 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -17,14 +17,14 @@ namespace Emby.Common.Implementations.Net #region Fields - private System.Net.Sockets.Socket _Socket; + private Socket _Socket; private int _LocalPort; #endregion #region Constructors - public UdpSocket(System.Net.Sockets.Socket socket, int localPort, IPAddress ip) + public UdpSocket(Socket socket, int localPort, IPAddress ip) { if (socket == null) throw new ArgumentNullException("socket"); @@ -46,12 +46,12 @@ namespace Emby.Common.Implementations.Net var tcs = new TaskCompletionSource(); - System.Net.EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); + EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); state.TaskCompletionSource = tcs; #if NETSTANDARD1_6 - _Socket.ReceiveFromAsync(new System.ArraySegment(state.Buffer), System.Net.Sockets.SocketFlags.None, state.EndPoint) + _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer),SocketFlags.None, state.EndPoint) .ContinueWith((task, asyncState) => { if (task.Status != TaskStatus.Faulted) @@ -62,7 +62,7 @@ namespace Emby.Common.Implementations.Net } }, state); #else - _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, System.Net.Sockets.SocketFlags.None, ref state.EndPoint, new AsyncCallback(this.ProcessResponse), state); + _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, new AsyncCallback(this.ProcessResponse), state); #endif return tcs.Task; @@ -84,7 +84,7 @@ namespace Emby.Common.Implementations.Net buffer = copy; } - _Socket.SendTo(buffer, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); + _Socket.SendTo(buffer, new IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); return Task.FromResult(true); #else var taskSource = new TaskCompletionSource(); @@ -153,7 +153,6 @@ namespace Emby.Common.Implementations.Net #region Private Methods - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")] private static void ProcessResponse(AsyncReceiveState state, Func receiveData) { try @@ -206,7 +205,6 @@ namespace Emby.Common.Implementations.Net }; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")] private void ProcessResponse(IAsyncResult asyncResult) { #if NET46 @@ -249,7 +247,7 @@ namespace Emby.Common.Implementations.Net private class AsyncReceiveState { - public AsyncReceiveState(System.Net.Sockets.Socket socket, EndPoint endPoint) + public AsyncReceiveState(Socket socket, EndPoint endPoint) { this.Socket = socket; this.EndPoint = endPoint; @@ -258,7 +256,7 @@ namespace Emby.Common.Implementations.Net public EndPoint EndPoint; public byte[] Buffer = new byte[8192]; - public System.Net.Sockets.Socket Socket { get; private set; } + public Socket Socket { get; private set; } public TaskCompletionSource TaskCompletionSource { get; set; } diff --git a/Emby.Common.Implementations/Serialization/XmlSerializer.cs b/Emby.Common.Implementations/Serialization/XmlSerializer.cs index aea63a57e..3583f998e 100644 --- a/Emby.Common.Implementations/Serialization/XmlSerializer.cs +++ b/Emby.Common.Implementations/Serialization/XmlSerializer.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Xml; +using System.Xml.Serialization; using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -13,12 +14,12 @@ namespace Emby.Common.Implementations.Serialization /// /// Provides a wrapper around third party xml serialization. /// - public class XmlSerializer : IXmlSerializer + public class MyXmlSerializer : IXmlSerializer { private readonly IFileSystem _fileSystem; private readonly ILogger _logger; - public XmlSerializer(IFileSystem fileSystem, ILogger logger) + public MyXmlSerializer(IFileSystem fileSystem, ILogger logger) { _fileSystem = fileSystem; _logger = logger; @@ -26,18 +27,18 @@ namespace Emby.Common.Implementations.Serialization // Need to cache these // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html - private readonly Dictionary _serializers = - new Dictionary(); + private readonly Dictionary _serializers = + new Dictionary(); - private System.Xml.Serialization.XmlSerializer GetSerializer(Type type) + private XmlSerializer GetSerializer(Type type) { var key = type.FullName; lock (_serializers) { - System.Xml.Serialization.XmlSerializer serializer; + XmlSerializer serializer; if (!_serializers.TryGetValue(key, out serializer)) { - serializer = new System.Xml.Serialization.XmlSerializer(type); + serializer = new XmlSerializer(type); _serializers[key] = serializer; } return serializer; @@ -80,7 +81,7 @@ namespace Emby.Common.Implementations.Serialization #if NET46 using (var writer = new XmlTextWriter(stream, null)) { - writer.Formatting = System.Xml.Formatting.Indented; + writer.Formatting = Formatting.Indented; SerializeToWriter(obj, writer); } #else diff --git a/Emby.Common.Implementations/project.json b/Emby.Common.Implementations/project.json index dc96f5726..2b2357e38 100644 --- a/Emby.Common.Implementations/project.json +++ b/Emby.Common.Implementations/project.json @@ -12,15 +12,14 @@ "System.IO": "4.0.0.0", "System.Net": "4.0.0.0", "System.Net.Http": "4.0.0.0", - "System.Net.Http.WebRequest": "4.0.0.0", "System.Net.Primitives": "4.0.0.0", + "System.Net.Http.WebRequest": "4.0.0.0", "System.Runtime": "4.0.0.0", "System.Runtime.Extensions": "4.0.0.0", "System.Text.Encoding": "4.0.0.0", "System.Threading": "4.0.0.0", "System.Threading.Tasks": "4.0.0.0", - "System.Xml": "4.0.0.0", - "System.Xml.Serialization": "4.0.0.0" + "System.Xml.ReaderWriter": "4.0.0" }, "dependencies": { "SimpleInjector": "3.2.4", @@ -30,7 +29,8 @@ }, "MediaBrowser.Common": { "target": "project" - } } + } + } }, "netstandard1.6": { "imports": "dnxcore50", @@ -40,6 +40,7 @@ "System.Diagnostics.Process": "4.1.0", "System.Threading.Timer": "4.0.1", "System.Net.Requests": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XmlSerializer": "4.0.11", "System.Net.Http": "4.1.0", "System.Net.Primitives": "4.0.11", diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 14fbbcfa2..bff87bcac 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -20,6 +20,7 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; using MediaBrowser.Model.Threading; using Rssdp; using Rssdp.Infrastructure; @@ -52,7 +53,7 @@ namespace Emby.Dlna.Main private readonly ITimerFactory _timerFactory; private readonly ISocketFactory _socketFactory; - + private readonly IEnvironmentInfo _environmentInfo; public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, @@ -66,7 +67,7 @@ namespace Emby.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory) + IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo) { _config = config; _appHost = appHost; @@ -83,6 +84,7 @@ namespace Emby.Dlna.Main _mediaEncoder = mediaEncoder; _socketFactory = socketFactory; _timerFactory = timerFactory; + _environmentInfo = environmentInfo; _logger = logManager.GetLogger("Dlna"); } @@ -169,7 +171,7 @@ namespace Emby.Dlna.Main private void StartPublishing() { SsdpDevicePublisherBase.LogFunction = LogMessage; - _Publisher = new SsdpDevicePublisher(_socketFactory, _timerFactory, "Windows", "10"); + _Publisher = new SsdpDevicePublisher(_socketFactory, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); } private void StartDeviceDiscovery() diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 295ecc465..39033249f 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -13,6 +13,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Threading; @@ -32,8 +33,9 @@ namespace Emby.Server.Implementations.IO public string Path { get; private set; } public event EventHandler Completed; + private readonly IEnvironmentInfo _environmentInfo; - public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory) + public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo) { logger.Debug("New file refresher created for {0}", path); Path = path; @@ -44,6 +46,7 @@ namespace Emby.Server.Implementations.IO TaskManager = taskManager; Logger = logger; _timerFactory = timerFactory; + _environmentInfo = environmentInfo; AddPath(path); } @@ -226,11 +229,11 @@ namespace Emby.Server.Implementations.IO private bool IsFileLocked(string path) { - //if (Environment.OSVersion.Platform != PlatformID.Win32NT) - //{ - // // Causing lockups on linux - // return false; - //} + if (_environmentInfo.OperatingSystem != OperatingSystem.Windows) + { + // Causing lockups on linux + return false; + } try { diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index c85b215f2..52e477b1a 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -141,6 +141,7 @@ + diff --git a/MediaBrowser.Model/System/IEnvironmentInfo.cs b/MediaBrowser.Model/System/IEnvironmentInfo.cs new file mode 100644 index 000000000..3fcacb30d --- /dev/null +++ b/MediaBrowser.Model/System/IEnvironmentInfo.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.Model.System +{ + public interface IEnvironmentInfo + { + MediaBrowser.Model.System.OperatingSystem OperatingSystem { get; } + string OperatingSystemName { get; } + string OperatingSystemVersion { get; } + } + + public enum OperatingSystem + { + Windows, + Linux, + OSX + } +} diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 34fc85e7b..a8363558d 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -139,11 +139,12 @@ namespace MediaBrowser.Server.Implementations.IO private readonly IFileSystem _fileSystem; private readonly ITimerFactory _timerFactory; + private readonly IEnvironmentInfo _environmentInfo; /// /// Initializes a new instance of the class. /// - public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory, ISystemEvents systemEvents) + public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory, ISystemEvents systemEvents, IEnvironmentInfo environmentInfo) { if (taskManager == null) { @@ -156,6 +157,7 @@ namespace MediaBrowser.Server.Implementations.IO ConfigurationManager = configurationManager; _fileSystem = fileSystem; _timerFactory = timerFactory; + _environmentInfo = environmentInfo; systemEvents.Resume += _systemEvents_Resume; } @@ -525,7 +527,7 @@ namespace MediaBrowser.Server.Implementations.IO } } - var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory); + var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory, _environmentInfo); newRefresher.Completed += NewRefresher_Completed; _activeRefreshers.Add(newRefresher); } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 79f7b5f05..ba04338bb 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -592,7 +592,7 @@ namespace MediaBrowser.Server.Startup.Common var musicManager = new MusicManager(LibraryManager); RegisterSingleInstance(new MusicManager(LibraryManager)); - LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, SystemEvents); + LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, SystemEvents, EnvironmentInfo); RegisterSingleInstance(LibraryMonitor); ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer, MemoryStreamProvider); -- cgit v1.2.3 From a8b340cbb29dbcf7fd5d101e640d66470c6d32bf Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 8 Nov 2016 13:44:23 -0500 Subject: update portable projects --- BDInfo/BDROM.cs | 4 +- BDInfo/TSPlaylistFile.cs | 10 +- BDInfo/TSStreamClipFile.cs | 14 +- Emby.Common.Implementations/BaseApplicationHost.cs | 10 +- .../Cryptography/CryptographyProvider.cs | 17 +- .../HttpClientManager/HttpClientManager.cs | 4 +- Emby.Common.Implementations/Net/NetSocket.cs | 85 +++++ Emby.Common.Implementations/Net/SocketAcceptor.cs | 111 ++++++ Emby.Common.Implementations/Net/SocketFactory.cs | 37 +- Emby.Common.Implementations/Net/UdpSocket.cs | 12 +- .../Networking/BaseNetworkManager.cs | 91 ++++- .../TextEncoding/TextEncoding.cs | 13 +- Emby.Dlna/Main/DlnaEntryPoint.cs | 6 +- Emby.Dlna/PlayTo/PlayToManager.cs | 11 +- .../Connect/ConnectEntryPoint.cs | 8 +- .../Connect/ConnectManager.cs | 2 +- .../Emby.Server.Implementations.csproj | 10 + .../HttpServer/LoggerUtils.cs | 43 +++ .../HttpServer/RangeRequestWriter.cs | 224 +++++++++++++ .../HttpServer/ResponseFilter.cs | 129 +++++++ .../HttpServer/SocketSharp/Extensions.cs | 12 + .../HttpServer/SocketSharp/SharpWebSocket.cs | 166 +++++++++ .../SocketSharp/WebSocketSharpListener.cs | 203 +++++++++++ .../SocketSharp/WebSocketSharpResponse.cs | 204 +++++++++++ Emby.Server.Implementations/Library/UserManager.cs | 6 +- .../Security/MBLicenseFile.cs | 10 +- .../Security/PluginSecurityManager.cs | 4 +- .../ServerManager/ServerManager.cs | 8 +- .../ServerManager/WebSocketConnection.cs | 10 +- Emby.Server.Implementations/Sync/MediaSync.cs | 6 +- .../Sync/MultiProviderSync.cs | 4 +- .../Sync/ServerSyncScheduledTask.cs | 4 +- Emby.Server.Implementations/Sync/SyncManager.cs | 4 +- .../Sync/TargetDataProvider.cs | 4 +- .../Updates/InstallationManager.cs | 6 +- MediaBrowser.Api/Dlna/DlnaServerService.cs | 4 +- MediaBrowser.Api/Playback/BaseStreamingService.cs | 1 + MediaBrowser.Common/Extensions/BaseExtensions.cs | 2 +- MediaBrowser.Common/Net/INetworkManager.cs | 5 + MediaBrowser.Controller/IServerApplicationHost.cs | 2 +- .../MediaEncoding/MediaStreamSelector.cs | 3 +- .../Net/AuthenticatedAttribute.cs | 14 +- .../BdInfo/BdInfoExaminer.cs | 6 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 4 +- .../Probing/ProbeResultNormalizer.cs | 4 +- .../Subtitles/SubtitleEncoder.cs | 8 +- MediaBrowser.Model/Cryptography/ICryptoProvider.cs | 13 + .../Cryptography/ICryptographyProvider.cs | 14 - MediaBrowser.Model/IO/IMemoryStreamFactory.cs | 12 + MediaBrowser.Model/IO/IMemoryStreamProvider.cs | 11 - MediaBrowser.Model/MediaBrowser.Model.csproj | 8 +- MediaBrowser.Model/Net/ISocket.cs | 16 + MediaBrowser.Model/Net/ISocketFactory.cs | 14 +- MediaBrowser.Model/Net/IpAddressInfo.cs | 29 +- MediaBrowser.Model/Net/IpEndPointInfo.cs | 14 +- MediaBrowser.Model/Services/IHasRequestFilter.cs | 13 +- MediaBrowser.Model/Services/IHttpResult.cs | 47 +++ MediaBrowser.Model/Services/IRequest.cs | 10 - .../Services/QueryParamCollection.cs | 31 +- MediaBrowser.Model/Text/ITextEncoding.cs | 10 + MediaBrowser.Model/TextEncoding/IEncoding.cs | 12 - MediaBrowser.Providers/Manager/ImageSaver.cs | 4 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 4 +- .../MediaInfo/SubtitleResolver.cs | 13 +- .../TV/TheTVDB/TvdbSeriesProvider.cs | 4 +- .../HttpServer/ContainerAdapter.cs | 11 +- .../HttpServer/HttpListenerHost.cs | 371 ++++++++++++--------- .../HttpServer/HttpResultFactory.cs | 1 + .../HttpServer/LoggerUtils.cs | 43 --- .../HttpServer/RangeRequestWriter.cs | 230 ------------- .../HttpServer/ResponseFilter.cs | 127 ------- .../HttpServer/ServerFactory.cs | 15 +- .../HttpServer/ServerLogFactory.cs | 46 --- .../HttpServer/ServerLogger.cs | 194 ----------- .../HttpServer/SocketSharp/Extensions.cs | 28 -- .../HttpServer/SocketSharp/RequestMono.cs | 13 +- .../HttpServer/SocketSharp/SharpWebSocket.cs | 172 ---------- .../SocketSharp/WebSocketSharpListener.cs | 216 ------------ .../SocketSharp/WebSocketSharpRequest.cs | 100 ++---- .../SocketSharp/WebSocketSharpResponse.cs | 152 --------- .../IO/MemoryStreamProvider.cs | 16 +- .../MediaBrowser.Server.Implementations.csproj | 18 +- .../Persistence/DataExtensions.cs | 4 +- .../SqliteDisplayPreferencesRepository.cs | 4 +- .../Persistence/SqliteItemRepository.cs | 4 +- .../Persistence/SqliteUserRepository.cs | 4 +- .../packages.config | 1 - .../ApplicationHost.cs | 54 ++- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- OpenSubtitlesHandler/Utilities.cs | 10 +- 91 files changed, 1899 insertions(+), 1765 deletions(-) create mode 100644 Emby.Common.Implementations/Net/NetSocket.cs create mode 100644 Emby.Common.Implementations/Net/SocketAcceptor.cs create mode 100644 Emby.Server.Implementations/HttpServer/LoggerUtils.cs create mode 100644 Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs create mode 100644 Emby.Server.Implementations/HttpServer/ResponseFilter.cs create mode 100644 Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs create mode 100644 Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs create mode 100644 Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs create mode 100644 Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs create mode 100644 MediaBrowser.Model/Cryptography/ICryptoProvider.cs delete mode 100644 MediaBrowser.Model/Cryptography/ICryptographyProvider.cs create mode 100644 MediaBrowser.Model/IO/IMemoryStreamFactory.cs delete mode 100644 MediaBrowser.Model/IO/IMemoryStreamProvider.cs create mode 100644 MediaBrowser.Model/Net/ISocket.cs create mode 100644 MediaBrowser.Model/Services/IHttpResult.cs create mode 100644 MediaBrowser.Model/Text/ITextEncoding.cs delete mode 100644 MediaBrowser.Model/TextEncoding/IEncoding.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/ServerLogFactory.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/ServerLogger.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/BDInfo/BDROM.cs b/BDInfo/BDROM.cs index 2a23645b1..97dbfbf3b 100644 --- a/BDInfo/BDROM.cs +++ b/BDInfo/BDROM.cs @@ -22,7 +22,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Model.IO; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace BDInfo { @@ -75,7 +75,7 @@ namespace BDInfo public event OnPlaylistFileScanError PlaylistFileScanError; public BDROM( - string path, IFileSystem fileSystem, IEncoding textEncoding) + string path, IFileSystem fileSystem, ITextEncoding textEncoding) { _fileSystem = fileSystem; // diff --git a/BDInfo/TSPlaylistFile.cs b/BDInfo/TSPlaylistFile.cs index 2826b3c80..46d66f513 100644 --- a/BDInfo/TSPlaylistFile.cs +++ b/BDInfo/TSPlaylistFile.cs @@ -23,14 +23,14 @@ using System.Collections.Generic; using System.IO; using System.Text; using MediaBrowser.Model.IO; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace BDInfo { public class TSPlaylistFile { private readonly IFileSystem _fileSystem; - private readonly IEncoding _textEncoding; + private readonly ITextEncoding _textEncoding; private FileSystemMetadata FileInfo = null; public string FileType = null; public bool IsInitialized = false; @@ -67,7 +67,7 @@ namespace BDInfo public TSPlaylistFile( BDROM bdrom, - FileSystemMetadata fileInfo, IFileSystem fileSystem, IEncoding textEncoding) + FileSystemMetadata fileInfo, IFileSystem fileSystem, ITextEncoding textEncoding) { BDROM = bdrom; FileInfo = fileInfo; @@ -79,7 +79,7 @@ namespace BDInfo public TSPlaylistFile( BDROM bdrom, string name, - List clips, IFileSystem fileSystem, IEncoding textEncoding) + List clips, IFileSystem fileSystem, ITextEncoding textEncoding) { BDROM = bdrom; Name = name; @@ -1247,7 +1247,7 @@ namespace BDInfo ref int pos) { string val = - _textEncoding.GetASCIIString(data, pos, count); + _textEncoding.GetASCIIEncoding().GetString(data, pos, count); pos += count; diff --git a/BDInfo/TSStreamClipFile.cs b/BDInfo/TSStreamClipFile.cs index 118e3b5fc..f2accb88d 100644 --- a/BDInfo/TSStreamClipFile.cs +++ b/BDInfo/TSStreamClipFile.cs @@ -23,14 +23,14 @@ using System.Collections.Generic; using System.IO; using System.Text; using MediaBrowser.Model.IO; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace BDInfo { public class TSStreamClipFile { private readonly IFileSystem _fileSystem; - private readonly IEncoding _textEncoding; + private readonly ITextEncoding _textEncoding; public FileSystemMetadata FileInfo = null; public string FileType = null; public bool IsValid = false; @@ -40,7 +40,7 @@ namespace BDInfo new Dictionary(); public TSStreamClipFile( - FileSystemMetadata fileInfo, IFileSystem fileSystem, IEncoding textEncoding) + FileSystemMetadata fileInfo, IFileSystem fileSystem, ITextEncoding textEncoding) { FileInfo = fileInfo; _fileSystem = fileSystem; @@ -70,7 +70,7 @@ namespace BDInfo byte[] fileType = new byte[8]; Array.Copy(data, 0, fileType, 0, fileType.Length); - FileType = _textEncoding.GetASCIIString(fileType, 0, fileType.Length); + FileType = _textEncoding.GetASCIIEncoding().GetString(fileType, 0, fileType.Length); if (FileType != "HDMV0100" && FileType != "HDMV0200") { @@ -167,7 +167,7 @@ namespace BDInfo Array.Copy(clipData, streamOffset + 3, languageBytes, 0, languageBytes.Length); string languageCode = - _textEncoding.GetASCIIString(languageBytes, 0, languageBytes.Length); + _textEncoding.GetASCIIEncoding().GetString(languageBytes, 0, languageBytes.Length); TSChannelLayout channelLayout = (TSChannelLayout) (clipData[streamOffset + 2] >> 4); @@ -198,7 +198,7 @@ namespace BDInfo Array.Copy(clipData, streamOffset + 2, languageBytes, 0, languageBytes.Length); string languageCode = - _textEncoding.GetASCIIString(languageBytes, 0, languageBytes.Length); + _textEncoding.GetASCIIEncoding().GetString(languageBytes, 0, languageBytes.Length); stream = new TSGraphicsStream(); stream.LanguageCode = languageCode; @@ -218,7 +218,7 @@ namespace BDInfo Array.Copy(clipData, streamOffset + 3, languageBytes, 0, languageBytes.Length); string languageCode = - _textEncoding.GetASCIIString(languageBytes, 0, languageBytes.Length); + _textEncoding.GetASCIIEncoding().GetString(languageBytes, 0, languageBytes.Length); #if DEBUG Debug.WriteLine(string.Format( "\t{0} {1} {2}", diff --git a/Emby.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs index 9585abb2a..0cf11e825 100644 --- a/Emby.Common.Implementations/BaseApplicationHost.cs +++ b/Emby.Common.Implementations/BaseApplicationHost.cs @@ -170,7 +170,7 @@ namespace Emby.Common.Implementations /// true if this instance is running as service; otherwise, false. public abstract bool IsRunningAsService { get; } - protected ICryptographyProvider CryptographyProvider = new CryptographyProvider(); + protected ICryptoProvider CryptographyProvider = new CryptographyProvider(); protected IEnvironmentInfo EnvironmentInfo = new Emby.Common.Implementations.EnvironmentInfo.EnvironmentInfo(); @@ -183,7 +183,7 @@ namespace Emby.Common.Implementations { _deviceId = new DeviceId(ApplicationPaths, LogManager.GetLogger("SystemId"), FileSystemManager); } - + return _deviceId.Value; } } @@ -193,7 +193,7 @@ namespace Emby.Common.Implementations get { return EnvironmentInfo.OperatingSystemName; } } - public IMemoryStreamProvider MemoryStreamProvider { get; set; } + public IMemoryStreamFactory MemoryStreamProvider { get; set; } /// /// The container @@ -209,7 +209,7 @@ namespace Emby.Common.Implementations { // hack alert, until common can target .net core BaseExtensions.CryptographyProvider = CryptographyProvider; - + XmlSerializer = new MyXmlSerializer(fileSystem, logManager.GetLogger("XmlSerializer")); FailedAssemblies = new List(); @@ -267,7 +267,7 @@ namespace Emby.Common.Implementations progress.Report(100); } - protected abstract IMemoryStreamProvider CreateMemoryStreamProvider(); + protected abstract IMemoryStreamFactory CreateMemoryStreamProvider(); protected abstract ISystemEvents CreateSystemEvents(); protected virtual void OnLoggerLoaded(bool isFirstLoad) diff --git a/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs index 7b8d95b96..01a31bcc0 100644 --- a/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs @@ -6,21 +6,14 @@ using MediaBrowser.Model.Cryptography; namespace Emby.Common.Implementations.Cryptography { - public class CryptographyProvider : ICryptographyProvider + public class CryptographyProvider : ICryptoProvider { public Guid GetMD5(string str) { - return new Guid(GetMD5Bytes(str)); - } - public byte[] GetMD5Bytes(string str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(Encoding.Unicode.GetBytes(str)); - } + return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); } - public byte[] GetSHA1Bytes(byte[] bytes) + public byte[] ComputeSHA1(byte[] bytes) { using (var provider = SHA1.Create()) { @@ -28,7 +21,7 @@ namespace Emby.Common.Implementations.Cryptography } } - public byte[] GetMD5Bytes(Stream str) + public byte[] ComputeMD5(Stream str) { using (var provider = MD5.Create()) { @@ -36,7 +29,7 @@ namespace Emby.Common.Implementations.Cryptography } } - public byte[] GetMD5Bytes(byte[] bytes) + public byte[] ComputeMD5(byte[] bytes) { using (var provider = MD5.Create()) { diff --git a/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs index 85fcb556f..06af5af53 100644 --- a/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -42,7 +42,7 @@ namespace Emby.Common.Implementations.HttpClientManager private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; /// /// Initializes a new instance of the class. @@ -53,7 +53,7 @@ namespace Emby.Common.Implementations.HttpClientManager /// appPaths /// or /// logger - public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamProvider memoryStreamProvider) + public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider) { if (appPaths == null) { diff --git a/Emby.Common.Implementations/Net/NetSocket.cs b/Emby.Common.Implementations/Net/NetSocket.cs new file mode 100644 index 000000000..72faa41a9 --- /dev/null +++ b/Emby.Common.Implementations/Net/NetSocket.cs @@ -0,0 +1,85 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using Emby.Common.Implementations.Networking; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Logging; + +namespace Emby.Common.Implementations.Net +{ + public class NetSocket : ISocket + { + public Socket Socket { get; private set; } + private readonly ILogger _logger; + + public NetSocket(Socket socket, ILogger logger) + { + Socket = socket; + _logger = logger; + } + + public IpEndPointInfo LocalEndPoint + { + get + { + return BaseNetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.LocalEndPoint); + } + } + + public IpEndPointInfo RemoteEndPoint + { + get + { + return BaseNetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.RemoteEndPoint); + } + } + + public void Close() + { +#if NET46 + Socket.Close(); +#else + Socket.Dispose(); +#endif + } + + public void Shutdown(bool both) + { + if (both) + { + Socket.Shutdown(SocketShutdown.Both); + } + else + { + // Change interface if ever needed + throw new NotImplementedException(); + } + } + + public void Listen(int backlog) + { + Socket.Listen(backlog); + } + + public void Bind(IpEndPointInfo endpoint) + { + var nativeEndpoint = BaseNetworkManager.ToIPEndPoint(endpoint); + + Socket.Bind(nativeEndpoint); + } + + private SocketAcceptor _acceptor; + public void StartAccept(Action onAccept, Func isClosed) + { + _acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed); + + _acceptor.StartAccept(); + } + + public void Dispose() + { + Socket.Dispose(); + } + } +} diff --git a/Emby.Common.Implementations/Net/SocketAcceptor.cs b/Emby.Common.Implementations/Net/SocketAcceptor.cs new file mode 100644 index 000000000..fd65e9fbc --- /dev/null +++ b/Emby.Common.Implementations/Net/SocketAcceptor.cs @@ -0,0 +1,111 @@ +using System; +using System.Net.Sockets; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; + +namespace Emby.Common.Implementations.Net +{ + public class SocketAcceptor + { + private readonly ILogger _logger; + private readonly Socket _originalSocket; + private readonly Func _isClosed; + private readonly Action _onAccept; + + public SocketAcceptor(ILogger logger, Socket originalSocket, Action onAccept, Func isClosed) + { + _logger = logger; + _originalSocket = originalSocket; + _isClosed = isClosed; + _onAccept = onAccept; + } + + public void StartAccept() + { + Socket dummy = null; + StartAccept(null, ref dummy); + } + + public void StartAccept(SocketAsyncEventArgs acceptEventArg, ref Socket accepted) + { + if (acceptEventArg == null) + { + acceptEventArg = new SocketAsyncEventArgs(); + acceptEventArg.Completed += new EventHandler(AcceptEventArg_Completed); + } + else + { + // socket must be cleared since the context object is being reused + acceptEventArg.AcceptSocket = null; + } + + try + { + bool willRaiseEvent = _originalSocket.AcceptAsync(acceptEventArg); + + if (!willRaiseEvent) + { + ProcessAccept(acceptEventArg); + } + } + catch (Exception ex) + { + if (accepted != null) + { + try + { +#if NET46 + accepted.Close(); +#else + accepted.Dispose(); +#endif + } + catch + { + } + accepted = null; + } + } + } + + // This method is the callback method associated with Socket.AcceptAsync + // operations and is invoked when an accept operation is complete + // + void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e) + { + ProcessAccept(e); + } + + private void ProcessAccept(SocketAsyncEventArgs e) + { + if (_isClosed()) + { + return; + } + + // http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.acceptasync%28v=vs.110%29.aspx + // Under certain conditions ConnectionReset can occur + // Need to attept to re-accept + if (e.SocketError == SocketError.ConnectionReset) + { + _logger.Error("SocketError.ConnectionReset reported. Attempting to re-accept."); + Socket dummy = null; + StartAccept(e, ref dummy); + return; + } + + var acceptSocket = e.AcceptSocket; + if (acceptSocket != null) + { + //ProcessAccept(acceptSocket); + _onAccept(new NetSocket(acceptSocket, _logger)); + } + + if (_originalSocket != null) + { + // Accept the next connection request + StartAccept(e, ref acceptSocket); + } + } + } +} diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index bb38c72da..922b0f3cc 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; namespace Emby.Common.Implementations.Net @@ -22,16 +23,28 @@ namespace Emby.Common.Implementations.Net /// private IPAddress _LocalIP; - /// - /// Default constructor. - /// - /// A string containing the IP address of the local network adapter to bind sockets to. Null or empty string will use . - public SocketFactory(string localIP) + private ILogger _logger; + + public SocketFactory(ILogger logger) + { + _logger = logger; + _LocalIP = IPAddress.Any; + } + + public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode) { - if (String.IsNullOrEmpty(localIP)) - _LocalIP = IPAddress.Any; - else - _LocalIP = IPAddress.Parse(localIP); + var addressFamily = family == IpAddressFamily.InterNetwork + ? AddressFamily.InterNetwork + : AddressFamily.InterNetworkV6; + + var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + + if (dualMode) + { + socket.DualMode = true; + } + + return new NetSocket(socket, _logger); } #region ISocketFactory Members @@ -44,7 +57,7 @@ namespace Emby.Common.Implementations.Net { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); @@ -68,7 +81,7 @@ namespace Emby.Common.Implementations.Net { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); @@ -99,7 +112,7 @@ namespace Emby.Common.Implementations.Net if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", "multicastTimeToLive"); if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try { diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index d999d3fe8..244b37bb4 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Sockets; using System.Security; using System.Threading.Tasks; +using Emby.Common.Implementations.Networking; using MediaBrowser.Model.Net; namespace Emby.Common.Implementations.Net @@ -174,16 +175,7 @@ namespace Emby.Common.Implementations.Net return null; } - return new IpEndPointInfo - { - IpAddress = new IpAddressInfo - { - Address = endpoint.Address.ToString(), - IsIpv6 = endpoint.AddressFamily == AddressFamily.InterNetworkV6 - }, - - Port = endpoint.Port - }; + return BaseNetworkManager.ToIpEndPointInfo(endpoint); } private void ProcessResponse(IAsyncResult asyncResult) diff --git a/Emby.Common.Implementations/Networking/BaseNetworkManager.cs b/Emby.Common.Implementations/Networking/BaseNetworkManager.cs index 10d0db968..f1ac8413b 100644 --- a/Emby.Common.Implementations/Networking/BaseNetworkManager.cs +++ b/Emby.Common.Implementations/Networking/BaseNetworkManager.cs @@ -22,14 +22,10 @@ namespace Emby.Common.Implementations.Networking Logger = logger; } - private List _localIpAddresses; + private List _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); - /// - /// Gets the machine's local ip address - /// - /// IPAddress. - public IEnumerable GetLocalIpAddresses() + public IEnumerable GetLocalIpAddresses() { const int cacheMinutes = 5; @@ -39,7 +35,7 @@ namespace Emby.Common.Implementations.Networking if (_localIpAddresses == null || forceRefresh) { - var addresses = GetLocalIpAddressesInternal().ToList(); + var addresses = GetLocalIpAddressesInternal().Select(ToIpAddressInfo).ToList(); _localIpAddresses = addresses; _lastRefresh = DateTime.UtcNow; @@ -405,18 +401,85 @@ namespace Emby.Common.Implementations.Networking IPAddress address; if (IPAddress.TryParse(ipAddress, out address)) { - - ipAddressInfo = new IpAddressInfo - { - Address = address.ToString(), - IsIpv6 = address.AddressFamily == AddressFamily.InterNetworkV6 - }; - + ipAddressInfo = ToIpAddressInfo(address); return true; } ipAddressInfo = null; return false; } + + public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) + { + if (endpoint == null) + { + return null; + } + + return new IpEndPointInfo(ToIpAddressInfo(endpoint.Address), endpoint.Port); + } + + public static IPEndPoint ToIPEndPoint(IpEndPointInfo endpoint) + { + if (endpoint == null) + { + return null; + } + + return new IPEndPoint(ToIPAddress(endpoint.IpAddress), endpoint.Port); + } + + public static IPAddress ToIPAddress(IpAddressInfo address) + { + if (address.Equals(IpAddressInfo.Any)) + { + return IPAddress.Any; + } + if (address.Equals(IpAddressInfo.IPv6Any)) + { + return IPAddress.IPv6Any; + } + if (address.Equals(IpAddressInfo.Loopback)) + { + return IPAddress.Loopback; + } + if (address.Equals(IpAddressInfo.IPv6Loopback)) + { + return IPAddress.IPv6Loopback; + } + + return IPAddress.Parse(address.Address); + } + + public static IpAddressInfo ToIpAddressInfo(IPAddress address) + { + if (address.Equals(IPAddress.Any)) + { + return IpAddressInfo.Any; + } + if (address.Equals(IPAddress.IPv6Any)) + { + return IpAddressInfo.IPv6Any; + } + if (address.Equals(IPAddress.Loopback)) + { + return IpAddressInfo.Loopback; + } + if (address.Equals(IPAddress.IPv6Loopback)) + { + return IpAddressInfo.IPv6Loopback; + } + return new IpAddressInfo + { + Address = address.ToString(), + AddressFamily = address.AddressFamily == AddressFamily.InterNetworkV6 ? IpAddressFamily.InterNetworkV6 : IpAddressFamily.InterNetwork + }; + } + + public async Task GetHostAddressesAsync(string host) + { + var addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false); + return addresses.Select(ToIpAddressInfo).ToArray(); + } } } diff --git a/Emby.Common.Implementations/TextEncoding/TextEncoding.cs b/Emby.Common.Implementations/TextEncoding/TextEncoding.cs index 35b869e43..254d35222 100644 --- a/Emby.Common.Implementations/TextEncoding/TextEncoding.cs +++ b/Emby.Common.Implementations/TextEncoding/TextEncoding.cs @@ -1,10 +1,10 @@ using System.Text; using MediaBrowser.Model.IO; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace Emby.Common.Implementations.TextEncoding { - public class TextEncoding : IEncoding + public class TextEncoding : ITextEncoding { private readonly IFileSystem _fileSystem; @@ -13,14 +13,9 @@ namespace Emby.Common.Implementations.TextEncoding _fileSystem = fileSystem; } - public byte[] GetASCIIBytes(string text) + public Encoding GetASCIIEncoding() { - return Encoding.ASCII.GetBytes(text); - } - - public string GetASCIIString(byte[] bytes, int startIndex, int length) - { - return Encoding.ASCII.GetString(bytes, 0, bytes.Length); + return Encoding.ASCII; } public Encoding GetFileEncoding(string srcFile) diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 142b9f96e..ef27c029d 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -250,14 +250,12 @@ namespace Emby.Dlna.Main // continue; //} - var addressString = address.ToString(); - var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; - _logger.Info("Registering publisher for {0} on {1}", fullService, addressString); + _logger.Info("Registering publisher for {0} on {1}", fullService, address.ToString()); var descriptorUri = "/dlna/" + udn + "/description.xml"; - var uri = new Uri(_appHost.GetLocalApiUrl(addressString, address.IsIpv6) + descriptorUri); + var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri); var device = new SsdpRootDevice { diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index b714f4ac0..a93f7da14 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Threading; namespace Emby.Dlna.PlayTo @@ -131,11 +132,11 @@ namespace Emby.Dlna.PlayTo string serverAddress; if (info.LocalIpAddress == null) { - serverAddress = await GetServerAddress(null, false).ConfigureAwait(false); + serverAddress = await GetServerAddress(null).ConfigureAwait(false); } else { - serverAddress = await GetServerAddress(info.LocalIpAddress.Address, info.LocalIpAddress.IsIpv6).ConfigureAwait(false); + serverAddress = await GetServerAddress(info.LocalIpAddress).ConfigureAwait(false); } string accessToken = null; @@ -189,14 +190,14 @@ namespace Emby.Dlna.PlayTo } } - private Task GetServerAddress(string ipAddress, bool isIpv6) + private Task GetServerAddress(IpAddressInfo address) { - if (string.IsNullOrWhiteSpace(ipAddress)) + if (address == null) { return _appHost.GetLocalApiUrl(); } - return Task.FromResult(_appHost.GetLocalApiUrl(ipAddress, isIpv6)); + return Task.FromResult(_appHost.GetLocalApiUrl(address)); } public void Dispose() diff --git a/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs b/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs index d7574d466..170ef07f3 100644 --- a/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs +++ b/Emby.Server.Implementations/Connect/ConnectEntryPoint.cs @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Connect validIpAddress = await GetIpAddress(ipLookupUrl).ConfigureAwait(false); // Try to find the ipv4 address, if present - if (!validIpAddress.IsIpv6) + if (validIpAddress.AddressFamily != IpAddressFamily.InterNetworkV6) { break; } @@ -77,9 +77,9 @@ namespace Emby.Server.Implementations.Connect _logger.ErrorException("Error getting connection info", ex); } } - + // If this produced an ipv6 address, try again - if (validIpAddress != null && validIpAddress.IsIpv6) + if (validIpAddress != null && validIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { foreach (var ipLookupUrl in _ipLookups) { @@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Connect var newAddress = await GetIpAddress(ipLookupUrl, true).ConfigureAwait(false); // Try to find the ipv4 address, if present - if (!newAddress.IsIpv6) + if (newAddress.AddressFamily != IpAddressFamily.InterNetworkV6) { validIpAddress = newAddress; break; diff --git a/Emby.Server.Implementations/Connect/ConnectManager.cs b/Emby.Server.Implementations/Connect/ConnectManager.cs index 6c2ac40c3..079bfe868 100644 --- a/Emby.Server.Implementations/Connect/ConnectManager.cs +++ b/Emby.Server.Implementations/Connect/ConnectManager.cs @@ -74,7 +74,7 @@ namespace Emby.Server.Implementations.Connect if (string.IsNullOrWhiteSpace(address) && DiscoveredWanIpAddress != null) { - if (DiscoveredWanIpAddress.IsIpv6) + if (DiscoveredWanIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { address = "[" + DiscoveredWanIpAddress + "]"; } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 894ba334d..806702dfd 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -68,11 +68,18 @@ + + + + + + + @@ -254,6 +261,9 @@ {442b5058-dcaf-4263-bb6a-f21e31120a1b} MediaBrowser.Providers + + ..\ThirdParty\emby\SocketHttpListener.Portable.dll + ..\packages\UniversalDetector.1.0.1\lib\portable-net45+sl4+wp71+win8+wpa81\UniversalDetector.dll True diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs new file mode 100644 index 000000000..8fc92a09a --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs @@ -0,0 +1,43 @@ +using MediaBrowser.Model.Logging; +using System; +using System.Globalization; +using SocketHttpListener.Net; + +namespace Emby.Server.Implementations.HttpServer +{ + public static class LoggerUtils + { + /// + /// Logs the request. + /// + /// The logger. + /// The request. + public static void LogRequest(ILogger logger, HttpListenerRequest request) + { + var url = request.Url.ToString(); + + logger.Info("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty); + } + + public static void LogRequest(ILogger logger, string url, string method, string userAgent) + { + logger.Info("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty); + } + + /// + /// Logs the response. + /// + /// The logger. + /// The status code. + /// The URL. + /// The end point. + /// The duration. + public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration) + { + var durationMs = duration.TotalMilliseconds; + var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; + + logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url); + } + } +} diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs new file mode 100644 index 000000000..e88994bec --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -0,0 +1,224 @@ +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.HttpServer +{ + public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult + { + /// + /// Gets or sets the source stream. + /// + /// The source stream. + private Stream SourceStream { get; set; } + private string RangeHeader { get; set; } + private bool IsHeadRequest { get; set; } + + private long RangeStart { get; set; } + private long RangeEnd { get; set; } + private long RangeLength { get; set; } + private long TotalContentLength { get; set; } + + public Action OnComplete { get; set; } + private readonly ILogger _logger; + + private const int BufferSize = 81920; + + /// + /// The _options + /// + private readonly Dictionary _options = new Dictionary(); + + /// + /// The us culture + /// + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + public List Cookies { get; private set; } + + /// + /// Additional HTTP Headers + /// + /// The headers. + public IDictionary Headers + { + get { return _options; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The range header. + /// The source. + /// Type of the content. + /// if set to true [is head request]. + public RangeRequestWriter(string rangeHeader, Stream source, string contentType, bool isHeadRequest, ILogger logger) + { + if (string.IsNullOrEmpty(contentType)) + { + throw new ArgumentNullException("contentType"); + } + + RangeHeader = rangeHeader; + SourceStream = source; + IsHeadRequest = isHeadRequest; + this._logger = logger; + + ContentType = contentType; + Headers["Content-Type"] = contentType; + Headers["Accept-Ranges"] = "bytes"; + StatusCode = HttpStatusCode.PartialContent; + + Cookies = new List(); + SetRangeValues(); + } + + /// + /// Sets the range values. + /// + private void SetRangeValues() + { + var requestedRange = RequestedRanges[0]; + + TotalContentLength = SourceStream.Length; + + // If the requested range is "0-", we can optimize by just doing a stream copy + if (!requestedRange.Value.HasValue) + { + RangeEnd = TotalContentLength - 1; + } + else + { + RangeEnd = requestedRange.Value.Value; + } + + RangeStart = requestedRange.Key; + RangeLength = 1 + RangeEnd - RangeStart; + + // Content-Length is the length of what we're serving, not the original content + Headers["Content-Length"] = RangeLength.ToString(UsCulture); + Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); + + if (RangeStart > 0) + { + SourceStream.Position = RangeStart; + } + } + + /// + /// The _requested ranges + /// + private List> _requestedRanges; + /// + /// Gets the requested ranges. + /// + /// The requested ranges. + protected List> RequestedRanges + { + get + { + if (_requestedRanges == null) + { + _requestedRanges = new List>(); + + // Example: bytes=0-,32-63 + var ranges = RangeHeader.Split('=')[1].Split(','); + + foreach (var range in ranges) + { + var vals = range.Split('-'); + + long start = 0; + long? end = null; + + if (!string.IsNullOrEmpty(vals[0])) + { + start = long.Parse(vals[0], UsCulture); + } + if (!string.IsNullOrEmpty(vals[1])) + { + end = long.Parse(vals[1], UsCulture); + } + + _requestedRanges.Add(new KeyValuePair(start, end)); + } + } + + return _requestedRanges; + } + } + + public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) + { + try + { + // Headers only + if (IsHeadRequest) + { + return; + } + + using (var source = SourceStream) + { + // If the requested range is "0-", we can optimize by just doing a stream copy + if (RangeEnd >= TotalContentLength - 1) + { + await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); + } + else + { + await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false); + } + } + } + finally + { + if (OnComplete != null) + { + OnComplete(); + } + } + } + + private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength) + { + var array = new byte[BufferSize]; + int count; + while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0) + { + var bytesToCopy = Math.Min(count, copyLength); + + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false); + + copyLength -= bytesToCopy; + + if (copyLength <= 0) + { + break; + } + } + } + + public string ContentType { get; set; } + + public IRequest RequestContext { get; set; } + + public object Response { get; set; } + + public int Status { get; set; } + + public HttpStatusCode StatusCode + { + get { return (HttpStatusCode)Status; } + set { Status = (int)value; } + } + + public string StatusDescription { get; set; } + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs new file mode 100644 index 000000000..6d9d7d921 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -0,0 +1,129 @@ +using MediaBrowser.Model.Logging; +using System; +using System.Globalization; +using System.Text; +using Emby.Server.Implementations.HttpServer.SocketSharp; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.HttpServer +{ + public class ResponseFilter + { + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + private readonly ILogger _logger; + + public ResponseFilter(ILogger logger) + { + _logger = logger; + } + + /// + /// Filters the response. + /// + /// The req. + /// The res. + /// The dto. + public void FilterResponse(IRequest req, IResponse res, object dto) + { + // Try to prevent compatibility view + res.AddHeader("X-UA-Compatible", "IE=Edge"); + res.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); + res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); + res.AddHeader("Access-Control-Allow-Origin", "*"); + + var exception = dto as Exception; + + if (exception != null) + { + _logger.ErrorException("Error processing request for {0}", exception, req.RawUrl); + + if (!string.IsNullOrEmpty(exception.Message)) + { + var error = exception.Message.Replace(Environment.NewLine, " "); + error = RemoveControlCharacters(error); + + res.AddHeader("X-Application-Error-Code", error); + } + } + + var vary = "Accept-Encoding"; + + var hasHeaders = dto as IHasHeaders; + var sharpResponse = res as WebSocketSharpResponse; + + if (hasHeaders != null) + { + if (!hasHeaders.Headers.ContainsKey("Server")) + { + hasHeaders.Headers["Server"] = "Mono-HTTPAPI/1.1, UPnP/1.0 DLNADOC/1.50"; + //hasHeaders.Headers["Server"] = "Mono-HTTPAPI/1.1"; + } + + // Content length has to be explicitly set on on HttpListenerResponse or it won't be happy + string contentLength; + + if (hasHeaders.Headers.TryGetValue("Content-Length", out contentLength) && !string.IsNullOrEmpty(contentLength)) + { + var length = long.Parse(contentLength, UsCulture); + + if (length > 0) + { + res.SetContentLength(length); + + //var listenerResponse = res.OriginalResponse as HttpListenerResponse; + + //if (listenerResponse != null) + //{ + // // Disable chunked encoding. Technically this is only needed when using Content-Range, but + // // anytime we know the content length there's no need for it + // listenerResponse.SendChunked = false; + // return; + //} + + if (sharpResponse != null) + { + sharpResponse.SendChunked = false; + } + } + } + + string hasHeadersVary; + if (hasHeaders.Headers.TryGetValue("Vary", out hasHeadersVary)) + { + vary = hasHeadersVary; + } + + hasHeaders.Headers["Vary"] = vary; + } + + //res.KeepAlive = false; + + // Per Google PageSpeed + // This instructs the proxies to cache two versions of the resource: one compressed, and one uncompressed. + // The correct version of the resource is delivered based on the client request header. + // This is a good choice for applications that are singly homed and depend on public proxies for user locality. + res.AddHeader("Vary", vary); + } + + /// + /// Removes the control characters. + /// + /// The in string. + /// System.String. + public static string RemoveControlCharacters(string inString) + { + if (inString == null) return null; + + var newString = new StringBuilder(); + + foreach (var ch in inString) + { + if (!char.IsControl(ch)) + { + newString.Append(ch); + } + } + return newString.ToString(); + } + } +} diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs new file mode 100644 index 000000000..07a338f19 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/Extensions.cs @@ -0,0 +1,12 @@ +using SocketHttpListener.Net; + +namespace Emby.Server.Implementations.HttpServer.SocketSharp +{ + public static class Extensions + { + public static string GetOperationName(this HttpListenerRequest request) + { + return request.Url.Segments[request.Url.Segments.Length - 1]; + } + } +} diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs new file mode 100644 index 000000000..0a312f7b9 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs @@ -0,0 +1,166 @@ +using MediaBrowser.Common.Events; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; +using WebSocketState = MediaBrowser.Model.Net.WebSocketState; + +namespace Emby.Server.Implementations.HttpServer.SocketSharp +{ + public class SharpWebSocket : IWebSocket + { + /// + /// The logger + /// + private readonly ILogger _logger; + + public event EventHandler Closed; + + /// + /// Gets or sets the web socket. + /// + /// The web socket. + private SocketHttpListener.WebSocket WebSocket { get; set; } + + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) + { + if (socket == null) + { + throw new ArgumentNullException("socket"); + } + + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + + _logger = logger; + WebSocket = socket; + + socket.OnMessage += socket_OnMessage; + socket.OnClose += socket_OnClose; + socket.OnError += socket_OnError; + + WebSocket.ConnectAsServer(); + } + + void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e) + { + _logger.Error("Error in SharpWebSocket: {0}", e.Message ?? string.Empty); + //EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger); + } + + void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e) + { + EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger); + } + + void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e) + { + //if (!string.IsNullOrWhiteSpace(e.Data)) + //{ + // if (OnReceive != null) + // { + // OnReceive(e.Data); + // } + // return; + //} + if (OnReceiveBytes != null) + { + OnReceiveBytes(e.RawData); + } + } + + /// + /// Gets or sets the state. + /// + /// The state. + public WebSocketState State + { + get + { + WebSocketState commonState; + + if (!Enum.TryParse(WebSocket.ReadyState.ToString(), true, out commonState)) + { + _logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.ReadyState.ToString()); + } + + return commonState; + } + } + + /// + /// Sends the async. + /// + /// The bytes. + /// if set to true [end of message]. + /// The cancellation token. + /// Task. + public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) + { + var completionSource = new TaskCompletionSource(); + + WebSocket.SendAsync(bytes, res => completionSource.TrySetResult(true)); + + return completionSource.Task; + } + + /// + /// Sends the asynchronous. + /// + /// The text. + /// if set to true [end of message]. + /// The cancellation token. + /// Task. + public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) + { + var completionSource = new TaskCompletionSource(); + + WebSocket.SendAsync(text, res => completionSource.TrySetResult(true)); + + return completionSource.Task; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + WebSocket.OnMessage -= socket_OnMessage; + WebSocket.OnClose -= socket_OnClose; + WebSocket.OnError -= socket_OnError; + + _cancellationTokenSource.Cancel(); + + WebSocket.Close(); + } + } + + /// + /// Gets or sets the receive action. + /// + /// The receive action. + public Action OnReceiveBytes { get; set; } + + /// + /// Gets or sets the on receive. + /// + /// The on receive. + public Action OnReceive { get; set; } + } +} diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs new file mode 100644 index 000000000..0cb4d428b --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -0,0 +1,203 @@ +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Logging; +using SocketHttpListener.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Emby.Server.Implementations.Logging; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.Text; +using SocketHttpListener.Primitives; + +namespace Emby.Server.Implementations.HttpServer.SocketSharp +{ + public class WebSocketSharpListener : IHttpListener + { + private HttpListener _listener; + + private readonly ILogger _logger; + private readonly ICertificate _certificate; + private readonly IMemoryStreamFactory _memoryStreamProvider; + private readonly ITextEncoding _textEncoding; + private readonly INetworkManager _networkManager; + private readonly ISocketFactory _socketFactory; + private readonly ICryptoProvider _cryptoProvider; + private readonly IStreamFactory _streamFactory; + private readonly Func _httpRequestFactory; + + public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, Func httpRequestFactory) + { + _logger = logger; + _certificate = certificate; + _memoryStreamProvider = memoryStreamProvider; + _textEncoding = textEncoding; + _networkManager = networkManager; + _socketFactory = socketFactory; + _cryptoProvider = cryptoProvider; + _streamFactory = streamFactory; + _httpRequestFactory = httpRequestFactory; + } + + public Action ErrorHandler { get; set; } + public Func RequestHandler { get; set; } + + public Action WebSocketConnecting { get; set; } + + public Action WebSocketConnected { get; set; } + + public void Start(IEnumerable urlPrefixes) + { + if (_listener == null) + _listener = new HttpListener(new PatternsLogger(_logger), _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider); + + if (_certificate != null) + { + _listener.LoadCert(_certificate); + } + + foreach (var prefix in urlPrefixes) + { + _logger.Info("Adding HttpListener prefix " + prefix); + _listener.Prefixes.Add(prefix); + } + + _listener.OnContext = ProcessContext; + + _listener.Start(); + } + + private void ProcessContext(HttpListenerContext context) + { + Task.Factory.StartNew(() => InitTask(context)); + } + + private Task InitTask(HttpListenerContext context) + { + IHttpRequest httpReq = null; + var request = context.Request; + + try + { + if (request.IsWebSocketRequest) + { + LoggerUtils.LogRequest(_logger, request); + + ProcessWebSocketRequest(context); + return Task.FromResult(true); + } + + httpReq = GetRequest(context); + } + catch (Exception ex) + { + _logger.ErrorException("Error processing request", ex); + + httpReq = httpReq ?? GetRequest(context); + ErrorHandler(ex, httpReq); + return Task.FromResult(true); + } + + return RequestHandler(httpReq, request.Url); + } + + private void ProcessWebSocketRequest(HttpListenerContext ctx) + { + try + { + var endpoint = ctx.Request.RemoteEndPoint.ToString(); + var url = ctx.Request.RawUrl; + + var connectingArgs = new WebSocketConnectingEventArgs + { + Url = url, + QueryString = ctx.Request.QueryString, + Endpoint = endpoint + }; + + if (WebSocketConnecting != null) + { + WebSocketConnecting(connectingArgs); + } + + if (connectingArgs.AllowConnection) + { + _logger.Debug("Web socket connection allowed"); + + var webSocketContext = ctx.AcceptWebSocket(null); + + if (WebSocketConnected != null) + { + WebSocketConnected(new WebSocketConnectEventArgs + { + Url = url, + QueryString = ctx.Request.QueryString, + WebSocket = new SharpWebSocket(webSocketContext.WebSocket, _logger), + Endpoint = endpoint + }); + } + } + else + { + _logger.Warn("Web socket connection not allowed"); + ctx.Response.StatusCode = 401; + ctx.Response.Close(); + } + } + catch (Exception ex) + { + _logger.ErrorException("AcceptWebSocketAsync error", ex); + ctx.Response.StatusCode = 500; + ctx.Response.Close(); + } + } + + private IHttpRequest GetRequest(HttpListenerContext httpContext) + { + return _httpRequestFactory(httpContext); + } + + public void Stop() + { + if (_listener != null) + { + foreach (var prefix in _listener.Prefixes.ToList()) + { + _listener.Prefixes.Remove(prefix); + } + + _listener.Close(); + } + } + + public void Dispose() + { + Dispose(true); + } + + private bool _disposed; + private readonly object _disposeLock = new object(); + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + lock (_disposeLock) + { + if (_disposed) return; + + if (disposing) + { + Stop(); + } + + //release unmanaged resources here... + _disposed = true; + } + } + } + +} \ No newline at end of file diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs new file mode 100644 index 000000000..de0b33fe3 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using MediaBrowser.Model.Logging; +using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; +using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; +using IRequest = MediaBrowser.Model.Services.IRequest; + +namespace Emby.Server.Implementations.HttpServer.SocketSharp +{ + public class WebSocketSharpResponse : IHttpResponse + { + private readonly ILogger _logger; + private readonly HttpListenerResponse _response; + + public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request) + { + _logger = logger; + this._response = response; + Items = new Dictionary(); + Request = request; + } + + public IRequest Request { get; private set; } + public bool UseBufferedStream { get; set; } + public Dictionary Items { get; private set; } + public object OriginalResponse + { + get { return _response; } + } + + public int StatusCode + { + get { return this._response.StatusCode; } + set { this._response.StatusCode = value; } + } + + public string StatusDescription + { + get { return this._response.StatusDescription; } + set { this._response.StatusDescription = value; } + } + + public string ContentType + { + get { return _response.ContentType; } + set { _response.ContentType = value; } + } + + //public ICookies Cookies { get; set; } + + public void AddHeader(string name, string value) + { + if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) + { + ContentType = value; + return; + } + + _response.AddHeader(name, value); + } + + public string GetHeader(string name) + { + return _response.Headers[name]; + } + + public void Redirect(string url) + { + _response.Redirect(url); + } + + public Stream OutputStream + { + get { return _response.OutputStream; } + } + + public object Dto { get; set; } + + public void Write(string text) + { + var bOutput = System.Text.Encoding.UTF8.GetBytes(text); + _response.ContentLength64 = bOutput.Length; + + var outputStream = _response.OutputStream; + outputStream.Write(bOutput, 0, bOutput.Length); + Close(); + } + + public void Close() + { + if (!this.IsClosed) + { + this.IsClosed = true; + + try + { + CloseOutputStream(this._response); + } + catch (Exception ex) + { + _logger.ErrorException("Error closing HttpListener output stream", ex); + } + } + } + + public void CloseOutputStream(HttpListenerResponse response) + { + try + { + response.OutputStream.Flush(); + response.OutputStream.Dispose(); + response.Close(); + } + catch (Exception ex) + { + _logger.ErrorException("Error in HttpListenerResponseWrapper: " + ex.Message, ex); + } + } + + public void End() + { + Close(); + } + + public void Flush() + { + _response.OutputStream.Flush(); + } + + public bool IsClosed + { + get; + private set; + } + + public void SetContentLength(long contentLength) + { + //you can happily set the Content-Length header in Asp.Net + //but HttpListener will complain if you do - you have to set ContentLength64 on the response. + //workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header + _response.ContentLength64 = contentLength; + } + + public void SetCookie(Cookie cookie) + { + var cookieStr = AsHeaderValue(cookie); + _response.Headers.Add("Set-Cookie", cookieStr); + } + + public static string AsHeaderValue(Cookie cookie) + { + var defaultExpires = DateTime.MinValue; + + var path = cookie.Expires == defaultExpires + ? "/" + : cookie.Path ?? "/"; + + var sb = new StringBuilder(); + + sb.Append($"{cookie.Name}={cookie.Value};path={path}"); + + if (cookie.Expires != defaultExpires) + { + sb.Append($";expires={cookie.Expires:R}"); + } + + if (!string.IsNullOrEmpty(cookie.Domain)) + { + sb.Append($";domain={cookie.Domain}"); + } + //else if (restrictAllCookiesToDomain != null) + //{ + // sb.Append($";domain={restrictAllCookiesToDomain}"); + //} + + if (cookie.Secure) + { + sb.Append(";Secure"); + } + if (cookie.HttpOnly) + { + sb.Append(";HttpOnly"); + } + + return sb.ToString(); + } + + + public bool SendChunked + { + get { return _response.SendChunked; } + set { _response.SendChunked = value; } + } + + public bool KeepAlive { get; set; } + + public void ClearCookies() + { + } + } +} diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 9c1d7fdf1..2a5706b3b 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -70,10 +70,10 @@ namespace Emby.Server.Implementations.Library private readonly Func _connectFactory; private readonly IServerApplicationHost _appHost; private readonly IFileSystem _fileSystem; - private readonly ICryptographyProvider _cryptographyProvider; + private readonly ICryptoProvider _cryptographyProvider; private readonly string _defaultUserName; - public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func imageProcessorFactory, Func dtoServiceFactory, Func connectFactory, IServerApplicationHost appHost, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ICryptographyProvider cryptographyProvider, string defaultUserName) + public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func imageProcessorFactory, Func dtoServiceFactory, Func connectFactory, IServerApplicationHost appHost, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, string defaultUserName) { _logger = logger; UserRepository = userRepository; @@ -334,7 +334,7 @@ namespace Emby.Server.Implementations.Library /// System.String. private string GetSha1String(string str) { - return BitConverter.ToString(_cryptographyProvider.GetSHA1Bytes(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); } /// diff --git a/Emby.Server.Implementations/Security/MBLicenseFile.cs b/Emby.Server.Implementations/Security/MBLicenseFile.cs index 7cb6165a5..4b6a82b6c 100644 --- a/Emby.Server.Implementations/Security/MBLicenseFile.cs +++ b/Emby.Server.Implementations/Security/MBLicenseFile.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Security { private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; - private readonly ICryptographyProvider _cryptographyProvider; + private readonly ICryptoProvider _cryptographyProvider; public string RegKey { @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.Security private readonly object _fileLock = new object(); private string _regKey; - public MBLicenseFile(IApplicationPaths appPaths, IFileSystem fileSystem, ICryptographyProvider cryptographyProvider) + public MBLicenseFile(IApplicationPaths appPaths, IFileSystem fileSystem, ICryptoProvider cryptographyProvider) { _appPaths = appPaths; _fileSystem = fileSystem; @@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Security public void AddRegCheck(string featureId) { - var key = new Guid(_cryptographyProvider.GetMD5Bytes(Encoding.Unicode.GetBytes(featureId))); + var key = new Guid(_cryptographyProvider.ComputeMD5(Encoding.Unicode.GetBytes(featureId))); var value = DateTime.UtcNow; SetUpdateRecord(key, value); @@ -68,7 +68,7 @@ namespace Emby.Server.Implementations.Security public void RemoveRegCheck(string featureId) { - var key = new Guid(_cryptographyProvider.GetMD5Bytes(Encoding.Unicode.GetBytes(featureId))); + var key = new Guid(_cryptographyProvider.ComputeMD5(Encoding.Unicode.GetBytes(featureId))); DateTime val; _updateRecords.TryRemove(key, out val); @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.Security public DateTime LastChecked(string featureId) { DateTime last; - _updateRecords.TryGetValue(new Guid(_cryptographyProvider.GetMD5Bytes(Encoding.Unicode.GetBytes(featureId))), out last); + _updateRecords.TryGetValue(new Guid(_cryptographyProvider.ComputeMD5(Encoding.Unicode.GetBytes(featureId))), out last); // guard agains people just putting a large number in the file return last < DateTime.UtcNow ? last : DateTime.MinValue; diff --git a/Emby.Server.Implementations/Security/PluginSecurityManager.cs b/Emby.Server.Implementations/Security/PluginSecurityManager.cs index c3a7e9450..61d4f5252 100644 --- a/Emby.Server.Implementations/Security/PluginSecurityManager.cs +++ b/Emby.Server.Implementations/Security/PluginSecurityManager.cs @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Security private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; - private readonly ICryptographyProvider _cryptographyProvider; + private readonly ICryptoProvider _cryptographyProvider; private IEnumerable _registeredEntities; protected IEnumerable RegisteredEntities @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.Security /// Initializes a new instance of the class. /// public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, - IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem, ICryptographyProvider cryptographyProvider) + IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem, ICryptoProvider cryptographyProvider) { if (httpClient == null) { diff --git a/Emby.Server.Implementations/ServerManager/ServerManager.cs b/Emby.Server.Implementations/ServerManager/ServerManager.cs index 83ce0b6a3..f660d01ec 100644 --- a/Emby.Server.Implementations/ServerManager/ServerManager.cs +++ b/Emby.Server.Implementations/ServerManager/ServerManager.cs @@ -15,7 +15,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace Emby.Server.Implementations.ServerManager { @@ -75,8 +75,8 @@ namespace Emby.Server.Implementations.ServerManager private readonly List _webSocketListeners = new List(); private bool _disposed; - private readonly IMemoryStreamProvider _memoryStreamProvider; - private readonly IEncoding _textEncoding; + private readonly IMemoryStreamFactory _memoryStreamProvider; + private readonly ITextEncoding _textEncoding; /// /// Initializes a new instance of the class. @@ -86,7 +86,7 @@ namespace Emby.Server.Implementations.ServerManager /// The logger. /// The configuration manager. /// applicationHost - public ServerManager(IServerApplicationHost applicationHost, IJsonSerializer jsonSerializer, ILogger logger, IServerConfigurationManager configurationManager, IMemoryStreamProvider memoryStreamProvider, IEncoding textEncoding) + public ServerManager(IServerApplicationHost applicationHost, IJsonSerializer jsonSerializer, ILogger logger, IServerConfigurationManager configurationManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding) { if (applicationHost == null) { diff --git a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs b/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs index dd17edea5..4608a13e6 100644 --- a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs +++ b/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; using UniversalDetector; namespace Emby.Server.Implementations.ServerManager @@ -77,8 +77,8 @@ namespace Emby.Server.Implementations.ServerManager /// /// The query string. public QueryParamCollection QueryString { get; set; } - private readonly IMemoryStreamProvider _memoryStreamProvider; - private readonly IEncoding _textEncoding; + private readonly IMemoryStreamFactory _memoryStreamProvider; + private readonly ITextEncoding _textEncoding; /// /// Initializes a new instance of the class. @@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.ServerManager /// The json serializer. /// The logger. /// socket - public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger, IMemoryStreamProvider memoryStreamProvider, IEncoding textEncoding) + public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding) { if (socket == null) { @@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.ServerManager } else { - OnReceiveInternal(_textEncoding.GetASCIIString(bytes, 0, bytes.Length)); + OnReceiveInternal(_textEncoding.GetASCIIEncoding().GetString(bytes, 0, bytes.Length)); } } private string DetectCharset(byte[] bytes) diff --git a/Emby.Server.Implementations/Sync/MediaSync.cs b/Emby.Server.Implementations/Sync/MediaSync.cs index b420a3df4..fa8388b6c 100644 --- a/Emby.Server.Implementations/Sync/MediaSync.cs +++ b/Emby.Server.Implementations/Sync/MediaSync.cs @@ -29,12 +29,12 @@ namespace Emby.Server.Implementations.Sync private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IConfigurationManager _config; - private readonly ICryptographyProvider _cryptographyProvider; + private readonly ICryptoProvider _cryptographyProvider; public const string PathSeparatorString = "/"; public const char PathSeparatorChar = '/'; - public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost, IFileSystem fileSystem, IConfigurationManager config, ICryptographyProvider cryptographyProvider) + public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost, IFileSystem fileSystem, IConfigurationManager config, ICryptoProvider cryptographyProvider) { _logger = logger; _syncManager = syncManager; @@ -370,7 +370,7 @@ namespace Emby.Server.Implementations.Sync private byte[] CreateMd5(byte[] value) { - return _cryptographyProvider.GetMD5Bytes(value); + return _cryptographyProvider.ComputeMD5(value); } public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncJob job, SyncTarget target, BaseItemDto libraryItem, string serverId, string serverName, string originalFileName) diff --git a/Emby.Server.Implementations/Sync/MultiProviderSync.cs b/Emby.Server.Implementations/Sync/MultiProviderSync.cs index db6cfcbd6..8189b8550 100644 --- a/Emby.Server.Implementations/Sync/MultiProviderSync.cs +++ b/Emby.Server.Implementations/Sync/MultiProviderSync.cs @@ -23,9 +23,9 @@ namespace Emby.Server.Implementations.Sync private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IConfigurationManager _config; - private readonly ICryptographyProvider _cryptographyProvider; + private readonly ICryptoProvider _cryptographyProvider; - public MultiProviderSync(SyncManager syncManager, IServerApplicationHost appHost, ILogger logger, IFileSystem fileSystem, IConfigurationManager config, ICryptographyProvider cryptographyProvider) + public MultiProviderSync(SyncManager syncManager, IServerApplicationHost appHost, ILogger logger, IFileSystem fileSystem, IConfigurationManager config, ICryptoProvider cryptographyProvider) { _syncManager = syncManager; _appHost = appHost; diff --git a/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs b/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs index 17171633e..09a0bfde4 100644 --- a/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs +++ b/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs @@ -20,9 +20,9 @@ namespace Emby.Server.Implementations.Sync private readonly IFileSystem _fileSystem; private readonly IServerApplicationHost _appHost; private readonly IConfigurationManager _config; - private readonly ICryptographyProvider _cryptographyProvider; + private readonly ICryptoProvider _cryptographyProvider; - public ServerSyncScheduledTask(ISyncManager syncManager, ILogger logger, IFileSystem fileSystem, IServerApplicationHost appHost, IConfigurationManager config, ICryptographyProvider cryptographyProvider) + public ServerSyncScheduledTask(ISyncManager syncManager, ILogger logger, IFileSystem fileSystem, IServerApplicationHost appHost, IConfigurationManager config, ICryptoProvider cryptographyProvider) { _syncManager = syncManager; _logger = logger; diff --git a/Emby.Server.Implementations/Sync/SyncManager.cs b/Emby.Server.Implementations/Sync/SyncManager.cs index d06ed49fd..13f60f5ee 100644 --- a/Emby.Server.Implementations/Sync/SyncManager.cs +++ b/Emby.Server.Implementations/Sync/SyncManager.cs @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.Sync private readonly Func _mediaSourceManager; private readonly IJsonSerializer _json; private readonly ITaskManager _taskManager; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; private ISyncProvider[] _providers = { }; @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Sync 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, IMemoryStreamProvider memoryStreamProvider) + 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; diff --git a/Emby.Server.Implementations/Sync/TargetDataProvider.cs b/Emby.Server.Implementations/Sync/TargetDataProvider.cs index a0e0f4313..fbd82aa7a 100644 --- a/Emby.Server.Implementations/Sync/TargetDataProvider.cs +++ b/Emby.Server.Implementations/Sync/TargetDataProvider.cs @@ -26,9 +26,9 @@ namespace Emby.Server.Implementations.Sync private readonly IFileSystem _fileSystem; private readonly IApplicationPaths _appPaths; private readonly IServerApplicationHost _appHost; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; - public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths, IMemoryStreamProvider memoryStreamProvider) + public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths, IMemoryStreamFactory memoryStreamProvider) { _logger = logger; _json = json; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 3c7a8242c..52bf09284 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -119,9 +119,9 @@ namespace Emby.Server.Implementations.Updates /// The application host. private readonly IApplicationHost _applicationHost; - private readonly ICryptographyProvider _cryptographyProvider; + private readonly ICryptoProvider _cryptographyProvider; - public InstallationManager(ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, IHttpClient httpClient, IJsonSerializer jsonSerializer, ISecurityManager securityManager, IConfigurationManager config, IFileSystem fileSystem, ICryptographyProvider cryptographyProvider) + public InstallationManager(ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, IHttpClient httpClient, IJsonSerializer jsonSerializer, ISecurityManager securityManager, IConfigurationManager config, IFileSystem fileSystem, ICryptoProvider cryptographyProvider) { if (logger == null) { @@ -606,7 +606,7 @@ namespace Emby.Server.Implementations.Updates { using (var stream = _fileSystem.OpenRead(tempFile)) { - var check = Guid.Parse(BitConverter.ToString(_cryptographyProvider.GetMD5Bytes(stream)).Replace("-", String.Empty)); + var check = Guid.Parse(BitConverter.ToString(_cryptographyProvider.ComputeMD5(stream)).Replace("-", String.Empty)); if (check != packageChecksum) { throw new Exception(string.Format("Download validation failed for {0}. Probably corrupted during transfer.", package.name)); diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs index 93d0b3d55..8125951b5 100644 --- a/MediaBrowser.Api/Dlna/DlnaServerService.cs +++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs @@ -111,9 +111,9 @@ namespace MediaBrowser.Api.Dlna private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar; private const string XMLContentType = "text/xml; charset=UTF-8"; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; - public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IMediaReceiverRegistrar mediaReceiverRegistrar, IMemoryStreamProvider memoryStreamProvider) + public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IMediaReceiverRegistrar mediaReceiverRegistrar, IMemoryStreamFactory memoryStreamProvider) { _dlnaManager = dlnaManager; _contentDirectory = contentDirectory; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index c62889214..4fa663b19 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1263,6 +1263,7 @@ namespace MediaBrowser.Api.Playback private bool EnableThrottling(StreamState state) { + return false; // do not use throttling with hardware encoders return state.InputProtocol == MediaProtocol.File && state.RunTimeTicks.HasValue && diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs index 09f6e8706..d7f4424fa 100644 --- a/MediaBrowser.Common/Extensions/BaseExtensions.cs +++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Common.Extensions /// public static class BaseExtensions { - public static ICryptographyProvider CryptographyProvider { get; set; } + public static ICryptoProvider CryptographyProvider { get; set; } /// /// Strips the HTML. diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index fe60c7ebf..779db0a82 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -2,6 +2,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using System.Collections.Generic; using System.Net; +using System.Threading.Tasks; namespace MediaBrowser.Common.Net { @@ -46,10 +47,14 @@ namespace MediaBrowser.Common.Net /// true if [is in local network] [the specified endpoint]; otherwise, false. bool IsInLocalNetwork(string endpoint); + IEnumerable GetLocalIpAddresses(); + IpAddressInfo ParseIpAddress(string ipAddress); bool TryParseIpAddress(string ipAddress, out IpAddressInfo ipAddressInfo); + Task GetHostAddressesAsync(string host); + /// /// Generates a self signed certificate at the locatation specified by . /// diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 2fd96577a..d2cf23f43 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -85,7 +85,7 @@ namespace MediaBrowser.Controller /// /// Gets the local API URL. /// - string GetLocalApiUrl(string ipAddress, bool isIpv6); + string GetLocalApiUrl(IpAddressInfo address); void LaunchUrl(string url); diff --git a/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs b/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs index 52199c5a2..8169cc7d9 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs @@ -69,7 +69,8 @@ namespace MediaBrowser.Controller.MediaEncoding // if the audio language is not understood by the user, load their preferred subs, if there are any if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage)) { - stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language)); + stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language)) ?? + streams.FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language)); } } else if (mode == SubtitlePlaybackMode.Always) diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs index d45416f53..b025011d7 100644 --- a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net { public class AuthenticatedAttribute : Attribute, IHasRequestFilter, IAuthenticationAttributes { - public IAuthService AuthService { get; set; } + public static IAuthService AuthService { get; set; } /// /// Gets or sets the roles. @@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Net /// /// true if [allow before startup wizard]; otherwise, false. public bool AllowBeforeStartupWizard { get; set; } - + /// /// The request filter is executed before the service. /// @@ -40,15 +40,6 @@ namespace MediaBrowser.Controller.Net AuthService.Authenticate(serviceRequest, this); } - /// - /// A new shallow copy of this filter is used on every request. - /// - /// IHasRequestFilter. - public IHasRequestFilter Copy() - { - return this; - } - /// /// Order in which Request Filters are executed. /// <0 Executed before global request filters @@ -60,7 +51,6 @@ namespace MediaBrowser.Controller.Net get { return 0; } } - public IEnumerable GetRoles() { return (Roles ?? string.Empty).Split(',') diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs index ee7cb9dda..bf7343f3d 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Model.IO; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace MediaBrowser.MediaEncoding.BdInfo { @@ -15,9 +15,9 @@ namespace MediaBrowser.MediaEncoding.BdInfo public class BdInfoExaminer : IBlurayExaminer { private readonly IFileSystem _fileSystem; - private readonly IEncoding _textEncoding; + private readonly ITextEncoding _textEncoding; - public BdInfoExaminer(IFileSystem fileSystem, IEncoding textEncoding) + public BdInfoExaminer(IFileSystem fileSystem, ITextEncoding textEncoding) { _fileSystem = fileSystem; _textEncoding = textEncoding; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 098cd14db..0d7a7912a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly IHttpClient _httpClient; private readonly IZipClient _zipClient; private readonly IProcessFactory _processFactory; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly List _runningProcesses = new List(); private readonly bool _hasExternalEncoder; @@ -88,7 +88,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly int DefaultImageExtractionTimeoutMs; private readonly bool EnableEncoderFontFile; - public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamProvider memoryStreamProvider, IProcessFactory processFactory, + public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, int defaultImageExtractionTimeoutMs, bool enableEncoderFontFile) { diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index f0c4c465d..6a1583094 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -23,9 +23,9 @@ namespace MediaBrowser.MediaEncoding.Probing private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly ILogger _logger; private readonly IFileSystem _fileSystem; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; - public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, IMemoryStreamProvider memoryStreamProvider) + public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider) { _logger = logger; _fileSystem = fileSystem; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 0baee67ea..5d065f528 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -19,7 +19,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; using UniversalDetector; namespace MediaBrowser.MediaEncoding.Subtitles @@ -34,11 +34,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles private readonly IJsonSerializer _json; private readonly IHttpClient _httpClient; private readonly IMediaSourceManager _mediaSourceManager; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly IProcessFactory _processFactory; - private readonly IEncoding _textEncoding; + private readonly ITextEncoding _textEncoding; - public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IMemoryStreamProvider memoryStreamProvider, IProcessFactory processFactory, IEncoding textEncoding) + public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, ITextEncoding textEncoding) { _libraryManager = libraryManager; _logger = logger; diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs new file mode 100644 index 000000000..7a82dee52 --- /dev/null +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -0,0 +1,13 @@ +using System; +using System.IO; + +namespace MediaBrowser.Model.Cryptography +{ + public interface ICryptoProvider + { + Guid GetMD5(string str); + byte[] ComputeMD5(Stream str); + byte[] ComputeMD5(byte[] bytes); + byte[] ComputeSHA1(byte[] bytes); + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Cryptography/ICryptographyProvider.cs b/MediaBrowser.Model/Cryptography/ICryptographyProvider.cs deleted file mode 100644 index a3f86f9e2..000000000 --- a/MediaBrowser.Model/Cryptography/ICryptographyProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.IO; - -namespace MediaBrowser.Model.Cryptography -{ - public interface ICryptographyProvider - { - Guid GetMD5(string str); - byte[] GetMD5Bytes(string str); - byte[] GetSHA1Bytes(byte[] bytes); - byte[] GetMD5Bytes(Stream str); - byte[] GetMD5Bytes(byte[] bytes); - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/IO/IMemoryStreamFactory.cs b/MediaBrowser.Model/IO/IMemoryStreamFactory.cs new file mode 100644 index 000000000..f4f174643 --- /dev/null +++ b/MediaBrowser.Model/IO/IMemoryStreamFactory.cs @@ -0,0 +1,12 @@ +using System.IO; + +namespace MediaBrowser.Model.IO +{ + public interface IMemoryStreamFactory + { + MemoryStream CreateNew(); + MemoryStream CreateNew(int capacity); + MemoryStream CreateNew(byte[] buffer); + bool TryGetBuffer(MemoryStream stream, out byte[] buffer); + } +} diff --git a/MediaBrowser.Model/IO/IMemoryStreamProvider.cs b/MediaBrowser.Model/IO/IMemoryStreamProvider.cs deleted file mode 100644 index 82a758d9a..000000000 --- a/MediaBrowser.Model/IO/IMemoryStreamProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.IO; - -namespace MediaBrowser.Model.IO -{ - public interface IMemoryStreamProvider - { - MemoryStream CreateNew(); - MemoryStream CreateNew(int capacity); - MemoryStream CreateNew(byte[] buffer); - } -} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 52e477b1a..ee7582619 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -100,7 +100,7 @@ - + @@ -138,17 +138,19 @@ + + - + - + diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs new file mode 100644 index 000000000..371fbc567 --- /dev/null +++ b/MediaBrowser.Model/Net/ISocket.cs @@ -0,0 +1,16 @@ +using System; + +namespace MediaBrowser.Model.Net +{ + public interface ISocket : IDisposable + { + IpEndPointInfo LocalEndPoint { get; } + IpEndPointInfo RemoteEndPoint { get; } + void Close(); + void Shutdown(bool both); + void Listen(int backlog); + void Bind(IpEndPointInfo endpoint); + + void StartAccept(Action onAccept, Func isClosed); + } +} diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs index 3f1ddf84f..599292ddf 100644 --- a/MediaBrowser.Model/Net/ISocketFactory.cs +++ b/MediaBrowser.Model/Net/ISocketFactory.cs @@ -27,5 +27,17 @@ namespace MediaBrowser.Model.Net /// The local port to bind to. /// A implementation. IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort); - } + + ISocket CreateSocket(IpAddressFamily family, SocketType socketType, ProtocolType protocolType, bool dualMode); + } + + public enum SocketType + { + Stream + } + + public enum ProtocolType + { + Tcp + } } diff --git a/MediaBrowser.Model/Net/IpAddressInfo.cs b/MediaBrowser.Model/Net/IpAddressInfo.cs index b48347d1d..47ffe5118 100644 --- a/MediaBrowser.Model/Net/IpAddressInfo.cs +++ b/MediaBrowser.Model/Net/IpAddressInfo.cs @@ -4,12 +4,39 @@ namespace MediaBrowser.Model.Net { public class IpAddressInfo { + public static IpAddressInfo Any = new IpAddressInfo("0.0.0.0", IpAddressFamily.InterNetwork); + public static IpAddressInfo IPv6Any = new IpAddressInfo("00000000000000000000", IpAddressFamily.InterNetworkV6); + public static IpAddressInfo Loopback = new IpAddressInfo("127.0.0.1", IpAddressFamily.InterNetwork); + public static IpAddressInfo IPv6Loopback = new IpAddressInfo("IPv6Loopback", IpAddressFamily.InterNetworkV6); + public string Address { get; set; } - public bool IsIpv6 { get; set; } + public IpAddressFamily AddressFamily { get; set; } + + public IpAddressInfo() + { + + } + + public IpAddressInfo(string address, IpAddressFamily addressFamily) + { + Address = address; + AddressFamily = addressFamily; + } + + public bool Equals(IpAddressInfo address) + { + return string.Equals(address.Address, Address, StringComparison.OrdinalIgnoreCase); + } public override String ToString() { return Address; } } + + public enum IpAddressFamily + { + InterNetwork, + InterNetworkV6 + } } diff --git a/MediaBrowser.Model/Net/IpEndPointInfo.cs b/MediaBrowser.Model/Net/IpEndPointInfo.cs index 5fd331a16..b5cadc429 100644 --- a/MediaBrowser.Model/Net/IpEndPointInfo.cs +++ b/MediaBrowser.Model/Net/IpEndPointInfo.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace MediaBrowser.Model.Net { @@ -8,11 +9,22 @@ namespace MediaBrowser.Model.Net public int Port { get; set; } + public IpEndPointInfo() + { + + } + + public IpEndPointInfo(IpAddressInfo address, int port) + { + IpAddress = address; + Port = port; + } + public override string ToString() { var ipAddresString = IpAddress == null ? string.Empty : IpAddress.ToString(); - return ipAddresString + ":" + this.Port.ToString(); + return ipAddresString + ":" + Port.ToString(CultureInfo.InvariantCulture); } } } diff --git a/MediaBrowser.Model/Services/IHasRequestFilter.cs b/MediaBrowser.Model/Services/IHasRequestFilter.cs index c5c6ccf59..2164179d5 100644 --- a/MediaBrowser.Model/Services/IHasRequestFilter.cs +++ b/MediaBrowser.Model/Services/IHasRequestFilter.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - + namespace MediaBrowser.Model.Services { public interface IHasRequestFilter @@ -22,11 +17,5 @@ namespace MediaBrowser.Model.Services /// The http response wrapper /// The request DTO void RequestFilter(IRequest req, IResponse res, object requestDto); - - /// - /// A new shallow copy of this filter is used on every request. - /// - /// - IHasRequestFilter Copy(); } } diff --git a/MediaBrowser.Model/Services/IHttpResult.cs b/MediaBrowser.Model/Services/IHttpResult.cs new file mode 100644 index 000000000..36ffeb284 --- /dev/null +++ b/MediaBrowser.Model/Services/IHttpResult.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.Model.Services +{ + public interface IHttpResult : IHasHeaders + { + /// + /// The HTTP Response Status + /// + int Status { get; set; } + + /// + /// The HTTP Response Status Code + /// + HttpStatusCode StatusCode { get; set; } + + /// + /// The HTTP Status Description + /// + string StatusDescription { get; set; } + + /// + /// The HTTP Response ContentType + /// + string ContentType { get; set; } + + /// + /// Additional HTTP Cookies + /// + List Cookies { get; } + + /// + /// Response DTO + /// + object Response { get; set; } + + /// + /// Holds the request call context + /// + IRequest RequestContext { get; set; } + } +} diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 45dc97b76..5dc995b06 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -60,16 +60,6 @@ namespace MediaBrowser.Model.Services QueryParamCollection QueryString { get; } QueryParamCollection FormData { get; } - /// - /// Buffer the Request InputStream so it can be re-read - /// - bool UseBufferedStream { get; set; } - - /// - /// The entire string contents of Request.InputStream - /// - /// - string GetRawBody(); string RawUrl { get; } diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 1ab3f0bfb..dfea62821 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Model.Services { public QueryParamCollection() { - + } public QueryParamCollection(IDictionary headers) @@ -30,15 +30,30 @@ namespace MediaBrowser.Model.Services return StringComparer.OrdinalIgnoreCase; } + public string GetKey(int index) + { + return this[index].Name; + } + + public string Get(int index) + { + return this[index].Value; + } + + public virtual string[] GetValues(int index) + { + return new[] { Get(index) }; + } + /// /// Adds a new query parameter. /// - public void Add(string key, string value) + public virtual void Add(string key, string value) { Add(new NameValuePair(key, value)); } - public void Set(string key, string value) + public virtual void Set(string key, string value) { if (string.IsNullOrWhiteSpace(value)) { @@ -81,17 +96,21 @@ namespace MediaBrowser.Model.Services /// /// The number of parameters that were removed /// is null. - public int Remove(string name) + public virtual int Remove(string name) { return RemoveAll(p => p.Name == name); } public string Get(string name) { - return GetValues(name).FirstOrDefault(); + var stringComparison = GetStringComparison(); + + return this.Where(p => string.Equals(p.Name, name, stringComparison)) + .Select(p => p.Value) + .FirstOrDefault(); } - public string[] GetValues(string name) + public virtual string[] GetValues(string name) { var stringComparison = GetStringComparison(); diff --git a/MediaBrowser.Model/Text/ITextEncoding.cs b/MediaBrowser.Model/Text/ITextEncoding.cs new file mode 100644 index 000000000..6901f1f94 --- /dev/null +++ b/MediaBrowser.Model/Text/ITextEncoding.cs @@ -0,0 +1,10 @@ +using System.Text; + +namespace MediaBrowser.Model.Text +{ + public interface ITextEncoding + { + Encoding GetASCIIEncoding(); + Encoding GetFileEncoding(string path); + } +} diff --git a/MediaBrowser.Model/TextEncoding/IEncoding.cs b/MediaBrowser.Model/TextEncoding/IEncoding.cs deleted file mode 100644 index 3d884c9d2..000000000 --- a/MediaBrowser.Model/TextEncoding/IEncoding.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text; - -namespace MediaBrowser.Model.TextEncoding -{ - public interface IEncoding - { - byte[] GetASCIIBytes(string text); - string GetASCIIString(byte[] bytes, int startIndex, int length); - - Encoding GetFileEncoding(string path); - } -} diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index d4bfad174..59d67740d 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Providers.Manager private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; /// /// Initializes a new instance of the class. @@ -49,7 +49,7 @@ namespace MediaBrowser.Providers.Manager /// The directory watchers. /// The file system. /// The logger. - public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger, IMemoryStreamProvider memoryStreamProvider) + public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger, IMemoryStreamFactory memoryStreamProvider) { _config = config; _libraryMonitor = libraryMonitor; diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 67ad21dbb..5e00b356a 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.Providers.Manager private IExternalId[] _externalIds; private readonly Func _libraryManagerFactory; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; /// /// Initializes a new instance of the class. @@ -76,7 +76,7 @@ namespace MediaBrowser.Providers.Manager /// The directory watchers. /// The log manager. /// The file system. - public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func libraryManagerFactory, IJsonSerializer json, IMemoryStreamProvider memoryStreamProvider) + public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func libraryManagerFactory, IJsonSerializer json, IMemoryStreamFactory memoryStreamProvider) { _logger = logManager.GetLogger("ProviderManager"); _httpClient = httpClient; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index e4becec56..313feda52 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -45,6 +45,11 @@ namespace MediaBrowser.Providers.MediaInfo var codec = Path.GetExtension(fullName).ToLower().TrimStart('.'); + if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase)) + { + codec = "srt"; + } + // If the subtitle file matches the video file name if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { @@ -74,9 +79,9 @@ namespace MediaBrowser.Providers.MediaInfo // Try to translate to three character code // Be flexible and check against both the full and three character versions var culture = _localization.GetCultures() - .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || - string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || - string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || + .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || + string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || + string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); if (culture != null) @@ -119,7 +124,7 @@ namespace MediaBrowser.Providers.MediaInfo { get { - return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami" }; + return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami", ".txt" }; } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 66adf6c8a..5147e9d10 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -40,10 +40,10 @@ namespace MediaBrowser.Providers.TV private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly ILocalizationManager _localizationManager; - public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IMemoryStreamProvider memoryStreamProvider, IXmlReaderSettingsFactory xmlSettings, ILocalizationManager localizationManager) + public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IMemoryStreamFactory memoryStreamProvider, IXmlReaderSettingsFactory xmlSettings, ILocalizationManager localizationManager) { _zipClient = zipClient; _httpClient = httpClient; diff --git a/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs b/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs index 93d224b8d..235b62f69 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/ContainerAdapter.cs @@ -6,7 +6,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// /// Class ContainerAdapter /// - class ContainerAdapter : IContainerAdapter, IRelease + class ContainerAdapter : IContainerAdapter { /// /// The _app host @@ -40,14 +40,5 @@ namespace MediaBrowser.Server.Implementations.HttpServer { return _appHost.TryResolve(); } - - /// - /// Releases the specified instance. - /// - /// The instance. - public void Release(object instance) - { - // Leave this empty so SS doesn't try to dispose our objects - } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 805cb0353..ebb282503 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -8,24 +8,31 @@ using MediaBrowser.Server.Implementations.HttpServer.SocketSharp; using ServiceStack; using ServiceStack.Host; using ServiceStack.Host.Handlers; -using ServiceStack.Logging; using ServiceStack.Web; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Security; +using System.Net.Sockets; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; +using Emby.Common.Implementations.Net; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; using MediaBrowser.Common.Net; using MediaBrowser.Common.Security; using MediaBrowser.Controller; +using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; -using ServiceStack.Api.Swagger; +using MediaBrowser.Model.Text; +using SocketHttpListener.Net; +using SocketHttpListener.Primitives; namespace MediaBrowser.Server.Implementations.HttpServer { @@ -49,21 +56,28 @@ namespace MediaBrowser.Server.Implementations.HttpServer private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly IServerApplicationHost _appHost; + private readonly ITextEncoding _textEncoding; + private readonly ISocketFactory _socketFactory; + private readonly ICryptoProvider _cryptoProvider; + public HttpListenerHost(IServerApplicationHost applicationHost, ILogManager logManager, IServerConfigurationManager config, string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamProvider memoryStreamProvider, params Assembly[] assembliesWithServices) - : base(serviceName, assembliesWithServices) + string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider) + : base(serviceName, new Assembly[] { }) { _appHost = applicationHost; DefaultRedirectPath = defaultRedirectPath; _networkManager = networkManager; _memoryStreamProvider = memoryStreamProvider; + _textEncoding = textEncoding; + _socketFactory = socketFactory; + _cryptoProvider = cryptoProvider; _config = config; _logger = logManager.GetLogger("HttpServer"); @@ -73,10 +87,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer public string GlobalResponse { get; set; } - public override void Configure(Container container) + public override void Configure() { HostConfig.Instance.DefaultRedirectPath = DefaultRedirectPath; - HostConfig.Instance.LogUnobservedTaskExceptions = false; HostConfig.Instance.MapExceptionToStatusCode = new Dictionary { @@ -94,19 +107,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer }; HostConfig.Instance.GlobalResponseHeaders = new Dictionary(); - HostConfig.Instance.DebugMode = false; - - HostConfig.Instance.LogFactory = LogManager.LogFactory; - HostConfig.Instance.AllowJsonpRequests = false; // The Markdown feature causes slow startup times (5 mins+) on cold boots for some users // Custom format allows images HostConfig.Instance.EnableFeatures = Feature.Html | Feature.Json | Feature.Xml | Feature.CustomFormat; - container.Adapter = _containerAdapter; - - Plugins.Add(new SwaggerFeature()); - Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization")); + Container.Adapter = _containerAdapter; //Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { // new SessionAuthProvider(_containerAdapter.Resolve()), @@ -130,6 +136,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); } + protected override ILogger Logger + { + get + { + return _logger; + } + } + public override void OnAfterInit() { SetAppDomainData(); @@ -207,7 +221,33 @@ namespace MediaBrowser.Server.Implementations.HttpServer private IHttpListener GetListener() { - return new WebSocketSharpListener(_logger, CertificatePath, _memoryStreamProvider); + var cert = !string.IsNullOrWhiteSpace(CertificatePath) && File.Exists(CertificatePath) + ? GetCert(CertificatePath) : + null; + + return new WebSocketSharpListener(_logger, cert, _memoryStreamProvider, _textEncoding, _networkManager, _socketFactory, _cryptoProvider, new StreamFactory(), GetRequest); + } + + public static ICertificate GetCert(string certificateLocation) + { + X509Certificate2 localCert = new X509Certificate2(certificateLocation); + //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; + if (localCert.PrivateKey == null) + { + //throw new FileNotFoundException("Secure requested, no private key included", certificateLocation); + return null; + } + + return new Certificate(localCert); + } + + private IHttpRequest GetRequest(HttpListenerContext httpContext) + { + var operationName = httpContext.Request.GetOperationName(); + + var req = new WebSocketSharpRequest(httpContext, operationName, _logger, _memoryStreamProvider); + + return req; } private void OnWebSocketConnecting(WebSocketConnectingEventArgs args) @@ -259,11 +299,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer var contentType = httpReq.ResponseContentType; - var serializer = HostContext.ContentTypes.GetResponseSerializer(contentType); + var serializer = ContentTypes.Instance.GetResponseSerializer(contentType); if (serializer == null) { contentType = HostContext.Config.DefaultContentType; - serializer = HostContext.ContentTypes.GetResponseSerializer(contentType); + serializer = ContentTypes.Instance.GetResponseSerializer(contentType); } var httpError = ex as IHttpError; @@ -411,171 +451,170 @@ namespace MediaBrowser.Server.Implementations.HttpServer protected async Task RequestHandler(IHttpRequest httpReq, Uri url) { var date = DateTime.Now; - var httpRes = httpReq.Response; + bool enableLog = false; + string urlToLog = null; + string remoteIp = null; - if (_disposed) + try { - httpRes.StatusCode = 503; - httpRes.Close(); - return ; - } + if (_disposed) + { + httpRes.StatusCode = 503; + return; + } - if (!ValidateHost(url)) - { - httpRes.StatusCode = 400; - httpRes.ContentType = "text/plain"; - httpRes.Write("Invalid host"); + if (!ValidateHost(url)) + { + httpRes.StatusCode = 400; + httpRes.ContentType = "text/plain"; + httpRes.Write("Invalid host"); + return; + } - httpRes.Close(); - return; - } + if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) + { + httpRes.StatusCode = 200; + httpRes.AddHeader("Access-Control-Allow-Origin", "*"); + httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); + httpRes.AddHeader("Access-Control-Allow-Headers", + "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); + httpRes.ContentType = "text/html"; + return; + } - if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) - { - httpRes.StatusCode = 200; - httpRes.AddHeader("Access-Control-Allow-Origin", "*"); - httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); - httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); - httpRes.ContentType = "text/html"; + var operationName = httpReq.OperationName; + var localPath = url.LocalPath; - httpRes.Close(); - } + var urlString = url.OriginalString; + enableLog = EnableLogging(urlString, localPath); + urlToLog = urlString; - var operationName = httpReq.OperationName; - var localPath = url.LocalPath; + if (enableLog) + { + urlToLog = GetUrlToLog(urlString); + remoteIp = httpReq.RemoteIp; - var urlString = url.OriginalString; - var enableLog = EnableLogging(urlString, localPath); - var urlToLog = urlString; + LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent); + } - if (enableLog) - { - urlToLog = GetUrlToLog(urlString); - LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent); - } + if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) || + string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) + { + RedirectToUrl(httpRes, DefaultRedirectPath); + return; + } + if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) || + string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) + { + RedirectToUrl(httpRes, "emby/" + DefaultRedirectPath); + return; + } - if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) || - string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase)) - { - httpRes.RedirectToUrl(DefaultRedirectPath); - return; - } - if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) || - string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase)) - { - httpRes.RedirectToUrl("emby/" + DefaultRedirectPath); - return; - } + if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) || + string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase) || + localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1) + { + httpRes.StatusCode = 200; + httpRes.ContentType = "text/html"; + var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase) + .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase); - if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) || - string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase) || - localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1) - { - httpRes.StatusCode = 200; - httpRes.ContentType = "text/html"; - var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase) - .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase); + if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) + { + httpRes.Write( + "EmbyPlease update your Emby bookmark to " + newUrl + ""); + return; + } + } - if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) + if (localPath.IndexOf("dashboard/", StringComparison.OrdinalIgnoreCase) != -1 && + localPath.IndexOf("web/dashboard", StringComparison.OrdinalIgnoreCase) == -1) { - httpRes.Write("EmbyPlease update your Emby bookmark to " + newUrl + ""); + httpRes.StatusCode = 200; + httpRes.ContentType = "text/html"; + var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase) + .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase); - httpRes.Close(); - return; + if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) + { + httpRes.Write( + "EmbyPlease update your Emby bookmark to " + newUrl + ""); + return; + } } - } - if (localPath.IndexOf("dashboard/", StringComparison.OrdinalIgnoreCase) != -1 && - localPath.IndexOf("web/dashboard", StringComparison.OrdinalIgnoreCase) == -1) - { - httpRes.StatusCode = 200; - httpRes.ContentType = "text/html"; - var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase) - .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase); + if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase)) + { + RedirectToUrl(httpRes, DefaultRedirectPath); + return; + } + if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase)) + { + RedirectToUrl(httpRes, "../" + DefaultRedirectPath); + return; + } + if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) + { + RedirectToUrl(httpRes, DefaultRedirectPath); + return; + } + if (string.IsNullOrEmpty(localPath)) + { + RedirectToUrl(httpRes, "/" + DefaultRedirectPath); + return; + } - if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase)) { - httpRes.Write("EmbyPlease update your Emby bookmark to " + newUrl + ""); + RedirectToUrl(httpRes, "web/pin.html"); + return; + } - httpRes.Close(); + if (!string.IsNullOrWhiteSpace(GlobalResponse)) + { + httpRes.StatusCode = 503; + httpRes.ContentType = "text/html"; + httpRes.Write(GlobalResponse); return; } - } - if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase)) - { - httpRes.RedirectToUrl(DefaultRedirectPath); - return; - } - if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase)) - { - httpRes.RedirectToUrl("../" + DefaultRedirectPath); - return; - } - if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)) - { - httpRes.RedirectToUrl(DefaultRedirectPath); - return; - } - if (string.IsNullOrEmpty(localPath)) - { - httpRes.RedirectToUrl("/" + DefaultRedirectPath); - return; - } + var handler = HttpHandlerFactory.GetHandler(httpReq); - if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase)) - { - httpRes.RedirectToUrl("web/pin.html"); - return; + if (handler != null) + { + await handler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false); + } } - - if (!string.IsNullOrWhiteSpace(GlobalResponse)) + catch (Exception ex) { - httpRes.StatusCode = 503; - httpRes.ContentType = "text/html"; - httpRes.Write(GlobalResponse); - - httpRes.Close(); - return; + ErrorHandler(ex, httpReq); } - - var handler = HttpHandlerFactory.GetHandler(httpReq); - - var remoteIp = httpReq.RemoteIp; - - var serviceStackHandler = handler as IServiceStackHandler; - if (serviceStackHandler != null) + finally { - var restHandler = serviceStackHandler as RestHandler; - if (restHandler != null) - { - httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName(); - } + httpRes.Close(); - try - { - await serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false); - } - finally + if (enableLog) { - httpRes.Close(); var statusCode = httpRes.StatusCode; var duration = DateTime.Now - date; - if (enableLog) - { - LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration); - } + LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration); } } - else - { - httpRes.Close(); - } } + public static void RedirectToUrl(IResponse httpRes, string url) + { + httpRes.StatusCode = 302; + httpRes.AddHeader(HttpHeaders.Location, url); + httpRes.EndRequest(); + } + + /// /// Adds the rest handlers. /// @@ -653,15 +692,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer return "mediabrowser/" + path; } - /// - /// Releases the specified instance. - /// - /// The instance. - public override void Release(object instance) - { - // Leave this empty so SS doesn't try to dispose our objects - } - private bool _disposed; private readonly object _disposeLock = new object(); protected virtual void Dispose(bool disposing) @@ -696,4 +726,37 @@ namespace MediaBrowser.Server.Implementations.HttpServer Start(UrlPrefixes.First()); } } + + public class StreamFactory : IStreamFactory + { + public Stream CreateNetworkStream(ISocket socket, bool ownsSocket) + { + var netSocket = (NetSocket)socket; + + return new NetworkStream(netSocket.Socket, ownsSocket); + } + + public Task AuthenticateSslStreamAsServer(Stream stream, ICertificate certificate) + { + var sslStream = (SslStream)stream; + var cert = (Certificate)certificate; + + return sslStream.AuthenticateAsServerAsync(cert.X509Certificate); + } + + public Stream CreateSslStream(Stream innerStream, bool leaveInnerStreamOpen) + { + return new SslStream(innerStream, leaveInnerStreamOpen); + } + } + + public class Certificate : ICertificate + { + public Certificate(X509Certificate x509Certificate) + { + X509Certificate = x509Certificate; + } + + public X509Certificate X509Certificate { get; private set; } + } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 95e1a35e6..4c251ba24 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -9,6 +9,7 @@ using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; +using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using ServiceStack; diff --git a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs deleted file mode 100644 index bfbb228ed..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs +++ /dev/null @@ -1,43 +0,0 @@ -using MediaBrowser.Model.Logging; -using System; -using System.Globalization; -using SocketHttpListener.Net; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - public static class LoggerUtils - { - /// - /// Logs the request. - /// - /// The logger. - /// The request. - public static void LogRequest(ILogger logger, HttpListenerRequest request) - { - var url = request.Url.ToString(); - - logger.Info("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty); - } - - public static void LogRequest(ILogger logger, string url, string method, string userAgent) - { - logger.Info("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty); - } - - /// - /// Logs the response. - /// - /// The logger. - /// The status code. - /// The URL. - /// The end point. - /// The duration. - public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration) - { - var durationMs = duration.TotalMilliseconds; - var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; - - logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url); - } - } -} diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs deleted file mode 100644 index 7d4cd3b4d..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ /dev/null @@ -1,230 +0,0 @@ -using MediaBrowser.Model.Logging; -using ServiceStack.Web; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult - { - /// - /// Gets or sets the source stream. - /// - /// The source stream. - private Stream SourceStream { get; set; } - private string RangeHeader { get; set; } - private bool IsHeadRequest { get; set; } - - private long RangeStart { get; set; } - private long RangeEnd { get; set; } - private long RangeLength { get; set; } - private long TotalContentLength { get; set; } - - public Action OnComplete { get; set; } - private readonly ILogger _logger; - - private const int BufferSize = 81920; - - /// - /// The _options - /// - private readonly Dictionary _options = new Dictionary(); - - /// - /// The us culture - /// - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - public Func ResultScope { get; set; } - public List Cookies { get; private set; } - - /// - /// Additional HTTP Headers - /// - /// The headers. - public IDictionary Headers - { - get { return _options; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The range header. - /// The source. - /// Type of the content. - /// if set to true [is head request]. - public RangeRequestWriter(string rangeHeader, Stream source, string contentType, bool isHeadRequest, ILogger logger) - { - if (string.IsNullOrEmpty(contentType)) - { - throw new ArgumentNullException("contentType"); - } - - RangeHeader = rangeHeader; - SourceStream = source; - IsHeadRequest = isHeadRequest; - this._logger = logger; - - ContentType = contentType; - Headers["Content-Type"] = contentType; - Headers["Accept-Ranges"] = "bytes"; - StatusCode = HttpStatusCode.PartialContent; - - Cookies = new List(); - SetRangeValues(); - } - - /// - /// Sets the range values. - /// - private void SetRangeValues() - { - var requestedRange = RequestedRanges[0]; - - TotalContentLength = SourceStream.Length; - - // If the requested range is "0-", we can optimize by just doing a stream copy - if (!requestedRange.Value.HasValue) - { - RangeEnd = TotalContentLength - 1; - } - else - { - RangeEnd = requestedRange.Value.Value; - } - - RangeStart = requestedRange.Key; - RangeLength = 1 + RangeEnd - RangeStart; - - // Content-Length is the length of what we're serving, not the original content - Headers["Content-Length"] = RangeLength.ToString(UsCulture); - Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); - - if (RangeStart > 0) - { - SourceStream.Position = RangeStart; - } - } - - /// - /// The _requested ranges - /// - private List> _requestedRanges; - /// - /// Gets the requested ranges. - /// - /// The requested ranges. - protected List> RequestedRanges - { - get - { - if (_requestedRanges == null) - { - _requestedRanges = new List>(); - - // Example: bytes=0-,32-63 - var ranges = RangeHeader.Split('=')[1].Split(','); - - foreach (var range in ranges) - { - var vals = range.Split('-'); - - long start = 0; - long? end = null; - - if (!string.IsNullOrEmpty(vals[0])) - { - start = long.Parse(vals[0], UsCulture); - } - if (!string.IsNullOrEmpty(vals[1])) - { - end = long.Parse(vals[1], UsCulture); - } - - _requestedRanges.Add(new KeyValuePair(start, end)); - } - } - - return _requestedRanges; - } - } - - public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) - { - try - { - // Headers only - if (IsHeadRequest) - { - return; - } - - using (var source = SourceStream) - { - // If the requested range is "0-", we can optimize by just doing a stream copy - if (RangeEnd >= TotalContentLength - 1) - { - await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); - } - else - { - await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false); - } - } - } - finally - { - if (OnComplete != null) - { - OnComplete(); - } - } - } - - private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength) - { - var array = new byte[BufferSize]; - int count; - while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0) - { - var bytesToCopy = Math.Min(count, copyLength); - - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false); - - copyLength -= bytesToCopy; - - if (copyLength <= 0) - { - break; - } - } - } - - public string ContentType { get; set; } - - public IRequest RequestContext { get; set; } - - public object Response { get; set; } - - public IContentTypeWriter ResponseFilter { get; set; } - - public int Status { get; set; } - - public HttpStatusCode StatusCode - { - get { return (HttpStatusCode)Status; } - set { Status = (int)value; } - } - - public string StatusDescription { get; set; } - - public int PaddingLength { get; set; } - } -} \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs b/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs deleted file mode 100644 index 6247e4c17..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs +++ /dev/null @@ -1,127 +0,0 @@ -using MediaBrowser.Model.Logging; -using MediaBrowser.Server.Implementations.HttpServer.SocketSharp; -using System; -using System.Globalization; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - public class ResponseFilter - { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - private readonly ILogger _logger; - - public ResponseFilter(ILogger logger) - { - _logger = logger; - } - - /// - /// Filters the response. - /// - /// The req. - /// The res. - /// The dto. - public void FilterResponse(IRequest req, IResponse res, object dto) - { - // Try to prevent compatibility view - res.AddHeader("X-UA-Compatible", "IE=Edge"); - - var exception = dto as Exception; - - if (exception != null) - { - _logger.ErrorException("Error processing request for {0}", exception, req.RawUrl); - - if (!string.IsNullOrEmpty(exception.Message)) - { - var error = exception.Message.Replace(Environment.NewLine, " "); - error = RemoveControlCharacters(error); - - res.AddHeader("X-Application-Error-Code", error); - } - } - - var vary = "Accept-Encoding"; - - var hasHeaders = dto as IHasHeaders; - var sharpResponse = res as WebSocketSharpResponse; - - if (hasHeaders != null) - { - if (!hasHeaders.Headers.ContainsKey("Server")) - { - hasHeaders.Headers["Server"] = "Mono-HTTPAPI/1.1, UPnP/1.0 DLNADOC/1.50"; - //hasHeaders.Headers["Server"] = "Mono-HTTPAPI/1.1"; - } - - // Content length has to be explicitly set on on HttpListenerResponse or it won't be happy - string contentLength; - - if (hasHeaders.Headers.TryGetValue("Content-Length", out contentLength) && !string.IsNullOrEmpty(contentLength)) - { - var length = long.Parse(contentLength, UsCulture); - - if (length > 0) - { - res.SetContentLength(length); - - var listenerResponse = res.OriginalResponse as HttpListenerResponse; - - if (listenerResponse != null) - { - // Disable chunked encoding. Technically this is only needed when using Content-Range, but - // anytime we know the content length there's no need for it - listenerResponse.SendChunked = false; - return; - } - - if (sharpResponse != null) - { - sharpResponse.SendChunked = false; - } - } - } - - string hasHeadersVary; - if (hasHeaders.Headers.TryGetValue("Vary", out hasHeadersVary)) - { - vary = hasHeadersVary; - } - - hasHeaders.Headers["Vary"] = vary; - } - - //res.KeepAlive = false; - - // Per Google PageSpeed - // This instructs the proxies to cache two versions of the resource: one compressed, and one uncompressed. - // The correct version of the resource is delivered based on the client request header. - // This is a good choice for applications that are singly homed and depend on public proxies for user locality. - res.AddHeader("Vary", vary); - } - - /// - /// Removes the control characters. - /// - /// The in string. - /// System.String. - public static string RemoveControlCharacters(string inString) - { - if (inString == null) return null; - - var newString = new StringBuilder(); - - foreach (var ch in inString) - { - if (!char.IsControl(ch)) - { - newString.Append(ch); - } - } - return newString.ToString(); - } - } -} diff --git a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs index 4dff2d5a3..5da515900 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs @@ -2,9 +2,11 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; -using ServiceStack.Logging; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Text; namespace MediaBrowser.Server.Implementations.HttpServer { @@ -21,13 +23,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer ILogManager logManager, IServerConfigurationManager config, INetworkManager networkmanager, - IMemoryStreamProvider streamProvider, + IMemoryStreamFactory streamProvider, string serverName, - string defaultRedirectpath) + string defaultRedirectpath, + ITextEncoding textEncoding, + ISocketFactory socketFactory, + ICryptoProvider cryptoProvider) { - LogManager.LogFactory = new ServerLogFactory(logManager); - - return new HttpListenerHost(applicationHost, logManager, config, serverName, defaultRedirectpath, networkmanager, streamProvider); + return new HttpListenerHost(applicationHost, logManager, config, serverName, defaultRedirectpath, networkmanager, streamProvider, textEncoding, socketFactory, cryptoProvider); } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/ServerLogFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/ServerLogFactory.cs deleted file mode 100644 index 40af3f3b0..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/ServerLogFactory.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using MediaBrowser.Model.Logging; -using ServiceStack.Logging; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - /// - /// Class ServerLogFactory - /// - public class ServerLogFactory : ILogFactory - { - /// - /// The _log manager - /// - private readonly ILogManager _logManager; - - /// - /// Initializes a new instance of the class. - /// - /// The log manager. - public ServerLogFactory(ILogManager logManager) - { - _logManager = logManager; - } - - /// - /// Gets the logger. - /// - /// Name of the type. - /// ILog. - public ILog GetLogger(string typeName) - { - return new ServerLogger(_logManager.GetLogger(typeName)); - } - - /// - /// Gets the logger. - /// - /// The type. - /// ILog. - public ILog GetLogger(Type type) - { - return GetLogger(type.Name); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/ServerLogger.cs b/MediaBrowser.Server.Implementations/HttpServer/ServerLogger.cs deleted file mode 100644 index bf7924784..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/ServerLogger.cs +++ /dev/null @@ -1,194 +0,0 @@ -using MediaBrowser.Model.Logging; -using ServiceStack.Logging; -using System; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - /// - /// Class ServerLogger - /// - public class ServerLogger : ILog - { - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - public ServerLogger(ILogger logger) - { - _logger = logger; - } - - /// - /// Logs a Debug message and exception. - /// - /// The message. - /// The exception. - public void Debug(object message, Exception exception) - { - _logger.ErrorException(GetMesssage(message), exception); - } - - /// - /// Logs a Debug message. - /// - /// The message. - public void Debug(object message) - { - // Way too verbose. Can always make this configurable if needed again. - //_logger.Debug(GetMesssage(message)); - } - - /// - /// Logs a Debug format message. - /// - /// The format. - /// The args. - public void DebugFormat(string format, params object[] args) - { - // Way too verbose. Can always make this configurable if needed again. - //_logger.Debug(format, args); - } - - /// - /// Logs a Error message and exception. - /// - /// The message. - /// The exception. - public void Error(object message, Exception exception) - { - _logger.ErrorException(GetMesssage(message), exception); - } - - /// - /// Logs a Error message. - /// - /// The message. - public void Error(object message) - { - _logger.Error(GetMesssage(message)); - } - - /// - /// Logs a Error format message. - /// - /// The format. - /// The args. - public void ErrorFormat(string format, params object[] args) - { - _logger.Error(format, args); - } - - /// - /// Logs a Fatal message and exception. - /// - /// The message. - /// The exception. - public void Fatal(object message, Exception exception) - { - _logger.FatalException(GetMesssage(message), exception); - } - - /// - /// Logs a Fatal message. - /// - /// The message. - public void Fatal(object message) - { - _logger.Fatal(GetMesssage(message)); - } - - /// - /// Logs a Error format message. - /// - /// The format. - /// The args. - public void FatalFormat(string format, params object[] args) - { - _logger.Fatal(format, args); - } - - /// - /// Logs an Info message and exception. - /// - /// The message. - /// The exception. - public void Info(object message, Exception exception) - { - _logger.ErrorException(GetMesssage(message), exception); - } - - /// - /// Logs an Info message and exception. - /// - /// The message. - public void Info(object message) - { - _logger.Info(GetMesssage(message)); - } - - /// - /// Logs an Info format message. - /// - /// The format. - /// The args. - public void InfoFormat(string format, params object[] args) - { - _logger.Info(format, args); - } - - /// - /// Gets or sets a value indicating whether this instance is debug enabled. - /// - /// true if this instance is debug enabled; otherwise, false. - public bool IsDebugEnabled - { - get { return true; } - } - - /// - /// Logs a Warning message and exception. - /// - /// The message. - /// The exception. - public void Warn(object message, Exception exception) - { - _logger.ErrorException(GetMesssage(message), exception); - } - - /// - /// Logs a Warning message. - /// - /// The message. - public void Warn(object message) - { - // Hide StringMapTypeDeserializer messages - // _logger.Warn(GetMesssage(message)); - } - - /// - /// Logs a Warning format message. - /// - /// The format. - /// The args. - public void WarnFormat(string format, params object[] args) - { - // Hide StringMapTypeDeserializer messages - // _logger.Warn(format, args); - } - - /// - /// Gets the messsage. - /// - /// The o. - /// System.String. - private string GetMesssage(object o) - { - return o == null ? string.Empty : o.ToString(); - } - } -} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs deleted file mode 100644 index 154313fb9..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/Extensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MediaBrowser.Model.Logging; -using SocketHttpListener.Net; -using System; - -namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp -{ - public static class Extensions - { - public static string GetOperationName(this HttpListenerRequest request) - { - return request.Url.Segments[request.Url.Segments.Length - 1]; - } - - public static void CloseOutputStream(this HttpListenerResponse response, ILogger logger) - { - try - { - response.OutputStream.Flush(); - response.OutputStream.Close(); - response.Close(); - } - catch (Exception ex) - { - logger.ErrorException("Error in HttpListenerResponseWrapper: " + ex.Message, ex); - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs index 13ae48cff..543eb4afe 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs @@ -2,11 +2,10 @@ using System.Collections.Specialized; using System.Globalization; using System.IO; +using System.Net; using System.Text; using System.Threading.Tasks; -using System.Web; using MediaBrowser.Model.Services; -using ServiceStack; namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { @@ -128,7 +127,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { get { - return string.IsNullOrEmpty(request.Headers[HttpHeaders.Accept]) ? null : request.Headers[HttpHeaders.Accept]; + return string.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"]; } } @@ -136,7 +135,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { get { - return string.IsNullOrEmpty(request.Headers[HttpHeaders.Authorization]) ? null : request.Headers[HttpHeaders.Authorization]; + return string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"]; } } @@ -152,7 +151,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp string msg = String.Format("A potentially dangerous Request.{0} value was " + "detected from the client ({1}={2}).", name, key, v); - throw new HttpRequestValidationException(msg); + throw new Exception(msg); } static void ValidateNameValueCollection(string name, QueryParamCollection coll) @@ -278,9 +277,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp void AddRawKeyValue(StringBuilder key, StringBuilder value) { - string decodedKey = HttpUtility.UrlDecode(key.ToString(), ContentEncoding); + string decodedKey = WebUtility.UrlDecode(key.ToString()); form.Add(decodedKey, - HttpUtility.UrlDecode(value.ToString(), ContentEncoding)); + WebUtility.UrlDecode(value.ToString())); key.Length = 0; value.Length = 0; diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs deleted file mode 100644 index d363c4de6..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs +++ /dev/null @@ -1,172 +0,0 @@ -using MediaBrowser.Common.Events; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Logging; -using System; -using System.Threading; -using System.Threading.Tasks; -using WebSocketState = MediaBrowser.Model.Net.WebSocketState; - -namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp -{ - public class SharpWebSocket : IWebSocket - { - /// - /// The logger - /// - private readonly ILogger _logger; - - public event EventHandler Closed; - - /// - /// Gets or sets the web socket. - /// - /// The web socket. - private SocketHttpListener.WebSocket WebSocket { get; set; } - - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - /// - /// Initializes a new instance of the class. - /// - /// The socket. - /// The logger. - /// socket - public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) - { - if (socket == null) - { - throw new ArgumentNullException("socket"); - } - - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - - _logger = logger; - WebSocket = socket; - - socket.OnMessage += socket_OnMessage; - socket.OnClose += socket_OnClose; - socket.OnError += socket_OnError; - - WebSocket.ConnectAsServer(); - } - - void socket_OnError(object sender, SocketHttpListener.ErrorEventArgs e) - { - _logger.Error("Error in SharpWebSocket: {0}", e.Message ?? string.Empty); - //EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger); - } - - void socket_OnClose(object sender, SocketHttpListener.CloseEventArgs e) - { - EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger); - } - - void socket_OnMessage(object sender, SocketHttpListener.MessageEventArgs e) - { - //if (!string.IsNullOrWhiteSpace(e.Data)) - //{ - // if (OnReceive != null) - // { - // OnReceive(e.Data); - // } - // return; - //} - if (OnReceiveBytes != null) - { - OnReceiveBytes(e.RawData); - } - } - - /// - /// Gets or sets the state. - /// - /// The state. - public WebSocketState State - { - get - { - WebSocketState commonState; - - if (!Enum.TryParse(WebSocket.ReadyState.ToString(), true, out commonState)) - { - _logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.ReadyState.ToString()); - } - - return commonState; - } - } - - /// - /// Sends the async. - /// - /// The bytes. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) - { - var completionSource = new TaskCompletionSource(); - - WebSocket.SendAsync(bytes, res => completionSource.TrySetResult(true)); - - return completionSource.Task; - } - - /// - /// Sends the asynchronous. - /// - /// The text. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) - { - var completionSource = new TaskCompletionSource(); - - WebSocket.SendAsync(text, res => completionSource.TrySetResult(true)); - - return completionSource.Task; - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - WebSocket.OnMessage -= socket_OnMessage; - WebSocket.OnClose -= socket_OnClose; - WebSocket.OnError -= socket_OnError; - - _cancellationTokenSource.Cancel(); - - WebSocket.Close(); - } - } - - /// - /// Gets or sets the receive action. - /// - /// The receive action. - public Action OnReceiveBytes { get; set; } - - /// - /// Gets or sets the on receive. - /// - /// The on receive. - public Action OnReceive { get; set; } - } -} diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs deleted file mode 100644 index 56f8ab429..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ /dev/null @@ -1,216 +0,0 @@ -using System.Collections.Specialized; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Logging; -using SocketHttpListener.Net; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; -using Emby.Server.Implementations.Logging; -using MediaBrowser.Common.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using ServiceStack; - -namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp -{ - public class WebSocketSharpListener : IHttpListener - { - private HttpListener _listener; - - private readonly ILogger _logger; - private readonly string _certificatePath; - private readonly IMemoryStreamProvider _memoryStreamProvider; - - public WebSocketSharpListener(ILogger logger, string certificatePath, IMemoryStreamProvider memoryStreamProvider) - { - _logger = logger; - _certificatePath = certificatePath; - _memoryStreamProvider = memoryStreamProvider; - } - - public Action ErrorHandler { get; set; } - - public Func RequestHandler { get; set; } - - public Action WebSocketConnecting { get; set; } - - public Action WebSocketConnected { get; set; } - - public void Start(IEnumerable urlPrefixes) - { - if (_listener == null) - _listener = new HttpListener(new PatternsLogger(_logger), _certificatePath); - - foreach (var prefix in urlPrefixes) - { - _logger.Info("Adding HttpListener prefix " + prefix); - _listener.Prefixes.Add(prefix); - } - - _listener.OnContext = ProcessContext; - - _listener.Start(); - } - - private void ProcessContext(HttpListenerContext context) - { - Task.Factory.StartNew(() => InitTask(context)); - } - - private void InitTask(HttpListenerContext context) - { - try - { - var task = this.ProcessRequestAsync(context); - task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent); - - //if (task.Status == TaskStatus.Created) - //{ - // task.RunSynchronously(); - //} - } - catch (Exception ex) - { - HandleError(ex, context); - } - } - - private Task ProcessRequestAsync(HttpListenerContext context) - { - var request = context.Request; - - if (request.IsWebSocketRequest) - { - LoggerUtils.LogRequest(_logger, request); - - ProcessWebSocketRequest(context); - return Task.FromResult(true); - } - - if (string.IsNullOrEmpty(context.Request.RawUrl)) - return ((object)null).AsTaskResult(); - - var httpReq = GetRequest(context); - - return RequestHandler(httpReq, request.Url); - } - - private void ProcessWebSocketRequest(HttpListenerContext ctx) - { - try - { - var endpoint = ctx.Request.RemoteEndPoint.ToString(); - var url = ctx.Request.RawUrl; - var queryString = ctx.Request.QueryString ?? new NameValueCollection(); - - var queryParamCollection = new QueryParamCollection(); - - foreach (var key in queryString.AllKeys) - { - queryParamCollection[key] = queryString[key]; - } - - var connectingArgs = new WebSocketConnectingEventArgs - { - Url = url, - QueryString = queryParamCollection, - Endpoint = endpoint - }; - - if (WebSocketConnecting != null) - { - WebSocketConnecting(connectingArgs); - } - - if (connectingArgs.AllowConnection) - { - _logger.Debug("Web socket connection allowed"); - - var webSocketContext = ctx.AcceptWebSocket(null); - - if (WebSocketConnected != null) - { - WebSocketConnected(new WebSocketConnectEventArgs - { - Url = url, - QueryString = queryParamCollection, - WebSocket = new SharpWebSocket(webSocketContext.WebSocket, _logger), - Endpoint = endpoint - }); - } - } - else - { - _logger.Warn("Web socket connection not allowed"); - ctx.Response.StatusCode = 401; - ctx.Response.Close(); - } - } - catch (Exception ex) - { - _logger.ErrorException("AcceptWebSocketAsync error", ex); - ctx.Response.StatusCode = 500; - ctx.Response.Close(); - } - } - - private IHttpRequest GetRequest(HttpListenerContext httpContext) - { - var operationName = httpContext.Request.GetOperationName(); - - var req = new WebSocketSharpRequest(httpContext, operationName, RequestAttributes.None, _logger, _memoryStreamProvider); - - return req; - } - - private void HandleError(Exception ex, HttpListenerContext context) - { - var httpReq = GetRequest(context); - - if (ErrorHandler != null) - { - ErrorHandler(ex, httpReq); - } - } - - public void Stop() - { - if (_listener != null) - { - foreach (var prefix in _listener.Prefixes.ToList()) - { - _listener.Prefixes.Remove(prefix); - } - - _listener.Close(); - } - } - - public void Dispose() - { - Dispose(true); - } - - private bool _disposed; - private readonly object _disposeLock = new object(); - protected virtual void Dispose(bool disposing) - { - if (_disposed) return; - - lock (_disposeLock) - { - if (_disposed) return; - - if (disposing) - { - Stop(); - } - - //release unmanaged resources here... - _disposed = true; - } - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs index 72047609d..6f44fcce7 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -1,17 +1,14 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; using System.Text; using Emby.Server.Implementations.HttpServer.SocketSharp; using Funq; -using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Services; using ServiceStack; using ServiceStack.Host; -using ServiceStack.Web; using SocketHttpListener.Net; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; @@ -25,9 +22,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp public Container Container { get; set; } private readonly HttpListenerRequest request; private readonly IHttpResponse response; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; - public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, RequestAttributes requestAttributes, ILogger logger, IMemoryStreamProvider memoryStreamProvider) + public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, ILogger logger, IMemoryStreamFactory memoryStreamProvider) { this.OperationName = operationName; _memoryStreamProvider = memoryStreamProvider; @@ -55,36 +52,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp get { return response; } } - public T TryResolve() - { - if (typeof(T) == typeof(IHttpRequest)) - throw new Exception("You don't need to use IHttpRequest.TryResolve to resolve itself"); - - if (typeof(T) == typeof(IHttpResponse)) - throw new Exception("Resolve IHttpResponse with 'Response' property instead of IHttpRequest.TryResolve"); - - return Container == null - ? HostContext.TryResolve() - : Container.TryResolve(); - } - public string OperationName { get; set; } public object Dto { get; set; } - public string GetRawBody() - { - if (bufferedStream != null) - { - return bufferedStream.ToArray().FromUtf8Bytes(); - } - - using (var reader = new StreamReader(InputStream)) - { - return reader.ReadToEnd(); - } - } - public string RawUrl { get { return request.RawUrl; } @@ -104,7 +75,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { get { - return String.IsNullOrEmpty(request.Headers[HttpHeaders.XForwardedFor]) ? null : request.Headers[HttpHeaders.XForwardedFor]; + return String.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"]; } } @@ -112,7 +83,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { get { - return string.IsNullOrEmpty(request.Headers[HttpHeaders.XForwardedPort]) ? (int?)null : int.Parse(request.Headers[HttpHeaders.XForwardedPort]); + return string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]); } } @@ -120,7 +91,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { get { - return string.IsNullOrEmpty(request.Headers[HttpHeaders.XForwardedProtocol]) ? null : request.Headers[HttpHeaders.XForwardedProtocol]; + return string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"]; } } @@ -128,7 +99,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { get { - return String.IsNullOrEmpty(request.Headers[HttpHeaders.XRealIp]) ? null : request.Headers[HttpHeaders.XRealIp]; + return String.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"]; } } @@ -140,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp return remoteIp ?? (remoteIp = (CheckBadChars(XForwardedFor)) ?? (NormalizeIp(CheckBadChars(XRealIp)) ?? - (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); + (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.IpAddress.ToString()) : null))); } } @@ -280,7 +251,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp defaultContentType = HostContext.Config.DefaultContentType; } - var customContentTypes = HostContext.ContentTypes.ContentTypeFormats.Values; + var customContentTypes = ContentTypes.Instance.ContentTypeFormats.Values; var preferredContentTypes = new string[] {}; var acceptsAnything = false; @@ -328,11 +299,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } } - if (httpReq.ContentType.MatchesContentType(MimeTypes.Soap12)) - { - return MimeTypes.Soap12; - } - if (acceptContentTypes == null && httpReq.ContentType == MimeTypes.Soap11) { return MimeTypes.Soap11; @@ -344,10 +310,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp private static string GetQueryStringContentType(IRequest httpReq) { - var callback = httpReq.QueryString[Keywords.Callback]; - if (!string.IsNullOrEmpty(callback)) return MimeTypes.Json; - - var format = httpReq.QueryString[Keywords.Format]; + var format = httpReq.QueryString["format"]; if (format == null) { const int formatMaxLength = 4; @@ -359,12 +322,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } format = format.LeftPart('.').ToLower(); - if (format.Contains("json")) return MimeTypes.Json; + if (format.Contains("json")) return "application/json"; if (format.Contains("xml")) return MimeTypes.Xml; - if (format.Contains("jsv")) return MimeTypes.Jsv; string contentType; - HostContext.ContentTypes.ContentTypeFormats.TryGetValue(format, out contentType); + ContentTypes.Instance.ContentTypeFormats.TryGetValue(format, out contentType); return contentType; } @@ -474,10 +436,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp get { return request.UserAgent; } } - private QueryParamCollection headers; public QueryParamCollection Headers { - get { return headers ?? (headers = ToQueryParams(request.Headers)); } + get { return request.Headers; } } private QueryParamCollection queryString; @@ -492,18 +453,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp get { return formData ?? (formData = this.Form); } } - private QueryParamCollection ToQueryParams(NameValueCollection collection) - { - var result = new QueryParamCollection(); - - foreach (var key in collection.AllKeys) - { - result[key] = collection[key]; - } - - return result; - } - public bool IsLocal { get { return request.IsLocal; } @@ -563,21 +512,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } } - public bool UseBufferedStream - { - get { return bufferedStream != null; } - set - { - bufferedStream = value - ? bufferedStream ?? _memoryStreamProvider.CreateNew(request.InputStream.ReadFully()) - : null; - } - } - - private MemoryStream bufferedStream; public Stream InputStream { - get { return bufferedStream ?? request.InputStream; } + get { return request.InputStream; } } public long ContentLength @@ -613,7 +550,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } } - static Stream GetSubStream(Stream stream, IMemoryStreamProvider streamProvider) + static Stream GetSubStream(Stream stream, IMemoryStreamFactory streamProvider) { if (stream is MemoryStream) { @@ -654,4 +591,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp return pathInfo; } } + + public class HttpFile : IHttpFile + { + public string Name { get; set; } + public string FileName { get; set; } + public long ContentLength { get; set; } + public string ContentType { get; set; } + public Stream InputStream { get; set; } + } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs deleted file mode 100644 index 3aae6c9ca..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using MediaBrowser.Model.Logging; -using ServiceStack; -using ServiceStack.Host; -using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; -using IRequest = MediaBrowser.Model.Services.IRequest; - -namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp -{ - public class WebSocketSharpResponse : IHttpResponse - { - private readonly ILogger _logger; - private readonly HttpListenerResponse _response; - - public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request) - { - _logger = logger; - this._response = response; - Items = new Dictionary(); - Request = request; - } - - public IRequest Request { get; private set; } - public bool UseBufferedStream { get; set; } - public Dictionary Items { get; private set; } - public object OriginalResponse - { - get { return _response; } - } - - public int StatusCode - { - get { return this._response.StatusCode; } - set { this._response.StatusCode = value; } - } - - public string StatusDescription - { - get { return this._response.StatusDescription; } - set { this._response.StatusDescription = value; } - } - - public string ContentType - { - get { return _response.ContentType; } - set { _response.ContentType = value; } - } - - //public ICookies Cookies { get; set; } - - public void AddHeader(string name, string value) - { - if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) - { - ContentType = value; - return; - } - - _response.AddHeader(name, value); - } - - public string GetHeader(string name) - { - return _response.Headers[name]; - } - - public void Redirect(string url) - { - _response.Redirect(url); - } - - public Stream OutputStream - { - get { return _response.OutputStream; } - } - - public object Dto { get; set; } - - public void Write(string text) - { - var bOutput = System.Text.Encoding.UTF8.GetBytes(text); - _response.ContentLength64 = bOutput.Length; - - var outputStream = _response.OutputStream; - outputStream.Write(bOutput, 0, bOutput.Length); - Close(); - } - - public void Close() - { - if (!this.IsClosed) - { - this.IsClosed = true; - - try - { - this._response.CloseOutputStream(_logger); - } - catch (Exception ex) - { - _logger.ErrorException("Error closing HttpListener output stream", ex); - } - } - } - - public void End() - { - Close(); - } - - public void Flush() - { - _response.OutputStream.Flush(); - } - - public bool IsClosed - { - get; - private set; - } - - public void SetContentLength(long contentLength) - { - //you can happily set the Content-Length header in Asp.Net - //but HttpListener will complain if you do - you have to set ContentLength64 on the response. - //workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header - _response.ContentLength64 = contentLength; - } - - public void SetCookie(Cookie cookie) - { - var cookieStr = cookie.AsHeaderValue(); - _response.Headers.Add(HttpHeaders.SetCookie, cookieStr); - } - - public bool SendChunked - { - get { return _response.SendChunked; } - set { _response.SendChunked = value; } - } - - public bool KeepAlive { get; set; } - - public void ClearCookies() - { - } - } -} diff --git a/MediaBrowser.Server.Implementations/IO/MemoryStreamProvider.cs b/MediaBrowser.Server.Implementations/IO/MemoryStreamProvider.cs index 0a0a04d5d..cb62ffa98 100644 --- a/MediaBrowser.Server.Implementations/IO/MemoryStreamProvider.cs +++ b/MediaBrowser.Server.Implementations/IO/MemoryStreamProvider.cs @@ -4,7 +4,7 @@ using Microsoft.IO; namespace MediaBrowser.Server.Implementations.IO { - public class RecyclableMemoryStreamProvider : IMemoryStreamProvider + public class RecyclableMemoryStreamProvider : IMemoryStreamFactory { readonly RecyclableMemoryStreamManager _manager = new RecyclableMemoryStreamManager(); @@ -22,9 +22,15 @@ namespace MediaBrowser.Server.Implementations.IO { return _manager.GetStream("RecyclableMemoryStream", buffer, 0, buffer.Length); } + + public bool TryGetBuffer(MemoryStream stream, out byte[] buffer) + { + buffer = stream.GetBuffer(); + return true; + } } - public class MemoryStreamProvider : IMemoryStreamProvider + public class MemoryStreamProvider : IMemoryStreamFactory { public MemoryStream CreateNew() { @@ -40,5 +46,11 @@ namespace MediaBrowser.Server.Implementations.IO { return new MemoryStream(buffer); } + + public bool TryGetBuffer(MemoryStream stream, out byte[] buffer) + { + buffer = stream.GetBuffer(); + return true; + } } } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 42e2e0d7b..4096d71dc 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -69,16 +69,12 @@ ..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll True - - ..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll - False ..\ThirdParty\SharpCompress\SharpCompress.dll - - ..\packages\SocketHttpListener.1.0.0.44\lib\net45\SocketHttpListener.dll - True + + ..\ThirdParty\emby\SocketHttpListener.Portable.dll @@ -88,7 +84,6 @@ - ..\ThirdParty\ServiceStack\ServiceStack.dll @@ -119,18 +114,9 @@ - - - - - - - - - diff --git a/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs index 86ecbd24d..179101ca2 100644 --- a/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs +++ b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs @@ -55,7 +55,7 @@ namespace MediaBrowser.Server.Implementations.Persistence /// /// Stream. /// reader - public static Stream GetMemoryStream(this IDataReader reader, int ordinal, IMemoryStreamProvider streamProvider) + public static Stream GetMemoryStream(this IDataReader reader, int ordinal, IMemoryStreamFactory streamProvider) { if (reader == null) { @@ -134,7 +134,7 @@ namespace MediaBrowser.Server.Implementations.Persistence /// /// System.Byte[][]. /// obj - public static byte[] SerializeToBytes(this IJsonSerializer json, object obj, IMemoryStreamProvider streamProvider) + public static byte[] SerializeToBytes(this IJsonSerializer json, object obj, IMemoryStreamFactory streamProvider) { if (obj == null) { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs index 3d20cad36..c97ba8792 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs @@ -20,9 +20,9 @@ namespace MediaBrowser.Server.Implementations.Persistence /// public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository { - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; - public SqliteDisplayPreferencesRepository(ILogManager logManager, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IDbConnector dbConnector, IMemoryStreamProvider memoryStreamProvider) + public SqliteDisplayPreferencesRepository(ILogManager logManager, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IDbConnector dbConnector, IMemoryStreamFactory memoryStreamProvider) : base(logManager, dbConnector) { _jsonSerializer = jsonSerializer; diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 5fcd38f87..3577d1883 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -99,12 +99,12 @@ namespace MediaBrowser.Server.Implementations.Persistence private IDbCommand _updateInheritedTagsCommand; public const int LatestSchemaVersion = 109; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; /// /// Initializes a new instance of the class. /// - public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogManager logManager, IDbConnector connector, IMemoryStreamProvider memoryStreamProvider) + public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogManager logManager, IDbConnector connector, IMemoryStreamFactory memoryStreamProvider) : base(logManager, connector) { if (config == null) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs index c3cf4acc4..0c1367e0a 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs @@ -20,9 +20,9 @@ namespace MediaBrowser.Server.Implementations.Persistence public class SqliteUserRepository : BaseSqliteRepository, IUserRepository { private readonly IJsonSerializer _jsonSerializer; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IMemoryStreamFactory _memoryStreamProvider; - public SqliteUserRepository(ILogManager logManager, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer, IDbConnector dbConnector, IMemoryStreamProvider memoryStreamProvider) : base(logManager, dbConnector) + public SqliteUserRepository(ILogManager logManager, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer, IDbConnector dbConnector, IMemoryStreamFactory memoryStreamProvider) : base(logManager, dbConnector) { _jsonSerializer = jsonSerializer; _memoryStreamProvider = memoryStreamProvider; diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 909246f68..84a5d5a1d 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -5,6 +5,5 @@ - \ No newline at end of file diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index fd84940dd..c0f184bef 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -126,7 +126,7 @@ using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.Social; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; using MediaBrowser.Model.Xml; using MediaBrowser.Server.Implementations.Archiving; using MediaBrowser.Server.Implementations.Serialization; @@ -252,6 +252,8 @@ namespace MediaBrowser.Server.Startup.Common /// The zip client. protected IZipClient ZipClient { get; private set; } + protected IAuthService AuthService { get; private set; } + private readonly StartupOptions _startupOptions; private readonly string _releaseAssetFilename; @@ -410,7 +412,7 @@ namespace MediaBrowser.Server.Startup.Common LogManager.RemoveConsoleOutput(); } - protected override IMemoryStreamProvider CreateMemoryStreamProvider() + protected override IMemoryStreamFactory CreateMemoryStreamProvider() { if (Environment.OSVersion.Platform == PlatformID.Win32NT) { @@ -555,7 +557,7 @@ namespace MediaBrowser.Server.Startup.Common StringExtensions.LocalizationManager = LocalizationManager; RegisterSingleInstance(LocalizationManager); - IEncoding textEncoding = new TextEncoding(FileSystemManager); + ITextEncoding textEncoding = new TextEncoding(FileSystemManager); RegisterSingleInstance(textEncoding); Utilities.EncodingHelper = textEncoding; RegisterSingleInstance(() => new BdInfoExaminer(FileSystemManager, textEncoding)); @@ -601,7 +603,7 @@ namespace MediaBrowser.Server.Startup.Common RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); - HttpServer = ServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamProvider, "Emby", "web/index.html"); + HttpServer = ServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamProvider, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); RegisterSingleInstance(HttpServer, false); progress.Report(10); @@ -702,7 +704,9 @@ namespace MediaBrowser.Server.Startup.Common var authContext = new AuthorizationContext(AuthenticationRepository, ConnectManager); RegisterSingleInstance(authContext); RegisterSingleInstance(new SessionContext(UserManager, authContext, SessionManager)); - RegisterSingleInstance(new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager, DeviceManager)); + + AuthService = new AuthService(UserManager, authContext, ServerConfigurationManager, ConnectManager, SessionManager, DeviceManager); + RegisterSingleInstance(AuthService); SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, MemoryStreamProvider, ProcessFactory, textEncoding); RegisterSingleInstance(SubtitleEncoder); @@ -900,6 +904,7 @@ namespace MediaBrowser.Server.Startup.Common BaseStreamingService.AppHost = this; BaseStreamingService.HttpClient = HttpClient; Utilities.CryptographyProvider = CryptographyProvider; + AuthenticatedAttribute.AuthService = AuthService; } /// @@ -1291,7 +1296,7 @@ namespace MediaBrowser.Server.Startup.Common try { // Return the first matched address, if found, or the first known local address - var address = (await GetLocalIpAddressesInternal().ConfigureAwait(false)).FirstOrDefault(i => !IPAddress.IsLoopback(i)); + var address = (await GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !i.Equals(IpAddressInfo.Loopback) && !i.Equals(IpAddressInfo.IPv6Loopback)); if (address != null) { @@ -1308,19 +1313,14 @@ namespace MediaBrowser.Server.Startup.Common return null; } - public string GetLocalApiUrl(IPAddress ipAddress) - { - return GetLocalApiUrl(ipAddress.ToString(), ipAddress.AddressFamily == AddressFamily.InterNetworkV6); - } - - public string GetLocalApiUrl(string ipAddress, bool isIpv6) + public string GetLocalApiUrl(IpAddressInfo ipAddress) { - if (isIpv6) + if (ipAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { - return GetLocalApiUrl("[" + ipAddress + "]"); + return GetLocalApiUrl("[" + ipAddress.Address + "]"); } - return GetLocalApiUrl(ipAddress); + return GetLocalApiUrl(ipAddress.Address); } public string GetLocalApiUrl(string host) @@ -1332,23 +1332,8 @@ namespace MediaBrowser.Server.Startup.Common public async Task> GetLocalIpAddresses() { - var list = await GetLocalIpAddressesInternal().ConfigureAwait(false); - - return list.Select(i => new IpAddressInfo - { - Address = i.ToString(), - IsIpv6 = i.AddressFamily == AddressFamily.InterNetworkV6 - - }).ToList(); - } - - private async Task> GetLocalIpAddressesInternal() - { - // Need to do this until Common will compile with this method - var nativeNetworkManager = (BaseNetworkManager)NetworkManager; - - var addresses = nativeNetworkManager.GetLocalIpAddresses().ToList(); - var list = new List(); + var addresses = NetworkManager.GetLocalIpAddresses().ToList(); + var list = new List(); foreach (var address in addresses) { @@ -1364,9 +1349,10 @@ namespace MediaBrowser.Server.Startup.Common private readonly ConcurrentDictionary _validAddressResults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private DateTime _lastAddressCacheClear; - private async Task IsIpAddressValidAsync(IPAddress address) + private async Task IsIpAddressValidAsync(IpAddressInfo address) { - if (IPAddress.IsLoopback(address)) + if (address.Equals(IpAddressInfo.Loopback) || + address.Equals(IpAddressInfo.IPv6Loopback)) { return true; } diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index e813c0a0a..7b949fcb1 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.684 + 3.0.688 Emby.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 9b0c10cbb..96986de9b 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.684 + 3.0.688 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 - + diff --git a/OpenSubtitlesHandler/Utilities.cs b/OpenSubtitlesHandler/Utilities.cs index b2ad2d0f1..3fe606c78 100644 --- a/OpenSubtitlesHandler/Utilities.cs +++ b/OpenSubtitlesHandler/Utilities.cs @@ -24,7 +24,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.TextEncoding; +using MediaBrowser.Model.Text; namespace OpenSubtitlesHandler { @@ -33,9 +33,9 @@ namespace OpenSubtitlesHandler /// public sealed class Utilities { - public static ICryptographyProvider CryptographyProvider { get; set; } + public static ICryptoProvider CryptographyProvider { get; set; } public static IHttpClient HttpClient { get; set; } - public static IEncoding EncodingHelper { get; set; } + public static ITextEncoding EncodingHelper { get; set; } private const string XML_RPC_SERVER = "https://api.opensubtitles.org/xml-rpc"; @@ -124,13 +124,13 @@ namespace OpenSubtitlesHandler data.Add((byte)r); } var bytes = data.ToArray(); - return EncodingHelper.GetASCIIString(bytes, 0, bytes.Length); + return EncodingHelper.GetASCIIEncoding().GetString(bytes, 0, bytes.Length); } } public static byte[] GetASCIIBytes(string text) { - return EncodingHelper.GetASCIIBytes(text); + return EncodingHelper.GetASCIIEncoding().GetBytes(text); } /// -- cgit v1.2.3 From 0e9cd51f9c64d4cfad5cb5c7b0ddae6af8d18ac6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 13 Nov 2016 16:04:21 -0500 Subject: update .net core startup --- Emby.Common.Implementations/BaseApplicationHost.cs | 4 +- .../BaseApplicationPaths.cs | 10 +- .../EnvironmentInfo/EnvironmentInfo.cs | 10 + .../IO/ManagedFileSystem.cs | 8 + Emby.Common.Implementations/Net/NetSocket.cs | 9 + Emby.Common.Implementations/Net/SocketAcceptor.cs | 24 ++- Emby.Common.Implementations/Net/SocketFactory.cs | 7 +- Emby.Server.Core/ApplicationHost.cs | 14 +- Emby.Server.Core/Data/DataExtensions.cs | 4 +- Emby.Server.Core/Data/SqliteItemRepository.cs | 19 +- .../Notifications/SqliteNotificationsRepository.cs | 2 +- Emby.Server.Core/ServerApplicationPaths.cs | 4 +- .../Channels/ChannelManager.cs | 16 +- .../HttpServer/HttpListenerHost.cs | 2 + .../LiveTv/EmbyTV/EmbyTV.cs | 90 +++++++-- .../LiveTv/EmbyTV/ItemDataProvider.cs | 3 +- .../LiveTv/LiveTvDtoService.cs | 20 +- .../LiveTv/LiveTvManager.cs | 67 +++++-- .../LiveTv/ProgramImageProvider.cs | 14 +- MediaBrowser.Api/ApiEntryPoint.cs | 6 +- .../Configuration/IApplicationPaths.cs | 6 - MediaBrowser.Controller/Entities/BaseItem.cs | 19 +- .../Encoder/EncoderValidator.cs | 16 +- MediaBrowser.Model/IO/IFileSystem.cs | 1 + MediaBrowser.Model/System/IEnvironmentInfo.cs | 2 + MediaBrowser.Server.Mono/MonoAppHost.cs | 2 +- MediaBrowser.Server.Mono/Program.cs | 10 +- .../Persistence/SqliteExtensions.cs | 6 +- MediaBrowser.ServerApplication/MainStartup.cs | 28 +-- .../Updates/ApplicationUpdater.cs | 2 +- MediaBrowser.ServerApplication/WindowsAppHost.cs | 9 +- MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs | 2 - Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- src/Emby.Server/ApplicationPathHelper.cs | 11 +- src/Emby.Server/CoreAppHost.cs | 2 +- src/Emby.Server/Program.cs | 213 ++++----------------- src/Emby.Server/project.json | 8 +- 38 files changed, 353 insertions(+), 323 deletions(-) (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/Emby.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs index f0309511e..1f194968c 100644 --- a/Emby.Common.Implementations/BaseApplicationHost.cs +++ b/Emby.Common.Implementations/BaseApplicationHost.cs @@ -326,7 +326,7 @@ namespace Emby.Common.Implementations builder.AppendLine(string.Format("Processor count: {0}", Environment.ProcessorCount)); builder.AppendLine(string.Format("Program data path: {0}", appPaths.ProgramDataPath)); - builder.AppendLine(string.Format("Application Path: {0}", appPaths.ApplicationPath)); + builder.AppendLine(string.Format("Application directory: {0}", appPaths.ProgramSystemPath)); return builder; } @@ -548,7 +548,7 @@ return null; TimerFactory = new TimerFactory(); RegisterSingleInstance(TimerFactory); - SocketFactory = new SocketFactory(null); + SocketFactory = new SocketFactory(LogManager.GetLogger("SocketFactory")); RegisterSingleInstance(SocketFactory); RegisterSingleInstance(CryptographyProvider); diff --git a/Emby.Common.Implementations/BaseApplicationPaths.cs b/Emby.Common.Implementations/BaseApplicationPaths.cs index 628d62bd4..8792778ba 100644 --- a/Emby.Common.Implementations/BaseApplicationPaths.cs +++ b/Emby.Common.Implementations/BaseApplicationPaths.cs @@ -12,22 +12,18 @@ namespace Emby.Common.Implementations /// /// Initializes a new instance of the class. /// - protected BaseApplicationPaths(string programDataPath, string applicationPath) + protected BaseApplicationPaths(string programDataPath, string appFolderPath) { ProgramDataPath = programDataPath; - ApplicationPath = applicationPath; + ProgramSystemPath = appFolderPath; } - public string ApplicationPath { get; private set; } public string ProgramDataPath { get; private set; } /// /// Gets the path to the system folder /// - public string ProgramSystemPath - { - get { return Path.GetDirectoryName(ApplicationPath); } - } + public string ProgramSystemPath { get; private set; } /// /// The _data directory diff --git a/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs index 6a1b3ef74..c040e3931 100644 --- a/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs +++ b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs @@ -95,5 +95,15 @@ namespace Emby.Common.Implementations.EnvironmentInfo return MediaBrowser.Model.System.Architecture.X64; } } + + public string GetEnvironmentVariable(string name) + { + return Environment.GetEnvironmentVariable(name); + } + + public virtual string GetUserId() + { + return null; + } } } diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs index 1f0aa55fa..83bb50f94 100644 --- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs @@ -57,6 +57,14 @@ namespace Emby.Common.Implementations.IO } } + public char PathSeparator + { + get + { + return Path.DirectorySeparatorChar; + } + } + public string GetFullPath(string path) { return Path.GetFullPath(path); diff --git a/Emby.Common.Implementations/Net/NetSocket.cs b/Emby.Common.Implementations/Net/NetSocket.cs index faa1a81e2..62ca3d6ac 100644 --- a/Emby.Common.Implementations/Net/NetSocket.cs +++ b/Emby.Common.Implementations/Net/NetSocket.cs @@ -15,6 +15,15 @@ namespace Emby.Common.Implementations.Net public NetSocket(Socket socket, ILogger logger) { + if (socket == null) + { + throw new ArgumentNullException("socket"); + } + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + Socket = socket; _logger = logger; } diff --git a/Emby.Common.Implementations/Net/SocketAcceptor.cs b/Emby.Common.Implementations/Net/SocketAcceptor.cs index fd65e9fbc..bddb7a079 100644 --- a/Emby.Common.Implementations/Net/SocketAcceptor.cs +++ b/Emby.Common.Implementations/Net/SocketAcceptor.cs @@ -14,6 +14,23 @@ namespace Emby.Common.Implementations.Net public SocketAcceptor(ILogger logger, Socket originalSocket, Action onAccept, Func isClosed) { + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + if (originalSocket == null) + { + throw new ArgumentNullException("originalSocket"); + } + if (onAccept == null) + { + throw new ArgumentNullException("onAccept"); + } + if (isClosed == null) + { + throw new ArgumentNullException("isClosed"); + } + _logger = logger; _originalSocket = originalSocket; _isClosed = isClosed; @@ -101,11 +118,8 @@ namespace Emby.Common.Implementations.Net _onAccept(new NetSocket(acceptSocket, _logger)); } - if (_originalSocket != null) - { - // Accept the next connection request - StartAccept(e, ref acceptSocket); - } + // Accept the next connection request + StartAccept(e, ref acceptSocket); } } } diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 922b0f3cc..f26137683 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -23,10 +23,15 @@ namespace Emby.Common.Implementations.Net /// private IPAddress _LocalIP; - private ILogger _logger; + private readonly ILogger _logger; public SocketFactory(ILogger logger) { + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + _logger = logger; _LocalIP = IPAddress.Any; } diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 7f795a68d..d3d292ca5 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -725,6 +725,11 @@ namespace Emby.Server.Core try { + if (!FileSystemManager.FileExists(certificateLocation)) + { + return null; + } + X509Certificate2 localCert = new X509Certificate2(certificateLocation); //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; if (!localCert.HasPrivateKey) @@ -1438,12 +1443,7 @@ namespace Emby.Server.Core try { - AuthorizeServer( - UdpServerEntryPoint.PortNumber, - ServerConfigurationManager.Configuration.HttpServerPortNumber, - ServerConfigurationManager.Configuration.HttpsPortNumber, - ConfigurationManager.CommonApplicationPaths.ApplicationPath, - ConfigurationManager.CommonApplicationPaths.TempDirectory); + AuthorizeServer(); } catch (Exception ex) { @@ -1451,7 +1451,7 @@ namespace Emby.Server.Core } } - protected abstract void AuthorizeServer(int udpPort, int httpServerPort, int httpsServerPort, string applicationPath, string tempDirectory); + protected abstract void AuthorizeServer(); protected abstract IDbConnector GetDbConnector(); public event EventHandler HasUpdateAvailableChanged; diff --git a/Emby.Server.Core/Data/DataExtensions.cs b/Emby.Server.Core/Data/DataExtensions.cs index b633d9217..631c1c500 100644 --- a/Emby.Server.Core/Data/DataExtensions.cs +++ b/Emby.Server.Core/Data/DataExtensions.cs @@ -40,7 +40,7 @@ namespace Emby.Server.Core.Data public static IDataParameter Add(this IDataParameterCollection paramCollection, IDbCommand cmd, string name) { var param = cmd.CreateParameter(); - + param.ParameterName = name; paramCollection.Add(param); @@ -173,7 +173,7 @@ namespace Emby.Server.Core.Data var builder = new StringBuilder(); builder.AppendLine("alter table " + table); - builder.AppendLine("add column " + columnName + " " + type); + builder.AppendLine("add column " + columnName + " " + type + " NULL"); connection.RunQueries(new[] { builder.ToString() }, logger); } diff --git a/Emby.Server.Core/Data/SqliteItemRepository.cs b/Emby.Server.Core/Data/SqliteItemRepository.cs index 2ca86c831..6ed409aa1 100644 --- a/Emby.Server.Core/Data/SqliteItemRepository.cs +++ b/Emby.Server.Core/Data/SqliteItemRepository.cs @@ -157,7 +157,7 @@ namespace Emby.Server.Core.Data string[] queries = { - "create table if not exists TypedBaseItems (guid GUID primary key, type TEXT, data BLOB, ParentId GUID, Path TEXT)", + "create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)", "create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))", "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)", @@ -286,6 +286,7 @@ namespace Emby.Server.Core.Data _connection.AddColumn(Logger, "TypedBaseItems", "ExtraType", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "Artists", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "AlbumArtists", "Text"); + _connection.AddColumn(Logger, "TypedBaseItems", "ExternalId", "Text"); _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text"); @@ -440,7 +441,8 @@ namespace Emby.Server.Core.Data "TotalBitrate", "ExtraType", "Artists", - "AlbumArtists" + "AlbumArtists", + "ExternalId" }; private readonly string[] _mediaStreamSaveColumns = @@ -575,7 +577,8 @@ namespace Emby.Server.Core.Data "TotalBitrate", "ExtraType", "Artists", - "AlbumArtists" + "AlbumArtists", + "ExternalId" }; _saveItemCommand = _connection.CreateCommand(); _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -1084,6 +1087,10 @@ namespace Emby.Server.Core.Data } } + _saveItemCommand.GetParameter(index++).Value = item.ExternalId; + + //Logger.Debug(_saveItemCommand.CommandText); + _saveItemCommand.Transaction = transaction; _saveItemCommand.ExecuteNonQuery(); @@ -1967,6 +1974,12 @@ namespace Emby.Server.Core.Data } index++; + if (!reader.IsDBNull(index)) + { + item.ExternalId = reader.GetString(index); + } + index++; + if (string.IsNullOrWhiteSpace(item.Tagline)) { var movie = item as Movie; diff --git a/Emby.Server.Core/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Core/Notifications/SqliteNotificationsRepository.cs index 8a7fc9270..dee0d4cfd 100644 --- a/Emby.Server.Core/Notifications/SqliteNotificationsRepository.cs +++ b/Emby.Server.Core/Notifications/SqliteNotificationsRepository.cs @@ -30,7 +30,7 @@ namespace Emby.Server.Core.Notifications { string[] queries = { - "create table if not exists Notifications (Id GUID NOT NULL, UserId GUID NOT NULL, Date DATETIME NOT NULL, Name TEXT NOT NULL, Description TEXT, Url TEXT, Level TEXT NOT NULL, IsRead BOOLEAN NOT NULL, Category TEXT NOT NULL, RelatedId TEXT, PRIMARY KEY (Id, UserId))", + "create table if not exists Notifications (Id GUID NOT NULL, UserId GUID NOT NULL, Date DATETIME NOT NULL, Name TEXT NOT NULL, Description TEXT NULL, Url TEXT NULL, Level TEXT NOT NULL, IsRead BOOLEAN NOT NULL, Category TEXT NOT NULL, RelatedId TEXT NULL, PRIMARY KEY (Id, UserId))", "create index if not exists idx_Notifications1 on Notifications(Id)", "create index if not exists idx_Notifications2 on Notifications(UserId)" }; diff --git a/Emby.Server.Core/ServerApplicationPaths.cs b/Emby.Server.Core/ServerApplicationPaths.cs index d59dd89d9..dc80b773c 100644 --- a/Emby.Server.Core/ServerApplicationPaths.cs +++ b/Emby.Server.Core/ServerApplicationPaths.cs @@ -12,8 +12,8 @@ namespace Emby.Server.Core /// /// Initializes a new instance of the class. /// - public ServerApplicationPaths(string programDataPath, string applicationPath, string applicationResourcesPath) - : base(programDataPath, applicationPath) + public ServerApplicationPaths(string programDataPath, string appFolderPath, string applicationResourcesPath) + : base(programDataPath, appFolderPath) { ApplicationResourcesPath = applicationResourcesPath; } diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 2ce880c93..94ff7c342 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -326,7 +326,7 @@ namespace Emby.Server.Implementations.Channels if (requiresCallback != null) { - results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken) + results = await GetChannelItemMediaSourcesInternal(requiresCallback, GetItemExternalId(item), cancellationToken) .ConfigureAwait(false); } else @@ -1075,6 +1075,18 @@ namespace Emby.Server.Implementations.Channels return result; } + private string GetItemExternalId(BaseItem item) + { + var externalId = item.ExternalId; + + if (string.IsNullOrWhiteSpace(externalId)) + { + externalId = item.GetProviderId("ProviderExternalId"); + } + + return externalId; + } + private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private async Task GetChannelItems(IChannel channel, User user, @@ -1145,7 +1157,7 @@ namespace Emby.Server.Implementations.Channels { var categoryItem = _libraryManager.GetItemById(new Guid(folderId)); - query.FolderId = categoryItem.ExternalId; + query.FolderId = GetItemExternalId(categoryItem); } var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 41b7a4622..876d140ec 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -396,6 +396,8 @@ namespace Emby.Server.Implementations.HttpServer if (_disposed) { httpRes.StatusCode = 503; + httpRes.ContentType = "text/plain"; + Write(httpRes, "Server shutting down"); return; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 63356a845..aaf74b5c6 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1551,13 +1551,28 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { try { + if (timer.IsSports) + { + AddGenre(timer.Genres, "Sports"); + } + if (timer.IsKids) + { + AddGenre(timer.Genres, "Kids"); + AddGenre(timer.Genres, "Children"); + } + if (timer.IsNews) + { + AddGenre(timer.Genres, "News"); + } + if (timer.IsProgramSeries) { SaveSeriesNfo(timer, recordingPath, seriesPath); + SaveVideoNfo(timer, recordingPath, false); } else if (!timer.IsMovie || timer.IsSports || timer.IsNews) { - SaveVideoNfo(timer, recordingPath); + SaveVideoNfo(timer, recordingPath, true); } } catch (Exception ex) @@ -1594,6 +1609,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV writer.WriteElementString("title", timer.Name); } + if (!string.IsNullOrEmpty(timer.OfficialRating)) + { + writer.WriteElementString("mpaa", timer.OfficialRating); + } + + foreach (var genre in timer.Genres) + { + writer.WriteElementString("genre", genre); + } + writer.WriteEndElement(); writer.WriteEndDocument(); } @@ -1601,7 +1626,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; - private void SaveVideoNfo(TimerInfo timer, string recordingPath) + private void SaveVideoNfo(TimerInfo timer, string recordingPath, bool lockData) { var nfoPath = Path.ChangeExtension(recordingPath, ".nfo"); @@ -1622,11 +1647,41 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV using (XmlWriter writer = XmlWriter.Create(stream, settings)) { writer.WriteStartDocument(true); - writer.WriteStartElement("movie"); - if (!string.IsNullOrWhiteSpace(timer.Name)) + if (timer.IsProgramSeries) { - writer.WriteElementString("title", timer.Name); + writer.WriteStartElement("episodedetails"); + + if (!string.IsNullOrWhiteSpace(timer.EpisodeTitle)) + { + writer.WriteElementString("title", timer.EpisodeTitle); + } + + if (timer.OriginalAirDate.HasValue) + { + var formatString = _config.GetNfoConfiguration().ReleaseDateFormat; + + writer.WriteElementString("aired", timer.OriginalAirDate.Value.ToLocalTime().ToString(formatString)); + } + + if (timer.EpisodeNumber.HasValue) + { + writer.WriteElementString("episode", timer.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (timer.SeasonNumber.HasValue) + { + writer.WriteElementString("season", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); + } + } + else + { + writer.WriteStartElement("movie"); + + if (!string.IsNullOrWhiteSpace(timer.Name)) + { + writer.WriteElementString("title", timer.Name); + } } writer.WriteElementString("dateadded", DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat)); @@ -1645,25 +1700,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV .Replace(""", "'"); writer.WriteElementString("plot", overview); - writer.WriteElementString("lockdata", true.ToString().ToLower()); - if (timer.CommunityRating.HasValue) + if (lockData) { - writer.WriteElementString("rating", timer.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); + writer.WriteElementString("lockdata", true.ToString().ToLower()); } - if (timer.IsSports) - { - AddGenre(timer.Genres, "Sports"); - } - if (timer.IsKids) - { - AddGenre(timer.Genres, "Kids"); - AddGenre(timer.Genres, "Children"); - } - if (timer.IsNews) + if (timer.CommunityRating.HasValue) { - AddGenre(timer.Genres, "News"); + writer.WriteElementString("rating", timer.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); } foreach (var genre in timer.Genres) @@ -1968,4 +2013,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public CancellationTokenSource CancellationTokenSource { get; set; } } } + public static class ConfigurationExtension + { + public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager manager) + { + return manager.GetConfiguration("xbmcmetadata"); + } + } } \ No newline at end of file diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index ded4f04c4..16ae26d45 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -54,9 +54,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV catch (FileNotFoundException) { } - catch (IOException ex) + catch (IOException) { - Logger.ErrorException("Error deserializing {0}", ex, jsonFile); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 4e7161521..d3e30a46b 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -269,6 +269,18 @@ namespace Emby.Server.Implementations.LiveTv return _libraryManager.GetNewItemId(name.ToLower(), typeof(ILiveTvRecording)); } + private string GetItemExternalId(BaseItem item) + { + var externalId = item.ExternalId; + + if (string.IsNullOrWhiteSpace(externalId)) + { + externalId = item.GetProviderId("ProviderExternalId"); + } + + return externalId; + } + public async Task GetTimerInfo(TimerInfoDto dto, bool isNew, LiveTvManager liveTv, CancellationToken cancellationToken) { var info = new TimerInfo @@ -304,7 +316,7 @@ namespace Emby.Server.Implementations.LiveTv if (channel != null) { - info.ChannelId = channel.ExternalId; + info.ChannelId = GetItemExternalId(channel); } } @@ -314,7 +326,7 @@ namespace Emby.Server.Implementations.LiveTv if (program != null) { - info.ProgramId = program.ExternalId; + info.ProgramId = GetItemExternalId(program); } } @@ -370,7 +382,7 @@ namespace Emby.Server.Implementations.LiveTv if (channel != null) { - info.ChannelId = channel.ExternalId; + info.ChannelId = GetItemExternalId(channel); } } @@ -380,7 +392,7 @@ namespace Emby.Server.Implementations.LiveTv if (program != null) { - info.ProgramId = program.ExternalId; + info.ProgramId = GetItemExternalId(program); } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index adec66858..3a6f23fe9 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -251,12 +251,24 @@ namespace Emby.Server.Implementations.LiveTv return await GetLiveStream(id, mediaSourceId, true, cancellationToken).ConfigureAwait(false); } + private string GetItemExternalId(BaseItem item) + { + var externalId = item.ExternalId; + + if (string.IsNullOrWhiteSpace(externalId)) + { + externalId = item.GetProviderId("ProviderExternalId"); + } + + return externalId; + } + public async Task> GetRecordingMediaSources(IHasMediaSources item, CancellationToken cancellationToken) { var baseItem = (BaseItem)item; var service = GetService(baseItem); - return await service.GetRecordingStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAwait(false); + return await service.GetRecordingStreamMediaSources(GetItemExternalId(baseItem), cancellationToken).ConfigureAwait(false); } public async Task> GetChannelMediaSources(IHasMediaSources item, CancellationToken cancellationToken) @@ -313,18 +325,18 @@ namespace Emby.Server.Implementations.LiveTv var channel = GetInternalChannel(id); isVideo = channel.ChannelType == ChannelType.TV; service = GetService(channel); - _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); + _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, GetItemExternalId(channel)); var supportsManagedStream = service as ISupportsDirectStreamProvider; if (supportsManagedStream != null) { - var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false); + var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(GetItemExternalId(channel), mediaSourceId, cancellationToken).ConfigureAwait(false); info = streamInfo.Item1; directStreamProvider = streamInfo.Item2; } else { - info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false); + info = await service.GetChannelStream(GetItemExternalId(channel), mediaSourceId, cancellationToken).ConfigureAwait(false); } info.RequiresClosing = true; @@ -341,8 +353,8 @@ namespace Emby.Server.Implementations.LiveTv isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase); service = GetService(recording); - _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId); - info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false); + _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, GetItemExternalId(recording)); + info = await service.GetRecordingStream(GetItemExternalId(recording), null, cancellationToken).ConfigureAwait(false); info.RequiresClosing = true; if (info.RequiresClosing) @@ -493,7 +505,7 @@ namespace Emby.Server.Implementations.LiveTv isNew = true; } - if (!string.Equals(channelInfo.Id, item.ExternalId)) + if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal)) { isNew = true; } @@ -601,7 +613,6 @@ namespace Emby.Server.Implementations.LiveTv item.EpisodeTitle = info.EpisodeTitle; item.ExternalId = info.Id; - item.ExternalSeriesIdLegacy = seriesId; if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal)) { @@ -841,6 +852,13 @@ namespace Emby.Server.Implementations.LiveTv return item.Id; } + + + private string GetExternalSeriesIdLegacy(BaseItem item) + { + return item.GetProviderId("ProviderExternalSeriesId"); + } + public async Task GetProgram(string id, CancellationToken cancellationToken, User user = null) { var program = GetInternalProgram(id); @@ -848,7 +866,15 @@ namespace Emby.Server.Implementations.LiveTv var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user); var list = new List>(); - list.Add(new Tuple(dto, program.ServiceName, program.ExternalId, program.ExternalSeriesIdLegacy)); + + var externalSeriesId = program.ExternalSeriesId; + + if (string.IsNullOrWhiteSpace(externalSeriesId)) + { + externalSeriesId = GetExternalSeriesIdLegacy(program); + } + + list.Add(new Tuple(dto, program.ServiceName, GetItemExternalId(program), externalSeriesId)); await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false); @@ -1283,7 +1309,7 @@ namespace Emby.Server.Implementations.LiveTv var isKids = false; var iSSeries = false; - var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false); + var channelPrograms = await service.GetProgramsAsync(GetItemExternalId(currentChannel), start, end, cancellationToken).ConfigureAwait(false); var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery { @@ -1830,7 +1856,14 @@ namespace Emby.Server.Implementations.LiveTv dto.ServiceName = serviceName; } - programTuples.Add(new Tuple(dto, serviceName, program.ExternalId, program.ExternalSeriesIdLegacy)); + var externalSeriesId = program.ExternalSeriesId; + + if (string.IsNullOrWhiteSpace(externalSeriesId)) + { + externalSeriesId = GetExternalSeriesIdLegacy(program); + } + + programTuples.Add(new Tuple(dto, serviceName, GetItemExternalId(program), externalSeriesId)); } await AddRecordingInfo(programTuples, CancellationToken.None).ConfigureAwait(false); @@ -2006,7 +2039,7 @@ namespace Emby.Server.Implementations.LiveTv if (service is EmbyTV.EmbyTV) { // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says - return service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None); + return service.DeleteRecordingAsync(GetItemExternalId(recording), CancellationToken.None); } return Task.FromResult(true); @@ -2030,7 +2063,7 @@ namespace Emby.Server.Implementations.LiveTv try { - await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false); + await service.DeleteRecordingAsync(GetItemExternalId(recording), CancellationToken.None).ConfigureAwait(false); } catch (ResourceNotFoundException) { @@ -2289,12 +2322,12 @@ namespace Emby.Server.Implementations.LiveTv programInfo = new ProgramInfo { Audio = program.Audio, - ChannelId = channel.ExternalId, + ChannelId = GetItemExternalId(channel), CommunityRating = program.CommunityRating, EndDate = program.EndDate ?? DateTime.MinValue, EpisodeTitle = program.EpisodeTitle, Genres = program.Genres, - Id = program.ExternalId, + Id = GetItemExternalId(program), IsHD = program.IsHD, IsKids = program.IsKids, IsLive = program.IsLive, @@ -2360,7 +2393,7 @@ namespace Emby.Server.Implementations.LiveTv info.Name = program.Name; info.Overview = program.Overview; info.ProgramId = programDto.Id; - info.ExternalProgramId = program.ExternalId; + info.ExternalProgramId = GetItemExternalId(program); if (program.EndDate.HasValue) { @@ -2804,7 +2837,7 @@ namespace Emby.Server.Implementations.LiveTv public async Task SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings) { - info = _jsonSerializer.DeserializeFromString< ListingsProviderInfo>(_jsonSerializer.SerializeToString(info)); + info = _jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info)); var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); diff --git a/Emby.Server.Implementations/LiveTv/ProgramImageProvider.cs b/Emby.Server.Implementations/LiveTv/ProgramImageProvider.cs index f5d298af4..5a0389b16 100644 --- a/Emby.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/Emby.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -24,6 +24,18 @@ namespace Emby.Server.Implementations.LiveTv return new[] { ImageType.Primary }; } + private string GetItemExternalId(BaseItem item) + { + var externalId = item.ExternalId; + + if (string.IsNullOrWhiteSpace(externalId)) + { + externalId = item.GetProviderId("ProviderExternalId"); + } + + return externalId; + } + public async Task GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken) { var liveTvItem = (LiveTvProgram)item; @@ -38,7 +50,7 @@ namespace Emby.Server.Implementations.LiveTv { var channel = _liveTvManager.GetInternalChannel(liveTvItem.ChannelId); - var response = await service.GetProgramImageAsync(liveTvItem.ExternalId, channel.ExternalId, cancellationToken).ConfigureAwait(false); + var response = await service.GetProgramImageAsync(GetItemExternalId(liveTvItem), GetItemExternalId(channel), cancellationToken).ConfigureAwait(false); if (response != null) { diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index bc0241766..37ab12366 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -128,7 +128,11 @@ namespace MediaBrowser.Api { // Don't clutter the log } - catch (IOException ex) + catch (IOException) + { + // Don't clutter the log + } + catch (Exception ex) { Logger.ErrorException("Error deleting encoded media cache", ex); } diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index d3bf03302..d2446ce46 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -6,12 +6,6 @@ namespace MediaBrowser.Common.Configuration /// public interface IApplicationPaths { - /// - /// Gets the application path. - /// - /// The application path. - string ApplicationPath { get; } - /// /// Gets the path to the program data folder /// diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index b079da97c..10cac7992 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -296,28 +296,11 @@ namespace MediaBrowser.Controller.Entities /// If this content came from an external service, the id of the content on that service /// [IgnoreDataMember] - public string ExternalId - { - get { return this.GetProviderId("ProviderExternalId"); } - set - { - this.SetProviderId("ProviderExternalId", value); - } - } + public string ExternalId { get; set; } [IgnoreDataMember] public string ExternalSeriesId { get; set; } - [IgnoreDataMember] - public string ExternalSeriesIdLegacy - { - get { return this.GetProviderId("ProviderExternalSeriesId"); } - set - { - this.SetProviderId("ProviderExternalSeriesId", value); - } - } - /// /// Gets or sets the etag. /// diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 6acdccf3d..1b8b3feec 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -37,20 +37,22 @@ namespace MediaBrowser.MediaEncoding.Encoder { output = GetProcessOutput(encoderAppPath, "-version"); } - catch + catch (Exception ex) { + if (logOutput) + { + _logger.ErrorException("Error validating encoder", ex); + } } - output = output ?? string.Empty; - - if (logOutput) + if (string.IsNullOrWhiteSpace(output)) { - _logger.Info("ffmpeg info: {0}", output); + return false; } - if (string.IsNullOrWhiteSpace(output)) + if (logOutput) { - return false; + _logger.Info("ffmpeg info: {0}", output); } if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index f219d9295..22e1e7758 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -308,6 +308,7 @@ namespace MediaBrowser.Model.IO void SetReadOnly(string path, bool isHidden); char DirectorySeparatorChar { get; } + char PathSeparator { get; } string GetFullPath(string path); diff --git a/MediaBrowser.Model/System/IEnvironmentInfo.cs b/MediaBrowser.Model/System/IEnvironmentInfo.cs index c5f493e7c..7e7d81e30 100644 --- a/MediaBrowser.Model/System/IEnvironmentInfo.cs +++ b/MediaBrowser.Model/System/IEnvironmentInfo.cs @@ -12,6 +12,8 @@ namespace MediaBrowser.Model.System string OperatingSystemName { get; } string OperatingSystemVersion { get; } Architecture SystemArchitecture { get; } + string GetEnvironmentVariable(string name); + string GetUserId(); } public enum OperatingSystem diff --git a/MediaBrowser.Server.Mono/MonoAppHost.cs b/MediaBrowser.Server.Mono/MonoAppHost.cs index 5f0ecde24..fd3c9f506 100644 --- a/MediaBrowser.Server.Mono/MonoAppHost.cs +++ b/MediaBrowser.Server.Mono/MonoAppHost.cs @@ -91,7 +91,7 @@ namespace MediaBrowser.Server.Mono MainClass.Shutdown(); } - protected override void AuthorizeServer(int udpPort, int httpServerPort, int httpsServerPort, string applicationPath, string tempDirectory) + protected override void AuthorizeServer() { throw new NotImplementedException(); } diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index 48390f078..470525ece 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -5,6 +5,7 @@ using MediaBrowser.Server.Startup.Common; using Microsoft.Win32; using System; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -74,7 +75,9 @@ namespace MediaBrowser.Server.Mono programDataPath = ApplicationPathHelper.GetProgramDataPath(applicationPath); } - return new ServerApplicationPaths(programDataPath, applicationPath, Path.GetDirectoryName(applicationPath)); + var appFolderPath = Path.GetDirectoryName(applicationPath); + + return new ServerApplicationPaths(programDataPath, appFolderPath, Path.GetDirectoryName(applicationPath)); } private static readonly TaskCompletionSource ApplicationTaskCompletionSource = new TaskCompletionSource(); @@ -305,5 +308,10 @@ namespace MediaBrowser.Server.Mono public class MonoEnvironmentInfo : EnvironmentInfo { public bool IsBsd { get; set; } + + public virtual string GetUserId() + { + return Syscall.getuid().ToString(CultureInfo.InvariantCulture); + } } } diff --git a/MediaBrowser.Server.Startup.Common/Persistence/SqliteExtensions.cs b/MediaBrowser.Server.Startup.Common/Persistence/SqliteExtensions.cs index 72ecfc1bf..22aeb53dd 100644 --- a/MediaBrowser.Server.Startup.Common/Persistence/SqliteExtensions.cs +++ b/MediaBrowser.Server.Startup.Common/Persistence/SqliteExtensions.cs @@ -14,7 +14,11 @@ namespace Emby.Server.Core.Data /// /// Connects to db. /// - public static async Task ConnectToDb(string dbPath, bool isReadOnly, bool enablePooling, int? cacheSize, ILogger logger) + public static async Task ConnectToDb(string dbPath, + bool isReadOnly, + bool enablePooling, + int? cacheSize, + ILogger logger) { if (string.IsNullOrEmpty(dbPath)) { diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index ab0a36aff..c0ebcde74 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -44,6 +44,8 @@ namespace MediaBrowser.ServerApplication [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetDllDirectory(string lpPathName); + public static string ApplicationPath; + public static bool TryGetLocalFromUncDirectory(string local, out string unc) { if ((local == null) || (local == "")) @@ -81,14 +83,14 @@ namespace MediaBrowser.ServerApplication var currentProcess = Process.GetCurrentProcess(); - var applicationPath = currentProcess.MainModule.FileName; - var architecturePath = Path.Combine(Path.GetDirectoryName(applicationPath), Environment.Is64BitProcess ? "x64" : "x86"); + ApplicationPath = currentProcess.MainModule.FileName; + var architecturePath = Path.Combine(Path.GetDirectoryName(ApplicationPath), Environment.Is64BitProcess ? "x64" : "x86"); Wand.SetMagickCoderModulePath(architecturePath); var success = SetDllDirectory(architecturePath); - var appPaths = CreateApplicationPaths(applicationPath, IsRunningAsService); + var appPaths = CreateApplicationPaths(ApplicationPath, IsRunningAsService); var logManager = new NlogManager(appPaths.LogDirectoryPath, "server"); logManager.ReloadLogger(LogSeverity.Debug); @@ -102,7 +104,7 @@ namespace MediaBrowser.ServerApplication if (options.ContainsOption("-installservice")) { logger.Info("Performing service installation"); - InstallService(applicationPath, logger); + InstallService(ApplicationPath, logger); return; } @@ -110,7 +112,7 @@ namespace MediaBrowser.ServerApplication if (options.ContainsOption("-installserviceasadmin")) { logger.Info("Performing service installation"); - RunServiceInstallation(applicationPath); + RunServiceInstallation(ApplicationPath); return; } @@ -118,7 +120,7 @@ namespace MediaBrowser.ServerApplication if (options.ContainsOption("-uninstallservice")) { logger.Info("Performing service uninstallation"); - UninstallService(applicationPath, logger); + UninstallService(ApplicationPath, logger); return; } @@ -126,15 +128,15 @@ namespace MediaBrowser.ServerApplication if (options.ContainsOption("-uninstallserviceasadmin")) { logger.Info("Performing service uninstallation"); - RunServiceUninstallation(applicationPath); + RunServiceUninstallation(ApplicationPath); return; } AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - RunServiceInstallationIfNeeded(applicationPath); + RunServiceInstallationIfNeeded(ApplicationPath); - if (IsAlreadyRunning(applicationPath, currentProcess)) + if (IsAlreadyRunning(ApplicationPath, currentProcess)) { logger.Info("Shutting down because another instance of Emby Server is already running."); return; @@ -250,6 +252,8 @@ namespace MediaBrowser.ServerApplication /// ServerApplicationPaths. private static ServerApplicationPaths CreateApplicationPaths(string applicationPath, bool runAsService) { + var appFolderPath = Path.GetDirectoryName(applicationPath); + var resourcesPath = Path.GetDirectoryName(applicationPath); if (runAsService) @@ -258,10 +262,10 @@ namespace MediaBrowser.ServerApplication var programDataPath = Path.GetDirectoryName(systemPath); - return new ServerApplicationPaths(programDataPath, applicationPath, resourcesPath); + return new ServerApplicationPaths(programDataPath, appFolderPath, resourcesPath); } - return new ServerApplicationPaths(ApplicationPathHelper.GetProgramDataPath(applicationPath), applicationPath, resourcesPath); + return new ServerApplicationPaths(ApplicationPathHelper.GetProgramDataPath(applicationPath), appFolderPath, resourcesPath); } /// @@ -663,7 +667,7 @@ namespace MediaBrowser.ServerApplication _logger.Info("Starting new instance"); //Application.Restart(); - Process.Start(_appHost.ServerConfigurationManager.ApplicationPaths.ApplicationPath); + Process.Start(ApplicationPath); ShutdownWindowsApplication(); } diff --git a/MediaBrowser.ServerApplication/Updates/ApplicationUpdater.cs b/MediaBrowser.ServerApplication/Updates/ApplicationUpdater.cs index c426395b7..97537b27d 100644 --- a/MediaBrowser.ServerApplication/Updates/ApplicationUpdater.cs +++ b/MediaBrowser.ServerApplication/Updates/ApplicationUpdater.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.ServerApplication.Updates // startpath = executable to launch // systempath = folder containing installation var args = string.Format("product={0} archive=\"{1}\" caller={2} pismo=false version={3} service={4} installpath=\"{5}\" startpath=\"{6}\" systempath=\"{7}\"", - product, archive, Process.GetCurrentProcess().Id, version, restartServiceName ?? string.Empty, appPaths.ProgramDataPath, appPaths.ApplicationPath, systemPath); + product, archive, Process.GetCurrentProcess().Id, version, restartServiceName ?? string.Empty, appPaths.ProgramDataPath, MainStartup.ApplicationPath, systemPath); logger.Info("Args: {0}", args); Process.Start(tempUpdater, args); diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index 8fd718432..d9bead6b7 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -6,6 +6,7 @@ using System.Reflection; using Emby.Server.Core; using Emby.Server.Core.Data; using Emby.Server.Core.FFMpeg; +using Emby.Server.Implementations.EntryPoints; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.System; @@ -60,9 +61,13 @@ namespace MediaBrowser.ServerApplication MainStartup.Shutdown(); } - protected override void AuthorizeServer(int udpPort, int httpServerPort, int httpsServerPort, string applicationPath, string tempDirectory) + protected override void AuthorizeServer() { - ServerAuthorization.AuthorizeServer(udpPort, httpServerPort, httpsServerPort, applicationPath, tempDirectory); + ServerAuthorization.AuthorizeServer(UdpServerEntryPoint.PortNumber, + ServerConfigurationManager.Configuration.HttpServerPortNumber, + ServerConfigurationManager.Configuration.HttpsPortNumber, + MainStartup.ApplicationPath, + ConfigurationManager.CommonApplicationPaths.TempDirectory); } protected override IDbConnector GetDbConnector() diff --git a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs index 4c22f0246..b512939a7 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs @@ -90,8 +90,6 @@ namespace MediaBrowser.XbmcMetadata.Savers } } - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - protected override List GetTagsUsed() { var list = new List diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 464a29eb2..daeb754ba 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.689 + 3.0.691 Emby.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 96e9a697a..a34a9bee2 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.689 + 3.0.691 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 - + diff --git a/src/Emby.Server/ApplicationPathHelper.cs b/src/Emby.Server/ApplicationPathHelper.cs index 4da87b6a0..c611ff372 100644 --- a/src/Emby.Server/ApplicationPathHelper.cs +++ b/src/Emby.Server/ApplicationPathHelper.cs @@ -8,7 +8,7 @@ namespace Emby.Server { public class ApplicationPathHelper { - public static string GetProgramDataPath(string applicationPath) + public static string GetProgramDataPath(string appDirectory) { var useDebugPath = false; @@ -27,14 +27,7 @@ namespace Emby.Server // If it's a relative path, e.g. "..\" if (!Path.IsPathRooted(programDataPath)) { - var path = Path.GetDirectoryName(applicationPath); - - if (string.IsNullOrEmpty(path)) - { - throw new Exception("Unable to determine running assembly location"); - } - - programDataPath = Path.Combine(path, programDataPath); + programDataPath = Path.Combine(appDirectory, programDataPath); programDataPath = Path.GetFullPath(programDataPath); } diff --git a/src/Emby.Server/CoreAppHost.cs b/src/Emby.Server/CoreAppHost.cs index 1a1526513..21f6ae445 100644 --- a/src/Emby.Server/CoreAppHost.cs +++ b/src/Emby.Server/CoreAppHost.cs @@ -51,7 +51,7 @@ namespace Emby.Server return list; } - protected override void AuthorizeServer(int udpPort, int httpServerPort, int httpsServerPort, string applicationPath, string tempDirectory) + protected override void AuthorizeServer() { } diff --git a/src/Emby.Server/Program.cs b/src/Emby.Server/Program.cs index 64fc423a1..6f871990d 100644 --- a/src/Emby.Server/Program.cs +++ b/src/Emby.Server/Program.cs @@ -28,8 +28,6 @@ namespace Emby.Server private static ILogger _logger; - private static bool _isRunningAsService = false; - private static bool _canRestartService = false; private static bool _appHostDisposed; [DllImport("kernel32.dll", SetLastError = true)] @@ -41,39 +39,33 @@ namespace Emby.Server public static void Main(string[] args) { var options = new StartupOptions(); - _isRunningAsService = options.ContainsOption("-service"); - - if (_isRunningAsService) - { - //_canRestartService = CanRestartWindowsService(); - } var currentProcess = Process.GetCurrentProcess(); - - var applicationPath = currentProcess.MainModule.FileName; + + var baseDirectory = System.AppContext.BaseDirectory; //var architecturePath = Path.Combine(Path.GetDirectoryName(applicationPath), Environment.Is64BitProcess ? "x64" : "x86"); //Wand.SetMagickCoderModulePath(architecturePath); //var success = SetDllDirectory(architecturePath); - var appPaths = CreateApplicationPaths(applicationPath, _isRunningAsService); + var appPaths = CreateApplicationPaths(baseDirectory); var logManager = new NlogManager(appPaths.LogDirectoryPath, "server"); logManager.ReloadLogger(LogSeverity.Debug); logManager.AddConsoleOutput(); var logger = _logger = logManager.GetLogger("Main"); - + ApplicationHost.LogEnvironmentInfo(logger, appPaths, true); AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - if (IsAlreadyRunning(applicationPath, currentProcess)) - { - logger.Info("Shutting down because another instance of Emby Server is already running."); - return; - } + //if (IsAlreadyRunning(applicationPath, currentProcess)) + //{ + // logger.Info("Shutting down because another instance of Emby Server is already running."); + // return; + //} if (PerformUpdateIfNeeded(appPaths, logger)) { @@ -81,14 +73,7 @@ namespace Emby.Server return; } - try - { - RunApplication(appPaths, logManager, _isRunningAsService, options); - } - finally - { - OnServiceShutdown(); - } + RunApplication(appPaths, logManager, options); } /// @@ -139,34 +124,17 @@ namespace Emby.Server } } - if (!_isRunningAsService) - { - return false; - } - return false; } /// /// Creates the application paths. /// - /// The application path. - /// if set to true [run as service]. - /// ServerApplicationPaths. - private static ServerApplicationPaths CreateApplicationPaths(string applicationPath, bool runAsService) + private static ServerApplicationPaths CreateApplicationPaths(string appDirectory) { - var resourcesPath = Path.GetDirectoryName(applicationPath); - - if (runAsService) - { - var systemPath = Path.GetDirectoryName(applicationPath); - - var programDataPath = Path.GetDirectoryName(systemPath); - - return new ServerApplicationPaths(programDataPath, applicationPath, resourcesPath); - } + var resourcesPath = appDirectory; - return new ServerApplicationPaths(ApplicationPathHelper.GetProgramDataPath(applicationPath), applicationPath, resourcesPath); + return new ServerApplicationPaths(ApplicationPathHelper.GetProgramDataPath(appDirectory), appDirectory, resourcesPath); } /// @@ -177,14 +145,7 @@ namespace Emby.Server { get { - if (_isRunningAsService) - { - return _canRestartService; - } - else - { - return true; - } + return true; } } @@ -196,14 +157,7 @@ namespace Emby.Server { get { - if (_isRunningAsService) - { - return _canRestartService; - } - else - { - return true; - } + return false; } } @@ -214,9 +168,8 @@ namespace Emby.Server /// /// The app paths. /// The log manager. - /// if set to true [run service]. /// The options. - private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, bool runService, StartupOptions options) + private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, StartupOptions options) { var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), true, true, true); @@ -240,29 +193,19 @@ namespace Emby.Server var initProgress = new Progress(); - if (!runService) - { - // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes - SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | - ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); - } + // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes + SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | + ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); var task = _appHost.Init(initProgress); Task.WaitAll(task); task = task.ContinueWith(new Action(a => _appHost.RunStartupTasks()), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); - if (runService) - { - StartService(logManager); - } - else - { - Task.WaitAll(task); + Task.WaitAll(task); - task = ApplicationTaskCompletionSource.Task; - Task.WaitAll(task); - } + task = ApplicationTaskCompletionSource.Task; + Task.WaitAll(task); } private static void GenerateCertificate(string certPath, string certHost) @@ -270,31 +213,6 @@ namespace Emby.Server //CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, _logger); } - /// - /// Starts the service. - /// - private static void StartService(ILogManager logManager) - { - } - - /// - /// Handles the Disposed event of the service control. - /// - /// The source of the event. - /// The instance containing the event data. - static void service_Disposed(object sender, EventArgs e) - { - ApplicationTaskCompletionSource.SetResult(true); - OnServiceShutdown(); - } - - private static void OnServiceShutdown() - { - _logger.Info("Shutting down"); - - DisposeAppHost(); - } - /// /// Handles the UnhandledException event of the CurrentDomain control. /// @@ -306,10 +224,7 @@ namespace Emby.Server new UnhandledExceptionWriter(_appHost.ServerConfigurationManager.ApplicationPaths, _logger, _appHost.LogManager).Log(exception); - if (!_isRunningAsService) - { - ShowMessageBox("Unhandled exception: " + exception.Message); - } + ShowMessageBox("Unhandled exception: " + exception.Message); if (!Debugger.IsAttached) { @@ -325,29 +240,6 @@ namespace Emby.Server /// true if XXXX, false otherwise private static bool PerformUpdateIfNeeded(ServerApplicationPaths appPaths, ILogger logger) { - // Look for the existence of an update archive - var updateArchive = Path.Combine(appPaths.TempUpdatePath, "MBServer" + ".zip"); - if (File.Exists(updateArchive)) - { - logger.Info("An update is available from {0}", updateArchive); - - // Update is there - execute update - try - { - //var serviceName = _isRunningAsService ? BackgroundService.GetExistingServiceName() : string.Empty; - //new ApplicationUpdater().UpdateApplication(appPaths, updateArchive, logger, serviceName); - - // And just let the app exit so it can update - return true; - } - catch (Exception e) - { - logger.ErrorException("Error starting updater.", e); - - ShowMessageBox(string.Format("Error attempting to update application.\n\n{0}\n\n{1}", e.GetType().Name, e.Message)); - } - } - return false; } @@ -358,37 +250,25 @@ namespace Emby.Server public static void Shutdown() { - if (_isRunningAsService) - { - ShutdownWindowsService(); - } - else - { - DisposeAppHost(); + DisposeAppHost(); - ShutdownWindowsApplication(); - } + //_logger.Info("Calling Application.Exit"); + //Application.Exit(); + + _logger.Info("Calling Environment.Exit"); + Environment.Exit(0); + + _logger.Info("Calling ApplicationTaskCompletionSource.SetResult"); + ApplicationTaskCompletionSource.SetResult(true); } public static void Restart() { DisposeAppHost(); - if (_isRunningAsService) - { - RestartWindowsService(); - } - else - { - //_logger.Info("Hiding server notify icon"); - //_serverNotifyIcon.Visible = false; + // todo: start new instance - _logger.Info("Starting new instance"); - //Application.Restart(); - Process.Start(_appHost.ServerConfigurationManager.ApplicationPaths.ApplicationPath); - - ShutdownWindowsApplication(); - } + Shutdown(); } private static void DisposeAppHost() @@ -402,31 +282,6 @@ namespace Emby.Server } } - private static void ShutdownWindowsApplication() - { - //_logger.Info("Calling Application.Exit"); - //Application.Exit(); - - _logger.Info("Calling Environment.Exit"); - Environment.Exit(0); - - _logger.Info("Calling ApplicationTaskCompletionSource.SetResult"); - ApplicationTaskCompletionSource.SetResult(true); - } - - private static void ShutdownWindowsService() - { - } - - private static void RestartWindowsService() - { - } - - private static bool CanRestartWindowsService() - { - return false; - } - /// /// Sets the error mode. /// diff --git a/src/Emby.Server/project.json b/src/Emby.Server/project.json index c64db844f..55acee514 100644 --- a/src/Emby.Server/project.json +++ b/src/Emby.Server/project.json @@ -12,10 +12,10 @@ "version": "1.0.1" }, "Mono.Nat": "1.0.0-*", - "Microsoft.Win32.Registry": "4.0.0", - "System.Runtime.Extensions": "4.1.0", - "System.Diagnostics.Process": "4.1.0", - "Microsoft.Data.SQLite": "1.0.0" + "Microsoft.Win32.Registry": "4.0.0", + "System.Runtime.Extensions": "4.1.0", + "System.Diagnostics.Process": "4.1.0", + "Microsoft.Data.SQLite": "1.1.0-preview1-final" }, "frameworks": { -- cgit v1.2.3 From 44336488f375adcdd5aa696790706b38483381fe Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 14 Nov 2016 14:48:01 -0500 Subject: update udp sockets --- Emby.Common.Implementations/Net/SocketFactory.cs | 1 + Emby.Common.Implementations/Net/UdpSocket.cs | 11 ++------- Emby.Dlna/Main/DlnaEntryPoint.cs | 30 ++++++++++++++++++------ Emby.Dlna/Ssdp/DeviceDiscovery.cs | 20 +++++++--------- RSSDP/SsdpCommunicationsServer.cs | 17 +++++++------- 5 files changed, 42 insertions(+), 37 deletions(-) (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index f26137683..c65593242 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -131,6 +131,7 @@ namespace Emby.Common.Implementations.Net #else retVal.ExclusiveAddressUse = false; #endif + //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP)); diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index eca82034b..b9b7d8a2d 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -63,7 +63,7 @@ namespace Emby.Common.Implementations.Net } }, state); #else - _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, new AsyncCallback(this.ProcessResponse), state); + _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, ProcessResponse, state); #endif return tcs.Task; @@ -99,7 +99,7 @@ namespace Emby.Common.Implementations.Net _Socket.EndSend(result); taskSource.TrySetResult(true); } - catch (SocketException ex) + catch (Exception ex) { taskSource.TrySetException(ex); } @@ -200,13 +200,6 @@ namespace Emby.Common.Implementations.Net { state.TaskCompletionSource.SetCanceled(); } - catch (SocketException se) - { - if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown) - state.TaskCompletionSource.SetException(se); - else - state.TaskCompletionSource.SetCanceled(); - } catch (Exception ex) { state.TaskCompletionSource.SetException(ex); diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index ef27c029d..49bb9a74f 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -55,6 +55,8 @@ namespace Emby.Dlna.Main private readonly ISocketFactory _socketFactory; private readonly IEnvironmentInfo _environmentInfo; + private ISsdpCommunicationsServer _communicationsServer; + public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, @@ -152,10 +154,18 @@ namespace Emby.Dlna.Main { try { - StartPublishing(); + if (_communicationsServer == null) + { + _communicationsServer = new SsdpCommunicationsServer(_socketFactory) + { + IsShared = true + }; + } + + StartPublishing(_communicationsServer); _ssdpHandlerStarted = true; - StartDeviceDiscovery(); + StartDeviceDiscovery(_communicationsServer); } catch (Exception ex) { @@ -165,20 +175,20 @@ namespace Emby.Dlna.Main private void LogMessage(string msg) { - //_logger.Debug(msg); + _logger.Debug(msg); } - private void StartPublishing() + private void StartPublishing(ISsdpCommunicationsServer communicationsServer) { SsdpDevicePublisherBase.LogFunction = LogMessage; - _Publisher = new SsdpDevicePublisher(_socketFactory, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); + _Publisher = new SsdpDevicePublisher(communicationsServer, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); } - private void StartDeviceDiscovery() + private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer) { try { - ((DeviceDiscovery)_deviceDiscovery).Start(); + ((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer); } catch (Exception ex) { @@ -374,6 +384,12 @@ namespace Emby.Dlna.Main DisposeDlnaServer(); DisposePlayToManager(); DisposeSsdpHandler(); + + if (_communicationsServer != null) + { + _communicationsServer.Dispose(); + _communicationsServer = null; + } } public void DisposeDlnaServer() diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index 6f5842968..1cd19d010 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -15,6 +15,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Net; using MediaBrowser.Model.Threading; using Rssdp; +using Rssdp.Infrastructure; namespace Emby.Dlna.Ssdp { @@ -29,7 +30,7 @@ namespace Emby.Dlna.Ssdp public event EventHandler> DeviceDiscovered; public event EventHandler> DeviceLeft; - private SsdpDeviceLocator _DeviceLocator; + private SsdpDeviceLocator _deviceLocator; private readonly ITimerFactory _timerFactory; private readonly ISocketFactory _socketFactory; @@ -45,9 +46,9 @@ namespace Emby.Dlna.Ssdp } // Call this method from somewhere in your code to start the search. - public void BeginSearch() + public void Start(ISsdpCommunicationsServer communicationsServer) { - _DeviceLocator = new SsdpDeviceLocator(_socketFactory, _timerFactory); + _deviceLocator = new SsdpDeviceLocator(communicationsServer, _timerFactory); // (Optional) Set the filter so we only see notifications for devices we care about // (can be any search target value i.e device type, uuid value etc - any value that appears in the @@ -55,8 +56,8 @@ namespace Emby.Dlna.Ssdp //_DeviceLocator.NotificationFilter = "upnp:rootdevice"; // Connect our event handler so we process devices as they are found - _DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable; - _DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable; + _deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable; + _deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable; // Perform a search so we don't have to wait for devices to broadcast notifications // again to get any results right away (notifications are broadcast periodically). @@ -72,9 +73,9 @@ namespace Emby.Dlna.Ssdp try { // Enable listening for notifications (optional) - _DeviceLocator.StartListeningForNotifications(); + _deviceLocator.StartListeningForNotifications(); - await _DeviceLocator.SearchAsync().ConfigureAwait(false); + await _deviceLocator.SearchAsync().ConfigureAwait(false); } catch (Exception ex) { @@ -130,11 +131,6 @@ namespace Emby.Dlna.Ssdp EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger); } - public void Start() - { - BeginSearch(); - } - public void Dispose() { if (!_disposed) diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index c0b9c6542..4de47f3d3 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -170,12 +170,11 @@ namespace Rssdp.Infrastructure /// /// Sends a message to the SSDP multicast address and port. /// - /// A byte array containing the data to send. - /// Thrown if the argument is null. - /// Thrown if the property is true (because has been called previously). - public async Task SendMulticastMessage(byte[] messageData) + public async Task SendMulticastMessage(string message) { - if (messageData == null) throw new ArgumentNullException("messageData"); + if (message == null) throw new ArgumentNullException("messageData"); + + byte[] messageData = Encoding.UTF8.GetBytes(message); ThrowIfDisposed(); @@ -294,21 +293,19 @@ namespace Rssdp.Infrastructure // Tasks are captured to local variables even if we don't use them just to avoid compiler warnings. var t = Task.Run(async () => { - var cancelled = false; while (!cancelled) { try { - var result = await socket.ReceiveAsync(); + var result = await socket.ReceiveAsync().ConfigureAwait(false); if (result.ReceivedBytes > 0) { // Strange cannot convert compiler error here if I don't explicitly // assign or cast to Action first. Assignment is easier to read, // so went with that. - Action processWork = () => ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint); - var processTask = Task.Run(processWork); + ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.RemoteEndPoint); } } catch (ObjectDisposedException) @@ -330,7 +327,9 @@ namespace Rssdp.Infrastructure lock (_SendSocketSynchroniser) { if (_SendSocket == null) + { _SendSocket = CreateSocketAndListenForResponsesAsync(); + } } } } -- cgit v1.2.3 From 401a6b8f4a84f378f0f6682ee7aecccc6ab30935 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 4 Dec 2016 16:30:38 -0500 Subject: add request logging --- Emby.Common.Implementations/Net/SocketFactory.cs | 26 ++-- Emby.Common.Implementations/Net/UdpSocket.cs | 46 +++--- .../Networking/NetworkManager.cs | 2 +- Emby.Dlna/ConnectionManager/ControlHandler.cs | 4 +- Emby.Dlna/ContentDirectory/ControlHandler.cs | 45 ++++-- Emby.Dlna/Emby.Dlna.csproj | 1 - Emby.Dlna/Main/DlnaEntryPoint.cs | 6 +- Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs | 6 +- Emby.Dlna/Server/Headers.cs | 169 --------------------- Emby.Dlna/Service/BaseControlHandler.cs | 17 +-- .../HttpServer/HttpListenerHost.cs | 2 +- ServiceStack/HttpHandlerFactory.cs | 9 +- 12 files changed, 96 insertions(+), 237 deletions(-) delete mode 100644 Emby.Dlna/Server/Headers.cs (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index c65593242..124252097 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; +using Emby.Common.Implementations.Networking; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; @@ -18,11 +19,6 @@ namespace Emby.Common.Implementations.Net // but that wasn't really the point so kept to YAGNI principal for now, even if the // interfaces are a bit ugly, specific and make assumptions. - /// - /// Used by RSSDP components to create implementations of the interface, to perform platform agnostic socket communications. - /// - private IPAddress _LocalIP; - private readonly ILogger _logger; public SocketFactory(ILogger logger) @@ -33,7 +29,6 @@ namespace Emby.Common.Implementations.Net } _logger = logger; - _LocalIP = IPAddress.Any; } public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode) @@ -66,7 +61,7 @@ namespace Emby.Common.Implementations.Net try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - return new UdpSocket(retVal, localPort, _LocalIP); + return new UdpSocket(retVal, localPort, IPAddress.Any); } catch { @@ -80,9 +75,8 @@ namespace Emby.Common.Implementations.Net /// /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port. /// - /// An integer specifying the local port to bind the socket to. /// An implementation of the interface used by RSSDP components to perform socket operations. - public IUdpSocket CreateSsdpUdpSocket(int localPort) + public IUdpSocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort) { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); @@ -91,8 +85,11 @@ namespace Emby.Common.Implementations.Net { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); - retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), _LocalIP)); - return new UdpSocket(retVal, localPort, _LocalIP); + + var localIp = NetworkManager.ToIPAddress(localIpAddress); + + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp)); + return new UdpSocket(retVal, localPort, localIp); } catch { @@ -134,10 +131,13 @@ namespace Emby.Common.Implementations.Net //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); - retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP)); + + var localIp = IPAddress.Any; + + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), localIp)); retVal.MulticastLoopback = true; - return new UdpSocket(retVal, localPort, _LocalIP); + return new UdpSocket(retVal, localPort, localIp); } catch { diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 367d2242c..b2af9d162 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -20,7 +20,6 @@ namespace Emby.Common.Implementations.Net private Socket _Socket; private int _LocalPort; - #endregion #region Constructors @@ -31,12 +30,19 @@ namespace Emby.Common.Implementations.Net _Socket = socket; _LocalPort = localPort; + LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); _Socket.Bind(new IPEndPoint(ip, _LocalPort)); } #endregion + public IpAddressInfo LocalIPAddress + { + get; + private set; + } + #region IUdpSocket Members public Task ReceiveAsync() @@ -50,18 +56,18 @@ namespace Emby.Common.Implementations.Net state.TaskCompletionSource = tcs; #if NETSTANDARD1_6 - _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer),SocketFlags.None, state.EndPoint) + _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer),SocketFlags.None, state.RemoteEndPoint) .ContinueWith((task, asyncState) => { if (task.Status != TaskStatus.Faulted) { var receiveState = asyncState as AsyncReceiveState; - receiveState.EndPoint = task.Result.RemoteEndPoint; - ProcessResponse(receiveState, () => task.Result.ReceivedBytes); + receiveState.RemoteEndPoint = task.Result.RemoteEndPoint; + ProcessResponse(receiveState, () => task.Result.ReceivedBytes, LocalIPAddress); } }, state); #else - _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, ProcessResponse, state); + _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); #endif return tcs.Task; @@ -74,6 +80,8 @@ namespace Emby.Common.Implementations.Net if (buffer == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); + var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); + #if NETSTANDARD1_6 if (size != buffer.Length) @@ -83,14 +91,14 @@ namespace Emby.Common.Implementations.Net buffer = copy; } - _Socket.SendTo(buffer, new IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); + _Socket.SendTo(buffer, ipEndPoint); return Task.FromResult(true); #else var taskSource = new TaskCompletionSource(); try { - _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port), result => + _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result => { try { @@ -109,7 +117,7 @@ namespace Emby.Common.Implementations.Net taskSource.TrySetException(ex); } - //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port)); + //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port)); return taskSource.Task; #endif @@ -133,19 +141,20 @@ namespace Emby.Common.Implementations.Net #region Private Methods - private static void ProcessResponse(AsyncReceiveState state, Func receiveData) + private static void ProcessResponse(AsyncReceiveState state, Func receiveData, IpAddressInfo localIpAddress) { try { var bytesRead = receiveData(); - var ipEndPoint = state.EndPoint as IPEndPoint; + var ipEndPoint = state.RemoteEndPoint as IPEndPoint; state.TaskCompletionSource.SetResult( - new SocketReceiveResult() + new SocketReceiveResult { Buffer = state.Buffer, ReceivedBytes = bytesRead, - RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) + RemoteEndPoint = ToIpEndPointInfo(ipEndPoint), + LocalIPAddress = localIpAddress } ); } @@ -182,15 +191,16 @@ namespace Emby.Common.Implementations.Net var state = asyncResult.AsyncState as AsyncReceiveState; try { - var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.EndPoint); + var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint); - var ipEndPoint = state.EndPoint as IPEndPoint; + var ipEndPoint = state.RemoteEndPoint as IPEndPoint; state.TaskCompletionSource.SetResult( new SocketReceiveResult { Buffer = state.Buffer, ReceivedBytes = bytesRead, - RemoteEndPoint = ToIpEndPointInfo(ipEndPoint) + RemoteEndPoint = ToIpEndPointInfo(ipEndPoint), + LocalIPAddress = LocalIPAddress } ); } @@ -211,13 +221,13 @@ namespace Emby.Common.Implementations.Net private class AsyncReceiveState { - public AsyncReceiveState(Socket socket, EndPoint endPoint) + public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint) { this.Socket = socket; - this.EndPoint = endPoint; + this.RemoteEndPoint = remoteEndPoint; } - public EndPoint EndPoint; + public EndPoint RemoteEndPoint; public byte[] Buffer = new byte[8192]; public Socket Socket { get; private set; } diff --git a/Emby.Common.Implementations/Networking/NetworkManager.cs b/Emby.Common.Implementations/Networking/NetworkManager.cs index a4f8f7ced..b9100f9db 100644 --- a/Emby.Common.Implementations/Networking/NetworkManager.cs +++ b/Emby.Common.Implementations/Networking/NetworkManager.cs @@ -27,7 +27,7 @@ namespace Emby.Common.Implementations.Networking private List _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); - public IEnumerable GetLocalIpAddresses() + public List GetLocalIpAddresses() { const int cacheMinutes = 5; diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs index 0bc44db17..ae983c5e7 100644 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs @@ -14,7 +14,7 @@ namespace Emby.Dlna.ConnectionManager { private readonly DeviceProfile _profile; - protected override IEnumerable> GetResult(string methodName, Headers methodParams) + protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) { if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase)) { @@ -26,7 +26,7 @@ namespace Emby.Dlna.ConnectionManager private IEnumerable> HandleGetProtocolInfo() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Source", _profile.ProtocolInfo }, { "Sink", "" } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index d77919e47..98a151f29 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -65,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, mediaEncoder); } - protected override IEnumerable> GetResult(string methodName, Headers methodParams) + protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) { var deviceId = "test"; @@ -118,17 +118,20 @@ namespace Emby.Dlna.ContentDirectory _userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed, CancellationToken.None); - return new Headers(); + return new Dictionary(StringComparer.OrdinalIgnoreCase); } private IEnumerable> HandleGetSearchCapabilities() { - return new Headers(true) { { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" } }; + return new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" } + }; } private IEnumerable> HandleGetSortCapabilities() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } }; @@ -136,7 +139,7 @@ namespace Emby.Dlna.ContentDirectory private IEnumerable> HandleGetSortExtensionCapabilities() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } }; @@ -144,14 +147,14 @@ namespace Emby.Dlna.ContentDirectory private IEnumerable> HandleGetSystemUpdateID() { - var headers = new Headers(true); + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); headers.Add("Id", _systemUpdateId.ToString(_usCulture)); return headers; } private IEnumerable> HandleGetFeatureList() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "FeatureList", GetFeatureListXml() } }; @@ -159,7 +162,7 @@ namespace Emby.Dlna.ContentDirectory private IEnumerable> HandleXGetFeatureList() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "FeatureList", GetFeatureListXml() } }; @@ -183,12 +186,24 @@ namespace Emby.Dlna.ContentDirectory return builder.ToString(); } - private async Task>> HandleBrowse(Headers sparams, User user, string deviceId) + public string GetValueOrDefault(IDictionary sparams, string key, string defaultValue) + { + string val; + + if (sparams.TryGetValue(key, out val)) + { + return val; + } + + return defaultValue; + } + + private async Task>> HandleBrowse(IDictionary sparams, User user, string deviceId) { var id = sparams["ObjectID"]; var flag = sparams["BrowseFlag"]; - var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); - var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); + var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); + var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); var provided = 0; @@ -294,11 +309,11 @@ namespace Emby.Dlna.ContentDirectory }; } - private async Task>> HandleSearch(Headers sparams, User user, string deviceId) + private async Task>> HandleSearch(IDictionary sparams, User user, string deviceId) { - var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", "")); - var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", "")); - var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); + var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", "")); + var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); + var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); // sort example: dc:title, dc:date diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 0441cb3be..4d1aacfec 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -152,7 +152,6 @@ - diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 170b4cee0..858b1ae9e 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -54,6 +54,7 @@ namespace Emby.Dlna.Main private readonly ITimerFactory _timerFactory; private readonly ISocketFactory _socketFactory; private readonly IEnvironmentInfo _environmentInfo; + private readonly INetworkManager _networkManager; private ISsdpCommunicationsServer _communicationsServer; @@ -69,7 +70,7 @@ namespace Emby.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo) + IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, INetworkManager networkManager) { _config = config; _appHost = appHost; @@ -87,6 +88,7 @@ namespace Emby.Dlna.Main _socketFactory = socketFactory; _timerFactory = timerFactory; _environmentInfo = environmentInfo; + _networkManager = networkManager; _logger = logManager.GetLogger("Dlna"); } @@ -156,7 +158,7 @@ namespace Emby.Dlna.Main { if (_communicationsServer == null) { - _communicationsServer = new SsdpCommunicationsServer(_socketFactory) + _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger) { IsShared = true }; diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 5e232aeac..daf46b106 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -11,7 +11,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar { public class ControlHandler : BaseControlHandler { - protected override IEnumerable> GetResult(string methodName, Headers methodParams) + protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) { if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase)) return HandleIsAuthorized(); @@ -23,7 +23,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar private IEnumerable> HandleIsAuthorized() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Result", "1" } }; @@ -31,7 +31,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar private IEnumerable> HandleIsValidated() { - return new Headers(true) + return new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Result", "1" } }; diff --git a/Emby.Dlna/Server/Headers.cs b/Emby.Dlna/Server/Headers.cs deleted file mode 100644 index 47dd8e321..000000000 --- a/Emby.Dlna/Server/Headers.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace Emby.Dlna.Server -{ - public class Headers : IDictionary - { - private readonly bool _asIs = false; - private readonly Dictionary _dict = new Dictionary(); - private readonly static Regex Validator = new Regex(@"^[a-z\d][a-z\d_.-]+$", RegexOptions.IgnoreCase); - - public Headers(bool asIs) - { - _asIs = asIs; - } - - public Headers() - : this(asIs: false) - { - } - - public int Count - { - get - { - return _dict.Count; - } - } - public string HeaderBlock - { - get - { - var hb = new StringBuilder(); - foreach (var h in this) - { - hb.AppendFormat("{0}: {1}\r\n", h.Key, h.Value); - } - return hb.ToString(); - } - } - public bool IsReadOnly - { - get - { - return false; - } - } - public ICollection Keys - { - get - { - return _dict.Keys; - } - } - public ICollection Values - { - get - { - return _dict.Values; - } - } - - - public string this[string key] - { - get - { - return _dict[Normalize(key)]; - } - set - { - _dict[Normalize(key)] = value; - } - } - - - private string Normalize(string header) - { - if (!_asIs) - { - header = header.ToLower(); - } - header = header.Trim(); - if (!Validator.IsMatch(header)) - { - throw new ArgumentException("Invalid header: " + header); - } - return header; - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return _dict.GetEnumerator(); - } - - public void Add(KeyValuePair item) - { - Add(item.Key, item.Value); - } - - public void Add(string key, string value) - { - _dict.Add(Normalize(key), value); - } - - public void Clear() - { - _dict.Clear(); - } - - public bool Contains(KeyValuePair item) - { - var p = new KeyValuePair(Normalize(item.Key), item.Value); - return _dict.Contains(p); - } - - public bool ContainsKey(string key) - { - return _dict.ContainsKey(Normalize(key)); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - public IEnumerator> GetEnumerator() - { - return _dict.GetEnumerator(); - } - - public bool Remove(string key) - { - return _dict.Remove(Normalize(key)); - } - - public bool Remove(KeyValuePair item) - { - return Remove(item.Key); - } - - public override string ToString() - { - return string.Format("({0})", string.Join(", ", (from x in _dict - select string.Format("{0}={1}", x.Key, x.Value)))); - } - - public bool TryGetValue(string key, out string value) - { - return _dict.TryGetValue(Normalize(key), out value); - } - - public string GetValueOrDefault(string key, string defaultValue) - { - string val; - - if (TryGetValue(key, out val)) - { - return val; - } - - return defaultValue; - } - } -} diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 4ce047172..3092589c1 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text; using System.Xml; using Emby.Dlna.Didl; +using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Xml; namespace Emby.Dlna.Service @@ -185,8 +186,7 @@ namespace Emby.Dlna.Service { using (var subReader = reader.ReadSubtree()) { - result.Headers = ParseFirstBodyChild(subReader); - + ParseFirstBodyChild(subReader, result.Headers); return result; } } @@ -204,10 +204,8 @@ namespace Emby.Dlna.Service return result; } - private Headers ParseFirstBodyChild(XmlReader reader) + private void ParseFirstBodyChild(XmlReader reader, IDictionary headers) { - var result = new Headers(); - reader.MoveToContent(); reader.Read(); @@ -216,25 +214,24 @@ namespace Emby.Dlna.Service { if (reader.NodeType == XmlNodeType.Element) { - result.Add(reader.LocalName, reader.ReadElementContentAsString()); + // TODO: Should we be doing this here, or should it be handled earlier when decoding the request? + headers[reader.LocalName.RemoveDiacritics()] = reader.ReadElementContentAsString(); } else { reader.Read(); } } - - return result; } private class ControlRequestInfo { public string LocalName; public string NamespaceURI; - public Headers Headers = new Headers(); + public IDictionary Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); } - protected abstract IEnumerable> GetResult(string methodName, Headers methodParams); + protected abstract IEnumerable> GetResult(string methodName, IDictionary methodParams); private void LogRequest(ControlRequest request) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 8a5ae2c3a..0e1f5a551 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -518,7 +518,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - var handler = HttpHandlerFactory.GetHandler(httpReq); + var handler = HttpHandlerFactory.GetHandler(httpReq, _logger); if (handler != null) { diff --git a/ServiceStack/HttpHandlerFactory.cs b/ServiceStack/HttpHandlerFactory.cs index d48bfeb5f..5f4892d51 100644 --- a/ServiceStack/HttpHandlerFactory.cs +++ b/ServiceStack/HttpHandlerFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Services; using ServiceStack.Host; @@ -9,12 +10,16 @@ namespace ServiceStack public class HttpHandlerFactory { // Entry point for HttpListener - public static RestHandler GetHandler(IHttpRequest httpReq) + public static RestHandler GetHandler(IHttpRequest httpReq, ILogger logger) { var pathInfo = httpReq.PathInfo; var pathParts = pathInfo.TrimStart('/').Split('/'); - if (pathParts.Length == 0) return null; + if (pathParts.Length == 0) + { + logger.Error("Path parts empty for PathInfo: {0}, Url: {1}", pathInfo, httpReq.RawUrl); + return null; + } string contentType; var restPath = RestHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out contentType); -- cgit v1.2.3 From 9accc3b025c7071779efbd4fb3dd8ef2b17a6039 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 5 Dec 2016 13:46:38 -0500 Subject: update recording fields --- Emby.Common.Implementations/Net/SocketFactory.cs | 33 ++++++++++++++----- Emby.Dlna/Main/DlnaEntryPoint.cs | 4 ++- RSSDP/SsdpCommunicationsServer.cs | 41 +++++++++++++++--------- 3 files changed, 54 insertions(+), 24 deletions(-) (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 124252097..79310e017 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -33,18 +33,35 @@ namespace Emby.Common.Implementations.Net public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode) { - var addressFamily = family == IpAddressFamily.InterNetwork - ? AddressFamily.InterNetwork - : AddressFamily.InterNetworkV6; + try + { + var addressFamily = family == IpAddressFamily.InterNetwork + ? AddressFamily.InterNetwork + : AddressFamily.InterNetworkV6; - var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); - if (dualMode) - { - socket.DualMode = true; + if (dualMode) + { + socket.DualMode = true; + } + + return new NetSocket(socket, _logger); } + catch (SocketException ex) + { + if (dualMode) + { + _logger.Error("Error creating dual mode socket: {0}. Will retry with ipv4-only.", ex.SocketErrorCode); + + if (ex.SocketErrorCode == SocketError.AddressFamilyNotSupported) + { + return CreateSocket(IpAddressFamily.InterNetwork, socketType, protocolType, false); + } + } - return new NetSocket(socket, _logger); + throw; + } } #region ISocketFactory Members diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 858b1ae9e..16108522c 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -158,7 +158,9 @@ namespace Emby.Dlna.Main { if (_communicationsServer == null) { - _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger) + var enableMultiSocketBinding = _environmentInfo.OperatingSystem == OperatingSystem.Windows; + + _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding) { IsShared = true }; diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 5d8ca15bb..b709861d5 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -52,6 +52,7 @@ namespace Rssdp.Infrastructure private int _MulticastTtl; private bool _IsShared; + private readonly bool _enableMultiSocketBinding; #endregion @@ -75,8 +76,8 @@ namespace Rssdp.Infrastructure /// Minimum constructor. /// /// The argument is null. - public SsdpCommunicationsServer(ISocketFactory socketFactory, INetworkManager networkManager, ILogger logger) - : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger) + public SsdpCommunicationsServer(ISocketFactory socketFactory, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding) + : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding) { } @@ -85,7 +86,7 @@ namespace Rssdp.Infrastructure /// /// The argument is null. /// The argument is less than or equal to zero. - public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive, INetworkManager networkManager, ILogger logger) + public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding) { if (socketFactory == null) throw new ArgumentNullException("socketFactory"); if (multicastTimeToLive <= 0) throw new ArgumentOutOfRangeException("multicastTimeToLive", "multicastTimeToLive must be greater than zero."); @@ -102,6 +103,7 @@ namespace Rssdp.Infrastructure _MulticastTtl = multicastTimeToLive; _networkManager = networkManager; _logger = logger; + _enableMultiSocketBinding = enableMultiSocketBinding; } #endregion @@ -199,16 +201,22 @@ namespace Rssdp.Infrastructure if (fromLocalIpAddress.AddressFamily == IpAddressFamily.InterNetwork) { sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.Any) || fromLocalIpAddress.Equals(i.LocalIPAddress)); + + // If sending to the loopback address, filter the socket list as well + if (destination.IpAddress.Equals(IpAddressInfo.Loopback)) + { + sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.Any) || i.LocalIPAddress.Equals(IpAddressInfo.Loopback)); + } } else if (fromLocalIpAddress.AddressFamily == IpAddressFamily.InterNetworkV6) { sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.IPv6Any) || fromLocalIpAddress.Equals(i.LocalIPAddress)); - } - // If sending to the loopback address, filter the socket list as well - if (destination.IpAddress.Equals(IpAddressInfo.Loopback)) - { - sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.Any) || i.LocalIPAddress.Equals(IpAddressInfo.Loopback)); + // If sending to the loopback address, filter the socket list as well + if (destination.IpAddress.Equals(IpAddressInfo.IPv6Loopback)) + { + sockets = sockets.Where(i => i.LocalIPAddress.Equals(IpAddressInfo.IPv6Any) || i.LocalIPAddress.Equals(IpAddressInfo.IPv6Loopback)); + } } return sockets.ToList(); @@ -353,15 +361,18 @@ namespace Rssdp.Infrastructure sockets.Add(_SocketFactory.CreateSsdpUdpSocket(IpAddressInfo.Any, _LocalPort)); - foreach (var address in _networkManager.GetLocalIpAddresses().ToList()) + if (_enableMultiSocketBinding) { - try + foreach (var address in _networkManager.GetLocalIpAddresses().ToList()) { - sockets.Add(_SocketFactory.CreateSsdpUdpSocket(address, _LocalPort)); - } - catch (Exception ex) - { - _logger.ErrorException("Error in CreateSsdpUdpSocket. IPAddress: {0}", ex, address); + try + { + sockets.Add(_SocketFactory.CreateSsdpUdpSocket(address, _LocalPort)); + } + catch (Exception ex) + { + _logger.ErrorException("Error in CreateSsdpUdpSocket. IPAddress: {0}", ex, address); + } } } -- cgit v1.2.3 From 0130209cdce07dc042b075c6cf972a7eb1339861 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 7 Dec 2016 15:02:34 -0500 Subject: improve ipv6 error handling --- Emby.Common.Implementations/Net/NetSocket.cs | 7 +++- Emby.Common.Implementations/Net/SocketAcceptor.cs | 6 ++- Emby.Common.Implementations/Net/SocketFactory.cs | 14 +------ Emby.Server.Core/ApplicationHost.cs | 47 ++++++++++++++++++---- MediaBrowser.Model/Net/ISocket.cs | 12 ++++++ .../Net/EndPointListener.cs | 20 ++++++++- 6 files changed, 80 insertions(+), 26 deletions(-) (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/Emby.Common.Implementations/Net/NetSocket.cs b/Emby.Common.Implementations/Net/NetSocket.cs index 62ca3d6ac..bc012dfe2 100644 --- a/Emby.Common.Implementations/Net/NetSocket.cs +++ b/Emby.Common.Implementations/Net/NetSocket.cs @@ -13,7 +13,9 @@ namespace Emby.Common.Implementations.Net public Socket Socket { get; private set; } private readonly ILogger _logger; - public NetSocket(Socket socket, ILogger logger) + public bool DualMode { get; private set; } + + public NetSocket(Socket socket, ILogger logger, bool isDualMode) { if (socket == null) { @@ -26,6 +28,7 @@ namespace Emby.Common.Implementations.Net Socket = socket; _logger = logger; + DualMode = isDualMode; } public IpEndPointInfo LocalEndPoint @@ -81,7 +84,7 @@ namespace Emby.Common.Implementations.Net private SocketAcceptor _acceptor; public void StartAccept(Action onAccept, Func isClosed) { - _acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed); + _acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed, DualMode); _acceptor.StartAccept(); } diff --git a/Emby.Common.Implementations/Net/SocketAcceptor.cs b/Emby.Common.Implementations/Net/SocketAcceptor.cs index bddb7a079..d4c6d33e5 100644 --- a/Emby.Common.Implementations/Net/SocketAcceptor.cs +++ b/Emby.Common.Implementations/Net/SocketAcceptor.cs @@ -11,8 +11,9 @@ namespace Emby.Common.Implementations.Net private readonly Socket _originalSocket; private readonly Func _isClosed; private readonly Action _onAccept; + private readonly bool _isDualMode; - public SocketAcceptor(ILogger logger, Socket originalSocket, Action onAccept, Func isClosed) + public SocketAcceptor(ILogger logger, Socket originalSocket, Action onAccept, Func isClosed, bool isDualMode) { if (logger == null) { @@ -34,6 +35,7 @@ namespace Emby.Common.Implementations.Net _logger = logger; _originalSocket = originalSocket; _isClosed = isClosed; + _isDualMode = isDualMode; _onAccept = onAccept; } @@ -115,7 +117,7 @@ namespace Emby.Common.Implementations.Net if (acceptSocket != null) { //ProcessAccept(acceptSocket); - _onAccept(new NetSocket(acceptSocket, _logger)); + _onAccept(new NetSocket(acceptSocket, _logger, _isDualMode)); } // Accept the next connection request diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 79310e017..1f41ffff5 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -46,21 +46,11 @@ namespace Emby.Common.Implementations.Net socket.DualMode = true; } - return new NetSocket(socket, _logger); + return new NetSocket(socket, _logger, dualMode); } catch (SocketException ex) { - if (dualMode) - { - _logger.Error("Error creating dual mode socket: {0}. Will retry with ipv4-only.", ex.SocketErrorCode); - - if (ex.SocketErrorCode == SocketError.AddressFamilyNotSupported) - { - return CreateSocket(IpAddressFamily.InterNetwork, socketType, protocolType, false); - } - } - - throw; + throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex); } } diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index f7fe02fba..06c761f21 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -1305,19 +1305,49 @@ namespace Emby.Server.Core public async Task> GetLocalIpAddresses() { - var addresses = NetworkManager.GetLocalIpAddresses().ToList(); - var list = new List(); + var addresses = ServerConfigurationManager + .Configuration + .LocalNetworkAddresses + .Select(NormalizeConfiguredLocalAddress) + .Where(i => i != null) + .ToList(); - foreach (var address in addresses) + if (addresses.Count == 0) { - var valid = await IsIpAddressValidAsync(address).ConfigureAwait(false); - if (valid) + addresses.AddRange(NetworkManager.GetLocalIpAddresses()); + + var list = new List(); + + foreach (var address in addresses) { - list.Add(address); + var valid = await IsIpAddressValidAsync(address).ConfigureAwait(false); + if (valid) + { + list.Add(address); + } } + + addresses = list; } - return list; + return addresses; + } + + private IpAddressInfo NormalizeConfiguredLocalAddress(string address) + { + var index = address.Trim('/').IndexOf('/'); + + if (index != -1) + { + address = address.Substring(index + 1); + } + + IpAddressInfo result; + if (NetworkManager.TryParseIpAddress(address.Trim('/'), out result)) + { + return result; + } + return null; } private readonly ConcurrentDictionary _validAddressResults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); @@ -1553,7 +1583,8 @@ namespace Emby.Server.Core throw new NotImplementedException(); } - var process = ProcessFactory.Create(new ProcessOptions { + var process = ProcessFactory.Create(new ProcessOptions + { FileName = url, EnableRaisingEvents = true, UseShellExecute = true, diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs index 371fbc567..aed35bce8 100644 --- a/MediaBrowser.Model/Net/ISocket.cs +++ b/MediaBrowser.Model/Net/ISocket.cs @@ -4,6 +4,7 @@ namespace MediaBrowser.Model.Net { public interface ISocket : IDisposable { + bool DualMode { get; } IpEndPointInfo LocalEndPoint { get; } IpEndPointInfo RemoteEndPoint { get; } void Close(); @@ -13,4 +14,15 @@ namespace MediaBrowser.Model.Net void StartAccept(Action onAccept, Func isClosed); } + + public class SocketCreateException : Exception + { + public SocketCreateException(string errorCode, Exception originalException) + : base(errorCode, originalException) + { + ErrorCode = errorCode; + } + + public string ErrorCode { get; private set; } + } } diff --git a/SocketHttpListener.Portable/Net/EndPointListener.cs b/SocketHttpListener.Portable/Net/EndPointListener.cs index 52385e2ba..690dedd09 100644 --- a/SocketHttpListener.Portable/Net/EndPointListener.cs +++ b/SocketHttpListener.Portable/Net/EndPointListener.cs @@ -26,7 +26,7 @@ namespace SocketHttpListener.Net Dictionary unregistered; private readonly ILogger _logger; private bool _closed; - private readonly bool _enableDualMode; + private bool _enableDualMode; private readonly ICryptoProvider _cryptoProvider; private readonly IStreamFactory _streamFactory; private readonly ISocketFactory _socketFactory; @@ -65,7 +65,23 @@ namespace SocketHttpListener.Net private void CreateSocket() { - sock = _socketFactory.CreateSocket(endpoint.IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, _enableDualMode); + try + { + sock = _socketFactory.CreateSocket(endpoint.IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, _enableDualMode); + } + catch (SocketCreateException ex) + { + if (_enableDualMode && endpoint.IpAddress.Equals(IpAddressInfo.IPv6Any) && string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase)) + { + endpoint = new IpEndPointInfo(IpAddressInfo.Any, endpoint.Port); + _enableDualMode = false; + sock = _socketFactory.CreateSocket(endpoint.IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, _enableDualMode); + } + else + { + throw; + } + } sock.Bind(endpoint); -- cgit v1.2.3 From 524e7facc87e746745af9095a3f100dcec1799b6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 13 Dec 2016 18:38:26 -0500 Subject: fix socket errors on linux under .net core --- Emby.Common.Implementations/Net/SocketFactory.cs | 12 ++--- .../Networking/NetworkManager.cs | 2 +- .../HttpServer/SocketSharp/SharpWebSocket.cs | 12 +---- MediaBrowser.Api/BaseApiService.cs | 3 +- SocketHttpListener.Portable/WebSocket.cs | 55 +++++++++------------- src/Emby.Server/Program.cs | 9 ++-- 6 files changed, 39 insertions(+), 54 deletions(-) (limited to 'Emby.Common.Implementations/Net/SocketFactory.cs') diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 1f41ffff5..70c7ba845 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -125,15 +125,15 @@ namespace Emby.Common.Implementations.Net try { -#if NETSTANDARD1_3 - // The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket - // See https://github.com/dotnet/corefx/pull/11509 for more details - if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) +#if NET46 + retVal.ExclusiveAddressUse = false; +#else + // The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket + // See https://github.com/dotnet/corefx/pull/11509 for more details + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) { retVal.ExclusiveAddressUse = false; } -#else - retVal.ExclusiveAddressUse = false; #endif //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); diff --git a/Emby.Common.Implementations/Networking/NetworkManager.cs b/Emby.Common.Implementations/Networking/NetworkManager.cs index a4e6d47d4..4485e8b14 100644 --- a/Emby.Common.Implementations/Networking/NetworkManager.cs +++ b/Emby.Common.Implementations/Networking/NetworkManager.cs @@ -245,7 +245,7 @@ namespace Emby.Common.Implementations.Networking //} return ipProperties.UnicastAddresses - .Where(i => i.IsDnsEligible) + //.Where(i => i.IsDnsEligible) .Select(i => i.Address) .Where(i => i.AddressFamily == AddressFamily.InterNetwork) .ToList(); diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs index 0a312f7b9..9823a2ff5 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs @@ -102,11 +102,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp /// Task. public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) { - var completionSource = new TaskCompletionSource(); - - WebSocket.SendAsync(bytes, res => completionSource.TrySetResult(true)); - - return completionSource.Task; + return WebSocket.SendAsync(bytes); } /// @@ -118,11 +114,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp /// Task. public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) { - var completionSource = new TaskCompletionSource(); - - WebSocket.SendAsync(text, res => completionSource.TrySetResult(true)); - - return completionSource.Task; + return WebSocket.SendAsync(text); } /// diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 7a8951396..07ec6d955 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -151,9 +151,10 @@ namespace MediaBrowser.Api } if (client.IndexOf("web", StringComparison.OrdinalIgnoreCase) == -1 && + + // covers both emby mobile and emby for android mobile client.IndexOf("mobile", StringComparison.OrdinalIgnoreCase) == -1 && client.IndexOf("ios", StringComparison.OrdinalIgnoreCase) == -1 && - client.IndexOf("android", StringComparison.OrdinalIgnoreCase) == -1 && client.IndexOf("theater", StringComparison.OrdinalIgnoreCase) == -1) { options.Fields.Add(Model.Querying.ItemFields.ChildCount); diff --git a/SocketHttpListener.Portable/WebSocket.cs b/SocketHttpListener.Portable/WebSocket.cs index 889880387..9966d3fcf 100644 --- a/SocketHttpListener.Portable/WebSocket.cs +++ b/SocketHttpListener.Portable/WebSocket.cs @@ -5,6 +5,7 @@ using System.IO; using System.Net; using System.Text; using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using SocketHttpListener.Net.WebSockets; @@ -621,26 +622,22 @@ namespace SocketHttpListener } } - private void sendAsync(Opcode opcode, Stream stream, Action completed) + private Task sendAsync(Opcode opcode, Stream stream) { - Func sender = send; - sender.BeginInvoke( - opcode, - stream, - ar => - { - try - { - var sent = sender.EndInvoke(ar); - if (completed != null) - completed(sent); - } - catch (Exception ex) - { - error("An exception has occurred while callback.", ex); - } - }, - null); + var completionSource = new TaskCompletionSource(); + Task.Run(() => + { + try + { + send(opcode, stream); + completionSource.TrySetResult(true); + } + catch (Exception ex) + { + completionSource.TrySetException(ex); + } + }); + return completionSource.Task; } // As server @@ -833,22 +830,18 @@ namespace SocketHttpListener /// /// An array of that represents the binary data to send. /// - /// /// An Action<bool> delegate that references the method(s) called when the send is /// complete. A passed to this delegate is true if the send is /// complete successfully; otherwise, false. - /// - public void SendAsync(byte[] data, Action completed) + public Task SendAsync(byte[] data) { var msg = _readyState.CheckIfOpen() ?? data.CheckIfValidSendData(); if (msg != null) { - error(msg); - - return; + throw new Exception(msg); } - sendAsync(Opcode.Binary, _memoryStreamFactory.CreateNew(data), completed); + return sendAsync(Opcode.Binary, _memoryStreamFactory.CreateNew(data)); } /// @@ -860,22 +853,18 @@ namespace SocketHttpListener /// /// A that represents the text data to send. /// - /// /// An Action<bool> delegate that references the method(s) called when the send is /// complete. A passed to this delegate is true if the send is /// complete successfully; otherwise, false. - /// - public void SendAsync(string data, Action completed) + public Task SendAsync(string data) { var msg = _readyState.CheckIfOpen() ?? data.CheckIfValidSendData(); if (msg != null) { - error(msg); - - return; + throw new Exception(msg); } - sendAsync(Opcode.Text, _memoryStreamFactory.CreateNew(Encoding.UTF8.GetBytes(data)), completed); + return sendAsync(Opcode.Text, _memoryStreamFactory.CreateNew(Encoding.UTF8.GetBytes(data))); } #endregion diff --git a/src/Emby.Server/Program.cs b/src/Emby.Server/Program.cs index 24e391c73..26141a0ce 100644 --- a/src/Emby.Server/Program.cs +++ b/src/Emby.Server/Program.cs @@ -215,9 +215,12 @@ namespace Emby.Server var initProgress = new Progress(); - // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes - SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | - ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); + if (environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) + { + // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes + SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | + ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); + } var task = _appHost.Init(initProgress); Task.WaitAll(task); -- cgit v1.2.3