Explorar o código

support run as service

Luke Pulverenti %!s(int64=11) %!d(string=hai) anos
pai
achega
2e511fba83

+ 8 - 11
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Implementations.Logging;
 using MediaBrowser.Common.Implementations.NetworkManagement;
 using MediaBrowser.Common.Implementations.ScheduledTasks;
 using MediaBrowser.Common.Implementations.Security;
@@ -71,7 +70,7 @@ namespace MediaBrowser.Common.Implementations
         /// Gets the application paths.
         /// </summary>
         /// <value>The application paths.</value>
-        protected TApplicationPathsType ApplicationPaths = new TApplicationPathsType();
+        protected TApplicationPathsType ApplicationPaths { get; private set; }
 
         /// <summary>
         /// The container
@@ -153,11 +152,12 @@ namespace MediaBrowser.Common.Implementations
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class.
         /// </summary>
-        protected BaseApplicationHost()
+        protected BaseApplicationHost(TApplicationPathsType applicationPaths, ILogManager logManager)
         {
             FailedAssemblies = new List<string>();
 
-            LogManager = new NlogManager(ApplicationPaths.LogDirectoryPath, LogFilePrefixName);
+            ApplicationPaths = applicationPaths;
+            LogManager = logManager;
 
             ConfigurationManager = GetConfigurationManager();
         }
@@ -172,7 +172,10 @@ namespace MediaBrowser.Common.Implementations
 
             Logger = LogManager.GetLogger("App");
 
-            LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info);
+            LogManager.LogSeverity = ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging
+                                         ? LogSeverity.Debug
+                                         : LogSeverity.Info;
+
             OnLoggerLoaded();
 
             DiscoverTypes();
@@ -211,12 +214,6 @@ namespace MediaBrowser.Common.Implementations
         /// <returns>IEnumerable{Assembly}.</returns>
         protected abstract IEnumerable<Assembly> GetComposablePartAssemblies();
 
-        /// <summary>
-        /// Gets the name of the log file prefix.
-        /// </summary>
-        /// <value>The name of the log file prefix.</value>
-        protected abstract string LogFilePrefixName { get; }
-
         /// <summary>
         /// Gets the configuration manager.
         /// </summary>

+ 9 - 0
MediaBrowser.Common.Implementations/BaseApplicationPaths.cs

@@ -26,6 +26,15 @@ namespace MediaBrowser.Common.Implementations
             _useDebugPath = useDebugPath;
         }
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
+        /// </summary>
+        /// <param name="programDataPath">The program data path.</param>
+        protected BaseApplicationPaths(string programDataPath)
+        {
+            _programDataPath = programDataPath;
+        }
+
         /// <summary>
         /// The _program data path
         /// </summary>

+ 39 - 2
MediaBrowser.Common.Implementations/Logging/NlogManager.cs

@@ -1,10 +1,10 @@
-using System.Linq;
-using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Logging;
 using NLog;
 using NLog.Config;
 using NLog.Targets;
 using System;
 using System.IO;
+using System.Linq;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Common.Implementations.Logging
@@ -45,6 +45,41 @@ namespace MediaBrowser.Common.Implementations.Logging
             LogFilePrefix = logFileNamePrefix;
         }
 
+        private LogSeverity _severity = LogSeverity.Debug;
+        public LogSeverity LogSeverity
+        {
+            get
+            {
+                return _severity;
+            }
+            set
+            {
+                var changed = _severity != value;
+
+                _severity = value;
+
+                if (changed)
+                {
+                    UpdateLogLevel(value);
+                }
+            }
+        }
+
+        private void UpdateLogLevel(LogSeverity newLevel)
+        {
+            var level = GetLogLevel(newLevel);
+
+            var rules = LogManager.Configuration.LoggingRules;
+
+            foreach (var rule in rules)
+            {
+                if (!rule.IsLoggingEnabledForLevel(level))
+                {
+                    rule.EnableLoggingForLevel(level);
+                }
+            }
+        }
+
         /// <summary>
         /// Adds the file target.
         /// </summary>
@@ -154,6 +189,8 @@ namespace MediaBrowser.Common.Implementations.Logging
 
             AddFileTarget(LogFilePath, level);
 
+            LogSeverity = level;
+
             if (LoggerLoaded != null)
             {
                 Task.Run(() =>

+ 0 - 6
MediaBrowser.Controller/IServerApplicationHost.cs

@@ -25,11 +25,5 @@ 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; }
     }
 }

+ 6 - 0
MediaBrowser.Model/Logging/ILogManager.cs

@@ -7,6 +7,12 @@ namespace MediaBrowser.Model.Logging
     /// </summary>
     public interface ILogManager
     {
+        /// <summary>
+        /// Gets or sets the log level.
+        /// </summary>
+        /// <value>The log level.</value>
+        LogSeverity LogSeverity { get; set; }
+
         /// <summary>
         /// Gets the logger.
         /// </summary>

+ 0 - 6
MediaBrowser.Model/System/SystemInfo.cs

@@ -80,12 +80,6 @@ namespace MediaBrowser.Model.System
         /// <value>The HTTP server port number.</value>
         public int HttpServerPortNumber { get; set; }
 
-        /// <summary>
-        /// 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>

+ 12 - 1
MediaBrowser.Server.Implementations/ServerApplicationPaths.cs

@@ -26,6 +26,17 @@ namespace MediaBrowser.Server.Implementations
         {
         }
 #endif
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BaseApplicationPaths" /> class.
+        /// </summary>
+        /// <param name="programDataPath">The program data path.</param>
+        public ServerApplicationPaths(string programDataPath)
+            : base(programDataPath)
+        {
+
+        }
+
         /// <summary>
         /// Gets the path to the base root media directory
         /// </summary>
@@ -117,7 +128,7 @@ namespace MediaBrowser.Server.Implementations
                 return Path.Combine(ItemsByNamePath, "MusicGenre");
             }
         }
-        
+
         /// <summary>
         /// Gets the path to the Studio directory
         /// </summary>

+ 68 - 48
MediaBrowser.ServerApplication/App.xaml.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Logging;
@@ -12,32 +13,35 @@ namespace MediaBrowser.ServerApplication
     /// <summary>
     /// Interaction logic for App.xaml
     /// </summary>
-    public partial class App : Application, IApplicationInterface
+    public partial class App : Application
     {
         /// <summary>
         /// Gets or sets the logger.
         /// </summary>
         /// <value>The logger.</value>
-        protected ILogger Logger { get; set; }
+        private readonly ILogger _logger;
 
         /// <summary>
         /// Gets or sets the composition root.
         /// </summary>
         /// <value>The composition root.</value>
-        protected ApplicationHost CompositionRoot { get; set; }
+        private readonly ApplicationHost _appHost;
+
+        public event EventHandler AppStarted;
+
+        public bool IsRunningAsService { get; private set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="App" /> class.
         /// </summary>
         /// <param name="logger">The logger.</param>
-        public App()
+        public App(ApplicationHost appHost, ILogger logger, bool isRunningAsService)
         {
-            InitializeComponent();
-        }
+            _appHost = appHost;
+            _logger = logger;
+            IsRunningAsService = isRunningAsService;
 
-        public bool IsBackgroundService
-        {
-            get { return false; }
+            InitializeComponent();
         }
 
         /// <summary>
@@ -51,7 +55,7 @@ namespace MediaBrowser.ServerApplication
 
         public void OnUnhandledException(Exception ex)
         {
-            Logger.ErrorException("UnhandledException", ex);
+            _logger.ErrorException("UnhandledException", ex);
 
             MessageBox.Show("Unhandled exception: " + ex.Message);
         }
@@ -70,27 +74,32 @@ namespace MediaBrowser.ServerApplication
         {
             try
             {
-                CompositionRoot = new ApplicationHost(this);
-
-                Logger = CompositionRoot.LogManager.GetLogger("App");
-
-                var splash = new SplashWindow(CompositionRoot.ApplicationVersion);
+                if (!IsRunningAsService)
+                {
+                    ShowSplashWindow();
+                }
 
-                splash.Show();
+                await _appHost.Init();
 
-                await CompositionRoot.Init();
+                if (!IsRunningAsService)
+                {
+                    HideSplashWindow();
+                }
 
-                splash.Hide();
+                var task = _appHost.RunStartupTasks();
 
-                var task = CompositionRoot.RunStartupTasks();
+                if (!IsRunningAsService)
+                {
+                    ShowMainWindow();
+                }
 
-                new MainWindow(CompositionRoot.LogManager, CompositionRoot, CompositionRoot.ServerConfigurationManager, CompositionRoot.UserManager, CompositionRoot.LibraryManager, CompositionRoot.JsonSerializer, CompositionRoot.DisplayPreferencesRepository).Show();
+                EventHelper.FireEventIfNotNull(AppStarted, this, EventArgs.Empty, _logger);
 
                 await task.ConfigureAwait(false);
             }
             catch (Exception ex)
             {
-                Logger.ErrorException("Error launching application", ex);
+                _logger.ErrorException("Error launching application", ex);
 
                 MessageBox.Show("There was an error launching Media Browser: " + ex.Message);
 
@@ -99,27 +108,53 @@ namespace MediaBrowser.ServerApplication
             }
         }
 
-        public void ShutdownApplication()
+        private MainWindow _mainWindow;
+        private void ShowMainWindow()
         {
-            Dispatcher.Invoke(Shutdown);
+            var host = _appHost;
+
+            var win = new MainWindow(host.LogManager, host,
+                                     host.ServerConfigurationManager, host.UserManager,
+                                     host.LibraryManager, host.JsonSerializer,
+                                     host.DisplayPreferencesRepository);
+
+            win.Show();
+
+            _mainWindow = win;
         }
 
-        /// <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)
+        private void HideMainWindow()
         {
-            MainStartup.ReleaseMutex();
+            if (_mainWindow != null)
+            {
+                _mainWindow.Hide();
+                _mainWindow = null;
+            }
+        }
 
-            base.OnExit(e);
+        private SplashWindow _splashWindow;
+        private void ShowSplashWindow()
+        {
+            var win = new SplashWindow(_appHost.ApplicationVersion);
+            win.Show();
 
-            if (CompositionRoot != null)
+            _splashWindow = win;
+        }
+
+        private void HideSplashWindow()
+        {
+            if (_splashWindow != null)
             {
-                CompositionRoot.Dispose();
+                _splashWindow.Hide();
+                _splashWindow = null;
             }
         }
 
+        public void ShutdownApplication()
+        {
+            Dispatcher.Invoke(Shutdown);
+        }
+
         /// <summary>
         /// Opens the dashboard page.
         /// </summary>
@@ -172,20 +207,5 @@ namespace MediaBrowser.ServerApplication
         {
             ((Process)sender).Dispose();
         }
-
-        /// <summary>
-        /// Restarts this instance.
-        /// </summary>
-        /// <exception cref="System.NotImplementedException"></exception>
-        public void RestartApplication()
-        {
-            Dispatcher.Invoke(MainStartup.ReleaseMutex);
-
-            CompositionRoot.Dispose();
-
-            System.Windows.Forms.Application.Restart();
-
-            Dispatcher.Invoke(Shutdown);
-        }
     }
 }

+ 28 - 22
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -26,6 +26,7 @@ using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.IsoMounter;
 using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.Updates;
@@ -59,6 +60,7 @@ using System.Net.Http;
 using System.Reflection;
 using System.Threading;
 using System.Threading.Tasks;
+using System.Windows;
 
 namespace MediaBrowser.ServerApplication
 {
@@ -84,15 +86,6 @@ namespace MediaBrowser.ServerApplication
             get { return (IServerConfigurationManager)ConfigurationManager; }
         }
 
-        /// <summary>
-        /// Gets the name of the log file prefix.
-        /// </summary>
-        /// <value>The name of the log file prefix.</value>
-        protected override string LogFilePrefixName
-        {
-            get { return "server"; }
-        }
-
         /// <summary>
         /// Gets the name of the web application that can be used for url building.
         /// All api urls will be of the form {protocol}://{host}:{port}/{appname}/...
@@ -182,13 +175,6 @@ 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>
@@ -199,9 +185,15 @@ namespace MediaBrowser.ServerApplication
 
         private Task<IHttpServer> _httpServerCreationTask;
 
-        public ApplicationHost(IApplicationInterface appInterface)
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
+        /// </summary>
+        /// <param name="applicationPaths">The application paths.</param>
+        /// <param name="logManager">The log manager.</param>
+        public ApplicationHost(ServerApplicationPaths applicationPaths, ILogManager logManager)
+            : base(applicationPaths, logManager)
         {
-            _appInterface = appInterface;
+
         }
 
         /// <summary>
@@ -542,7 +534,14 @@ namespace MediaBrowser.ServerApplication
                 Logger.ErrorException("Error sending server restart web socket message", ex);
             }
 
-            _appInterface.RestartApplication();
+            // Second instance will start first, so release the mutex and dispose the http server ahead of time
+            Application.Current.Dispatcher.Invoke(() => MainStartup.ReleaseMutex(Logger));
+
+            Dispose();
+
+            System.Windows.Forms.Application.Restart();
+
+            ShutdownInternal();
         }
 
         /// <summary>
@@ -627,8 +626,7 @@ namespace MediaBrowser.ServerApplication
                 Id = _systemId,
                 ProgramDataPath = ApplicationPaths.ProgramDataPath,
                 MacAddress = GetMacAddress(),
-                HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber,
-                IsBackgroundService = IsBackgroundService
+                HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber
             };
         }
 
@@ -663,7 +661,15 @@ namespace MediaBrowser.ServerApplication
                 Logger.ErrorException("Error sending server shutdown web socket message", ex);
             }
 
-            _appInterface.ShutdownApplication();
+            ShutdownInternal();
+        }
+
+        public void ShutdownInternal()
+        {
+            Logger.Info("Shutting down application");
+            var app = Application.Current;
+
+            app.Dispatcher.Invoke(app.Shutdown);
         }
 
         /// <summary>

+ 25 - 15
MediaBrowser.ServerApplication/BackgroundService.cs

@@ -1,30 +1,40 @@
-using System.ServiceProcess;
+using MediaBrowser.Model.Logging;
+using System.ServiceProcess;
 
 namespace MediaBrowser.ServerApplication
 {
+    /// <summary>
+    /// Class BackgroundService
+    /// </summary>
     public class BackgroundService : ServiceBase
     {
-        public BackgroundService()
+        public static string Name = "MediaBrowser";
+        public static string DisplayName = "Media Browser";
+
+        private readonly ILogger _logger;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BackgroundService"/> class.
+        /// </summary>
+        public BackgroundService(ILogger logger)
         {
+            _logger = logger;
+
             CanPauseAndContinue = false;
-            CanHandleSessionChangeEvent = true;
-            CanStop = false;
-            CanShutdown = true;
-            ServiceName = "Media Browser";
-        }
 
-        protected override void OnSessionChange(SessionChangeDescription changeDescription)
-        {
-            base.OnSessionChange(changeDescription);
-        }
+            CanStop = true;
 
-        protected override void OnStart(string[] args)
-        {
+            ServiceName = Name;
         }
 
-        protected override void OnShutdown()
+        /// <summary>
+        /// When implemented in a derived class, executes when a Stop command is sent to the service by the Service Control Manager (SCM). Specifies actions to take when a service stops running.
+        /// </summary>
+        protected override void OnStop()
         {
-            base.OnShutdown();
+            _logger.Info("Stop command received");
+
+            base.OnStop();
         }
     }
 }

+ 60 - 0
MediaBrowser.ServerApplication/BackgroundServiceInstaller.cs

@@ -0,0 +1,60 @@
+using System.Collections;
+using System.ComponentModel;
+using System.ServiceProcess;
+
+namespace MediaBrowser.ServerApplication
+{
+    [RunInstaller(true)]
+    public class BackgroundServiceInstaller : System.Configuration.Install.Installer
+    {
+        public BackgroundServiceInstaller()
+        {
+            var process = new ServiceProcessInstaller
+            {
+                Account = ServiceAccount.LocalSystem
+            };
+
+            var serviceAdmin = new ServiceInstaller
+            {
+                StartType = ServiceStartMode.Manual,
+                ServiceName = BackgroundService.Name,
+                DisplayName = BackgroundService.DisplayName,
+                DelayedAutoStart = true,
+                Description = "The windows background service for Media Browser Server."
+            };
+
+            // Microsoft didn't add the ability to add a
+            // description for the services we are going to install
+            // To work around this we'll have to add the
+            // information directly to the registry but I'll leave
+            // this exercise for later.
+
+            // now just add the installers that we created to our
+            // parents container, the documentation
+            // states that there is not any order that you need to
+            // worry about here but I'll still
+            // go ahead and add them in the order that makes sense.
+            Installers.Add(process);
+            Installers.Add(serviceAdmin);
+        }
+
+        protected override void OnBeforeInstall(IDictionary savedState)
+        {
+            Context.Parameters["assemblypath"] = "\"" +
+                Context.Parameters["assemblypath"] + "\" " + GetStartArgs();
+            base.OnBeforeInstall(savedState);
+        }
+
+        protected override void OnBeforeUninstall(IDictionary savedState)
+        {
+            Context.Parameters["assemblypath"] = "\"" +
+                Context.Parameters["assemblypath"] + "\" " + GetStartArgs();
+            base.OnBeforeUninstall(savedState);
+        }
+
+        private string GetStartArgs()
+        {
+            return "-service";
+        }
+    }
+}

+ 1 - 4
MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs

@@ -64,10 +64,7 @@ namespace MediaBrowser.ServerApplication.EntryPoints
             {
                 _logger.ErrorException("Error launching startup wizard", ex);
 
-                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");
-                }
+                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");
             }
         }
 

+ 0 - 32
MediaBrowser.ServerApplication/IApplicationInterface.cs

@@ -1,32 +0,0 @@
-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);
-    }
-}

+ 273 - 38
MediaBrowser.ServerApplication/MainStartup.cs

@@ -1,12 +1,18 @@
 using MediaBrowser.Common.Constants;
+using MediaBrowser.Common.Implementations.Logging;
 using MediaBrowser.Common.Implementations.Updates;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Server.Implementations;
+using Microsoft.Win32;
 using System;
+using System.Configuration.Install;
 using System.Diagnostics;
 using System.IO;
+using System.Linq;
+using System.ServiceProcess;
 using System.Threading;
+using System.Threading.Tasks;
 using System.Windows;
-using Microsoft.Win32;
 
 namespace MediaBrowser.ServerApplication
 {
@@ -17,7 +23,9 @@ namespace MediaBrowser.ServerApplication
         /// </summary>
         private static Mutex _singleInstanceMutex;
 
-        private static IApplicationInterface _applicationInterface;
+        private static ApplicationHost _appHost;
+
+        private static App _app;
 
         /// <summary>
         /// Defines the entry point of the application.
@@ -25,6 +33,50 @@ namespace MediaBrowser.ServerApplication
         [STAThread]
         public static void Main()
         {
+            var startFlag = Environment.GetCommandLineArgs().ElementAtOrDefault(1);
+            var runService = string.Equals(startFlag, "-service", StringComparison.OrdinalIgnoreCase);
+
+            var appPaths = CreateApplicationPaths(runService);
+
+            var logManager = new NlogManager(appPaths.LogDirectoryPath, "server");
+            logManager.ReloadLogger(LogSeverity.Info);
+
+            var logger = logManager.GetLogger("Main");
+
+            BeginLog(logger);
+
+            // Install directly
+            if (string.Equals(startFlag, "-installservice", StringComparison.OrdinalIgnoreCase))
+            {
+                logger.Info("Performing service installation");
+                InstallService(logger);
+                return;
+            }
+
+            // Restart with admin rights, then install
+            if (string.Equals(startFlag, "-launchinstallservice", StringComparison.OrdinalIgnoreCase))
+            {
+                logger.Info("Performing service installation");
+                RunServiceInstallation();
+                return;
+            }
+
+            // Uninstall directly
+            if (string.Equals(startFlag, "-uninstallservice", StringComparison.OrdinalIgnoreCase))
+            {
+                logger.Info("Performing service uninstallation");
+                UninstallService(logger);
+                return;
+            }
+
+            // Restart with admin rights, then uninstall
+            if (string.Equals(startFlag, "-launchuninstallservice", StringComparison.OrdinalIgnoreCase))
+            {
+                logger.Info("Performing service uninstallation");
+                RunServiceUninstallation();
+                return;
+            }
+
             AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
 
             bool createdNew;
@@ -36,78 +88,228 @@ namespace MediaBrowser.ServerApplication
             if (!createdNew)
             {
                 _singleInstanceMutex = null;
+                logger.Info("Shutting down because another instance of Media Browser Server is already running.");
                 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))
+            if (PerformUpdateIfNeeded(appPaths, logger))
             {
-                // Update is there - execute update
-                try
-                {
-                    new ApplicationUpdater().UpdateApplication(MBApplication.MBServer, appPaths, updateArchive);
+                logger.Info("Exiting to perform application update.");
+                return;
+            }
 
-                    // 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));
-                }
+            try
+            {
+                RunApplication(appPaths, logManager, runService);
             }
+            finally
+            {
+                logger.Info("Shutting down");
 
-            StartApplication();
+                ReleaseMutex(logger);
+
+                _appHost.Dispose();
+            }
         }
 
