1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2024-12-16 11:37:58 +02:00

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
This commit is contained in:
Mark McDowall 2014-04-27 23:34:29 -07:00
parent 5c2f77339d
commit ef3777fccf
46 changed files with 588 additions and 255 deletions

View File

@ -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']"
} }

View File

@ -3,7 +3,9 @@
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 @@ public class HostConfigModule : NzbDroneRestModule<HostConfigResource>
{ {
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 @@ public HostConfigModule(ConfigFileProvider configFileProvider)
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()

View File

@ -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 @@ public class HostConfigResource : RestResource
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; }
} }
} }

View File

@ -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" />

View File

@ -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 @@ private List<UpdateResource> GetRecentUpdates()
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; }
}
} }

View File

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

View File

@ -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 @@ public void Setup()
{ {
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 @@ public void should_continue_if_only_instance()
[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 @@ public void should_enforce_if_another_console_is_running()
[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());

View File

@ -168,25 +168,25 @@ public void Config_path_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]

View File

@ -447,5 +447,15 @@ public string GetVolumeLabel(string path)
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);
}
} }
} }

View File

@ -11,7 +11,6 @@ public interface IDiskProvider
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 @@ public interface IDiskProvider
void EmptyFolder(string path); void EmptyFolder(string path);
string[] GetFixedDrives(); string[] GetFixedDrives();
string GetVolumeLabel(string path); string GetVolumeLabel(string path);
FileStream StreamFile(string path);
} }
} }

View File

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

View File

@ -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" />

View File

@ -13,10 +13,10 @@ public static class PathExtensions
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;

View File

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

View File

@ -21,7 +21,7 @@ public interface IProcessProvider
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 @@ public class ProcessProvider : IProcessProvider
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 @@ public ProcessInfo GetProcessById(int id)
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 @@ public void SetPriority(int processId, ProcessPriorityClass priority)
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 @@ private static string GetExeFileName(Process process)
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);
} }
} }
} }

View File

@ -12,5 +12,5 @@
// 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.*")]

View File

@ -13,6 +13,15 @@ public static void Register()
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;
} }
} }

View File

@ -3,6 +3,7 @@
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 @@ public static bool IsNullOrWhiteSpace(this string text)
{ {
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 + "\"";
}
} }
} }

View File

@ -9,6 +9,7 @@
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 @@ public void Setup()
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 @@ public void should_not_delete_sandbox_before_update_if_folder_doesnt_exists()
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 @@ public void should_start_update_client()
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 @@ public void when_no_updates_are_available_should_return_without_error_or_warning
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()

View File

@ -11,6 +11,7 @@
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 @@ public interface IConfigFileProvider : IHandleAsync<ApplicationStartedEvent>,
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 @@ public string Branch
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 @@ public string UrlBase
} }
} }
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 @@ public bool GetValueBoolean(string key, bool defaultValue, bool persist = true)
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 @@ public string GetValue(string key, object defaultValue, bool persist = true)
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)

View File

@ -6,6 +6,7 @@
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

View File

@ -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 @@ public interface IConfigService
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; }

View File

@ -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" />

View File

@ -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 @@ public static class Security
{ {
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)));
}
}
foreach (var b in result) public static string SHA256Hash(this Stream input)
{ {
stringBuilder.Append(b.ToString("x2")); 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(); return stringBuilder.ToString();

View File

@ -6,6 +6,7 @@
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 @@ public class InstallUpdateService : IInstallUpdates, IExecute<ApplicationUpdateC
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 @@ public void InstallUpdate(UpdatePackage updatePackage)
_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 @@ public void InstallUpdate(UpdatePackage updatePackage)
} }
} }
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");

View File

@ -18,7 +18,9 @@ public class CheckUpdateService : ICheckUpdateService
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 @@ public CheckUpdateService(IUpdatePackageProvider updatePackageProvider, IConfigF
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);

View File

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

View File

@ -11,5 +11,6 @@ public class UpdatePackage
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; }
} }
} }

View File

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

View File

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

View File

@ -16,17 +16,14 @@ public class SingleInstancePolicy : ISingleInstancePolicy
{ {
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 @@ private bool IsAlreadyRunning()
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())
{ {

View File

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

View File

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

View File

@ -21,7 +21,7 @@ protected static void InitLogging()
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();
} }

View File

@ -9,7 +9,9 @@ public class DummyApp
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();
} }
} }

View File

@ -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" />

View File

@ -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 @@ public static void Main(string[] args)
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)
{ {
int id; if (args == null || !args.Any())
if (args == null || !Int32.TryParse(args[0], out id) || id <= 0)
{ {
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; return id;
} }
} }

View File

@ -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 @@ public DetectApplicationType(IServiceProvider serviceProvider, IProcessProvider
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))
{ {

View File

@ -82,7 +82,6 @@ public void Start(string installationFolder)
_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
{ {

View File

@ -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 @@ public TerminateNzbDrone(IServiceProvider serviceProvider, IProcessProvider proc
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 @@ public void Terminate()
{ {
_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)
{ {

View File

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

View File

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

View File

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

View File

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

View File

@ -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"/>-->
<!--<p>--> <div class="col-sm-8">
<!--<span>Yes</span>--> <div class="input-group">
<!--<span>No</span>--> <label class="checkbox toggle well">
<!--</p>--> <input type="checkbox" name="updateAutomatically"/>
<p>
<span>On</span>
<span>Off</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<!--<div class="btn btn-primary slide-button"/>--> <span class="help-inline-checkbox">
<!--</label>--> <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>
<!--<span class="help-inline-checkbox">--> <div class="form-group">
<!--<i class="icon-nd-form-info" title="Use drone's built in auto update instead of package manager/manual updating"/>--> <label class="col-sm-3 control-label">Mechanism</label>
<!--</span>-->
<!--</div>--> <div class="col-sm-1 col-sm-push-4 help-inline">
<!--</div>--> <i class="icon-nd-form-info" title="Use built-in updater or external script"/>
<!--{{/if_mono}}--> </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>
<div class="form-group x-script-group">
<label class="col-sm-3 control-label">Script Path</label>
<div class="col-sm-1 col-sm-push-4 help-inline">
<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>
<div class="col-sm-4 col-sm-pull-1">
<input type="text" name="updateScriptPath" class="form-control"/>
</div>
</div>
{{/if_mono}}
</fieldset> </fieldset>
{{/if_windows}}
</div> </div>

View File

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