From f52373609eac871c2883e1052020ff5327b19707 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 3 Nov 2016 18:34:16 -0400 Subject: move classes to portable project --- Emby.Server.Implementations/IO/ThrottledStream.cs | 394 ++++++++++++++++++++++ 1 file changed, 394 insertions(+) create mode 100644 Emby.Server.Implementations/IO/ThrottledStream.cs (limited to 'Emby.Server.Implementations/IO') diff --git a/Emby.Server.Implementations/IO/ThrottledStream.cs b/Emby.Server.Implementations/IO/ThrottledStream.cs new file mode 100644 index 0000000000..81760b6397 --- /dev/null +++ b/Emby.Server.Implementations/IO/ThrottledStream.cs @@ -0,0 +1,394 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Emby.Server.Implementations.IO +{ + /// + /// Class for streaming data with throttling support. + /// + public class ThrottledStream : Stream + { + /// + /// A constant used to specify an infinite number of bytes that can be transferred per second. + /// + public const long Infinite = 0; + + #region Private members + /// + /// The base stream. + /// + private readonly Stream _baseStream; + + /// + /// The maximum bytes per second that can be transferred through the base stream. + /// + private long _maximumBytesPerSecond; + + /// + /// The number of bytes that has been transferred since the last throttle. + /// + private long _byteCount; + + /// + /// The start time in milliseconds of the last throttle. + /// + private long _start; + #endregion + + #region Properties + /// + /// Gets the current milliseconds. + /// + /// The current milliseconds. + protected long CurrentMilliseconds + { + get + { + return Environment.TickCount; + } + } + + /// + /// Gets or sets the maximum bytes per second that can be transferred through the base stream. + /// + /// The maximum bytes per second. + public long MaximumBytesPerSecond + { + get + { + return _maximumBytesPerSecond; + } + set + { + if (MaximumBytesPerSecond != value) + { + _maximumBytesPerSecond = value; + Reset(); + } + } + } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + /// true if the stream supports reading; otherwise, false. + public override bool CanRead + { + get + { + return _baseStream.CanRead; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + /// + /// true if the stream supports seeking; otherwise, false. + public override bool CanSeek + { + get + { + return _baseStream.CanSeek; + } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + /// + /// true if the stream supports writing; otherwise, false. + public override bool CanWrite + { + get + { + return _baseStream.CanWrite; + } + } + + /// + /// Gets the length in bytes of the stream. + /// + /// + /// A long value representing the length of the stream in bytes. + /// The base stream does not support seeking. + /// Methods were called after the stream was closed. + public override long Length + { + get + { + return _baseStream.Length; + } + } + + /// + /// Gets or sets the position within the current stream. + /// + /// + /// The current position within the stream. + /// An I/O error occurs. + /// The base stream does not support seeking. + /// Methods were called after the stream was closed. + public override long Position + { + get + { + return _baseStream.Position; + } + set + { + _baseStream.Position = value; + } + } + #endregion + + public long MinThrottlePosition; + + #region Ctor + /// + /// Initializes a new instance of the class. + /// + /// The base stream. + /// The maximum bytes per second that can be transferred through the base stream. + /// Thrown when is a null reference. + /// Thrown when is a negative value. + public ThrottledStream(Stream baseStream, long maximumBytesPerSecond) + { + if (baseStream == null) + { + throw new ArgumentNullException("baseStream"); + } + + if (maximumBytesPerSecond < 0) + { + throw new ArgumentOutOfRangeException("maximumBytesPerSecond", + maximumBytesPerSecond, "The maximum number of bytes per second can't be negative."); + } + + _baseStream = baseStream; + _maximumBytesPerSecond = maximumBytesPerSecond; + _start = CurrentMilliseconds; + _byteCount = 0; + } + #endregion + + #region Public methods + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + /// An I/O error occurs. + public override void Flush() + { + _baseStream.Flush(); + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// + /// The sum of offset and count is larger than the buffer length. + /// Methods were called after the stream was closed. + /// The base stream does not support reading. + /// buffer is null. + /// An I/O error occurs. + /// offset or count is negative. + public override int Read(byte[] buffer, int offset, int count) + { + Throttle(count); + + return _baseStream.Read(buffer, offset, count); + } + + /// + /// Sets the position within the current stream. + /// + /// A byte offset relative to the origin parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// + /// The new position within the current stream. + /// + /// An I/O error occurs. + /// The base stream does not support seeking, such as if the stream is constructed from a pipe or console output. + /// Methods were called after the stream was closed. + public override long Seek(long offset, SeekOrigin origin) + { + return _baseStream.Seek(offset, origin); + } + + /// + /// Sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + /// The base stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. + /// An I/O error occurs. + /// Methods were called after the stream was closed. + public override void SetLength(long value) + { + _baseStream.SetLength(value); + } + + private long _bytesWritten; + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies count bytes from buffer to the current stream. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + /// An I/O error occurs. + /// The base stream does not support writing. + /// Methods were called after the stream was closed. + /// buffer is null. + /// The sum of offset and count is greater than the buffer length. + /// offset or count is negative. + public override void Write(byte[] buffer, int offset, int count) + { + Throttle(count); + + _baseStream.Write(buffer, offset, count); + + _bytesWritten += count; + } + + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + await ThrottleAsync(count, cancellationToken).ConfigureAwait(false); + + await _baseStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); + + _bytesWritten += count; + } + + /// + /// Returns a that represents the current . + /// + /// + /// A that represents the current . + /// + public override string ToString() + { + return _baseStream.ToString(); + } + #endregion + + private bool ThrottleCheck(int bufferSizeInBytes) + { + if (_bytesWritten < MinThrottlePosition) + { + return false; + } + + // Make sure the buffer isn't empty. + if (_maximumBytesPerSecond <= 0 || bufferSizeInBytes <= 0) + { + return false; + } + + return true; + } + + #region Protected methods + /// + /// Throttles for the specified buffer size in bytes. + /// + /// The buffer size in bytes. + protected void Throttle(int bufferSizeInBytes) + { + if (!ThrottleCheck(bufferSizeInBytes)) + { + return ; + } + + _byteCount += bufferSizeInBytes; + long elapsedMilliseconds = CurrentMilliseconds - _start; + + if (elapsedMilliseconds > 0) + { + // Calculate the current bps. + long bps = _byteCount * 1000L / elapsedMilliseconds; + + // If the bps are more then the maximum bps, try to throttle. + if (bps > _maximumBytesPerSecond) + { + // Calculate the time to sleep. + long wakeElapsed = _byteCount * 1000L / _maximumBytesPerSecond; + int toSleep = (int)(wakeElapsed - elapsedMilliseconds); + + if (toSleep > 1) + { + try + { + // The time to sleep is more then a millisecond, so sleep. + var task = Task.Delay(toSleep); + Task.WaitAll(task); + } + catch + { + // Eatup ThreadAbortException. + } + + // A sleep has been done, reset. + Reset(); + } + } + } + } + + protected async Task ThrottleAsync(int bufferSizeInBytes, CancellationToken cancellationToken) + { + if (!ThrottleCheck(bufferSizeInBytes)) + { + return; + } + + _byteCount += bufferSizeInBytes; + long elapsedMilliseconds = CurrentMilliseconds - _start; + + if (elapsedMilliseconds > 0) + { + // Calculate the current bps. + long bps = _byteCount * 1000L / elapsedMilliseconds; + + // If the bps are more then the maximum bps, try to throttle. + if (bps > _maximumBytesPerSecond) + { + // Calculate the time to sleep. + long wakeElapsed = _byteCount * 1000L / _maximumBytesPerSecond; + int toSleep = (int)(wakeElapsed - elapsedMilliseconds); + + if (toSleep > 1) + { + // The time to sleep is more then a millisecond, so sleep. + await Task.Delay(toSleep, cancellationToken).ConfigureAwait(false); + + // A sleep has been done, reset. + Reset(); + } + } + } + } + + /// + /// Will reset the bytecount to 0 and reset the start time to the current time. + /// + protected void Reset() + { + long difference = CurrentMilliseconds - _start; + + // Only reset counters when a known history is available of more then 1 second. + if (difference > 1000) + { + _byteCount = 0; + _start = CurrentMilliseconds; + } + } + #endregion + } +} \ No newline at end of file -- 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.Server.Implementations/IO') diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 3a2cea12a7..6f0ff29961 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 86ce9c83be..997d3f25fa 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 bab340e27d..d1c299dc96 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 0000000000..41b89ce52b --- /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 0000000000..d7574d466c --- /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 0000000000..6c2ac40c32 --- /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 0000000000..87cb6cdf93 --- /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 0000000000..5c94fa71c7 --- /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 0000000000..df5a7c985d --- /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 0000000000..295ecc4651 --- /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 0000000000..271b0bbdb5 --- /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 0000000000..c15e0ee41b --- /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 0a565f6701..fe60c7ebfa 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 03bbafe60a..c85b215f2c 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 c0e0440c25..3f1ddf84f9 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 cbeb8a995f..ef090e010c 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 1fdb22c930..0000000000 --- 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 0000000000..0a2d04ad39 --- /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 5ec0bea22c..0000000000 --- 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 565eeb259b..0000000000 --- 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 27bbfbe82d..0000000000 --- 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 f865278294..0000000000 --- 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 8cdfc4a6b3..0000000000 --- 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 20d89d2eb1..5619348543 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 2742e1a268..0000000000 --- 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 49cb1e75f4..34fc85e7b1 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 00b6cc5a8b..0000000000 --- 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 d6223c4655..066ee8e306 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 cd9b9651ec..0000000000 --- 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 5c83a13007..0000000000 --- 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 c2082f0d2a..0000000000 --- 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 5f609de27a..79f7b5f051 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 64278fe4e5..dadb1bff4b 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.Server.Implementations/IO') diff --git a/Emby.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs index bdebe894ef..9585abb2a2 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 0000000000..8cea617eae --- /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 6f0ff29961..bb38c72da5 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 997d3f25fa..6afb071ae3 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 aea63a57e2..3583f998e5 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 dc96f5726c..2b2357e383 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 14fbbcfa20..bff87bcacc 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 295ecc4651..39033249fd 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 c85b215f2c..52e477b1a0 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 0000000000..3fcacb30d4 --- /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 34fc85e7b1..a8363558d5 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 79f7b5f051..ba04338bb9 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 00cbadea2c5ea8c717808fb4e8b11004509dc379 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 11 Nov 2016 02:24:36 -0500 Subject: update core project --- .../IO/ManagedFileSystem.cs | 5 + Emby.Common.Implementations/Net/NetSocket.cs | 6 +- Emby.Common.Implementations/Net/UdpSocket.cs | 2 +- .../Networking/BaseNetworkManager.cs | 485 -------------------- .../Networking/NetworkManager.cs | 506 +++++++++++++++++++++ Emby.Server.Core/FFMpeg/FFMpegLoader.cs | 240 ++++++++++ Emby.Server.Core/Localization/TextLocalizer.cs | 25 + .../Security/AuthenticationRepository.cs | 317 +++++++++++++ Emby.Server.Core/project.json | 7 + .../Emby.Server.Implementations.csproj | 5 + .../IO/MbLinkShortcutHandler.cs | 55 +++ MediaBrowser.Common/Net/INetworkManager.cs | 7 - .../Devices/CameraUploadsFolder.cs | 67 --- .../MediaBrowser.Controller.csproj | 2 - .../Playlists/ManualPlaylistsFolder.cs | 53 --- MediaBrowser.Model/IO/IFileSystem.cs | 2 + .../Devices/CameraUploadsFolder.cs | 67 +++ .../MediaBrowser.Server.Implementations.csproj | 6 +- .../Playlists/ManualPlaylistsFolder.cs | 53 +++ .../MediaBrowser.Server.Mono.csproj | 8 +- MediaBrowser.Server.Mono/Native/MonoApp.cs | 3 +- MediaBrowser.Server.Mono/Native/MonoFileSystem.cs | 21 + MediaBrowser.Server.Mono/Program.cs | 3 +- .../ApplicationHost.cs | 20 +- .../Cryptography/CertificateGenerator.cs | 2 +- .../FFMpeg/FFMpegLoader.cs | 249 ---------- .../MbLinkShortcutHandler.cs | 55 --- .../MediaBrowser.Server.Startup.Common.csproj | 5 - .../Networking/NetworkManager.cs | 50 -- .../Security/AuthenticationRepository.cs | 317 ------------- .../TextLocalizer.cs | 25 - MediaBrowser.ServerApplication/MainStartup.cs | 1 + .../MediaBrowser.ServerApplication.csproj | 7 +- .../Native/WindowsApp.cs | 1 - .../Networking/NetworkManager.cs | 2 +- 35 files changed, 1334 insertions(+), 1345 deletions(-) delete mode 100644 Emby.Common.Implementations/Networking/BaseNetworkManager.cs create mode 100644 Emby.Common.Implementations/Networking/NetworkManager.cs create mode 100644 Emby.Server.Core/FFMpeg/FFMpegLoader.cs create mode 100644 Emby.Server.Core/Localization/TextLocalizer.cs create mode 100644 Emby.Server.Core/Security/AuthenticationRepository.cs create mode 100644 Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs delete mode 100644 MediaBrowser.Controller/Devices/CameraUploadsFolder.cs delete mode 100644 MediaBrowser.Controller/Playlists/ManualPlaylistsFolder.cs create mode 100644 MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs create mode 100644 MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs create mode 100644 MediaBrowser.Server.Mono/Native/MonoFileSystem.cs delete mode 100644 MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs delete mode 100644 MediaBrowser.Server.Startup.Common/MbLinkShortcutHandler.cs delete mode 100644 MediaBrowser.Server.Startup.Common/Networking/NetworkManager.cs delete mode 100644 MediaBrowser.Server.Startup.Common/Security/AuthenticationRepository.cs delete mode 100644 MediaBrowser.Server.Startup.Common/TextLocalizer.cs (limited to 'Emby.Server.Implementations/IO') diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs index 37b4575987..81ca8dcff2 100644 --- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs @@ -761,5 +761,10 @@ namespace Emby.Common.Implementations.IO var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; return Directory.EnumerateFileSystemEntries(path, "*", searchOption); } + + public virtual void SetExecutable(string path) + { + + } } } diff --git a/Emby.Common.Implementations/Net/NetSocket.cs b/Emby.Common.Implementations/Net/NetSocket.cs index 72faa41a9a..faa1a81e2d 100644 --- a/Emby.Common.Implementations/Net/NetSocket.cs +++ b/Emby.Common.Implementations/Net/NetSocket.cs @@ -23,7 +23,7 @@ namespace Emby.Common.Implementations.Net { get { - return BaseNetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.LocalEndPoint); + return NetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.LocalEndPoint); } } @@ -31,7 +31,7 @@ namespace Emby.Common.Implementations.Net { get { - return BaseNetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.RemoteEndPoint); + return NetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.RemoteEndPoint); } } @@ -64,7 +64,7 @@ namespace Emby.Common.Implementations.Net public void Bind(IpEndPointInfo endpoint) { - var nativeEndpoint = BaseNetworkManager.ToIPEndPoint(endpoint); + var nativeEndpoint = NetworkManager.ToIPEndPoint(endpoint); Socket.Bind(nativeEndpoint); } diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 244b37bb41..eca82034b0 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -175,7 +175,7 @@ namespace Emby.Common.Implementations.Net return null; } - return BaseNetworkManager.ToIpEndPointInfo(endpoint); + return NetworkManager.ToIpEndPointInfo(endpoint); } private void ProcessResponse(IAsyncResult asyncResult) diff --git a/Emby.Common.Implementations/Networking/BaseNetworkManager.cs b/Emby.Common.Implementations/Networking/BaseNetworkManager.cs deleted file mode 100644 index f1ac8413be..0000000000 --- a/Emby.Common.Implementations/Networking/BaseNetworkManager.cs +++ /dev/null @@ -1,485 +0,0 @@ -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -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 -{ - public abstract class BaseNetworkManager - { - protected ILogger Logger { get; private set; } - private DateTime _lastRefresh; - - protected BaseNetworkManager(ILogger logger) - { - Logger = logger; - } - - private List _localIpAddresses; - private readonly object _localIpAddressSyncLock = new object(); - - public IEnumerable GetLocalIpAddresses() - { - const int cacheMinutes = 5; - - lock (_localIpAddressSyncLock) - { - var forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes; - - if (_localIpAddresses == null || forceRefresh) - { - var addresses = GetLocalIpAddressesInternal().Select(ToIpAddressInfo).ToList(); - - _localIpAddresses = addresses; - _lastRefresh = DateTime.UtcNow; - - return addresses; - } - } - - return _localIpAddresses; - } - - private IEnumerable GetLocalIpAddressesInternal() - { - var list = GetIPsDefault() - .ToList(); - - if (list.Count == 0) - { - list.AddRange(GetLocalIpAddressesFallback().Result); - } - - return list.Where(FilterIpAddress).DistinctBy(i => i.ToString()); - } - - private bool FilterIpAddress(IPAddress address) - { - var addressString = address.ToString(); - - if (addressString.StartsWith("169.", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - return true; - } - - public bool IsInPrivateAddressSpace(string endpoint) - { - if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - // Handle ipv4 mapped to ipv6 - endpoint = endpoint.Replace("::ffff:", string.Empty); - - // Private address space: - // http://en.wikipedia.org/wiki/Private_network - - if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase)) - { - return Is172AddressPrivate(endpoint); - } - - return - - endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase); - } - - private bool Is172AddressPrivate(string endpoint) - { - for (var i = 16; i <= 31; i++) - { - if (endpoint.StartsWith("172." + i.ToString(CultureInfo.InvariantCulture) + ".", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - - public bool IsInLocalNetwork(string endpoint) - { - return IsInLocalNetworkInternal(endpoint, true); - } - - public bool IsInLocalNetworkInternal(string endpoint, bool resolveHost) - { - if (string.IsNullOrWhiteSpace(endpoint)) - { - throw new ArgumentNullException("endpoint"); - } - - IPAddress address; - if (IPAddress.TryParse(endpoint, out address)) - { - var addressString = address.ToString(); - - int lengthMatch = 100; - if (address.AddressFamily == AddressFamily.InterNetwork) - { - lengthMatch = 4; - if (IsInPrivateAddressSpace(addressString)) - { - return true; - } - } - else if (address.AddressFamily == AddressFamily.InterNetworkV6) - { - lengthMatch = 10; - if (IsInPrivateAddressSpace(endpoint)) - { - return true; - } - } - - // Should be even be doing this with ipv6? - if (addressString.Length >= lengthMatch) - { - var prefix = addressString.Substring(0, lengthMatch); - - if (GetLocalIpAddresses().Any(i => i.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) - { - return true; - } - } - } - else if (resolveHost) - { - Uri uri; - if (Uri.TryCreate(endpoint, UriKind.RelativeOrAbsolute, out uri)) - { - try - { - var host = uri.DnsSafeHost; - Logger.Debug("Resolving host {0}", host); - - address = GetIpAddresses(host).Result.FirstOrDefault(); - - if (address != null) - { - Logger.Debug("{0} resolved to {1}", host, address); - - return IsInLocalNetworkInternal(address.ToString(), false); - } - } - catch (InvalidOperationException) - { - // Can happen with reverse proxy or IIS url rewriting - } - catch (Exception ex) - { - Logger.ErrorException("Error resovling hostname", ex); - } - } - } - - return false; - } - - private Task GetIpAddresses(string hostName) - { - return Dns.GetHostAddressesAsync(hostName); - } - - private List GetIPsDefault() - { - NetworkInterface[] interfaces; - - try - { - var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown }; - - interfaces = NetworkInterface.GetAllNetworkInterfaces() - .Where(i => validStatuses.Contains(i.OperationalStatus)) - .ToArray(); - } - catch (Exception ex) - { - Logger.ErrorException("Error in GetAllNetworkInterfaces", ex); - return new List(); - } - - return interfaces.SelectMany(network => - { - - try - { - Logger.Debug("Querying interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus); - - var properties = network.GetIPProperties(); - - return properties.UnicastAddresses - .Where(i => i.IsDnsEligible) - .Select(i => i.Address) - .Where(i => i.AddressFamily == AddressFamily.InterNetwork) - .ToList(); - } - catch (Exception ex) - { - Logger.ErrorException("Error querying network interface", ex); - return new List(); - } - - }).DistinctBy(i => i.ToString()) - .ToList(); - } - - private async Task> GetLocalIpAddressesFallback() - { - var host = await Dns.GetHostEntryAsync(Dns.GetHostName()).ConfigureAwait(false); - - // Reverse them because the last one is usually the correct one - // It's not fool-proof so ultimately the consumer will have to examine them and decide - return host.AddressList - .Where(i => i.AddressFamily == AddressFamily.InterNetwork) - .Reverse(); - } - - /// - /// Gets a random port number that is currently available - /// - /// System.Int32. - public int GetRandomUnusedPort() - { - var listener = new TcpListener(IPAddress.Any, 0); - listener.Start(); - var port = ((IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - return port; - } - - /// - /// Returns MAC Address from first Network Card in Computer - /// - /// [string] MAC Address - public string GetMacAddress() - { - return NetworkInterface.GetAllNetworkInterfaces() - .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback) - .Select(i => BitConverter.ToString(i.GetPhysicalAddress().GetAddressBytes())) - .FirstOrDefault(); - } - - /// - /// Parses the specified endpointstring. - /// - /// The endpointstring. - /// IPEndPoint. - public IPEndPoint Parse(string endpointstring) - { - return Parse(endpointstring, -1).Result; - } - - /// - /// Parses the specified endpointstring. - /// - /// The endpointstring. - /// The defaultport. - /// IPEndPoint. - /// Endpoint descriptor may not be empty. - /// - private static async Task Parse(string endpointstring, int defaultport) - { - if (String.IsNullOrEmpty(endpointstring) - || endpointstring.Trim().Length == 0) - { - throw new ArgumentException("Endpoint descriptor may not be empty."); - } - - if (defaultport != -1 && - (defaultport < IPEndPoint.MinPort - || defaultport > IPEndPoint.MaxPort)) - { - throw new ArgumentException(String.Format("Invalid default port '{0}'", defaultport)); - } - - string[] values = endpointstring.Split(new char[] { ':' }); - IPAddress ipaddy; - int port = -1; - - //check if we have an IPv6 or ports - if (values.Length <= 2) // ipv4 or hostname - { - port = values.Length == 1 ? defaultport : GetPort(values[1]); - - //try to use the address as IPv4, otherwise get hostname - if (!IPAddress.TryParse(values[0], out ipaddy)) - ipaddy = await GetIPfromHost(values[0]).ConfigureAwait(false); - } - else if (values.Length > 2) //ipv6 - { - //could [a:b:c]:d - if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]")) - { - string ipaddressstring = String.Join(":", values.Take(values.Length - 1).ToArray()); - ipaddy = IPAddress.Parse(ipaddressstring); - port = GetPort(values[values.Length - 1]); - } - else //[a:b:c] or a:b:c - { - ipaddy = IPAddress.Parse(endpointstring); - port = defaultport; - } - } - else - { - throw new FormatException(String.Format("Invalid endpoint ipaddress '{0}'", endpointstring)); - } - - if (port == -1) - throw new ArgumentException(String.Format("No port specified: '{0}'", endpointstring)); - - return new IPEndPoint(ipaddy, port); - } - - protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - /// - /// Gets the port. - /// - /// The p. - /// System.Int32. - /// - private static int GetPort(string p) - { - int port; - - if (!Int32.TryParse(p, out port) - || port < IPEndPoint.MinPort - || port > IPEndPoint.MaxPort) - { - throw new FormatException(String.Format("Invalid end point port '{0}'", p)); - } - - return port; - } - - /// - /// Gets the I pfrom host. - /// - /// The p. - /// IPAddress. - /// - private static async Task GetIPfromHost(string p) - { - var hosts = await Dns.GetHostAddressesAsync(p).ConfigureAwait(false); - - if (hosts == null || hosts.Length == 0) - throw new ArgumentException(String.Format("Host not found: {0}", p)); - - 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 = 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/Networking/NetworkManager.cs b/Emby.Common.Implementations/Networking/NetworkManager.cs new file mode 100644 index 0000000000..e336973377 --- /dev/null +++ b/Emby.Common.Implementations/Networking/NetworkManager.cs @@ -0,0 +1,506 @@ +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Threading.Tasks; +using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.IO; +using MediaBrowser.Common.Net; + +namespace Emby.Common.Implementations.Networking +{ + public class NetworkManager : INetworkManager + { + protected ILogger Logger { get; private set; } + private DateTime _lastRefresh; + + public NetworkManager(ILogger logger) + { + Logger = logger; + } + + private List _localIpAddresses; + private readonly object _localIpAddressSyncLock = new object(); + + public IEnumerable GetLocalIpAddresses() + { + const int cacheMinutes = 5; + + lock (_localIpAddressSyncLock) + { + var forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes; + + if (_localIpAddresses == null || forceRefresh) + { + var addresses = GetLocalIpAddressesInternal().Select(ToIpAddressInfo).ToList(); + + _localIpAddresses = addresses; + _lastRefresh = DateTime.UtcNow; + + return addresses; + } + } + + return _localIpAddresses; + } + + private IEnumerable GetLocalIpAddressesInternal() + { + var list = GetIPsDefault() + .ToList(); + + if (list.Count == 0) + { + list.AddRange(GetLocalIpAddressesFallback().Result); + } + + return list.Where(FilterIpAddress).DistinctBy(i => i.ToString()); + } + + private bool FilterIpAddress(IPAddress address) + { + var addressString = address.ToString(); + + if (addressString.StartsWith("169.", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + + public bool IsInPrivateAddressSpace(string endpoint) + { + if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Handle ipv4 mapped to ipv6 + endpoint = endpoint.Replace("::ffff:", string.Empty); + + // Private address space: + // http://en.wikipedia.org/wiki/Private_network + + if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase)) + { + return Is172AddressPrivate(endpoint); + } + + return + + endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) || + endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || + endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || + endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase) || + endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase); + } + + private bool Is172AddressPrivate(string endpoint) + { + for (var i = 16; i <= 31; i++) + { + if (endpoint.StartsWith("172." + i.ToString(CultureInfo.InvariantCulture) + ".", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + public bool IsInLocalNetwork(string endpoint) + { + return IsInLocalNetworkInternal(endpoint, true); + } + + public bool IsInLocalNetworkInternal(string endpoint, bool resolveHost) + { + if (string.IsNullOrWhiteSpace(endpoint)) + { + throw new ArgumentNullException("endpoint"); + } + + IPAddress address; + if (IPAddress.TryParse(endpoint, out address)) + { + var addressString = address.ToString(); + + int lengthMatch = 100; + if (address.AddressFamily == AddressFamily.InterNetwork) + { + lengthMatch = 4; + if (IsInPrivateAddressSpace(addressString)) + { + return true; + } + } + else if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + lengthMatch = 10; + if (IsInPrivateAddressSpace(endpoint)) + { + return true; + } + } + + // Should be even be doing this with ipv6? + if (addressString.Length >= lengthMatch) + { + var prefix = addressString.Substring(0, lengthMatch); + + if (GetLocalIpAddresses().Any(i => i.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) + { + return true; + } + } + } + else if (resolveHost) + { + Uri uri; + if (Uri.TryCreate(endpoint, UriKind.RelativeOrAbsolute, out uri)) + { + try + { + var host = uri.DnsSafeHost; + Logger.Debug("Resolving host {0}", host); + + address = GetIpAddresses(host).Result.FirstOrDefault(); + + if (address != null) + { + Logger.Debug("{0} resolved to {1}", host, address); + + return IsInLocalNetworkInternal(address.ToString(), false); + } + } + catch (InvalidOperationException) + { + // Can happen with reverse proxy or IIS url rewriting + } + catch (Exception ex) + { + Logger.ErrorException("Error resovling hostname", ex); + } + } + } + + return false; + } + + private Task GetIpAddresses(string hostName) + { + return Dns.GetHostAddressesAsync(hostName); + } + + private List GetIPsDefault() + { + NetworkInterface[] interfaces; + + try + { + var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown }; + + interfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(i => validStatuses.Contains(i.OperationalStatus)) + .ToArray(); + } + catch (Exception ex) + { + Logger.ErrorException("Error in GetAllNetworkInterfaces", ex); + return new List(); + } + + return interfaces.SelectMany(network => + { + + try + { + Logger.Debug("Querying interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus); + + var properties = network.GetIPProperties(); + + return properties.UnicastAddresses + .Where(i => i.IsDnsEligible) + .Select(i => i.Address) + .Where(i => i.AddressFamily == AddressFamily.InterNetwork) + .ToList(); + } + catch (Exception ex) + { + Logger.ErrorException("Error querying network interface", ex); + return new List(); + } + + }).DistinctBy(i => i.ToString()) + .ToList(); + } + + private async Task> GetLocalIpAddressesFallback() + { + var host = await Dns.GetHostEntryAsync(Dns.GetHostName()).ConfigureAwait(false); + + // Reverse them because the last one is usually the correct one + // It's not fool-proof so ultimately the consumer will have to examine them and decide + return host.AddressList + .Where(i => i.AddressFamily == AddressFamily.InterNetwork) + .Reverse(); + } + + /// + /// Gets a random port number that is currently available + /// + /// System.Int32. + public int GetRandomUnusedPort() + { + var listener = new TcpListener(IPAddress.Any, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } + + /// + /// Returns MAC Address from first Network Card in Computer + /// + /// [string] MAC Address + public string GetMacAddress() + { + return NetworkInterface.GetAllNetworkInterfaces() + .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .Select(i => BitConverter.ToString(i.GetPhysicalAddress().GetAddressBytes())) + .FirstOrDefault(); + } + + /// + /// Parses the specified endpointstring. + /// + /// The endpointstring. + /// IPEndPoint. + public IPEndPoint Parse(string endpointstring) + { + return Parse(endpointstring, -1).Result; + } + + /// + /// Parses the specified endpointstring. + /// + /// The endpointstring. + /// The defaultport. + /// IPEndPoint. + /// Endpoint descriptor may not be empty. + /// + private static async Task Parse(string endpointstring, int defaultport) + { + if (String.IsNullOrEmpty(endpointstring) + || endpointstring.Trim().Length == 0) + { + throw new ArgumentException("Endpoint descriptor may not be empty."); + } + + if (defaultport != -1 && + (defaultport < IPEndPoint.MinPort + || defaultport > IPEndPoint.MaxPort)) + { + throw new ArgumentException(String.Format("Invalid default port '{0}'", defaultport)); + } + + string[] values = endpointstring.Split(new char[] { ':' }); + IPAddress ipaddy; + int port = -1; + + //check if we have an IPv6 or ports + if (values.Length <= 2) // ipv4 or hostname + { + port = values.Length == 1 ? defaultport : GetPort(values[1]); + + //try to use the address as IPv4, otherwise get hostname + if (!IPAddress.TryParse(values[0], out ipaddy)) + ipaddy = await GetIPfromHost(values[0]).ConfigureAwait(false); + } + else if (values.Length > 2) //ipv6 + { + //could [a:b:c]:d + if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]")) + { + string ipaddressstring = String.Join(":", values.Take(values.Length - 1).ToArray()); + ipaddy = IPAddress.Parse(ipaddressstring); + port = GetPort(values[values.Length - 1]); + } + else //[a:b:c] or a:b:c + { + ipaddy = IPAddress.Parse(endpointstring); + port = defaultport; + } + } + else + { + throw new FormatException(String.Format("Invalid endpoint ipaddress '{0}'", endpointstring)); + } + + if (port == -1) + throw new ArgumentException(String.Format("No port specified: '{0}'", endpointstring)); + + return new IPEndPoint(ipaddy, port); + } + + protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// + /// Gets the port. + /// + /// The p. + /// System.Int32. + /// + private static int GetPort(string p) + { + int port; + + if (!Int32.TryParse(p, out port) + || port < IPEndPoint.MinPort + || port > IPEndPoint.MaxPort) + { + throw new FormatException(String.Format("Invalid end point port '{0}'", p)); + } + + return port; + } + + /// + /// Gets the I pfrom host. + /// + /// The p. + /// IPAddress. + /// + private static async Task GetIPfromHost(string p) + { + var hosts = await Dns.GetHostAddressesAsync(p).ConfigureAwait(false); + + if (hosts == null || hosts.Length == 0) + throw new ArgumentException(String.Format("Host not found: {0}", p)); + + 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 = 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(); + } + + /// + /// Gets the network shares. + /// + /// The path. + /// IEnumerable{NetworkShare}. + public IEnumerable GetNetworkShares(string path) + { + return new List(); + } + + /// + /// Gets available devices within the domain + /// + /// PC's in the Domain + public IEnumerable GetNetworkDevices() + { + return new List(); + } + } +} diff --git a/Emby.Server.Core/FFMpeg/FFMpegLoader.cs b/Emby.Server.Core/FFMpeg/FFMpegLoader.cs new file mode 100644 index 0000000000..6b090102ab --- /dev/null +++ b/Emby.Server.Core/FFMpeg/FFMpegLoader.cs @@ -0,0 +1,240 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Core; +using Emby.Server.Core.FFMpeg; + +namespace Emby.Server.Core.FFMpeg +{ + public class FFMpegLoader + { + private readonly IHttpClient _httpClient; + private readonly IApplicationPaths _appPaths; + private readonly ILogger _logger; + private readonly IZipClient _zipClient; + private readonly IFileSystem _fileSystem; + private readonly FFMpegInstallInfo _ffmpegInstallInfo; + + public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo) + { + _logger = logger; + _appPaths = appPaths; + _httpClient = httpClient; + _zipClient = zipClient; + _fileSystem = fileSystem; + _ffmpegInstallInfo = ffmpegInstallInfo; + } + + public async Task GetFFMpegInfo(StartupOptions options, IProgress progress) + { + var customffMpegPath = options.GetOption("-ffmpeg"); + var customffProbePath = options.GetOption("-ffprobe"); + + if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath)) + { + return new FFMpegInfo + { + ProbePath = customffProbePath, + EncoderPath = customffMpegPath, + Version = "external" + }; + } + + var downloadInfo = _ffmpegInstallInfo; + + var version = downloadInfo.Version; + + if (string.Equals(version, "path", StringComparison.OrdinalIgnoreCase)) + { + return new FFMpegInfo + { + ProbePath = downloadInfo.FFProbeFilename, + EncoderPath = downloadInfo.FFMpegFilename, + Version = version + }; + } + + if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase)) + { + return new FFMpegInfo(); + } + + var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg"); + var versionedDirectoryPath = Path.Combine(rootEncoderPath, version); + + var info = new FFMpegInfo + { + ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename), + EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename), + Version = version + }; + + _fileSystem.CreateDirectory(versionedDirectoryPath); + + var excludeFromDeletions = new List { versionedDirectoryPath }; + + if (!_fileSystem.FileExists(info.ProbePath) || !_fileSystem.FileExists(info.EncoderPath)) + { + // ffmpeg not present. See if there's an older version we can start with + var existingVersion = GetExistingVersion(info, rootEncoderPath); + + // No older version. Need to download and block until complete + if (existingVersion == null) + { + var success = await DownloadFFMpeg(downloadInfo, versionedDirectoryPath, progress).ConfigureAwait(false); + if (!success) + { + return new FFMpegInfo(); + } + } + else + { + info = existingVersion; + versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath); + excludeFromDeletions.Add(versionedDirectoryPath); + } + } + + // Allow just one of these to be overridden, if desired. + if (!string.IsNullOrWhiteSpace(customffMpegPath)) + { + info.EncoderPath = customffMpegPath; + } + if (!string.IsNullOrWhiteSpace(customffProbePath)) + { + info.EncoderPath = customffProbePath; + } + + return info; + } + + private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath) + { + var encoderFilename = Path.GetFileName(info.EncoderPath); + var probeFilename = Path.GetFileName(info.ProbePath); + + foreach (var directory in Directory.EnumerateDirectories(rootEncoderPath, "*", SearchOption.TopDirectoryOnly) + .ToList()) + { + var allFiles = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories).ToList(); + + var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase)); + var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrWhiteSpace(encoder) && + !string.IsNullOrWhiteSpace(probe)) + { + return new FFMpegInfo + { + EncoderPath = encoder, + ProbePath = probe, + Version = Path.GetFileName(Path.GetDirectoryName(probe)) + }; + } + } + + return null; + } + + private async Task DownloadFFMpeg(FFMpegInstallInfo downloadinfo, string directory, IProgress progress) + { + foreach (var url in downloadinfo.DownloadUrls) + { + progress.Report(0); + + try + { + var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions + { + Url = url, + CancellationToken = CancellationToken.None, + Progress = progress + + }).ConfigureAwait(false); + + ExtractFFMpeg(downloadinfo, tempFile, directory); + return true; + } + catch (Exception ex) + { + _logger.ErrorException("Error downloading {0}", ex, url); + } + } + return false; + } + + private void ExtractFFMpeg(FFMpegInstallInfo downloadinfo, string tempFile, string targetFolder) + { + _logger.Info("Extracting ffmpeg from {0}", tempFile); + + var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString()); + + _fileSystem.CreateDirectory(tempFolder); + + try + { + ExtractArchive(downloadinfo, tempFile, tempFolder); + + var files = Directory.EnumerateFiles(tempFolder, "*", SearchOption.AllDirectories) + .ToList(); + + foreach (var file in files.Where(i => + { + var filename = Path.GetFileName(i); + + return + string.Equals(filename, downloadinfo.FFProbeFilename, StringComparison.OrdinalIgnoreCase) || + string.Equals(filename, downloadinfo.FFMpegFilename, StringComparison.OrdinalIgnoreCase); + })) + { + var targetFile = Path.Combine(targetFolder, Path.GetFileName(file)); + _fileSystem.CopyFile(file, targetFile, true); + SetFilePermissions(targetFile); + } + } + finally + { + DeleteFile(tempFile); + } + } + + private void SetFilePermissions(string path) + { + _fileSystem.SetExecutable(path); + } + + private void ExtractArchive(FFMpegInstallInfo downloadinfo, string archivePath, string targetPath) + { + _logger.Info("Extracting {0} to {1}", archivePath, targetPath); + + if (string.Equals(downloadinfo.ArchiveType, "7z", StringComparison.OrdinalIgnoreCase)) + { + _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); + } + else if (string.Equals(downloadinfo.ArchiveType, "gz", StringComparison.OrdinalIgnoreCase)) + { + _zipClient.ExtractAllFromTar(archivePath, targetPath, true); + } + } + + private void DeleteFile(string path) + { + try + { + _fileSystem.DeleteFile(path); + } + catch (IOException ex) + { + _logger.ErrorException("Error deleting temp file {0}", ex, path); + } + } + + } +} diff --git a/Emby.Server.Core/Localization/TextLocalizer.cs b/Emby.Server.Core/Localization/TextLocalizer.cs new file mode 100644 index 0000000000..016d596590 --- /dev/null +++ b/Emby.Server.Core/Localization/TextLocalizer.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using Emby.Server.Implementations.Localization; + +namespace Emby.Server.Core.Localization +{ + public class TextLocalizer : ITextLocalizer + { + public string RemoveDiacritics(string text) + { + return String.Concat( + text.Normalize(NormalizationForm.FormD) + .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != + UnicodeCategory.NonSpacingMark) + ).Normalize(NormalizationForm.FormC); + } + + public string NormalizeFormKD(string text) + { + return text.Normalize(NormalizationForm.FormKD); + } + } +} diff --git a/Emby.Server.Core/Security/AuthenticationRepository.cs b/Emby.Server.Core/Security/AuthenticationRepository.cs new file mode 100644 index 0000000000..eaf91c710e --- /dev/null +++ b/Emby.Server.Core/Security/AuthenticationRepository.cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Core.Data; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; + +namespace Emby.Server.Core.Security +{ + public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository + { + private readonly IServerApplicationPaths _appPaths; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public AuthenticationRepository(ILogManager logManager, IServerApplicationPaths appPaths, IDbConnector connector) + : base(logManager, connector) + { + _appPaths = appPaths; + DbFilePath = Path.Combine(appPaths.DataPath, "authentication.db"); + } + + public async Task Initialize() + { + using (var connection = await CreateConnection().ConfigureAwait(false)) + { + string[] queries = { + + "create table if not exists AccessTokens (Id GUID PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT, AppName TEXT, AppVersion TEXT, DeviceName TEXT, UserId TEXT, IsActive BIT, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)", + "create index if not exists idx_AccessTokens on AccessTokens(Id)" + }; + + connection.RunQueries(queries, Logger); + + connection.AddColumn(Logger, "AccessTokens", "AppVersion", "TEXT"); + } + } + + public Task Create(AuthenticationInfo info, CancellationToken cancellationToken) + { + info.Id = Guid.NewGuid().ToString("N"); + + return Update(info, cancellationToken); + } + + public async Task Update(AuthenticationInfo info, CancellationToken cancellationToken) + { + if (info == null) + { + throw new ArgumentNullException("info"); + } + + cancellationToken.ThrowIfCancellationRequested(); + + using (var connection = await CreateConnection().ConfigureAwait(false)) + { + using (var saveInfoCommand = connection.CreateCommand()) + { + saveInfoCommand.CommandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)"; + + saveInfoCommand.Parameters.Add(saveInfoCommand, "@Id"); + saveInfoCommand.Parameters.Add(saveInfoCommand, "@AccessToken"); + saveInfoCommand.Parameters.Add(saveInfoCommand, "@DeviceId"); + saveInfoCommand.Parameters.Add(saveInfoCommand, "@AppName"); + saveInfoCommand.Parameters.Add(saveInfoCommand, "@AppVersion"); + saveInfoCommand.Parameters.Add(saveInfoCommand, "@DeviceName"); + saveInfoCommand.Parameters.Add(saveInfoCommand, "@UserId"); + saveInfoCommand.Parameters.Add(saveInfoCommand, "@IsActive"); + saveInfoCommand.Parameters.Add(saveInfoCommand, "@DateCreated"); + saveInfoCommand.Parameters.Add(saveInfoCommand, "@DateRevoked"); + + IDbTransaction transaction = null; + + try + { + transaction = connection.BeginTransaction(); + + var index = 0; + + saveInfoCommand.GetParameter(index++).Value = new Guid(info.Id); + saveInfoCommand.GetParameter(index++).Value = info.AccessToken; + saveInfoCommand.GetParameter(index++).Value = info.DeviceId; + saveInfoCommand.GetParameter(index++).Value = info.AppName; + saveInfoCommand.GetParameter(index++).Value = info.AppVersion; + saveInfoCommand.GetParameter(index++).Value = info.DeviceName; + saveInfoCommand.GetParameter(index++).Value = info.UserId; + saveInfoCommand.GetParameter(index++).Value = info.IsActive; + saveInfoCommand.GetParameter(index++).Value = info.DateCreated; + saveInfoCommand.GetParameter(index++).Value = info.DateRevoked; + + saveInfoCommand.Transaction = transaction; + + saveInfoCommand.ExecuteNonQuery(); + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + catch (Exception e) + { + Logger.ErrorException("Failed to save record:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + } + } + } + } + + private const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; + + public QueryResult Get(AuthenticationInfoQuery query) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + using (var connection = CreateConnection(true).Result) + { + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = BaseSelectText; + + var whereClauses = new List(); + + var startIndex = query.StartIndex ?? 0; + + if (!string.IsNullOrWhiteSpace(query.AccessToken)) + { + whereClauses.Add("AccessToken=@AccessToken"); + cmd.Parameters.Add(cmd, "@AccessToken", DbType.String).Value = query.AccessToken; + } + + if (!string.IsNullOrWhiteSpace(query.UserId)) + { + whereClauses.Add("UserId=@UserId"); + cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId; + } + + if (!string.IsNullOrWhiteSpace(query.DeviceId)) + { + whereClauses.Add("DeviceId=@DeviceId"); + cmd.Parameters.Add(cmd, "@DeviceId", DbType.String).Value = query.DeviceId; + } + + if (query.IsActive.HasValue) + { + whereClauses.Add("IsActive=@IsActive"); + cmd.Parameters.Add(cmd, "@IsActive", DbType.Boolean).Value = query.IsActive.Value; + } + + if (query.HasUser.HasValue) + { + if (query.HasUser.Value) + { + whereClauses.Add("UserId not null"); + } + else + { + whereClauses.Add("UserId is null"); + } + } + + var whereTextWithoutPaging = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + if (startIndex > 0) + { + var pagingWhereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens {0} ORDER BY DateCreated LIMIT {1})", + pagingWhereText, + startIndex.ToString(_usCulture))); + } + + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); + + cmd.CommandText += whereText; + + cmd.CommandText += " ORDER BY DateCreated"; + + if (query.Limit.HasValue) + { + cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); + } + + cmd.CommandText += "; select count (Id) from AccessTokens" + whereTextWithoutPaging; + + var list = new List(); + var count = 0; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(Get(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } + } + + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + } + + public AuthenticationInfo Get(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } + + using (var connection = CreateConnection(true).Result) + { + var guid = new Guid(id); + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = BaseSelectText + " where Id=@Id"; + + cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) + { + if (reader.Read()) + { + return Get(reader); + } + } + } + + return null; + } + } + + private AuthenticationInfo Get(IDataReader reader) + { + var info = new AuthenticationInfo + { + Id = reader.GetGuid(0).ToString("N"), + AccessToken = reader.GetString(1) + }; + + if (!reader.IsDBNull(2)) + { + info.DeviceId = reader.GetString(2); + } + + if (!reader.IsDBNull(3)) + { + info.AppName = reader.GetString(3); + } + + if (!reader.IsDBNull(4)) + { + info.AppVersion = reader.GetString(4); + } + + if (!reader.IsDBNull(5)) + { + info.DeviceName = reader.GetString(5); + } + + if (!reader.IsDBNull(6)) + { + info.UserId = reader.GetString(6); + } + + info.IsActive = reader.GetBoolean(7); + info.DateCreated = reader.GetDateTime(8).ToUniversalTime(); + + if (!reader.IsDBNull(9)) + { + info.DateRevoked = reader.GetDateTime(9).ToUniversalTime(); + } + + return info; + } + } +} diff --git a/Emby.Server.Core/project.json b/Emby.Server.Core/project.json index e4e4741b13..8e120c8a11 100644 --- a/Emby.Server.Core/project.json +++ b/Emby.Server.Core/project.json @@ -28,6 +28,9 @@ }, "Emby.Server.Implementations": { "target": "project" + }, + "MediaBrowser.Server.Implementations": { + "target": "project" } } }, @@ -36,6 +39,7 @@ "dependencies": { "NETStandard.Library": "1.6.0", "System.AppDomain": "2.0.11", + "System.Globalization.Extensions": "4.0.1", "MediaBrowser.Model": { "target": "project" }, @@ -53,6 +57,9 @@ }, "Emby.Server.Implementations": { "target": "project" + }, + "MediaBrowser.Server.Implementations": { + "target": "project" } } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 438edf212f..f65b8ac4aa 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -92,6 +92,7 @@ + @@ -270,6 +271,10 @@ {442b5058-dcaf-4263-bb6a-f21e31120a1b} MediaBrowser.Providers + + {2e781478-814d-4a48-9d80-bff206441a65} + MediaBrowser.Server.Implementations + ..\ThirdParty\ServiceStack\ServiceStack.dll diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs new file mode 100644 index 0000000000..0b1391ae02 --- /dev/null +++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; + +namespace Emby.Server.Implementations.IO +{ + public class MbLinkShortcutHandler : IShortcutHandler + { + private readonly IFileSystem _fileSystem; + + public MbLinkShortcutHandler(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + public string Extension + { + get { return ".mblink"; } + } + + public string Resolve(string shortcutPath) + { + if (string.IsNullOrEmpty(shortcutPath)) + { + throw new ArgumentNullException("filenshortcutPathame"); + } + + if (string.Equals(Path.GetExtension(shortcutPath), ".mblink", StringComparison.OrdinalIgnoreCase)) + { + var path = _fileSystem.ReadAllText(shortcutPath); + + return _fileSystem.NormalizePath(path); + } + + return null; + } + + public void Create(string shortcutPath, string targetPath) + { + if (string.IsNullOrEmpty(shortcutPath)) + { + throw new ArgumentNullException("shortcutPath"); + } + + if (string.IsNullOrEmpty(targetPath)) + { + throw new ArgumentNullException("targetPath"); + } + + _fileSystem.WriteAllText(shortcutPath, targetPath); + } + } +} diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 779db0a828..5ac701f820 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -54,12 +54,5 @@ namespace MediaBrowser.Common.Net bool TryParseIpAddress(string ipAddress, out IpAddressInfo ipAddressInfo); Task GetHostAddressesAsync(string host); - - /// - /// Generates a self signed certificate at the locatation specified by . - /// - /// The path to generate the certificate. - /// The common name for the certificate. - void GenerateSelfSignedSslCertificate(string certificatePath, string hostname); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Devices/CameraUploadsFolder.cs b/MediaBrowser.Controller/Devices/CameraUploadsFolder.cs deleted file mode 100644 index 979a929ca7..0000000000 --- a/MediaBrowser.Controller/Devices/CameraUploadsFolder.cs +++ /dev/null @@ -1,67 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Entities; -using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Server.Implementations.Devices -{ - public class CameraUploadsFolder : BasePluginFolder, ISupportsUserSpecificView - { - public CameraUploadsFolder() - { - Name = "Camera Uploads"; - } - - public override bool IsVisible(User user) - { - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - return base.IsVisible(user) && HasChildren(); - } - - [IgnoreDataMember] - public override string CollectionType - { - get { return Model.Entities.CollectionType.Photos; } - } - - public override string GetClientTypeName() - { - return typeof(CollectionFolder).Name; - } - - private bool? _hasChildren; - private bool HasChildren() - { - if (!_hasChildren.HasValue) - { - _hasChildren = LibraryManager.GetItemIds(new InternalItemsQuery { ParentId = Id }).Count > 0; - } - - return _hasChildren.Value; - } - - protected override Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) - { - _hasChildren = null; - return base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService); - } - - [IgnoreDataMember] - public bool EnableUserSpecificView - { - get { return true; } - } - } -} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index d867b15123..518daa6d75 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -77,7 +77,6 @@ - @@ -218,7 +217,6 @@ - diff --git a/MediaBrowser.Controller/Playlists/ManualPlaylistsFolder.cs b/MediaBrowser.Controller/Playlists/ManualPlaylistsFolder.cs deleted file mode 100644 index 07773d846b..0000000000 --- a/MediaBrowser.Controller/Playlists/ManualPlaylistsFolder.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Playlists; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Server.Implementations.Playlists -{ - public class PlaylistsFolder : BasePluginFolder - { - public PlaylistsFolder() - { - Name = "Playlists"; - } - - public override bool IsVisible(User user) - { - return base.IsVisible(user) && GetChildren(user, true).Any(); - } - - protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) - { - return base.GetEligibleChildrenForRecursiveChildren(user).OfType(); - } - - [IgnoreDataMember] - public override bool IsHidden - { - get - { - return true; - } - } - - [IgnoreDataMember] - public override string CollectionType - { - get { return MediaBrowser.Model.Entities.CollectionType.Playlists; } - } - - protected override Task> GetItemsInternal(InternalItemsQuery query) - { - query.Recursive = false; - return base.GetItemsInternal(query); - } - } -} - diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index d2bb355203..f219d92959 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -312,6 +312,8 @@ namespace MediaBrowser.Model.IO string GetFullPath(string path); List GetDrives(); + + void SetExecutable(string path); } public enum FileOpenMode diff --git a/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs new file mode 100644 index 0000000000..979a929ca7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs @@ -0,0 +1,67 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Server.Implementations.Devices +{ + public class CameraUploadsFolder : BasePluginFolder, ISupportsUserSpecificView + { + public CameraUploadsFolder() + { + Name = "Camera Uploads"; + } + + public override bool IsVisible(User user) + { + if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + return base.IsVisible(user) && HasChildren(); + } + + [IgnoreDataMember] + public override string CollectionType + { + get { return Model.Entities.CollectionType.Photos; } + } + + public override string GetClientTypeName() + { + return typeof(CollectionFolder).Name; + } + + private bool? _hasChildren; + private bool HasChildren() + { + if (!_hasChildren.HasValue) + { + _hasChildren = LibraryManager.GetItemIds(new InternalItemsQuery { ParentId = Id }).Count > 0; + } + + return _hasChildren.Value; + } + + protected override Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) + { + _hasChildren = null; + return base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService); + } + + [IgnoreDataMember] + public bool EnableUserSpecificView + { + get { return true; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 16c45f2e51..3bfbff6e67 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -44,13 +44,11 @@ Properties\SharedVersion.cs + + - - {e383961b-9356-4d5d-8233-9a1079d03055} - Emby.Server.Implementations - {9142EEFA-7570-41E1-BFCC-468BB571AF2F} MediaBrowser.Common diff --git a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs new file mode 100644 index 0000000000..07773d846b --- /dev/null +++ b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Playlists; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Server.Implementations.Playlists +{ + public class PlaylistsFolder : BasePluginFolder + { + public PlaylistsFolder() + { + Name = "Playlists"; + } + + public override bool IsVisible(User user) + { + return base.IsVisible(user) && GetChildren(user, true).Any(); + } + + protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) + { + return base.GetEligibleChildrenForRecursiveChildren(user).OfType(); + } + + [IgnoreDataMember] + public override bool IsHidden + { + get + { + return true; + } + } + + [IgnoreDataMember] + public override string CollectionType + { + get { return MediaBrowser.Model.Entities.CollectionType.Playlists; } + } + + protected override Task> GetItemsInternal(InternalItemsQuery query) + { + query.Recursive = false; + return base.GetItemsInternal(query); + } + } +} + diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index d59725d1d1..b62d99a136 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -77,9 +77,6 @@ True - - ..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll - ..\ThirdParty\MediaBrowser.IsoMounting.Linux\MediaBrowser.IsoMounting.Linux.dll @@ -103,12 +100,17 @@ + + + {e383961b-9356-4d5d-8233-9a1079d03055} + Emby.Server.Implementations + {b90ab8f2-1bff-4568-a3fd-2a338a435a75} MediaBrowser.Server.Startup.Common diff --git a/MediaBrowser.Server.Mono/Native/MonoApp.cs b/MediaBrowser.Server.Mono/Native/MonoApp.cs index 59c95316c1..6e7a677aee 100644 --- a/MediaBrowser.Server.Mono/Native/MonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/MonoApp.cs @@ -7,12 +7,11 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; +using Emby.Common.Implementations.Networking; using Emby.Server.Core; using Emby.Server.Core.Data; using Emby.Server.Core.FFMpeg; using MediaBrowser.Model.System; -using MediaBrowser.Server.Startup.Common.FFMpeg; -using MediaBrowser.Server.Startup.Common.Networking; using OperatingSystem = MediaBrowser.Server.Startup.Common.OperatingSystem; namespace MediaBrowser.Server.Mono.Native diff --git a/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs b/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs new file mode 100644 index 0000000000..9639566947 --- /dev/null +++ b/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs @@ -0,0 +1,21 @@ +using Emby.Common.Implementations.IO; +using MediaBrowser.Model.Logging; +using Mono.Unix.Native; + +namespace MediaBrowser.Server.Mono.Native +{ + public class MonoFileSystem : ManagedFileSystem + { + public MonoFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars) : base(logger, supportsAsyncFileStreams, enableManagedInvalidFileNameChars) + { + } + + public override void SetExecutable(string path) + { + // Linux: File permission to 666, and user's execute bit + Logger.Info("Syscall.chmod {0} FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH", path); + + Syscall.chmod(path, FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH); + } + } +} diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index d453596278..7d687bb545 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -15,6 +15,7 @@ using System.Threading.Tasks; using Emby.Common.Implementations.IO; using Emby.Common.Implementations.Logging; using Emby.Server.Core; +using Emby.Server.Implementations.IO; namespace MediaBrowser.Server.Mono { @@ -76,7 +77,7 @@ namespace MediaBrowser.Server.Mono // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); - var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), false, false); + var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), false, false); fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); var nativeApp = new MonoApp(options, logManager.GetLogger("App")); diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 6d7ec699d9..0646c71fda 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -48,9 +48,6 @@ using MediaBrowser.Model.Updates; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Subtitles; -using MediaBrowser.Server.Implementations; -using MediaBrowser.Server.Implementations.Devices; -using MediaBrowser.Server.Startup.Common.FFMpeg; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using System; @@ -69,6 +66,7 @@ using Emby.Common.Implementations; using Emby.Common.Implementations.Archiving; using Emby.Common.Implementations.Networking; using Emby.Common.Implementations.Reflection; +using Emby.Common.Implementations.Security; using Emby.Common.Implementations.Serialization; using Emby.Common.Implementations.TextEncoding; using Emby.Common.Implementations.Updates; @@ -93,8 +91,11 @@ using Emby.Server.Core.Activity; using Emby.Server.Core.Configuration; using Emby.Server.Core.Data; using Emby.Server.Core.Devices; +using Emby.Server.Core.FFMpeg; +using Emby.Server.Core.Localization; using Emby.Server.Core.Migrations; using Emby.Server.Core.Notifications; +using Emby.Server.Core.Security; using Emby.Server.Core.Social; using Emby.Server.Core.Sync; using Emby.Server.Implementations.Activity; @@ -134,7 +135,6 @@ using MediaBrowser.Model.Social; using MediaBrowser.Model.Text; using MediaBrowser.Model.Xml; using MediaBrowser.Server.Startup.Common.IO; -using MediaBrowser.Server.Startup.Common.Security; using OpenSubtitlesHandler; using ServiceStack; using SocketHttpListener.Primitives; @@ -817,8 +817,8 @@ namespace MediaBrowser.Server.Startup.Common string encoderPath = null; string probePath = null; - var info = await new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, NativeApp.Environment, NativeApp.GetFfmpegInstallInfo()) - .GetFFMpegInfo(NativeApp.Environment, _startupOptions, progress).ConfigureAwait(false); + var info = await new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, NativeApp.GetFfmpegInstallInfo()) + .GetFFMpegInfo(_startupOptions, progress).ConfigureAwait(false); encoderPath = info.EncoderPath; probePath = info.ProbePath; @@ -1079,7 +1079,7 @@ namespace MediaBrowser.Server.Startup.Common try { - NetworkManager.GenerateSelfSignedSslCertificate(certPath, certHost); + CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, Logger); } catch (Exception ex) { @@ -1213,12 +1213,12 @@ namespace MediaBrowser.Server.Startup.Common // Common implementations list.Add(typeof(TaskManager).Assembly); - // MediaBrowser.Server implementations - list.Add(typeof(ServerApplicationPaths).Assembly); - // Emby.Server implementations list.Add(typeof(InstallationManager).Assembly); + // Emby.Server.Core + list.Add(typeof(ServerApplicationPaths).Assembly); + // MediaEncoding list.Add(typeof(MediaEncoder).Assembly); diff --git a/MediaBrowser.Server.Startup.Common/Cryptography/CertificateGenerator.cs b/MediaBrowser.Server.Startup.Common/Cryptography/CertificateGenerator.cs index f36b14cae2..f5a337505b 100644 --- a/MediaBrowser.Server.Startup.Common/Cryptography/CertificateGenerator.cs +++ b/MediaBrowser.Server.Startup.Common/Cryptography/CertificateGenerator.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography; namespace Emby.Common.Implementations.Security { - internal class CertificateGenerator + public class CertificateGenerator { private const string MonoTestRootAgency = "v/4nALBxCE+9JgEC0LnDUvKh6e96PwTpN4Rj+vWnqKT7IAp1iK/JjuqvAg6DQ2vTfv0dTlqffmHH51OyioprcT5nzxcSTsZb/9jcHScG0s3/FRIWnXeLk/fgm7mSYhjUaHNI0m1/NTTktipicjKxo71hGIg9qucCWnDum+Krh/k=AQAB

9jbKxMXEruW2CfZrzhxtull4O8P47+mNsEL+9gf9QsRO1jJ77C+jmzfU6zbzjf8+ViK+q62tCMdC1ZzulwdpXQ==

x5+p198l1PkK0Ga2mRh0SIYSykENpY2aLXoyZD/iUpKYAvATm0/wvKNrE4dKJyPCA+y3hfTdgVag+SP9avvDTQ==ISSjCvXsUfbOGG05eddN1gXxL2pj+jegQRfjpk7RAsnWKvNExzhqd5x+ZuNQyc6QH5wxun54inP4RTUI0P/IaQ==R815VQmR3RIbPqzDXzv5j6CSH6fYlcTiQRtkBsUnzhWmkd/y3XmamO+a8zJFjOCCx9CcjpVuGziivBqi65lVPQ==iYiu0KwMWI/dyqN3RJYUzuuLj02/oTD1pYpwo2rvNCXU1Q5VscOeu2DpNg1gWqI+1RrRCsEoaTNzXB1xtKNlSw==nIfh1LYF8fjRBgMdAH/zt9UKHWiaCnc+jXzq5tkR8HVSKTVdzitD8bl1JgAfFQD8VjSXiCJqluexy/B5SGrCXQ49c78NIQj0hD+J13Y8/E0fUbW1QYbhj6Ff7oHyhaYe1WOQfkp2t/h+llHOdt1HRf7bt7dUknYp7m8bQKGxoYE=
"; diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs deleted file mode 100644 index 08e6b435b5..0000000000 --- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs +++ /dev/null @@ -1,249 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using Mono.Unix.Native; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Core; -using Emby.Server.Core.FFMpeg; - -namespace MediaBrowser.Server.Startup.Common.FFMpeg -{ - public class FFMpegLoader - { - private readonly IHttpClient _httpClient; - private readonly IApplicationPaths _appPaths; - private readonly ILogger _logger; - private readonly IZipClient _zipClient; - private readonly IFileSystem _fileSystem; - private readonly NativeEnvironment _environment; - private readonly FFMpegInstallInfo _ffmpegInstallInfo; - - public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment, FFMpegInstallInfo ffmpegInstallInfo) - { - _logger = logger; - _appPaths = appPaths; - _httpClient = httpClient; - _zipClient = zipClient; - _fileSystem = fileSystem; - _environment = environment; - _ffmpegInstallInfo = ffmpegInstallInfo; - } - - public async Task GetFFMpegInfo(NativeEnvironment environment, StartupOptions options, IProgress progress) - { - var customffMpegPath = options.GetOption("-ffmpeg"); - var customffProbePath = options.GetOption("-ffprobe"); - - if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath)) - { - return new FFMpegInfo - { - ProbePath = customffProbePath, - EncoderPath = customffMpegPath, - Version = "external" - }; - } - - var downloadInfo = _ffmpegInstallInfo; - - var version = downloadInfo.Version; - - if (string.Equals(version, "path", StringComparison.OrdinalIgnoreCase)) - { - return new FFMpegInfo - { - ProbePath = downloadInfo.FFProbeFilename, - EncoderPath = downloadInfo.FFMpegFilename, - Version = version - }; - } - - if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase)) - { - return new FFMpegInfo(); - } - - var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg"); - var versionedDirectoryPath = Path.Combine(rootEncoderPath, version); - - var info = new FFMpegInfo - { - ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename), - EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename), - Version = version - }; - - _fileSystem.CreateDirectory(versionedDirectoryPath); - - var excludeFromDeletions = new List { versionedDirectoryPath }; - - if (!_fileSystem.FileExists(info.ProbePath) || !_fileSystem.FileExists(info.EncoderPath)) - { - // ffmpeg not present. See if there's an older version we can start with - var existingVersion = GetExistingVersion(info, rootEncoderPath); - - // No older version. Need to download and block until complete - if (existingVersion == null) - { - var success = await DownloadFFMpeg(downloadInfo, versionedDirectoryPath, progress).ConfigureAwait(false); - if (!success) - { - return new FFMpegInfo(); - } - } - else - { - info = existingVersion; - versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath); - excludeFromDeletions.Add(versionedDirectoryPath); - } - } - - // Allow just one of these to be overridden, if desired. - if (!string.IsNullOrWhiteSpace(customffMpegPath)) - { - info.EncoderPath = customffMpegPath; - } - if (!string.IsNullOrWhiteSpace(customffProbePath)) - { - info.EncoderPath = customffProbePath; - } - - return info; - } - - private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath) - { - var encoderFilename = Path.GetFileName(info.EncoderPath); - var probeFilename = Path.GetFileName(info.ProbePath); - - foreach (var directory in Directory.EnumerateDirectories(rootEncoderPath, "*", SearchOption.TopDirectoryOnly) - .ToList()) - { - var allFiles = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories).ToList(); - - var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase)); - var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase)); - - if (!string.IsNullOrWhiteSpace(encoder) && - !string.IsNullOrWhiteSpace(probe)) - { - return new FFMpegInfo - { - EncoderPath = encoder, - ProbePath = probe, - Version = Path.GetFileName(Path.GetDirectoryName(probe)) - }; - } - } - - return null; - } - - private async Task DownloadFFMpeg(FFMpegInstallInfo downloadinfo, string directory, IProgress progress) - { - foreach (var url in downloadinfo.DownloadUrls) - { - progress.Report(0); - - try - { - var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = url, - CancellationToken = CancellationToken.None, - Progress = progress - - }).ConfigureAwait(false); - - ExtractFFMpeg(downloadinfo, tempFile, directory); - return true; - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading {0}", ex, url); - } - } - return false; - } - - private void ExtractFFMpeg(FFMpegInstallInfo downloadinfo, string tempFile, string targetFolder) - { - _logger.Info("Extracting ffmpeg from {0}", tempFile); - - var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString()); - - _fileSystem.CreateDirectory(tempFolder); - - try - { - ExtractArchive(downloadinfo, tempFile, tempFolder); - - var files = Directory.EnumerateFiles(tempFolder, "*", SearchOption.AllDirectories) - .ToList(); - - foreach (var file in files.Where(i => - { - var filename = Path.GetFileName(i); - - return - string.Equals(filename, downloadinfo.FFProbeFilename, StringComparison.OrdinalIgnoreCase) || - string.Equals(filename, downloadinfo.FFMpegFilename, StringComparison.OrdinalIgnoreCase); - })) - { - var targetFile = Path.Combine(targetFolder, Path.GetFileName(file)); - _fileSystem.CopyFile(file, targetFile, true); - SetFilePermissions(targetFile); - } - } - finally - { - DeleteFile(tempFile); - } - } - - private void SetFilePermissions(string path) - { - // Linux: File permission to 666, and user's execute bit - if (_environment.OperatingSystem == OperatingSystem.Bsd || _environment.OperatingSystem == OperatingSystem.Linux || _environment.OperatingSystem == OperatingSystem.Osx) - { - _logger.Info("Syscall.chmod {0} FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH", path); - - Syscall.chmod(path, FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH); - } - } - - private void ExtractArchive(FFMpegInstallInfo downloadinfo, string archivePath, string targetPath) - { - _logger.Info("Extracting {0} to {1}", archivePath, targetPath); - - if (string.Equals(downloadinfo.ArchiveType, "7z", StringComparison.OrdinalIgnoreCase)) - { - _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); - } - else if (string.Equals(downloadinfo.ArchiveType, "gz", StringComparison.OrdinalIgnoreCase)) - { - _zipClient.ExtractAllFromTar(archivePath, targetPath, true); - } - } - - private void DeleteFile(string path) - { - try - { - _fileSystem.DeleteFile(path); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting temp file {0}", ex, path); - } - } - - } -} diff --git a/MediaBrowser.Server.Startup.Common/MbLinkShortcutHandler.cs b/MediaBrowser.Server.Startup.Common/MbLinkShortcutHandler.cs deleted file mode 100644 index 26318365b6..0000000000 --- a/MediaBrowser.Server.Startup.Common/MbLinkShortcutHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.IO; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.Server.Startup.Common -{ - public class MbLinkShortcutHandler : IShortcutHandler - { - private readonly IFileSystem _fileSystem; - - public MbLinkShortcutHandler(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - } - - public string Extension - { - get { return ".mblink"; } - } - - public string Resolve(string shortcutPath) - { - if (string.IsNullOrEmpty(shortcutPath)) - { - throw new ArgumentNullException("filenshortcutPathame"); - } - - if (string.Equals(Path.GetExtension(shortcutPath), ".mblink", StringComparison.OrdinalIgnoreCase)) - { - var path = _fileSystem.ReadAllText(shortcutPath); - - return _fileSystem.NormalizePath(path); - } - - return null; - } - - public void Create(string shortcutPath, string targetPath) - { - if (string.IsNullOrEmpty(shortcutPath)) - { - throw new ArgumentNullException("shortcutPath"); - } - - if (string.IsNullOrEmpty(targetPath)) - { - throw new ArgumentNullException("targetPath"); - } - - File.WriteAllText(shortcutPath, targetPath); - } - } -} diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index 79c1356de3..4bbd86060e 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj @@ -113,7 +113,6 @@ - @@ -140,13 +139,9 @@ - - - -
diff --git a/MediaBrowser.Server.Startup.Common/Networking/NetworkManager.cs b/MediaBrowser.Server.Startup.Common/Networking/NetworkManager.cs deleted file mode 100644 index bd260bf873..0000000000 --- a/MediaBrowser.Server.Startup.Common/Networking/NetworkManager.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using Emby.Common.Implementations.Networking; -using Emby.Common.Implementations.Security; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; - -namespace MediaBrowser.Server.Startup.Common.Networking -{ - /// - /// Class NetUtils - /// - public class NetworkManager : BaseNetworkManager, INetworkManager - { - public NetworkManager(ILogger logger) - : base(logger) - { - } - - /// - /// Gets the network shares. - /// - /// The path. - /// IEnumerable{NetworkShare}. - public IEnumerable GetNetworkShares(string path) - { - return new List(); - } - - /// - /// Gets available devices within the domain - /// - /// PC's in the Domain - public IEnumerable GetNetworkDevices() - { - return new List(); - } - - /// - /// Generates a self signed certificate at the locatation specified by . - /// - /// The path to generate the certificate. - /// The common name for the certificate. - public void GenerateSelfSignedSslCertificate(string certificatePath, string hostname) - { - CertificateGenerator.CreateSelfSignCertificatePfx(certificatePath, hostname, Logger); - } - } -} diff --git a/MediaBrowser.Server.Startup.Common/Security/AuthenticationRepository.cs b/MediaBrowser.Server.Startup.Common/Security/AuthenticationRepository.cs deleted file mode 100644 index 0cf9f0879a..0000000000 --- a/MediaBrowser.Server.Startup.Common/Security/AuthenticationRepository.cs +++ /dev/null @@ -1,317 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Globalization; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Core.Data; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Startup.Common.Security -{ - public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository - { - private readonly IServerApplicationPaths _appPaths; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public AuthenticationRepository(ILogManager logManager, IServerApplicationPaths appPaths, IDbConnector connector) - : base(logManager, connector) - { - _appPaths = appPaths; - DbFilePath = Path.Combine(appPaths.DataPath, "authentication.db"); - } - - public async Task Initialize() - { - using (var connection = await CreateConnection().ConfigureAwait(false)) - { - string[] queries = { - - "create table if not exists AccessTokens (Id GUID PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT, AppName TEXT, AppVersion TEXT, DeviceName TEXT, UserId TEXT, IsActive BIT, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)", - "create index if not exists idx_AccessTokens on AccessTokens(Id)" - }; - - connection.RunQueries(queries, Logger); - - connection.AddColumn(Logger, "AccessTokens", "AppVersion", "TEXT"); - } - } - - public Task Create(AuthenticationInfo info, CancellationToken cancellationToken) - { - info.Id = Guid.NewGuid().ToString("N"); - - return Update(info, cancellationToken); - } - - public async Task Update(AuthenticationInfo info, CancellationToken cancellationToken) - { - if (info == null) - { - throw new ArgumentNullException("info"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = await CreateConnection().ConfigureAwait(false)) - { - using (var saveInfoCommand = connection.CreateCommand()) - { - saveInfoCommand.CommandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)"; - - saveInfoCommand.Parameters.Add(saveInfoCommand, "@Id"); - saveInfoCommand.Parameters.Add(saveInfoCommand, "@AccessToken"); - saveInfoCommand.Parameters.Add(saveInfoCommand, "@DeviceId"); - saveInfoCommand.Parameters.Add(saveInfoCommand, "@AppName"); - saveInfoCommand.Parameters.Add(saveInfoCommand, "@AppVersion"); - saveInfoCommand.Parameters.Add(saveInfoCommand, "@DeviceName"); - saveInfoCommand.Parameters.Add(saveInfoCommand, "@UserId"); - saveInfoCommand.Parameters.Add(saveInfoCommand, "@IsActive"); - saveInfoCommand.Parameters.Add(saveInfoCommand, "@DateCreated"); - saveInfoCommand.Parameters.Add(saveInfoCommand, "@DateRevoked"); - - IDbTransaction transaction = null; - - try - { - transaction = connection.BeginTransaction(); - - var index = 0; - - saveInfoCommand.GetParameter(index++).Value = new Guid(info.Id); - saveInfoCommand.GetParameter(index++).Value = info.AccessToken; - saveInfoCommand.GetParameter(index++).Value = info.DeviceId; - saveInfoCommand.GetParameter(index++).Value = info.AppName; - saveInfoCommand.GetParameter(index++).Value = info.AppVersion; - saveInfoCommand.GetParameter(index++).Value = info.DeviceName; - saveInfoCommand.GetParameter(index++).Value = info.UserId; - saveInfoCommand.GetParameter(index++).Value = info.IsActive; - saveInfoCommand.GetParameter(index++).Value = info.DateCreated; - saveInfoCommand.GetParameter(index++).Value = info.DateRevoked; - - saveInfoCommand.Transaction = transaction; - - saveInfoCommand.ExecuteNonQuery(); - - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save record:", e); - - if (transaction != null) - { - transaction.Rollback(); - } - - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } - } - } - } - } - - private const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens"; - - public QueryResult Get(AuthenticationInfoQuery query) - { - if (query == null) - { - throw new ArgumentNullException("query"); - } - - using (var connection = CreateConnection(true).Result) - { - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = BaseSelectText; - - var whereClauses = new List(); - - var startIndex = query.StartIndex ?? 0; - - if (!string.IsNullOrWhiteSpace(query.AccessToken)) - { - whereClauses.Add("AccessToken=@AccessToken"); - cmd.Parameters.Add(cmd, "@AccessToken", DbType.String).Value = query.AccessToken; - } - - if (!string.IsNullOrWhiteSpace(query.UserId)) - { - whereClauses.Add("UserId=@UserId"); - cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId; - } - - if (!string.IsNullOrWhiteSpace(query.DeviceId)) - { - whereClauses.Add("DeviceId=@DeviceId"); - cmd.Parameters.Add(cmd, "@DeviceId", DbType.String).Value = query.DeviceId; - } - - if (query.IsActive.HasValue) - { - whereClauses.Add("IsActive=@IsActive"); - cmd.Parameters.Add(cmd, "@IsActive", DbType.Boolean).Value = query.IsActive.Value; - } - - if (query.HasUser.HasValue) - { - if (query.HasUser.Value) - { - whereClauses.Add("UserId not null"); - } - else - { - whereClauses.Add("UserId is null"); - } - } - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - if (startIndex > 0) - { - var pagingWhereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens {0} ORDER BY DateCreated LIMIT {1})", - pagingWhereText, - startIndex.ToString(_usCulture))); - } - - var whereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - cmd.CommandText += whereText; - - cmd.CommandText += " ORDER BY DateCreated"; - - if (query.Limit.HasValue) - { - cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); - } - - cmd.CommandText += "; select count (Id) from AccessTokens" + whereTextWithoutPaging; - - var list = new List(); - var count = 0; - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) - { - while (reader.Read()) - { - list.Add(Get(reader)); - } - - if (reader.NextResult() && reader.Read()) - { - count = reader.GetInt32(0); - } - } - - return new QueryResult() - { - Items = list.ToArray(), - TotalRecordCount = count - }; - } - } - } - - public AuthenticationInfo Get(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - using (var connection = CreateConnection(true).Result) - { - var guid = new Guid(id); - - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = BaseSelectText + " where Id=@Id"; - - cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid; - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) - { - if (reader.Read()) - { - return Get(reader); - } - } - } - - return null; - } - } - - private AuthenticationInfo Get(IDataReader reader) - { - var info = new AuthenticationInfo - { - Id = reader.GetGuid(0).ToString("N"), - AccessToken = reader.GetString(1) - }; - - if (!reader.IsDBNull(2)) - { - info.DeviceId = reader.GetString(2); - } - - if (!reader.IsDBNull(3)) - { - info.AppName = reader.GetString(3); - } - - if (!reader.IsDBNull(4)) - { - info.AppVersion = reader.GetString(4); - } - - if (!reader.IsDBNull(5)) - { - info.DeviceName = reader.GetString(5); - } - - if (!reader.IsDBNull(6)) - { - info.UserId = reader.GetString(6); - } - - info.IsActive = reader.GetBoolean(7); - info.DateCreated = reader.GetDateTime(8).ToUniversalTime(); - - if (!reader.IsDBNull(9)) - { - info.DateRevoked = reader.GetDateTime(9).ToUniversalTime(); - } - - return info; - } - } -} diff --git a/MediaBrowser.Server.Startup.Common/TextLocalizer.cs b/MediaBrowser.Server.Startup.Common/TextLocalizer.cs deleted file mode 100644 index c578199a07..0000000000 --- a/MediaBrowser.Server.Startup.Common/TextLocalizer.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text; -using Emby.Server.Implementations.Localization; - -namespace MediaBrowser.Server.Startup.Common -{ - public class TextLocalizer : ITextLocalizer - { - public string RemoveDiacritics(string text) - { - return String.Concat( - text.Normalize(NormalizationForm.FormD) - .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != - UnicodeCategory.NonSpacingMark) - ).Normalize(NormalizationForm.FormC); - } - - public string NormalizeFormKD(string text) - { - return text.Normalize(NormalizationForm.FormKD); - } - } -} diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 2d97cbdf7a..f2967e14a0 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -21,6 +21,7 @@ using Emby.Common.Implementations.IO; using Emby.Common.Implementations.Logging; using Emby.Server.Core; using Emby.Server.Core.Browser; +using Emby.Server.Implementations.IO; using ImageMagickSharp; using MediaBrowser.Common.Net; diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 2c78c22c92..b082b14d8b 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -82,9 +82,6 @@ ..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll True - - ..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll - @@ -1064,6 +1061,10 @@ {08fff49b-f175-4807-a2b5-73b0ebd9f716} Emby.Drawing + + {e383961b-9356-4d5d-8233-9a1079d03055} + Emby.Server.Implementations + {4fd51ac5-2c16-4308-a993-c3a84f3b4582} MediaBrowser.Api diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index 8424bf5746..d0f8239fec 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -15,7 +15,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; -using MediaBrowser.Server.Startup.Common.FFMpeg; using OperatingSystem = MediaBrowser.Server.Startup.Common.OperatingSystem; namespace MediaBrowser.ServerApplication.Native diff --git a/MediaBrowser.ServerApplication/Networking/NetworkManager.cs b/MediaBrowser.ServerApplication/Networking/NetworkManager.cs index 7b4379abf0..ed99dcc061 100644 --- a/MediaBrowser.ServerApplication/Networking/NetworkManager.cs +++ b/MediaBrowser.ServerApplication/Networking/NetworkManager.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.ServerApplication.Networking /// /// Class NetUtils /// - public class NetworkManager : Server.Startup.Common.Networking.NetworkManager + public class NetworkManager : Emby.Common.Implementations.Networking.NetworkManager { public NetworkManager(ILogger logger) : base(logger) -- cgit v1.2.3