Update improvements

Include NzbDrone.Update in mono/osx package
Do not ignore certificate warnings for services
Check hash before extracting update
New: Update support for Linux/OS X - see the wiki for more information
pull/4/head
Mark McDowall 11 years ago
parent 5c2f77339d
commit ef3777fccf

@ -6,6 +6,7 @@ $testPackageFolder = '.\_tests\'
$testSearchPattern = '*.Test\bin\x86\Release' $testSearchPattern = '*.Test\bin\x86\Release'
$sourceFolder = '.\src' $sourceFolder = '.\src'
$updateFolder = $outputFolder + '\NzbDrone.Update' $updateFolder = $outputFolder + '\NzbDrone.Update'
$updateFolderMono = $outputFolderMono + '\NzbDrone.Update'
Function Build() Function Build()
{ {
@ -73,9 +74,6 @@ Function PackageMono()
Copy-Item $outputFolder $outputFolderMono -recurse Copy-Item $outputFolder $outputFolderMono -recurse
Write-Host Removing Update Client
Remove-Item -Recurse -Force "$outputFolderMono\NzbDrone.Update"
Write-Host Creating MDBs Write-Host Creating MDBs
get-childitem $outputFolderMono -File -Include @("*.exe", "*.dll") -Exclude @("MediaInfo.dll", "sqlite3.dll") -Recurse | foreach ($_) { get-childitem $outputFolderMono -File -Include @("*.exe", "*.dll") -Exclude @("MediaInfo.dll", "sqlite3.dll") -Recurse | foreach ($_) {
Write-Host "Creating .mdb for $_" Write-Host "Creating .mdb for $_"
@ -110,6 +108,9 @@ Function PackageMono()
Remove-Item "$outputFolderMono\NzbDrone.Console.vshost.exe" 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']" Write-Host "##teamcity[progressFinish 'Creating Mono Package']"
} }

@ -3,7 +3,9 @@ using System.Reflection;
using FluentValidation; using FluentValidation;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Update;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using Omu.ValueInjecter; using Omu.ValueInjecter;
namespace NzbDrone.Api.Config namespace NzbDrone.Api.Config
@ -12,7 +14,7 @@ namespace NzbDrone.Api.Config
{ {
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
public HostConfigModule(ConfigFileProvider configFileProvider) public HostConfigModule(IConfigFileProvider configFileProvider)
: base("/config/host") : base("/config/host")
{ {
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
@ -29,6 +31,8 @@ namespace NzbDrone.Api.Config
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl); 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.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script);
} }
private HostConfigResource GetHostConfig() private HostConfigResource GetHostConfig()

@ -1,5 +1,6 @@
using System; using System;
using NzbDrone.Api.REST; using NzbDrone.Api.REST;
using NzbDrone.Core.Update;
namespace NzbDrone.Api.Config namespace NzbDrone.Api.Config
{ {
@ -14,10 +15,12 @@ namespace NzbDrone.Api.Config
public String Password { get; set; } public String Password { get; set; }
public String LogLevel { get; set; } public String LogLevel { get; set; }
public String Branch { get; set; } public String Branch { get; set; }
public Boolean AutoUpdate { get; set; }
public String ApiKey { get; set; } public String ApiKey { get; set; }
public Boolean Torrent { get; set; } public Boolean Torrent { get; set; }
public String SslCertHash { get; set; } public String SslCertHash { get; set; }
public String UrlBase { get; set; } public String UrlBase { get; set; }
public Boolean UpdateAutomatically { get; set; }
public UpdateMechanism UpdateMechanism { get; set; }
public String UpdateScriptPath { get; set; }
} }
} }

@ -162,6 +162,7 @@
<Compile Include="Mapping\MappingValidation.cs" /> <Compile Include="Mapping\MappingValidation.cs" />
<Compile Include="Mapping\ResourceMappingException.cs" /> <Compile Include="Mapping\ResourceMappingException.cs" />
<Compile Include="Mapping\ValueInjectorExtensions.cs" /> <Compile Include="Mapping\ValueInjectorExtensions.cs" />
<Compile Include="Update\UpdateResource.cs" />
<Compile Include="Wanted\CutoffModule.cs" /> <Compile Include="Wanted\CutoffModule.cs" />
<Compile Include="Wanted\LegacyMissingModule.cs" /> <Compile Include="Wanted\LegacyMissingModule.cs" />
<Compile Include="Wanted\MissingModule.cs" /> <Compile Include="Wanted\MissingModule.cs" />

@ -1,8 +1,5 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Api.REST;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Update; using NzbDrone.Core.Update;
using NzbDrone.Api.Mapping; using NzbDrone.Api.Mapping;
@ -44,18 +41,4 @@ namespace NzbDrone.Api.Update
return resources; 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; }
}
} }

@ -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; }
}
}

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Model; using NzbDrone.Common.Model;
@ -18,17 +19,25 @@ namespace NzbDrone.App.Test
{ {
Mocker.GetMock<IProcessProvider>().Setup(c => c.GetCurrentProcess()) Mocker.GetMock<IProcessProvider>().Setup(c => c.GetCurrentProcess())
.Returns(new ProcessInfo() { Id = CURRENT_PROCESS_ID }); .Returns(new ProcessInfo() { Id = CURRENT_PROCESS_ID });
Mocker.GetMock<IProcessProvider>()
.Setup(s => s.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME))
.Returns(new List<ProcessInfo>());
Mocker.GetMock<IProcessProvider>()
.Setup(s => s.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME))
.Returns(new List<ProcessInfo>());
} }
[Test] [Test]
public void should_continue_if_only_instance() public void should_continue_if_only_instance()
{ {
Mocker.GetMock<INzbDroneProcessProvider>().Setup(c => c.FindNzbDroneProcesses()) Mocker.GetMock<IProcessProvider>()
.Returns(new List<ProcessInfo> .Setup(c => c.FindProcessByName(It.Is<String>(f => f.Contains("NzbDrone"))))
{ .Returns(new List<ProcessInfo>
new ProcessInfo{Id = CURRENT_PROCESS_ID} {
}); new ProcessInfo {Id = CURRENT_PROCESS_ID}
});
Subject.PreventStartIfAlreadyRunning(); Subject.PreventStartIfAlreadyRunning();
@ -38,13 +47,13 @@ namespace NzbDrone.App.Test
[Test] [Test]
public void should_enforce_if_another_console_is_running() public void should_enforce_if_another_console_is_running()
{ {
Mocker.GetMock<INzbDroneProcessProvider>() Mocker.GetMock<IProcessProvider>()
.Setup(c => c.FindNzbDroneProcesses()) .Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME))
.Returns(new List<ProcessInfo> .Returns(new List<ProcessInfo>
{ {
new ProcessInfo{Id = 10}, new ProcessInfo {Id = 10},
new ProcessInfo{Id = CURRENT_PROCESS_ID} new ProcessInfo {Id = CURRENT_PROCESS_ID}
}); });
Assert.Throws<TerminateApplicationException>(() => Subject.PreventStartIfAlreadyRunning()); Assert.Throws<TerminateApplicationException>(() => Subject.PreventStartIfAlreadyRunning());
Mocker.GetMock<IBrowserService>().Verify(c => c.LaunchWebUI(), Times.Once()); Mocker.GetMock<IBrowserService>().Verify(c => c.LaunchWebUI(), Times.Once());
@ -54,14 +63,14 @@ namespace NzbDrone.App.Test
[Test] [Test]
public void should_return_false_if_another_gui_is_running() public void should_return_false_if_another_gui_is_running()
{ {
Mocker.GetMock<INzbDroneProcessProvider>() Mocker.GetMock<IProcessProvider>()
.Setup(c => c.FindNzbDroneProcesses()) .Setup(c => c.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME))
.Returns(new List<ProcessInfo> .Returns(new List<ProcessInfo>
{ {
new ProcessInfo{Id = CURRENT_PROCESS_ID}, new ProcessInfo {Id = CURRENT_PROCESS_ID},
new ProcessInfo{Id = 10} new ProcessInfo {Id = 10}
}); });
Assert.Throws<TerminateApplicationException>(() => Subject.PreventStartIfAlreadyRunning()); Assert.Throws<TerminateApplicationException>(() => Subject.PreventStartIfAlreadyRunning());
Mocker.GetMock<IBrowserService>().Verify(c => c.LaunchWebUI(), Times.Once()); Mocker.GetMock<IBrowserService>().Verify(c => c.LaunchWebUI(), Times.Once());

@ -168,25 +168,25 @@ namespace NzbDrone.Common.Test
[Test] [Test]
public void Sanbox() public void Sanbox()
{ {
GetIAppDirectoryInfo().GetUpdateSandboxFolder().Should().BeEquivalentTo(@"C:\Temp\Nzbdrone_update\".AsOsAgnostic()); GetIAppDirectoryInfo().GetUpdateSandboxFolder().Should().BeEquivalentTo(@"C:\Temp\nzbdrone_update\".AsOsAgnostic());
} }
[Test] [Test]
public void GetUpdatePackageFolder() public void GetUpdatePackageFolder()
{ {
GetIAppDirectoryInfo().GetUpdatePackageFolder().Should().BeEquivalentTo(@"C:\Temp\Nzbdrone_update\NzbDrone\".AsOsAgnostic()); GetIAppDirectoryInfo().GetUpdatePackageFolder().Should().BeEquivalentTo(@"C:\Temp\nzbdrone_update\NzbDrone\".AsOsAgnostic());
} }
[Test] [Test]
public void GetUpdateClientFolder() 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] [Test]
public void GetUpdateClientExePath() 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] [Test]

@ -447,5 +447,15 @@ namespace NzbDrone.Common.Disk
return driveInfo.VolumeLabel; 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);
}
} }
} }

@ -11,7 +11,6 @@ namespace NzbDrone.Common.Disk
void InheritFolderPermissions(string filename); void InheritFolderPermissions(string filename);
void SetPermissions(string path, string mask, string user, string group); void SetPermissions(string path, string mask, string user, string group);
long? GetTotalSize(string path); long? GetTotalSize(string path);
DateTime FolderGetLastWrite(string path); DateTime FolderGetLastWrite(string path);
DateTime FileGetLastWrite(string path); DateTime FileGetLastWrite(string path);
DateTime FileGetLastWriteUtc(string path); DateTime FileGetLastWriteUtc(string path);
@ -44,5 +43,6 @@ namespace NzbDrone.Common.Disk
void EmptyFolder(string path); void EmptyFolder(string path);
string[] GetFixedDrives(); string[] GetFixedDrives();
string GetVolumeLabel(string path); string GetVolumeLabel(string path);
FileStream StreamFile(string path);
} }
} }

@ -1,4 +1,6 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Text;
namespace NzbDrone.Common.EnvironmentInfo namespace NzbDrone.Common.EnvironmentInfo
{ {

@ -105,7 +105,6 @@
<Compile Include="Messaging\IEvent.cs" /> <Compile Include="Messaging\IEvent.cs" />
<Compile Include="Messaging\IMessage.cs" /> <Compile Include="Messaging\IMessage.cs" />
<Compile Include="PathEqualityComparer.cs" /> <Compile Include="PathEqualityComparer.cs" />
<Compile Include="Processes\INzbDroneProcessProvider.cs" />
<Compile Include="Processes\PidFileProvider.cs" /> <Compile Include="Processes\PidFileProvider.cs" />
<Compile Include="Processes\ProcessOutput.cs" /> <Compile Include="Processes\ProcessOutput.cs" />
<Compile Include="RateGate.cs" /> <Compile Include="RateGate.cs" />

@ -13,10 +13,10 @@ namespace NzbDrone.Common
private const string NZBDRONE_LOG_DB = "logs.db"; private const string NZBDRONE_LOG_DB = "logs.db";
private const string BACKUP_ZIP_FILE = "NzbDrone_Backup.zip"; private const string BACKUP_ZIP_FILE = "NzbDrone_Backup.zip";
private const string NLOG_CONFIG_FILE = "nlog.config"; 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_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_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_BACKUP_APPDATA_FOLDER_NAME = "nzbdrone_appdata_backup" + Path.DirectorySeparatorChar;
private static readonly string UPDATE_CLIENT_FOLDER_NAME = "NzbDrone.Update" + Path.DirectorySeparatorChar; private static readonly string UPDATE_CLIENT_FOLDER_NAME = "NzbDrone.Update" + Path.DirectorySeparatorChar;

@ -1,10 +0,0 @@
using System.Collections.Generic;
using NzbDrone.Common.Model;
namespace NzbDrone.Common.Processes
{
public interface INzbDroneProcessProvider
{
List<ProcessInfo> FindNzbDroneProcesses();
}
}

@ -21,7 +21,7 @@ namespace NzbDrone.Common.Processes
void SetPriority(int processId, ProcessPriorityClass priority); void SetPriority(int processId, ProcessPriorityClass priority);
void KillAll(string processName); void KillAll(string processName);
void Kill(int processId); void Kill(int processId);
bool Exists(string processName); Boolean Exists(string processName);
ProcessPriorityClass GetCurrentProcessPriority(); ProcessPriorityClass GetCurrentProcessPriority();
Process Start(string path, string args = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null); Process Start(string path, string args = null, Action<string> onOutputDataReceived = null, Action<string> onErrorDataReceived = null);
Process SpawnNewProcess(string path, string args = 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_PROCESS_NAME = "NzbDrone";
public const string NZB_DRONE_CONSOLE_PROCESS_NAME = "NzbDrone.Console"; public const string NZB_DRONE_CONSOLE_PROCESS_NAME = "NzbDrone.Console";
private static List<Process> GetProcessesByName(string name)
{
var monoProcesses = Process.GetProcessesByName("mono")
.Where(process => process.Modules.Cast<ProcessModule>().Any(module => module.ModuleName.ToLower() == name.ToLower() + ".exe"));
return Process.GetProcessesByName(name)
.Union(monoProcesses).ToList();
}
public ProcessInfo GetCurrentProcess() public ProcessInfo GetCurrentProcess()
{ {
return ConvertToProcessInfo(Process.GetCurrentProcess()); return ConvertToProcessInfo(Process.GetCurrentProcess());
} }
public bool Exists(string processName) public Boolean Exists(string processName)
{ {
return GetProcessesByName(processName).Any(); return GetProcessesByName(processName).Any();
} }
@ -78,7 +70,7 @@ namespace NzbDrone.Common.Processes
public List<ProcessInfo> FindProcessByName(string name) public List<ProcessInfo> 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) public void OpenDefaultBrowser(string url)
@ -203,12 +195,40 @@ namespace NzbDrone.Common.Processes
process.PriorityClass = priority; 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) 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); Kill(processInfo.Id);
} }
} }
@ -254,29 +274,23 @@ namespace NzbDrone.Common.Processes
return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName; return process.Modules.Cast<ProcessModule>().FirstOrDefault(module => module.ModuleName.ToLower().EndsWith(".exe")).FileName;
} }
public void Kill(int processId) private static List<Process> GetProcessesByName(string name)
{ {
var process = Process.GetProcesses().FirstOrDefault(p => p.Id == processId); //TODO: move this to an OS specific class
if (process == null) var monoProcesses = Process.GetProcessesByName("mono")
{ .Union(Process.GetProcessesByName("mono-sgen"))
Logger.Warn("Cannot find process with id: {0}", processId); .Where(process =>
return; process.Modules.Cast<ProcessModule>()
} .Any(module =>
module.ModuleName.ToLower() == name.ToLower() + ".exe"));
process.Refresh(); var processes = Process.GetProcessesByName(name)
.Union(monoProcesses).ToList();
if (process.HasExited) Logger.Debug("Found {0} processes with the name: {1}", processes.Count, name);
{
Logger.Debug("Process has already exited");
return;
}
Logger.Info("[{0}]: Killing process", process.Id); return processes;
process.Kill();
Logger.Info("[{0}]: Waiting for exit", process.Id);
process.WaitForExit();
Logger.Info("[{0}]: Process terminated successfully", process.Id);
} }
} }
} }

@ -12,5 +12,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("10.0.0.*")] [assembly: AssemblyVersion("2.0.0.1")]
[assembly: AssemblyFileVersion("10.0.0.*")] [assembly: AssemblyFileVersion("10.0.0.*")]

@ -13,6 +13,15 @@ namespace NzbDrone.Common.Security
private static bool ValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyerrors) 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; return true;
} }
} }

