From 6efd22a3d22f8d87ad17da3a1e47ca26c5bb09f2 Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Tue, 26 Feb 2013 11:10:55 -0500 Subject: added a shutdown api method, font size fix and other decouplings --- MediaBrowser.Api/SystemService.cs | 32 +- .../WebSocket/LogFileWebSocketListener.cs | 13 +- .../BaseApplicationHost.cs | 43 +- .../MediaBrowser.Common.Implementations.csproj | 6 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 14 +- .../ScheduledTasks/TaskManager.cs | 12 +- .../Server/RegisterServer.bat | 28 ++ .../Server/ServerManager.cs | 520 +++++++++++++++++++++ .../Server/WebSocketConnection.cs | 225 +++++++++ MediaBrowser.Common/Kernel/BaseKernel.cs | 54 +-- .../Kernel/BasePeriodicWebSocketListener.cs | 12 +- MediaBrowser.Common/Kernel/IApplicationHost.cs | 5 + MediaBrowser.Common/Kernel/IKernel.cs | 11 +- MediaBrowser.Common/Kernel/IServerManager.cs | 54 +++ MediaBrowser.Common/Kernel/RegisterServer.bat | 28 -- MediaBrowser.Common/Kernel/TcpManager.cs | 511 -------------------- MediaBrowser.Common/MediaBrowser.Common.csproj | 5 +- MediaBrowser.Common/Net/IWebSocketConnection.cs | 85 ++++ MediaBrowser.Common/Net/IWebSocketServer.cs | 6 + MediaBrowser.Common/Net/WebSocketConnection.cs | 254 ---------- MediaBrowser.Controller/Kernel.cs | 31 +- MediaBrowser.Controller/Library/LibraryManager.cs | 52 ++- MediaBrowser.Controller/Library/UserManager.cs | 4 +- .../Updates/InstallationManager.cs | 10 +- MediaBrowser.Networking/Udp/UdpServer.cs | 40 +- MediaBrowser.Networking/WebSocket/AlchemyServer.cs | 8 + MediaBrowser.ServerApplication/App.xaml.cs | 3 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 55 ++- MediaBrowser.WebDashboard/Html/css/site.css | 4 - Nuget/MediaBrowser.ApiClient.nuspec | 2 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 33 files changed, 1161 insertions(+), 976 deletions(-) create mode 100644 MediaBrowser.Common.Implementations/Server/RegisterServer.bat create mode 100644 MediaBrowser.Common.Implementations/Server/ServerManager.cs create mode 100644 MediaBrowser.Common.Implementations/Server/WebSocketConnection.cs create mode 100644 MediaBrowser.Common/Kernel/IServerManager.cs delete mode 100644 MediaBrowser.Common/Kernel/RegisterServer.bat delete mode 100644 MediaBrowser.Common/Kernel/TcpManager.cs create mode 100644 MediaBrowser.Common/Net/IWebSocketConnection.cs delete mode 100644 MediaBrowser.Common/Net/WebSocketConnection.cs diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs index cf0be35a2..55e8ee693 100644 --- a/MediaBrowser.Api/SystemService.cs +++ b/MediaBrowser.Api/SystemService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Kernel; using MediaBrowser.Controller; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; @@ -29,6 +30,11 @@ namespace MediaBrowser.Api { } + [Route("/System/Shutdown", "POST")] + public class ShutdownApplication + { + } + /// /// Class GetConfiguration /// @@ -61,19 +67,30 @@ namespace MediaBrowser.Api /// private readonly IJsonSerializer _jsonSerializer; + /// + /// The _app host + /// + private readonly IApplicationHost _appHost; + /// /// Initializes a new instance of the class. /// /// The json serializer. + /// The app host. /// jsonSerializer - public SystemService(IJsonSerializer jsonSerializer) + public SystemService(IJsonSerializer jsonSerializer, IApplicationHost appHost) : base() { if (jsonSerializer == null) { throw new ArgumentNullException("jsonSerializer"); } + if (appHost == null) + { + throw new ArgumentNullException("appHost"); + } + _appHost = appHost; _jsonSerializer = jsonSerializer; } @@ -118,6 +135,19 @@ namespace MediaBrowser.Api }); } + /// + /// Posts the specified request. + /// + /// The request. + public void Post(ShutdownApplication request) + { + Task.Run(async () => + { + await Task.Delay(100); + _appHost.Shutdown(); + }); + } + /// /// Posts the specified configuraiton. /// diff --git a/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs b/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs index 6e3e10361..e0879d924 100644 --- a/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs +++ b/MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs @@ -1,6 +1,5 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Kernel; -using MediaBrowser.Controller; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -27,18 +26,20 @@ namespace MediaBrowser.Api.WebSocket /// /// The _kernel /// + private readonly IApplicationHost _appHost; private readonly IKernel _kernel; - + /// /// Initializes a new instance of the class. /// /// The logger. /// The kernel. - public LogFileWebSocketListener(ILogger logger, Kernel kernel) + public LogFileWebSocketListener(ILogger logger, IApplicationHost host, IKernel kernel) : base(logger) { + _appHost = host; _kernel = kernel; - _kernel.LoggerLoaded += kernel_LoggerLoaded; + kernel.LoggerLoaded += kernel_LoggerLoaded; } /// @@ -48,9 +49,9 @@ namespace MediaBrowser.Api.WebSocket /// IEnumerable{System.String}. protected override async Task> GetDataToSend(LogFileWebSocketState state) { - if (!string.Equals(_kernel.LogFilePath, state.LastLogFilePath)) + if (!string.Equals(_appHost.LogFilePath, state.LastLogFilePath)) { - state.LastLogFilePath = _kernel.LogFilePath; + state.LastLogFilePath = _appHost.LogFilePath; state.StartLine = 0; } diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index c5af5059f..f5855bf75 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -151,21 +151,22 @@ namespace MediaBrowser.Common.Implementations /// /// /// The obj. - protected void RegisterSingleInstance(T obj) + /// if set to true [manage lifetime]. + protected void RegisterSingleInstance(T obj, bool manageLifetime = true) where T : class { Container.RegisterSingle(obj); - } - /// - /// Registers the specified func. - /// - /// - /// The func. - protected void Register(Func func) - where T : class - { - Container.Register(func); + if (manageLifetime) + { + var disposable = obj as IDisposable; + + if (disposable != null) + { + Logger.Info("Registering " + disposable.GetType().Name); + DisposableParts.Add(disposable); + } + } } /// @@ -205,16 +206,6 @@ namespace MediaBrowser.Common.Implementations return (T)result.GetInstance(); } - /// - /// Registers the specified service type. - /// - /// Type of the service. - /// Type of the concrete. - protected void Register(Type serviceType, Type implementation) - { - Container.Register(serviceType, implementation); - } - /// /// Loads the assembly. /// @@ -282,13 +273,17 @@ namespace MediaBrowser.Common.Implementations /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { - foreach (var part in DisposableParts) + var type = GetType(); + + Logger.Info("Disposing " + type.Name); + + foreach (var part in DisposableParts.Distinct().Where(i => i.GetType() != type).ToList()) { + Logger.Info("Disposing " + part.GetType().Name); + part.Dispose(); } - var b = Container.GetCurrentRegistrations(); - DisposableParts.Clear(); } } diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index acd798d73..59ec01aaf 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -47,10 +47,7 @@ - - - @@ -69,6 +66,8 @@ + + @@ -82,6 +81,7 @@ + diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 7fa30f4ae..320f440fc 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -48,6 +48,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks /// The task manager. private ITaskManager TaskManager { get; set; } + /// + /// Gets or sets the server manager. + /// + /// The server manager. + private IServerManager ServerManager { get; set; } + /// /// Initializes a new instance of the class. /// @@ -56,13 +62,15 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks /// The task manager. /// The json serializer. /// The logger. - public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger) + /// The server manager. + public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger, IServerManager serverManager) { ScheduledTask = scheduledTask; ApplicationPaths = applicationPaths; TaskManager = taskManager; JsonSerializer = jsonSerializer; Logger = logger; + ServerManager = serverManager; } /// @@ -302,7 +310,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks TaskCompletionStatus status; CurrentExecutionStartTime = DateTime.UtcNow; - //Kernel.TcpManager.SendWebSocketMessage("ScheduledTaskBeginExecute", Name); + ServerManager.SendWebSocketMessage("ScheduledTaskBeginExecute", Name); try { @@ -324,7 +332,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks var startTime = CurrentExecutionStartTime; var endTime = DateTime.UtcNow; - //Kernel.TcpManager.SendWebSocketMessage("ScheduledTaskEndExecute", LastExecutionResult); + ServerManager.SendWebSocketMessage("ScheduledTaskEndExecute", LastExecutionResult); progress.ProgressChanged -= progress_ProgressChanged; CurrentCancellationTokenSource.Dispose(); diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs index 4b61492d6..335820e1c 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs @@ -43,18 +43,26 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks /// The logger. private ILogger Logger { get; set; } + /// + /// Gets or sets the server manager. + /// + /// The server manager. + private IServerManager ServerManager { get; set; } + /// /// Initializes a new instance of the class. /// /// The application paths. /// The json serializer. /// The logger. + /// The server manager. /// kernel - public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger) + public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger, IServerManager serverManager) { ApplicationPaths = applicationPaths; JsonSerializer = jsonSerializer; Logger = logger; + ServerManager = serverManager; ScheduledTasks = new IScheduledTaskWorker[] { }; } @@ -155,7 +163,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks { var myTasks = ScheduledTasks.ToList(); - myTasks.AddRange(tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger))); + myTasks.AddRange(tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, ServerManager))); ScheduledTasks = myTasks.ToArray(); } diff --git a/MediaBrowser.Common.Implementations/Server/RegisterServer.bat b/MediaBrowser.Common.Implementations/Server/RegisterServer.bat new file mode 100644 index 000000000..d762dfaf7 --- /dev/null +++ b/MediaBrowser.Common.Implementations/Server/RegisterServer.bat @@ -0,0 +1,28 @@ +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.Implementations/Server/ServerManager.cs b/MediaBrowser.Common.Implementations/Server/ServerManager.cs new file mode 100644 index 000000000..04747bad6 --- /dev/null +++ b/MediaBrowser.Common.Implementations/Server/ServerManager.cs @@ -0,0 +1,520 @@ +using MediaBrowser.Common.Kernel; +using MediaBrowser.Common.Net; +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.Implementations.Server +{ + /// + /// Manages the Http Server, Udp Server and WebSocket connections + /// + public class ServerManager : IServerManager, IDisposable + { + /// + /// This is the udp server used for server discovery by clients + /// + /// The UDP server. + private IUdpServer UdpServer { get; set; } + + /// + /// 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. + /// + /// The HTTP server. + private IHttpServer HttpServer { get; set; } + + /// + /// Gets or sets the json serializer. + /// + /// The json serializer. + private readonly IJsonSerializer _jsonSerializer; + + /// + /// This subscribes to HttpListener requests and finds the appropriate BaseHandler to process it + /// + /// The HTTP listener. + private IDisposable HttpListener { get; set; } + + /// + /// The web socket connections + /// + private readonly List _webSocketConnections = new List(); + + /// + /// Gets or sets the external web socket server. + /// + /// The external web socket server. + private IWebSocketServer ExternalWebSocketServer { get; set; } + + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// The _network manager + /// + private readonly INetworkManager _networkManager; + + /// + /// The _application host + /// + private readonly IApplicationHost _applicationHost; + + /// + /// The _kernel + /// + private readonly IKernel _kernel; + + /// + /// Gets a value indicating whether [supports web socket]. + /// + /// true if [supports web socket]; otherwise, false. + public bool SupportsNativeWebSocket + { + get { return HttpServer != null && HttpServer.SupportsWebSockets; } + } + + /// + /// Gets the web socket port number. + /// + /// The web socket port number. + public int WebSocketPortNumber + { + get { return SupportsNativeWebSocket ? _kernel.Configuration.HttpServerPortNumber : _kernel.Configuration.LegacyWebSocketPortNumber; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The application host. + /// The kernel. + /// The network manager. + /// The json serializer. + /// The logger. + /// applicationHost + public ServerManager(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; + } + + /// + /// Starts this instance. + /// + public void Start() + { + if (_applicationHost.IsFirstRun) + { + RegisterServerWithAdministratorAccess(); + } + + ReloadUdpServer(); + ReloadHttpServer(); + + if (!SupportsNativeWebSocket) + { + ReloadExternalWebSocketServer(); + } + + _kernel.ConfigurationUpdated += _kernel_ConfigurationUpdated; + } + + /// + /// Starts the external web socket server. + /// + private void ReloadExternalWebSocketServer() + { + // Avoid windows firewall prompts in the ui + if (_kernel.KernelContext != KernelContext.Server) + { + return; + } + + DisposeExternalWebSocketServer(); + + ExternalWebSocketServer = _applicationHost.Resolve(); + + ExternalWebSocketServer.Start(_kernel.Configuration.LegacyWebSocketPortNumber); + ExternalWebSocketServer.WebSocketConnected += HttpServer_WebSocketConnected; + } + + /// + /// Restarts the Http Server, or starts it if not currently running + /// + /// if set to true [register server on failure]. + private 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(); + 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; + } + + /// + /// Handles the WebSocketConnected event of the HttpServer control. + /// + /// The source of the event. + /// The instance containing the event data. + void HttpServer_WebSocketConnected(object sender, WebSocketConnectEventArgs e) + { + var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger) { OnReceive = ProcessWebSocketMessageReceived }; + + _webSocketConnections.Add(connection); + } + + /// + /// Processes the web socket message received. + /// + /// The result. + 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); + } + + /// + /// Starts or re-starts the udp server + /// + 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(); + UdpServer.Start(_kernel.UdpServerPortNumber); + } + catch (SocketException ex) + { + _logger.ErrorException("Failed to start UDP Server", ex); + return; + } + + UdpServer.MessageReceived += UdpServer_MessageReceived; + } + + /// + /// Handles the MessageReceived event of the UdpServer control. + /// + /// The source of the event. + /// The instance containing the event data. + 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); + } + } + + /// + /// Sends a message to all clients currently connected via a web socket + /// + /// + /// Type of the message. + /// The data. + /// Task. + public void SendWebSocketMessage(string messageType, T data) + { + SendWebSocketMessage(messageType, () => data); + } + + /// + /// Sends a message to all clients currently connected via a web socket + /// + /// + /// Type of the message. + /// The function that generates the data to send, if there are any connected clients + public void SendWebSocketMessage(string messageType, Func dataFunction) + { + Task.Run(async () => await SendWebSocketMessageAsync(messageType, dataFunction, CancellationToken.None).ConfigureAwait(false)); + } + + /// + /// Sends a message to all clients currently connected via a web socket + /// + /// + /// Type of the message. + /// The function that generates the data to send, if there are any connected clients + /// The cancellation token. + /// Task. + /// messageType + public async Task SendWebSocketMessageAsync(string messageType, Func 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 { 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); + } + } + + /// + /// Disposes the udp server + /// + private void DisposeUdpServer() + { + if (UdpServer != null) + { + UdpServer.MessageReceived -= UdpServer_MessageReceived; + UdpServer.Dispose(); + } + } + + /// + /// Disposes the current HttpServer + /// + 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(); + } + + /// + /// Registers the server with administrator access. + /// + 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.Implementations.Server.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(); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 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) + { + DisposeUdpServer(); + DisposeHttpServer(); + } + } + + /// + /// Disposes the external web socket server. + /// + private void DisposeExternalWebSocketServer() + { + if (ExternalWebSocketServer != null) + { + ExternalWebSocketServer.Dispose(); + } + } + + /// + /// Handles the ConfigurationUpdated event of the _kernel control. + /// + /// The source of the event. + /// The instance containing the event data. + /// + void _kernel_ConfigurationUpdated(object sender, EventArgs e) + { + HttpServer.EnableHttpRequestLogging = _kernel.Configuration.EnableHttpLevelLogging; + + if (!string.Equals(HttpServer.UrlPrefix, _kernel.HttpServerUrlPrefix, StringComparison.OrdinalIgnoreCase)) + { + ReloadHttpServer(); + } + + if (!SupportsNativeWebSocket && ExternalWebSocketServer != null && ExternalWebSocketServer.Port != _kernel.Configuration.LegacyWebSocketPortNumber) + { + ReloadExternalWebSocketServer(); + } + } + } +} diff --git a/MediaBrowser.Common.Implementations/Server/WebSocketConnection.cs b/MediaBrowser.Common.Implementations/Server/WebSocketConnection.cs new file mode 100644 index 000000000..b8766523c --- /dev/null +++ b/MediaBrowser.Common.Implementations/Server/WebSocketConnection.cs @@ -0,0 +1,225 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Common.Implementations.Server +{ + /// + /// Class WebSocketConnection + /// + public class WebSocketConnection : IWebSocketConnection + { + /// + /// The _socket + /// + private readonly IWebSocket _socket; + + /// + /// The _remote end point + /// + public string RemoteEndPoint { get; private set; } + + /// + /// The _cancellation token source + /// + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + /// + /// The _send semaphore + /// + private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1,1); + + /// + /// The logger + /// + private readonly ILogger _logger; + + /// + /// The _json serializer + /// + private readonly IJsonSerializer _jsonSerializer; + + /// + /// Gets or sets the receive action. + /// + /// The receive action. + public Action OnReceive { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The socket. + /// The remote end point. + /// The json serializer. + /// The logger. + /// socket + 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; + } + + /// + /// Called when [receive]. + /// + /// The bytes. + 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); + } + } + + /// + /// Sends a message asynchronously. + /// + /// + /// The message. + /// The cancellation token. + /// Task. + /// message + public Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken) + { + if (message == null) + { + throw new ArgumentNullException("message"); + } + + var bytes = _jsonSerializer.SerializeToBytes(message); + + return SendAsync(bytes, cancellationToken); + } + + /// + /// Sends a message asynchronously. + /// + /// The buffer. + /// The cancellation token. + /// Task. + public Task SendAsync(byte[] buffer, CancellationToken cancellationToken) + { + return SendAsync(buffer, WebSocketMessageType.Text, cancellationToken); + } + + /// + /// Sends a message asynchronously. + /// + /// The buffer. + /// The type. + /// The cancellation token. + /// Task. + /// buffer + 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(); + } + } + + /// + /// Gets the state. + /// + /// The state. + public WebSocketState State + { + get { return _socket.State; } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 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) + { + _cancellationTokenSource.Dispose(); + _socket.Dispose(); + } + } + } +} 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. /// /// The TCP manager. - public TcpManager TcpManager { get; private set; } + public IServerManager ServerManager { get; private set; } /// /// Gets the UDP server port number. @@ -202,15 +201,6 @@ namespace MediaBrowser.Common.Kernel /// The kernel context. public abstract KernelContext KernelContext { get; } - /// - /// Gets the log file path. - /// - /// The log file path. - public string LogFilePath - { - get { return ApplicationHost.LogFilePath; } - } - /// /// Gets the logger. /// @@ -238,23 +228,6 @@ namespace MediaBrowser.Common.Kernel /// isoManager 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(); + ServerManager.Start(); } /// @@ -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 /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { - if (dispose) - { - DisposeTcpManager(); - } - } - /// - /// Disposes the TCP manager. - /// - private void DisposeTcpManager() - { - if (TcpManager != null) - { - TcpManager.Dispose(); - TcpManager = null; - } } /// @@ -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 /// /// The _active connections /// - protected readonly List> ActiveConnections = - new List>(); + protected readonly List> ActiveConnections = + new List>(); /// /// Gets the name. @@ -103,7 +103,7 @@ namespace MediaBrowser.Common.Kernel lock (ActiveConnections) { - ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, timer, state, semaphore)); + ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, timer, state, semaphore)); } timer.Change(TimeSpan.FromMilliseconds(dueTimeMs), TimeSpan.FromMilliseconds(periodMs)); @@ -115,9 +115,9 @@ namespace MediaBrowser.Common.Kernel /// The state. private async void TimerCallback(object state) { - var connection = (WebSocketConnection)state; + var connection = (IWebSocketConnection)state; - Tuple tuple; + Tuple tuple; lock (ActiveConnections) { @@ -187,7 +187,7 @@ namespace MediaBrowser.Common.Kernel /// Disposes the connection. /// /// The connection. - private void DisposeConnection(Tuple connection) + private void DisposeConnection(Tuple 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 /// /// ``0. T TryResolve(); + + /// + /// Shuts down. + /// + 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; @@ -71,12 +70,6 @@ namespace MediaBrowser.Common.Kernel /// The name of the web application. string WebApplicationName { get; } - /// - /// Gets the log file path. - /// - /// The log file path. - string LogFilePath { get; } - /// /// Performs the pending restart. /// @@ -104,7 +97,7 @@ namespace MediaBrowser.Common.Kernel /// Gets the TCP manager. /// /// The TCP manager. - TcpManager TcpManager { get; } + IServerManager ServerManager { get; } /// /// 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 + { + /// + /// Gets a value indicating whether [supports web socket]. + /// + /// true if [supports web socket]; otherwise, false. + bool SupportsNativeWebSocket { get; } + + /// + /// Gets the web socket port number. + /// + /// The web socket port number. + int WebSocketPortNumber { get; } + + /// + /// Starts this instance. + /// + void Start(); + + /// + /// Sends a message to all clients currently connected via a web socket + /// + /// + /// Type of the message. + /// The data. + /// Task. + void SendWebSocketMessage(string messageType, T data); + + /// + /// Sends a message to all clients currently connected via a web socket + /// + /// + /// Type of the message. + /// The function that generates the data to send, if there are any connected clients + void SendWebSocketMessage(string messageType, Func dataFunction); + + /// + /// Sends a message to all clients currently connected via a web socket + /// + /// + /// Type of the message. + /// The function that generates the data to send, if there are any connected clients + /// The cancellation token. + /// Task. + /// messageType + Task SendWebSocketMessageAsync(string messageType, Func 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 -{ - /// - /// Manages the Http Server, Udp Server and WebSocket connections - /// - public class TcpManager : IDisposable - { - /// - /// This is the udp server used for server discovery by clients - /// - /// The UDP server. - private IUdpServer UdpServer { get; set; } - - /// - /// 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. - /// - /// The HTTP server. - private IHttpServer HttpServer { get; set; } - - /// - /// Gets or sets the json serializer. - /// - /// The json serializer. - private readonly IJsonSerializer _jsonSerializer; - - /// - /// This subscribes to HttpListener requests and finds the appropriate BaseHandler to process it - /// - /// The HTTP listener. - private IDisposable HttpListener { get; set; } - - /// - /// The web socket connections - /// - private readonly List _webSocketConnections = new List(); - - /// - /// Gets or sets the external web socket server. - /// - /// The external web socket server. - private IWebSocketServer ExternalWebSocketServer { get; set; } - - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// The _network manager - /// - private readonly INetworkManager _networkManager; - - /// - /// The _application host - /// - private readonly IApplicationHost _applicationHost; - - /// - /// The _kernel - /// - private readonly IKernel _kernel; - - /// - /// Gets a value indicating whether [supports web socket]. - /// - /// true if [supports web socket]; otherwise, false. - internal bool SupportsNativeWebSocket - { - get { return HttpServer != null && HttpServer.SupportsWebSockets; } - } - - /// - /// Gets the web socket port number. - /// - /// The web socket port number. - public int WebSocketPortNumber - { - get { return SupportsNativeWebSocket ? _kernel.Configuration.HttpServerPortNumber : _kernel.Configuration.LegacyWebSocketPortNumber; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The application host. - /// The kernel. - /// The network manager. - /// The json serializer. - /// The logger. - /// applicationHost - 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(); - } - } - - /// - /// Starts the external web socket server. - /// - private void ReloadExternalWebSocketServer() - { - // Avoid windows firewall prompts in the ui - if (_kernel.KernelContext != KernelContext.Server) - { - return; - } - - DisposeExternalWebSocketServer(); - - ExternalWebSocketServer = _applicationHost.Resolve(); - - ExternalWebSocketServer.Start(_kernel.Configuration.LegacyWebSocketPortNumber); - ExternalWebSocketServer.WebSocketConnected += HttpServer_WebSocketConnected; - } - - /// - /// Restarts the Http Server, or starts it if not currently running - /// - /// if set to true [register server on failure]. - 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(); - 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; - } - - /// - /// Handles the WebSocketConnected event of the HttpServer control. - /// - /// The source of the event. - /// The instance containing the event data. - void HttpServer_WebSocketConnected(object sender, WebSocketConnectEventArgs e) - { - var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger) { OnReceive = ProcessWebSocketMessageReceived }; - - _webSocketConnections.Add(connection); - } - - /// - /// Processes the web socket message received. - /// - /// The result. - 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); - } - - /// - /// Starts or re-starts the udp server - /// - 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(); - UdpServer.Start(_kernel.UdpServerPortNumber); - } - catch (SocketException ex) - { - _logger.ErrorException("Failed to start UDP Server", ex); - return; - } - - UdpServer.MessageReceived += UdpServer_MessageReceived; - } - - /// - /// Handles the MessageReceived event of the UdpServer control. - /// - /// The source of the event. - /// The instance containing the event data. - 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); - } - } - - /// - /// Sends a message to all clients currently connected via a web socket - /// - /// - /// Type of the message. - /// The data. - /// Task. - public void SendWebSocketMessage(string messageType, T data) - { - SendWebSocketMessage(messageType, () => data); - } - - /// - /// Sends a message to all clients currently connected via a web socket - /// - /// - /// Type of the message. - /// The function that generates the data to send, if there are any connected clients - public void SendWebSocketMessage(string messageType, Func dataFunction) - { - Task.Run(async () => await SendWebSocketMessageAsync(messageType, dataFunction, CancellationToken.None).ConfigureAwait(false)); - } - - /// - /// Sends a message to all clients currently connected via a web socket - /// - /// - /// Type of the message. - /// The function that generates the data to send, if there are any connected clients - /// The cancellation token. - /// Task. - /// messageType - public async Task SendWebSocketMessageAsync(string messageType, Func 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 { 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); - } - } - - /// - /// Disposes the udp server - /// - private void DisposeUdpServer() - { - if (UdpServer != null) - { - UdpServer.MessageReceived -= UdpServer_MessageReceived; - UdpServer.Dispose(); - } - } - - /// - /// Disposes the current HttpServer - /// - 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(); - } - - /// - /// Registers the server with administrator access. - /// - 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(); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// 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) - { - DisposeUdpServer(); - DisposeHttpServer(); - } - } - - /// - /// Disposes the external web socket server. - /// - private void DisposeExternalWebSocketServer() - { - if (ExternalWebSocketServer != null) - { - ExternalWebSocketServer.Dispose(); - } - } - - /// - /// Called when [application configuration changed]. - /// - /// The old config. - /// The new config. - 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 @@ + - @@ -103,12 +103,12 @@ + - @@ -138,7 +138,6 @@ - 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 + { + /// + /// Gets or sets the receive action. + /// + /// The receive action. + Action OnReceive { get; set; } + + /// + /// Gets the state. + /// + /// The state. + WebSocketState State { get; } + + /// + /// Gets the remote end point. + /// + /// The remote end point. + string RemoteEndPoint { get; } + + /// + /// Sends a message asynchronously. + /// + /// + /// The message. + /// The cancellation token. + /// Task. + /// message + Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken); + + /// + /// Sends a message asynchronously. + /// + /// The buffer. + /// The cancellation token. + /// Task. + Task SendAsync(byte[] buffer, CancellationToken cancellationToken); + + /// + /// Sends a message asynchronously. + /// + /// The buffer. + /// The type. + /// The cancellation token. + /// Task. + /// buffer + Task SendAsync(byte[] buffer, WebSocketMessageType type, CancellationToken cancellationToken); + } + + /// + /// Class WebSocketMessage + /// + /// + public class WebSocketMessage + { + /// + /// Gets or sets the type of the message. + /// + /// The type of the message. + public string MessageType { get; set; } + /// + /// Gets or sets the data. + /// + /// The data. + public T Data { get; set; } + } + + /// + /// Class WebSocketMessageInfo + /// + public class WebSocketMessageInfo : WebSocketMessage + { + /// + /// Gets or sets the connection. + /// + /// The connection. + 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]. /// event EventHandler WebSocketConnected; + + /// + /// Gets the port. + /// + /// The port. + 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 -{ - /// - /// Class WebSocketConnection - /// - public class WebSocketConnection : IDisposable - { - /// - /// The _socket - /// - private readonly IWebSocket _socket; - - /// - /// The _remote end point - /// - public readonly string RemoteEndPoint; - - /// - /// The _cancellation token source - /// - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - /// - /// The _send semaphore - /// - private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1,1); - - /// - /// The logger - /// - private readonly ILogger _logger; - - /// - /// The _json serializer - /// - private readonly IJsonSerializer _jsonSerializer; - - /// - /// Gets or sets the receive action. - /// - /// The receive action. - public Action OnReceive { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The socket. - /// The remote end point. - /// The json serializer. - /// The logger. - /// socket - 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; - } - - /// - /// Called when [receive]. - /// - /// The bytes. - 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); - } - } - - /// - /// Sends a message asynchronously. - /// - /// - /// The message. - /// The cancellation token. - /// Task. - /// message - public Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken) - { - if (message == null) - { - throw new ArgumentNullException("message"); - } - - var bytes = _jsonSerializer.SerializeToBytes(message); - - return SendAsync(bytes, cancellationToken); - } - - /// - /// Sends a message asynchronously. - /// - /// The buffer. - /// The cancellation token. - /// Task. - public Task SendAsync(byte[] buffer, CancellationToken cancellationToken) - { - return SendAsync(buffer, WebSocketMessageType.Text, cancellationToken); - } - - /// - /// Sends a message asynchronously. - /// - /// The buffer. - /// The type. - /// The cancellation token. - /// Task. - /// buffer - 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(); - } - } - - /// - /// Gets the state. - /// - /// The state. - public WebSocketState State - { - get { return _socket.State; } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// 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) - { - _cancellationTokenSource.Dispose(); - _socket.Dispose(); - } - } - } - - /// - /// Class WebSocketMessage - /// - /// - public class WebSocketMessage - { - /// - /// Gets or sets the type of the message. - /// - /// The type of the message. - public string MessageType { get; set; } - /// - /// Gets or sets the data. - /// - /// The data. - public T Data { get; set; } - } - - /// - /// Class WebSocketMessageInfo - /// - public class WebSocketMessageInfo : WebSocketMessage - { - /// - /// Gets or sets the connection. - /// - /// The connection. - public WebSocketConnection Connection { get; set; } - } -} diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index d879b888b..ed49d26d8 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -12,7 +12,6 @@ using MediaBrowser.Controller.Playback; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; -using MediaBrowser.Controller.ScheduledTasks; using MediaBrowser.Controller.Updates; using MediaBrowser.Controller.Weather; using MediaBrowser.Model.Configuration; @@ -293,8 +292,6 @@ namespace MediaBrowser.Controller get { return 7359; } } - private readonly ITaskManager _taskManager; - /// /// Creates a kernel based on a Data path, which is akin to our current programdata path /// @@ -304,13 +301,11 @@ namespace MediaBrowser.Controller /// The task manager. /// The logger. /// isoManager - public Kernel(IApplicationHost appHost, IServerApplicationPaths appPaths, IXmlSerializer xmlSerializer, ITaskManager taskManager, ILogger logger) + public Kernel(IApplicationHost appHost, IServerApplicationPaths appPaths, IXmlSerializer xmlSerializer, ILogger logger) : base(appHost, appPaths, xmlSerializer, logger) { Instance = this; - _taskManager = taskManager; - // For now there's no real way to inject this properly BaseItem.Logger = logger; Ratings.Logger = logger; @@ -469,7 +464,7 @@ namespace MediaBrowser.Controller { DisposeFileSystemManager(); - FileSystemManager = new FileSystemManager(this, Logger, _taskManager); + FileSystemManager = new FileSystemManager(this, Logger, ApplicationHost.Resolve()); FileSystemManager.StartWatchers(); } @@ -540,18 +535,6 @@ namespace MediaBrowser.Controller var reloadLogger = config.ShowLogWindow != oldConfiguration.ShowLogWindow; - // Figure out whether or not we should refresh people after the update is finished - var refreshPeopleAfterUpdate = !oldConfiguration.EnableInternetProviders && config.EnableInternetProviders; - - // This is true if internet providers has just been turned on, or if People have just been removed from InternetProviderExcludeTypes - if (!refreshPeopleAfterUpdate) - { - var oldConfigurationFetchesPeopleImages = oldConfiguration.InternetProviderExcludeTypes == null || !oldConfiguration.InternetProviderExcludeTypes.Contains(typeof(Person).Name, StringComparer.OrdinalIgnoreCase); - var newConfigurationFetchesPeopleImages = config.InternetProviderExcludeTypes == null || !config.InternetProviderExcludeTypes.Contains(typeof(Person).Name, StringComparer.OrdinalIgnoreCase); - - refreshPeopleAfterUpdate = newConfigurationFetchesPeopleImages && !oldConfigurationFetchesPeopleImages; - } - Configuration = config; SaveConfiguration(); @@ -560,20 +543,10 @@ namespace MediaBrowser.Controller ReloadLogger(); } - TcpManager.OnApplicationConfigurationChanged(oldConfiguration, config); - // Validate currently executing providers, in the background Task.Run(() => { ProviderManager.ValidateCurrentlyRunningProviders(); - - // Any number of configuration settings could change the way the library is refreshed, so do that now - _taskManager.CancelIfRunningAndQueue(); - - if (refreshPeopleAfterUpdate) - { - _taskManager.CancelIfRunningAndQueue(); - } }); } diff --git a/MediaBrowser.Controller/Library/LibraryManager.cs b/MediaBrowser.Controller/Library/LibraryManager.cs index c0c1fe1d6..53f4af4b2 100644 --- a/MediaBrowser.Controller/Library/LibraryManager.cs +++ b/MediaBrowser.Controller/Library/LibraryManager.cs @@ -1,8 +1,10 @@ using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Controller.ScheduledTasks; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MoreLinq; @@ -20,7 +22,7 @@ namespace MediaBrowser.Controller.Library /// /// Class LibraryManager /// - public class LibraryManager : BaseManager + public class LibraryManager { #region LibraryChanged Event /// @@ -48,7 +50,7 @@ namespace MediaBrowser.Controller.Library private void SendLibraryChangedWebSocketMessage(ChildrenChangedEventArgs args) { // Notify connected ui's - Kernel.TcpManager.SendWebSocketMessage("LibraryChanged", () => DtoBuilder.GetLibraryUpdateInfo(args)); + Kernel.ServerManager.SendWebSocketMessage("LibraryChanged", () => DtoBuilder.GetLibraryUpdateInfo(args)); } #endregion @@ -57,15 +59,57 @@ namespace MediaBrowser.Controller.Library /// private readonly ILogger _logger; + /// + /// The _task manager + /// + private readonly ITaskManager _taskManager; + + /// + /// Gets or sets the kernel. + /// + /// The kernel. + private Kernel Kernel { get; set; } + /// /// Initializes a new instance of the class. /// /// The kernel. /// The logger. - public LibraryManager(Kernel kernel, ILogger logger) - : base(kernel) + /// The task manager. + public LibraryManager(Kernel kernel, ILogger logger, ITaskManager taskManager) { + Kernel = kernel; _logger = logger; + _taskManager = taskManager; + + kernel.ConfigurationUpdated += kernel_ConfigurationUpdated; + } + + /// + /// Handles the ConfigurationUpdated event of the kernel control. + /// + /// The source of the event. + /// The instance containing the event data. + void kernel_ConfigurationUpdated(object sender, EventArgs e) + { + //// Figure out whether or not we should refresh people after the update is finished + //var refreshPeopleAfterUpdate = !oldConfiguration.EnableInternetProviders && config.EnableInternetProviders; + + //// This is true if internet providers has just been turned on, or if People have just been removed from InternetProviderExcludeTypes + //if (!refreshPeopleAfterUpdate) + //{ + // var oldConfigurationFetchesPeopleImages = oldConfiguration.InternetProviderExcludeTypes == null || !oldConfiguration.InternetProviderExcludeTypes.Contains(typeof(Person).Name, StringComparer.OrdinalIgnoreCase); + // var newConfigurationFetchesPeopleImages = config.InternetProviderExcludeTypes == null || !config.InternetProviderExcludeTypes.Contains(typeof(Person).Name, StringComparer.OrdinalIgnoreCase); + + // refreshPeopleAfterUpdate = newConfigurationFetchesPeopleImages && !oldConfigurationFetchesPeopleImages; + //} + + Task.Run(() => + { + // Any number of configuration settings could change the way the library is refreshed, so do that now + _taskManager.CancelIfRunningAndQueue(); + _taskManager.CancelIfRunningAndQueue(); + }); } /// diff --git a/MediaBrowser.Controller/Library/UserManager.cs b/MediaBrowser.Controller/Library/UserManager.cs index 8084366b7..5340e70be 100644 --- a/MediaBrowser.Controller/Library/UserManager.cs +++ b/MediaBrowser.Controller/Library/UserManager.cs @@ -73,7 +73,7 @@ namespace MediaBrowser.Controller.Library EventHelper.QueueEventIfNotNull(UserUpdated, this, new GenericEventArgs { Argument = user }, _logger); // Notify connected ui's - Kernel.TcpManager.SendWebSocketMessage("UserUpdated", new DtoBuilder(_logger).GetDtoUser(user)); + Kernel.ServerManager.SendWebSocketMessage("UserUpdated", new DtoBuilder(_logger).GetDtoUser(user)); } #endregion @@ -91,7 +91,7 @@ namespace MediaBrowser.Controller.Library EventHelper.QueueEventIfNotNull(UserDeleted, this, new GenericEventArgs { Argument = user }, _logger); // Notify connected ui's - Kernel.TcpManager.SendWebSocketMessage("UserDeleted", user.Id.ToString()); + Kernel.ServerManager.SendWebSocketMessage("UserDeleted", user.Id.ToString()); } #endregion diff --git a/MediaBrowser.Controller/Updates/InstallationManager.cs b/MediaBrowser.Controller/Updates/InstallationManager.cs index 8751bd427..efef87f86 100644 --- a/MediaBrowser.Controller/Updates/InstallationManager.cs +++ b/MediaBrowser.Controller/Updates/InstallationManager.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Updates EventHelper.QueueEventIfNotNull(PluginUninstalled, this, new GenericEventArgs { Argument = plugin }, _logger); // Notify connected ui's - Kernel.TcpManager.SendWebSocketMessage("PluginUninstalled", plugin.GetPluginInfo()); + Kernel.ServerManager.SendWebSocketMessage("PluginUninstalled", plugin.GetPluginInfo()); } #endregion @@ -371,7 +371,7 @@ namespace MediaBrowser.Controller.Updates var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token; - Kernel.TcpManager.SendWebSocketMessage("PackageInstalling", installationInfo); + Kernel.ServerManager.SendWebSocketMessage("PackageInstalling", installationInfo); try { @@ -384,7 +384,7 @@ namespace MediaBrowser.Controller.Updates CompletedInstallations.Add(installationInfo); - Kernel.TcpManager.SendWebSocketMessage("PackageInstallationCompleted", installationInfo); + Kernel.ServerManager.SendWebSocketMessage("PackageInstallationCompleted", installationInfo); } catch (OperationCanceledException) { @@ -395,7 +395,7 @@ namespace MediaBrowser.Controller.Updates _logger.Info("Package installation cancelled: {0} {1}", package.name, package.versionStr); - Kernel.TcpManager.SendWebSocketMessage("PackageInstallationCancelled", installationInfo); + Kernel.ServerManager.SendWebSocketMessage("PackageInstallationCancelled", installationInfo); throw; } @@ -406,7 +406,7 @@ namespace MediaBrowser.Controller.Updates CurrentInstallations.Remove(tuple); } - Kernel.TcpManager.SendWebSocketMessage("PackageInstallationFailed", installationInfo); + Kernel.ServerManager.SendWebSocketMessage("PackageInstallationFailed", installationInfo); throw; } diff --git a/MediaBrowser.Networking/Udp/UdpServer.cs b/MediaBrowser.Networking/Udp/UdpServer.cs index 9b1c254cf..00eb2a0d1 100644 --- a/MediaBrowser.Networking/Udp/UdpServer.cs +++ b/MediaBrowser.Networking/Udp/UdpServer.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Model.Logging; using MediaBrowser.Networking.Management; using System; using System.Net; @@ -19,6 +20,21 @@ namespace MediaBrowser.Networking.Udp /// public event EventHandler MessageReceived; + /// + /// Gets or sets the logger. + /// + /// The logger. + private ILogger Logger { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + public UdpServer(ILogger logger) + { + Logger = logger; + } + /// /// Raises the event. /// @@ -54,8 +70,24 @@ namespace MediaBrowser.Networking.Udp private IObservable CreateObservable() { return Observable.Create(obs => - Observable.FromAsync(() => _udpClient.ReceiveAsync()) - .Subscribe(obs)) + Observable.FromAsync(() => + { + 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))); + } + }) + + .Subscribe(obs)) .Repeat() .Retry() .Publish() @@ -68,6 +100,10 @@ namespace MediaBrowser.Networking.Udp /// The message. private void OnMessageReceived(UdpReceiveResult message) { + if (message.RemoteEndPoint.Port == 0) + { + return; + } var bytes = message.Buffer; OnMessageReceived(new UdpMessageReceivedEventArgs diff --git a/MediaBrowser.Networking/WebSocket/AlchemyServer.cs b/MediaBrowser.Networking/WebSocket/AlchemyServer.cs index 1a3971c7f..b3d5d710e 100644 --- a/MediaBrowser.Networking/WebSocket/AlchemyServer.cs +++ b/MediaBrowser.Networking/WebSocket/AlchemyServer.cs @@ -42,6 +42,12 @@ namespace MediaBrowser.Networking.WebSocket _logger = logger; } + /// + /// Gets the port. + /// + /// The port. + public int Port { get; private set; } + /// /// Starts the specified port number. /// @@ -56,6 +62,8 @@ namespace MediaBrowser.Networking.WebSocket WebSocketServer.Start(); + Port = portNumber; + _logger.Info("Alchemy Web Socket Server started"); } diff --git a/MediaBrowser.ServerApplication/App.xaml.cs b/MediaBrowser.ServerApplication/App.xaml.cs index f6f70b382..1199aeaf1 100644 --- a/MediaBrowser.ServerApplication/App.xaml.cs +++ b/MediaBrowser.ServerApplication/App.xaml.cs @@ -255,7 +255,6 @@ namespace MediaBrowser.ServerApplication base.OnExit(e); - Kernel.Dispose(); CompositionRoot.Dispose(); } @@ -360,7 +359,7 @@ namespace MediaBrowser.ServerApplication { Dispatcher.Invoke(ReleaseMutex); - Kernel.Dispose(); + CompositionRoot.Dispose(); System.Windows.Forms.Application.Restart(); diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index e862a394e..07246eded 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -4,6 +4,7 @@ using MediaBrowser.Common.Implementations; using MediaBrowser.Common.Implementations.ScheduledTasks; using MediaBrowser.Common.Implementations.Serialization; using MediaBrowser.Common.IO; +using MediaBrowser.Common.Implementations.Server; using MediaBrowser.Common.Kernel; using MediaBrowser.Common.Net; using MediaBrowser.Common.ScheduledTasks; @@ -65,16 +66,6 @@ namespace MediaBrowser.ServerApplication /// private readonly IServerApplicationPaths _applicationPaths = new ServerApplicationPaths(); - /// - /// The _task manager - /// - private readonly ITaskManager _taskManager; - - /// - /// The _task manager - /// - private readonly IHttpServer _httpServer; - /// /// Gets a value indicating whether this instance is first run. /// @@ -89,29 +80,34 @@ namespace MediaBrowser.ServerApplication : base() { IsFirstRun = !File.Exists(_applicationPaths.SystemConfigurationFilePath); - + Logger = new NLogger("App"); DiscoverTypes(); - _taskManager = new TaskManager(_applicationPaths, _jsonSerializer, Logger); + Kernel = new Kernel(this, _applicationPaths, _xmlSerializer, Logger); + + var networkManager = new NetworkManager(); + + var serverManager = new ServerManager(this, Kernel, networkManager, _jsonSerializer, Logger); + + var taskManager = new TaskManager(_applicationPaths, _jsonSerializer, Logger, serverManager); - Kernel = new Kernel(this, _applicationPaths, _xmlSerializer, _taskManager, Logger); ReloadLogger(); Logger.Info("Version {0} initializing", ApplicationVersion); - _httpServer = ServerFactory.CreateServer(this, ProtobufSerializer, Logger, "Media Browser", "index.html"); + var httpServer = ServerFactory.CreateServer(this, ProtobufSerializer, Logger, "Media Browser", "index.html"); - RegisterResources(); + RegisterResources(taskManager, httpServer, networkManager, serverManager); - FindParts(); + FindParts(taskManager, httpServer); } /// /// Registers resources that classes will depend on /// - private void RegisterResources() + private void RegisterResources(ITaskManager taskManager, IHttpServer httpServer, INetworkManager networkManager, IServerManager serverManager) { RegisterSingleInstance(Kernel); RegisterSingleInstance(Kernel); @@ -121,28 +117,31 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(_applicationPaths); RegisterSingleInstance(_applicationPaths); - RegisterSingleInstance(_taskManager); + RegisterSingleInstance(taskManager); RegisterSingleInstance(new PismoIsoManager(Logger)); RegisterSingleInstance(new BdInfoExaminer()); RegisterSingleInstance(new HttpManager(_applicationPaths, Logger)); - RegisterSingleInstance(new NetworkManager()); RegisterSingleInstance(new DotNetZipClient()); RegisterSingleInstance(() => new AlchemyServer(Logger)); RegisterSingleInstance(_jsonSerializer); RegisterSingleInstance(_xmlSerializer); RegisterSingleInstance(ProtobufSerializer); - RegisterSingleInstance(new UdpServer()); - RegisterSingleInstance(_httpServer); + RegisterSingleInstance(new UdpServer(Logger), false); + RegisterSingleInstance(httpServer, false); + + RegisterSingleInstance(networkManager); + + RegisterSingleInstance(serverManager); } /// /// Finds the parts. /// - private void FindParts() + private void FindParts(ITaskManager taskManager, IHttpServer httpServer) { - _taskManager.AddTasks(GetExports(false)); + taskManager.AddTasks(GetExports(false)); - _httpServer.Init(GetExports(false)); + httpServer.Init(GetExports(false)); } /// @@ -240,5 +239,13 @@ namespace MediaBrowser.ServerApplication // Include composable parts in the running assembly yield return GetType().Assembly; } + + /// + /// Shuts down. + /// + public void Shutdown() + { + App.Instance.Shutdown(); + } } } diff --git a/MediaBrowser.WebDashboard/Html/css/site.css b/MediaBrowser.WebDashboard/Html/css/site.css index 32818e3cc..3e32d4e1f 100644 --- a/MediaBrowser.WebDashboard/Html/css/site.css +++ b/MediaBrowser.WebDashboard/Html/css/site.css @@ -293,10 +293,6 @@ form, .readOnlyContent { right: 30px; } - .localnav .ui-btn-inner { - font-size: 16px; - } - .libraryPage .ui-content { padding-right: 50px; padding-left: 50px; diff --git a/Nuget/MediaBrowser.ApiClient.nuspec b/Nuget/MediaBrowser.ApiClient.nuspec index 3d6ec3a31..a13bfd84c 100644 --- a/Nuget/MediaBrowser.ApiClient.nuspec +++ b/Nuget/MediaBrowser.ApiClient.nuspec @@ -2,7 +2,7 @@ MediaBrowser.ApiClient - 3.0.0.6-beta + 3.0.0.7-beta MediaBrowser.ApiClient Media Browser Team scottisafool,Luke diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 6fc5414b6..d215e503b 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.6 + 3.0.7 <authors>Luke</authors> <owners>Media Browser Team</owners> @@ -10,7 +10,7 @@ <requireLicenseAcceptance>false</requireLicenseAcceptance> <description>Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.</description> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.6" /> + <dependency id="MediaBrowser.Common" version="3.0.7" /> <dependency id="NLog" version="2.0.0.2000" /> <dependency id="ServiceStack" version="3.9.37" /> <dependency id="ServiceStack.Api.Swagger" version="3.9.35" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 1044fd633..c529f29a3 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.6</version> + <version>3.0.7</version> <title>MediaBrowser.Common Media Browser Team diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index b152a5d3e..d07345f09 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.6 + 3.0.7 Media Browser.Server.Core Media Browser Team @@ -10,7 +10,7 @@ false Contains core components required to build plugins for Media Browser Server. - + -- cgit v1.2.3