mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-20 21:43:33 -07:00
New: UI Updates (Backup Restore in App, Profile Cloning)
UI Pulls from Sonarr
This commit is contained in:
parent
80a5701b99
commit
744742b5ff
80 changed files with 2376 additions and 795 deletions
|
@ -1,3 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FluentValidation;
|
||||
|
@ -46,6 +47,11 @@ namespace Lidarr.Api.V1.Config
|
|||
|
||||
SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
|
||||
SharedValidator.RuleFor(c => c.UpdateScriptPath).IsValidPath().When(c => c.UpdateMechanism == UpdateMechanism.Script);
|
||||
|
||||
SharedValidator.RuleFor(c => c.BackupFolder).IsValidPath().When(c => Path.IsPathRooted(c.BackupFolder));
|
||||
SharedValidator.RuleFor(c => c.BackupInterval).InclusiveBetween(1, 7);
|
||||
SharedValidator.RuleFor(c => c.BackupRetention).InclusiveBetween(1, 90);
|
||||
|
||||
}
|
||||
|
||||
private HostConfigResource GetHostConfig()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Update;
|
||||
|
@ -33,6 +33,9 @@ namespace Lidarr.Api.V1.Config
|
|||
public string ProxyPassword { get; set; }
|
||||
public string ProxyBypassFilter { get; set; }
|
||||
public bool ProxyBypassLocalAddresses { get; set; }
|
||||
public string BackupFolder { get; set; }
|
||||
public int BackupInterval { get; set; }
|
||||
public int BackupRetention { get; set; }
|
||||
}
|
||||
|
||||
public static class HostConfigResourceMapper
|
||||
|
@ -66,7 +69,10 @@ namespace Lidarr.Api.V1.Config
|
|||
ProxyUsername = configService.ProxyUsername,
|
||||
ProxyPassword = configService.ProxyPassword,
|
||||
ProxyBypassFilter = configService.ProxyBypassFilter,
|
||||
ProxyBypassLocalAddresses = configService.ProxyBypassLocalAddresses
|
||||
ProxyBypassLocalAddresses = configService.ProxyBypassLocalAddresses,
|
||||
BackupFolder = configService.BackupFolder,
|
||||
BackupInterval = configService.BackupInterval,
|
||||
BackupRetention = configService.BackupRetention
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 Lidarr.Http;
|
||||
using Lidarr.Http.Extensions;
|
||||
using Lidarr.Http.REST;
|
||||
|
||||
namespace Lidarr.Api.V1.System.Backup
|
||||
{
|
||||
public class BackupModule : LidarrRestModule<BackupResource>
|
||||
{
|
||||
private readonly IBackupService _backupService;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
|
||||
public BackupModule(IBackupService backupService) : base("system/backup")
|
||||
private static readonly List<string> ValidExtensions = new List<string> { ".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/(?<id>[\d]{1,10})"] = x => Restore((int)x.Id);
|
||||
Post["/restore/upload"] = x => UploadAndRestore();
|
||||
}
|
||||
|
||||
public List<BackupResource> GetBackupFiles()
|
||||
|
@ -21,15 +41,93 @@ namespace Lidarr.Api.V1.System.Backup
|
|||
var backups = _backupService.GetBackups();
|
||||
|
||||
return backups.Select(b => new BackupResource
|
||||
{
|
||||
Id = b.Name.GetHashCode(),
|
||||
Name = b.Name,
|
||||
Path = $"/backup/{b.Type.ToString().ToLower()}/{b.Name}",
|
||||
Type = b.Type,
|
||||
Time = b.Time
|
||||
})
|
||||
{
|
||||
Id = GetBackupId(b),
|
||||
Name = b.Name,
|
||||
Path = $"/backup/{b.Type.ToString().ToLower()}/{b.Name}",
|
||||
Type = b.Type,
|
||||
Time = b.Time
|
||||
})
|
||||
.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, $"lidarr_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.ToString(), 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 => id == GetBackupId(b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System.Threading.Tasks;
|
||||
using Nancy;
|
||||
using Nancy.Routing;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
@ -81,14 +82,14 @@ namespace Lidarr.Api.V1.System
|
|||
|
||||
private Response Shutdown()
|
||||
{
|
||||
_lifecycleService.Shutdown();
|
||||
return "".AsResponse();
|
||||
Task.Factory.StartNew(() => _lifecycleService.Shutdown());
|
||||
return new { ShuttingDown = true }.AsResponse();
|
||||
}
|
||||
|
||||
private Response Restart()
|
||||
{
|
||||
_lifecycleService.Restart();
|
||||
return "".AsResponse();
|
||||
Task.Factory.StartNew(() => _lifecycleService.Restart());
|
||||
return new { Restarting = true }.AsResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,30 @@
|
|||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Backup;
|
||||
|
||||
namespace Lidarr.Http.Frontend.Mappers
|
||||
{
|
||||
public class BackupFileMapper : StaticResourceMapperBase
|
||||
{
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IBackupService _backupService;
|
||||
|
||||
public BackupFileMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
|
||||
public BackupFileMapper(IBackupService backupService, IDiskProvider diskProvider, Logger logger)
|
||||
: base(diskProvider, logger)
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_backupService = backupService;
|
||||
}
|
||||
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);
|
||||
|
||||
return Path.Combine(_appFolderInfo.GetBackupFolder(), path);
|
||||
return Path.Combine(_backupService.GetBackupFolder(), path);
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
{
|
||||
return resourceUrl.StartsWith("/backup/") && resourceUrl.ContainsIgnoreCase("lidarr_backup_") && resourceUrl.EndsWith(".zip");
|
||||
return resourceUrl.StartsWith("/backup/") && BackupService.BackupFileRegex.IsMatch(resourceUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@
|
|||
<Compile Include="LidarrBootstrapper.cs" />
|
||||
<Compile Include="LidarrRestModule.cs" />
|
||||
<Compile Include="LidarrRestModuleWithSignalR.cs" />
|
||||
<Compile Include="REST\UnsupportedMediaTypeException.cs" />
|
||||
<Compile Include="TinyIoCNancyBootstrapper.cs" />
|
||||
<Compile Include="Validation\EmptyCollectionValidator.cs" />
|
||||
<Compile Include="Validation\RssSyncIntervalValidator.cs" />
|
||||
|
|
13
src/Lidarr.Http/REST/UnsupportedMediaTypeException.cs
Normal file
13
src/Lidarr.Http/REST/UnsupportedMediaTypeException.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Nancy;
|
||||
using Lidarr.Http.Exceptions;
|
||||
|
||||
namespace Lidarr.Http.REST
|
||||
{
|
||||
public class UnsupportedMediaTypeException : ApiException
|
||||
{
|
||||
public UnsupportedMediaTypeException(object content = null)
|
||||
: base(HttpStatusCode.UnsupportedMediaType, content)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -474,5 +474,13 @@ namespace NzbDrone.Common.Disk
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveStream(Stream stream, string path)
|
||||
{
|
||||
using (var fileStream = OpenWriteStream(path))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.AccessControl;
|
||||
|
@ -48,5 +48,6 @@ namespace NzbDrone.Common.Disk
|
|||
List<DirectoryInfo> GetDirectoryInfos(string path);
|
||||
List<FileInfo> GetFileInfos(string path);
|
||||
void RemoveEmptySubfolders(string path);
|
||||
void SaveStream(Stream stream, string path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,10 @@ namespace NzbDrone.Common.Extensions
|
|||
{
|
||||
private const string APP_CONFIG_FILE = "config.xml";
|
||||
private const string DB = "lidarr.db";
|
||||
private const string DB_RESTORE = "lidarr.restore";
|
||||
private const string LOG_DB = "logs.db";
|
||||
private const string NLOG_CONFIG_FILE = "nlog.config";
|
||||
private const string UPDATE_CLIENT_EXE = "Lidarr.Update.exe";
|
||||
private const string BACKUP_FOLDER = "Backups";
|
||||
|
||||
private static readonly string UPDATE_SANDBOX_FOLDER_NAME = "lidarr_update" + Path.DirectorySeparatorChar;
|
||||
private static readonly string UPDATE_PACKAGE_FOLDER_NAME = "Lidarr" + Path.DirectorySeparatorChar;
|
||||
|
@ -256,16 +256,16 @@ namespace NzbDrone.Common.Extensions
|
|||
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE);
|
||||
}
|
||||
|
||||
public static string GetBackupFolder(this IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
return Path.Combine(GetAppDataPath(appFolderInfo), BACKUP_FOLDER);
|
||||
}
|
||||
|
||||
public static string GetDatabase(this IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
return Path.Combine(GetAppDataPath(appFolderInfo), DB);
|
||||
}
|
||||
|
||||
public static string GetDatabaseRestore(this IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
return Path.Combine(GetAppDataPath(appFolderInfo), DB_RESTORE);
|
||||
}
|
||||
|
||||
public static string GetLogDatabase(this IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
return Path.Combine(GetAppDataPath(appFolderInfo), LOG_DB);
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SQLite;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Marr.Data;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
|
@ -21,6 +21,8 @@ namespace NzbDrone.Core.Backup
|
|||
{
|
||||
void Backup(BackupType backupType);
|
||||
List<Backup> GetBackups();
|
||||
void Restore(string backupFileName);
|
||||
string GetBackupFolder();
|
||||
}
|
||||
|
||||
public class BackupService : IBackupService, IExecute<BackupCommand>
|
||||
|
@ -31,11 +33,12 @@ namespace NzbDrone.Core.Backup
|
|||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IArchiveService _archiveService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private string _backupTempFolder;
|
||||
|
||||
private static readonly Regex BackupFileRegex = new Regex(@"lidarr_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public static readonly Regex BackupFileRegex = new Regex(@"lidarr_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public BackupService(IMainDatabase maindDb,
|
||||
IMakeDatabaseBackup makeDatabaseBackup,
|
||||
|
@ -43,6 +46,7 @@ namespace NzbDrone.Core.Backup
|
|||
IDiskProvider diskProvider,
|
||||
IAppFolderInfo appFolderInfo,
|
||||
IArchiveService archiveService,
|
||||
IConfigService configService,
|
||||
Logger logger)
|
||||
{
|
||||
_maindDb = maindDb;
|
||||
|
@ -51,6 +55,7 @@ namespace NzbDrone.Core.Backup
|
|||
_diskProvider = diskProvider;
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_archiveService = archiveService;
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
|
||||
_backupTempFolder = Path.Combine(_appFolderInfo.TempFolder, "lidarr_backup");
|
||||
|
@ -75,9 +80,15 @@ namespace NzbDrone.Core.Backup
|
|||
|
||||
BackupConfigFile();
|
||||
BackupDatabase();
|
||||
CreateVersionInfo();
|
||||
|
||||
_logger.ProgressDebug("Creating backup zip");
|
||||
|
||||
// Delete journal file created during database backup
|
||||
_diskProvider.DeleteFile(Path.Combine(_backupTempFolder, "lidarr.db-journal"));
|
||||
|
||||
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
|
||||
|
||||
_logger.ProgressDebug("Backup zip created");
|
||||
}
|
||||
|
||||
|
@ -103,6 +114,62 @@ namespace NzbDrone.Core.Backup
|
|||
return backups;
|
||||
}
|
||||
|
||||
public void Restore(string backupFileName)
|
||||
{
|
||||
if (backupFileName.EndsWith(".zip"))
|
||||
{
|
||||
var restoredFile = false;
|
||||
var temporaryPath = Path.Combine(_appFolderInfo.TempFolder, "lidarr_backup_restore");
|
||||
|
||||
_archiveService.Extract(backupFileName, temporaryPath);
|
||||
|
||||
foreach (var file in _diskProvider.GetFiles(temporaryPath, SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
var fileName = Path.GetFileName(file);
|
||||
|
||||
if (fileName.Equals("Config.xml", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_diskProvider.MoveFile(file, _appFolderInfo.GetConfigPath(), true);
|
||||
restoredFile = true;
|
||||
}
|
||||
|
||||
if (fileName.Equals("lidarr.db", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
_diskProvider.MoveFile(file, _appFolderInfo.GetDatabaseRestore(), true);
|
||||
restoredFile = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!restoredFile)
|
||||
{
|
||||
throw new RestoreBackupFailedException(HttpStatusCode.NotFound, "Unable to restore database file from backup");
|
||||
}
|
||||
|
||||
_diskProvider.DeleteFolder(temporaryPath, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_diskProvider.MoveFile(backupFileName, _appFolderInfo.GetDatabaseRestore(), true);
|
||||
}
|
||||
|
||||
public string GetBackupFolder()
|
||||
{
|
||||
var backupFolder = _configService.BackupFolder;
|
||||
|
||||
if (Path.IsPathRooted(backupFolder))
|
||||
{
|
||||
return backupFolder;
|
||||
}
|
||||
|
||||
return Path.Combine(_appFolderInfo.GetAppDataPath(), backupFolder);
|
||||
}
|
||||
|
||||
private string GetBackupFolder(BackupType backupType)
|
||||
{
|
||||
return Path.Combine(GetBackupFolder(), backupType.ToString().ToLower());
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
if (_diskProvider.FolderExists(_backupTempFolder))
|
||||
|
@ -128,16 +195,25 @@ namespace NzbDrone.Core.Backup
|
|||
_diskTransferService.TransferFile(configFile, tempConfigFile, TransferMode.Copy);
|
||||
}
|
||||
|
||||
private void CreateVersionInfo()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine(BuildInfo.Version.ToString());
|
||||
}
|
||||
|
||||
private void CleanupOldBackups(BackupType backupType)
|
||||
{
|
||||
_logger.Debug("Cleaning up old backup files");
|
||||
var retention = _configService.BackupRetention;
|
||||
|
||||
_logger.Debug("Cleaning up backup files older than {0} days", retention);
|
||||
var files = GetBackupFiles(GetBackupFolder(backupType));
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var lastWriteTime = _diskProvider.FileGetLastWrite(file);
|
||||
|
||||
if (lastWriteTime.AddDays(28) < DateTime.UtcNow)
|
||||
if (lastWriteTime.AddDays(retention) < DateTime.UtcNow)
|
||||
{
|
||||
_logger.Debug("Deleting old backup file: {0}", file);
|
||||
_diskProvider.DeleteFile(file);
|
||||
|
@ -147,11 +223,6 @@ namespace NzbDrone.Core.Backup
|
|||
_logger.Debug("Finished cleaning up old backup files");
|
||||
}
|
||||
|
||||
private string GetBackupFolder(BackupType backupType)
|
||||
{
|
||||
return Path.Combine(_appFolderInfo.GetBackupFolder(), backupType.ToString().ToLower());
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetBackupFiles(string path)
|
||||
{
|
||||
var files = _diskProvider.GetFiles(path, SearchOption.TopDirectoryOnly);
|
||||
|
|
16
src/NzbDrone.Core/Backup/RestoreBackupFailedException.cs
Normal file
16
src/NzbDrone.Core/Backup/RestoreBackupFailedException.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.Net;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
|
||||
namespace NzbDrone.Core.Backup
|
||||
{
|
||||
public class RestoreBackupFailedException : NzbDroneClientException
|
||||
{
|
||||
public RestoreBackupFailedException(HttpStatusCode statusCode, string message, params object[] args) : base(statusCode, message, args)
|
||||
{
|
||||
}
|
||||
|
||||
public RestoreBackupFailedException(HttpStatusCode statusCode, string message) : base(statusCode, message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
@ -332,6 +331,12 @@ namespace NzbDrone.Core.Configuration
|
|||
|
||||
public bool ProxyBypassLocalAddresses => GetValueBoolean("ProxyBypassLocalAddresses", true);
|
||||
|
||||
public string BackupFolder => GetValue("BackupFolder", "Backups");
|
||||
|
||||
public int BackupInterval => GetValueInt("BackupInterval", 7);
|
||||
|
||||
public int BackupRetention => GetValueInt("BackupRetention", 28);
|
||||
|
||||
private string GetValue(string key)
|
||||
{
|
||||
return GetValue(key, string.Empty);
|
||||
|
|
|
@ -78,5 +78,11 @@ namespace NzbDrone.Core.Configuration
|
|||
string ProxyPassword { get; }
|
||||
string ProxyBypassFilter { get; }
|
||||
bool ProxyBypassLocalAddresses { get; }
|
||||
|
||||
// Backups
|
||||
string BackupFolder { get; }
|
||||
int BackupInterval { get; }
|
||||
int BackupRetention { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
56
src/NzbDrone.Core/Datastore/DatabaseRestorationService.cs
Normal file
56
src/NzbDrone.Core/Datastore/DatabaseRestorationService.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public interface IRestoreDatabase
|
||||
{
|
||||
void Restore();
|
||||
}
|
||||
|
||||
public class DatabaseRestorationService : IRestoreDatabase
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DatabaseRestorationService));
|
||||
|
||||
public DatabaseRestorationService(IDiskProvider diskProvider, IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_appFolderInfo = appFolderInfo;
|
||||
}
|
||||
|
||||
public void Restore()
|
||||
{
|
||||
var dbRestorePath = _appFolderInfo.GetDatabaseRestore();
|
||||
|
||||
if (!_diskProvider.FileExists(dbRestorePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Info("Restoring Database");
|
||||
|
||||
var dbPath = _appFolderInfo.GetDatabase();
|
||||
|
||||
_diskProvider.DeleteFile(dbPath + "-shm");
|
||||
_diskProvider.DeleteFile(dbPath + "-wal");
|
||||
_diskProvider.DeleteFile(dbPath + "-journal");
|
||||
_diskProvider.DeleteFile(dbPath);
|
||||
|
||||
_diskProvider.MoveFile(dbRestorePath, dbPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, "Failed to restore database");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ namespace NzbDrone.Core.Datastore
|
|||
private readonly IMigrationController _migrationController;
|
||||
private readonly IConnectionStringFactory _connectionStringFactory;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IRestoreDatabase _restoreDatabaseService;
|
||||
|
||||
static DbFactory()
|
||||
{
|
||||
|
@ -44,11 +45,13 @@ namespace NzbDrone.Core.Datastore
|
|||
|
||||
public DbFactory(IMigrationController migrationController,
|
||||
IConnectionStringFactory connectionStringFactory,
|
||||
IDiskProvider diskProvider)
|
||||
IDiskProvider diskProvider,
|
||||
IRestoreDatabase restoreDatabaseService)
|
||||
{
|
||||
_migrationController = migrationController;
|
||||
_connectionStringFactory = connectionStringFactory;
|
||||
_diskProvider = diskProvider;
|
||||
_restoreDatabaseService = restoreDatabaseService;
|
||||
}
|
||||
|
||||
public IDatabase Create(MigrationType migrationType = MigrationType.Main)
|
||||
|
@ -59,18 +62,21 @@ namespace NzbDrone.Core.Datastore
|
|||
public IDatabase Create(MigrationContext migrationContext)
|
||||
{
|
||||
string connectionString;
|
||||
|
||||
|
||||
|
||||
switch (migrationContext.MigrationType)
|
||||
{
|
||||
case MigrationType.Main:
|
||||
{
|
||||
connectionString = _connectionStringFactory.MainDbConnectionString;
|
||||
CreateMain(connectionString, migrationContext);
|
||||
|
||||
break;
|
||||
}
|
||||
case MigrationType.Log:
|
||||
{
|
||||
connectionString = _connectionStringFactory.LogDbConnectionString;
|
||||
CreateLog(connectionString, migrationContext);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -79,53 +85,15 @@ namespace NzbDrone.Core.Datastore
|
|||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_migrationController.Migrate(connectionString, migrationContext);
|
||||
}
|
||||
catch (SQLiteException ex)
|
||||
{
|
||||
var fileName = _connectionStringFactory.GetDatabasePath(connectionString);
|
||||
|
||||
if (migrationContext.MigrationType == MigrationType.Log)
|
||||
{
|
||||
Logger.Error(ex, "Logging database is corrupt, attempting to recreate it automatically");
|
||||
|
||||
try
|
||||
{
|
||||
_diskProvider.DeleteFile(fileName + "-shm");
|
||||
_diskProvider.DeleteFile(fileName + "-wal");
|
||||
_diskProvider.DeleteFile(fileName + "-journal");
|
||||
_diskProvider.DeleteFile(fileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Logger.Error("Unable to recreate logging database automatically. It will need to be removed manually.");
|
||||
}
|
||||
|
||||
_migrationController.Migrate(connectionString, migrationContext);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (OsInfo.IsOsx)
|
||||
{
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Lidarr/Lidarr/wiki/FAQ#i-use-Lidarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", ex, fileName);
|
||||
}
|
||||
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Lidarr/Lidarr/wiki/FAQ#i-am-getting-an-error-database-disk-image-is-malformed", ex, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
var db = new Database(migrationContext.MigrationType.ToString(), () =>
|
||||
{
|
||||
var dataMapper = new DataMapper(SQLiteFactory.Instance, connectionString)
|
||||
{
|
||||
var dataMapper = new DataMapper(SQLiteFactory.Instance, connectionString)
|
||||
{
|
||||
SqlMode = SqlModes.Text,
|
||||
};
|
||||
SqlMode = SqlModes.Text,
|
||||
};
|
||||
|
||||
return dataMapper;
|
||||
});
|
||||
return dataMapper;
|
||||
});
|
||||
|
||||
if (db.Migration > 100) //Quick DB Migration Check. This should get rid of users on old DB format
|
||||
{
|
||||
|
@ -134,5 +102,54 @@ namespace NzbDrone.Core.Datastore
|
|||
|
||||
return db;
|
||||
}
|
||||
|
||||
private void CreateMain(string connectionString, MigrationContext migrationContext)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
_restoreDatabaseService.Restore();
|
||||
_migrationController.Migrate(connectionString, migrationContext);
|
||||
}
|
||||
catch (SQLiteException e)
|
||||
{
|
||||
var fileName = _connectionStringFactory.GetDatabasePath(connectionString);
|
||||
|
||||
if (OsInfo.IsOsx)
|
||||
{
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-use-sonarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
|
||||
}
|
||||
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateLog(string connectionString, MigrationContext migrationContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
_migrationController.Migrate(connectionString, migrationContext);
|
||||
}
|
||||
catch (SQLiteException e)
|
||||
{
|
||||
var fileName = _connectionStringFactory.GetDatabasePath(connectionString);
|
||||
|
||||
Logger.Error(e, "Logging database is corrupt, attempting to recreate it automatically");
|
||||
|
||||
try
|
||||
{
|
||||
_diskProvider.DeleteFile(fileName + "-shm");
|
||||
_diskProvider.DeleteFile(fileName + "-wal");
|
||||
_diskProvider.DeleteFile(fileName + "-journal");
|
||||
_diskProvider.DeleteFile(fileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Logger.Error("Unable to recreate logging database automatically. It will need to be removed manually.");
|
||||
}
|
||||
|
||||
_migrationController.Migrate(connectionString, migrationContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,12 @@ namespace NzbDrone.Core.Jobs
|
|||
new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName},
|
||||
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshArtistCommand).FullName},
|
||||
new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
|
||||
new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName},
|
||||
|
||||
new ScheduledTask
|
||||
{
|
||||
Interval = GetBackupInterval(),
|
||||
TypeName = typeof(BackupCommand).FullName
|
||||
},
|
||||
|
||||
new ScheduledTask
|
||||
{
|
||||
|
@ -102,6 +107,13 @@ namespace NzbDrone.Core.Jobs
|
|||
}
|
||||
}
|
||||
|
||||
private int GetBackupInterval()
|
||||
{
|
||||
var interval = _configService.BackupInterval;
|
||||
|
||||
return interval * 60 * 24;
|
||||
}
|
||||
|
||||
private int GetRssSyncInterval()
|
||||
{
|
||||
var interval = _configService.RssSyncInterval;
|
||||
|
|
|
@ -128,6 +128,7 @@
|
|||
<Compile Include="Backup\BackupCommand.cs" />
|
||||
<Compile Include="Backup\BackupService.cs" />
|
||||
<Compile Include="Backup\MakeDatabaseBackup.cs" />
|
||||
<Compile Include="Backup\RestoreBackupFailedException.cs" />
|
||||
<Compile Include="Blacklisting\Blacklist.cs" />
|
||||
<Compile Include="Blacklisting\BlacklistRepository.cs" />
|
||||
<Compile Include="Blacklisting\BlacklistService.cs" />
|
||||
|
@ -163,6 +164,7 @@
|
|||
<Compile Include="Datastore\Converters\UtcConverter.cs" />
|
||||
<Compile Include="Datastore\CorruptDatabaseException.cs" />
|
||||
<Compile Include="Datastore\Database.cs" />
|
||||
<Compile Include="Datastore\DatabaseRestorationService.cs" />
|
||||
<Compile Include="Datastore\DbFactory.cs" />
|
||||
<Compile Include="Datastore\Events\ModelEvent.cs" />
|
||||
<Compile Include="Datastore\Extensions\MappingExtensions.cs" />
|
||||
|
|
|
@ -90,7 +90,5 @@ namespace NzbDrone.Host
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue