diff options
20 files changed, 357 insertions, 191 deletions
diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs index 3360e5e9e..9bbd6a588 100644 --- a/MediaBrowser.Api/SystemService.cs +++ b/MediaBrowser.Api/SystemService.cs @@ -133,8 +133,8 @@ namespace MediaBrowser.Api { Task.Run(async () => { - await Task.Delay(100); - _appHost.Restart(); + await Task.Delay(100).ConfigureAwait(false); + await _appHost.Restart().ConfigureAwait(false); }); } @@ -146,8 +146,8 @@ namespace MediaBrowser.Api { Task.Run(async () => { - await Task.Delay(100); - _appHost.Shutdown(); + await Task.Delay(100).ConfigureAwait(false); + await _appHost.Shutdown().ConfigureAwait(false); }); } diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index bae2f9f07..f333eb22c 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -552,7 +552,7 @@ namespace MediaBrowser.Common.Implementations /// <summary> /// Restarts this instance. /// </summary> - public abstract void Restart(); + public abstract Task Restart(); /// <summary> /// Gets or sets a value indicating whether this instance can self update. @@ -582,7 +582,7 @@ namespace MediaBrowser.Common.Implementations /// <summary> /// Shuts down. /// </summary> - public abstract void Shutdown(); + public abstract Task Shutdown(); /// <summary> /// Called when [application updated]. diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 1ff28d924..7cb58f580 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Common /// <summary> /// Restarts this instance. /// </summary> - void Restart(); + Task Restart(); /// <summary> /// Gets the application version. @@ -113,7 +113,7 @@ namespace MediaBrowser.Common /// <summary> /// Shuts down. /// </summary> - void Shutdown(); + Task Shutdown(); /// <summary> /// Gets the plugins. diff --git a/MediaBrowser.Common/Net/IServerManager.cs b/MediaBrowser.Common/Net/IServerManager.cs index 3234e7060..32be88a34 100644 --- a/MediaBrowser.Common/Net/IServerManager.cs +++ b/MediaBrowser.Common/Net/IServerManager.cs @@ -5,6 +5,9 @@ using System.Threading.Tasks; namespace MediaBrowser.Common.Net { + /// <summary> + /// Interface IServerManager + /// </summary> public interface IServerManager : IDisposable { /// <summary> @@ -22,7 +25,14 @@ namespace MediaBrowser.Common.Net /// <summary> /// Starts this instance. /// </summary> - void Start(); + /// <param name="urlPrefix">The URL prefix.</param> + /// <param name="enableHttpLogging">if set to <c>true</c> [enable HTTP logging].</param> + void Start(string urlPrefix, bool enableHttpLogging); + + /// <summary> + /// Starts the web socket server. + /// </summary> + void StartWebSocketServer(); /// <summary> /// Sends a message to all clients currently connected via a web socket @@ -62,7 +72,7 @@ namespace MediaBrowser.Common.Net /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> Task SendWebSocketMessageAsync<T>(string messageType, Func<T> dataFunction, IEnumerable<IWebSocketConnection> connections, CancellationToken cancellationToken); - + /// <summary> /// Adds the web socket listeners. /// </summary> diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index c7df48c04..e4c68a8ab 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -77,5 +77,14 @@ namespace MediaBrowser.Controller.Drawing /// <param name="toStream">To stream.</param> /// <returns>Task.</returns> Task ProcessImage(ImageProcessingOptions options, Stream toStream); + + /// <summary> + /// Gets the enhanced image. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="imageType">Type of the image.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <returns>Task{System.String}.</returns> + Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex); } } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index f96c2536e..495147125 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -25,5 +25,11 @@ namespace MediaBrowser.Controller /// </summary> /// <value>The HTTP server URL prefix.</value> string HttpServerUrlPrefix { get; } + + /// <summary> + /// Gets a value indicating whether this instance is background service. + /// </summary> + /// <value><c>true</c> if this instance is background service; otherwise, <c>false</c>.</value> + bool IsBackgroundService { get; } } } diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index f77856c6e..b00d7f29d 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -81,7 +81,13 @@ namespace MediaBrowser.Model.System public int HttpServerPortNumber { get; set; } /// <summary> - /// Initializes a new instance of the <see cref="SystemInfo"/> class. + /// Gets or sets a value indicating whether this instance is background service. + /// </summary> + /// <value><c>true</c> if this instance is background service; otherwise, <c>false</c>.</value> + public bool IsBackgroundService { get; set; } + + /// <summary> + /// Initializes a new instance of the <see cref="SystemInfo" /> class. /// </summary> public SystemInfo() { diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index c9f9af4f2..6458435ba 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -539,6 +539,26 @@ namespace MediaBrowser.Server.Implementations.Drawing return string.Join("|", cacheKeys.ToArray()).GetMD5(); } + /// <summary> + /// Gets the enhanced image. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="imageType">Type of the image.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <returns>Task{System.String}.</returns> + public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex) + { + var enhancers = GetSupportedEnhancers(item, imageType).ToList(); + + var imagePath = item.GetImagePath(imageType, imageIndex); + + var dateModified = item.GetImageDateModified(imagePath); + + var result = await GetEnhancedImage(imagePath, dateModified, item, imageType, imageIndex, enhancers); + + return result.Item1; + } + private async Task<Tuple<string, DateTime>> GetEnhancedImage(string originalImagePath, DateTime dateModified, BaseItem item, ImageType imageType, int imageIndex, List<IImageEnhancer> enhancers) diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs index aa30cee26..7792a28bb 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs @@ -529,7 +529,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer { new ClientWebSocket(); - _supportsNativeWebSocket = true; + _supportsNativeWebSocket = false; } catch (PlatformNotSupportedException) { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index bf6526538..f6fd11960 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -69,7 +70,16 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio foreach (var fullName in Directory.EnumerateFiles(path)) { - if (EntityResolutionHelper.IsAudioFile(fullName)) foundAudio++; + if (EntityResolutionHelper.IsAudioFile(fullName)) + { + // Don't resolve these into audio files + if (string.Equals(Path.GetFileNameWithoutExtension(fullName), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(fullName)) + { + continue; + } + + foundAudio++; + } if (foundAudio >= 2) { return true; diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs index b63cf0031..a840ae214 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs @@ -118,43 +118,44 @@ namespace MediaBrowser.Server.Implementations.ServerManager _jsonSerializer = jsonSerializer; _applicationHost = applicationHost; ConfigurationManager = configurationManager; - - ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; } /// <summary> /// Starts this instance. /// </summary> - public void Start() + public void Start(string urlPrefix, bool enableHttpLogging) { - ReloadHttpServer(); + ReloadHttpServer(urlPrefix, enableHttpLogging); + } + public void StartWebSocketServer() + { if (!SupportsNativeWebSocket) { - ReloadExternalWebSocketServer(); + ReloadExternalWebSocketServer(ConfigurationManager.Configuration.LegacyWebSocketPortNumber); } } /// <summary> /// Starts the external web socket server. /// </summary> - private void ReloadExternalWebSocketServer() + private void ReloadExternalWebSocketServer(int portNumber) { DisposeExternalWebSocketServer(); ExternalWebSocketServer = _applicationHost.Resolve<IWebSocketServer>(); - ExternalWebSocketServer.Start(ConfigurationManager.Configuration.LegacyWebSocketPortNumber); + ExternalWebSocketServer.Start(portNumber); ExternalWebSocketServer.WebSocketConnected += HttpServer_WebSocketConnected; } /// <summary> /// Restarts the Http Server, or starts it if not currently running /// </summary> - private void ReloadHttpServer() + private void ReloadHttpServer(string urlPrefix, bool enableHttpLogging) { // Only reload if the port has changed, so that we don't disconnect any active users - if (HttpServer != null && HttpServer.UrlPrefix.Equals(_applicationHost.HttpServerUrlPrefix, StringComparison.OrdinalIgnoreCase)) + if (HttpServer != null && HttpServer.UrlPrefix.Equals(urlPrefix, StringComparison.OrdinalIgnoreCase)) { return; } @@ -166,8 +167,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager try { HttpServer = _applicationHost.Resolve<IHttpServer>(); - HttpServer.EnableHttpRequestLogging = ConfigurationManager.Configuration.EnableHttpLevelLogging; - HttpServer.Start(_applicationHost.HttpServerUrlPrefix); + HttpServer.EnableHttpRequestLogging = enableHttpLogging; + HttpServer.Start(urlPrefix); } catch (SocketException ex) { @@ -376,17 +377,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager } /// <summary> - /// Handles the ConfigurationUpdated event of the _kernel control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> - /// <exception cref="System.NotImplementedException"></exception> - void ConfigurationUpdated(object sender, EventArgs e) - { - HttpServer.EnableHttpRequestLogging = ConfigurationManager.Configuration.EnableHttpLevelLogging; - } - - /// <summary> /// Adds the web socket listeners. /// </summary> /// <param name="listeners">The listeners.</param> diff --git a/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs b/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs index 6649fd197..1470a209e 100644 --- a/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs +++ b/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs @@ -55,14 +55,14 @@ namespace MediaBrowser.Server.Implementations.WebSocket /// <param name="portNumber">The port number.</param> public void Start(int portNumber) { - WebSocketServer = new WebSocketServer(portNumber, IPAddress.Any) - { - OnConnected = OnAlchemyWebSocketClientConnected, - TimeOut = TimeSpan.FromHours(12) - }; - try { + WebSocketServer = new WebSocketServer(portNumber, IPAddress.Any) + { + OnConnected = OnAlchemyWebSocketClientConnected, + TimeOut = TimeSpan.FromHours(12) + }; + WebSocketServer.Start(); } catch (SocketException ex) diff --git a/MediaBrowser.ServerApplication/App.xaml.cs b/MediaBrowser.ServerApplication/App.xaml.cs index 362dd4bc0..42045257a 100644 --- a/MediaBrowser.ServerApplication/App.xaml.cs +++ b/MediaBrowser.ServerApplication/App.xaml.cs @@ -1,90 +1,20 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Constants; -using MediaBrowser.Common.Implementations.Updates; -using MediaBrowser.Controller; +using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Server.Implementations; using MediaBrowser.ServerApplication.Splash; -using Microsoft.Win32; using System; using System.Diagnostics; -using System.IO; -using System.Net.Cache; -using System.Threading; using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Media.Imaging; namespace MediaBrowser.ServerApplication { /// <summary> /// Interaction logic for App.xaml /// </summary> - public partial class App : Application + public partial class App : Application, IApplicationInterface { /// <summary> - /// The single instance mutex - /// </summary> - private static Mutex _singleInstanceMutex; - - /// <summary> - /// Defines the entry point of the application. - /// </summary> - [STAThread] - public static void Main() - { - bool createdNew; - - var runningPath = Process.GetCurrentProcess().MainModule.FileName.Replace(Path.DirectorySeparatorChar.ToString(), string.Empty); - - _singleInstanceMutex = new Mutex(true, @"Local\" + runningPath, out createdNew); - - if (!createdNew) - { - _singleInstanceMutex = null; - return; - } - - // Look for the existence of an update archive - var appPaths = new ServerApplicationPaths(); - var updateArchive = Path.Combine(appPaths.TempUpdatePath, Constants.MbServerPkgName + ".zip"); - if (File.Exists(updateArchive)) - { - // Update is there - execute update - try - { - new ApplicationUpdater().UpdateApplication(MBApplication.MBServer, appPaths, updateArchive); - - // And just let the app exit so it can update - return; - } - catch (Exception e) - { - MessageBox.Show(string.Format("Error attempting to update application.\n\n{0}\n\n{1}", e.GetType().Name, e.Message)); - } - } - - var application = new App(); - - application.Run(); - } - - /// <summary> - /// Gets the instance. - /// </summary> - /// <value>The instance.</value> - public static App Instance - { - get - { - return Current as App; - } - } - - /// <summary> /// Gets or sets the logger. /// </summary> /// <value>The logger.</value> @@ -95,7 +25,7 @@ namespace MediaBrowser.ServerApplication /// </summary> /// <value>The composition root.</value> protected ApplicationHost CompositionRoot { get; set; } - + /// <summary> /// Initializes a new instance of the <see cref="App" /> class. /// </summary> @@ -105,6 +35,11 @@ namespace MediaBrowser.ServerApplication InitializeComponent(); } + public bool IsBackgroundService + { + get { return false; } + } + /// <summary> /// Gets the name of the uninstaller file. /// </summary> @@ -114,63 +49,35 @@ namespace MediaBrowser.ServerApplication get { return "MediaBrowser.Server.Uninstall.exe"; } } - /// <summary> - /// Raises the <see cref="E:System.Windows.Application.Startup" /> event. - /// </summary> - /// <param name="e">A <see cref="T:System.Windows.StartupEventArgs" /> that contains the event data.</param> - protected override void OnStartup(StartupEventArgs e) + public void OnUnhandledException(Exception ex) { - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - LoadKernel(); + Logger.ErrorException("UnhandledException", ex); - SystemEvents.SessionEnding += SystemEvents_SessionEnding; + MessageBox.Show("Unhandled exception: " + ex.Message); } - /// <summary> - /// Handles the UnhandledException event of the CurrentDomain control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="UnhandledExceptionEventArgs" /> instance containing the event data.</param> - void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + protected override void OnStartup(StartupEventArgs e) { - var exception = (Exception)e.ExceptionObject; - - Logger.ErrorException("UnhandledException", exception); + base.OnStartup(e); - MessageBox.Show("Unhandled exception: " + exception.Message); - - if (!Debugger.IsAttached) - { - Environment.Exit(System.Runtime.InteropServices.Marshal.GetHRForException(exception)); - } - } - - /// <summary> - /// Handles the SessionEnding event of the SystemEvents control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="SessionEndingEventArgs" /> instance containing the event data.</param> - void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) - { - // Try to shut down gracefully - Shutdown(); + LoadApplication(); } /// <summary> /// Loads the kernel. /// </summary> - protected async void LoadKernel() + protected async void LoadApplication() { try { - CompositionRoot = new ApplicationHost(); + CompositionRoot = new ApplicationHost(this); Logger = CompositionRoot.LogManager.GetLogger("App"); var splash = new SplashWindow(CompositionRoot.ApplicationVersion); splash.Show(); - + await CompositionRoot.Init(); splash.Hide(); @@ -192,13 +99,18 @@ namespace MediaBrowser.ServerApplication } } + public void ShutdownApplication() + { + Dispatcher.Invoke(Shutdown); + } + /// <summary> /// Raises the <see cref="E:System.Windows.Application.Exit" /> event. /// </summary> /// <param name="e">An <see cref="T:System.Windows.ExitEventArgs" /> that contains the event data.</param> protected override void OnExit(ExitEventArgs e) { - ReleaseMutex(); + MainStartup.ReleaseMutex(); base.OnExit(e); @@ -209,22 +121,6 @@ namespace MediaBrowser.ServerApplication } /// <summary> - /// Releases the mutex. - /// </summary> - private void ReleaseMutex() - { - if (_singleInstanceMutex == null) - { - return; - } - - _singleInstanceMutex.ReleaseMutex(); - _singleInstanceMutex.Close(); - _singleInstanceMutex.Dispose(); - _singleInstanceMutex = null; - } - - /// <summary> /// Opens the dashboard page. /// </summary> /// <param name="page">The page.</param> @@ -281,9 +177,9 @@ namespace MediaBrowser.ServerApplication /// Restarts this instance. /// </summary> /// <exception cref="System.NotImplementedException"></exception> - public void Restart() + public void RestartApplication() { - Dispatcher.Invoke(ReleaseMutex); + Dispatcher.Invoke(MainStartup.ReleaseMutex); CompositionRoot.Dispose(); diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 126bea9e5..7288d70d9 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -6,7 +6,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Implementations; using MediaBrowser.Common.Implementations.IO; using MediaBrowser.Common.Implementations.ScheduledTasks; -using MediaBrowser.Common.Implementations.Updates; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -183,6 +182,13 @@ namespace MediaBrowser.ServerApplication private IItemRepository ItemRepository { get; set; } private INotificationsRepository NotificationsRepository { get; set; } + public bool IsBackgroundService + { + get { return _appInterface != null && _appInterface.IsBackgroundService; } + } + + private readonly IApplicationInterface _appInterface; + /// <summary> /// The full path to our startmenu shortcut /// </summary> @@ -193,6 +199,11 @@ namespace MediaBrowser.ServerApplication private Task<IHttpServer> _httpServerCreationTask; + public ApplicationHost(IApplicationInterface appInterface) + { + _appInterface = appInterface; + } + /// <summary> /// Runs the startup tasks. /// </summary> @@ -475,7 +486,7 @@ namespace MediaBrowser.ServerApplication { try { - ServerManager.Start(); + ServerManager.Start(HttpServerUrlPrefix, ServerConfigurationManager.Configuration.EnableHttpLevelLogging); } catch { @@ -490,6 +501,8 @@ namespace MediaBrowser.ServerApplication throw; } } + + ServerManager.StartWebSocketServer(); } /// <summary> @@ -501,6 +514,8 @@ namespace MediaBrowser.ServerApplication { base.OnConfigurationUpdated(sender, e); + HttpServer.EnableHttpRequestLogging = ServerConfigurationManager.Configuration.EnableHttpLevelLogging; + if (!string.Equals(HttpServer.UrlPrefix, HttpServerUrlPrefix, StringComparison.OrdinalIgnoreCase)) { NotifyPendingRestart(); @@ -516,19 +531,18 @@ namespace MediaBrowser.ServerApplication /// <summary> /// Restarts this instance. /// </summary> - public override void Restart() + public override async Task Restart() { try { - var task = ServerManager.SendWebSocketMessageAsync("ServerRestarting", () => string.Empty, CancellationToken.None); - task.Wait(); + await ServerManager.SendWebSocketMessageAsync("ServerRestarting", () => string.Empty, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { Logger.ErrorException("Error sending server restart web socket message", ex); } - App.Instance.Restart(); + _appInterface.RestartApplication(); } /// <summary> @@ -613,7 +627,8 @@ namespace MediaBrowser.ServerApplication Id = _systemId, ProgramDataPath = ApplicationPaths.ProgramDataPath, MacAddress = GetMacAddress(), - HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber + HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber, + IsBackgroundService = IsBackgroundService }; } @@ -637,19 +652,18 @@ namespace MediaBrowser.ServerApplication /// <summary> /// Shuts down. /// </summary> - public override void Shutdown() + public override async Task Shutdown() { try { - var task = ServerManager.SendWebSocketMessageAsync("ServerShuttingDown", () => string.Empty, CancellationToken.None); - task.Wait(); + await ServerManager.SendWebSocketMessageAsync("ServerShuttingDown", () => string.Empty, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { Logger.ErrorException("Error sending server shutdown web socket message", ex); } - App.Instance.Dispatcher.Invoke(App.Instance.Shutdown); + _appInterface.ShutdownApplication(); } /// <summary> diff --git a/MediaBrowser.ServerApplication/BackgroundService.cs b/MediaBrowser.ServerApplication/BackgroundService.cs new file mode 100644 index 000000000..a8a9a5b50 --- /dev/null +++ b/MediaBrowser.ServerApplication/BackgroundService.cs @@ -0,0 +1,30 @@ +using System.ServiceProcess; + +namespace MediaBrowser.ServerApplication +{ + public class BackgroundService : ServiceBase + { + public BackgroundService() + { + CanPauseAndContinue = false; + CanHandleSessionChangeEvent = true; + CanStop = false; + CanShutdown = true; + ServiceName = "Media Browser"; + } + + protected override void OnSessionChange(SessionChangeDescription changeDescription) + { + base.OnSessionChange(changeDescription); + } + + protected override void OnStart(string[] args) + { + } + + protected override void OnShutdown() + { + base.OnShutdown(); + } + } +} diff --git a/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs b/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs index 87578ef84..aac5a8cb8 100644 --- a/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs +++ b/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs @@ -64,7 +64,10 @@ namespace MediaBrowser.ServerApplication.EntryPoints { _logger.ErrorException("Error launching startup wizard", ex); - MessageBox.Show("There was an error launching the Media Browser startup wizard. Please ensure a web browser is installed on the machine and is configured as the default browser.", "Media Browser"); + if (!_appHost.IsBackgroundService) + { + MessageBox.Show("There was an error launching the Media Browser startup wizard. Please ensure a web browser is installed on the machine and is configured as the default browser.", "Media Browser"); + } } } diff --git a/MediaBrowser.ServerApplication/IApplicationInterface.cs b/MediaBrowser.ServerApplication/IApplicationInterface.cs new file mode 100644 index 000000000..e75324826 --- /dev/null +++ b/MediaBrowser.ServerApplication/IApplicationInterface.cs @@ -0,0 +1,32 @@ +using System; + +namespace MediaBrowser.ServerApplication +{ + /// <summary> + /// Interface IApplicationInterface + /// </summary> + public interface IApplicationInterface + { + /// <summary> + /// Gets a value indicating whether this instance is background service. + /// </summary> + /// <value><c>true</c> if this instance is background service; otherwise, <c>false</c>.</value> + bool IsBackgroundService { get; } + + /// <summary> + /// Shutdowns the application. + /// </summary> + void ShutdownApplication(); + + /// <summary> + /// Restarts the application. + /// </summary> + void RestartApplication(); + + /// <summary> + /// Called when [unhandled exception]. + /// </summary> + /// <param name="ex">The ex.</param> + void OnUnhandledException(Exception ex); + } +} diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs new file mode 100644 index 000000000..e5d44c0f5 --- /dev/null +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -0,0 +1,134 @@ +using MediaBrowser.Common.Constants; +using MediaBrowser.Common.Implementations.Updates; +using MediaBrowser.Server.Implementations; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Windows; +using Microsoft.Win32; + +namespace MediaBrowser.ServerApplication +{ + public class MainStartup + { + /// <summary> + /// The single instance mutex + /// </summary> + private static Mutex _singleInstanceMutex; + + private static IApplicationInterface _applicationInterface; + + /// <summary> + /// Defines the entry point of the application. + /// </summary> + [STAThread] + public static void Main() + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + + bool createdNew; + + var runningPath = Process.GetCurrentProcess().MainModule.FileName.Replace(Path.DirectorySeparatorChar.ToString(), string.Empty); + + _singleInstanceMutex = new Mutex(true, @"Local\" + runningPath, out createdNew); + + if (!createdNew) + { + _singleInstanceMutex = null; + return; + } + + // Look for the existence of an update archive + var appPaths = new ServerApplicationPaths(); + var updateArchive = Path.Combine(appPaths.TempUpdatePath, Constants.MbServerPkgName + ".zip"); + if (File.Exists(updateArchive)) + { + // Update is there - execute update + try + { + new ApplicationUpdater().UpdateApplication(MBApplication.MBServer, appPaths, updateArchive); + + // And just let the app exit so it can update + return; + } + catch (Exception e) + { + MessageBox.Show(string.Format("Error attempting to update application.\n\n{0}\n\n{1}", e.GetType().Name, e.Message)); + } + } + + StartApplication(); + } + + private static void StartApplication() + { + SystemEvents.SessionEnding += SystemEvents_SessionEnding; + var commandLineArgs = Environment.GetCommandLineArgs(); + + if (commandLineArgs.Length > 1 && commandLineArgs[1].Equals("-service")) + { + // Start application as a service + StartBackgroundService(); + } + else + { + StartWpfApp(); + } + } + + static void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) + { + // Try to shutdown gracefully + if (_applicationInterface != null) + { + _applicationInterface.ShutdownApplication(); + } + } + + private static void StartWpfApp() + { + var app = new App(); + + _applicationInterface = app; + + app.Run(); + } + + private static void StartBackgroundService() + { + + } + + static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + var exception = (Exception)e.ExceptionObject; + + if (_applicationInterface != null) + { + _applicationInterface.OnUnhandledException(exception); + } + + if (!Debugger.IsAttached) + { + Environment.Exit(System.Runtime.InteropServices.Marshal.GetHRForException(exception)); + } + } + + /// <summary> + /// Releases the mutex. + /// </summary> + internal static void ReleaseMutex() + { + if (_singleInstanceMutex == null) + { + return; + } + + _singleInstanceMutex.ReleaseMutex(); + _singleInstanceMutex.Close(); + _singleInstanceMutex.Dispose(); + _singleInstanceMutex = null; + } + } +} diff --git a/MediaBrowser.ServerApplication/MainWindow.xaml.cs b/MediaBrowser.ServerApplication/MainWindow.xaml.cs index 974bb6f48..4dcdeef59 100644 --- a/MediaBrowser.ServerApplication/MainWindow.xaml.cs +++ b/MediaBrowser.ServerApplication/MainWindow.xaml.cs @@ -119,7 +119,7 @@ namespace MediaBrowser.ServerApplication Dispatcher.InvokeAsync(() => { - var logWindow = App.Instance.Windows.OfType<LogWindow>().FirstOrDefault(); + var logWindow = App.Current.Windows.OfType<LogWindow>().FirstOrDefault(); if ((logWindow == null && _configurationManager.Configuration.ShowLogWindow) || (logWindow != null && !_configurationManager.Configuration.ShowLogWindow)) { @@ -204,7 +204,7 @@ namespace MediaBrowser.ServerApplication { App.OpenUrl("https://github.com/MediaBrowser/MediaBrowser/wiki"); } - + /// <summary> /// Occurs when [property changed]. /// </summary> @@ -258,7 +258,7 @@ namespace MediaBrowser.ServerApplication { App.OpenDashboardPage("dashboard.html", loggedInUser, _configurationManager, _appHost); } - + /// <summary> /// Handles the click event of the cmVisitCT control. /// </summary> @@ -285,9 +285,9 @@ namespace MediaBrowser.ServerApplication /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param> - private void cmExit_click(object sender, RoutedEventArgs e) + private async void cmExit_click(object sender, RoutedEventArgs e) { - Application.Current.Shutdown(); + await _appHost.Shutdown().ConfigureAwait(false); } /// <summary> @@ -295,9 +295,9 @@ namespace MediaBrowser.ServerApplication /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param> - private void cmdReloadServer_click(object sender, RoutedEventArgs e) + private async void cmdReloadServer_click(object sender, RoutedEventArgs e) { - App.Instance.Restart(); + await _appHost.Restart().ConfigureAwait(false); } /// <summary> diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index bc84fca50..34d7eaf02 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -57,7 +57,7 @@ <WarningLevel>4</WarningLevel> </PropertyGroup> <PropertyGroup> - <StartupObject>MediaBrowser.ServerApplication.App</StartupObject> + <StartupObject>MediaBrowser.ServerApplication.MainStartup</StartupObject> </PropertyGroup> <PropertyGroup> <ApplicationIcon>Resources\Images\icon.ico</ApplicationIcon> @@ -187,6 +187,7 @@ <Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http.WebRequest" /> <Reference Include="System.Runtime.Remoting" /> + <Reference Include="System.ServiceProcess" /> <Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\MahApps.Metro.0.11.0.17-ALPHA\lib\net45\System.Windows.Interactivity.dll</HintPath> @@ -203,8 +204,13 @@ <Reference Include="PresentationFramework" /> </ItemGroup> <ItemGroup> + <Compile Include="BackgroundService.cs"> + <SubType>Component</SubType> + </Compile> <Compile Include="EntryPoints\StartupWizard.cs" /> <Compile Include="EntryPoints\UdpServerEntryPoint.cs" /> + <Compile Include="IApplicationInterface.cs" /> + <Compile Include="MainStartup.cs" /> <Compile Include="Splash\SplashWindow.xaml.cs"> <DependentUpon>SplashWindow.xaml</DependentUpon> </Compile> |
