You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Readarr/src/NzbDrone.Core/Backup/BackupService.cs

246 lines
8.3 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
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;
namespace NzbDrone.Core.Backup
{
public interface IBackupService
{
void Backup(BackupType backupType);
List<Backup> GetBackups();
void Restore(string backupFileName);
string GetBackupFolder();
string GetBackupFolder(BackupType backupType);
}
public class BackupService : IBackupService, IExecute<BackupCommand>
{
private readonly IMainDatabase _maindDb;
private readonly IMakeDatabaseBackup _makeDatabaseBackup;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider;
private readonly IAppFolderInfo _appFolderInfo;
private readonly IArchiveService _archiveService;
private readonly IConfigService _configService;
private readonly Logger _logger;
private string _backupTempFolder;
public static readonly Regex BackupFileRegex = new Regex(@"readarr_backup_(v[0-9.]+_)?[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public BackupService(IMainDatabase maindDb,
IMakeDatabaseBackup makeDatabaseBackup,
IDiskTransferService diskTransferService,
IDiskProvider diskProvider,
IAppFolderInfo appFolderInfo,
IArchiveService archiveService,
IConfigService configService,
Logger logger)
{
_maindDb = maindDb;
_makeDatabaseBackup = makeDatabaseBackup;
_diskTransferService = diskTransferService;
_diskProvider = diskProvider;
_appFolderInfo = appFolderInfo;
_archiveService = archiveService;
_configService = configService;
_logger = logger;
_backupTempFolder = Path.Combine(_appFolderInfo.TempFolder, "readarr_backup");
}
public void Backup(BackupType backupType)
{
_logger.ProgressInfo("Starting Backup");
_diskProvider.EnsureFolder(_backupTempFolder);
_diskProvider.EnsureFolder(GetBackupFolder(backupType));
var backupFilename = string.Format("readarr_backup_v{0}_{1:yyyy.MM.dd_HH.mm.ss}.zip", BuildInfo.Version, DateTime.Now);
var backupPath = Path.Combine(GetBackupFolder(backupType), backupFilename);
Cleanup();
if (backupType != BackupType.Manual)
{
CleanupOldBackups(backupType);
}
BackupConfigFile();
BackupDatabase();
CreateVersionInfo();
_logger.ProgressDebug("Creating backup zip");
// Delete journal file created during database backup
_diskProvider.DeleteFile(Path.Combine(_backupTempFolder, "readarr.db-journal"));
_archiveService.CreateZip(backupPath, _diskProvider.GetFiles(_backupTempFolder, SearchOption.TopDirectoryOnly));
Cleanup();
_logger.ProgressDebug("Backup zip created");
}
public List<Backup> GetBackups()
{
var backups = new List<Backup>();
foreach (var backupType in Enum.GetValues(typeof(BackupType)).Cast<BackupType>())
{
var folder = GetBackupFolder(backupType);
if (_diskProvider.FolderExists(folder))
{
backups.AddRange(GetBackupFiles(folder).Select(b => new Backup
{
Name = Path.GetFileName(b),
Type = backupType,
Size = _diskProvider.GetFileSize(b),
Time = _diskProvider.FileGetLastWrite(b)
}));
}
}
return backups;
}
public void Restore(string backupFileName)
{
if (backupFileName.EndsWith(".zip"))
{
var restoredFile = false;
var temporaryPath = Path.Combine(_appFolderInfo.TempFolder, "readarr_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("readarr.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);
}
public string GetBackupFolder(BackupType backupType)
{
return Path.Combine(GetBackupFolder(), backupType.ToString().ToLower());
}
private void Cleanup()
{
if (_diskProvider.FolderExists(_backupTempFolder))
{
_diskProvider.EmptyFolder(_backupTempFolder);
}
}
private void BackupDatabase()
{
if (_maindDb.DatabaseType == DatabaseType.SQLite)
{
_logger.ProgressDebug("Backing up database");
_makeDatabaseBackup.BackupDatabase(_maindDb, _backupTempFolder);
}
}
private void BackupConfigFile()
{
_logger.ProgressDebug("Backing up config.xml");
var configFile = _appFolderInfo.GetConfigPath();
var tempConfigFile = Path.Combine(_backupTempFolder, Path.GetFileName(configFile));
_diskTransferService.TransferFile(configFile, tempConfigFile, TransferMode.Copy);
}
private void CreateVersionInfo()
{
var builder = new StringBuilder();
builder.AppendLine(BuildInfo.Version.ToString());
}
private void CleanupOldBackups(BackupType backupType)
{
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(retention) < DateTime.UtcNow)
{
_logger.Debug("Deleting old backup file: {0}", file);
_diskProvider.DeleteFile(file);
}
}
_logger.Debug("Finished cleaning up old backup files");
}
private IEnumerable<string> GetBackupFiles(string path)
{
var files = _diskProvider.GetFiles(path, SearchOption.TopDirectoryOnly);
return files.Where(f => BackupFileRegex.IsMatch(f));
}
public void Execute(BackupCommand message)
{
Backup(message.Type);
}
}
}