@ -3,6 +3,7 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ICSharpCode.SharpZipLib.Zip;
namespace NzbDrone.Common namespace NzbDrone.Common
{ {
@ -65,5 +66,20 @@ namespace NzbDrone.Common
{ {
return String.IsNullOrWhiteSpace(text); 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 + "\"";
}
} }
} }

@ -9,6 +9,7 @@ using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Model; using NzbDrone.Common.Model;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Update; using NzbDrone.Core.Update;
using NzbDrone.Core.Update.Commands; using NzbDrone.Core.Update.Commands;
@ -49,12 +50,28 @@ namespace NzbDrone.Core.Test.UpdateTests
Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.TempFolder).Returns(TempFolder); Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.TempFolder).Returns(TempFolder);
Mocker.GetMock<ICheckUpdateService>().Setup(c => c.AvailableUpdate()).Returns(_updatePackage); Mocker.GetMock<ICheckUpdateService>().Setup(c => c.AvailableUpdate()).Returns(_updatePackage);
Mocker.GetMock<IVerifyUpdates>().Setup(c => c.Verify(It.IsAny<UpdatePackage>(), It.IsAny<String>())).Returns(true);
Mocker.GetMock<IProcessProvider>().Setup(c => c.GetCurrentProcess()).Returns(new ProcessInfo { Id = 12 }); Mocker.GetMock<IProcessProvider>().Setup(c => c.GetCurrentProcess()).Returns(new ProcessInfo { Id = 12 });
Mocker.GetMock<IRuntimeInfo>().Setup(c => c.ExecutingApplication).Returns(@"C:\Test\NzbDrone.exe");
_sandboxFolder = Mocker.GetMock<IAppFolderInfo>().Object.GetUpdateSandboxFolder(); _sandboxFolder = Mocker.GetMock<IAppFolderInfo>().Object.GetUpdateSandboxFolder();
} }
private void GivenInstallScript(string path)
{
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.UpdateMechanism)
.Returns(UpdateMechanism.Script);
Mocker.GetMock<IConfigFileProvider>()
.SetupGet(s => s.UpdateScriptPath)
.Returns(path);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FileExists(path, true))
.Returns(true);
}
[Test] [Test]
public void should_delete_sandbox_before_update_if_folder_exists() public void should_delete_sandbox_before_update_if_folder_exists()
@ -77,7 +94,6 @@ namespace NzbDrone.Core.Test.UpdateTests
Mocker.GetMock<IDiskProvider>().Verify(c => c.DeleteFolder(_sandboxFolder, true), Times.Never()); Mocker.GetMock<IDiskProvider>().Verify(c => c.DeleteFolder(_sandboxFolder, true), Times.Never());
} }
[Test] [Test]
public void Should_download_update_package() public void Should_download_update_package()
{ {
@ -118,11 +134,11 @@ namespace NzbDrone.Core.Test.UpdateTests
Subject.Execute(new ApplicationUpdateCommand()); Subject.Execute(new ApplicationUpdateCommand());
Mocker.GetMock<IProcessProvider>() Mocker.GetMock<IProcessProvider>()
.Verify(c => c.Start(It.IsAny<string>(), "12", null, null), Times.Once()); .Verify(c => c.Start(It.IsAny<string>(), It.Is<String>(s => s.StartsWith("12")), null, null), Times.Once());
} }
[Test] [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<ICheckUpdateService>().Setup(c => c.AvailableUpdate()).Returns<UpdatePackage>(null); Mocker.GetMock<ICheckUpdateService>().Setup(c => c.AvailableUpdate()).Returns<UpdatePackage>(null);
@ -132,6 +148,75 @@ namespace NzbDrone.Core.Test.UpdateTests
ExceptionVerification.AssertNoUnexpectedLogs(); ExceptionVerification.AssertNoUnexpectedLogs();
} }
[Test]
public void should_not_extract_if_verification_fails()
{
Mocker.GetMock<IVerifyUpdates>().Setup(c => c.Verify(It.IsAny<UpdatePackage>(), It.IsAny<String>())).Returns(false);
Subject.Execute(new ApplicationUpdateCommand());
Mocker.GetMock<IArchiveService>().Verify(v => v.Extract(It.IsAny<String>(), It.IsAny<String>()), 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<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), 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<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), 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<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), 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<IDiskProvider>()
.Setup(s => s.FileExists(scriptPath, true))
.Returns(false);
Subject.Execute(new ApplicationUpdateCommand());
ExceptionVerification.ExpectedErrors(1);
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never());
}
[Test] [Test]
[IntegrationTest] [IntegrationTest]
public void Should_download_and_extract_to_temp_folder() public void Should_download_and_extract_to_temp_folder()

