From f09ef025c5fee7a3c44439eccd355d224e47c2c1 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sat, 28 Oct 2017 23:54:26 -0400 Subject: [PATCH] Fixed: Improved database backup journal handling --- src/NzbDrone.Core/Backup/BackupService.cs | 18 ++---- .../Backup/MakeDatabaseBackup.cs | 64 +++++++++++++++++++ src/NzbDrone.Core/Datastore/Database.cs | 5 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + 4 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 src/NzbDrone.Core/Backup/MakeDatabaseBackup.cs diff --git a/src/NzbDrone.Core/Backup/BackupService.cs b/src/NzbDrone.Core/Backup/BackupService.cs index e00a06cbd..8728e7dc1 100644 --- a/src/NzbDrone.Core/Backup/BackupService.cs +++ b/src/NzbDrone.Core/Backup/BackupService.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Data; +using System.Data.SQLite; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -25,6 +26,7 @@ namespace NzbDrone.Core.Backup public class BackupService : IBackupService, IExecute { private readonly IMainDatabase _maindDb; + private readonly IMakeDatabaseBackup _makeDatabaseBackup; private readonly IDiskTransferService _diskTransferService; private readonly IDiskProvider _diskProvider; private readonly IAppFolderInfo _appFolderInfo; @@ -36,6 +38,7 @@ namespace NzbDrone.Core.Backup private static readonly Regex BackupFileRegex = new Regex(@"nzbdrone_backup_[._0-9]+\.zip", RegexOptions.Compiled | RegexOptions.IgnoreCase); public BackupService(IMainDatabase maindDb, + IMakeDatabaseBackup makeDatabaseBackup, IDiskTransferService diskTransferService, IDiskProvider diskProvider, IAppFolderInfo appFolderInfo, @@ -43,6 +46,7 @@ namespace NzbDrone.Core.Backup Logger logger) { _maindDb = maindDb; + _makeDatabaseBackup = makeDatabaseBackup; _diskTransferService = diskTransferService; _diskProvider = diskProvider; _appFolderInfo = appFolderInfo; @@ -111,17 +115,7 @@ namespace NzbDrone.Core.Backup { _logger.ProgressDebug("Backing up database"); - using (var unitOfWork = new UnitOfWork(() => _maindDb.GetDataMapper())) - { - unitOfWork.BeginTransaction(IsolationLevel.Serializable); - - var databaseFile = _appFolderInfo.GetDatabase(); - var tempDatabaseFile = Path.Combine(_backupTempFolder, Path.GetFileName(databaseFile)); - - _diskTransferService.TransferFile(databaseFile, tempDatabaseFile, TransferMode.Copy); - - unitOfWork.Commit(); - } + _makeDatabaseBackup.BackupDatabase(_maindDb, _backupTempFolder); } private void BackupConfigFile() diff --git a/src/NzbDrone.Core/Backup/MakeDatabaseBackup.cs b/src/NzbDrone.Core/Backup/MakeDatabaseBackup.cs new file mode 100644 index 000000000..3681b2afe --- /dev/null +++ b/src/NzbDrone.Core/Backup/MakeDatabaseBackup.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Data.SQLite; +using System.IO; +using System.Linq; +using System.Text; +using NLog; +using NzbDrone.Core.Datastore; +using System.Data; + +namespace NzbDrone.Core.Backup +{ + public interface IMakeDatabaseBackup + { + void BackupDatabase(IDatabase database, string targetDirectory); + } + + public class MakeDatabaseBackup : IMakeDatabaseBackup + { + private readonly Logger _logger; + + public MakeDatabaseBackup(Logger logger) + { + _logger = logger; + } + + public void BackupDatabase(IDatabase database, string targetDirectory) + { + var sourceConnectionString = database.GetDataMapper().ConnectionString; + var backupConnectionStringBuilder = new SQLiteConnectionStringBuilder(sourceConnectionString); + + backupConnectionStringBuilder.DataSource = Path.Combine(targetDirectory, Path.GetFileName(backupConnectionStringBuilder.DataSource)); + + using (var sourceConnection = (SQLiteConnection)SQLiteFactory.Instance.CreateConnection()) + using (var backupConnection = (SQLiteConnection)SQLiteFactory.Instance.CreateConnection()) + { + sourceConnection.ConnectionString = sourceConnectionString; + backupConnection.ConnectionString = backupConnectionStringBuilder.ToString(); + + sourceConnection.Open(); + backupConnection.Open(); + + sourceConnection.BackupDatabase(backupConnection, "main", "main", -1, null, 500); + + // Make sure there are no lingering connections so the wal gets truncated. + SQLiteConnection.ClearAllPools(); + } + + var backupWalPath = backupConnectionStringBuilder.DataSource + "-wal"; + if (backupConnectionStringBuilder.JournalMode == SQLiteJournalModeEnum.Wal && !File.Exists(backupWalPath)) + { + // Make sure the wal gets created in the backup so users are less likely to make an error during restore. + File.WriteAllBytes(backupWalPath, new byte[0]); + } + + var backupJournalPath = backupConnectionStringBuilder.DataSource + "-journal"; + if (backupConnectionStringBuilder.JournalMode != SQLiteJournalModeEnum.Wal && !File.Exists(backupJournalPath)) + { + // Make sure the journal gets created in the backup so users are less likely to make an error during restore. + File.WriteAllBytes(backupJournalPath, new byte[0]); + } + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Database.cs b/src/NzbDrone.Core/Datastore/Database.cs index c4e59f983..8d60ec009 100644 --- a/src/NzbDrone.Core/Datastore/Database.cs +++ b/src/NzbDrone.Core/Datastore/Database.cs @@ -1,4 +1,4 @@ -using System; +using System; using Marr.Data; using NLog; using NzbDrone.Common.Instrumentation; @@ -25,7 +25,6 @@ namespace NzbDrone.Core.Datastore _datamapperFactory = datamapperFactory; } - public IDataMapper GetDataMapper() { return _datamapperFactory(); @@ -54,4 +53,4 @@ namespace NzbDrone.Core.Datastore } } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index c0b454e1e..9c82d21bf 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -127,6 +127,7 @@ +