-        private static void StartApplication()
+        /// <summary>
+        /// Creates the application paths.
+        /// </summary>
+        /// <param name="runAsService">if set to <c>true</c> [run as service].</param>
+        /// <returns>ServerApplicationPaths.</returns>
+        private static ServerApplicationPaths CreateApplicationPaths(bool runAsService)
+        {
+            if (runAsService)
+            {
+#if (RELEASE)
+                var systemPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
+
+                var programDataPath = Path.GetDirectoryName(systemPath);
+
+                return new ServerApplicationPaths(programDataPath);
+#endif
+            }
+
+            return new ServerApplicationPaths();
+        }
+
+        /// <summary>
+        /// Begins the log.
+        /// </summary>
+        /// <param name="logger">The logger.</param>
+        private static void BeginLog(ILogger logger)
+        {
+            logger.Info("Media Browser Server started");
+            logger.Info("Command line: {0}", string.Join(" ", Environment.GetCommandLineArgs()));
+
+            logger.Info("Server: {0}", Environment.MachineName);
+            logger.Info("Operating system: {0}", Environment.OSVersion.ToString());
+        }
+
+        /// <summary>
+        /// Runs the application.
+        /// </summary>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="logManager">The log manager.</param>
+        /// <param name="runService">if set to <c>true</c> [run service].</param>
+        private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, bool runService)
         {
             SystemEvents.SessionEnding += SystemEvents_SessionEnding;
+
             var commandLineArgs = Environment.GetCommandLineArgs();
 
-            if (commandLineArgs.Length > 1 && commandLineArgs[1].Equals("-service"))
+            _appHost = new ApplicationHost(appPaths, logManager);
+
+            _app = new App(_appHost, _appHost.LogManager.GetLogger("App"), runService);
+
+            if (runService)
             {
-                // Start application as a service
-                StartBackgroundService();
+                _app.AppStarted += (sender, args) => StartService(logManager);
             }
-            else
+
+            _app.Run();
+        }
+
+        /// <summary>
+        /// Starts the service.
+        /// </summary>
+        private static void StartService(ILogManager logManager)
+        {
+            var ctl = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == BackgroundService.Name);
+
+            if (ctl == null)
             {
-                StartWpfApp();
+                RunServiceInstallation();
             }
+
+            var service = new BackgroundService(logManager.GetLogger("Service"));
+
+            service.Disposed += service_Disposed;
+
+            ServiceBase.Run(service);
         }
 
-        static void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
+        /// <summary>
+        /// Handles the Disposed event of the service control.
+        /// </summary>
+        /// <param name="sender">The source of the event.</param>
+        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
+        static async void service_Disposed(object sender, EventArgs e)
         {
-            // Try to shutdown gracefully
-            if (_applicationInterface != null)
+            await _appHost.Shutdown();
+        }
+
+        /// <summary>
+        /// Installs the service.
+        /// </summary>
+        private static void InstallService(ILogger logger)
+        {
+            var runningPath = Process.GetCurrentProcess().MainModule.FileName;
+
+            try
             {
-                _applicationInterface.ShutdownApplication();
+                ManagedInstallerClass.InstallHelper(new[] { runningPath });
+
+                logger.Info("Service installation succeeded");
+            }
+            catch (Exception ex)
+            {
+                logger.ErrorException("Uninstall failed", ex);
             }
         }
 
-        private static void StartWpfApp()
+        /// <summary>
+        /// Uninstalls the service.
+        /// </summary>
+        private static void UninstallService(ILogger logger)
         {
-            var app = new App();
+            var runningPath = Process.GetCurrentProcess().MainModule.FileName;
 
-            _applicationInterface = app;
+            try
+            {
+                ManagedInstallerClass.InstallHelper(new[] { "/u", runningPath });
 
-            app.Run();
+                logger.Info("Service uninstallation succeeded");
+            }
+            catch (Exception ex)
+            {
+                logger.ErrorException("Uninstall failed", ex);
+            }
         }
 