@ -11,6 +11,7 @@ using NzbDrone.Core.Configuration.Events;
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;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Configuration namespace NzbDrone.Core.Configuration
@ -30,11 +31,13 @@ namespace NzbDrone.Core.Configuration
string Password { get; } string Password { get; }
string LogLevel { get; } string LogLevel { get; }
string Branch { get; } string Branch { get; }
bool AutoUpdate { get; }
string ApiKey { get; } string ApiKey { get; }
bool Torrent { get; } bool Torrent { get; }
string SslCertHash { get; } string SslCertHash { get; }
string UrlBase { get; } string UrlBase { get; }
Boolean UpdateAutomatically { get; }
UpdateMechanism UpdateMechanism { get; }
String UpdateScriptPath { get; }
} }
public class ConfigFileProvider : IConfigFileProvider public class ConfigFileProvider : IConfigFileProvider
@ -141,11 +144,6 @@ namespace NzbDrone.Core.Configuration
get { return GetValue("Branch", "master").ToLowerInvariant(); } get { return GetValue("Branch", "master").ToLowerInvariant(); }
} }
public bool AutoUpdate
{
get { return GetValueBoolean("AutoUpdate", false, persist: false); }
}
public string Username public string Username
{ {
get { return GetValue("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) public int GetValueInt(string key, int defaultValue)
{ {
return Convert.ToInt32(GetValue(key, defaultValue)); return Convert.ToInt32(GetValue(key, defaultValue));
@ -191,9 +204,9 @@ namespace NzbDrone.Core.Configuration
return Convert.ToBoolean(GetValue(key, defaultValue, persist)); return Convert.ToBoolean(GetValue(key, defaultValue, persist));
} }
public T GetValueEnum<T>(string key, T defaultValue) public T GetValueEnum<T>(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) public string GetValue(string key, object defaultValue, bool persist = true)
@ -210,7 +223,9 @@ namespace NzbDrone.Core.Configuration
var valueHolder = parentContainer.Descendants(key).ToList(); var valueHolder = parentContainer.Descendants(key).ToList();
if (valueHolder.Count() == 1) if (valueHolder.Count() == 1)
{
return valueHolder.First().Value.Trim(); return valueHolder.First().Value.Trim();
}
//Save the value //Save the value
if (persist) if (persist)

@ -6,6 +6,7 @@ using NzbDrone.Common.EnsureThat;
using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Configuration.Events;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Configuration namespace NzbDrone.Core.Configuration

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Update;
namespace NzbDrone.Core.Configuration namespace NzbDrone.Core.Configuration
{ {
@ -23,7 +24,6 @@ namespace NzbDrone.Core.Configuration
Int32 BlacklistRetryInterval { get; set; } Int32 BlacklistRetryInterval { get; set; }
Int32 BlacklistRetryLimit { get; set; } Int32 BlacklistRetryLimit { get; set; }
//Media Management //Media Management
Boolean AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; } Boolean AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
String RecycleBin { get; set; } String RecycleBin { get; set; }

@ -693,10 +693,13 @@
<Compile Include="Update\InstallUpdateService.cs" /> <Compile Include="Update\InstallUpdateService.cs" />
<Compile Include="Update\RecentUpdateProvider.cs" /> <Compile Include="Update\RecentUpdateProvider.cs" />
<Compile Include="Update\UpdateChanges.cs" /> <Compile Include="Update\UpdateChanges.cs" />
<Compile Include="Update\UpdateMechanism.cs" />
<Compile Include="Update\UpdatePackageAvailable.cs" /> <Compile Include="Update\UpdatePackageAvailable.cs" />
<Compile Include="Update\UpdatePackageProvider.cs" /> <Compile Include="Update\UpdatePackageProvider.cs" />
<Compile Include="Update\UpdatePackage.cs" /> <Compile Include="Update\UpdatePackage.cs" />
<Compile Include="Update\UpdateCheckService.cs" /> <Compile Include="Update\UpdateCheckService.cs" />
<Compile Include="Update\UpdateVerification.cs" />
<Compile Include="Update\UpdateVerificationFailedException.cs" />
<Compile Include="Validation\Paths\SeriesExistsValidator.cs" /> <Compile Include="Validation\Paths\SeriesExistsValidator.cs" />
<Compile Include="Validation\Paths\RootFolderValidator.cs" /> <Compile Include="Validation\Paths\RootFolderValidator.cs" />
<Compile Include="Validation\Paths\DroneFactoryValidator.cs" /> <Compile Include="Validation\Paths\DroneFactoryValidator.cs" />

@ -1,4 +1,5 @@
using System.Security.Cryptography; using System.IO;
using System.Security.Cryptography;
using System.Text; using System.Text;
namespace NzbDrone.Core namespace NzbDrone.Core
@ -7,17 +8,28 @@ namespace NzbDrone.Core
{ {
public static string SHA256Hash(this string input) public static string SHA256Hash(this string input)
{ {
var stringBuilder = new StringBuilder();
using (var hash = SHA256Managed.Create()) using (var hash = SHA256Managed.Create())
{ {
var enc = Encoding.UTF8; var enc = Encoding.UTF8;
var result = hash.ComputeHash(enc.GetBytes(input)); return GetHash(hash.ComputeHash(enc.GetBytes(input)));
}
}
public static string SHA256Hash(this Stream input)
{
using (var hash = SHA256Managed.Create())
{
return GetHash(hash.ComputeHash(input));
}
}
foreach (var b in result) private static string GetHash(byte[] bytes)
{ {
stringBuilder.Append(b.ToString("x2")); var stringBuilder = new StringBuilder();
}
foreach (var b in bytes)
{
stringBuilder.Append(b.ToString("x2"));
} }
return stringBuilder.ToString(); return stringBuilder.ToString();

@ -6,6 +6,7 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Instrumentation.Extensions; using NzbDrone.Core.Instrumentation.Extensions;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Update.Commands; using NzbDrone.Core.Update.Commands;
@ -27,18 +28,31 @@ namespace NzbDrone.Core.Update
private readonly IHttpProvider _httpProvider; private readonly IHttpProvider _httpProvider;
private readonly IArchiveService _archiveService; private readonly IArchiveService _archiveService;
private readonly IProcessProvider _processProvider; private readonly IProcessProvider _processProvider;
private readonly IVerifyUpdates _updateVerifier;
private readonly IConfigFileProvider _configFileProvider;
private readonly IRuntimeInfo _runtimeInfo;
public InstallUpdateService(ICheckUpdateService checkUpdateService, IAppFolderInfo appFolderInfo, public InstallUpdateService(ICheckUpdateService checkUpdateService, IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider, IHttpProvider httpProvider, 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; _checkUpdateService = checkUpdateService;
_appFolderInfo = appFolderInfo; _appFolderInfo = appFolderInfo;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_httpProvider = httpProvider; _httpProvider = httpProvider;
_archiveService = archiveService; _archiveService = archiveService;
_processProvider = processProvider; _processProvider = processProvider;
_updateVerifier = updateVerifier;
_configFileProvider = configFileProvider;
_runtimeInfo = runtimeInfo;
_logger = logger; _logger = logger;
} }
@ -60,19 +74,34 @@ namespace NzbDrone.Core.Update
_logger.Debug("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination); _logger.Debug("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination);
_httpProvider.DownloadFile(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"); _logger.ProgressInfo("Extracting Update package");
_archiveService.Extract(packageDestination, updateSandboxFolder); _archiveService.Extract(packageDestination, updateSandboxFolder);
_logger.Info("Update package extracted successfully"); _logger.Info("Update package extracted successfully");
if (OsInfo.IsMono && _configFileProvider.UpdateMechanism == UpdateMechanism.Script)
{
InstallUpdateWithScript(updateSandboxFolder);
return;
}
_logger.Info("Preparing client"); _logger.Info("Preparing client");
_diskProvider.MoveFolder(_appFolderInfo.GetUpdateClientFolder(), _diskProvider.MoveFolder(_appFolderInfo.GetUpdateClientFolder(),
updateSandboxFolder); updateSandboxFolder);
_logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath()); _logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath());
_logger.ProgressInfo("NzbDrone will restart shortly."); _logger.ProgressInfo("NzbDrone will restart shortly.");
_processProvider.Start(_appFolderInfo.GetUpdateClientExePath(), _processProvider.GetCurrentProcess().Id.ToString()); _processProvider.Start(_appFolderInfo.GetUpdateClientExePath(), GetUpdaterArgs(updateSandboxFolder));
} }
catch (Exception ex) 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) public void Execute(ApplicationUpdateCommand message)
{ {
_logger.ProgressDebug("Checking for updates"); _logger.ProgressDebug("Checking for updates");

@ -18,7 +18,9 @@ namespace NzbDrone.Core.Update
private readonly Logger _logger; private readonly Logger _logger;
public CheckUpdateService(IUpdatePackageProvider updatePackageProvider, IConfigFileProvider configFileProvider, Logger logger) public CheckUpdateService(IUpdatePackageProvider updatePackageProvider,
IConfigFileProvider configFileProvider,
Logger logger)
{ {
_updatePackageProvider = updatePackageProvider; _updatePackageProvider = updatePackageProvider;
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
@ -27,7 +29,10 @@ namespace NzbDrone.Core.Update
public UpdatePackage AvailableUpdate() public UpdatePackage AvailableUpdate()
{ {
if (OsInfo.IsMono) return null; if (OsInfo.IsMono && !_configFileProvider.UpdateAutomatically)
{
return null;
}
var latestAvailable = _updatePackageProvider.GetLatestUpdate(_configFileProvider.Branch, BuildInfo.Version); var latestAvailable = _updatePackageProvider.GetLatestUpdate(_configFileProvider.Branch, BuildInfo.Version);

@ -0,0 +1,8 @@
namespace NzbDrone.Core.Update
{
public enum UpdateMechanism
{
BuiltIn = 0,
Script = 1
}
}

@ -11,5 +11,6 @@ namespace NzbDrone.Core.Update
public String FileName { get; set; } public String FileName { get; set; }
public String Url { get; set; } public String Url { get; set; }
public UpdateChanges Changes { get; set; } public UpdateChanges Changes { get; set; }
public String Hash { get; set; }
} }
} }

@ -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);
}
}
}
}

@ -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)
{
}
}
}

