Fix Tray App and Windows Server Restart

Co-Authored-By: ta264 <ta264@users.noreply.github.com>
pull/5116/head
Qstick 3 years ago committed by Mark McDowall
parent e65aebdcf8
commit a4232549cb

@ -3,6 +3,8 @@ using DryIoc;
using DryIoc.Microsoft.DependencyInjection; using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Composition.Extensions; using NzbDrone.Common.Composition.Extensions;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
@ -25,12 +27,15 @@ namespace NzbDrone.Common.Test
.AddNzbDroneLogger() .AddNzbDroneLogger()
.AutoAddServices(Bootstrap.ASSEMBLIES) .AutoAddServices(Bootstrap.ASSEMBLIES)
.AddDummyDatabase() .AddDummyDatabase()
.AddStartupContext(new StartupContext("first", "second")) .AddStartupContext(new StartupContext("first", "second"));
.GetServiceProvider();
container.GetRequiredService<IAppFolderFactory>().Register(); container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
Mocker.SetConstant<System.IServiceProvider>(container); var serviceProvider = container.GetServiceProvider();
serviceProvider.GetRequiredService<IAppFolderFactory>().Register();
Mocker.SetConstant<System.IServiceProvider>(serviceProvider);
var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>() var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>()
.Select(c => c.GetType().FullName); .Select(c => c.GetType().FullName);