-        private static void StartBackgroundService()
+        /// <summary>
+        /// Runs the service installation.
+        /// </summary>
+        private static void RunServiceInstallation()
         {
+            var runningPath = Process.GetCurrentProcess().MainModule.FileName;
+
+            var startInfo = new ProcessStartInfo
+            {
+                FileName = runningPath,
+
+                Arguments = "-installservice",
+
+                CreateNoWindow = true,
+                WindowStyle = ProcessWindowStyle.Hidden,
+                Verb = "runas",
+                ErrorDialog = false
+            };
 
+            using (var process = Process.Start(startInfo))
+            {
+                process.WaitForExit();
+            }
         }
 
-        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+        /// <summary>
+        /// Runs the service uninstallation.
+        /// </summary>
+        private static void RunServiceUninstallation()
         {
-            var exception = (Exception)e.ExceptionObject;
+            var runningPath = Process.GetCurrentProcess().MainModule.FileName;
 
-            if (_applicationInterface != null)
+            var startInfo = new ProcessStartInfo
             {
-                _applicationInterface.OnUnhandledException(exception);
+                FileName = runningPath,
+
+                Arguments = "-uninstallservice",
+
+                CreateNoWindow = true,
+                WindowStyle = ProcessWindowStyle.Hidden,
+                Verb = "runas",
+                ErrorDialog = false
+            };
+
+            using (var process = Process.Start(startInfo))
+            {
+                process.WaitForExit();
             }
+        }
+
+        /// <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>
+        static void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
+        {
+            // Try to shutdown gracefully
+            var task = _appHost.Shutdown();
+
+            Task.WaitAll(task);
+        }
+
+        /// <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>
+        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+        {
+            var exception = (Exception)e.ExceptionObject;
+
+            _app.OnUnhandledException(exception);
 
             if (!Debugger.IsAttached)
             {
@@ -118,17 +320,50 @@ namespace MediaBrowser.ServerApplication
         /// <summary>
         /// Releases the mutex.
         /// </summary>
-        internal static void ReleaseMutex()
+        internal static void ReleaseMutex(ILogger logger)
         {
             if (_singleInstanceMutex == null)
             {
                 return;
             }
 
+            logger.Debug("Releasing mutex");
+
             _singleInstanceMutex.ReleaseMutex();
             _singleInstanceMutex.Close();
             _singleInstanceMutex.Dispose();
             _singleInstanceMutex = null;
         }
+
+        /// <summary>
+        /// Performs the update if needed.
+        /// </summary>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="logger">The logger.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        private static bool PerformUpdateIfNeeded(ServerApplicationPaths appPaths, ILogger logger)
+        {
+            // Look for the existence of an update archive
+            var updateArchive = Path.Combine(appPaths.TempUpdatePath, Constants.MbServerPkgName + ".zip");
+            if (File.Exists(updateArchive))
+            {
+                logger.Info("An update is available from {0}", 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 true;
+                }
+                catch (Exception e)
+                {
+                    MessageBox.Show(string.Format("Error attempting to update application.\n\n{0}\n\n{1}", e.GetType().Name, e.Message));
+                }
+            }
+
+            return false;
+        }
     }
 }

+ 1 - 3
MediaBrowser.ServerApplication/MainWindow.xaml.cs

@@ -1,6 +1,4 @@
-using MahApps.Metro.Controls;
-using MediaBrowser.Common;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;

+ 4 - 1
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -173,6 +173,7 @@
       <HintPath>..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll</HintPath>
     </Reference>
     <Reference Include="System" />
+    <Reference Include="System.Configuration.Install" />
     <Reference Include="System.Data" />
     <Reference Include="System.Data.SQLite, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
       <SpecificVersion>False</SpecificVersion>
@@ -209,8 +210,10 @@
     </Compile>
     <Compile Include="EntryPoints\StartupWizard.cs" />
     <Compile Include="EntryPoints\UdpServerEntryPoint.cs" />
-    <Compile Include="IApplicationInterface.cs" />
     <Compile Include="MainStartup.cs" />
+    <Compile Include="BackgroundServiceInstaller.cs">
+      <SubType>Component</SubType>
+    </Compile>
     <Compile Include="Splash\SplashWindow.xaml.cs">
       <DependentUpon>SplashWindow.xaml</DependentUpon>
     </Compile>