@ -16,17 +16,14 @@ namespace NzbDrone.Host
{ {
private readonly IProcessProvider _processProvider; private readonly IProcessProvider _processProvider;
private readonly IBrowserService _browserService; private readonly IBrowserService _browserService;
private readonly INzbDroneProcessProvider _nzbDroneProcessProvider;
private readonly Logger _logger; private readonly Logger _logger;
public SingleInstancePolicy(IProcessProvider processProvider, public SingleInstancePolicy(IProcessProvider processProvider,
IBrowserService browserService, IBrowserService browserService,
INzbDroneProcessProvider nzbDroneProcessProvider,
Logger logger) Logger logger)
{ {
_processProvider = processProvider; _processProvider = processProvider;
_browserService = browserService; _browserService = browserService;
_nzbDroneProcessProvider = nzbDroneProcessProvider;
_logger = logger; _logger = logger;
} }
@ -56,10 +53,11 @@ namespace NzbDrone.Host
private List<int> GetOtherNzbDroneProcessIds() private List<int> GetOtherNzbDroneProcessIds()
{ {
var currentId = _processProvider.GetCurrentProcess().Id; var currentId = _processProvider.GetCurrentProcess().Id;
var otherProcesses = _nzbDroneProcessProvider.FindNzbDroneProcesses() var otherProcesses = _processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME)
.Select(c => c.Id) .Union(_processProvider.FindProcessByName(ProcessProvider.NZB_DRONE_PROCESS_NAME))
.Except(new[] {currentId}) .Select(c => c.Id)
.ToList(); .Except(new[] {currentId})
.ToList();
if (otherProcesses.Any()) if (otherProcesses.Any())
{ {

@ -70,7 +70,6 @@
<ItemGroup> <ItemGroup>
<Compile Include="DiskProvider.cs" /> <Compile Include="DiskProvider.cs" />
<Compile Include="LinuxPermissionsException.cs" /> <Compile Include="LinuxPermissionsException.cs" />
<Compile Include="NzbDroneProcessProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -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<ProcessInfo> 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();
}
}
}

@ -21,7 +21,7 @@ namespace NzbDrone.Test.Common
LogManager.Configuration = new LoggingConfiguration(); LogManager.Configuration = new LoggingConfiguration();
var consoleTarget = new ConsoleTarget { Layout = "${level}: ${message} ${exception}" }; var consoleTarget = new ConsoleTarget { Layout = "${level}: ${message} ${exception}" };
LogManager.Configuration.AddTarget(consoleTarget.GetType().Name, consoleTarget); 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(); RegisterExceptionVerification();
} }

