diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index f333eb22c7..c0ac6a4b3a 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/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. /// /// The application paths. - protected TApplicationPathsType ApplicationPaths = new TApplicationPathsType(); + protected TApplicationPathsType ApplicationPaths { get; private set; } /// /// The container @@ -153,11 +152,12 @@ namespace MediaBrowser.Common.Implementations /// /// Initializes a new instance of the class. /// - protected BaseApplicationHost() + protected BaseApplicationHost(TApplicationPathsType applicationPaths, ILogManager logManager) { FailedAssemblies = new List(); - 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 /// IEnumerable{Assembly}. protected abstract IEnumerable GetComposablePartAssemblies(); - /// - /// Gets the name of the log file prefix. - /// - /// The name of the log file prefix. - protected abstract string LogFilePrefixName { get; } - /// /// Gets the configuration manager. /// diff --git a/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs b/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs index f6667f2364..c177bd1eda 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs @@ -26,6 +26,15 @@ namespace MediaBrowser.Common.Implementations _useDebugPath = useDebugPath; } + /// + /// Initializes a new instance of the class. + /// + /// The program data path. + protected BaseApplicationPaths(string programDataPath) + { + _programDataPath = programDataPath; + } + /// /// The _program data path /// diff --git a/MediaBrowser.Common.Implementations/Logging/NlogManager.cs b/MediaBrowser.Common.Implementations/Logging/NlogManager.cs index cd3dc5c163..109e85d80a 100644 --- a/MediaBrowser.Common.Implementations/Logging/NlogManager.cs +++ b/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); + } + } + } + /// /// Adds the file target. /// @@ -154,6 +189,8 @@ namespace MediaBrowser.Common.Implementations.Logging AddFileTarget(LogFilePath, level); + LogSeverity = level; + if (LoggerLoaded != null) { Task.Run(() => diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 495147125b..f96c2536e6 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -25,11 +25,5 @@ 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/Logging/ILogManager.cs b/MediaBrowser.Model/Logging/ILogManager.cs index 1d6db2bd83..2207ddc759 100644 --- a/MediaBrowser.Model/Logging/ILogManager.cs +++ b/MediaBrowser.Model/Logging/ILogManager.cs @@ -7,6 +7,12 @@ namespace MediaBrowser.Model.Logging /// public interface ILogManager { + /// + /// Gets or sets the log level. + /// + /// The log level. + LogSeverity LogSeverity { get; set; } + /// /// Gets the logger. /// diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index b00d7f29d3..9ef1618551 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -80,12 +80,6 @@ namespace MediaBrowser.Model.System /// The HTTP server port number. public int HttpServerPortNumber { get; set; } - /// - /// 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. /// diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs index ac552b8de2..fb3c5aec86 100644 --- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs +++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs @@ -26,6 +26,17 @@ namespace MediaBrowser.Server.Implementations { } #endif + + /// + /// Initializes a new instance of the class. + /// + /// The program data path. + public ServerApplicationPaths(string programDataPath) + : base(programDataPath) + { + + } + /// /// Gets the path to the base root media directory /// @@ -117,7 +128,7 @@ namespace MediaBrowser.Server.Implementations return Path.Combine(ItemsByNamePath, "MusicGenre"); } } - + /// /// Gets the path to the Studio directory /// diff --git a/MediaBrowser.ServerApplication/App.xaml.cs b/MediaBrowser.ServerApplication/App.xaml.cs index 42045257af..69de391a4d 100644 --- a/MediaBrowser.ServerApplication/App.xaml.cs +++ b/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 /// /// Interaction logic for App.xaml /// - public partial class App : Application, IApplicationInterface + public partial class App : Application { /// /// Gets or sets the logger. /// /// The logger. - protected ILogger Logger { get; set; } + private readonly ILogger _logger; /// /// Gets or sets the composition root. /// /// The composition root. - protected ApplicationHost CompositionRoot { get; set; } + private readonly ApplicationHost _appHost; + + public event EventHandler AppStarted; + + public bool IsRunningAsService { get; private set; } /// /// Initializes a new instance of the class. /// /// The logger. - public App() + public App(ApplicationHost appHost, ILogger logger, bool isRunningAsService) { - InitializeComponent(); - } + _appHost = appHost; + _logger = logger; + IsRunningAsService = isRunningAsService; - public bool IsBackgroundService - { - get { return false; } + InitializeComponent(); } /// @@ -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; } - /// - /// Raises the event. - /// - /// An that contains the event data. - 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); + } + /// /// Opens the dashboard page. /// @@ -172,20 +207,5 @@ namespace MediaBrowser.ServerApplication { ((Process)sender).Dispose(); } - - /// - /// Restarts this instance. - /// - /// - public void RestartApplication() - { - Dispatcher.Invoke(MainStartup.ReleaseMutex); - - CompositionRoot.Dispose(); - - System.Windows.Forms.Application.Restart(); - - Dispatcher.Invoke(Shutdown); - } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 7288d70d92..648ff9fc31 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/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; } } - /// - /// Gets the name of the log file prefix. - /// - /// The name of the log file prefix. - protected override string LogFilePrefixName - { - get { return "server"; } - } - /// /// 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; - /// /// The full path to our startmenu shortcut /// @@ -199,9 +185,15 @@ namespace MediaBrowser.ServerApplication private Task _httpServerCreationTask; - public ApplicationHost(IApplicationInterface appInterface) + /// + /// Initializes a new instance of the class. + /// + /// The application paths. + /// The log manager. + public ApplicationHost(ServerApplicationPaths applicationPaths, ILogManager logManager) + : base(applicationPaths, logManager) { - _appInterface = appInterface; + } /// @@ -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(); } /// @@ -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); } /// diff --git a/MediaBrowser.ServerApplication/BackgroundService.cs b/MediaBrowser.ServerApplication/BackgroundService.cs index a8a9a5b50d..019a11e1c0 100644 --- a/MediaBrowser.ServerApplication/BackgroundService.cs +++ b/MediaBrowser.ServerApplication/BackgroundService.cs @@ -1,30 +1,40 @@ -using System.ServiceProcess; +using MediaBrowser.Model.Logging; +using System.ServiceProcess; namespace MediaBrowser.ServerApplication { + /// + /// Class BackgroundService + /// public class BackgroundService : ServiceBase { - public BackgroundService() + public static string Name = "MediaBrowser"; + public static string DisplayName = "Media Browser"; + + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + 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() + /// + /// 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. + /// + protected override void OnStop() { - base.OnShutdown(); + _logger.Info("Stop command received"); + + base.OnStop(); } } } diff --git a/MediaBrowser.ServerApplication/BackgroundServiceInstaller.cs b/MediaBrowser.ServerApplication/BackgroundServiceInstaller.cs new file mode 100644 index 0000000000..435d2f181b --- /dev/null +++ b/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"; + } + } +} diff --git a/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs b/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs index aac5a8cb83..87578ef84c 100644 --- a/MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs +++ b/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"); } } diff --git a/MediaBrowser.ServerApplication/IApplicationInterface.cs b/MediaBrowser.ServerApplication/IApplicationInterface.cs deleted file mode 100644 index e753248262..0000000000 --- a/MediaBrowser.ServerApplication/IApplicationInterface.cs +++ /dev/null @@ -1,32 +0,0 @@ -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 index e5d44c0f5b..921a4c1296 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/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 /// private static Mutex _singleInstanceMutex; - private static IApplicationInterface _applicationInterface; + private static ApplicationHost _appHost; + + private static App _app; /// /// 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() + /// + /// Creates the application paths. + /// + /// if set to true [run as service]. + /// ServerApplicationPaths. + 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(); + } + + /// + /// Begins the log. + /// + /// The logger. + 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()); + } + + /// + /// Runs the application. + /// + /// The app paths. + /// The log manager. + /// if set to true [run service]. + 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(); + } + + /// + /// Starts the service. + /// + 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) + /// + /// Handles the Disposed event of the service control. + /// + /// The source of the event. + /// The instance containing the event data. + static async void service_Disposed(object sender, EventArgs e) { - // Try to shutdown gracefully - if (_applicationInterface != null) + await _appHost.Shutdown(); + } + + /// + /// Installs the service. + /// + 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() + /// + /// Uninstalls the service. + /// + 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() + /// + /// Runs the service installation. + /// + 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) + /// + /// Runs the service uninstallation. + /// + 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(); } + } + + /// + /// Handles the SessionEnding event of the SystemEvents control. + /// + /// The source of the event. + /// The instance containing the event data. + static void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) + { + // Try to shutdown gracefully + var task = _appHost.Shutdown(); + + Task.WaitAll(task); + } + + /// + /// Handles the UnhandledException event of the CurrentDomain control. + /// + /// The source of the event. + /// The instance containing the event data. + 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 /// /// Releases the mutex. /// - 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; } + + /// + /// Performs the update if needed. + /// + /// The app paths. + /// The logger. + /// true if XXXX, false otherwise + 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; + } } } diff --git a/MediaBrowser.ServerApplication/MainWindow.xaml.cs b/MediaBrowser.ServerApplication/MainWindow.xaml.cs index 4dcdeef599..4c9c065e67 100644 --- a/MediaBrowser.ServerApplication/MainWindow.xaml.cs +++ b/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; diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 34d7eaf02e..3dc4faee6e 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -173,6 +173,7 @@ ..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll + False @@ -209,8 +210,10 @@ - + + Component + SplashWindow.xaml