From f69bb790771c85f90ddd7f93cf16376e7d7b9d1d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 12 Jan 2014 17:14:57 -0800 Subject: [PATCH] Shutdown! Restart working for services --- .../EnvironmentInfo/RuntimeInfo.cs | 32 +++++++---- .../Instrumentation/LogTargets.cs | 3 +- src/NzbDrone.Common/ServiceProvider.cs | 16 ++++++ .../Lifecycle/ApplicationRestartRequested.cs | 9 ++++ .../Lifecycle/Commands/RestartCommand.cs | 8 +++ .../Lifecycle/Commands/ShutdownCommand.cs | 8 +++ .../Lifecycle/LifestyleService.cs | 53 +++++++++++++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 4 ++ src/NzbDrone.Host/ApplicationServer.cs | 30 +++++++---- src/NzbDrone.Host/Bootstrap.cs | 4 +- src/NzbDrone/SysTray/SysTrayApp.cs | 10 +++- 11 files changed, 155 insertions(+), 22 deletions(-) create mode 100644 src/NzbDrone.Core/Lifecycle/ApplicationRestartRequested.cs create mode 100644 src/NzbDrone.Core/Lifecycle/Commands/RestartCommand.cs create mode 100644 src/NzbDrone.Core/Lifecycle/Commands/ShutdownCommand.cs create mode 100644 src/NzbDrone.Core/Lifecycle/LifestyleService.cs diff --git a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs index 7f81d23fd..4937ea1cb 100644 --- a/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs +++ b/src/NzbDrone.Common/EnvironmentInfo/RuntimeInfo.cs @@ -13,25 +13,23 @@ public interface IRuntimeInfo bool IsUserInteractive { get; } bool IsAdmin { get; } bool IsWindowsService { get; } + bool IsConsole { get; } + bool IsRunning { get; set; } } public class RuntimeInfo : IRuntimeInfo { private readonly Logger _logger; + private static readonly string ProcessName = Process.GetCurrentProcess().ProcessName.ToLower(); public RuntimeInfo(Logger logger, IServiceProvider serviceProvider) { _logger = logger; IsWindowsService = !IsUserInteractive && - OsInfo.IsWindows && - serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME) && - serviceProvider.GetStatus(ServiceProvider.NZBDRONE_SERVICE_NAME) == ServiceControllerStatus.StartPending; - } - - public bool IsUserInteractive - { - get { return Environment.UserInteractive; } + OsInfo.IsWindows && + serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME) && + serviceProvider.GetStatus(ServiceProvider.NZBDRONE_SERVICE_NAME) == ServiceControllerStatus.StartPending; } static RuntimeInfo() @@ -39,6 +37,11 @@ static RuntimeInfo() IsProduction = InternalIsProduction(); } + public bool IsUserInteractive + { + get { return Environment.UserInteractive; } + } + public bool IsAdmin { get @@ -58,7 +61,18 @@ public bool IsAdmin public bool IsWindowsService { get; private set; } - private static readonly string ProcessName = Process.GetCurrentProcess().ProcessName.ToLower(); + public bool IsConsole + { + get + { + return (OsInfo.IsWindows && + IsUserInteractive && + ProcessName.Equals("NzbDrone.Console.exe", StringComparison.InvariantCultureIgnoreCase)) || + OsInfo.IsLinux; + } + } + + public bool IsRunning { get; set; } public static bool IsProduction { get; private set; } diff --git a/src/NzbDrone.Common/Instrumentation/LogTargets.cs b/src/NzbDrone.Common/Instrumentation/LogTargets.cs index 514caf506..f44ce456c 100644 --- a/src/NzbDrone.Common/Instrumentation/LogTargets.cs +++ b/src/NzbDrone.Common/Instrumentation/LogTargets.cs @@ -5,6 +5,7 @@ using NLog.Config; using NLog.Targets; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Processes; namespace NzbDrone.Common.Instrumentation { @@ -30,7 +31,7 @@ public static void Register(IStartupContext startupContext, bool updateApp, bool } else { - if (inConsole && (OsInfo.IsLinux || new RuntimeInfo(null, new ServiceProvider()).IsUserInteractive)) + if (inConsole && (OsInfo.IsLinux || new RuntimeInfo(null, new ServiceProvider(new ProcessProvider())).IsUserInteractive)) { RegisterConsole(); } diff --git a/src/NzbDrone.Common/ServiceProvider.cs b/src/NzbDrone.Common/ServiceProvider.cs index e669321cf..1137017f8 100644 --- a/src/NzbDrone.Common/ServiceProvider.cs +++ b/src/NzbDrone.Common/ServiceProvider.cs @@ -6,6 +6,7 @@ using System.ServiceProcess; using NLog; using NzbDrone.Common.Instrumentation; +using NzbDrone.Common.Processes; namespace NzbDrone.Common { @@ -20,14 +21,22 @@ public interface IServiceProvider void Stop(string serviceName); void Start(string serviceName); ServiceControllerStatus GetStatus(string serviceName); + void Restart(string serviceName); } public class ServiceProvider : IServiceProvider { public const string NZBDRONE_SERVICE_NAME = "NzbDrone"; + private readonly IProcessProvider _processProvider; + private static readonly Logger Logger = NzbDroneLogger.GetLogger(); + public ServiceProvider(IProcessProvider processProvider) + { + _processProvider = processProvider; + } + public virtual bool ServiceExist(string name) { Logger.Debug("Checking if service {0} exists.", name); @@ -173,5 +182,12 @@ public void Start(string serviceName) Logger.Error("Service start request has timed out. {0}", service.Status); } } + + public void Restart(string serviceName) + { + var args = String.Format("/C net.exe stop \"{0}\" && net.exe start \"{0}\"", serviceName); + + _processProvider.Start("cmd.exe", args); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Lifecycle/ApplicationRestartRequested.cs b/src/NzbDrone.Core/Lifecycle/ApplicationRestartRequested.cs new file mode 100644 index 000000000..7aa08bb1f --- /dev/null +++ b/src/NzbDrone.Core/Lifecycle/ApplicationRestartRequested.cs @@ -0,0 +1,9 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Lifecycle +{ + public class ApplicationRestartRequested : IEvent + { + + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Lifecycle/Commands/RestartCommand.cs b/src/NzbDrone.Core/Lifecycle/Commands/RestartCommand.cs new file mode 100644 index 000000000..82c20cc07 --- /dev/null +++ b/src/NzbDrone.Core/Lifecycle/Commands/RestartCommand.cs @@ -0,0 +1,8 @@ +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.Lifecycle.Commands +{ + public class RestartCommand : Command + { + } +} diff --git a/src/NzbDrone.Core/Lifecycle/Commands/ShutdownCommand.cs b/src/NzbDrone.Core/Lifecycle/Commands/ShutdownCommand.cs new file mode 100644 index 000000000..b0fffd8e5 --- /dev/null +++ b/src/NzbDrone.Core/Lifecycle/Commands/ShutdownCommand.cs @@ -0,0 +1,8 @@ +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.Lifecycle.Commands +{ + public class ShutdownCommand : Command + { + } +} diff --git a/src/NzbDrone.Core/Lifecycle/LifestyleService.cs b/src/NzbDrone.Core/Lifecycle/LifestyleService.cs new file mode 100644 index 000000000..d08aee767 --- /dev/null +++ b/src/NzbDrone.Core/Lifecycle/LifestyleService.cs @@ -0,0 +1,53 @@ +using NzbDrone.Common; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Processes; +using NzbDrone.Core.Lifecycle.Commands; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; +using IServiceProvider = NzbDrone.Common.IServiceProvider; + +namespace NzbDrone.Core.Lifecycle +{ + public class LifestyleService: IExecute, IExecute + { + private readonly IEventAggregator _eventAggregator; + private readonly IRuntimeInfo _runtimeInfo; + private readonly IServiceProvider _serviceProvider; + private readonly IProcessProvider _processProvider; + + + public LifestyleService(IEventAggregator eventAggregator, + IRuntimeInfo runtimeInfo, + IServiceProvider serviceProvider, + IProcessProvider processProvider) + { + _eventAggregator = eventAggregator; + _runtimeInfo = runtimeInfo; + _serviceProvider = serviceProvider; + _processProvider = processProvider; + } + + public void Execute(ShutdownCommand message) + { + if (_runtimeInfo.IsWindowsService) + { + _serviceProvider.Stop(ServiceProvider.NZBDRONE_SERVICE_NAME); + } + + else + { + _eventAggregator.PublishEvent(new ApplicationShutdownRequested()); + } + } + + public void Execute(RestartCommand message) + { + if (_runtimeInfo.IsWindowsService) + { + _serviceProvider.Restart(ServiceProvider.NZBDRONE_SERVICE_NAME); + } + + _eventAggregator.PublishEvent(new ApplicationRestartRequested()); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index f52c84c8c..800258bde 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -290,6 +290,10 @@ + + + + diff --git a/src/NzbDrone.Host/ApplicationServer.cs b/src/NzbDrone.Host/ApplicationServer.cs index 6ab53244f..b506e64fd 100644 --- a/src/NzbDrone.Host/ApplicationServer.cs +++ b/src/NzbDrone.Host/ApplicationServer.cs @@ -2,18 +2,19 @@ using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Host.Owin; namespace NzbDrone.Host { public interface INzbDroneServiceFactory { - bool IsServiceStopped { get; } ServiceBase Build(); void Start(); } - public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory + public class NzbDroneServiceFactory : ServiceBase, INzbDroneServiceFactory, IHandle { private readonly IConfigFileProvider _configFileProvider; private readonly IRuntimeInfo _runtimeInfo; @@ -42,6 +43,7 @@ protected override void OnStart(string[] args) public void Start() { + _runtimeInfo.IsRunning = true; _hostController.StartServer(); if (!_startupContext.Flags.Contains(StartupContext.NO_BROWSER) @@ -55,18 +57,28 @@ public void Start() protected override void OnStop() { - _logger.Info("Attempting to stop application."); - _hostController.StopServer(); - _logger.Info("Application has finished stop routine."); - IsServiceStopped = true; + Shutdown(); } - public bool IsServiceStopped { get; private set; } - public ServiceBase Build() { return this; } - } + private void Shutdown() + { + _logger.Info("Attempting to stop application."); + _hostController.StopServer(); + _logger.Info("Application has finished stop routine."); + _runtimeInfo.IsRunning = false; + } + + public void Handle(ApplicationShutdownRequested message) + { + if (!_runtimeInfo.IsWindowsService) + { + Shutdown(); + } + } + } } \ No newline at end of file diff --git a/src/NzbDrone.Host/Bootstrap.cs b/src/NzbDrone.Host/Bootstrap.cs index 7909ea526..87448075b 100644 --- a/src/NzbDrone.Host/Bootstrap.cs +++ b/src/NzbDrone.Host/Bootstrap.cs @@ -72,9 +72,9 @@ private static void SpinToExit(ApplicationModes applicationModes) return; } - var serviceFactory = _container.Resolve(); + var runTimeInfo = _container.Resolve(); - while (!serviceFactory.IsServiceStopped) + while (runTimeInfo.IsRunning) { Thread.Sleep(1000); } diff --git a/src/NzbDrone/SysTray/SysTrayApp.cs b/src/NzbDrone/SysTray/SysTrayApp.cs index bfbc96cb2..730ea346f 100644 --- a/src/NzbDrone/SysTray/SysTrayApp.cs +++ b/src/NzbDrone/SysTray/SysTrayApp.cs @@ -68,7 +68,15 @@ protected override void Dispose(bool isDisposing) _trayIcon.Dispose(); } - base.Dispose(isDisposing); + if (InvokeRequired) + { + base.Invoke(new MethodInvoker(() => Dispose(isDisposing))); + } + + else + { + base.Dispose(isDisposing); + } } private void OnExit(object sender, EventArgs e)