@ -9,7 +9,9 @@ namespace NzbDrone.Test.Dummy
static void Main(string[] args) 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(); Console.ReadLine();
} }
} }

@ -51,6 +51,7 @@
<Link>Properties\SharedAssemblyInfo.cs</Link> <Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile> </Compile>
<Compile Include="AppType.cs" /> <Compile Include="AppType.cs" />
<Compile Include="UpdateStartupContext.cs" />
<Compile Include="UpdateApp.cs" /> <Compile Include="UpdateApp.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UpdateContainerBuilder.cs" /> <Compile Include="UpdateContainerBuilder.cs" />

@ -1,6 +1,8 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using NLog; using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation;
@ -50,24 +52,60 @@ namespace NzbDrone.Update
public void Start(string[] args) public void Start(string[] args)
{ {
var processId = ParseProcessId(args); var startupContext = ParseArgs(args);
string targetFolder;
var exeFileInfo = new FileInfo(_processProvider.GetProcessById(processId).StartPath); if (startupContext.ExecutingApplication.IsNullOrWhiteSpace())
var targetFolder = exeFileInfo.Directory.FullName; {
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); logger.Info("Starting update process. Target Path:{0}", targetFolder);
_installUpdateService.Start(targetFolder); _installUpdateService.Start(targetFolder);
} }
private int ParseProcessId(string[] args) private UpdateStartupContext ParseArgs(string[] args)
{
if (args == null || !args.Any())
{
throw new ArgumentOutOfRangeException("args", "args must be specified");
}
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; int id;
if (args == null || !Int32.TryParse(args[0], out id) || id <= 0) if (!Int32.TryParse(arg, out id) || id <= 0)
{ {
throw new ArgumentOutOfRangeException("args", "Invalid process ID"); throw new ArgumentOutOfRangeException("arg", "Invalid process ID");
} }
logger.Debug("NzbDrone processId:{0}", id); logger.Debug("NzbDrone process ID: {0}", id);
return id; return id;
} }
} }

@ -1,4 +1,5 @@
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
namespace NzbDrone.Update.UpdateEngine namespace NzbDrone.Update.UpdateEngine
@ -21,6 +22,12 @@ namespace NzbDrone.Update.UpdateEngine
public AppType GetAppType() 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) if (_serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME)
&& _serviceProvider.IsServiceRunning(ServiceProvider.NZBDRONE_SERVICE_NAME)) && _serviceProvider.IsServiceRunning(ServiceProvider.NZBDRONE_SERVICE_NAME))
{ {

@ -82,7 +82,6 @@ namespace NzbDrone.Update.UpdateEngine
_backupAndRestore.Restore(installationFolder); _backupAndRestore.Restore(installationFolder);
_logger.FatalException("Failed to copy upgrade package to target folder.", e); _logger.FatalException("Failed to copy upgrade package to target folder.", e);
} }
} }
finally finally
{ {

@ -1,6 +1,7 @@
using System; using System;
using NLog; using NLog;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes; using NzbDrone.Common.Processes;
using IServiceProvider = NzbDrone.Common.IServiceProvider; using IServiceProvider = NzbDrone.Common.IServiceProvider;
@ -26,6 +27,15 @@ namespace NzbDrone.Update.UpdateEngine
public void Terminate() 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"); _logger.Info("Stopping all running services");
if (_serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME) if (_serviceProvider.ServiceExist(ServiceProvider.NZBDRONE_SERVICE_NAME)
@ -35,7 +45,6 @@ namespace NzbDrone.Update.UpdateEngine
{ {
_logger.Info("NzbDrone Service is installed and running"); _logger.Info("NzbDrone Service is installed and running");
_serviceProvider.Stop(ServiceProvider.NZBDRONE_SERVICE_NAME); _serviceProvider.Stop(ServiceProvider.NZBDRONE_SERVICE_NAME);
} }
catch (Exception e) catch (Exception e)
{ {

@ -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; }
}
}

@ -63,7 +63,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="DiskProvider.cs" /> <Compile Include="DiskProvider.cs" />
<Compile Include="NzbDroneProcessProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -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<ProcessInfo> 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();
}
}
}