@ -1,9 +1,9 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection;
using System.Security.Principal; using System.Security.Principal;
using System.ServiceProcess; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.WindowsServices;
using NLog; using NLog;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
@ -14,14 +14,11 @@ namespace NzbDrone.Common.EnvironmentInfo
private readonly Logger _logger; private readonly Logger _logger;
private readonly DateTime _startTime = DateTime.UtcNow; private readonly DateTime _startTime = DateTime.UtcNow;
public RuntimeInfo(IServiceProvider serviceProvider, Logger logger) public RuntimeInfo(Logger logger, IHostLifetime hostLifetime = null)
{ {
_logger = logger; _logger = logger;
IsWindowsService = !IsUserInteractive && IsWindowsService = hostLifetime is WindowsServiceLifetime;
OsInfo.IsWindows &&
serviceProvider.ServiceExist(ServiceProvider.SERVICE_NAME) &&
serviceProvider.GetStatus(ServiceProvider.SERVICE_NAME) == ServiceControllerStatus.StartPending;
// net6.0 will return Sonarr.dll for entry assembly, we need the actual // net6.0 will return Sonarr.dll for entry assembly, we need the actual
// executable name (Sonarr on linux). On mono this will return the location of // executable name (Sonarr on linux). On mono this will return the location of

@ -6,6 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DryIoc.dll" Version="4.8.6" /> <PackageReference Include="DryIoc.dll" Version="4.8.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.14" /> <PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" /> <PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" />

@ -4,7 +4,6 @@
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
<ApplicationIcon>..\NzbDrone.Host\Sonarr.ico</ApplicationIcon> <ApplicationIcon>..\NzbDrone.Host\Sonarr.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="!$(RuntimeIdentifier.StartsWith('win'))"> <PropertyGroup Condition="!$(RuntimeIdentifier.StartsWith('win'))">
<AssemblyName>Sonarr</AssemblyName> <AssemblyName>Sonarr</AssemblyName>

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.net>
<connectionManagement>
<add address="*" maxconnection="100" />
</connectionManagement>
</system.net>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
</startup>
<runtime>
<loadFromRemoteSources enabled="true" />
</runtime>
</configuration>

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
</assembly>

@ -4,6 +4,7 @@ using DryIoc;
using DryIoc.Microsoft.DependencyInjection; using DryIoc.Microsoft.DependencyInjection;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common; using NzbDrone.Common;
@ -14,7 +15,6 @@ using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@ -35,16 +35,15 @@ namespace NzbDrone.App.Test
{ {
var args = new StartupContext("first", "second"); var args = new StartupContext("first", "second");
// set up a dummy broadcaster to allow tests to resolve
var mockBroadcaster = new Mock<IBroadcastSignalRMessage>();
var container = new Container(rules => rules.WithNzbDroneRules()) var container = new Container(rules => rules.WithNzbDroneRules())
.AutoAddServices(Bootstrap.ASSEMBLIES) .AutoAddServices(Bootstrap.ASSEMBLIES)
.AddNzbDroneLogger() .AddNzbDroneLogger()
.AddDummyDatabase() .AddDummyDatabase()
.AddStartupContext(args); .AddStartupContext(args);
container.RegisterInstance<IBroadcastSignalRMessage>(mockBroadcaster.Object); // dummy lifetime and broadcaster so tests resolve
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
container.RegisterInstance<IBroadcastSignalRMessage>(new Mock<IBroadcastSignalRMessage>().Object);
_container = container.GetServiceProvider(); _container = container.GetServiceProvider();
} }

@ -20,7 +20,6 @@ namespace NzbDrone.Host
private readonly IBrowserService _browserService; private readonly IBrowserService _browserService;
private readonly IProcessProvider _processProvider; private readonly IProcessProvider _processProvider;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IUtilityModeRouter _utilityModeRouter;
private readonly Logger _logger; private readonly Logger _logger;
public AppLifetime(IHostApplicationLifetime appLifetime, public AppLifetime(IHostApplicationLifetime appLifetime,
@ -30,7 +29,6 @@ namespace NzbDrone.Host
IBrowserService browserService, IBrowserService browserService,
IProcessProvider processProvider, IProcessProvider processProvider,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IUtilityModeRouter utilityModeRouter,
Logger logger) Logger logger)
{ {
_appLifetime = appLifetime; _appLifetime = appLifetime;
@ -40,7 +38,6 @@ namespace NzbDrone.Host
_browserService = browserService; _browserService = browserService;
_processProvider = processProvider; _processProvider = processProvider;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_utilityModeRouter = utilityModeRouter;
_logger = logger; _logger = logger;
appLifetime.ApplicationStarted.Register(OnAppStarted); appLifetime.ApplicationStarted.Register(OnAppStarted);
@ -72,7 +69,7 @@ namespace NzbDrone.Host
private void OnAppStopped() private void OnAppStopped()
{ {
if (_runtimeInfo.RestartPending) if (_runtimeInfo.RestartPending && !_runtimeInfo.IsWindowsService)
{ {
var restartArgs = GetRestartArgs(); var restartArgs = GetRestartArgs();

@ -176,7 +176,18 @@ namespace NzbDrone.Host
return ApplicationModes.UninstallService; return ApplicationModes.UninstallService;
} }
if (OsInfo.IsWindows && WindowsServiceHelpers.IsWindowsService()) // IsWindowsService can throw sometimes, so wrap it
var isWindowsService = false;
try
{
isWindowsService = WindowsServiceHelpers.IsWindowsService();
}
catch (Exception e)
{
Logger.Error(e, "Failed to get service status");
}
if (OsInfo.IsWindows && isWindowsService)
{ {
return ApplicationModes.Service; return ApplicationModes.Service;
} }

@ -16,21 +16,18 @@ namespace NzbDrone.Host
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IConsoleService _consoleService; private readonly IConsoleService _consoleService;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IProcessProvider _processProvider; private readonly IProcessProvider _processProvider;
private readonly IRemoteAccessAdapter _remoteAccessAdapter; private readonly IRemoteAccessAdapter _remoteAccessAdapter;
private readonly Logger _logger; private readonly Logger _logger;
public UtilityModeRouter(IServiceProvider serviceProvider, public UtilityModeRouter(IServiceProvider serviceProvider,
IConsoleService consoleService, IConsoleService consoleService,
IRuntimeInfo runtimeInfo,
IProcessProvider processProvider, IProcessProvider processProvider,
IRemoteAccessAdapter remoteAccessAdapter, IRemoteAccessAdapter remoteAccessAdapter,
Logger logger) Logger logger)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_consoleService = consoleService; _consoleService = consoleService;
_runtimeInfo = runtimeInfo;
_processProvider = processProvider; _processProvider = processProvider;
_remoteAccessAdapter = remoteAccessAdapter; _remoteAccessAdapter = remoteAccessAdapter;
_logger = logger; _logger = logger;

@ -5,7 +5,6 @@
<RuntimeIdentifiers>win-x64;win-x86</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;win-x86</RuntimeIdentifiers>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>..\NzbDrone.Host\Sonarr.ico</ApplicationIcon> <ApplicationIcon>..\NzbDrone.Host\Sonarr.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
<GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources> <GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -13,6 +12,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Sonarr.Host.csproj" /> <ProjectReference Include="..\NzbDrone.Host\Sonarr.Host.csproj" />
<ProjectReference Include="..\NzbDrone.Windows\Sonarr.Windows.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Properties\Resources.Designer.cs"> <Compile Update="Properties\Resources.Designer.cs">

@ -4,9 +4,8 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes; using NzbDrone.Core.Lifecycle;
using NzbDrone.Host; using NzbDrone.Host;
namespace NzbDrone.SysTray namespace NzbDrone.SysTray
@ -14,28 +13,19 @@ namespace NzbDrone.SysTray
public class SystemTrayApp : Form, IHostedService public class SystemTrayApp : Form, IHostedService
{ {
private readonly IBrowserService _browserService; private readonly IBrowserService _browserService;
private readonly IRuntimeInfo _runtimeInfo; private readonly ILifecycleService _lifecycle;
private readonly IProcessProvider _processProvider;
private readonly NotifyIcon _trayIcon = new NotifyIcon(); private readonly NotifyIcon _trayIcon = new NotifyIcon();
private readonly ContextMenuStrip _trayMenu = new ContextMenuStrip(); private readonly ContextMenuStrip _trayMenu = new ContextMenuStrip();
public SystemTrayApp(IBrowserService browserService, IRuntimeInfo runtimeInfo, IProcessProvider processProvider) public SystemTrayApp(IBrowserService browserService, ILifecycleService lifecycle)
{ {
_browserService = browserService; _browserService = browserService;
_runtimeInfo = runtimeInfo; _lifecycle = lifecycle;
_processProvider = processProvider;
} }
public void Start() public void Start()
{ {
Application.ThreadException += OnThreadException;
Application.ApplicationExit += OnApplicationExit;
Application.SetHighDpiMode(HighDpiMode.PerMonitor);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
_trayMenu.Items.Add(new ToolStripMenuItem("Launch Browser", null, LaunchBrowser)); _trayMenu.Items.Add(new ToolStripMenuItem("Launch Browser", null, LaunchBrowser));
_trayMenu.Items.Add(new ToolStripSeparator()); _trayMenu.Items.Add(new ToolStripSeparator());
_trayMenu.Items.Add(new ToolStripMenuItem("Exit", null, OnExit)); _trayMenu.Items.Add(new ToolStripMenuItem("Exit", null, OnExit));
@ -69,12 +59,6 @@ namespace NzbDrone.SysTray
DisposeTrayIcon(); DisposeTrayIcon();
} }
protected override void OnClosed(EventArgs e)
{
Console.WriteLine("Closing");
base.OnClosed(e);
}
protected override void OnLoad(EventArgs e) protected override void OnLoad(EventArgs e)
{ {
Visible = false; Visible = false;
@ -102,8 +86,7 @@ namespace NzbDrone.SysTray
private void OnExit(object sender, EventArgs e) private void OnExit(object sender, EventArgs e)
{ {
LogManager.Configuration = null; _lifecycle.Shutdown();
Environment.Exit(0);
} }
private void LaunchBrowser(object sender, EventArgs e) private void LaunchBrowser(object sender, EventArgs e)
@ -117,33 +100,17 @@ namespace NzbDrone.SysTray
} }
} }
private void OnApplicationExit(object sender, EventArgs e) private void DisposeTrayIcon()
{ {
if (_runtimeInfo.RestartPending) if (_trayIcon == null)
{ {
_processProvider.SpawnNewProcess(_runtimeInfo.ExecutingApplication, "--restart --nobrowser"); return;
} }
DisposeTrayIcon();
}
private void OnThreadException(object sender, EventArgs e)
{
DisposeTrayIcon();
}
private void DisposeTrayIcon()
{
try
{
_trayIcon.Visible = false; _trayIcon.Visible = false;
_trayIcon.Icon = null; _trayIcon.Icon = null;
_trayIcon.Visible = false; _trayIcon.Visible = false;
_trayIcon.Dispose(); _trayIcon.Dispose();
} }
catch (Exception)
{
}
}
} }
} }

@ -16,20 +16,22 @@ namespace NzbDrone
public static void Main(string[] args) public static void Main(string[] args)
{ {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.SystemAware);
try try
{ {
var startupArgs = new StartupContext(args); var startupArgs = new StartupContext(args);
NzbDroneLogger.Register(startupArgs, false, true); NzbDroneLogger.Register(startupArgs, false, true);
Bootstrap.Start(args, e => Bootstrap.Start(args, e => { e.ConfigureServices((_, s) => s.AddSingleton<IHostedService, SystemTrayApp>()); });
{
e.ConfigureServices((_, s) => s.AddSingleton<IHostedService, SystemTrayApp>());
});
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Fatal(e, "EPIC FAIL"); Logger.Fatal(e, "EPIC FAIL");
var message = string.Format("{0}: {1}", e.GetType().Name, e.ToString());
MessageBox.Show($"{e.GetType().Name}: {e.Message}", buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Error, caption: "Epic Fail!"); MessageBox.Show($"{e.GetType().Name}: {e.Message}", buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Error, caption: "Epic Fail!");
} }
} }

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.net>
<connectionManagement>
<add address="*" maxconnection="100" />
</connectionManagement>
</system.net>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
</startup>
<runtime>
<loadFromRemoteSources enabled="true" />
</runtime>
</configuration>

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
</assembly>
Loading…
Cancel
Save