diff options
Diffstat (limited to 'MediaBrowser.Common')
| -rw-r--r-- | MediaBrowser.Common/Kernel/BaseKernel.cs | 54 | ||||
| -rw-r--r-- | MediaBrowser.Common/Kernel/BasePeriodicWebSocketListener.cs | 12 | ||||
| -rw-r--r-- | MediaBrowser.Common/Kernel/IApplicationHost.cs | 5 | ||||
| -rw-r--r-- | MediaBrowser.Common/Kernel/IKernel.cs | 11 | ||||
| -rw-r--r-- | MediaBrowser.Common/Kernel/IServerManager.cs | 54 | ||||
| -rw-r--r-- | MediaBrowser.Common/Kernel/RegisterServer.bat | 28 | ||||
| -rw-r--r-- | MediaBrowser.Common/Kernel/TcpManager.cs | 511 | ||||
| -rw-r--r-- | MediaBrowser.Common/MediaBrowser.Common.csproj | 5 | ||||
| -rw-r--r-- | MediaBrowser.Common/Net/IWebSocketConnection.cs | 85 | ||||
| -rw-r--r-- | MediaBrowser.Common/Net/IWebSocketServer.cs | 6 | ||||
| -rw-r--r-- | MediaBrowser.Common/Net/WebSocketConnection.cs | 254 |
11 files changed, 166 insertions, 859 deletions
diff --git a/MediaBrowser.Common/Kernel/BaseKernel.cs b/MediaBrowser.Common/Kernel/BaseKernel.cs index 5b8da5d09..2ec1dd829 100644 --- a/MediaBrowser.Common/Kernel/BaseKernel.cs +++ b/MediaBrowser.Common/Kernel/BaseKernel.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.Events; -using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; @@ -165,7 +164,7 @@ namespace MediaBrowser.Common.Kernel /// Gets or sets the TCP manager. /// </summary> /// <value>The TCP manager.</value> - public TcpManager TcpManager { get; private set; } + public IServerManager ServerManager { get; private set; } /// <summary> /// Gets the UDP server port number. @@ -203,15 +202,6 @@ namespace MediaBrowser.Common.Kernel public abstract KernelContext KernelContext { get; } /// <summary> - /// Gets the log file path. - /// </summary> - /// <value>The log file path.</value> - public string LogFilePath - { - get { return ApplicationHost.LogFilePath; } - } - - /// <summary> /// Gets the logger. /// </summary> /// <value>The logger.</value> @@ -238,23 +228,6 @@ namespace MediaBrowser.Common.Kernel /// <exception cref="System.ArgumentNullException">isoManager</exception> protected BaseKernel(IApplicationHost appHost, TApplicationPathsType appPaths, IXmlSerializer xmlSerializer, ILogger logger) { - if (appHost == null) - { - throw new ArgumentNullException("appHost"); - } - if (appPaths == null) - { - throw new ArgumentNullException("appPaths"); - } - if (xmlSerializer == null) - { - throw new ArgumentNullException("xmlSerializer"); - } - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - ApplicationPaths = appPaths; ApplicationHost = appHost; _xmlSerializer = xmlSerializer; @@ -291,8 +264,8 @@ namespace MediaBrowser.Common.Kernel await OnComposablePartsLoaded().ConfigureAwait(false); - DisposeTcpManager(); - TcpManager = (TcpManager)ApplicationHost.CreateInstance(typeof(TcpManager)); + ServerManager = ApplicationHost.Resolve<IServerManager>(); + ServerManager.Start(); } /// <summary> @@ -357,7 +330,7 @@ namespace MediaBrowser.Common.Kernel { HasPendingRestart = true; - TcpManager.SendWebSocketMessage("HasPendingRestartChanged", GetSystemInfo()); + ServerManager.SendWebSocketMessage("HasPendingRestartChanged", GetSystemInfo()); EventHelper.QueueEventIfNotNull(HasPendingRestartChanged, this, EventArgs.Empty, Logger); } @@ -377,22 +350,7 @@ namespace MediaBrowser.Common.Kernel /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> protected virtual void Dispose(bool dispose) { - if (dispose) - { - DisposeTcpManager(); - } - } - /// <summary> - /// Disposes the TCP manager. - /// </summary> - private void DisposeTcpManager() - { - if (TcpManager != null) - { - TcpManager.Dispose(); - TcpManager = null; - } } /// <summary> @@ -424,8 +382,8 @@ namespace MediaBrowser.Common.Kernel HasPendingRestart = HasPendingRestart, Version = ApplicationHost.ApplicationVersion.ToString(), IsNetworkDeployed = ApplicationHost.CanSelfUpdate, - WebSocketPortNumber = TcpManager.WebSocketPortNumber, - SupportsNativeWebSocket = TcpManager.SupportsNativeWebSocket, + WebSocketPortNumber = ServerManager.WebSocketPortNumber, + SupportsNativeWebSocket = ServerManager.SupportsNativeWebSocket, FailedPluginAssemblies = ApplicationHost.FailedAssemblies.ToArray() }; } diff --git a/MediaBrowser.Common/Kernel/BasePeriodicWebSocketListener.cs b/MediaBrowser.Common/Kernel/BasePeriodicWebSocketListener.cs index 5374bb714..6a44cf372 100644 --- a/MediaBrowser.Common/Kernel/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Common/Kernel/BasePeriodicWebSocketListener.cs @@ -19,8 +19,8 @@ namespace MediaBrowser.Common.Kernel /// <summary> /// The _active connections /// </summary> - protected readonly List<Tuple<WebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim>> ActiveConnections = - new List<Tuple<WebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim>>(); + protected readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim>> ActiveConnections = + new List<Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim>>(); /// <summary> /// Gets the name. @@ -103,7 +103,7 @@ namespace MediaBrowser.Common.Kernel lock (ActiveConnections) { - ActiveConnections.Add(new Tuple<WebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim>(message.Connection, cancellationTokenSource, timer, state, semaphore)); + ActiveConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim>(message.Connection, cancellationTokenSource, timer, state, semaphore)); } timer.Change(TimeSpan.FromMilliseconds(dueTimeMs), TimeSpan.FromMilliseconds(periodMs)); @@ -115,9 +115,9 @@ namespace MediaBrowser.Common.Kernel /// <param name="state">The state.</param> private async void TimerCallback(object state) { - var connection = (WebSocketConnection)state; + var connection = (IWebSocketConnection)state; - Tuple<WebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim> tuple; + Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim> tuple; lock (ActiveConnections) { @@ -187,7 +187,7 @@ namespace MediaBrowser.Common.Kernel /// Disposes the connection. /// </summary> /// <param name="connection">The connection.</param> - private void DisposeConnection(Tuple<WebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim> connection) + private void DisposeConnection(Tuple<IWebSocketConnection, CancellationTokenSource, Timer, TStateType, SemaphoreSlim> connection) { Logger.Info("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); diff --git a/MediaBrowser.Common/Kernel/IApplicationHost.cs b/MediaBrowser.Common/Kernel/IApplicationHost.cs index af9b039bc..1f99d10db 100644 --- a/MediaBrowser.Common/Kernel/IApplicationHost.cs +++ b/MediaBrowser.Common/Kernel/IApplicationHost.cs @@ -97,5 +97,10 @@ namespace MediaBrowser.Common.Kernel /// <typeparam name="T"></typeparam> /// <returns>``0.</returns> T TryResolve<T>(); + + /// <summary> + /// Shuts down. + /// </summary> + void Shutdown(); } } diff --git a/MediaBrowser.Common/Kernel/IKernel.cs b/MediaBrowser.Common/Kernel/IKernel.cs index 06c2e7b64..a28500eca 100644 --- a/MediaBrowser.Common/Kernel/IKernel.cs +++ b/MediaBrowser.Common/Kernel/IKernel.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.System; using System; @@ -72,12 +71,6 @@ namespace MediaBrowser.Common.Kernel string WebApplicationName { get; } /// <summary> - /// Gets the log file path. - /// </summary> - /// <value>The log file path.</value> - string LogFilePath { get; } - - /// <summary> /// Performs the pending restart. /// </summary> void PerformPendingRestart(); @@ -104,7 +97,7 @@ namespace MediaBrowser.Common.Kernel /// Gets the TCP manager. /// </summary> /// <value>The TCP manager.</value> - TcpManager TcpManager { get; } + IServerManager ServerManager { get; } /// <summary> /// Gets the web socket listeners. diff --git a/MediaBrowser.Common/Kernel/IServerManager.cs b/MediaBrowser.Common/Kernel/IServerManager.cs new file mode 100644 index 000000000..cfea6eee1 --- /dev/null +++ b/MediaBrowser.Common/Kernel/IServerManager.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Common.Kernel +{ + public interface IServerManager : IDisposable + { + /// <summary> + /// Gets a value indicating whether [supports web socket]. + /// </summary> + /// <value><c>true</c> if [supports web socket]; otherwise, <c>false</c>.</value> + bool SupportsNativeWebSocket { get; } + + /// <summary> + /// Gets the web socket port number. + /// </summary> + /// <value>The web socket port number.</value> + int WebSocketPortNumber { get; } + + /// <summary> + /// Starts this instance. + /// </summary> + void Start(); + + /// <summary> + /// Sends a message to all clients currently connected via a web socket + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="messageType">Type of the message.</param> + /// <param name="data">The data.</param> + /// <returns>Task.</returns> + void SendWebSocketMessage<T>(string messageType, T data); + + /// <summary> + /// Sends a message to all clients currently connected via a web socket + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="messageType">Type of the message.</param> + /// <param name="dataFunction">The function that generates the data to send, if there are any connected clients</param> + void SendWebSocketMessage<T>(string messageType, Func<T> dataFunction); + + /// <summary> + /// Sends a message to all clients currently connected via a web socket + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="messageType">Type of the message.</param> + /// <param name="dataFunction">The function that generates the data to send, if there are any connected clients</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">messageType</exception> + Task SendWebSocketMessageAsync<T>(string messageType, Func<T> dataFunction, CancellationToken cancellationToken); + } +}
\ No newline at end of file diff --git a/MediaBrowser.Common/Kernel/RegisterServer.bat b/MediaBrowser.Common/Kernel/RegisterServer.bat deleted file mode 100644 index d762dfaf7..000000000 --- a/MediaBrowser.Common/Kernel/RegisterServer.bat +++ /dev/null @@ -1,28 +0,0 @@ -rem %1 = http server port -rem %2 = http server url -rem %3 = udp server port -rem %4 = tcp server port (web socket) - -if [%1]==[] GOTO DONE - -netsh advfirewall firewall delete rule name="Port %1" protocol=TCP localport=%1 -netsh advfirewall firewall add rule name="Port %1" dir=in action=allow protocol=TCP localport=%1 - -if [%2]==[] GOTO DONE - -netsh http del urlacl url="%2" user="NT AUTHORITY\Authenticated Users" -netsh http add urlacl url="%2" user="NT AUTHORITY\Authenticated Users" - -if [%3]==[] GOTO DONE - -netsh advfirewall firewall delete rule name="Port %3" protocol=UDP localport=%3 -netsh advfirewall firewall add rule name="Port %3" dir=in action=allow protocol=UDP localport=%3 - -if [%4]==[] GOTO DONE - -netsh advfirewall firewall delete rule name="Port %4" protocol=TCP localport=%4 -netsh advfirewall firewall add rule name="Port %4" dir=in action=allow protocol=TCP localport=%4 - - -:DONE -Exit
\ No newline at end of file diff --git a/MediaBrowser.Common/Kernel/TcpManager.cs b/MediaBrowser.Common/Kernel/TcpManager.cs deleted file mode 100644 index 2dfed501a..000000000 --- a/MediaBrowser.Common/Kernel/TcpManager.cs +++ /dev/null @@ -1,511 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Kernel -{ - /// <summary> - /// Manages the Http Server, Udp Server and WebSocket connections - /// </summary> - public class TcpManager : IDisposable - { - /// <summary> - /// This is the udp server used for server discovery by clients - /// </summary> - /// <value>The UDP server.</value> - private IUdpServer UdpServer { get; set; } - - /// <summary> - /// Both the Ui and server will have a built-in HttpServer. - /// People will inevitably want remote control apps so it's needed in the Ui too. - /// </summary> - /// <value>The HTTP server.</value> - private IHttpServer HttpServer { get; set; } - - /// <summary> - /// Gets or sets the json serializer. - /// </summary> - /// <value>The json serializer.</value> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// This subscribes to HttpListener requests and finds the appropriate BaseHandler to process it - /// </summary> - /// <value>The HTTP listener.</value> - private IDisposable HttpListener { get; set; } - - /// <summary> - /// The web socket connections - /// </summary> - private readonly List<WebSocketConnection> _webSocketConnections = new List<WebSocketConnection>(); - - /// <summary> - /// Gets or sets the external web socket server. - /// </summary> - /// <value>The external web socket server.</value> - private IWebSocketServer ExternalWebSocketServer { get; set; } - - /// <summary> - /// The _logger - /// </summary> - private readonly ILogger _logger; - - /// <summary> - /// The _network manager - /// </summary> - private readonly INetworkManager _networkManager; - - /// <summary> - /// The _application host - /// </summary> - private readonly IApplicationHost _applicationHost; - - /// <summary> - /// The _kernel - /// </summary> - private readonly IKernel _kernel; - - /// <summary> - /// Gets a value indicating whether [supports web socket]. - /// </summary> - /// <value><c>true</c> if [supports web socket]; otherwise, <c>false</c>.</value> - internal bool SupportsNativeWebSocket - { - get { return HttpServer != null && HttpServer.SupportsWebSockets; } - } - - /// <summary> - /// Gets the web socket port number. - /// </summary> - /// <value>The web socket port number.</value> - public int WebSocketPortNumber - { - get { return SupportsNativeWebSocket ? _kernel.Configuration.HttpServerPortNumber : _kernel.Configuration.LegacyWebSocketPortNumber; } - } - - /// <summary> - /// Initializes a new instance of the <see cref="TcpManager" /> class. - /// </summary> - /// <param name="applicationHost">The application host.</param> - /// <param name="kernel">The kernel.</param> - /// <param name="networkManager">The network manager.</param> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="logger">The logger.</param> - /// <exception cref="System.ArgumentNullException">applicationHost</exception> - public TcpManager(IApplicationHost applicationHost, IKernel kernel, INetworkManager networkManager, IJsonSerializer jsonSerializer, ILogger logger) - { - if (applicationHost == null) - { - throw new ArgumentNullException("applicationHost"); - } - if (kernel == null) - { - throw new ArgumentNullException("kernel"); - } - if (networkManager == null) - { - throw new ArgumentNullException("networkManager"); - } - if (jsonSerializer == null) - { - throw new ArgumentNullException("jsonSerializer"); - } - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - - _logger = logger; - _jsonSerializer = jsonSerializer; - _kernel = kernel; - _applicationHost = applicationHost; - _networkManager = networkManager; - - if (applicationHost.IsFirstRun) - { - RegisterServerWithAdministratorAccess(); - } - - ReloadUdpServer(); - ReloadHttpServer(); - - if (!SupportsNativeWebSocket) - { - ReloadExternalWebSocketServer(); - } - } - - /// <summary> - /// Starts the external web socket server. - /// </summary> - private void ReloadExternalWebSocketServer() - { - // Avoid windows firewall prompts in the ui - if (_kernel.KernelContext != KernelContext.Server) - { - return; - } - - DisposeExternalWebSocketServer(); - - ExternalWebSocketServer = _applicationHost.Resolve<IWebSocketServer>(); - - ExternalWebSocketServer.Start(_kernel.Configuration.LegacyWebSocketPortNumber); - ExternalWebSocketServer.WebSocketConnected += HttpServer_WebSocketConnected; - } - - /// <summary> - /// Restarts the Http Server, or starts it if not currently running - /// </summary> - /// <param name="registerServerOnFailure">if set to <c>true</c> [register server on failure].</param> - public void ReloadHttpServer(bool registerServerOnFailure = true) - { - // Only reload if the port has changed, so that we don't disconnect any active users - if (HttpServer != null && HttpServer.UrlPrefix.Equals(_kernel.HttpServerUrlPrefix, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - DisposeHttpServer(); - - _logger.Info("Loading Http Server"); - - try - { - HttpServer = _applicationHost.Resolve<IHttpServer>(); - HttpServer.EnableHttpRequestLogging = _kernel.Configuration.EnableHttpLevelLogging; - HttpServer.Start(_kernel.HttpServerUrlPrefix); - } - catch (HttpListenerException ex) - { - _logger.ErrorException("Error starting Http Server", ex); - - if (registerServerOnFailure) - { - RegisterServerWithAdministratorAccess(); - - // Don't get stuck in a loop - ReloadHttpServer(false); - - return; - } - - throw; - } - - HttpServer.WebSocketConnected += HttpServer_WebSocketConnected; - } - - /// <summary> - /// Handles the WebSocketConnected event of the HttpServer control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="WebSocketConnectEventArgs" /> instance containing the event data.</param> - void HttpServer_WebSocketConnected(object sender, WebSocketConnectEventArgs e) - { - var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger) { OnReceive = ProcessWebSocketMessageReceived }; - - _webSocketConnections.Add(connection); - } - - /// <summary> - /// Processes the web socket message received. - /// </summary> - /// <param name="result">The result.</param> - private async void ProcessWebSocketMessageReceived(WebSocketMessageInfo result) - { - var tasks = _kernel.WebSocketListeners.Select(i => Task.Run(async () => - { - try - { - await i.ProcessMessage(result).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed processing WebSocket message {1}", ex, i.GetType().Name, result.MessageType); - } - })); - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - - /// <summary> - /// Starts or re-starts the udp server - /// </summary> - private void ReloadUdpServer() - { - // For now, there's no reason to keep reloading this over and over - if (UdpServer != null) - { - return; - } - - // Avoid windows firewall prompts in the ui - if (_kernel.KernelContext != KernelContext.Server) - { - return; - } - - DisposeUdpServer(); - - try - { - // The port number can't be in configuration because we don't want it to ever change - UdpServer = _applicationHost.Resolve<IUdpServer>(); - UdpServer.Start(_kernel.UdpServerPortNumber); - } - catch (SocketException ex) - { - _logger.ErrorException("Failed to start UDP Server", ex); - return; - } - - UdpServer.MessageReceived += UdpServer_MessageReceived; - } - - /// <summary> - /// Handles the MessageReceived event of the UdpServer control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="UdpMessageReceivedEventArgs" /> instance containing the event data.</param> - async void UdpServer_MessageReceived(object sender, UdpMessageReceivedEventArgs e) - { - var expectedMessage = String.Format("who is MediaBrowser{0}?", _kernel.KernelContext); - var expectedMessageBytes = Encoding.UTF8.GetBytes(expectedMessage); - - if (expectedMessageBytes.SequenceEqual(e.Bytes)) - { - _logger.Info("Received UDP server request from " + e.RemoteEndPoint); - - // Send a response back with our ip address and port - var response = String.Format("MediaBrowser{0}|{1}:{2}", _kernel.KernelContext, _networkManager.GetLocalIpAddress(), _kernel.UdpServerPortNumber); - - await UdpServer.SendAsync(Encoding.UTF8.GetBytes(response), e.RemoteEndPoint); - } - } - - /// <summary> - /// Sends a message to all clients currently connected via a web socket - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="messageType">Type of the message.</param> - /// <param name="data">The data.</param> - /// <returns>Task.</returns> - public void SendWebSocketMessage<T>(string messageType, T data) - { - SendWebSocketMessage(messageType, () => data); - } - - /// <summary> - /// Sends a message to all clients currently connected via a web socket - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="messageType">Type of the message.</param> - /// <param name="dataFunction">The function that generates the data to send, if there are any connected clients</param> - public void SendWebSocketMessage<T>(string messageType, Func<T> dataFunction) - { - Task.Run(async () => await SendWebSocketMessageAsync(messageType, dataFunction, CancellationToken.None).ConfigureAwait(false)); - } - - /// <summary> - /// Sends a message to all clients currently connected via a web socket - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="messageType">Type of the message.</param> - /// <param name="dataFunction">The function that generates the data to send, if there are any connected clients</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">messageType</exception> - public async Task SendWebSocketMessageAsync<T>(string messageType, Func<T> dataFunction, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(messageType)) - { - throw new ArgumentNullException("messageType"); - } - - if (dataFunction == null) - { - throw new ArgumentNullException("dataFunction"); - } - - if (cancellationToken == null) - { - throw new ArgumentNullException("cancellationToken"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - var connections = _webSocketConnections.Where(s => s.State == WebSocketState.Open).ToList(); - - if (connections.Count > 0) - { - _logger.Info("Sending web socket message {0}", messageType); - - var message = new WebSocketMessage<T> { MessageType = messageType, Data = dataFunction() }; - var bytes = _jsonSerializer.SerializeToBytes(message); - - var tasks = connections.Select(s => Task.Run(() => - { - try - { - s.SendAsync(bytes, cancellationToken); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error sending web socket message {0} to {1}", ex, messageType, s.RemoteEndPoint); - } - })); - - await Task.WhenAll(tasks).ConfigureAwait(false); - } - } - - /// <summary> - /// Disposes the udp server - /// </summary> - private void DisposeUdpServer() - { - if (UdpServer != null) - { - UdpServer.MessageReceived -= UdpServer_MessageReceived; - UdpServer.Dispose(); - } - } - - /// <summary> - /// Disposes the current HttpServer - /// </summary> - private void DisposeHttpServer() - { - foreach (var socket in _webSocketConnections) - { - // Dispose the connection - socket.Dispose(); - } - - _webSocketConnections.Clear(); - - if (HttpServer != null) - { - _logger.Info("Disposing Http Server"); - - HttpServer.WebSocketConnected -= HttpServer_WebSocketConnected; - HttpServer.Dispose(); - } - - if (HttpListener != null) - { - HttpListener.Dispose(); - } - - DisposeExternalWebSocketServer(); - } - - /// <summary> - /// Registers the server with administrator access. - /// </summary> - private void RegisterServerWithAdministratorAccess() - { - // Create a temp file path to extract the bat file to - var tmpFile = Path.Combine(_kernel.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".bat"); - - // Extract the bat file - using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.Common.Kernel.RegisterServer.bat")) - { - using (var fileStream = File.Create(tmpFile)) - { - stream.CopyTo(fileStream); - } - } - - var startInfo = new ProcessStartInfo - { - FileName = tmpFile, - - Arguments = string.Format("{0} {1} {2} {3}", _kernel.Configuration.HttpServerPortNumber, - _kernel.HttpServerUrlPrefix, - _kernel.UdpServerPortNumber, - _kernel.Configuration.LegacyWebSocketPortNumber), - - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - Verb = "runas", - ErrorDialog = false - }; - - using (var process = Process.Start(startInfo)) - { - process.WaitForExit(); - } - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - DisposeUdpServer(); - DisposeHttpServer(); - } - } - - /// <summary> - /// Disposes the external web socket server. - /// </summary> - private void DisposeExternalWebSocketServer() - { - if (ExternalWebSocketServer != null) - { - ExternalWebSocketServer.Dispose(); - } - } - - /// <summary> - /// Called when [application configuration changed]. - /// </summary> - /// <param name="oldConfig">The old config.</param> - /// <param name="newConfig">The new config.</param> - public void OnApplicationConfigurationChanged(BaseApplicationConfiguration oldConfig, BaseApplicationConfiguration newConfig) - { - HttpServer.EnableHttpRequestLogging = newConfig.EnableHttpLevelLogging; - - if (oldConfig.HttpServerPortNumber != newConfig.HttpServerPortNumber) - { - ReloadHttpServer(); - } - - if (!SupportsNativeWebSocket && oldConfig.LegacyWebSocketPortNumber != newConfig.LegacyWebSocketPortNumber) - { - ReloadExternalWebSocketServer(); - } - } - } -} diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 333c20beb..b136eb43c 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -91,10 +91,10 @@ <Compile Include="IO\StreamDefaults.cs" /> <Compile Include="Kernel\BasePeriodicWebSocketListener.cs" /> <Compile Include="Kernel\IApplicationPaths.cs" /> + <Compile Include="Kernel\IServerManager.cs" /> <Compile Include="Kernel\IWebSocketListener.cs" /> <Compile Include="Kernel\IApplicationHost.cs" /> <Compile Include="Kernel\IKernel.cs" /> - <Compile Include="Kernel\TcpManager.cs" /> <Compile Include="Net\Handlers\IHttpServerHandler.cs" /> <Compile Include="Net\Handlers\StaticFileHandler.cs" /> <Compile Include="Net\IHttpClient.cs" /> @@ -103,12 +103,12 @@ <Compile Include="Net\IRestfulService.cs" /> <Compile Include="Net\IUdpServer.cs" /> <Compile Include="Net\IWebSocket.cs" /> + <Compile Include="Net\IWebSocketConnection.cs" /> <Compile Include="Net\IWebSocketServer.cs" /> <Compile Include="Net\MimeTypes.cs" /> <Compile Include="Net\StreamWriter.cs" /> <Compile Include="Net\UdpMessageReceivedEventArgs.cs" /> <Compile Include="Net\WebSocketConnectEventArgs.cs" /> - <Compile Include="Net\WebSocketConnection.cs" /> <Compile Include="Net\WebSocketMessageType.cs" /> <Compile Include="Net\WebSocketState.cs" /> <Compile Include="Plugins\BaseUiPlugin.cs" /> @@ -138,7 +138,6 @@ </ItemGroup> <ItemGroup> <None Include="app.config" /> - <EmbeddedResource Include="Kernel\RegisterServer.bat" /> <None Include="packages.config" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Common/Net/IWebSocketConnection.cs b/MediaBrowser.Common/Net/IWebSocketConnection.cs new file mode 100644 index 000000000..8e1b13ceb --- /dev/null +++ b/MediaBrowser.Common/Net/IWebSocketConnection.cs @@ -0,0 +1,85 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Common.Net +{ + public interface IWebSocketConnection : IDisposable + { + /// <summary> + /// Gets or sets the receive action. + /// </summary> + /// <value>The receive action.</value> + Action<WebSocketMessageInfo> OnReceive { get; set; } + + /// <summary> + /// Gets the state. + /// </summary> + /// <value>The state.</value> + WebSocketState State { get; } + + /// <summary> + /// Gets the remote end point. + /// </summary> + /// <value>The remote end point.</value> + string RemoteEndPoint { get; } + + /// <summary> + /// Sends a message asynchronously. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="message">The message.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">message</exception> + Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken); + + /// <summary> + /// Sends a message asynchronously. + /// </summary> + /// <param name="buffer">The buffer.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task SendAsync(byte[] buffer, CancellationToken cancellationToken); + + /// <summary> + /// Sends a message asynchronously. + /// </summary> + /// <param name="buffer">The buffer.</param> + /// <param name="type">The type.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + /// <exception cref="System.ArgumentNullException">buffer</exception> + Task SendAsync(byte[] buffer, WebSocketMessageType type, CancellationToken cancellationToken); + } + + /// <summary> + /// Class WebSocketMessage + /// </summary> + /// <typeparam name="T"></typeparam> + public class WebSocketMessage<T> + { + /// <summary> + /// Gets or sets the type of the message. + /// </summary> + /// <value>The type of the message.</value> + public string MessageType { get; set; } + /// <summary> + /// Gets or sets the data. + /// </summary> + /// <value>The data.</value> + public T Data { get; set; } + } + + /// <summary> + /// Class WebSocketMessageInfo + /// </summary> + public class WebSocketMessageInfo : WebSocketMessage<string> + { + /// <summary> + /// Gets or sets the connection. + /// </summary> + /// <value>The connection.</value> + public IWebSocketConnection Connection { get; set; } + } +}
\ No newline at end of file diff --git a/MediaBrowser.Common/Net/IWebSocketServer.cs b/MediaBrowser.Common/Net/IWebSocketServer.cs index 5ce571fbb..187e03e09 100644 --- a/MediaBrowser.Common/Net/IWebSocketServer.cs +++ b/MediaBrowser.Common/Net/IWebSocketServer.cs @@ -22,5 +22,11 @@ namespace MediaBrowser.Common.Net /// Occurs when [web socket connected]. /// </summary> event EventHandler<WebSocketConnectEventArgs> WebSocketConnected; + + /// <summary> + /// Gets the port. + /// </summary> + /// <value>The port.</value> + int Port { get; } } } diff --git a/MediaBrowser.Common/Net/WebSocketConnection.cs b/MediaBrowser.Common/Net/WebSocketConnection.cs deleted file mode 100644 index 1ad0e8f06..000000000 --- a/MediaBrowser.Common/Net/WebSocketConnection.cs +++ /dev/null @@ -1,254 +0,0 @@ -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Net -{ - /// <summary> - /// Class WebSocketConnection - /// </summary> - public class WebSocketConnection : IDisposable - { - /// <summary> - /// The _socket - /// </summary> - private readonly IWebSocket _socket; - - /// <summary> - /// The _remote end point - /// </summary> - public readonly string RemoteEndPoint; - - /// <summary> - /// The _cancellation token source - /// </summary> - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - /// <summary> - /// The _send semaphore - /// </summary> - private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1,1); - - /// <summary> - /// The logger - /// </summary> - private readonly ILogger _logger; - - /// <summary> - /// The _json serializer - /// </summary> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// Gets or sets the receive action. - /// </summary> - /// <value>The receive action.</value> - public Action<WebSocketMessageInfo> OnReceive { get; set; } - - /// <summary> - /// Initializes a new instance of the <see cref="WebSocketConnection" /> class. - /// </summary> - /// <param name="socket">The socket.</param> - /// <param name="remoteEndPoint">The remote end point.</param> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="logger">The logger.</param> - /// <exception cref="System.ArgumentNullException">socket</exception> - public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger) - { - if (socket == null) - { - throw new ArgumentNullException("socket"); - } - if (string.IsNullOrEmpty(remoteEndPoint)) - { - throw new ArgumentNullException("remoteEndPoint"); - } - if (jsonSerializer == null) - { - throw new ArgumentNullException("jsonSerializer"); - } - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - - _jsonSerializer = jsonSerializer; - _socket = socket; - _socket.OnReceiveDelegate = OnReceiveInternal; - RemoteEndPoint = remoteEndPoint; - _logger = logger; - } - - /// <summary> - /// Called when [receive]. - /// </summary> - /// <param name="bytes">The bytes.</param> - private void OnReceiveInternal(byte[] bytes) - { - if (OnReceive == null) - { - return; - } - try - { - WebSocketMessageInfo info; - - using (var memoryStream = new MemoryStream(bytes)) - { - info = (WebSocketMessageInfo)_jsonSerializer.DeserializeFromStream(memoryStream, typeof(WebSocketMessageInfo)); - } - - info.Connection = this; - - OnReceive(info); - } - catch (Exception ex) - { - _logger.ErrorException("Error processing web socket message", ex); - } - } - - /// <summary> - /// Sends a message asynchronously. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="message">The message.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">message</exception> - public Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken) - { - if (message == null) - { - throw new ArgumentNullException("message"); - } - - var bytes = _jsonSerializer.SerializeToBytes(message); - - return SendAsync(bytes, cancellationToken); - } - - /// <summary> - /// Sends a message asynchronously. - /// </summary> - /// <param name="buffer">The buffer.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SendAsync(byte[] buffer, CancellationToken cancellationToken) - { - return SendAsync(buffer, WebSocketMessageType.Text, cancellationToken); - } - - /// <summary> - /// Sends a message asynchronously. - /// </summary> - /// <param name="buffer">The buffer.</param> - /// <param name="type">The type.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">buffer</exception> - public async Task SendAsync(byte[] buffer, WebSocketMessageType type, CancellationToken cancellationToken) - { - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } - - if (cancellationToken == null) - { - throw new ArgumentNullException("cancellationToken"); - } - - cancellationToken.ThrowIfCancellationRequested(); - - // Per msdn docs, attempting to send simultaneous messages will result in one failing. - // This should help us workaround that and ensure all messages get sent - await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - await _socket.SendAsync(buffer, type, true, cancellationToken); - } - catch (OperationCanceledException) - { - _logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint); - - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint); - - throw; - } - finally - { - _sendSemaphore.Release(); - } - } - - /// <summary> - /// Gets the state. - /// </summary> - /// <value>The state.</value> - public WebSocketState State - { - get { return _socket.State; } - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - _cancellationTokenSource.Dispose(); - _socket.Dispose(); - } - } - } - - /// <summary> - /// Class WebSocketMessage - /// </summary> - /// <typeparam name="T"></typeparam> - public class WebSocketMessage<T> - { - /// <summary> - /// Gets or sets the type of the message. - /// </summary> - /// <value>The type of the message.</value> - public string MessageType { get; set; } - /// <summary> - /// Gets or sets the data. - /// </summary> - /// <value>The data.</value> - public T Data { get; set; } - } - - /// <summary> - /// Class WebSocketMessageInfo - /// </summary> - public class WebSocketMessageInfo : WebSocketMessage<string> - { - /// <summary> - /// Gets or sets the connection. - /// </summary> - /// <value>The connection.</value> - public WebSocketConnection Connection { get; set; } - } -} |