@ -12,19 +12,22 @@ define(
template: 'Settings/General/GeneralViewTemplate', template: 'Settings/General/GeneralViewTemplate',
events: { events: {
'change .x-auth' : '_setAuthOptionsVisibility', 'change .x-auth' : '_setAuthOptionsVisibility',
'change .x-ssl' : '_setSslOptionsVisibility', 'change .x-ssl' : '_setSslOptionsVisibility',
'click .x-reset-api-key' : '_resetApiKey' 'click .x-reset-api-key' : '_resetApiKey',
'change .x-update-mechanism' : '_setScriptGroupVisibility'
}, },
ui: { ui: {
authToggle : '.x-auth', authToggle : '.x-auth',
authOptions : '.x-auth-options', authOptions : '.x-auth-options',
sslToggle : '.x-ssl', sslToggle : '.x-ssl',
sslOptions : '.x-ssl-options', sslOptions : '.x-ssl-options',
resetApiKey : '.x-reset-api-key', resetApiKey : '.x-reset-api-key',
copyApiKey : '.x-copy-api-key', copyApiKey : '.x-copy-api-key',
apiKeyInput : '.x-api-key' apiKeyInput : '.x-api-key',
updateMechanism : '.x-update-mechanism',
scriptGroup : '.x-script-group'
}, },
initialize: function () { initialize: function () {
@ -40,6 +43,10 @@ define(
this.ui.sslOptions.hide(); this.ui.sslOptions.hide();
} }
if (!this._showScriptGroup()) {
this.ui.scriptGroup.hide();
}
CommandController.bindToCommand({ CommandController.bindToCommand({
element: this.ui.resetApiKey, element: this.ui.resetApiKey,
command: { command: {
@ -79,7 +86,7 @@ define(
}, },
_resetApiKey: function () { _resetApiKey: function () {
if (window.confirm("Reset API Key?")) { if (window.confirm('Reset API Key?')) {
CommandController.Execute('resetApiKey', { CommandController.Execute('resetApiKey', {
name : 'resetApiKey' name : 'resetApiKey'
}); });
@ -90,6 +97,21 @@ define(
if (options.command.get('name') === 'resetapikey') { if (options.command.get('name') === 'resetapikey') {
this.model.fetch(); 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';
} }
}); });

@ -102,7 +102,7 @@
<div class="col-sm-8"> <div class="col-sm-8">
<div class="input-group"> <div class="input-group">
<label class="checkbox toggle well"> <label class="checkbox toggle well">
<input type="checkbox" class='x-auth' name="authenticationEnabled"/> <input type="checkbox" class="x-auth" name="authenticationEnabled"/>
<p> <p>
<span>On</span> <span>On</span>
<span>Off</span> <span>Off</span>
@ -117,7 +117,7 @@
</div> </div>
</div> </div>
<div class='x-auth-options'> <div class="x-auth-options">
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Username</label> <label class="col-sm-3 control-label">Username</label>
@ -174,9 +174,8 @@
</div> </div>
</fieldset> </fieldset>
{{#if_windows}}
<fieldset class="advanced-setting"> <fieldset class="advanced-setting">
<legend>Development</legend> <legend>Updating</legend>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Branch</label> <label class="col-sm-3 control-label">Branch</label>
@ -186,28 +185,56 @@
</div> </div>
</div> </div>
<!--{{#if_mono}}--> {{#if_mono}}
<!--<div class="form-group">--> <div class="alert alert-warning">Please see: <a href="https://github.com/NzbDrone/NzbDrone/wiki/Updating">the wiki</a> for more information</div>
<!--<label class="control-label">Auto Update</label>-->
<!--<div class="controls">--> <div class="form-group">
<!--<label class="checkbox toggle well">--> <label class="col-sm-3 control-label">Automatic</label>
<!--<input type="checkbox" name="autoUpdate"/>-->
<div class="col-sm-8">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="updateAutomatically"/>
<p>
<span>On</span>
<span>Off</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Automatically download and install updates. You will still be able to install from System: Updates"/>
</span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Mechanism</label>
<div class="col-sm-1 col-sm-push-4 help-inline">
<i class="icon-nd-form-info" title="Use built-in updater or external script"/>
</div>
<div class="col-sm-4 col-sm-pull-1">
<select name="updateMechanism" class="form-control x-update-mechanism">
<option value="builtIn">Built-in</option>
<option value="script">Script</option>
</select>
</div>
</div>
<!--<p>--> <div class="form-group x-script-group">
<!--<span>Yes</span>--> <label class="col-sm-3 control-label">Script Path</label>
<!--<span>No</span>-->
<!--</p>-->
<!--<div class="btn btn-primary slide-button"/>--> <div class="col-sm-1 col-sm-push-4 help-inline">
<!--</label>--> <i class="icon-nd-form-info" title="Path to a custom script that take an extracted update package and handle the remainder of the update process"/>
</div>
<!--<span class="help-inline-checkbox">--> <div class="col-sm-4 col-sm-pull-1">
<!--<i class="icon-nd-form-info" title="Use drone's built in auto update instead of package manager/manual updating"/>--> <input type="text" name="updateScriptPath" class="form-control"/>
<!--</span>--> </div>
<!--</div>--> </div>
<!--</div>--> {{/if_mono}}
<!--{{/if_mono}}-->
</fieldset> </fieldset>
{{/if_windows}}
</div> </div>

@ -5,17 +5,9 @@
- {{ShortDate releaseDate}} - {{ShortDate releaseDate}}
{{#if installed}}<i class="icon-ok" title="Installed"></i>{{/if}} {{#if installed}}<i class="icon-ok" title="Installed"></i>{{/if}}
{{#if_windows}} {{#if isUpgrade}}
{{#if isUpgrade}} <span class="label label-default install-update x-install-update">Install</span>
<span class="label label-default install-update x-install-update">Install</span> {{/if}}
{{/if}}
{{else}}
{{#if isUpgrade}}
<span class="label label-default install-update">
<a href="https://github.com/NzbDrone/NzbDrone/wiki/Installation#linux">Install</a>
</span>
{{/if}}
{{/if_windows}}
</span> </span>
</legend> </legend>

Loading…
Cancel
Save