diff --git a/build.ps1 b/build.ps1 index 69b85fcfe..0a20c3af3 100644 --- a/build.ps1 +++ b/build.ps1 @@ -6,6 +6,7 @@ $testPackageFolder = '.\_tests\' $testSearchPattern = '*.Test\bin\x86\Release' $sourceFolder = '.\src' $updateFolder = $outputFolder + '\NzbDrone.Update' +$updateFolderMono = $outputFolderMono + '\NzbDrone.Update' Function Build() { @@ -73,9 +74,6 @@ Function PackageMono() Copy-Item $outputFolder $outputFolderMono -recurse - Write-Host Removing Update Client - Remove-Item -Recurse -Force "$outputFolderMono\NzbDrone.Update" - Write-Host Creating MDBs get-childitem $outputFolderMono -File -Include @("*.exe", "*.dll") -Exclude @("MediaInfo.dll", "sqlite3.dll") -Recurse | foreach ($_) { Write-Host "Creating .mdb for $_" @@ -110,6 +108,9 @@ Function PackageMono() Remove-Item "$outputFolderMono\NzbDrone.Console.vshost.exe" + Write-Host Adding NzbDrone.Mono to UpdatePackage + Copy-Item $outputFolderMono\* $updateFolderMono -Filter NzbDrone.Mono.* + Write-Host "##teamcity[progressFinish 'Creating Mono Package']" } diff --git a/src/NzbDrone.Api/Config/HostConfigModule.cs b/src/NzbDrone.Api/Config/HostConfigModule.cs index 26c184c45..1c9d37aa7 100644 --- a/src/NzbDrone.Api/Config/HostConfigModule.cs +++ b/src/NzbDrone.Api/Config/HostConfigModule.cs @@ -3,7 +3,9 @@ using System.Reflection; using FluentValidation; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Update; using NzbDrone.Core.Validation; +using NzbDrone.Core.Validation.Paths; using Omu.ValueInjecter; namespace NzbDrone.Api.Config @@ -12,7 +14,7 @@ namespace NzbDrone.Api.Config { private readonly IConfigFileProvider _configFileProvider; - public HostConfigModule(ConfigFileProvider configFileProvider) + public HostConfigModule(IConfigFileProvider configFileProvider) : base("/config/host") { _configFileProvider = configFileProvider; @@ -29,6 +31,8 @@ namespace NzbDrone.Api.Config SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl); SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows); + + SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script); } private HostConfigResource GetHostConfig() diff --git a/src/NzbDrone.Api/Config/HostConfigResource.cs b/src/NzbDrone.Api/Config/HostConfigResource.cs index 3387ba421..cc52c01c4 100644 --- a/src/NzbDrone.Api/Config/HostConfigResource.cs +++ b/src/NzbDrone.Api/Config/HostConfigResource.cs @@ -1,5 +1,6 @@ using System; using NzbDrone.Api.REST; +using NzbDrone.Core.Update; namespace NzbDrone.Api.Config { @@ -14,10 +15,12 @@ namespace NzbDrone.Api.Config public String Password { get; set; } public String LogLevel { get; set; } public String Branch { get; set; } - public Boolean AutoUpdate { get; set; } public String ApiKey { get; set; } public Boolean Torrent { get; set; } public String SslCertHash { get; set; } public String UrlBase { get; set; } + public Boolean UpdateAutomatically { get; set; } + public UpdateMechanism UpdateMechanism { get; set; } + public String UpdateScriptPath { get; set; } } } diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 9c7f11503..da8ffac05 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -162,6 +162,7 @@ + diff --git a/src/NzbDrone.Api/Update/UpdateModule.cs b/src/NzbDrone.Api/Update/UpdateModule.cs index 02696a9b8..959dd0d7c 100644 --- a/src/NzbDrone.Api/Update/UpdateModule.cs +++ b/src/NzbDrone.Api/Update/UpdateModule.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; -using NzbDrone.Api.REST; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Update; using NzbDrone.Api.Mapping; @@ -44,18 +41,4 @@ namespace NzbDrone.Api.Update return resources; } } - - public class UpdateResource : RestResource - { - [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))] - public Version Version { get; set; } - - public String Branch { get; set; } - public DateTime ReleaseDate { get; set; } - public String FileName { get; set; } - public String Url { get; set; } - public Boolean IsUpgrade { get; set; } - public Boolean Installed { get; set; } - public UpdateChanges Changes { get; set; } - } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Update/UpdateResource.cs b/src/NzbDrone.Api/Update/UpdateResource.cs new file mode 100644 index 000000000..c12edfbc1 --- /dev/null +++ b/src/NzbDrone.Api/Update/UpdateResource.cs @@ -0,0 +1,22 @@ +using System; +using Newtonsoft.Json; +using NzbDrone.Api.REST; +using NzbDrone.Core.Update; + +namespace NzbDrone.Api.Update +{ + public class UpdateResource : RestResource + { + [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))] + public Version Version { get; set; } + + public String Branch { get; set; } + public DateTime ReleaseDate { get; set; } + public String FileName { get; set; } + public String Url { get; set; } + public Boolean IsUpgrade { get; set; } + public Boolean Installed { get; set; } + public UpdateChanges Changes { get; set; } + public String Hash { get; set; } + } +} diff --git a/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs b/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs index faef69f54..63693a88f 100644 --- a/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs +++ b/src/NzbDrone.App.Test/NzbDroneProcessServiceFixture.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Moq; using NUnit.Framework; using NzbDrone.Common.Model; @@ -18,17 +19,25 @@ namespace NzbDrone.App.Test { Mocker.GetMock().Setup(c => c.GetCurrentProcess()) .Returns(new ProcessInfo() { Id = CURRENT_PROCESS_ID }); + + Mocker.GetMock() + .Setup(s => s.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME)) + .Returns(new List()); + + Mocker.GetMock() + .Setup(s => s.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME)) + .Returns(new List()); } [Test] public void should_continue_if_only_instance() { - Mocker.GetMock().Setup(c => c.FindNzbDroneProcesses()) - .Returns(new List - { - new ProcessInfo{Id = CURRENT_PROCESS_ID} - }); - + Mocker.GetMock() + .Setup(c => c.FindProcessByName(It.Is(f => f.Contains("NzbDrone")))) + .Returns(new List + { + new ProcessInfo {Id = CURRENT_PROCESS_ID} + }); Subject.PreventStartIfAlreadyRunning(); @@ -38,13 +47,13 @@ namespace NzbDrone.App.Test [Test] public void should_enforce_if_another_console_is_running() { - Mocker.GetMock() - .Setup(c => c.FindNzbDroneProcesses()) - .Returns(new List - { - new ProcessInfo{Id = 10}, - new ProcessInfo{Id = CURRENT_PROCESS_ID} - }); + Mocker.GetMock() + .Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME)) + .Returns(new List + { + new ProcessInfo {Id = 10}, + new ProcessInfo {Id = CURRENT_PROCESS_ID} + }); Assert.Throws(() => Subject.PreventStartIfAlreadyRunning()); Mocker.GetMock().Verify(c => c.LaunchWebUI(), Times.Once()); @@ -54,14 +63,14 @@ namespace NzbDrone.App.Test [Test] public void should_return_false_if_another_gui_is_running() { - Mocker.GetMock() - .Setup(c => c.FindNzbDroneProcesses()) - .Returns(new List - { - new ProcessInfo{Id = CURRENT_PROCESS_ID}, - new ProcessInfo{Id = 10} - - }); + Mocker.GetMock() + .Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME)) + .Returns(new List + { + new ProcessInfo {Id = CURRENT_PROCESS_ID}, + new ProcessInfo {Id = 10} + + }); Assert.Throws(() => Subject.PreventStartIfAlreadyRunning()); Mocker.GetMock().Verify(c => c.LaunchWebUI(), Times.Once()); diff --git a/src/NzbDrone.Common.Test/PathExtensionFixture.cs b/src/NzbDrone.Common.Test/PathExtensionFixture.cs index 4dd274a33..fd59e7eec 100644 --- a/src/NzbDrone.Common.Test/PathExtensionFixture.cs +++ b/src/NzbDrone.Common.Test/PathExtensionFixture.cs @@ -168,25 +168,25 @@ namespace NzbDrone.Common.Test [Test] public void Sanbox() { - GetIAppDirectoryInfo().GetUpdateSandboxFolder().Should().BeEquivalentTo(@"C:\Temp\Nzbdrone_update\".AsOsAgnostic()); + GetIAppDirectoryInfo().GetUpdateSandboxFolder().Should().BeEquivalentTo(@"C:\Temp\nzbdrone_update\".AsOsAgnostic()); } [Test] public void GetUpdatePackageFolder() { - GetIAppDirectoryInfo().GetUpdatePackageFolder().Should().BeEquivalentTo(@"C:\Temp\Nzbdrone_update\NzbDrone\".AsOsAgnostic()); + GetIAppDirectoryInfo().GetUpdatePackageFolder().Should().BeEquivalentTo(@"C:\Temp\nzbdrone_update\NzbDrone\".AsOsAgnostic()); } [Test] public void GetUpdateClientFolder() { - GetIAppDirectoryInfo().GetUpdateClientFolder().Should().BeEquivalentTo(@"C:\Temp\Nzbdrone_update\NzbDrone\NzbDrone.Update\".AsOsAgnostic()); + GetIAppDirectoryInfo().GetUpdateClientFolder().Should().BeEquivalentTo(@"C:\Temp\nzbdrone_update\NzbDrone\NzbDrone.Update\".AsOsAgnostic()); } [Test] public void GetUpdateClientExePath() { - GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\Nzbdrone_update\NzbDrone.Update.exe".AsOsAgnostic()); + GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\nzbdrone_update\NzbDrone.Update.exe".AsOsAgnostic()); } [Test] diff --git a/src/NzbDrone.Common/Disk/DiskProviderBase.cs b/src/NzbDrone.Common/Disk/DiskProviderBase.cs index c2c4d0075..47a14be5c 100644 --- a/src/NzbDrone.Common/Disk/DiskProviderBase.cs +++ b/src/NzbDrone.Common/Disk/DiskProviderBase.cs @@ -447,5 +447,15 @@ namespace NzbDrone.Common.Disk return driveInfo.VolumeLabel; } + + public FileStream StreamFile(string path) + { + if (!FileExists(path)) + { + throw new FileNotFoundException("Unable to find file: " + path, path); + } + + return new FileStream(path, FileMode.Open); + } } } diff --git a/src/NzbDrone.Common/Disk/IDiskProvider.cs b/src/NzbDrone.Common/Disk/IDiskProvider.cs index 896f393bb..b57486ff9 100644 --- a/src/NzbDrone.Common/Disk/IDiskProvider.cs +++ b/src/NzbDrone.Common/Disk/IDiskProvider.cs @@ -11,7 +11,6 @@ namespace NzbDrone.Common.Disk void InheritFolderPermissions(string filename); void SetPermissions(string path, string mask, string user, string group); long? GetTotalSize(string path); - DateTime FolderGetLastWrite(string path); DateTime FileGetLastWrite(string path); DateTime FileGetLastWriteUtc(string path); @@ -44,5 +43,6 @@ namespace NzbDrone.Common.Disk void EmptyFolder(string path); string[] GetFixedDrives(); string GetVolumeLabel(string path); + FileStream StreamFile(string path); } } \ No newline at end of file diff --git a/src/NzbDrone.Common/EnvironmentInfo/StartupContext.cs b/src/NzbDrone.Common/EnvironmentInfo/StartupContext.cs index dbf4e6d65..1e21eeb48 100644 --- a/src/NzbDrone.Common/EnvironmentInfo/StartupContext.cs +++ b/src/NzbDrone.Common/EnvironmentInfo/StartupContext.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Text; namespace NzbDrone.Common.EnvironmentInfo { diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 6520ff59d..e0a90c375 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -105,7 +105,6 @@ - diff --git a/src/NzbDrone.Common/PathExtensions.cs b/src/NzbDrone.Common/PathExtensions.cs index 16a9bddd1..a5c171039 100644 --- a/src/NzbDrone.Common/PathExtensions.cs +++ b/src/NzbDrone.Common/PathExtensions.cs @@ -13,10 +13,10 @@ namespace NzbDrone.Common private const string NZBDRONE_LOG_DB = "logs.db"; private const string BACKUP_ZIP_FILE = "NzbDrone_Backup.zip"; private const string NLOG_CONFIG_FILE = "nlog.config"; - private const string UPDATE_CLIENT_EXE = "nzbdrone.update.exe"; + private const string UPDATE_CLIENT_EXE = "NzbDrone.Update.exe"; private static readonly string UPDATE_SANDBOX_FOLDER_NAME = "nzbdrone_update" + Path.DirectorySeparatorChar; - private static readonly string UPDATE_PACKAGE_FOLDER_NAME = "nzbdrone" + Path.DirectorySeparatorChar; + private static readonly string UPDATE_PACKAGE_FOLDER_NAME = "NzbDrone" + Path.DirectorySeparatorChar; private static readonly string UPDATE_BACKUP_FOLDER_NAME = "nzbdrone_backup" + Path.DirectorySeparatorChar; private static readonly string UPDATE_BACKUP_APPDATA_FOLDER_NAME = "nzbdrone_appdata_backup" + Path.DirectorySeparatorChar; private static readonly string UPDATE_CLIENT_FOLDER_NAME = "NzbDrone.Update" + Path.DirectorySeparatorChar; diff --git a/src/NzbDrone.Common/Processes/INzbDroneProcessProvider.cs b/src/NzbDrone.Common/Processes/INzbDroneProcessProvider.cs deleted file mode 100644 index 71abd6d0e..000000000 --- a/src/NzbDrone.Common/Processes/INzbDroneProcessProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using NzbDrone.Common.Model; - -namespace NzbDrone.Common.Processes -{ - public interface INzbDroneProcessProvider - { - List FindNzbDroneProcesses(); - } -} diff --git a/src/NzbDrone.Common/Processes/ProcessProvider.cs b/src/NzbDrone.Common/Processes/ProcessProvider.cs index 7581a4b5c..6e9d2c0db 100644 --- a/src/NzbDrone.Common/Processes/ProcessProvider.cs +++ b/src/NzbDrone.Common/Processes/ProcessProvider.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Common.Processes void SetPriority(int processId, ProcessPriorityClass priority); void KillAll(string processName); void Kill(int processId); - bool Exists(string processName); + Boolean Exists(string processName); ProcessPriorityClass GetCurrentProcessPriority(); Process Start(string path, string args = null, Action onOutputDataReceived = null, Action onErrorDataReceived = null); Process SpawnNewProcess(string path, string args = null); @@ -35,20 +35,12 @@ namespace NzbDrone.Common.Processes public const string NZB_DRONE_PROCESS_NAME = "NzbDrone"; public const string NZB_DRONE_CONSOLE_PROCESS_NAME = "NzbDrone.Console"; - private static List GetProcessesByName(string name) - { - var monoProcesses = Process.GetProcessesByName("mono") - .Where(process => process.Modules.Cast().Any(module => module.ModuleName.ToLower() == name.ToLower() + ".exe")); - return Process.GetProcessesByName(name) - .Union(monoProcesses).ToList(); - } - public ProcessInfo GetCurrentProcess() { return ConvertToProcessInfo(Process.GetCurrentProcess()); } - public bool Exists(string processName) + public Boolean Exists(string processName) { return GetProcessesByName(processName).Any(); } @@ -78,7 +70,7 @@ namespace NzbDrone.Common.Processes public List FindProcessByName(string name) { - return Process.GetProcessesByName(name).Select(ConvertToProcessInfo).Where(c => c != null).ToList(); + return GetProcessesByName(name).Select(ConvertToProcessInfo).Where(c => c != null).ToList(); } public void OpenDefaultBrowser(string url) @@ -203,12 +195,40 @@ namespace NzbDrone.Common.Processes process.PriorityClass = priority; } + public void Kill(int processId) + { + var process = Process.GetProcesses().FirstOrDefault(p => p.Id == processId); + + if (process == null) + { + Logger.Warn("Cannot find process with id: {0}", processId); + return; + } + + process.Refresh(); + + if (process.HasExited) + { + Logger.Debug("Process has already exited"); + return; + } + + Logger.Info("[{0}]: Killing process", process.Id); + process.Kill(); + Logger.Info("[{0}]: Waiting for exit", process.Id); + process.WaitForExit(); + Logger.Info("[{0}]: Process terminated successfully", process.Id); + } + public void KillAll(string processName) { - var processToKill = GetProcessesByName(processName); + var processes = GetProcessesByName(processName); - foreach (var processInfo in processToKill) + Logger.Debug("Found {0} processes to kill", processes.Count); + + foreach (var processInfo in processes) { + Logger.Debug("Killing process: {0} [{1}]", processInfo.Id, processInfo.ProcessName); Kill(processInfo.Id); } } @@ -254,29 +274,23 @@ namespace NzbDrone.Common.Processes return process.Modules.Cast().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName; } - public void Kill(int processId) + private static List GetProcessesByName(string name) { - var process = Process.GetProcesses().FirstOrDefault(p => p.Id == processId); + //TODO: move this to an OS specific class - if (process == null) - { - Logger.Warn("Cannot find process with id: {0}", processId); - return; - } + var monoProcesses = Process.GetProcessesByName("mono") + .Union(Process.GetProcessesByName("mono-sgen")) + .Where(process => + process.Modules.Cast() + .Any(module => + module.ModuleName.ToLower() == name.ToLower() + ".exe")); - process.Refresh(); + var processes = Process.GetProcessesByName(name) + .Union(monoProcesses).ToList(); - if (process.HasExited) - { - Logger.Debug("Process has already exited"); - return; - } + Logger.Debug("Found {0} processes with the name: {1}", processes.Count, name); - Logger.Info("[{0}]: Killing process", process.Id); - process.Kill(); - Logger.Info("[{0}]: Waiting for exit", process.Id); - process.WaitForExit(); - Logger.Info("[{0}]: Process terminated successfully", process.Id); + return processes; } } } diff --git a/src/NzbDrone.Common/Properties/AssemblyInfo.cs b/src/NzbDrone.Common/Properties/AssemblyInfo.cs index e9f498cc8..71f440bc5 100644 --- a/src/NzbDrone.Common/Properties/AssemblyInfo.cs +++ b/src/NzbDrone.Common/Properties/AssemblyInfo.cs @@ -12,5 +12,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("10.0.0.*")] +[assembly: AssemblyVersion("2.0.0.1")] [assembly: AssemblyFileVersion("10.0.0.*")] diff --git a/src/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs b/src/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs index 8e09b8024..604d357ba 100644 --- a/src/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs +++ b/src/NzbDrone.Common/Security/IgnoreCertErrorPolicy.cs @@ -13,6 +13,15 @@ namespace NzbDrone.Common.Security private static bool ValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyerrors) { + var request = sender as HttpWebRequest; + + if (request != null && + request.Address.OriginalString.ContainsIgnoreCase("nzbdrone.com") && + sslpolicyerrors != SslPolicyErrors.None) + { + return false; + } + return true; } } diff --git a/src/NzbDrone.Common/StringExtensions.cs b/src/NzbDrone.Common/StringExtensions.cs index 21b1db970..77a6c9532 100644 --- a/src/NzbDrone.Common/StringExtensions.cs +++ b/src/NzbDrone.Common/StringExtensions.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using ICSharpCode.SharpZipLib.Zip; namespace NzbDrone.Common { @@ -65,5 +66,20 @@ namespace NzbDrone.Common { return String.IsNullOrWhiteSpace(text); } + + public static bool ContainsIgnoreCase(this string text, string contains) + { + return text.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) > -1; + } + + public static string WrapInQuotes(this string text) + { + if (!text.Contains(" ")) + { + return text; + } + + return "\"" + text + "\""; + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs index 85e5c872a..e01a08761 100644 --- a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs +++ b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Http; using NzbDrone.Common.Model; using NzbDrone.Common.Processes; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Update; using NzbDrone.Core.Update.Commands; @@ -49,12 +50,28 @@ namespace NzbDrone.Core.Test.UpdateTests Mocker.GetMock().SetupGet(c => c.TempFolder).Returns(TempFolder); Mocker.GetMock().Setup(c => c.AvailableUpdate()).Returns(_updatePackage); + Mocker.GetMock().Setup(c => c.Verify(It.IsAny(), It.IsAny())).Returns(true); Mocker.GetMock().Setup(c => c.GetCurrentProcess()).Returns(new ProcessInfo { Id = 12 }); + Mocker.GetMock().Setup(c => c.ExecutingApplication).Returns(@"C:\Test\NzbDrone.exe"); _sandboxFolder = Mocker.GetMock().Object.GetUpdateSandboxFolder(); } + private void GivenInstallScript(string path) + { + Mocker.GetMock() + .SetupGet(s => s.UpdateMechanism) + .Returns(UpdateMechanism.Script); + + Mocker.GetMock() + .SetupGet(s => s.UpdateScriptPath) + .Returns(path); + + Mocker.GetMock() + .Setup(s => s.FileExists(path, true)) + .Returns(true); + } [Test] public void should_delete_sandbox_before_update_if_folder_exists() @@ -77,7 +94,6 @@ namespace NzbDrone.Core.Test.UpdateTests Mocker.GetMock().Verify(c => c.DeleteFolder(_sandboxFolder, true), Times.Never()); } - [Test] public void Should_download_update_package() { @@ -118,11 +134,11 @@ namespace NzbDrone.Core.Test.UpdateTests Subject.Execute(new ApplicationUpdateCommand()); Mocker.GetMock() - .Verify(c => c.Start(It.IsAny(), "12", null, null), Times.Once()); + .Verify(c => c.Start(It.IsAny(), It.Is(s => s.StartsWith("12")), null, null), Times.Once()); } [Test] - public void when_no_updates_are_available_should_return_without_error_or_warnings() + public void should_return_without_error_or_warnings_when_no_updates_are_available() { Mocker.GetMock().Setup(c => c.AvailableUpdate()).Returns(null); @@ -132,6 +148,75 @@ namespace NzbDrone.Core.Test.UpdateTests ExceptionVerification.AssertNoUnexpectedLogs(); } + [Test] + public void should_not_extract_if_verification_fails() + { + Mocker.GetMock().Setup(c => c.Verify(It.IsAny(), It.IsAny())).Returns(false); + + Subject.Execute(new ApplicationUpdateCommand()); + + Mocker.GetMock().Verify(v => v.Extract(It.IsAny(), It.IsAny()), Times.Never()); + } + + [Test] + [Platform("Mono")] + public void should_run_script_if_configured() + { + const string scriptPath = "/tmp/nzbdrone/update.sh"; + + GivenInstallScript(scriptPath); + + Subject.Execute(new ApplicationUpdateCommand()); + + Mocker.GetMock().Verify(v => v.Start(scriptPath, It.IsAny(), null, null), Times.Once()); + } + + [Test] + [Platform("Mono")] + public void should_throw_if_script_is_not_set() + { + const string scriptPath = "/tmp/nzbdrone/update.sh"; + + GivenInstallScript(""); + + Subject.Execute(new ApplicationUpdateCommand()); + + ExceptionVerification.ExpectedErrors(1); + Mocker.GetMock().Verify(v => v.Start(scriptPath, It.IsAny(), null, null), Times.Never()); + } + + [Test] + [Platform("Mono")] + public void should_throw_if_script_is_null() + { + const string scriptPath = "/tmp/nzbdrone/update.sh"; + + GivenInstallScript(null); + + Subject.Execute(new ApplicationUpdateCommand()); + + ExceptionVerification.ExpectedErrors(1); + Mocker.GetMock().Verify(v => v.Start(scriptPath, It.IsAny(), null, null), Times.Never()); + } + + [Test] + [Platform("Mono")] + public void should_throw_if_script_path_does_not_exist() + { + const string scriptPath = "/tmp/nzbdrone/update.sh"; + + GivenInstallScript(scriptPath); + + Mocker.GetMock() + .Setup(s => s.FileExists(scriptPath, true)) + .Returns(false); + + Subject.Execute(new ApplicationUpdateCommand()); + + ExceptionVerification.ExpectedErrors(1); + Mocker.GetMock().Verify(v => v.Start(scriptPath, It.IsAny(), null, null), Times.Never()); + } + [Test] [IntegrationTest] public void Should_download_and_extract_to_temp_folder() diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index ef9c5e4dd..d0954c221 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -11,6 +11,7 @@ using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Update; namespace NzbDrone.Core.Configuration @@ -30,11 +31,13 @@ namespace NzbDrone.Core.Configuration string Password { get; } string LogLevel { get; } string Branch { get; } - bool AutoUpdate { get; } string ApiKey { get; } bool Torrent { get; } string SslCertHash { get; } string UrlBase { get; } + Boolean UpdateAutomatically { get; } + UpdateMechanism UpdateMechanism { get; } + String UpdateScriptPath { get; } } public class ConfigFileProvider : IConfigFileProvider @@ -141,11 +144,6 @@ namespace NzbDrone.Core.Configuration get { return GetValue("Branch", "master").ToLowerInvariant(); } } - public bool AutoUpdate - { - get { return GetValueBoolean("AutoUpdate", false, persist: false); } - } - public string Username { get { return GetValue("Username", ""); } @@ -181,6 +179,21 @@ namespace NzbDrone.Core.Configuration } } + public bool UpdateAutomatically + { + get { return GetValueBoolean("UpdateAutomatically", false, false); } + } + + public UpdateMechanism UpdateMechanism + { + get { return GetValueEnum("UpdateMechanism", UpdateMechanism.BuiltIn, false); } + } + + public string UpdateScriptPath + { + get { return GetValue("UpdateScriptPath", "", false ); } + } + public int GetValueInt(string key, int defaultValue) { return Convert.ToInt32(GetValue(key, defaultValue)); @@ -191,9 +204,9 @@ namespace NzbDrone.Core.Configuration return Convert.ToBoolean(GetValue(key, defaultValue, persist)); } - public T GetValueEnum(string key, T defaultValue) + public T GetValueEnum(string key, T defaultValue, bool persist = true) { - return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), true); + return (T)Enum.Parse(typeof(T), GetValue(key, defaultValue), persist); } public string GetValue(string key, object defaultValue, bool persist = true) @@ -210,7 +223,9 @@ namespace NzbDrone.Core.Configuration var valueHolder = parentContainer.Descendants(key).ToList(); if (valueHolder.Count() == 1) + { return valueHolder.First().Value.Trim(); + } //Save the value if (persist) diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 3fc54f350..ce3252221 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -6,6 +6,7 @@ using NzbDrone.Common.EnsureThat; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Update; namespace NzbDrone.Core.Configuration diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index a3cbef057..10a1843a5 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Update; namespace NzbDrone.Core.Configuration { @@ -23,7 +24,6 @@ namespace NzbDrone.Core.Configuration Int32 BlacklistRetryInterval { get; set; } Int32 BlacklistRetryLimit { get; set; } - //Media Management Boolean AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } String RecycleBin { get; set; } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 06a54557a..c1e0810b2 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -693,10 +693,13 @@ + + + diff --git a/src/NzbDrone.Core/Security.cs b/src/NzbDrone.Core/Security.cs index abc950809..840466e98 100644 --- a/src/NzbDrone.Core/Security.cs +++ b/src/NzbDrone.Core/Security.cs @@ -1,4 +1,5 @@ -using System.Security.Cryptography; +using System.IO; +using System.Security.Cryptography; using System.Text; namespace NzbDrone.Core @@ -7,17 +8,28 @@ namespace NzbDrone.Core { public static string SHA256Hash(this string input) { - var stringBuilder = new StringBuilder(); - using (var hash = SHA256Managed.Create()) { var enc = Encoding.UTF8; - var result = hash.ComputeHash(enc.GetBytes(input)); + return GetHash(hash.ComputeHash(enc.GetBytes(input))); + } + } - foreach (var b in result) - { - stringBuilder.Append(b.ToString("x2")); - } + public static string SHA256Hash(this Stream input) + { + using (var hash = SHA256Managed.Create()) + { + return GetHash(hash.ComputeHash(input)); + } + } + + private static string GetHash(byte[] bytes) + { + var stringBuilder = new StringBuilder(); + + foreach (var b in bytes) + { + stringBuilder.Append(b.ToString("x2")); } return stringBuilder.ToString(); diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs index 01203d007..404ac8f12 100644 --- a/src/NzbDrone.Core/Update/InstallUpdateService.cs +++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs @@ -6,6 +6,7 @@ using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Http; using NzbDrone.Common.Processes; +using NzbDrone.Core.Configuration; using NzbDrone.Core.Instrumentation.Extensions; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Update.Commands; @@ -27,18 +28,31 @@ namespace NzbDrone.Core.Update private readonly IHttpProvider _httpProvider; private readonly IArchiveService _archiveService; private readonly IProcessProvider _processProvider; + private readonly IVerifyUpdates _updateVerifier; + private readonly IConfigFileProvider _configFileProvider; + private readonly IRuntimeInfo _runtimeInfo; public InstallUpdateService(ICheckUpdateService checkUpdateService, IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IHttpProvider httpProvider, - IArchiveService archiveService, IProcessProvider processProvider, Logger logger) + IArchiveService archiveService, IProcessProvider processProvider, + IVerifyUpdates updateVerifier, + IConfigFileProvider configFileProvider, + IRuntimeInfo runtimeInfo, Logger logger) { + if (configFileProvider == null) + { + throw new ArgumentNullException("configFileProvider"); + } _checkUpdateService = checkUpdateService; _appFolderInfo = appFolderInfo; _diskProvider = diskProvider; _httpProvider = httpProvider; _archiveService = archiveService; _processProvider = processProvider; + _updateVerifier = updateVerifier; + _configFileProvider = configFileProvider; + _runtimeInfo = runtimeInfo; _logger = logger; } @@ -60,19 +74,34 @@ namespace NzbDrone.Core.Update _logger.Debug("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination); _httpProvider.DownloadFile(updatePackage.Url, packageDestination); + _logger.ProgressInfo("Verifying update package"); + + if (!_updateVerifier.Verify(updatePackage, packageDestination)) + { + _logger.Error("Update package is invalid"); + throw new UpdateVerificationFailedException("Update file '{0}' is invalid", packageDestination); + } + + _logger.Info("Update package verified successfully"); + _logger.ProgressInfo("Extracting Update package"); _archiveService.Extract(packageDestination, updateSandboxFolder); _logger.Info("Update package extracted successfully"); + if (OsInfo.IsMono && _configFileProvider.UpdateMechanism == UpdateMechanism.Script) + { + InstallUpdateWithScript(updateSandboxFolder); + return; + } + _logger.Info("Preparing client"); _diskProvider.MoveFolder(_appFolderInfo.GetUpdateClientFolder(), updateSandboxFolder); _logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath()); - _logger.ProgressInfo("NzbDrone will restart shortly."); - _processProvider.Start(_appFolderInfo.GetUpdateClientExePath(), _processProvider.GetCurrentProcess().Id.ToString()); + _processProvider.Start(_appFolderInfo.GetUpdateClientExePath(), GetUpdaterArgs(updateSandboxFolder)); } catch (Exception ex) { @@ -80,6 +109,36 @@ namespace NzbDrone.Core.Update } } + private void InstallUpdateWithScript(String updateSandboxFolder) + { + var scriptPath = _configFileProvider.UpdateScriptPath; + + if (scriptPath.IsNullOrWhiteSpace()) + { + throw new ArgumentException("Update Script has not been defined"); + } + + if (!_diskProvider.FileExists(scriptPath, true)) + { + var message = String.Format("Update Script: '{0}' does not exist", scriptPath); + throw new FileNotFoundException(message, scriptPath); + } + + _logger.Info("Removing NzbDrone.Update"); + _diskProvider.DeleteFolder(_appFolderInfo.GetUpdateClientFolder(), true); + + _logger.ProgressInfo("Starting update script: {0}", _configFileProvider.UpdateScriptPath); + _processProvider.Start(scriptPath, GetUpdaterArgs(updateSandboxFolder.WrapInQuotes())); + } + + private string GetUpdaterArgs(string updateSandboxFolder) + { + var processId = _processProvider.GetCurrentProcess().Id.ToString(); + var executingApplication = _runtimeInfo.ExecutingApplication; + + return String.Join(" ", processId, updateSandboxFolder.WrapInQuotes(), executingApplication.WrapInQuotes()); + } + public void Execute(ApplicationUpdateCommand message) { _logger.ProgressDebug("Checking for updates"); diff --git a/src/NzbDrone.Core/Update/UpdateCheckService.cs b/src/NzbDrone.Core/Update/UpdateCheckService.cs index 57cd97905..b7e787895 100644 --- a/src/NzbDrone.Core/Update/UpdateCheckService.cs +++ b/src/NzbDrone.Core/Update/UpdateCheckService.cs @@ -18,7 +18,9 @@ namespace NzbDrone.Core.Update private readonly Logger _logger; - public CheckUpdateService(IUpdatePackageProvider updatePackageProvider, IConfigFileProvider configFileProvider, Logger logger) + public CheckUpdateService(IUpdatePackageProvider updatePackageProvider, + IConfigFileProvider configFileProvider, + Logger logger) { _updatePackageProvider = updatePackageProvider; _configFileProvider = configFileProvider; @@ -27,7 +29,10 @@ namespace NzbDrone.Core.Update public UpdatePackage AvailableUpdate() { - if (OsInfo.IsMono) return null; + if (OsInfo.IsMono && !_configFileProvider.UpdateAutomatically) + { + return null; + } var latestAvailable = _updatePackageProvider.GetLatestUpdate(_configFileProvider.Branch, BuildInfo.Version); diff --git a/src/NzbDrone.Core/Update/UpdateMechanism.cs b/src/NzbDrone.Core/Update/UpdateMechanism.cs new file mode 100644 index 000000000..8b647a1e7 --- /dev/null +++ b/src/NzbDrone.Core/Update/UpdateMechanism.cs @@ -0,0 +1,8 @@ +namespace NzbDrone.Core.Update +{ + public enum UpdateMechanism + { + BuiltIn = 0, + Script = 1 + } +} diff --git a/src/NzbDrone.Core/Update/UpdatePackage.cs b/src/NzbDrone.Core/Update/UpdatePackage.cs index a7ed63b5a..94ffa1fd0 100644 --- a/src/NzbDrone.Core/Update/UpdatePackage.cs +++ b/src/NzbDrone.Core/Update/UpdatePackage.cs @@ -11,5 +11,6 @@ namespace NzbDrone.Core.Update public String FileName { get; set; } public String Url { get; set; } public UpdateChanges Changes { get; set; } + public String Hash { get; set; } } } diff --git a/src/NzbDrone.Core/Update/UpdateVerification.cs b/src/NzbDrone.Core/Update/UpdateVerification.cs new file mode 100644 index 000000000..bafbb4e5d --- /dev/null +++ b/src/NzbDrone.Core/Update/UpdateVerification.cs @@ -0,0 +1,30 @@ +using System; +using NzbDrone.Common.Disk; + +namespace NzbDrone.Core.Update +{ + public interface IVerifyUpdates + { + Boolean Verify(UpdatePackage updatePackage, String packagePath); + } + + public class UpdateVerification : IVerifyUpdates + { + private readonly IDiskProvider _diskProvider; + + public UpdateVerification(IDiskProvider diskProvider) + { + _diskProvider = diskProvider; + } + + public Boolean Verify(UpdatePackage updatePackage, String packagePath) + { + using (var fileStream = _diskProvider.StreamFile(packagePath)) + { + var hash = fileStream.SHA256Hash(); + + return hash.Equals(updatePackage.Hash, StringComparison.CurrentCultureIgnoreCase); + } + } + } +} diff --git a/src/NzbDrone.Core/Update/UpdateVerificationFailedException.cs b/src/NzbDrone.Core/Update/UpdateVerificationFailedException.cs new file mode 100644 index 000000000..56d9da122 --- /dev/null +++ b/src/NzbDrone.Core/Update/UpdateVerificationFailedException.cs @@ -0,0 +1,15 @@ +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Update +{ + public class UpdateVerificationFailedException : NzbDroneException + { + public UpdateVerificationFailedException(string message, params object[] args) : base(message, args) + { + } + + public UpdateVerificationFailedException(string message) : base(message) + { + } + } +} diff --git a/src/NzbDrone.Host/SingleInstancePolicy.cs b/src/NzbDrone.Host/SingleInstancePolicy.cs index 2952997cb..a1b489705 100644 --- a/src/NzbDrone.Host/SingleInstancePolicy.cs +++ b/src/NzbDrone.Host/SingleInstancePolicy.cs @@ -16,17 +16,14 @@ namespace NzbDrone.Host { private readonly IProcessProvider _processProvider; private readonly IBrowserService _browserService; - private readonly INzbDroneProcessProvider _nzbDroneProcessProvider; private readonly Logger _logger; public SingleInstancePolicy(IProcessProvider processProvider, IBrowserService browserService, - INzbDroneProcessProvider nzbDroneProcessProvider, Logger logger) { _processProvider = processProvider; _browserService = browserService; - _nzbDroneProcessProvider = nzbDroneProcessProvider; _logger = logger; } @@ -56,10 +53,11 @@ namespace NzbDrone.Host private List GetOtherNzbDroneProcessIds() { var currentId = _processProvider.GetCurrentProcess().Id; - var otherProcesses = _nzbDroneProcessProvider.FindNzbDroneProcesses() - .Select(c => c.Id) - .Except(new[] {currentId}) - .ToList(); + var otherProcesses = _processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME) + .Union(_processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME)) + .Select(c => c.Id) + .Except(new[] {currentId}) + .ToList(); if (otherProcesses.Any()) { diff --git a/src/NzbDrone.Mono/NzbDrone.Mono.csproj b/src/NzbDrone.Mono/NzbDrone.Mono.csproj index e342f9a16..91d0efeb3 100644 --- a/src/NzbDrone.Mono/NzbDrone.Mono.csproj +++ b/src/NzbDrone.Mono/NzbDrone.Mono.csproj @@ -70,7 +70,6 @@ - diff --git a/src/NzbDrone.Mono/NzbDroneProcessProvider.cs b/src/NzbDrone.Mono/NzbDroneProcessProvider.cs deleted file mode 100644 index 1804c077d..000000000 --- a/src/NzbDrone.Mono/NzbDroneProcessProvider.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NLog; -using NzbDrone.Common.Model; -using NzbDrone.Common.Processes; - -namespace NzbDrone.Mono -{ - public class NzbDroneProcessProvider : INzbDroneProcessProvider - { - private readonly IProcessProvider _processProvider; - private readonly Logger _logger; - - public NzbDroneProcessProvider(IProcessProvider processProvider, Logger logger) - { - _processProvider = processProvider; - _logger = logger; - } - - public List FindNzbDroneProcesses() - { - var monoProcesses = _processProvider.FindProcessByName("mono"); - - return monoProcesses.Where(c => - { - try - { - var processArgs = _processProvider.StartAndCapture("ps", String.Format("-p {0} -o args=", c.Id)); - - return processArgs.Standard.Any(p => p.Contains(ProcessProvider.NZB_DRONE_PROCESS_NAME + ".exe") || - p.Contains(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME + ".exe")); - } - catch (InvalidOperationException ex) - { - _logger.WarnException("Error getting process arguments", ex); - return false; - } - - }).ToList(); - } - } -} diff --git a/src/NzbDrone.Test.Common/LoggingTest.cs b/src/NzbDrone.Test.Common/LoggingTest.cs index f7ad739fb..e706d3d04 100644 --- a/src/NzbDrone.Test.Common/LoggingTest.cs +++ b/src/NzbDrone.Test.Common/LoggingTest.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Test.Common LogManager.Configuration = new LoggingConfiguration(); var consoleTarget = new ConsoleTarget { Layout = "${level}: ${message} ${exception}" }; LogManager.Configuration.AddTarget(consoleTarget.GetType().Name, consoleTarget); - LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, consoleTarget)); + LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); RegisterExceptionVerification(); } diff --git a/src/NzbDrone.Test.Dummy/DummyApp.cs b/src/NzbDrone.Test.Dummy/DummyApp.cs index 87b04f712..5176dab6f 100644 --- a/src/NzbDrone.Test.Dummy/DummyApp.cs +++ b/src/NzbDrone.Test.Dummy/DummyApp.cs @@ -9,7 +9,9 @@ namespace NzbDrone.Test.Dummy static void Main(string[] args) { - Console.WriteLine("Dummy process. ID:{0} Path:{1}", Process.GetCurrentProcess().Id, Process.GetCurrentProcess().MainModule.FileName); + var process = Process.GetCurrentProcess(); + + Console.WriteLine("Dummy process. ID:{0} Name:{1} Path:{2}", process.Id, process.ProcessName, process.MainModule.FileName); Console.ReadLine(); } } diff --git a/src/NzbDrone.Update/NzbDrone.Update.csproj b/src/NzbDrone.Update/NzbDrone.Update.csproj index 0e85877ba..399a39203 100644 --- a/src/NzbDrone.Update/NzbDrone.Update.csproj +++ b/src/NzbDrone.Update/NzbDrone.Update.csproj @@ -51,6 +51,7 @@ Properties\SharedAssemblyInfo.cs + diff --git a/src/NzbDrone.Update/UpdateApp.cs b/src/NzbDrone.Update/UpdateApp.cs index 35d7febca..46de9307e 100644 --- a/src/NzbDrone.Update/UpdateApp.cs +++ b/src/NzbDrone.Update/UpdateApp.cs @@ -1,6 +1,8 @@ using System; using System.IO; +using System.Linq; using NLog; +using NzbDrone.Common; using NzbDrone.Common.Composition; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Instrumentation; @@ -50,24 +52,60 @@ namespace NzbDrone.Update public void Start(string[] args) { - var processId = ParseProcessId(args); + var startupContext = ParseArgs(args); + string targetFolder; - var exeFileInfo = new FileInfo(_processProvider.GetProcessById(processId).StartPath); - var targetFolder = exeFileInfo.Directory.FullName; + if (startupContext.ExecutingApplication.IsNullOrWhiteSpace()) + { + var exeFileInfo = new FileInfo(_processProvider.GetProcessById(startupContext.ProcessId).StartPath); + targetFolder = exeFileInfo.Directory.FullName; + } + else + { + var exeFileInfo = new FileInfo(startupContext.ExecutingApplication); + targetFolder = exeFileInfo.Directory.FullName; + } + logger.Info("Starting update process. Target Path:{0}", targetFolder); _installUpdateService.Start(targetFolder); } - private int ParseProcessId(string[] args) + private UpdateStartupContext ParseArgs(string[] args) { - int id; - if (args == null || !Int32.TryParse(args[0], out id) || id <= 0) + if (args == null || !args.Any()) { - throw new ArgumentOutOfRangeException("args", "Invalid process ID"); + throw new ArgumentOutOfRangeException("args", "args must be specified"); } - logger.Debug("NzbDrone processId:{0}", id); + var startupContext = new UpdateStartupContext + { + ProcessId = ParseProcessId(args[0]) + }; + + if (args.Count() == 1) + { + return startupContext; + } + + if (args.Count() >= 3) + { + startupContext.UpdateLocation = args[1]; + startupContext.ExecutingApplication = args[2]; + } + + return startupContext; + } + + private int ParseProcessId(string arg) + { + int id; + if (!Int32.TryParse(arg, out id) || id <= 0) + { + throw new ArgumentOutOfRangeException("arg", "Invalid process ID"); + } + + logger.Debug("NzbDrone process ID: {0}", id); return id; } } diff --git a/src/NzbDrone.Update/UpdateEngine/DetectApplicationType.cs b/src/NzbDrone.Update/UpdateEngine/DetectApplicationType.cs index ec9a80a96..aeed37c45 100644 --- a/src/NzbDrone.Update/UpdateEngine/DetectApplicationType.cs +++ b/src/NzbDrone.Update/UpdateEngine/DetectApplicationType.cs @@ -1,4 +1,5 @@ using NzbDrone.Common; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Processes; namespace NzbDrone.Update.UpdateEngine @@ -21,6 +22,12 @@ namespace NzbDrone.Update.UpdateEngine public AppType GetAppType() { + if (OsInfo.IsMono) + { + //Tehcnically its the console, but its been renamed for mono (Linux/OS X) + return AppType.Normal; + } + if (_serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME) && _serviceProvider.IsServiceRunning(ServiceProvider.NZBDRONE_SERVICE_NAME)) { diff --git a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs index f1b731e34..e70027a3a 100644 --- a/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs +++ b/src/NzbDrone.Update/UpdateEngine/InstallUpdateService.cs @@ -82,7 +82,6 @@ namespace NzbDrone.Update.UpdateEngine _backupAndRestore.Restore(installationFolder); _logger.FatalException("Failed to copy upgrade package to target folder.", e); } - } finally { diff --git a/src/NzbDrone.Update/UpdateEngine/TerminateNzbDrone.cs b/src/NzbDrone.Update/UpdateEngine/TerminateNzbDrone.cs index 329c9555e..4c8934f03 100644 --- a/src/NzbDrone.Update/UpdateEngine/TerminateNzbDrone.cs +++ b/src/NzbDrone.Update/UpdateEngine/TerminateNzbDrone.cs @@ -1,6 +1,7 @@ using System; using NLog; using NzbDrone.Common; +using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Processes; using IServiceProvider = NzbDrone.Common.IServiceProvider; @@ -26,6 +27,15 @@ namespace NzbDrone.Update.UpdateEngine public void Terminate() { + if (OsInfo.IsMono) + { + _logger.Info("Stopping all instances"); + _processProvider.KillAll(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME); + _processProvider.KillAll(ProcessProvider.NZB_DRONE_PROCESS_NAME); + + return; + } + _logger.Info("Stopping all running services"); if (_serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME) @@ -35,7 +45,6 @@ namespace NzbDrone.Update.UpdateEngine { _logger.Info("NzbDrone Service is installed and running"); _serviceProvider.Stop(ServiceProvider.NZBDRONE_SERVICE_NAME); - } catch (Exception e) { diff --git a/src/NzbDrone.Update/UpdateStartupContext.cs b/src/NzbDrone.Update/UpdateStartupContext.cs new file mode 100644 index 000000000..51b6bf103 --- /dev/null +++ b/src/NzbDrone.Update/UpdateStartupContext.cs @@ -0,0 +1,11 @@ +using System; + +namespace NzbDrone.Update +{ + public class UpdateStartupContext + { + public Int32 ProcessId { get; set; } + public String ExecutingApplication { get; set; } + public String UpdateLocation { get; set; } + } +} diff --git a/src/NzbDrone.Windows/NzbDrone.Windows.csproj b/src/NzbDrone.Windows/NzbDrone.Windows.csproj index 77e47f03c..607f09a4e 100644 --- a/src/NzbDrone.Windows/NzbDrone.Windows.csproj +++ b/src/NzbDrone.Windows/NzbDrone.Windows.csproj @@ -63,7 +63,6 @@ - diff --git a/src/NzbDrone.Windows/NzbDroneProcessProvider.cs b/src/NzbDrone.Windows/NzbDroneProcessProvider.cs deleted file mode 100644 index 1e18b6c50..000000000 --- a/src/NzbDrone.Windows/NzbDroneProcessProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Common.Model; -using NzbDrone.Common.Processes; - -namespace NzbDrone.Windows -{ - public class NzbDroneProcessProvider : INzbDroneProcessProvider - { - private readonly IProcessProvider _processProvider; - - public NzbDroneProcessProvider(IProcessProvider processProvider) - { - _processProvider = processProvider; - } - - public List FindNzbDroneProcesses() - { - var consoleProcesses = _processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME); - var winformProcesses = _processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME); - - return consoleProcesses.Concat(winformProcesses).ToList(); - } - } -} diff --git a/src/UI/Settings/General/GeneralView.js b/src/UI/Settings/General/GeneralView.js index 2e63d2de5..b28ed659c 100644 --- a/src/UI/Settings/General/GeneralView.js +++ b/src/UI/Settings/General/GeneralView.js @@ -12,19 +12,22 @@ define( template: 'Settings/General/GeneralViewTemplate', events: { - 'change .x-auth' : '_setAuthOptionsVisibility', - 'change .x-ssl' : '_setSslOptionsVisibility', - 'click .x-reset-api-key' : '_resetApiKey' + 'change .x-auth' : '_setAuthOptionsVisibility', + 'change .x-ssl' : '_setSslOptionsVisibility', + 'click .x-reset-api-key' : '_resetApiKey', + 'change .x-update-mechanism' : '_setScriptGroupVisibility' }, ui: { - authToggle : '.x-auth', - authOptions : '.x-auth-options', - sslToggle : '.x-ssl', - sslOptions : '.x-ssl-options', - resetApiKey : '.x-reset-api-key', - copyApiKey : '.x-copy-api-key', - apiKeyInput : '.x-api-key' + authToggle : '.x-auth', + authOptions : '.x-auth-options', + sslToggle : '.x-ssl', + sslOptions : '.x-ssl-options', + resetApiKey : '.x-reset-api-key', + copyApiKey : '.x-copy-api-key', + apiKeyInput : '.x-api-key', + updateMechanism : '.x-update-mechanism', + scriptGroup : '.x-script-group' }, initialize: function () { @@ -40,6 +43,10 @@ define( this.ui.sslOptions.hide(); } + if (!this._showScriptGroup()) { + this.ui.scriptGroup.hide(); + } + CommandController.bindToCommand({ element: this.ui.resetApiKey, command: { @@ -79,7 +86,7 @@ define( }, _resetApiKey: function () { - if (window.confirm("Reset API Key?")) { + if (window.confirm('Reset API Key?')) { CommandController.Execute('resetApiKey', { name : 'resetApiKey' }); @@ -90,6 +97,21 @@ define( if (options.command.get('name') === 'resetapikey') { this.model.fetch(); } + }, + + _setScriptGroupVisibility: function () { + + if (this._showScriptGroup()) { + this.ui.scriptGroup.slideDown(); + } + + else { + this.ui.scriptGroup.slideUp(); + } + }, + + _showScriptGroup: function () { + return this.ui.updateMechanism.val() === 'script'; } }); diff --git a/src/UI/Settings/General/GeneralViewTemplate.html b/src/UI/Settings/General/GeneralViewTemplate.html index f0968e721..cfbca24bd 100644 --- a/src/UI/Settings/General/GeneralViewTemplate.html +++ b/src/UI/Settings/General/GeneralViewTemplate.html @@ -102,7 +102,7 @@
-
+
@@ -174,9 +174,8 @@
- {{#if_windows}}
- Development + Updating
@@ -186,28 +185,56 @@
- - - + {{#if_mono}} +
Please see: the wiki for more information
- - - +
+ - - - - +
+
+
+
- - - - - - +
+ + +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+ +
+
+ {{/if_mono}} - {{/if_windows}}
diff --git a/src/UI/System/Update/UpdateItemViewTemplate.html b/src/UI/System/Update/UpdateItemViewTemplate.html index c8bcc3238..95b56de51 100644 --- a/src/UI/System/Update/UpdateItemViewTemplate.html +++ b/src/UI/System/Update/UpdateItemViewTemplate.html @@ -5,17 +5,9 @@ - {{ShortDate releaseDate}} {{#if installed}}{{/if}} - {{#if_windows}} - {{#if isUpgrade}} - Install - {{/if}} - {{else}} - {{#if isUpgrade}} - - Install - - {{/if}} - {{/if_windows}} + {{#if isUpgrade}} + Install + {{/if}}