From f3ce127a62714c97747f1e9575aaea1e9cdb3b6a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 20 Sep 2013 13:32:10 -0400 Subject: [PATCH] starting point towards running as a service --- MediaBrowser.Api/SystemService.cs | 8 +- .../BaseApplicationHost.cs | 4 +- MediaBrowser.Common/IApplicationHost.cs | 4 +- .../IServerApplicationHost.cs | 6 + MediaBrowser.Model/System/SystemInfo.cs | 8 +- MediaBrowser.ServerApplication/App.xaml.cs | 160 +++--------------- .../ApplicationHost.cs | 35 ++-- .../BackgroundService.cs | 30 ++++ .../EntryPoints/StartupWizard.cs | 5 +- .../IApplicationInterface.cs | 32 ++++ MediaBrowser.ServerApplication/MainStartup.cs | 134 +++++++++++++++ .../MainWindow.xaml.cs | 14 +- .../MediaBrowser.ServerApplication.csproj | 8 +- 13 files changed, 285 insertions(+), 163 deletions(-) create mode 100644 MediaBrowser.ServerApplication/BackgroundService.cs create mode 100644 MediaBrowser.ServerApplication/IApplicationInterface.cs create mode 100644 MediaBrowser.ServerApplication/MainStartup.cs 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 /// /// Restarts this instance. /// - public abstract void Restart(); + public abstract Task Restart(); /// /// Gets or sets a value indicating whether this instance can self update. @@ -582,7 +582,7 @@ namespace MediaBrowser.Common.Implementations /// /// Shuts down. /// - public abstract void Shutdown(); + public abstract Task Shutdown(); /// /// 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 /// /// Restarts this instance. /// - void Restart(); + Task Restart(); /// /// Gets the application version. @@ -113,7 +113,7 @@ namespace MediaBrowser.Common /// /// Shuts down. /// - void Shutdown(); + Task Shutdown(); /// /// Gets the plugins. 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 /// /// The HTTP server URL prefix. string HttpServerUrlPrefix { get; } + + /// + /// Gets a value indicating whether this instance is background service. + /// + /// true if this instance is background service; otherwise, false. + 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; } /// - /// Initializes a new instance of the class. + /// Gets or sets a value indicating whether this instance is background service. + /// + /// true if this instance is background service; otherwise, false. + public bool IsBackgroundService { get; set; } + + /// + /// Initializes a new instance of the class. /// public SystemInfo() { 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,89 +1,19 @@ -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 { /// /// Interaction logic for App.xaml /// - public partial class App : Application + public partial class App : Application, IApplicationInterface { - /// - /// The single instance mutex - /// - private static Mutex _singleInstanceMutex; - - /// - /// Defines the entry point of the application. - /// - [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(); - } - - /// - /// Gets the instance. - /// - /// The instance. - public static App Instance - { - get - { - return Current as App; - } - } - /// /// Gets or sets the logger. /// @@ -95,7 +25,7 @@ namespace MediaBrowser.ServerApplication /// /// The composition root. protected ApplicationHost CompositionRoot { get; set; } - + /// /// Initializes a new instance of the class. /// @@ -105,6 +35,11 @@ namespace MediaBrowser.ServerApplication InitializeComponent(); } + public bool IsBackgroundService + { + get { return false; } + } + /// /// Gets the name of the uninstaller file. /// @@ -114,63 +49,35 @@ namespace MediaBrowser.ServerApplication get { return "MediaBrowser.Server.Uninstall.exe"; } } - /// - /// Raises the event. - /// - /// A that contains the event data. + public void OnUnhandledException(Exception ex) + { + Logger.ErrorException("UnhandledException", ex); + + MessageBox.Show("Unhandled exception: " + ex.Message); + } + protected override void OnStartup(StartupEventArgs e) { - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - LoadKernel(); + base.OnStartup(e); - SystemEvents.SessionEnding += SystemEvents_SessionEnding; - } - - /// - /// Handles the UnhandledException event of the CurrentDomain control. - /// - /// The source of the event. - /// The instance containing the event data. - void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - var exception = (Exception)e.ExceptionObject; - - Logger.ErrorException("UnhandledException", exception); - - MessageBox.Show("Unhandled exception: " + exception.Message); - - if (!Debugger.IsAttached) - { - Environment.Exit(System.Runtime.InteropServices.Marshal.GetHRForException(exception)); - } - } - - /// - /// Handles the SessionEnding event of the SystemEvents control. - /// - /// The source of the event. - /// The instance containing the event data. - void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) - { - // Try to shut down gracefully - Shutdown(); + LoadApplication(); } /// /// Loads the kernel. /// - 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); + } + /// /// Raises the event. /// /// An that contains the event data. protected override void OnExit(ExitEventArgs e) { - ReleaseMutex(); + MainStartup.ReleaseMutex(); base.OnExit(e); @@ -208,22 +120,6 @@ namespace MediaBrowser.ServerApplication } } - /// - /// Releases the mutex. - /// - private void ReleaseMutex() - { - if (_singleInstanceMutex == null) - { - return; - } - - _singleInstanceMutex.ReleaseMutex(); - _singleInstanceMutex.Close(); - _singleInstanceMutex.Dispose(); - _singleInstanceMutex = null; - } - /// /// Opens the dashboard page. /// @@ -281,9 +177,9 @@ namespace MediaBrowser.ServerApplication /// Restarts this instance. /// /// - 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 13afb41e7..7288d70d9 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -1,5 +1,4 @@ -using System.Windows.Forms; -using MediaBrowser.Api; +using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Constants; @@ -7,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; @@ -184,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; + /// /// The full path to our startmenu shortcut /// @@ -194,6 +199,11 @@ namespace MediaBrowser.ServerApplication private Task _httpServerCreationTask; + public ApplicationHost(IApplicationInterface appInterface) + { + _appInterface = appInterface; + } + /// /// Runs the startup tasks. /// @@ -505,7 +515,7 @@ namespace MediaBrowser.ServerApplication base.OnConfigurationUpdated(sender, e); HttpServer.EnableHttpRequestLogging = ServerConfigurationManager.Configuration.EnableHttpLevelLogging; - + if (!string.Equals(HttpServer.UrlPrefix, HttpServerUrlPrefix, StringComparison.OrdinalIgnoreCase)) { NotifyPendingRestart(); @@ -521,19 +531,18 @@ namespace MediaBrowser.ServerApplication /// /// Restarts this instance. /// - 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(); } /// @@ -618,7 +627,8 @@ namespace MediaBrowser.ServerApplication Id = _systemId, ProgramDataPath = ApplicationPaths.ProgramDataPath, MacAddress = GetMacAddress(), - HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber + HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber, + IsBackgroundService = IsBackgroundService }; } @@ -642,19 +652,18 @@ namespace MediaBrowser.ServerApplication /// /// Shuts down. /// - 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(); } /// 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 +{ + /// + /// Interface IApplicationInterface + /// + public interface IApplicationInterface + { + /// + /// Gets a value indicating whether this instance is background service. + /// + /// true if this instance is background service; otherwise, false. + bool IsBackgroundService { get; } + + /// + /// Shutdowns the application. + /// + void ShutdownApplication(); + + /// + /// Restarts the application. + /// + void RestartApplication(); + + /// + /// Called when [unhandled exception]. + /// + /// The ex. + 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 + { + /// + /// The single instance mutex + /// + private static Mutex _singleInstanceMutex; + + private static IApplicationInterface _applicationInterface; + + /// + /// Defines the entry point of the application. + /// + [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)); + } + } + + /// + /// Releases the mutex. + /// + 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().FirstOrDefault(); + var logWindow = App.Current.Windows.OfType().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"); } - + /// /// Occurs when [property changed]. /// @@ -258,7 +258,7 @@ namespace MediaBrowser.ServerApplication { App.OpenDashboardPage("dashboard.html", loggedInUser, _configurationManager, _appHost); } - + /// /// Handles the click event of the cmVisitCT control. /// @@ -285,9 +285,9 @@ namespace MediaBrowser.ServerApplication /// /// The source of the event. /// The instance containing the event data. - 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); } /// @@ -295,9 +295,9 @@ namespace MediaBrowser.ServerApplication /// /// The source of the event. /// The instance containing the event data. - 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); } /// 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 @@ 4 - MediaBrowser.ServerApplication.App + MediaBrowser.ServerApplication.MainStartup Resources\Images\icon.ico @@ -187,6 +187,7 @@ + ..\packages\MahApps.Metro.0.11.0.17-ALPHA\lib\net45\System.Windows.Interactivity.dll @@ -203,8 +204,13 @@ + + Component + + + SplashWindow.xaml