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,30 +13,33 @@ namespace NzbDrone.Common.EnvironmentInfo 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; + OsInfo.IsWindows && + serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME) && + serviceProvider.GetStatus(ServiceProvider.NZBDRONE_SERVICE_NAME) == ServiceControllerStatus.StartPending; } - public bool IsUserInteractive + static RuntimeInfo() { - get { return Environment.UserInteractive; } + IsProduction = InternalIsProduction(); } - static RuntimeInfo() + public bool IsUserInteractive { - IsProduction = InternalIsProduction(); + get { return Environment.UserInteractive; } } public bool IsAdmin @@ -58,7 +61,18 @@ namespace NzbDrone.Common.EnvironmentInfo 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; using NLog.Config; using NLog.Targets; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Processes; namespace NzbDrone.Common.Instrumentation { @@ -30,7 +31,7 @@ namespace NzbDrone.Common.Instrumentation } 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.Linq; using System.ServiceProcess; using NLog; using NzbDrone.Common.Instrumentation; +using NzbDrone.Common.Processes; namespace NzbDrone.Common { @@ -20,14 +21,22 @@ namespace NzbDrone.Common 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 @@ namespace NzbDrone.Common 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 @@ namespace NzbDrone.Host public void Start() { + _runtimeInfo.IsRunning = true; _hostController.StartServer(); if (!_startupContext.Flags.Contains(StartupContext.NO_BROWSER) @@ -54,19 +56,29 @@ namespace NzbDrone.Host } protected override void OnStop() + { + Shutdown(); + } + + public ServiceBase Build() + { + return this; + } + + private void Shutdown() { _logger.Info("Attempting to stop application."); _hostController.StopServer(); _logger.Info("Application has finished stop routine."); - IsServiceStopped = true; + _runtimeInfo.IsRunning = false; } - public bool IsServiceStopped { get; private set; } - - public ServiceBase Build() + public void Handle(ApplicationShutdownRequested message) { - return this; + 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 @@ namespace NzbDrone.Host 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 @@ namespace NzbDrone.SysTray _trayIcon.Dispose(); } - base.Dispose(isDisposing); + if (InvokeRequired) + { + base.Invoke(new MethodInvoker(() => Dispose(isDisposing))); + } + + else + { + base.Dispose(isDisposing); + } } private void OnExit(object sender, EventArgs e)