diff --git a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs index a8175265d..29637d3cc 100644 --- a/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs +++ b/src/NzbDrone.Core.Test/UpdateTests/UpdateServiceFixture.cs @@ -280,7 +280,7 @@ namespace NzbDrone.Core.Test.UpdateTests } [Test] - public void should_throw_when_install_cannot_be_started() + public void should_log_when_install_cannot_be_started() { Mocker.GetMock() .Setup(c => c.FolderWritable(It.IsAny())) @@ -288,7 +288,7 @@ namespace NzbDrone.Core.Test.UpdateTests var updateArchive = Path.Combine(_sandboxFolder, _updatePackage.FileName); - Assert.Throws(() => Subject.Execute(new InstallUpdateCommand() { UpdatePackage = _updatePackage })); + Subject.Execute(new InstallUpdateCommand() { UpdatePackage = _updatePackage }); Mocker.GetMock().Verify(c => c.DownloadFile(_updatePackage.Url, updateArchive), Times.Never()); ExceptionVerification.ExpectedErrors(1); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 2c4a2f698..ae7c50e7f 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -855,8 +855,10 @@ + + diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs index af6ab6c6e..7635fe88a 100644 --- a/src/NzbDrone.Core/Update/InstallUpdateService.cs +++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using NLog; using NzbDrone.Common; using NzbDrone.Common.Disk; @@ -10,13 +12,11 @@ using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Processes; using NzbDrone.Core.Backup; using NzbDrone.Core.Configuration; -using NzbDrone.Core.Exceptions; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Update.Commands; namespace NzbDrone.Core.Update { - public class InstallUpdateService : IExecute, IExecute { private readonly ICheckUpdateService _checkUpdateService; @@ -62,72 +62,62 @@ namespace NzbDrone.Core.Update _logger = logger; } - private bool InstallUpdate(UpdatePackage updatePackage) + private void InstallUpdate(UpdatePackage updatePackage) { - try - { - EnsureAppDataSafety(); + EnsureAppDataSafety(); - if (OsInfo.IsWindows || _configFileProvider.UpdateMechanism != UpdateMechanism.Script) + if (OsInfo.IsWindows || _configFileProvider.UpdateMechanism != UpdateMechanism.Script) + { + if (!_diskProvider.FolderWritable(_appFolderInfo.StartUpFolder)) { - if (!_diskProvider.FolderWritable(_appFolderInfo.StartUpFolder)) - { - throw new ApplicationException(string.Format("Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", _appFolderInfo.StartUpFolder, Environment.UserName)); - } + throw new UpdateFolderNotWritableException("Cannot install update because startup folder '{0}' is not writable by the user '{1}'.", _appFolderInfo.StartUpFolder, Environment.UserName); } + } - var updateSandboxFolder = _appFolderInfo.GetUpdateSandboxFolder(); - - var packageDestination = Path.Combine(updateSandboxFolder, updatePackage.FileName); + var updateSandboxFolder = _appFolderInfo.GetUpdateSandboxFolder(); - if (_diskProvider.FolderExists(updateSandboxFolder)) - { - _logger.Info("Deleting old update files"); - _diskProvider.DeleteFolder(updateSandboxFolder, true); - } + var packageDestination = Path.Combine(updateSandboxFolder, updatePackage.FileName); - _logger.ProgressInfo("Downloading update {0}", updatePackage.Version); - _logger.Debug("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination); - _httpClient.DownloadFile(updatePackage.Url, packageDestination); + if (_diskProvider.FolderExists(updateSandboxFolder)) + { + _logger.Info("Deleting old update files"); + _diskProvider.DeleteFolder(updateSandboxFolder, true); + } - _logger.ProgressInfo("Verifying update package"); + _logger.ProgressInfo("Downloading update {0}", updatePackage.Version); + _logger.Debug("Downloading update package from [{0}] to [{1}]", updatePackage.Url, packageDestination); + _httpClient.DownloadFile(updatePackage.Url, packageDestination); - if (!_updateVerifier.Verify(updatePackage, packageDestination)) - { - _logger.Error("Update package is invalid"); - throw new UpdateVerificationFailedException("Update file '{0}' is invalid", packageDestination); - } + _logger.ProgressInfo("Verifying update package"); - _logger.Info("Update package verified successfully"); + if (!_updateVerifier.Verify(updatePackage, packageDestination)) + { + _logger.Error("Update package is invalid"); + throw new UpdateVerificationFailedException("Update file '{0}' is invalid", packageDestination); + } - _logger.ProgressInfo("Extracting Update package"); - _archiveService.Extract(packageDestination, updateSandboxFolder); - _logger.Info("Update package extracted successfully"); + _logger.Info("Update package verified successfully"); - _backupService.Backup(BackupType.Update); + _logger.ProgressInfo("Extracting Update package"); + _archiveService.Extract(packageDestination, updateSandboxFolder); + _logger.Info("Update package extracted successfully"); - if (OsInfo.IsNotWindows && _configFileProvider.UpdateMechanism == UpdateMechanism.Script) - { - InstallUpdateWithScript(updateSandboxFolder); - return true; - } + _backupService.Backup(BackupType.Update); - _logger.Info("Preparing client"); - _diskProvider.MoveFolder(_appFolderInfo.GetUpdateClientFolder(), - updateSandboxFolder); + if (OsInfo.IsNotWindows && _configFileProvider.UpdateMechanism == UpdateMechanism.Script) + { + InstallUpdateWithScript(updateSandboxFolder); + return; + } - _logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath()); - _logger.ProgressInfo("NzbDrone will restart shortly."); + _logger.Info("Preparing client"); + _diskProvider.MoveFolder(_appFolderInfo.GetUpdateClientFolder(), + updateSandboxFolder); - _processProvider.Start(_appFolderInfo.GetUpdateClientExePath(), GetUpdaterArgs(updateSandboxFolder)); + _logger.Info("Starting update client {0}", _appFolderInfo.GetUpdateClientExePath()); + _logger.ProgressInfo("Sonarr will restart shortly."); - return true; - } - catch (Exception ex) - { - _logger.ErrorException("Update process failed", ex); - return false; - } + _processProvider.Start(_appFolderInfo.GetUpdateClientExePath(), GetUpdaterArgs(updateSandboxFolder)); } private void InstallUpdateWithScript(String updateSandboxFolder) @@ -165,7 +155,32 @@ namespace NzbDrone.Core.Update if (_appFolderInfo.StartUpFolder.IsParentPath(_appFolderInfo.AppDataFolder) || _appFolderInfo.StartUpFolder.PathEquals(_appFolderInfo.AppDataFolder)) { - throw new NotSupportedException("Update will cause AppData to be deleted, correct you configuration before proceeding"); + throw new UpdateFailedException("You Sonarr configuration ('{0}') is being stored in application folder ('{1}') which will cause data lost during the upgrade. Please remove any symlinks or redirects before trying again.", _appFolderInfo.AppDataFolder, _appFolderInfo.StartUpFolder); + } + } + + private void ExecuteInstallUpdate(Command message, UpdatePackage package) + { + try + { + InstallUpdate(package); + + message.Completed("Restarting Sonarr to apply updates"); + } + catch (UpdateFolderNotWritableException ex) + { + _logger.ErrorException("Update process failed", ex); + message.Failed(ex, string.Format("Startup folder not writable by user '{0}'", Environment.UserName)); + } + catch (UpdateVerificationFailedException ex) + { + _logger.ErrorException("Update process failed", ex); + message.Failed(ex, "Downloaded update package is corrupt"); + } + catch (UpdateFailedException ex) + { + _logger.ErrorException("Update process failed", ex); + message.Failed(ex); } } @@ -176,18 +191,20 @@ namespace NzbDrone.Core.Update if (latestAvailable != null) { - InstallUpdate(latestAvailable); + ExecuteInstallUpdate(message, latestAvailable); } } public void Execute(InstallUpdateCommand message) { - var success = InstallUpdate(message.UpdatePackage); + var latestAvailable = _checkUpdateService.AvailableUpdate(); - if (!success) + if (latestAvailable == null || latestAvailable.Hash != message.UpdatePackage.Hash) { - throw new NzbDroneClientException(System.Net.HttpStatusCode.Conflict, "Failed to install update"); + throw new ApplicationException("Unknown or invalid update specified"); } + + ExecuteInstallUpdate(message, latestAvailable); } } } diff --git a/src/NzbDrone.Core/Update/UpdateAbortedException.cs b/src/NzbDrone.Core/Update/UpdateAbortedException.cs new file mode 100644 index 000000000..f6561dcec --- /dev/null +++ b/src/NzbDrone.Core/Update/UpdateAbortedException.cs @@ -0,0 +1,17 @@ +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Update +{ + public class UpdateFailedException : NzbDroneException + { + public UpdateFailedException(string message, params object[] args) + : base(message, args) + { + } + + public UpdateFailedException(string message) + : base(message) + { + } + } +} diff --git a/src/NzbDrone.Core/Update/UpdateFolderNotWritableException.cs b/src/NzbDrone.Core/Update/UpdateFolderNotWritableException.cs new file mode 100644 index 000000000..6d7ad9025 --- /dev/null +++ b/src/NzbDrone.Core/Update/UpdateFolderNotWritableException.cs @@ -0,0 +1,17 @@ +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Update +{ + public class UpdateFolderNotWritableException : UpdateFailedException + { + public UpdateFolderNotWritableException(string message, params object[] args) + : base(message, args) + { + } + + public UpdateFolderNotWritableException(string message) + : base(message) + { + } + } +} diff --git a/src/NzbDrone.Core/Update/UpdateVerificationFailedException.cs b/src/NzbDrone.Core/Update/UpdateVerificationFailedException.cs index 56d9da122..d7116fcb3 100644 --- a/src/NzbDrone.Core/Update/UpdateVerificationFailedException.cs +++ b/src/NzbDrone.Core/Update/UpdateVerificationFailedException.cs @@ -2,14 +2,16 @@ namespace NzbDrone.Core.Update { - public class UpdateVerificationFailedException : NzbDroneException + public class UpdateVerificationFailedException : UpdateFailedException { - public UpdateVerificationFailedException(string message, params object[] args) : base(message, args) + public UpdateVerificationFailedException(string message, params object[] args) + : base(message, args) { } - public UpdateVerificationFailedException(string message) : base(message) + public UpdateVerificationFailedException(string message) + : base(message) { } } -} +} \ No newline at end of file diff --git a/src/UI/Commands/CommandMessengerItemView.js b/src/UI/Commands/CommandMessengerItemView.js index 1b56ebc78..f3f6effde 100644 --- a/src/UI/Commands/CommandMessengerItemView.js +++ b/src/UI/Commands/CommandMessengerItemView.js @@ -15,12 +15,15 @@ module.exports = Marionette.ItemView.extend({ id : this.model.id, hideAfter : 0 }; + + var isManual = this.model.get('manual'); + switch (this.model.get('state')) { case 'completed': message.hideAfter = 4; break; case 'failed': - message.hideAfter = 4; + message.hideAfter = isManual ? 10 : 4; message.type = 'error'; break; default: