diff --git a/src/NzbDrone.Core/Backup/RestoreBackupFailedException.cs b/src/NzbDrone.Core/Backup/RestoreBackupFailedException.cs index 93a5f1105..3a06b1b1b 100644 --- a/src/NzbDrone.Core/Backup/RestoreBackupFailedException.cs +++ b/src/NzbDrone.Core/Backup/RestoreBackupFailedException.cs @@ -7,12 +7,10 @@ namespace NzbDrone.Core.Backup { public RestoreBackupFailedException(HttpStatusCode statusCode, string message, params object[] args) : base(statusCode, message, args) { - } public RestoreBackupFailedException(HttpStatusCode statusCode, string message) : base(statusCode, message) { - } } } diff --git a/src/Radarr.Api.V2/System/Backup/BackupModule.cs b/src/Radarr.Api.V2/System/Backup/BackupModule.cs index b924c48b1..ed9257735 100644 --- a/src/Radarr.Api.V2/System/Backup/BackupModule.cs +++ b/src/Radarr.Api.V2/System/Backup/BackupModule.cs @@ -1,19 +1,39 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Nancy; +using NzbDrone.Common.Crypto; +using NzbDrone.Common.Disk; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Backup; using Radarr.Http; +using Radarr.Http.Extensions; +using Radarr.Http.REST; namespace Radarr.Api.V2.System.Backup { public class BackupModule : RadarrRestModule { private readonly IBackupService _backupService; + private readonly IAppFolderInfo _appFolderInfo; + private readonly IDiskProvider _diskProvider; - public BackupModule(IBackupService backupService) : base("system/backup") + private static readonly List ValidExtensions = new List { ".zip", ".db", ".xml" }; + + public BackupModule(IBackupService backupService, + IAppFolderInfo appFolderInfo, + IDiskProvider diskProvider) + : base("system/backup") { _backupService = backupService; + _appFolderInfo = appFolderInfo; + _diskProvider = diskProvider; GetResourceAll = GetBackupFiles; + DeleteResource = DeleteBackup; + + Post[@"/restore/(?[\d]{1,10})"] = x => Restore((int)x.Id); + Post["/restore/upload"] = x => UploadAndRestore(); } public List GetBackupFiles() @@ -22,12 +42,92 @@ namespace Radarr.Api.V2.System.Backup return backups.Select(b => new BackupResource { - Id = b.Name.GetHashCode(), + Id = GetBackupId(b), Name = b.Name, Path = $"/backup/{b.Type.ToString().ToLower()}/{b.Name}", Type = b.Type, Time = b.Time - }).ToList(); + }) + .OrderByDescending(b => b.Time) + .ToList(); + } + + private void DeleteBackup(int id) + { + var backup = GetBackup(id); + var path = GetBackupPath(backup); + + if (!_diskProvider.FileExists(path)) + { + throw new NotFoundException(); + } + + _diskProvider.DeleteFile(path); + } + + public Response Restore(int id) + { + var backup = GetBackup(id); + + if (backup == null) + { + throw new NotFoundException(); + } + + var path = GetBackupPath(backup); + + _backupService.Restore(path); + + return new + { + RestartRequired = true + }.AsResponse(); + } + + public Response UploadAndRestore() + { + var files = Context.Request.Files.ToList(); + + if (files.Empty()) + { + throw new BadRequestException("file must be provided"); + } + + var file = files.First(); + var extension = Path.GetExtension(file.Name); + + if (!ValidExtensions.Contains(extension)) + { + throw new UnsupportedMediaTypeException($"Invalid extension, must be one of: {ValidExtensions.Join(", ")}"); + } + + var path = Path.Combine(_appFolderInfo.TempFolder, $"radarr_backup_restore{extension}"); + + _diskProvider.SaveStream(file.Value, path); + _backupService.Restore(path); + + // Cleanup restored file + _diskProvider.DeleteFile(path); + + return new + { + RestartRequired = true + }.AsResponse(); + } + + private string GetBackupPath(NzbDrone.Core.Backup.Backup backup) + { + return Path.Combine(_backupService.GetBackupFolder(backup.Type), backup.Name); + } + + private int GetBackupId(NzbDrone.Core.Backup.Backup backup) + { + return HashConverter.GetHashInt31($"backup-{backup.Type}-{backup.Name}"); + } + + private NzbDrone.Core.Backup.Backup GetBackup(int id) + { + return _backupService.GetBackups().SingleOrDefault(b => GetBackupId(b) == id); } } }