From 32efa4b845f8a88988134d2d7b44c52b38444c4b Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Thu, 19 Jun 2025 09:47:03 +0200 Subject: [PATCH] Updated all of the handler calls to database methods to forward their cancellation token --- .../Main/Database/LocalListDatabase.cs | 13 +- .../Library/Main/Operation/BackupHandler.cs | 74 ++--- .../Library/Main/Operation/CompactHandler.cs | 67 ++--- .../Main/Operation/CreateBugReportHandler.cs | 6 +- .../Library/Main/Operation/DeleteHandler.cs | 21 +- .../Main/Operation/FilelistProcessor.cs | 72 +++-- .../Library/Main/Operation/ListAffected.cs | 18 +- .../Main/Operation/ListBrokenFilesHandler.cs | 36 +-- .../Main/Operation/ListChangesHandler.cs | 24 +- .../Main/Operation/ListControlFilesHandler.cs | 4 +- .../Main/Operation/ListFileVersionsHandler.cs | 8 +- .../Main/Operation/ListFilesHandler.cs | 28 +- .../Main/Operation/ListFilesetsHandler.cs | 7 +- .../Main/Operation/ListFolderHandler.cs | 16 +- .../Main/Operation/PurgeBrokenFilesHandler.cs | 45 +-- .../Main/Operation/PurgeFilesHandler.cs | 62 ++--- .../Main/Operation/RecreateDatabaseHandler.cs | 161 ++++++----- .../Library/Main/Operation/RepairHandler.cs | 258 +++++++++--------- .../Operation/RestoreControlFilesHandler.cs | 11 +- .../Library/Main/Operation/RestoreHandler.cs | 126 ++++----- .../Main/Operation/SearchEntriesHandler.cs | 8 +- .../Library/Main/Operation/TestHandler.cs | 131 ++++----- .../Library/Main/Operation/VacuumHandler.cs | 4 +- Duplicati/Library/Main/Utility.cs | 22 +- 24 files changed, 638 insertions(+), 584 deletions(-) diff --git a/Duplicati/Library/Main/Database/LocalListDatabase.cs b/Duplicati/Library/Main/Database/LocalListDatabase.cs index ade62473d..96d311f64 100644 --- a/Duplicati/Library/Main/Database/LocalListDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListDatabase.cs @@ -71,7 +71,9 @@ namespace Duplicati.Library.Main.Database /// /// Asynchronously retrieves the sizes of the file versions. /// - IAsyncEnumerable Sizes(); + /// A cancellation token to cancel the operation. + /// An asynchronous enumerable of file sizes. + IAsyncEnumerable Sizes(CancellationToken token); } /// @@ -301,12 +303,13 @@ namespace Duplicati.Library.Main.Database More = true; } - public async IAsyncEnumerable Sizes() + /// + public async IAsyncEnumerable Sizes([EnumeratorCancellation] CancellationToken token) { while (More && Path == m_reader.ConvertValueToString(0)) { yield return m_reader.ConvertValueToInt64(1, -1); - More = await m_reader.ReadAsync().ConfigureAwait(false); + More = await m_reader.ReadAsync(token).ConfigureAwait(false); } } } @@ -316,8 +319,10 @@ namespace Duplicati.Library.Main.Database /// private class FileversionFixed : IFileversion { + /// public string? Path { get; internal set; } - public IAsyncEnumerable Sizes() => AsyncEnumerable.Empty(); + /// + public IAsyncEnumerable Sizes(CancellationToken token) => AsyncEnumerable.Empty(); } /// diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs index b00e3207a..ab5cbf7cf 100644 --- a/Duplicati/Library/Main/Operation/BackupHandler.cs +++ b/Duplicati/Library/Main/Operation/BackupHandler.cs @@ -214,7 +214,7 @@ namespace Duplicati.Library.Main.Operation // all of the local database classes handle this), as the constructor // is not async and shouldn't be executing "heavy" code, such as // database queries. - var journalData = m_database.GetChangeJournalData(lastfilesetid).ToEnumerable(); + var journalData = m_database.GetChangeJournalData(lastfilesetid, m_taskReader.ProgressToken).ToEnumerable(); var service = new UsnJournalService(fileProvider.SnapshotService, filter, m_options.FileAttributeFilter, m_options.SkipFilesLargerThan, journalData, cancellationTokenSource.Token); @@ -300,9 +300,9 @@ namespace Duplicati.Library.Main.Operation { try { - database = await LocalBackupDatabase.CreateAsync(options.Dbpath, options) + database = await LocalBackupDatabase.CreateAsync(options.Dbpath, options, result.TaskControl.ProgressToken) .ConfigureAwait(false); - var lastFilestTime = await database.FilesetTimes() + var lastFilestTime = await database.FilesetTimes(result.TaskControl.ProgressToken) .Select(x => x.Value) .Append(DateTime.UnixEpoch) .Select(Library.Utility.Utility.NormalizeDateTimeToEpochSeconds) @@ -319,40 +319,40 @@ namespace Duplicati.Library.Main.Operation Log.WriteVerboseMessage(LOGTAG, "DatabaseTimestampError", "The database has a timestamp of {0}, but the last fileset has a timestamp of {1}. Sleeping for {2} seconds to allow the clock to catch up.", database.OperationTimestamp.ToLocalTime(), Library.Utility.Utility.EPOCH.AddSeconds(lastFilestTime).ToLocalTime(), sleepSeconds); Thread.Sleep(TimeSpan.FromSeconds(sleepSeconds)); database.Dispose(); - database = await LocalBackupDatabase.CreateAsync(options.Dbpath, options) + database = await LocalBackupDatabase.CreateAsync(options.Dbpath, options, result.TaskControl.ProgressToken) .ConfigureAwait(false); } result.Dryrun = options.Dryrun; // Check the database integrity - await Utility.UpdateOptionsFromDb(database, options) + await Utility.UpdateOptionsFromDb(database, options, result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(database, options) + await Utility.VerifyOptionsAndUpdateDatabase(database, options, result.TaskControl.ProgressToken) .ConfigureAwait(false); var probe_path = await database - .GetFirstPath() + .GetFirstPath(result.TaskControl.ProgressToken) .ConfigureAwait(false); if (probe_path != null && Util.GuessDirSeparator(probe_path) != Util.DirectorySeparatorString) throw new UserInformationException(string.Format("The backup contains files that belong to another operating system. Proceeding with a backup would cause the database to contain paths from two different operation systems, which is not supported. To proceed without losing remote data, delete all filesets and make sure the --{0} option is set, then run the backup again to re-use the existing data on the remote store.", "no-auto-compact"), "CrossOsDatabaseReuseNotSupported"); - if (await database.PartiallyRecreated().ConfigureAwait(false)) + if (await database.PartiallyRecreated(result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The database was only partially recreated. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsPartiallyRecreated"); - if (await database.RepairInProgress().ConfigureAwait(false)) + if (await database.RepairInProgress(result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The database was attempted repaired, but the repair did not complete. This database may be incomplete and the backup process cannot continue. You may delete the local database and attempt to repair it again.", "DatabaseRepairInProgress"); // Make sure the database is sane await database - .VerifyConsistency(options.Blocksize, options.BlockhashSize, !options.DisableFilelistConsistencyChecks) + .VerifyConsistency(options.Blocksize, options.BlockhashSize, !options.DisableFilelistConsistencyChecks, result.TaskControl.ProgressToken) .ConfigureAwait(false); // Guard the last filelist if (!options.DisableSyntheticFilelist) lastTempFilelist = await database - .GetLastIncompleteFilesetVolume() + .GetLastIncompleteFilesetVolume(result.TaskControl.ProgressToken) .ConfigureAwait(false); try @@ -364,8 +364,8 @@ namespace Duplicati.Library.Main.Operation } else { - await FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: false, verifyMode: FilelistProcessor.VerifyMode.VerifyAndClean).ConfigureAwait(false); - var updatedLastTemp = string.IsNullOrWhiteSpace(lastTempFilelist.Name) ? default : await database.GetRemoteVolume(lastTempFilelist.Name).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: false, verifyMode: FilelistProcessor.VerifyMode.VerifyAndClean, result.TaskControl.ProgressToken).ConfigureAwait(false); + var updatedLastTemp = string.IsNullOrWhiteSpace(lastTempFilelist.Name) ? default : await database.GetRemoteVolume(lastTempFilelist.Name, result.TaskControl.ProgressToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(updatedLastTemp.Name) && updatedLastTemp.State == RemoteVolumeState.Deleting) { // The last temporary filelist was emptied, and scheduled for deletion, so we need to delete it @@ -381,7 +381,7 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(database, result.TaskControl.ProgressToken).ConfigureAwait(false); // In case the backend delete failed, manually set the state to deleted await database - .UpdateRemoteVolume(updatedLastTemp.Name, RemoteVolumeState.Deleted, updatedLastTemp.Size, updatedLastTemp.Hash, false, TimeSpan.FromHours(2), null) + .UpdateRemoteVolume(updatedLastTemp.Name, RemoteVolumeState.Deleted, updatedLastTemp.Size, updatedLastTemp.Hash, false, TimeSpan.FromHours(2), null, result.TaskControl.ProgressToken) .ConfigureAwait(false); // The last temporary filelist is no more lastTempFilelist = default; @@ -402,18 +402,18 @@ namespace Duplicati.Library.Main.Operation .ConfigureAwait(false); // Re-open the database and backend manager - database = await LocalBackupDatabase.CreateAsync(options.Dbpath, options) + database = await LocalBackupDatabase.CreateAsync(options.Dbpath, options, result.TaskControl.ProgressToken) .ConfigureAwait(false); Log.WriteInformationMessage(LOGTAG, "BackendCleanupFinished", "Backend cleanup finished, retrying verification"); - await FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, result.TaskControl.ProgressToken).ConfigureAwait(false); } else throw; } // Return the updated version of the fileset, in case it was actually complete - return new PreBackupVerifyResult(database, await database.GetLastIncompleteFilesetVolume().ConfigureAwait(false)); + return new PreBackupVerifyResult(database, await database.GetLastIncompleteFilesetVolume(result.TaskControl.ProgressToken).ConfigureAwait(false)); } catch { @@ -540,7 +540,7 @@ namespace Duplicati.Library.Main.Operation { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Delete); m_result.DeleteResults = new DeleteResults(m_result); - await using var db = await LocalDeleteDatabase.CreateAsync(m_database).ConfigureAwait(false); + await using var db = await LocalDeleteDatabase.CreateAsync(m_database, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await new DeleteHandler(m_options, (DeleteResults)m_result.DeleteResults) .DoRunAsync(db, true, currentIsSmall, backendManager) .ConfigureAwait(false); @@ -550,7 +550,7 @@ namespace Duplicati.Library.Main.Operation { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Compact); m_result.CompactResults = new CompactResults(m_result); - await using var db = await LocalDeleteDatabase.CreateAsync(m_database).ConfigureAwait(false); + await using var db = await LocalDeleteDatabase.CreateAsync(m_database, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await new CompactHandler(m_options, (CompactResults)m_result.CompactResults) .DoCompactAsync(db, true, backendManager) .ConfigureAwait(false); @@ -561,7 +561,7 @@ namespace Duplicati.Library.Main.Operation { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PostBackupVerify); using (new Logging.Timer(LOGTAG, "AfterBackupVerify", "AfterBackupVerify")) - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, m_database, m_result.BackendWriter, [currentFilelistVolume], [previousTemporaryFilelist], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, m_database, m_result.BackendWriter, [currentFilelistVolume], [previousTemporaryFilelist], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await backendManager .WaitForEmptyAsync(m_database, m_taskReader.ProgressToken) @@ -569,7 +569,7 @@ namespace Duplicati.Library.Main.Operation // Calculate the number of samples to test, using the largest number of file of a given type long remoteVolumeCount = await m_database - .GetRemoteVolumes() + .GetRemoteVolumes(m_result.TaskControl.ProgressToken) .Where(x => x.State == RemoteVolumeState.Verified) .GroupBy(x => x.Type) .SelectAwait(async x => await x.LongCountAsync()) @@ -582,7 +582,7 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PostBackupTest); m_result.TestResults = new TestResults(m_result); - await using var testdb = await LocalTestDatabase.CreateAsync(m_database).ConfigureAwait(false); + await using var testdb = await LocalTestDatabase.CreateAsync(m_database, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await new TestHandler(m_options, (TestResults)m_result.TestResults) .DoRunAsync(samplesToTest, testdb, backendManager) .ConfigureAwait(false); @@ -597,33 +597,33 @@ namespace Duplicati.Library.Main.Operation if (result.BackendWriter != null) { result.BackendWriter.KnownFileCount = await database - .GetRemoteVolumes() - .CountAsync() + .GetRemoteVolumes(cancelToken) + .CountAsync(cancelToken) .ConfigureAwait(false); result.BackendWriter.KnownFilesets = await database - .GetRemoteVolumes() + .GetRemoteVolumes(cancelToken) .CountAsync(x => x.Type == RemoteVolumeType.Files) .ConfigureAwait(false); result.BackendWriter.KnownFileSize = await database - .GetRemoteVolumes() + .GetRemoteVolumes(cancelToken) .Select(x => Math.Max(0, x.Size)) - .SumAsync() + .SumAsync(cancelToken) .ConfigureAwait(false); result.BackendWriter.UnknownFileCount = 0; result.BackendWriter.UnknownFileSize = 0; result.BackendWriter.BackupListCount = await database - .FilesetTimes() - .CountAsync() + .FilesetTimes(cancelToken) + .CountAsync(cancelToken) .ConfigureAwait(false); result.BackendWriter.LastBackupDate = ( await database - .FilesetTimes() - .FirstOrDefaultAsync() + .FilesetTimes(cancelToken) + .FirstOrDefaultAsync(cancelToken) .ConfigureAwait(false) ) .Value.ToLocalTime(); @@ -711,7 +711,7 @@ namespace Duplicati.Library.Main.Operation // If we crash now, there is a chance we leave behind partial remote files if (!m_options.Dryrun) await database - .TerminatedWithActiveUploads(true) + .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, true) .ConfigureAwait(false); await using (m_database = database) @@ -731,7 +731,7 @@ namespace Duplicati.Library.Main.Operation // Grab the previous backup ID, if any var prevfileset = await m_database - .FilesetTimes() + .FilesetTimes(m_result.TaskControl.ProgressToken) .FirstOrDefaultAsync() .ConfigureAwait(false); @@ -768,7 +768,7 @@ namespace Duplicati.Library.Main.Operation if (m_options.DisableFileScanner) { var d = await m_database - .GetLastBackupFileCountAndSize() + .GetLastBackupFileCountAndSize(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); m_result.OperationProgressUpdater.UpdatefileCount(d.Item1, d.Item2, true); @@ -815,7 +815,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) await database - .TerminatedWithActiveUploads(false) + .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, false) .ConfigureAwait(false); // Make sure we have the database up-to-date @@ -826,7 +826,7 @@ namespace Duplicati.Library.Main.Operation if (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { var lastVolumeSize = await m_database - .GetLastWrittenDBlockVolumeSize() + .GetLastWrittenDBlockVolumeSize(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await CompactIfRequired(backendManager, lastVolumeSize) @@ -836,7 +836,7 @@ namespace Duplicati.Library.Main.Operation if (m_options.UploadVerificationFile && await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_VerificationUpload); - await FilelistProcessor.UploadVerificationFile(backendManager, m_options, m_database) + await FilelistProcessor.UploadVerificationFile(backendManager, m_options, m_database, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Operation/CompactHandler.cs b/Duplicati/Library/Main/Operation/CompactHandler.cs index 781ca99f2..5374947fc 100644 --- a/Duplicati/Library/Main/Operation/CompactHandler.cs +++ b/Duplicati/Library/Main/Operation/CompactHandler.cs @@ -52,17 +52,17 @@ namespace Duplicati.Library.Main.Operation if (!System.IO.File.Exists(m_options.Dbpath)) throw new Exception(string.Format("Database file does not exist: {0}", m_options.Dbpath)); - await using (var db = await LocalDeleteDatabase.CreateAsync(m_options.Dbpath, "Compact", m_options.SqlitePageCache).ConfigureAwait(false)) + await using (var db = await LocalDeleteDatabase.CreateAsync(m_options.Dbpath, "Compact", m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await Utility.UpdateOptionsFromDb(db, m_options) + await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var changed = await DoCompactAsync(db, false, backendManager).ConfigureAwait(false); if (changed && m_options.UploadVerificationFile) - await FilelistProcessor.UploadVerificationFile(backendManager, m_options, db); + await FilelistProcessor.UploadVerificationFile(backendManager, m_options, db, m_result.TaskControl.ProgressToken); if (!m_options.Dryrun) { @@ -72,7 +72,8 @@ namespace Duplicati.Library.Main.Operation if (changed) { - await db.WriteResults(m_result).ConfigureAwait(false); + await db.WriteResults(m_result, m_result.TaskControl.ProgressToken) + .ConfigureAwait(false); if (m_options.AutoVacuum) { m_result.VacuumResults = new VacuumResults(m_result); @@ -86,7 +87,7 @@ namespace Duplicati.Library.Main.Operation internal async Task DoCompactAsync(LocalDeleteDatabase db, bool hasVerifiedBackend, IBackendManager backendManager) { var report = await db - .GetCompactReport(m_options.VolumeSize, m_options.Threshold, m_options.SmallFileSize, m_options.SmallFileMaxCount) + .GetCompactReport(m_options.VolumeSize, m_options.Threshold, m_options.SmallFileSize, m_options.SmallFileMaxCount, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); report.ReportCompactData(); @@ -95,11 +96,11 @@ namespace Duplicati.Library.Main.Operation { // Workaround where we allow a running backendmanager to be used if (!hasVerifiedBackend) - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, true, FilelistProcessor.VerifyMode.VerifyStrict).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, true, FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); var newvol = new BlockVolumeWriter(m_options); newvol.VolumeID = await db - .RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary) + .RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); IndexVolumeWriter newvolindex = null; @@ -107,20 +108,20 @@ namespace Duplicati.Library.Main.Operation { newvolindex = new IndexVolumeWriter(m_options); newvolindex.VolumeID = await db - .RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary) + .RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db - .AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID) + .AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } var blocksInVolume = 0L; var buffer = new byte[m_options.Blocksize]; var remoteList = await db - .GetRemoteVolumes() + .GetRemoteVolumes(m_result.TaskControl.ProgressToken) .Where(n => n.State == RemoteVolumeState.Uploaded || n.State == RemoteVolumeState.Verified) - .ToArrayAsync() + .ToArrayAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); //These are for bookkeeping @@ -152,7 +153,7 @@ namespace Duplicati.Library.Main.Operation // If we crash now, we may leave partial files if (!m_options.Dryrun) await db - .TerminatedWithActiveUploads(true) + .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, true) .ConfigureAwait(false); newvolindex?.StartVolume(newvol.RemoteFilename); @@ -167,7 +168,7 @@ namespace Duplicati.Library.Main.Operation .ToList(); } - using (var q = await db.CreateBlockQueryHelper().ConfigureAwait(false)) + using (var q = await db.CreateBlockQueryHelper(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { await foreach (var (tmpfile, hash, size, name) in backendManager.GetFilesOverlappedAsync(volumesToDownload, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { @@ -182,7 +183,7 @@ namespace Duplicati.Library.Main.Operation downloadedVolumes.Add(new KeyValuePair(entry.Name, entry.Size)); var volumeid = await db - .GetRemoteVolumeID(entry.Name) + .GetRemoteVolumeID(entry.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var inst = VolumeBase.ParseFilename(entry.Name); @@ -190,7 +191,7 @@ namespace Duplicati.Library.Main.Operation { foreach (var e in f.Blocks) { - if (await q.UseBlock(e.Key, e.Value, volumeid).ConfigureAwait(false)) + if (await q.UseBlock(e.Key, e.Value, volumeid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { //TODO: How do we get the compression hint? Reverse query for filename in db? var s = f.ReadBlock(e.Key, buffer); @@ -205,7 +206,7 @@ namespace Duplicati.Library.Main.Operation newvolindex.AddBlock(e.Key, e.Value); await db - .RegisterDuplicatedBlock(e.Key, e.Value, newvol.VolumeID) + .RegisterDuplicatedBlock(e.Key, e.Value, newvol.VolumeID, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); blocksInVolume++; @@ -217,18 +218,18 @@ namespace Duplicati.Library.Main.Operation newvol = new BlockVolumeWriter(m_options); newvol.VolumeID = await db - .RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary) + .RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (m_options.IndexfilePolicy != Options.IndexFileStrategy.None) { newvolindex = new IndexVolumeWriter(m_options); newvolindex.VolumeID = await db - .RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary) + .RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db - .AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID) + .AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); newvolindex.StartVolume(newvol.RemoteFilename); @@ -242,7 +243,7 @@ namespace Duplicati.Library.Main.Operation // Commit as we have uploaded a volume if (!m_options.Dryrun) await db.Transaction - .CommitAsync("CommitCompact") + .CommitAsync("CommitCompact", true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (deleteableVolumes.Any()) @@ -268,13 +269,13 @@ namespace Duplicati.Library.Main.Operation else { await db - .RemoveRemoteVolume(newvol.RemoteFilename) + .RemoveRemoteVolume(newvol.RemoteFilename, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (newvolindex != null) { await db - .RemoveRemoteVolume(newvolindex.RemoteFilename) + .RemoveRemoteVolume(newvolindex.RemoteFilename, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); newvolindex.FinishVolume(null, 0); @@ -285,7 +286,7 @@ namespace Duplicati.Library.Main.Operation // The remainder of the operation cannot leave partial files if (!m_options.Dryrun) await db - .TerminatedWithActiveUploads(false) + .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, false) .ConfigureAwait(false); } else @@ -355,27 +356,27 @@ namespace Duplicati.Library.Main.Operation { // Find volumes that can be deleted var remoteFilesToRemove = await db - .ReOrderDeleteableVolumes(deleteableVolumes) - .ToListAsync() + .ReOrderDeleteableVolumes(deleteableVolumes, cancellationToken) + .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); // Make sure we do not re-assign blocks to any of the volumes we are about to delete var toRemoveVolumeIds = await db - .GetRemoteVolumeIDs(remoteFilesToRemove.Select(x => x.Name)) + .GetRemoteVolumeIDs(remoteFilesToRemove.Select(x => x.Name), cancellationToken) .Select(x => x.Value) .Distinct() - .ToListAsync() + .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); // Mark all volumes and relevant index files as disposable foreach (var f in remoteFilesToRemove) { await db - .PrepareForDelete(f.Name, toRemoveVolumeIds) + .PrepareForDelete(f.Name, toRemoveVolumeIds, cancellationToken) .ConfigureAwait(false); await db - .UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash) + .UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, cancellationToken) .ConfigureAwait(false); } @@ -384,7 +385,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) await db.Transaction - .CommitAsync("CommitDelete") + .CommitAsync("CommitDelete", token: cancellationToken) .ConfigureAwait(false); await foreach (var d in PerformDelete(backend, remoteFilesToRemove, cancellationToken).ConfigureAwait(false)) @@ -397,7 +398,7 @@ namespace Duplicati.Library.Main.Operation if (newvolindex != null && m_options.IndexfilePolicy == Options.IndexFileStrategy.Full) indexVolumeFinished = async () => { - await foreach (var blocklist in db.GetBlocklists(newvol.VolumeID, m_options.Blocksize, m_options.BlockhashSize).ConfigureAwait(false)) + await foreach (var blocklist in db.GetBlocklists(newvol.VolumeID, m_options.Blocksize, m_options.BlockhashSize, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) newvolindex.WriteBlocklist(blocklist.Hash, blocklist.Buffer, 0, blocklist.Size); }; @@ -409,7 +410,7 @@ namespace Duplicati.Library.Main.Operation // because the transaction is not thread-safe, and shared with the upload await backendManager.WaitForEmptyAsync(db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await db - .UpdateRemoteVolume(newvol.RemoteFilename, RemoteVolumeState.Uploading, -1, null) + .UpdateRemoteVolume(newvol.RemoteFilename, RemoteVolumeState.Uploading, -1, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // TODO: The upload here does not flush the database messages, diff --git a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs index 69492ae26..9ff8ea208 100644 --- a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs +++ b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs @@ -68,11 +68,11 @@ namespace Duplicati.Library.Main.Operation using (var tmp = new TempFile()) { File.Copy(m_options.Dbpath, tmp, true); - await using (var db = await LocalBugReportDatabase.CreateAsync(tmp, m_options.SqlitePageCache).ConfigureAwait(false)) + await using (var db = await LocalBugReportDatabase.CreateAsync(tmp, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await db.Fix().ConfigureAwait(false); + await db.Fix(m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (m_options.AutoVacuum) - await db.Vacuum().ConfigureAwait(false); + await db.Vacuum(m_result.TaskControl.ProgressToken).ConfigureAwait(false); } // Apply zip64 option if not already set diff --git a/Duplicati/Library/Main/Operation/DeleteHandler.cs b/Duplicati/Library/Main/Operation/DeleteHandler.cs index 19498295f..260c33891 100644 --- a/Duplicati/Library/Main/Operation/DeleteHandler.cs +++ b/Duplicati/Library/Main/Operation/DeleteHandler.cs @@ -51,11 +51,11 @@ namespace Duplicati.Library.Main.Operation if (!System.IO.File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseFileMissing"); - await using var db = await LocalDeleteDatabase.CreateAsync(m_options.Dbpath, "Delete", m_options.SqlitePageCache).ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options) + await using var db = await LocalDeleteDatabase.CreateAsync(m_options.Dbpath, "Delete", m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await DoRunAsync(db, false, false, backendManager).ConfigureAwait(false); @@ -69,13 +69,13 @@ namespace Duplicati.Library.Main.Operation public async Task DoRunAsync(LocalDeleteDatabase db, bool hasVerifiedBackend, bool forceCompact, IBackendManager backendManager) { if (!hasVerifiedBackend) - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); // We collapse the async enumerablo into a array to avoid multiple // enumerations (and thus multiple database queries) var filesets = await db - .FilesetsWithBackupVersion() - .ToArrayAsync() + .FilesetsWithBackupVersion(m_result.TaskControl.ProgressToken) + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); List versionsToDelete = @@ -109,19 +109,20 @@ namespace Duplicati.Library.Main.Operation .DropFilesetsFromTable( versionsToDelete .Select(x => x.Time) - .ToArray() + .ToArray(), + m_result.TaskControl.ProgressToken ) - .ToArrayAsync() + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); foreach (var f in lst) await db - .UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null) + .UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (!m_options.Dryrun) await db.Transaction - .CommitAsync("CommitBeforeDelete") + .CommitAsync("CommitBeforeDelete", true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); foreach (var f in lst) diff --git a/Duplicati/Library/Main/Operation/FilelistProcessor.cs b/Duplicati/Library/Main/Operation/FilelistProcessor.cs index 39d0e8134..5bf01708b 100644 --- a/Duplicati/Library/Main/Operation/FilelistProcessor.cs +++ b/Duplicati/Library/Main/Operation/FilelistProcessor.cs @@ -71,7 +71,7 @@ namespace Duplicati.Library.Main.Operation /// A task that completes when the verification is done. public static async Task VerifyLocalList(IBackendManager backendManager, LocalDatabase database, CancellationToken cancellationToken) { - var locallist = database.GetRemoteVolumes(); + var locallist = database.GetRemoteVolumes(cancellationToken); await foreach (var i in locallist.ConfigureAwait(false)) { switch (i.State) @@ -115,20 +115,21 @@ namespace Duplicati.Library.Main.Operation /// The backend writer to use. /// True if only the latest volumes should be verified. /// The mode to use for verification. + /// The cancellation token to use. /// An awaitable task. - public static async Task VerifyRemoteList(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter backendWriter, bool latestVolumesOnly, VerifyMode verifyMode) + public static async Task VerifyRemoteList(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter backendWriter, bool latestVolumesOnly, VerifyMode verifyMode, CancellationToken cancellationToken) { if (!options.NoBackendverification) { LocalBackupDatabase backupDatabase = - await LocalBackupDatabase.CreateAsync(database, options) + await LocalBackupDatabase.CreateAsync(database, options, null, cancellationToken) .ConfigureAwait(false); IEnumerable protectedFiles = await backupDatabase - .GetTemporaryFilelistVolumeNames(latestVolumesOnly) + .GetTemporaryFilelistVolumeNames(latestVolumesOnly, cancellationToken) .ConfigureAwait(false); - await VerifyRemoteList(backend, options, database, backendWriter, protectedFiles, [], logErrors: true, verifyMode: verifyMode).ConfigureAwait(false); + await VerifyRemoteList(backend, options, database, backendWriter, protectedFiles, [], logErrors: true, verifyMode: verifyMode, cancellationToken).ConfigureAwait(false); } } @@ -144,10 +145,11 @@ namespace Duplicati.Library.Main.Operation /// Filenames that should be exempted from strict verification. /// Disable the logging of errors to prevent spamming the log; exceptions will be thrown regardless. /// The mode to use for verification. + /// The cancellation token to use. /// An awaitable task. - public static async Task VerifyRemoteList(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, bool logErrors, VerifyMode verifyMode) + public static async Task VerifyRemoteList(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, bool logErrors, VerifyMode verifyMode, CancellationToken cancellationToken) { - var tp = await RemoteListAnalysis(backend, options, database, log, protectedFiles, strictExcemptFiles, verifyMode).ConfigureAwait(false); + var tp = await RemoteListAnalysis(backend, options, database, log, protectedFiles, strictExcemptFiles, verifyMode, cancellationToken).ConfigureAwait(false); long extraCount = 0; long missingCount = 0; @@ -213,10 +215,17 @@ namespace Duplicati.Library.Main.Operation /// The verification file. /// The database instance /// The stream to write to - public static void CreateVerificationFile(LocalDatabase db, System.IO.StreamWriter stream) + /// The cancellation token to use. + public static void CreateVerificationFile(LocalDatabase db, System.IO.StreamWriter stream, CancellationToken cancellationToken) { var s = new Newtonsoft.Json.JsonSerializer(); - s.Serialize(stream, db.GetRemoteVolumes().Where(x => x.State != RemoteVolumeState.Temporary).ToEnumerable().Cast().ToArray()); + s.Serialize(stream, + db.GetRemoteVolumes(cancellationToken) + .Where(x => x.State != RemoteVolumeState.Temporary) + .ToEnumerable() + .Cast() + .ToArray() + ); } /// @@ -225,14 +234,15 @@ namespace Duplicati.Library.Main.Operation /// The backend manager instance to use. /// The options to use. /// The attached database. + /// The cancellation token to use. /// A task that completes when the upload is done. - public static async Task UploadVerificationFile(IBackendManager backendManager, Options options, LocalDatabase db) + public static async Task UploadVerificationFile(IBackendManager backendManager, Options options, LocalDatabase db, CancellationToken cancellationToken) { using (var tempfile = new Library.Utility.TempFile()) { var remotename = options.Prefix + "-verification.json"; await using (var stream = new System.IO.StreamWriter(tempfile, false, System.Text.Encoding.UTF8)) - CreateVerificationFile(db, stream); + CreateVerificationFile(db, stream, cancellationToken); if (options.Dryrun) { @@ -240,8 +250,8 @@ namespace Duplicati.Library.Main.Operation } else { - await backendManager.PutVerificationFileAsync(remotename, tempfile, CancellationToken.None).ConfigureAwait(false); - await backendManager.WaitForEmptyAsync(db, CancellationToken.None).ConfigureAwait(false); + await backendManager.PutVerificationFileAsync(remotename, tempfile, cancellationToken).ConfigureAwait(false); + await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); } } } @@ -257,18 +267,19 @@ namespace Duplicati.Library.Main.Operation /// Filenames that should be exempted from deletion. /// Filenames that should be exempted from strict verification. /// The mode to use for verification. + /// The cancellation token to use. /// A task that when awaited contains the analysis result. - public static async Task RemoteListAnalysis(IBackendManager backendManager, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, VerifyMode verifyMode) + public static async Task RemoteListAnalysis(IBackendManager backendManager, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, VerifyMode verifyMode, CancellationToken cancellationToken) { // If the last operation completed, no cleanup should be required - if (verifyMode == VerifyMode.VerifyAndClean && !await database.TerminatedWithActiveUploads().ConfigureAwait(false)) + if (verifyMode == VerifyMode.VerifyAndClean && !await database.TerminatedWithActiveUploads(cancellationToken).ConfigureAwait(false)) verifyMode = VerifyMode.VerifyStrict; // Force cleanup should set the mode to cleanup if (verifyMode == VerifyMode.VerifyAndCleanForced) verifyMode = VerifyMode.VerifyAndClean; - var rawlist = await backendManager.ListAsync(CancellationToken.None).ConfigureAwait(false); + var rawlist = await backendManager.ListAsync(cancellationToken).ConfigureAwait(false); var lookup = new Dictionary(); protectedFiles = protectedFiles ?? []; strictExcemptFiles = strictExcemptFiles ?? []; @@ -300,8 +311,8 @@ namespace Duplicati.Library.Main.Operation log.UnknownFileCount = unknownlist.Count; log.UnknownFileSize = unknownlist.Select(x => Math.Max(0, x.Size)).Sum(); log.BackupListCount = await database - .FilesetTimes() - .CountAsync() + .FilesetTimes(cancellationToken) + .CountAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); log.LastBackupDate = filesets.Count == 0 ? new DateTime(0) : filesets[0].Time.ToLocalTime(); @@ -316,17 +327,17 @@ namespace Duplicati.Library.Main.Operation var missingUploadingVolumes = new HashSet(); var protectedVolumes = new HashSet(); - await foreach (var e in database.DuplicateRemoteVolumes().ConfigureAwait(false)) + await foreach (var e in database.DuplicateRemoteVolumes(cancellationToken).ConfigureAwait(false)) { if (e.Value == RemoteVolumeState.Uploading || e.Value == RemoteVolumeState.Temporary) await database - .UnlinkRemoteVolume(e.Key, e.Value) + .UnlinkRemoteVolume(e.Key, e.Value, cancellationToken) .ConfigureAwait(false); else throw new RemoteListVerificationException(string.Format("The remote volume {0} appears in the database with state {1} and a deleted state, cannot continue", e.Key, e.Value.ToString()), "AmbiguousStateRemoteFiles"); } - var locallist = database.GetRemoteVolumes(); + var locallist = database.GetRemoteVolumes(cancellationToken); await foreach (var i in locallist.ConfigureAwait(false)) { var remoteFound = lookup.TryGetValue(i.Name, out var r); @@ -336,11 +347,11 @@ namespace Duplicati.Library.Main.Operation correctSize = true; if (archived && i.ArchiveTime <= DateTime.UnixEpoch) await database - .UpdateRemoteVolume(i.Name, i.State, i.Size, i.Hash, true, TimeSpan.Zero, true) + .UpdateRemoteVolume(i.Name, i.State, i.Size, i.Hash, true, TimeSpan.Zero, true, cancellationToken) .ConfigureAwait(false); else if (!archived && i.ArchiveTime > DateTime.UnixEpoch) await database - .UpdateRemoteVolume(i.Name, i.State, i.Size, i.Hash, true, TimeSpan.Zero, false) + .UpdateRemoteVolume(i.Name, i.State, i.Size, i.Hash, true, TimeSpan.Zero, false, cancellationToken) .ConfigureAwait(false); lookup.Remove(i.Name); @@ -397,7 +408,7 @@ namespace Duplicati.Library.Main.Operation { Logging.Log.WriteInformationMessage(LOGTAG, "PromotingCompleteFile", "Promoting uploaded complete file from {0} to {2}: {1}", i.State, i.Name, RemoteVolumeState.Uploaded); await database - .UpdateRemoteVolume(i.Name, RemoteVolumeState.Uploaded, i.Size, i.Hash) + .UpdateRemoteVolume(i.Name, RemoteVolumeState.Uploaded, i.Size, i.Hash, cancellationToken) .ConfigureAwait(false); } else if (!remoteFound) @@ -407,7 +418,7 @@ namespace Duplicati.Library.Main.Operation protectedVolumes.Add(i.Name); Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "Keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name); await database - .UpdateRemoteVolume(i.Name, RemoteVolumeState.Temporary, i.Size, i.Hash, false, new TimeSpan(0), null) + .UpdateRemoteVolume(i.Name, RemoteVolumeState.Temporary, i.Size, i.Hash, false, new TimeSpan(0), null, cancellationToken) .ConfigureAwait(false); } else if (verifyMode == VerifyMode.VerifyStrict && !strictExcemptFiles.Any(pf => pf == i.Name)) @@ -420,7 +431,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "SchedulingMissingFileForDelete", "Scheduling missing file for deletion, currently listed as {0}: {1}", i.State, i.Name); missingUploadingVolumes.Add(i.Name); await database - .UpdateRemoteVolume(i.Name, RemoteVolumeState.Deleting, i.Size, i.Hash, false, TimeSpan.FromHours(2), null) + .UpdateRemoteVolume(i.Name, RemoteVolumeState.Deleting, i.Size, i.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) .ConfigureAwait(false); } } @@ -460,7 +471,7 @@ namespace Duplicati.Library.Main.Operation missing.Add(i); else if (correctSize) await database - .UpdateRemoteVolume(i.Name, RemoteVolumeState.Verified, i.Size, i.Hash) + .UpdateRemoteVolume(i.Name, RemoteVolumeState.Verified, i.Size, i.Hash, cancellationToken) .ConfigureAwait(false); else missingHash.Add(new Tuple(r.File.Size, i)); @@ -491,17 +502,18 @@ namespace Duplicati.Library.Main.Operation await database .RemoveRemoteVolumes( missingUploadingVolumes - .Concat(temporaryAndDeletedVolumes) + .Concat(temporaryAndDeletedVolumes), + cancellationToken ) .ConfigureAwait(false); // Clear the flag after we have cleaned up if (!options.Dryrun && protectedVolumes.Count == 0) await database - .TerminatedWithActiveUploads(false) + .TerminatedWithActiveUploads(cancellationToken, false) .ConfigureAwait(false); } - else if (verifyMode == VerifyMode.VerifyOnly && await database.TerminatedWithActiveUploads().ConfigureAwait(false)) + else if (verifyMode == VerifyMode.VerifyOnly && await database.TerminatedWithActiveUploads(cancellationToken).ConfigureAwait(false)) { Logging.Log.WriteWarningMessage(LOGTAG, "ActiveUploadsDetected", null, "Active uploads detected, but no cleanup was performed. Run the \"repair\" command to clean up the incomplete files"); } diff --git a/Duplicati/Library/Main/Operation/ListAffected.cs b/Duplicati/Library/Main/Operation/ListAffected.cs index 33b0fa1bc..97284398e 100644 --- a/Duplicati/Library/Main/Operation/ListAffected.cs +++ b/Duplicati/Library/Main/Operation/ListAffected.cs @@ -44,21 +44,21 @@ namespace Duplicati.Library.Main.Operation if (!File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); - await using var db = await Database.LocalListAffectedDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache).ConfigureAwait(false); + await using var db = await Database.LocalListAffectedDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (callback == null) { m_result.SetResult( - await db.GetFilesets(args) + await db.GetFilesets(args, m_result.TaskControl.ProgressToken) .OrderByDescending(x => x.Time) .ToArrayAsync() .ConfigureAwait(false), - await db.GetFiles(args) + await db.GetFiles(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false), - await db.GetLogLines(args) + await db.GetLogLines(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false), - await db.GetVolumes(args) + await db.GetVolumes(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false) ); @@ -66,17 +66,17 @@ namespace Duplicati.Library.Main.Operation else { m_result.SetResult( - await db.GetFilesets(args) + await db.GetFilesets(args, m_result.TaskControl.ProgressToken) .OrderByDescending(x => x.Time) .ToArrayAsync() .ConfigureAwait(false), - await db.GetFiles(args) + await db.GetFiles(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false), - await db.GetLogLines(args) + await db.GetLogLines(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false), - await db.GetVolumes(args) + await db.GetVolumes(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false) ); diff --git a/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs b/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs index 13dc5502c..8f37f4226 100644 --- a/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs @@ -52,10 +52,10 @@ namespace Duplicati.Library.Main.Operation if (!File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); - await using var db = await Database.LocalListBrokenFilesDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache).ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options) + await using var db = await Database.LocalListBrokenFilesDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await DoRunAsync(backendManager, db, filter, callbackhandler).ConfigureAwait(false); await db.Transaction.RollBackAsync().ConfigureAwait(false); @@ -65,18 +65,18 @@ namespace Duplicati.Library.Main.Operation { List? missing = null; var brokensets = await db - .GetBrokenFilesets(options.Time, options.Version) - .ToArrayAsync() + .GetBrokenFilesets(options.Time, options.Version, result.TaskControl.ProgressToken) + .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); if (brokensets.Length == 0) { - if (await db.RepairInProgress().ConfigureAwait(false)) + if (await db.RepairInProgress(result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("Cannot continue because the database is marked as being under repair, but does not have broken files.", "CannotListOnDatabaseInRepair"); Logging.Log.WriteInformationMessage(LOGTAG, "NoBrokenFilesetsInDatabase", "No broken filesets found in database, checking for missing remote files"); - var remotestate = await FilelistProcessor.RemoteListAnalysis(backendManager, options, db, result.BackendWriter, null, null, FilelistProcessor.VerifyMode.VerifyOnly).ConfigureAwait(false); + var remotestate = await FilelistProcessor.RemoteListAnalysis(backendManager, options, db, result.BackendWriter, null, null, FilelistProcessor.VerifyMode.VerifyOnly, result.TaskControl.ProgressToken).ConfigureAwait(false); if (!remotestate.ParsedVolumes.Any()) throw new UserInformationException("No remote volumes were found, refusing purge", "CannotPurgeWithNoRemoteVolumes"); @@ -90,24 +90,24 @@ namespace Duplicati.Library.Main.Operation // Mark all volumes as disposable foreach (var f in missing) await db - .UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash) + .UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, result.TaskControl.ProgressToken) .ConfigureAwait(false); Logging.Log.WriteInformationMessage(LOGTAG, "MarkedRemoteFilesForDeletion", "Marked {0} remote files for deletion", missing.Count); // Drop all content from tables await db - .RemoveMissingBlocks(missing.Select(x => x.Name)) + .RemoveMissingBlocks(missing.Select(x => x.Name), result.TaskControl.ProgressToken) .ConfigureAwait(false); // Mark all orphaned index files as disposable after removing the missing block files - await foreach (var f in db.GetOrphanedIndexFiles().ConfigureAwait(false)) + await foreach (var f in db.GetOrphanedIndexFiles(result.TaskControl.ProgressToken).ConfigureAwait(false)) await db - .UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash) + .UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, result.TaskControl.ProgressToken) .ConfigureAwait(false); brokensets = await db - .GetBrokenFilesets(options.Time, options.Version) + .GetBrokenFilesets(options.Time, options.Version, result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false); } @@ -120,7 +120,7 @@ namespace Duplicati.Library.Main.Operation if (filter != null && !filter.Empty) throw new UserInformationException("Filters are not supported for this operation", "FiltersAreNotSupportedForListBrokenFiles"); - if (await db.PartiallyRecreated().ConfigureAwait(false)) + if (await db.PartiallyRecreated(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The command does not work on partially recreated databases", "ListBrokenFilesDoesNotWorkOnPartialDatabase"); (var brokensets, var missing) = await GetBrokenFilesetsFromRemote(backendManager, m_result, db, m_options).ConfigureAwait(false); @@ -142,8 +142,8 @@ namespace Duplicati.Library.Main.Operation } var fstimes = await db - .FilesetTimes() - .ToListAsync() + .FilesetTimes(m_result.TaskControl.ProgressToken) + .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var brokenfilesets = @@ -165,8 +165,8 @@ namespace Duplicati.Library.Main.Operation x.Timestamp, callbackhandler == null && !m_options.ListSetsOnly ? await db - .GetBrokenFilenames(x.FilesetID) - .ToArrayAsync() + .GetBrokenFilenames(x.FilesetID, m_result.TaskControl.ProgressToken) + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false) : new MockList>((int)x.BrokenCount) ))) @@ -175,7 +175,7 @@ namespace Duplicati.Library.Main.Operation if (callbackhandler != null) foreach (var bs in brokenfilesets) - await foreach (var fe in db.GetBrokenFilenames(bs.FilesetID).ConfigureAwait(false)) + await foreach (var fe in db.GetBrokenFilenames(bs.FilesetID, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) if (!callbackhandler(bs.Version, bs.Timestamp, bs.BrokenCount, fe.Item1, fe.Item2)) break; } diff --git a/Duplicati/Library/Main/Operation/ListChangesHandler.cs b/Duplicati/Library/Main/Operation/ListChangesHandler.cs index 8ee6cc459..e1c82db45 100644 --- a/Duplicati/Library/Main/Operation/ListChangesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListChangesHandler.cs @@ -80,13 +80,13 @@ namespace Duplicati.Library.Main.Operation DateTime compareVersionTime; using var tmpdb = useLocalDb ? null : new TempFile(); - await using var db = await Database.LocalListChangesDatabase.CreateAsync(useLocalDb ? m_options.Dbpath : (string)tmpdb, m_options.SqlitePageCache).ConfigureAwait(false); - await using var storageKeeper = await db.CreateStorageHelper().ConfigureAwait(false); + await using var db = await Database.LocalListChangesDatabase.CreateAsync(useLocalDb ? m_options.Dbpath : (string)tmpdb, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await using var storageKeeper = await db.CreateStorageHelper(m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (useLocalDb) { var dbtimes = await db - .FilesetTimes() - .ToListAsync() + .FilesetTimes(m_result.TaskControl.ProgressToken) + .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (dbtimes.Count < 2) @@ -101,10 +101,10 @@ namespace Duplicati.Library.Main.Operation SelectTime(compareVersion, times, out compareVersionIndex, out compareVersionTime, out compareVersionId); await storageKeeper - .AddFromDb(baseVersionId, false, filter) + .AddFromDb(baseVersionId, false, filter, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await storageKeeper - .AddFromDb(compareVersionId, true, filter) + .AddFromDb(compareVersionId, true, filter, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } else @@ -151,7 +151,7 @@ namespace Duplicati.Library.Main.Operation foreach (var f in rd.Files) if (FilterExpression.Matches(filter, f.Path)) await storageKeeper - .AddElement(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), false) + .AddElement(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), false, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) @@ -162,20 +162,20 @@ namespace Duplicati.Library.Main.Operation foreach (var f in rd.Files) if (FilterExpression.Matches(filter, f.Path)) await storageKeeper - .AddElement(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), true) + .AddElement(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } var changes = await storageKeeper - .CreateChangeCountReport() + .CreateChangeCountReport(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var sizes = await storageKeeper - .CreateChangeSizeReport() + .CreateChangeSizeReport(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var lst = (m_options.FullResult || callback != null) ? - (from n in storageKeeper.CreateChangedFileReport() + (from n in storageKeeper.CreateChangedFileReport(m_result.TaskControl.ProgressToken) select n) : null; m_result.SetResult( @@ -184,7 +184,7 @@ namespace Duplicati.Library.Main.Operation changes.DeletedFolders, changes.DeletedSymlinks, changes.DeletedFiles, changes.ModifiedFolders, changes.ModifiedSymlinks, changes.ModifiedFiles, sizes.AddedSize, sizes.DeletedSize, sizes.PreviousSize, sizes.CurrentSize, - (lst == null || callback == null) ? null : await lst.ToArrayAsync().ConfigureAwait(false) + (lst == null || callback == null) ? null : await lst.ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken).ConfigureAwait(false) ); if (callback != null) diff --git a/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs b/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs index ee5d229a3..d2f6bf925 100644 --- a/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs @@ -43,7 +43,7 @@ namespace Duplicati.Library.Main.Operation { var cancellationToken = CancellationToken.None; using var tmpdb = new TempFile(); - await using var db = await LocalDatabase.CreateLocalDatabaseAsync(System.IO.File.Exists(m_options.Dbpath) ? m_options.Dbpath : (string)tmpdb, "ListControlFiles", true, m_options.SqlitePageCache).ConfigureAwait(false); + await using var db = await LocalDatabase.CreateLocalDatabaseAsync(System.IO.File.Exists(m_options.Dbpath) ? m_options.Dbpath : (string)tmpdb, "ListControlFiles", true, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); var filter = JoinedFilterExpression.Join(new Library.Utility.FilterExpression(filterstrings), compositefilter); try @@ -62,7 +62,7 @@ namespace Duplicati.Library.Main.Operation var file = fileversion.Value.File; var entry = await db - .GetRemoteVolume(file.Name) + .GetRemoteVolume(file.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var files = new List(); diff --git a/Duplicati/Library/Main/Operation/ListFileVersionsHandler.cs b/Duplicati/Library/Main/Operation/ListFileVersionsHandler.cs index 90533677b..fb0cf4427 100644 --- a/Duplicati/Library/Main/Operation/ListFileVersionsHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFileVersionsHandler.cs @@ -48,14 +48,14 @@ internal static class ListFileVersionsHandler throw new UserInformationException("No local database found, this operation requires a local database", "NoLocalDatabase"); await using var db = - await Database.LocalListDatabase.CreateAsync(options.Dbpath, options.SqlitePageCache) + await Database.LocalListDatabase.CreateAsync(options.Dbpath, options.SqlitePageCache, null, result.TaskControl.ProgressToken) .ConfigureAwait(false); long[]? filesetIds = null; if (!options.AllVersions) { filesetIds = await db - .GetFilesetIDs(options.Time, options.Version) - .ToArrayAsync() + .GetFilesetIDs(options.Time, options.Version, false, result.TaskControl.ProgressToken) + .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); if (filesetIds.Length == 0) @@ -67,7 +67,7 @@ internal static class ListFileVersionsHandler paths = paths.Select(path => Util.AppendDirSeparator(path)).ToArray(); result.FileVersions = await db - .ListFileVersions(paths, filesetIds, offset, limit) + .ListFileVersions(paths, filesetIds, offset, limit, result.TaskControl.ProgressToken) .ConfigureAwait(false); } } diff --git a/Duplicati/Library/Main/Operation/ListFilesHandler.cs b/Duplicati/Library/Main/Operation/ListFilesHandler.cs index 77479350c..6d3375ddd 100644 --- a/Duplicati/Library/Main/Operation/ListFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFilesHandler.cs @@ -55,15 +55,15 @@ namespace Duplicati.Library.Main.Operation //Use a speedy local query if (!m_options.NoLocalDb && System.IO.File.Exists(m_options.Dbpath)) - await using (var db = await Database.LocalListDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache).ConfigureAwait(false)) + await using (var db = await Database.LocalListDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await using var filesets = await db.SelectFileSets(m_options.Time, m_options.Version).ConfigureAwait(false); + await using var filesets = await db.SelectFileSets(m_options.Time, m_options.Version, m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (!filter.Empty) { if (simpleList || (m_options.ListFolderContents && !m_options.AllVersions)) { await filesets - .TakeFirst() + .TakeFirst(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -71,11 +71,11 @@ namespace Duplicati.Library.Main.Operation IAsyncEnumerable files; if (m_options.ListFolderContents) { - files = filesets.SelectFolderContents(filter); + files = filesets.SelectFolderContents(filter, m_result.TaskControl.ProgressToken); } else if (m_options.ListPrefixOnly) { - files = filesets.GetLargestPrefix(filter); + files = filesets.GetLargestPrefix(filter, m_result.TaskControl.ProgressToken); } else if (filter.Empty) { @@ -83,16 +83,16 @@ namespace Duplicati.Library.Main.Operation } else { - files = filesets.SelectFiles(filter); + files = filesets.SelectFiles(filter, m_result.TaskControl.ProgressToken); } if (m_options.ListSetsOnly) { m_result.SetResult( await filesets - .QuickSets() + .QuickSets(m_result.TaskControl.ProgressToken) .Select(x => new ListResultFileset(x.Version, x.IsFullBackup, x.Time, x.FileCount, x.FileSizes)) - .ToArrayAsync() + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false), null ); @@ -101,9 +101,9 @@ namespace Duplicati.Library.Main.Operation { m_result.SetResult( await filesets - .Sets() + .Sets(m_result.TaskControl.ProgressToken) .Select(x => new ListResultFileset(x.Version, x.IsFullBackup, x.Time, x.FileCount, x.FileSizes)) - .ToArrayAsync() + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false), files == null ? null @@ -112,14 +112,14 @@ namespace Duplicati.Library.Main.Operation new ListResultFile( n.Path, await n - .Sizes() - .ToArrayAsync() + .Sizes(m_result.TaskControl.ProgressToken) + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false) ) ) .Select(x => x.Result) .Cast() - .ToArrayAsync() + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false) ); } @@ -137,7 +137,7 @@ namespace Duplicati.Library.Main.Operation // Otherwise, grab info from remote location using (var tmpdb = new TempFile()) - await using (var db = await LocalDatabase.CreateLocalDatabaseAsync(tmpdb, "List", true, m_options.SqlitePageCache).ConfigureAwait(false)) + await using (var db = await LocalDatabase.CreateLocalDatabaseAsync(tmpdb, "List", true, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { var filteredList = ParseAndFilterFilesets(await backendManager.ListAsync(cancellationToken).ConfigureAwait(false), m_options); if (filteredList.Count == 0) diff --git a/Duplicati/Library/Main/Operation/ListFilesetsHandler.cs b/Duplicati/Library/Main/Operation/ListFilesetsHandler.cs index b1169b1a2..8629f33e8 100644 --- a/Duplicati/Library/Main/Operation/ListFilesetsHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFilesetsHandler.cs @@ -22,6 +22,7 @@ #nullable enable using System.Linq; +using System.Threading; using System.Threading.Tasks; using Duplicati.Library.Interface; @@ -49,12 +50,12 @@ internal static class ListFilesetsHandler // Use a speedy local query if (System.IO.File.Exists(options.Dbpath) && !options.NoLocalDb) { - await using var db = await Database.LocalListDatabase.CreateAsync(options.Dbpath, options.SqlitePageCache) + await using var db = await Database.LocalListDatabase.CreateAsync(options.Dbpath, options.SqlitePageCache, null, result.TaskControl.ProgressToken) .ConfigureAwait(false); result.Filesets = await db - .ListFilesetsExtended() - .ToArrayAsync() + .ListFilesetsExtended(result.TaskControl.ProgressToken) + .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); return; diff --git a/Duplicati/Library/Main/Operation/ListFolderHandler.cs b/Duplicati/Library/Main/Operation/ListFolderHandler.cs index b71e0a81c..5a296f564 100644 --- a/Duplicati/Library/Main/Operation/ListFolderHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFolderHandler.cs @@ -46,12 +46,12 @@ internal static class ListFolderHandler if (!System.IO.File.Exists(options.Dbpath) || options.NoLocalDb) throw new UserInformationException("No local database found, this operation requires a local database", "NoLocalDatabase"); - await using var db = await Database.LocalListDatabase.CreateAsync(options.Dbpath, options.SqlitePageCache) + await using var db = await Database.LocalListDatabase.CreateAsync(options.Dbpath, options.SqlitePageCache, null, result.TaskControl.ProgressToken) .ConfigureAwait(false); var filesetIds = await db - .GetFilesetIDs(options.Time, options.Version, singleTimeMatch: true) - .ToArrayAsync() + .GetFilesetIDs(options.Time, options.Version, singleTimeMatch: true, result.TaskControl.ProgressToken) + .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); if (filesetIds.Length == 0) @@ -65,10 +65,11 @@ internal static class ListFolderHandler throw new UserInformationException("When no folder is specified, only one folder can be listed", "MultipleFoldersFound"); result.Entries = await db .ListFilesetEntries( - db.GetRootPrefixes(filesetIds[0]).ToEnumerable(), + db.GetRootPrefixes(filesetIds[0], result.TaskControl.ProgressToken).ToEnumerable(), filesetIds[0], offset, - limit + limit, + result.TaskControl.ProgressToken ) .ConfigureAwait(false); } @@ -76,10 +77,11 @@ internal static class ListFolderHandler { result.Entries = await db .ListFolder( - db.GetPrefixIds(folders).ToEnumerable(), + db.GetPrefixIds(folders, result.TaskControl.ProgressToken).ToEnumerable(), filesetIds[0], offset, - limit + limit, + result.TaskControl.ProgressToken ) .ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs b/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs index 47afcb0eb..348ebf383 100644 --- a/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs @@ -54,13 +54,13 @@ namespace Duplicati.Library.Main.Operation if (filter != null && !filter.Empty) throw new UserInformationException("Filters are not supported for this operation", "FiltersNotAllowedOnPurgeBrokenFiles"); - await using var db = await LocalListBrokenFilesDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache).ConfigureAwait(false); - if (await db.PartiallyRecreated().ConfigureAwait(false)) + await using var db = await LocalListBrokenFilesDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + if (await db.PartiallyRecreated(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The command does not work on partially recreated databases", "CannotPurgeOnPartialDatabase"); - await Utility.UpdateOptionsFromDb(db, m_options) + await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var (sets, missing) = await ListBrokenFilesHandler.GetBrokenFilesetsFromRemote(backendManager, m_result, db, m_options).ConfigureAwait(false); @@ -84,8 +84,8 @@ namespace Duplicati.Library.Main.Operation var pgspan = 0.95f / sets.Length; var filesets = await db - .FilesetTimes() - .ToListAsync() + .FilesetTimes(m_result.TaskControl.ProgressToken) + .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var compare_list = sets.Select(async x => new @@ -95,7 +95,7 @@ namespace Duplicati.Library.Main.Operation RemoveCount = x.RemoveCount, Version = filesets.FindIndex(y => y.Key == x.FilesetID), SetCount = await db - .GetFilesetFileCount(x.FilesetID) + .GetFilesetFileCount(x.FilesetID, m_result.TaskControl.ProgressToken) .ConfigureAwait(false) }) .Select(x => x.Result) @@ -109,7 +109,8 @@ namespace Duplicati.Library.Main.Operation .GetEmptyMetadataBlocksetId( (missing ?? []).Select(x => x.ID), emptymetadata.FileHash, - emptymetadata.Blob.Length + emptymetadata.Blob.Length, + m_result.TaskControl.ProgressToken ) .ConfigureAwait(false); if (replacementMetadataBlocksetId < 0) @@ -119,11 +120,11 @@ namespace Duplicati.Library.Main.Operation var fully_emptied = compare_list.Where(x => x.RemoveCount == x.SetCount).ToArray(); var to_purge = compare_list.Where(x => x.RemoveCount != x.SetCount).ToArray(); - if (fully_emptied.Length == await db.FilesetTimes().CountAsync().ConfigureAwait(false)) + if (fully_emptied.Length == await db.FilesetTimes(m_result.TaskControl.ProgressToken).CountAsync(cancellationToken: m_result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("All filesets are fully broken and needs to be removed. To avoid unexpected deletions, you must manually remove the remote files and delete the database.", "AllFilesetsBroken"); if (!m_options.Dryrun) - await db.Transaction.CommitAsync().ConfigureAwait(false); + await db.Transaction.CommitAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (fully_emptied.Length != 0) @@ -134,7 +135,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "RemovingFilesets", "Removing {0} filesets where all file(s) are broken: {1}", fully_emptied.Length, string.Join(", ", fully_emptied.Select(x => x.Timestamp.ToLocalTime().ToString()))); m_result.DeleteResults = new DeleteResults(m_result); - await using (var rmdb = await LocalDeleteDatabase.CreateAsync(db).ConfigureAwait(false)) + await using (var rmdb = await LocalDeleteDatabase.CreateAsync(db, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { var opts = new Options(new Dictionary(m_options.RawOptions)); opts.RawOptions["version"] = string.Join(",", fully_emptied.Select(x => x.Version.ToString())); @@ -146,7 +147,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) await rmdb.Transaction - .CommitAsync("CommitDelete") + .CommitAsync("CommitDelete", true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -163,12 +164,12 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "PurgingFiles", "Purging {0} file(s) from fileset {1}", bs.RemoveCount, bs.Timestamp.ToLocalTime()); var opts = new Options(new Dictionary(m_options.RawOptions)); - await using (var pgdb = await Database.LocalPurgeDatabase.CreateAsync(db).ConfigureAwait(false)) + await using (var pgdb = await Database.LocalPurgeDatabase.CreateAsync(db, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { // Recompute the version number after we deleted the versions before filesets = await pgdb - .FilesetTimes() - .ToListAsync() + .FilesetTimes(m_result.TaskControl.ProgressToken) + .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var thisversion = filesets.FindIndex(y => y.Key == bs.FilesetID); if (thisversion < 0) @@ -186,9 +187,9 @@ namespace Duplicati.Library.Main.Operation // Update entries that would be removed because of missing metadata var updatedEntries = 0; if (!m_options.DisableReplaceMissingMetadata) - updatedEntries = await db.ReplaceMetadata(filesetid, replacementMetadataBlocksetId); + updatedEntries = await db.ReplaceMetadata(filesetid, replacementMetadataBlocksetId, m_result.TaskControl.ProgressToken); - await db.InsertBrokenFileIDsIntoTable(filesetid, tablename, "FileID"); + await db.InsertBrokenFileIDsIntoTable(filesetid, tablename, "FileID", m_result.TaskControl.ProgressToken); return updatedEntries; }).ConfigureAwait(false); } @@ -201,20 +202,20 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress(0.95f); - if (!m_options.Dryrun && await db.RepairInProgress().ConfigureAwait(false)) + if (!m_options.Dryrun && await db.RepairInProgress(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { Logging.Log.WriteInformationMessage(LOGTAG, "ValidatingDatabase", "Database was previously marked as in-progress, checking if it is valid after purging files"); await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true) + .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); Logging.Log.WriteInformationMessage(LOGTAG, "UpdatingDatabase", "Purge completed, and consistency checks completed, marking database as complete"); - await db.RepairInProgress(false).ConfigureAwait(false); + await db.RepairInProgress(m_result.TaskControl.ProgressToken, false).ConfigureAwait(false); } else { - await db.Transaction.RollBackAsync().ConfigureAwait(false); + await db.Transaction.RollBackAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true) + .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs b/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs index dd7aece5a..378439872 100644 --- a/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs @@ -52,10 +52,10 @@ namespace Duplicati.Library.Main.Operation if (!System.IO.File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); - await using var db = await Database.LocalPurgeDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache).ConfigureAwait(false); + await using var db = await Database.LocalPurgeDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await DoRunAsync(backendManager, db, filter, null, 0, 1).ConfigureAwait(false); await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true) + .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -69,44 +69,44 @@ namespace Duplicati.Library.Main.Operation var doCompactStep = !m_options.NoAutoCompact && filtercommand == null; - if (await db.PartiallyRecreated().ConfigureAwait(false)) + if (await db.PartiallyRecreated(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The purge command does not work on partially recreated databases", "PurgeNotAllowedOnPartialDatabase"); - if (await db.RepairInProgress().ConfigureAwait(false) && filtercommand == null) + if (await db.RepairInProgress(m_result.TaskControl.ProgressToken).ConfigureAwait(false) && filtercommand == null) throw new UserInformationException(string.Format("The purge command does not work on an incomplete database, try the {0} operation.", "purge-broken-files"), "PurgeNotAllowedOnIncompleteDatabase"); var versions = await db - .GetFilesetIDs(m_options.Time, m_options.Version) + .GetFilesetIDs(m_options.Time, m_options.Version, false, m_result.TaskControl.ProgressToken) .OrderByDescending(x => x) - .ToArrayAsync() + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (versions.Length <= 0) throw new UserInformationException("No filesets matched the supplied time or versions", "NoFilesetFoundForTimeOrVersion"); - var orphans = await db.CountOrphanFiles().ConfigureAwait(false); + var orphans = await db.CountOrphanFiles(m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (orphans != 0) throw new UserInformationException(string.Format("Unable to start the purge process as there are {0} orphan file(s)", orphans), "CannotPurgeWithOrphans"); - await Utility.UpdateOptionsFromDb(db, m_options) + await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (filtercommand == null) { - await db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, false) + await db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, false, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (m_options.NoBackendverification) await FilelistProcessor.VerifyLocalList(backendManager, db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); else - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, null, null, logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, null, null, logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); } var filesets = await db - .FilesetTimes() + .FilesetTimes(m_result.TaskControl.ProgressToken) .OrderByDescending(x => x.Value) - .ToArrayAsync() + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var versionprogress = ((doCompactStep ? 0.75f : 1.0f) / versions.Length) * pgspan; @@ -118,7 +118,7 @@ namespace Duplicati.Library.Main.Operation // If we crash now, it is possible that the remote storage contains partial files if (!m_options.Dryrun) - await db.TerminatedWithActiveUploads(true).ConfigureAwait(false); + await db.TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); // Reverse makes sure we re-write the old versions first foreach (var versionid in versions.Reverse()) @@ -132,18 +132,18 @@ namespace Duplicati.Library.Main.Operation var ts = await FilesetVolumeWriter.ProbeUnusedFilenameName(db, m_options, tsOriginal) .ConfigureAwait(false); - var prevfilename = await db.GetRemoteVolumeNameForFileset(filesets[ix].Key) + var prevfilename = await db.GetRemoteVolumeNameForFileset(filesets[ix].Key, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (ix != 0 && filesets[ix - 1].Value <= ts) throw new Exception(string.Format("Unable to create a new fileset for {0} because the resulting timestamp {1} is larger than the next timestamp {2}", prevfilename, ts, filesets[ix - 1].Value)); - await using (var tempset = await db.CreateTemporaryFileset(versionid).ConfigureAwait(false)) + await using (var tempset = await db.CreateTemporaryFileset(versionid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { if (filtercommand == null) - await tempset.ApplyFilter(filter).ConfigureAwait(false); + await tempset.ApplyFilter(filter, m_result.TaskControl.ProgressToken).ConfigureAwait(false); else - await tempset.ApplyFilter(filtercommand) + await tempset.ApplyFilter(filtercommand, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (tempset.RemovedFileCount + tempset.UpdatedFileCount == 0) @@ -161,10 +161,10 @@ namespace Duplicati.Library.Main.Operation using (var vol = new FilesetVolumeWriter(m_options, ts)) { var isOriginalFilesetFullBackup = await db - .IsFilesetFullBackup(tsOriginal) + .IsFilesetFullBackup(tsOriginal, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var newids = await tempset - .ConvertToPermanentFileset(vol.RemoteFilename, ts, isOriginalFilesetFullBackup) + .ConvertToPermanentFileset(vol.RemoteFilename, ts, isOriginalFilesetFullBackup, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); vol.VolumeID = newids.Item1; vol.CreateFilesetFile(isOriginalFilesetFullBackup); @@ -172,7 +172,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "ReplacingFileset", "Replacing fileset {0} with {1} which has with {2} fewer file(s) ({3} reduction)", prevfilename, vol.RemoteFilename, tempset.RemovedFileCount, Library.Utility.Utility.FormatSizeString(tempset.RemovedFileSize)); await db - .WriteFileset(vol, newids.Item2) + .WriteFileset(vol, newids.Item2, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); m_result.RemovedFileSize += tempset.RemovedFileSize; @@ -185,7 +185,7 @@ namespace Duplicati.Library.Main.Operation if (m_options.Dryrun || m_options.FullResult) { - await foreach (var fe in tempset.ListAllDeletedFiles().ConfigureAwait(false)) + await foreach (var fe in tempset.ListAllDeletedFiles(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { var msg = string.Format(" Purging file {0} ({1})", fe.Key, Library.Utility.Utility.FormatSizeString(fe.Value)); @@ -212,19 +212,19 @@ namespace Duplicati.Library.Main.Operation else { await db - .UpdateRemoteVolume(vol.RemoteFilename, RemoteVolumeState.Uploading, -1, null) + .UpdateRemoteVolume(vol.RemoteFilename, RemoteVolumeState.Uploading, -1, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var lst = await db - .DropFilesetsFromTable(new[] { tsOriginal }) - .ToArrayAsync() + .DropFilesetsFromTable(new[] { tsOriginal }, m_result.TaskControl.ProgressToken) + .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); foreach (var f in lst) await db - .UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null) + .UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db.Transaction - .CommitAsync() + .CommitAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await backendManager.PutAsync(vol, null, null, true, async () => @@ -235,7 +235,7 @@ namespace Duplicati.Library.Main.Operation // passing down a transaction, but maybe is // not needed anymore. await db.Transaction - .CommitAsync() + .CommitAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); }, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await backendManager.DeleteAsync(prevfilename, -1, true, m_result.TaskControl.ProgressToken).ConfigureAwait(false); @@ -251,7 +251,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) await db - .TerminatedWithActiveUploads(false) + .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, false) .ConfigureAwait(false); if (doCompactStep) @@ -265,12 +265,12 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress(pgoffset + (0.75f * pgspan)); m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.PurgeFiles_Compact); m_result.CompactResults = new CompactResults(m_result); - await using var cdb = await Database.LocalDeleteDatabase.CreateAsync(db).ConfigureAwait(false); + await using var cdb = await Database.LocalDeleteDatabase.CreateAsync(db, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await new CompactHandler(m_options, (CompactResults)m_result.CompactResults) .DoCompactAsync(cdb, true, backendManager) .ConfigureAwait(false); - await cdb.Transaction.CommitAsync("PostCompact"); + await cdb.Transaction.CommitAsync("PostCompact", true, m_result.TaskControl.ProgressToken); } m_result.OperationProgressUpdater.UpdateProgress(pgoffset + pgspan); diff --git a/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs b/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs index 3c00e6c08..c5a116c54 100644 --- a/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs +++ b/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs @@ -23,6 +23,7 @@ using System.Collections.Generic; //using System.Data; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Library.Main.Database; @@ -65,14 +66,14 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(string.Format("Cannot recreate database because file already exists: {0}", path), "RecreateTargetDatabaseExists"); await using var db = - await LocalDatabase.CreateLocalDatabaseAsync(path, "Recreate", true, m_options.SqlitePageCache) + await LocalDatabase.CreateLocalDatabaseAsync(path, "Recreate", true, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await DoRunAsync(backendManager, db, false, filter, filelistfilter, blockprocessor).ConfigureAwait(false); // Ensure database is consistent after the recreate await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true) + .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -89,9 +90,9 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(string.Format("Can only update with paths, try setting --{0}", "repair-only-paths"), "RepairUpdateRequiresPathsOnly"); await using var db = - await LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, "Recreate", true, m_options.SqlitePageCache) + await LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, "Recreate", true, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - if ((await db.FindMatchingFilesets(m_options.Time, m_options.Version).ConfigureAwait(false)).Any()) + if ((await db.FindMatchingFilesets(m_options.Time, m_options.Version, m_result.TaskControl.ProgressToken).ConfigureAwait(false)).Any()) { if (m_options.IgnoreUpdateIfVersionExists) { @@ -103,17 +104,17 @@ namespace Duplicati.Library.Main.Operation } // Mark as incomplete - await db.PartiallyRecreated(true).ConfigureAwait(false); + await db.PartiallyRecreated(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); var preexistingOptionsInDatabase = - await Utility.ContainsOptionsForVerification(db) + await Utility.ContainsOptionsForVerification(db, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options) + await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // Make sure the options have not changed between calls, unless there are no previous options if (preexistingOptionsInDatabase) - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await DoRunAsync(backendManager, db, true, filter, filelistfilter, blockprocessor).ConfigureAwait(false); @@ -135,9 +136,9 @@ namespace Duplicati.Library.Main.Operation //We build a local database in steps. await using var restoredb = - await LocalRecreateDatabase.CreateAsync(dbparent, m_options) + await LocalRecreateDatabase.CreateAsync(dbparent, m_options, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await restoredb.RepairInProgress(true).ConfigureAwait(false); + await restoredb.RepairInProgress(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); var expRecreateDb = false; // experimental recreate db code flag var volumeIds = new Dictionary(); @@ -205,9 +206,9 @@ namespace Duplicati.Library.Main.Operation // If we are updating, all files should be accounted for foreach (var fl in remotefiles) volumeIds[fl.File.Name] = updating - ? await restoredb.GetRemoteVolumeID(fl.File.Name) + ? await restoredb.GetRemoteVolumeID(fl.File.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false) - : await restoredb.RegisterRemoteVolume(fl.File.Name, fl.FileType, fl.File.Size, RemoteVolumeState.Uploaded) + : await restoredb.RegisterRemoteVolume(fl.File.Name, fl.FileType, fl.File.Size, RemoteVolumeState.Uploaded, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var hasUpdatedOptions = false; @@ -224,7 +225,7 @@ namespace Duplicati.Library.Main.Operation foreach (var n in filelists) if (volumeIds[n.File.Name] == -1) volumeIds[n.File.Name] = await restoredb - .RegisterRemoteVolume(n.File.Name, n.FileType, RemoteVolumeState.Uploaded, n.File.Size, new TimeSpan(0)) + .RegisterRemoteVolume(n.File.Name, n.FileType, RemoteVolumeState.Uploaded, n.File.Size, new TimeSpan(0), m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -240,7 +241,7 @@ namespace Duplicati.Library.Main.Operation m_result.EndTime = DateTime.UtcNow; // Implicit rollback await restoredb.Transaction - .RollBackAsync() + .RollBackAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); return; } @@ -262,7 +263,7 @@ namespace Duplicati.Library.Main.Operation if (!string.IsNullOrWhiteSpace(hash) && size > 0) await restoredb - .UpdateRemoteVolume(entry.Name, RemoteVolumeState.Verified, size, hash) + .UpdateRemoteVolume(entry.Name, RemoteVolumeState.Verified, size, hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var parsed = VolumeBase.ParseFilename(entry.Name); @@ -280,10 +281,10 @@ namespace Duplicati.Library.Main.Operation // Create timestamped operations based on the file timestamp var filesetid = await restoredb - .CreateFileset(volumeIds[entry.Name], parsed.Time) + .CreateFileset(volumeIds[entry.Name], parsed.Time, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await RecreateFilesetFromRemoteList(restoredb, compressor, filesetid, m_options, filter) + await RecreateFilesetFromRemoteList(restoredb, compressor, filesetid, m_options, filter, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -295,7 +296,7 @@ namespace Duplicati.Library.Main.Operation m_result.EndTime = DateTime.UtcNow; // Implicit rollback await restoredb.Transaction - .RollBackAsync() + .RollBackAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); throw; } @@ -305,7 +306,7 @@ namespace Duplicati.Library.Main.Operation m_result.EndTime = DateTime.UtcNow; // Implicit rollback await restoredb.Transaction - .RollBackAsync() + .RollBackAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); throw; } @@ -314,7 +315,7 @@ namespace Duplicati.Library.Main.Operation { // Implicit rollback await restoredb.Transaction - .RollBackAsync() + .RollBackAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); throw; } @@ -323,12 +324,12 @@ namespace Duplicati.Library.Main.Operation //Make sure we write the config if it has been read from a manifest if (hasUpdatedOptions) - await Utility.VerifyOptionsAndUpdateDatabase(restoredb, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(restoredb, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); using (new Logging.Timer(LOGTAG, "CommitUpdateFilesetFromRemote", "CommitUpdateFilesetFromRemote")) await restoredb.Transaction - .CommitAsync() + .CommitAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // do we stop after just handling the dlist files ? @@ -361,7 +362,7 @@ namespace Duplicati.Library.Main.Operation m_result.EndTime = DateTime.UtcNow; // Implicit rollback await restoredb.Transaction - .RollBackAsync() + .RollBackAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); return; } @@ -374,7 +375,7 @@ namespace Duplicati.Library.Main.Operation { if (!string.IsNullOrWhiteSpace(hash) && size > 0) await restoredb - .UpdateRemoteVolume(name, RemoteVolumeState.Verified, size, hash) + .UpdateRemoteVolume(name, RemoteVolumeState.Verified, size, hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); using (var svr = new IndexVolumeReader(RestoreHandler.GetCompressionModule(name), tmpfile, m_options, hashsize)) @@ -383,13 +384,13 @@ namespace Duplicati.Library.Main.Operation { var filename = a.Filename; var volumeID = await restoredb - .GetRemoteVolumeID(filename) + .GetRemoteVolumeID(filename, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // No such file if (volumeID < 0) (volumeID, filename) = - await ProbeForMatchingFilename(filename, restoredb) + await ProbeForMatchingFilename(filename, restoredb, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var missing = false; @@ -402,7 +403,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteWarningMessage(LOGTAG, "MissingFileDetected", null, "Remote file referenced as {0} by {1}, but not found in list, registering a missing remote file", filename, name); missing = true; volumeID = await restoredb - .RegisterRemoteVolume(filename, p.FileType, RemoteVolumeState.Temporary) + .RegisterRemoteVolume(filename, p.FileType, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -410,16 +411,17 @@ namespace Duplicati.Library.Main.Operation //Add all block/volume mappings foreach (var b in a.Blocks) { - anyChange |= (await restoredb.UpdateBlock(b.Key, b.Value, volumeID).ConfigureAwait(false)).Item1; + anyChange |= (await restoredb.UpdateBlock(b.Key, b.Value, volumeID, m_result.TaskControl.ProgressToken).ConfigureAwait(false)).Item1; } await restoredb - .UpdateRemoteVolume(filename, missing ? RemoteVolumeState.Temporary : RemoteVolumeState.Verified, a.Length, a.Hash) + .UpdateRemoteVolume(filename, missing ? RemoteVolumeState.Temporary : RemoteVolumeState.Verified, a.Length, a.Hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await restoredb .AddIndexBlockLink( - await restoredb.GetRemoteVolumeID(name).ConfigureAwait(false), - volumeID + await restoredb.GetRemoteVolumeID(name, m_result.TaskControl.ProgressToken).ConfigureAwait(false), + volumeID, + m_result.TaskControl.ProgressToken ) .ConfigureAwait(false); } @@ -436,7 +438,7 @@ namespace Duplicati.Library.Main.Operation // done before we add it to the database, since we do not have nested transactions var list = b.Blocklist.ToList(); await restoredb - .AddTempBlockListHash(b.Hash, list) + .AddTempBlockListHash(b.Hash, list, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } catch (System.IO.InvalidDataException e) @@ -461,7 +463,7 @@ namespace Duplicati.Library.Main.Operation m_result.EndTime = DateTime.UtcNow; // Implicit rollback await restoredb.Transaction - .RollBackAsync() + .RollBackAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); throw; } @@ -470,7 +472,7 @@ namespace Duplicati.Library.Main.Operation { // Implicit rollback await restoredb.Transaction - .RollBackAsync() + .RollBackAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); throw; } @@ -479,25 +481,25 @@ namespace Duplicati.Library.Main.Operation using (new Logging.Timer(LOGTAG, "CommitRecreateDb", "CommitRecreatedDb")) await restoredb.Transaction - .CommitAsync() + .CommitAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // TODO: In some cases, we can avoid downloading all index files, // if we are lucky and pick the right ones } - await restoredb.CleanupMissingVolumes().ConfigureAwait(false); + await restoredb.CleanupMissingVolumes(m_result.TaskControl.ProgressToken).ConfigureAwait(false); // Update the real tables from the temp tables if (expRecreateDb) // add missing blocks and blocksetentry data (at this point // we have not yet anything in the blocksetentry table) await restoredb - .AddBlockAndBlockSetEntryFromTemp(hashsize, m_options.Blocksize) + .AddBlockAndBlockSetEntryFromTemp(hashsize, m_options.Blocksize, false, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); else await restoredb - .FindMissingBlocklistHashes(hashsize, m_options.Blocksize) + .FindMissingBlocklistHashes(hashsize, m_options.Blocksize, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // We have now grabbed as much information as possible, @@ -507,7 +509,7 @@ namespace Duplicati.Library.Main.Operation { // Grab the list matching the pass type var lst = await restoredb - .GetMissingBlockListVolumes(i, m_options.Blocksize, hashsize, m_options.RepairForceBlockUse) + .GetMissingBlockListVolumes(i, m_options.Blocksize, hashsize, m_options.RepairForceBlockUse, m_result.TaskControl.ProgressToken) .ToListAsync() .ConfigureAwait(false); @@ -545,7 +547,7 @@ namespace Duplicati.Library.Main.Operation m_result.EndTime = DateTime.UtcNow; // Implicit rollback await restoredb.Transaction - .RollBackAsync() + .RollBackAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); return; } @@ -555,26 +557,26 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingBlocklistVolumes", "Pass {0} of 3, processing blocklist volume {1} of {2}", (i + 1), progress, lst.Count); var volumeid = await restoredb - .GetRemoteVolumeID(name) + .GetRemoteVolumeID(name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await restoredb - .UpdateRemoteVolume(name, RemoteVolumeState.Uploaded, size, hash) + .UpdateRemoteVolume(name, RemoteVolumeState.Uploaded, size, hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); bool anyChange = false; // Update the block table so we know about the block/volume map foreach (var h in rd.Blocks) { - anyChange |= (await restoredb.UpdateBlock(h.Key, h.Value, volumeid).ConfigureAwait(false)).Item1; + anyChange |= (await restoredb.UpdateBlock(h.Key, h.Value, volumeid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)).Item1; } // now that we have the blocks/volume relationships, we can go from the (already known from dlist step) blocklisthashes // to the needed list blocks in the volume, so grab them from the database // read the blocks list hashes from the volume data (the handled file) and insert them into the temp blocklisthash table - await foreach (var blocklisthash in restoredb.GetBlockLists(volumeid).ConfigureAwait(false)) + await foreach (var blocklisthash in restoredb.GetBlockLists(volumeid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - if (await restoredb.AddTempBlockListHash(blocklisthash, rd.ReadBlocklist(blocklisthash, hashsize)).ConfigureAwait(false)) + if (await restoredb.AddTempBlockListHash(blocklisthash, rd.ReadBlocklist(blocklisthash, hashsize), m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { anyChange = true; } @@ -593,17 +595,17 @@ namespace Duplicati.Library.Main.Operation } if (expRecreateDb) await restoredb - .AddBlockAndBlockSetEntryFromTemp(hashsize, m_options.Blocksize, false) + .AddBlockAndBlockSetEntryFromTemp(hashsize, m_options.Blocksize, false, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); else await restoredb - .FindMissingBlocklistHashes(hashsize, m_options.Blocksize) + .FindMissingBlocklistHashes(hashsize, m_options.Blocksize, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } using (new Logging.Timer(LOGTAG, "CommitRestoredBlocklist", "CommitRestoredBlocklist")) await restoredb.Transaction - .CommitAsync() + .CommitAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); //At this point we can patch files with data from the block volume @@ -618,7 +620,7 @@ namespace Duplicati.Library.Main.Operation { // Implicit rollback await restoredb.Transaction - .RollBackAsync() + .RollBackAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); throw; } @@ -634,10 +636,10 @@ namespace Duplicati.Library.Main.Operation // All blocks are collected and added into the Block table // Find out which blocks are deleted and move them into DeletedBlock, // so that compact can calculate the unused space - await restoredb.CleanupDeletedBlocks().ConfigureAwait(false); + await restoredb.CleanupDeletedBlocks(m_result.TaskControl.ProgressToken).ConfigureAwait(false); } - await restoredb.CleanupMissingVolumes().ConfigureAwait(false); + await restoredb.CleanupMissingVolumes(m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (m_options.RepairOnlyPaths) { @@ -652,11 +654,11 @@ namespace Duplicati.Library.Main.Operation // except to continue a backup m_result.EndTime = DateTime.UtcNow; - await using (var lbfdb = await LocalListBrokenFilesDatabase.CreateAsync(restoredb).ConfigureAwait(false)) + await using (var lbfdb = await LocalListBrokenFilesDatabase.CreateAsync(restoredb, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { var broken = await lbfdb - .GetBrokenFilesets(new DateTime(0), null) - .CountAsync() + .GetBrokenFilesets(new DateTime(0), null, m_result.TaskControl.ProgressToken) + .CountAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (broken != 0) @@ -664,18 +666,28 @@ namespace Duplicati.Library.Main.Operation } await restoredb - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true) + .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); Logging.Log.WriteInformationMessage(LOGTAG, "RecreateCompleted", "Recreate completed, and consistency checks completed, marking database as complete"); - await restoredb.RepairInProgress(false).ConfigureAwait(false); + await restoredb.RepairInProgress(m_result.TaskControl.ProgressToken, false).ConfigureAwait(false); } m_result.EndTime = DateTime.UtcNow; } - public static async Task RecreateFilesetFromRemoteList(LocalRecreateDatabase restoredb, ICompression compressor, long filesetid, Options options, IFilter filter) + /// + /// Recreate a fileset from the remote filelist. + /// + /// The database to restore to. + /// The compression module to use. + /// The ID of the fileset to recreate. + /// The options to use for the operation. + /// The filter to apply to the files. + /// The cancellation token to monitor for cancellation requests. + /// A task that completes when the fileset has been recreated. + public static async Task RecreateFilesetFromRemoteList(LocalRecreateDatabase restoredb, ICompression compressor, long filesetid, Options options, IFilter filter, CancellationToken cancellationToken) { var blocksize = options.Blocksize; var hashes_pr_block = blocksize / options.BlockhashSize; @@ -685,11 +697,11 @@ namespace Duplicati.Library.Main.Operation // update fileset using filesetData await restoredb - .UpdateFullBackupStateInFileset(filesetid, filesetData.IsFullBackup) + .UpdateFullBackupStateInFileset(filesetid, filesetData.IsFullBackup, cancellationToken) .ConfigureAwait(false); // clear any existing fileset entries - await restoredb.ClearFilesetEntries(filesetid).ConfigureAwait(false); + await restoredb.ClearFilesetEntries(filesetid, cancellationToken).ConfigureAwait(false); using (var filelistreader = new FilesetVolumeReader(compressor, options)) foreach (var fe in filelistreader.Files.Where(x => Library.Utility.FilterExpression.Matches(filter, x.Path))) @@ -703,17 +715,17 @@ namespace Duplicati.Library.Main.Operation var metadataid = long.MinValue; var split = LocalDatabase.SplitIntoPrefixAndName(fe.Path); var prefixid = await restoredb - .GetOrCreatePathPrefix(split.Key) + .GetOrCreatePathPrefix(split.Key, cancellationToken) .ConfigureAwait(false); switch (fe.Type) { case FilelistEntryType.Folder: metadataid = await restoredb - .AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes) + .AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, cancellationToken) .ConfigureAwait(false); await restoredb - .AddDirectoryEntry(filesetid, prefixid, split.Value, fe.Time, metadataid) + .AddDirectoryEntry(filesetid, prefixid, split.Value, fe.Time, metadataid, cancellationToken) .ConfigureAwait(false); break; @@ -723,24 +735,24 @@ namespace Duplicati.Library.Main.Operation if (expectedblocks <= 1) expectedblocklisthashes = 0; var blocksetid = await restoredb - .AddBlockset(fe.Hash, fe.Size, fe.BlocklistHashes, expectedblocklisthashes) + .AddBlockset(fe.Hash, fe.Size, fe.BlocklistHashes, expectedblocklisthashes, cancellationToken) .ConfigureAwait(false); metadataid = await restoredb - .AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes) + .AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, cancellationToken) .ConfigureAwait(false); await restoredb - .AddFileEntry(filesetid, prefixid, split.Value, fe.Time, blocksetid, metadataid) + .AddFileEntry(filesetid, prefixid, split.Value, fe.Time, blocksetid, metadataid, cancellationToken) .ConfigureAwait(false); if (fe.Size <= blocksize) { if (!string.IsNullOrWhiteSpace(fe.Blockhash)) await restoredb - .AddSmallBlocksetLink(fe.Hash, fe.Blockhash, fe.Blocksize) + .AddSmallBlocksetLink(fe.Hash, fe.Blockhash, fe.Blocksize, cancellationToken) .ConfigureAwait(false); else if (options.BlockHashAlgorithm == options.FileHashAlgorithm) await restoredb - .AddSmallBlocksetLink(fe.Hash, fe.Hash, fe.Size) + .AddSmallBlocksetLink(fe.Hash, fe.Hash, fe.Size, cancellationToken) .ConfigureAwait(false); else if (fe.Size > 0) Logging.Log.WriteWarningMessage(LOGTAG, "MissingBlockHash", null, "No block hash found for file: {0}", fe.Path); @@ -749,10 +761,10 @@ namespace Duplicati.Library.Main.Operation break; case FilelistEntryType.Symlink: metadataid = await restoredb - .AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes) + .AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, cancellationToken) .ConfigureAwait(false); await restoredb - .AddSymlinkEntry(filesetid, prefixid, split.Value, fe.Time, metadataid) + .AddSymlinkEntry(filesetid, prefixid, split.Value, fe.Time, metadataid, cancellationToken) .ConfigureAwait(false); break; default: @@ -764,11 +776,11 @@ namespace Duplicati.Library.Main.Operation { if (!string.IsNullOrWhiteSpace(fe.Metablockhash)) await restoredb - .AddSmallBlocksetLink(fe.Metahash, fe.Metablockhash, fe.Metasize) + .AddSmallBlocksetLink(fe.Metahash, fe.Metablockhash, fe.Metasize, cancellationToken) .ConfigureAwait(false); else if (options.BlockHashAlgorithm == options.FileHashAlgorithm) await restoredb - .AddSmallBlocksetLink(fe.Metahash, fe.Metahash, fe.Metasize) + .AddSmallBlocksetLink(fe.Metahash, fe.Metahash, fe.Metasize, cancellationToken) .ConfigureAwait(false); else Logging.Log.WriteWarningMessage(LOGTAG, "MissingMetadataBlockHash", null, "No block hash found for file metadata: {0}", fe.Path); @@ -780,7 +792,7 @@ namespace Duplicati.Library.Main.Operation } } - await restoredb.Transaction.CommitAsync().ConfigureAwait(false); + await restoredb.Transaction.CommitAsync(cancellationToken).ConfigureAwait(false); } /// @@ -788,8 +800,9 @@ namespace Duplicati.Library.Main.Operation /// /// The filename read and written. /// The database to query. + /// A cancellation token to cancel the operation. /// The volume id of the item. - public static async Task<(long, string)> ProbeForMatchingFilename(string filename, LocalRecreateDatabase restoredb) + public static async Task<(long, string)> ProbeForMatchingFilename(string filename, LocalRecreateDatabase restoredb, CancellationToken cancellationToken) { var p = VolumeBase.ParseFilename(filename); if (p != null) @@ -799,7 +812,7 @@ namespace Duplicati.Library.Main.Operation { var testfilename = VolumeBase.GenerateFilename(p.FileType, p.Prefix, p.Guid, p.Time, compmodule, encmodule); var tvid = await restoredb - .GetRemoteVolumeID(testfilename) + .GetRemoteVolumeID(testfilename, cancellationToken) .ConfigureAwait(false); if (tvid >= 0) diff --git a/Duplicati/Library/Main/Operation/RepairHandler.cs b/Duplicati/Library/Main/Operation/RepairHandler.cs index 1383e1fda..547abae58 100644 --- a/Duplicati/Library/Main/Operation/RepairHandler.cs +++ b/Duplicati/Library/Main/Operation/RepairHandler.cs @@ -66,11 +66,11 @@ namespace Duplicati.Library.Main.Operation try { await using var db = - await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, m_options.SqlitePageCache) + await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); knownRemotes = await db - .GetRemoteVolumes() - .CountAsync() + .GetRemoteVolumes(m_result.TaskControl.ProgressToken) + .CountAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } catch (Exception ex) @@ -134,50 +134,50 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress(0); await using var db = - await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, m_options.SqlitePageCache) + await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, m_options.SqlitePageCache, null, cancellationToken) .ConfigureAwait(false); try { - await Utility.UpdateOptionsFromDb(db, m_options) + await Utility.UpdateOptionsFromDb(db, m_options, cancellationToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, cancellationToken) .ConfigureAwait(false); - if (await db.PartiallyRecreated().ConfigureAwait(false)) + if (await db.PartiallyRecreated(cancellationToken).ConfigureAwait(false)) throw new UserInformationException("The database was only partially recreated. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsPartiallyRecreated"); - if (await db.RepairInProgress().ConfigureAwait(false)) + if (await db.RepairInProgress(cancellationToken).ConfigureAwait(false)) throw new UserInformationException("The database was attempted repaired, but the repair did not complete. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsInRepairState"); // Ensure the database is consistent before we start fixing the remote await db - .VerifyConsistencyForRepair(m_options.Blocksize, m_options.BlockhashSize, true) + .VerifyConsistencyForRepair(m_options.Blocksize, m_options.BlockhashSize, true, cancellationToken) .ConfigureAwait(false); // If the last backup failed, guard the incomplete fileset, so we can create a synthetic filelist var lastTempFilelist = await db - .GetLastIncompleteFilesetVolume() + .GetLastIncompleteFilesetVolume(cancellationToken) .ConfigureAwait(false); - var tp = await FilelistProcessor.RemoteListAnalysis(backendManager, m_options, db, m_result.BackendWriter, [lastTempFilelist.Name], null, FilelistProcessor.VerifyMode.VerifyAndCleanForced).ConfigureAwait(false); + var tp = await FilelistProcessor.RemoteListAnalysis(backendManager, m_options, db, m_result.BackendWriter, [lastTempFilelist.Name], null, FilelistProcessor.VerifyMode.VerifyAndCleanForced, cancellationToken).ConfigureAwait(false); await db.Transaction - .CommitAsync("CommitRemoteListAnalysisTransaction") + .CommitAsync("CommitRemoteListAnalysisTransaction", token: cancellationToken) .ConfigureAwait(false); var buffer = new byte[m_options.Blocksize]; var hashsize = m_options.BlockhashSize; var missingRemoteFilesets = await db - .MissingRemoteFilesets() + .MissingRemoteFilesets(cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); var missingLocalFilesets = await db - .MissingLocalFilesets() + .MissingLocalFilesets(cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); var emptyIndexFiles = await db - .EmptyIndexFiles() + .EmptyIndexFiles(cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -192,12 +192,12 @@ namespace Duplicati.Library.Main.Operation // Find the most recent timestamp from either a fileset or a remote volume var mostRecentLocal = await db - .GetRemoteVolumes() + .GetRemoteVolumes(cancellationToken) .Where(x => x.Type == RemoteVolumeType.Files) .Select(x => VolumeBase.ParseFilename(x.Name).Time.ToLocalTime()) - .Concat(db.FilesetTimes().Select(x => x.Value.ToLocalTime())) + .Concat(db.FilesetTimes(cancellationToken).Select(x => x.Value.ToLocalTime())) .Append(DateTime.MinValue) - .MaxAsync() + .MaxAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); var mostRecentRemote = tp.ParsedVolumes.Select(x => x.Time.ToLocalTime()).Append(DateTime.MinValue).Max(); @@ -231,7 +231,7 @@ namespace Duplicati.Library.Main.Operation if (tp.VerificationRequiredVolumes.Any()) { await using var testdb = - await LocalTestDatabase.CreateAsync(db) + await LocalTestDatabase.CreateAsync(db, null, cancellationToken) .ConfigureAwait(false); foreach (var n in tp.VerificationRequiredVolumes) @@ -242,11 +242,11 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(testdb, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) await testdb.Transaction - .CommitAsync("CommitEarlyExit", false) + .CommitAsync("CommitEarlyExit", false, cancellationToken) .ConfigureAwait(false); else await testdb.Transaction - .RollBackAsync() + .RollBackAsync(cancellationToken) .ConfigureAwait(false); return; } @@ -258,7 +258,7 @@ namespace Duplicati.Library.Main.Operation var (tf, hash, size) = await backendManager.GetWithInfoAsync(n.Name, n.Hash, n.Size, cancellationToken).ConfigureAwait(false); using (tf) res = - await TestHandler.TestVolumeInternals(testdb, n, tf, m_options, 1) + await TestHandler.TestVolumeInternals(testdb, n, tf, m_options, 1, cancellationToken) .ConfigureAwait(false); if (res.Value.Any()) @@ -268,7 +268,7 @@ namespace Duplicati.Library.Main.Operation { Logging.Log.WriteInformationMessage(LOGTAG, "CapturedRemoteFileHash", "Sucessfully captured hash for {0}, updating database", n.Name); await db - .UpdateRemoteVolume(n.Name, RemoteVolumeState.Verified, size, hash) + .UpdateRemoteVolume(n.Name, RemoteVolumeState.Verified, size, hash, cancellationToken) .ConfigureAwait(false); } @@ -281,7 +281,7 @@ namespace Duplicati.Library.Main.Operation } await db.Transaction - .CommitAsync("CommitVerificationTransaction") + .CommitAsync("CommitVerificationTransaction", true, cancellationToken) .ConfigureAwait(false); } @@ -294,11 +294,11 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) await db.Transaction - .CommitAsync("CommitEarlyExit", false) + .CommitAsync("CommitEarlyExit", false, cancellationToken) .ConfigureAwait(false); else await db.Transaction - .RollBackAsync() + .RollBackAsync(cancellationToken) .ConfigureAwait(false); return; } @@ -319,7 +319,7 @@ namespace Duplicati.Library.Main.Operation foreach (var rv in ifr.Volumes) { var entry = await db - .GetRemoteVolume(rv.Filename) + .GetRemoteVolume(rv.Filename, cancellationToken) .ConfigureAwait(false); if (entry.ID < 0) throw new Exception(string.Format("Unknown remote file {0} detected", rv.Filename)); @@ -331,44 +331,44 @@ namespace Duplicati.Library.Main.Operation throw new Exception(string.Format("Volume {0} hash/size mismatch ({1} - {2}) vs ({3} - {4})", rv.Filename, entry.Hash, entry.Size, rv.Hash, rv.Length)); await db - .CheckAllBlocksAreInVolume(rv.Filename, rv.Blocks) + .CheckAllBlocksAreInVolume(rv.Filename, rv.Blocks, cancellationToken) .ConfigureAwait(false); } var blocksize = m_options.Blocksize; foreach (var ixb in ifr.BlockLists) await db - .CheckBlocklistCorrect(ixb.Hash, ixb.Length, ixb.Blocklist, blocksize, hashsize) + .CheckBlocklistCorrect(ixb.Hash, ixb.Length, ixb.Blocklist, blocksize, hashsize, cancellationToken) .ConfigureAwait(false); // Register the new index file and link it to the block files var selfid = await db - .RegisterRemoteVolume(n.File.Name, RemoteVolumeType.Index, RemoteVolumeState.Uploading, size, new TimeSpan(0)) + .RegisterRemoteVolume(n.File.Name, RemoteVolumeType.Index, RemoteVolumeState.Uploading, size, new TimeSpan(0), cancellationToken) .ConfigureAwait(false); foreach (var rv in ifr.Volumes) { // Guard against unknown block files long id = await db - .GetRemoteVolumeID(rv.Filename) + .GetRemoteVolumeID(rv.Filename, cancellationToken) .ConfigureAwait(false); if (id == -1) Logging.Log.WriteWarningMessage(LOGTAG, "UnknownBlockFile", null, "Index file {0} references unknown block file: {1}", n.File.Name, rv.Filename); else await db - .AddIndexBlockLink(selfid, id) + .AddIndexBlockLink(selfid, id, cancellationToken) .ConfigureAwait(false); } if (!m_options.Dryrun) await db.Transaction - .CommitAsync("CommitIndexFileTransaction") + .CommitAsync("CommitIndexFileTransaction", true, cancellationToken) .ConfigureAwait(false); } // All checks fine, we accept the new index file Logging.Log.WriteInformationMessage(LOGTAG, "AcceptNewIndexFile", "Accepting new index file {0}", n.File.Name); await db - .UpdateRemoteVolume(n.File.Name, RemoteVolumeState.Verified, size, hash) + .UpdateRemoteVolume(n.File.Name, RemoteVolumeState.Verified, size, hash, cancellationToken) .ConfigureAwait(false); continue; @@ -382,7 +382,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) { await db - .RegisterRemoteVolume(n.File.Name, n.FileType, n.File.Size, RemoteVolumeState.Deleting) + .RegisterRemoteVolume(n.File.Name, n.FileType, n.File.Size, RemoteVolumeState.Deleting, cancellationToken) .ConfigureAwait(false); await backendManager.DeleteAsync(n.File.Name, n.File.Size, false, cancellationToken).ConfigureAwait(false); } @@ -408,11 +408,11 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) await db.Transaction - .CommitAsync("CommitEarlyExit", false) + .CommitAsync("CommitEarlyExit", false, cancellationToken) .ConfigureAwait(false); else await db.Transaction - .RollBackAsync() + .RollBackAsync(cancellationToken) .ConfigureAwait(false); return; } @@ -432,17 +432,17 @@ namespace Duplicati.Library.Main.Operation fsw.CreateFilesetFile(isfull); await db - .WriteFileset(fsw, filesetId) + .WriteFileset(fsw, filesetId, cancellationToken) .ConfigureAwait(false); fsw.Close(); fsw.VolumeID = await db - .RegisterRemoteVolume(fsw.RemoteFilename, RemoteVolumeType.Files, -1, RemoteVolumeState.Temporary) + .RegisterRemoteVolume(fsw.RemoteFilename, RemoteVolumeType.Files, -1, RemoteVolumeState.Temporary, cancellationToken) .ConfigureAwait(false); await db - .LinkFilesetToVolume(filesetId, fsw.VolumeID) + .LinkFilesetToVolume(filesetId, fsw.VolumeID, cancellationToken) .ConfigureAwait(false); if (m_options.Dryrun) @@ -460,39 +460,39 @@ namespace Duplicati.Library.Main.Operation await backendManager.FlushPendingMessagesAsync(db, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) await db.Transaction - .CommitAsync("CommitFilesetTransaction") + .CommitAsync("CommitFilesetTransaction", token: cancellationToken) .ConfigureAwait(false); } foreach (var volumename in missingLocalFilesets) { var remoteVolume = await db - .GetRemoteVolume(volumename) + .GetRemoteVolume(volumename, cancellationToken) .ConfigureAwait(false); using var tmpfile = await backendManager.GetAsync(remoteVolume.Name, remoteVolume.Hash, remoteVolume.Size, cancellationToken).ConfigureAwait(false); var parsed = VolumeBase.ParseFilename(remoteVolume.Name); using var stream = new FileStream(tmpfile, FileMode.Open, FileAccess.Read, FileShare.Read); using var compressor = DynamicLoader.CompressionLoader.GetModule(parsed.CompressionModule, stream, ArchiveMode.Read, m_options.RawOptions); - await using var recreatedb = await LocalRecreateDatabase.CreateAsync(db, m_options).ConfigureAwait(false); + await using var recreatedb = await LocalRecreateDatabase.CreateAsync(db, m_options, null, cancellationToken).ConfigureAwait(false); if (compressor == null) throw new UserInformationException(string.Format("Failed to load compression module: {0}", parsed.CompressionModule), "FailedToLoadCompressionModule"); var filesetid = await db - .CreateFileset(remoteVolume.ID, parsed.Time) + .CreateFileset(remoteVolume.ID, parsed.Time, cancellationToken) .ConfigureAwait(false); - await RecreateDatabaseHandler.RecreateFilesetFromRemoteList(recreatedb, compressor, filesetid, m_options, new FilterExpression()) + await RecreateDatabaseHandler.RecreateFilesetFromRemoteList(recreatedb, compressor, filesetid, m_options, new FilterExpression(), cancellationToken) .ConfigureAwait(false); if (!m_options.Dryrun) await recreatedb.Transaction - .CommitAsync("CommitRecreateFilesetTransaction") + .CommitAsync("CommitRecreateFilesetTransaction", token: cancellationToken) .ConfigureAwait(false); } if (!m_options.Dryrun && tp.MissingVolumes.Any()) await db - .TerminatedWithActiveUploads(true) + .TerminatedWithActiveUploads(cancellationToken, true) .ConfigureAwait(false); if (tp.MissingVolumes.Any(x => x.Type != RemoteVolumeType.Index && x.Type != RemoteVolumeType.Files && x.Type != RemoteVolumeType.Blocks)) @@ -548,7 +548,7 @@ namespace Duplicati.Library.Main.Operation try { // Check if the index file has already been deleted, beause the dblock was recreated - var currentState = (await db.GetRemoteVolume(n.Name).ConfigureAwait(false)).State; + var currentState = (await db.GetRemoteVolume(n.Name, cancellationToken).ConfigureAwait(false)).State; if (currentState == RemoteVolumeState.Deleted || currentState == RemoteVolumeState.Temporary || currentState == RemoteVolumeState.Deleting) continue; @@ -577,7 +577,7 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); // Implicit rollback await db.Transaction - .RollBackAsync() + .RollBackAsync(cancellationToken) .ConfigureAwait(false); return; } @@ -604,7 +604,7 @@ namespace Duplicati.Library.Main.Operation // FlushPendingMessagesAsync, but maybe it // is not needed anymore. await db.Transaction - .CommitAsync() + .CommitAsync(cancellationToken) .ConfigureAwait(false); } } @@ -628,23 +628,23 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) { await db - .TerminatedWithActiveUploads(false) + .TerminatedWithActiveUploads(cancellationToken, false) .ConfigureAwait(false); await db.Transaction - .CommitAsync("CommitRepairTransaction") + .CommitAsync("CommitRepairTransaction", true, cancellationToken) .ConfigureAwait(false); } else { await db.Transaction - .RollBackAsync("RollbackRepairTransaction") + .RollBackAsync("RollbackRepairTransaction", true, cancellationToken) .ConfigureAwait(false); } } catch (Exception) { // Implicit rollback - await db.Transaction.RollBackAsync().ConfigureAwait(false); + await db.Transaction.RollBackAsync(cancellationToken).ConfigureAwait(false); throw; } } @@ -662,10 +662,10 @@ namespace Duplicati.Library.Main.Operation private async Task RunRepairDlist(IBackendManager backendManager, LocalRepairDatabase db, FilesetVolumeWriter volumeWriter, RemoteVolumeEntry originalVolume, DateTime filesetTime, CancellationToken cancellationToken) { volumeWriter.VolumeID = await db - .RegisterRemoteVolume(volumeWriter.RemoteFilename, RemoteVolumeType.Files, RemoteVolumeState.Temporary, -1, TimeSpan.Zero) + .RegisterRemoteVolume(volumeWriter.RemoteFilename, RemoteVolumeType.Files, RemoteVolumeState.Temporary, -1, TimeSpan.Zero, cancellationToken) .ConfigureAwait(false); (var prevFilesetId, var _, var isPrevFull) = await db - .GetFilesetFromRemotename(originalVolume.Name) + .GetFilesetFromRemotename(originalVolume.Name, cancellationToken) .ConfigureAwait(false); if (!string.IsNullOrEmpty(m_options.ControlFiles)) @@ -674,28 +674,28 @@ namespace Duplicati.Library.Main.Operation volumeWriter.CreateFilesetFile(isPrevFull); var newFilesetID = await db - .CreateFileset(volumeWriter.VolumeID, filesetTime) + .CreateFileset(volumeWriter.VolumeID, filesetTime, cancellationToken) .ConfigureAwait(false); await db - .UpdateFullBackupStateInFileset(newFilesetID, isPrevFull) + .UpdateFullBackupStateInFileset(newFilesetID, isPrevFull, cancellationToken) .ConfigureAwait(false); await db - .LinkFilesetToVolume(newFilesetID, volumeWriter.VolumeID) + .LinkFilesetToVolume(newFilesetID, volumeWriter.VolumeID, cancellationToken) .ConfigureAwait(false); await db - .MoveFilesFromFileset(newFilesetID, prevFilesetId) + .MoveFilesFromFileset(newFilesetID, prevFilesetId, cancellationToken) .ConfigureAwait(false); await db - .WriteFileset(volumeWriter, newFilesetID) + .WriteFileset(volumeWriter, newFilesetID, cancellationToken) .ConfigureAwait(false); volumeWriter.Close(); await db - .UpdateRemoteVolume(volumeWriter.RemoteFilename, RemoteVolumeState.Uploading, -1, null) + .UpdateRemoteVolume(volumeWriter.RemoteFilename, RemoteVolumeState.Uploading, -1, null, cancellationToken) .ConfigureAwait(false); await db - .UpdateRemoteVolume(originalVolume.Name, RemoteVolumeState.Deleted, originalVolume.Size, originalVolume.Hash, false, TimeSpan.FromHours(2), null) + .UpdateRemoteVolume(originalVolume.Name, RemoteVolumeState.Deleted, originalVolume.Size, originalVolume.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) .ConfigureAwait(false); await backendManager.FlushPendingMessagesAsync(db, cancellationToken).ConfigureAwait(false); @@ -704,7 +704,7 @@ namespace Duplicati.Library.Main.Operation else { await db.Transaction - .CommitAsync("CommitPriorToFilesetUpload") + .CommitAsync("CommitPriorToFilesetUpload", true, cancellationToken) .ConfigureAwait(false); await backendManager.PutAsync(volumeWriter, null, null, false, null, cancellationToken).ConfigureAwait(false); } @@ -712,7 +712,7 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) await db.Transaction - .CommitAsync("CommitFilesetTransaction") + .CommitAsync("CommitFilesetTransaction", true, cancellationToken) .ConfigureAwait(false); } @@ -729,26 +729,26 @@ namespace Duplicati.Library.Main.Operation public static async Task RunRepairDindex(IBackendManager backendManager, LocalRepairDatabase db, IndexVolumeWriter indexWriter, IRemoteVolume originalVolume, Options options, CancellationToken cancellationToken) { indexWriter.VolumeID = await db - .RegisterRemoteVolume(indexWriter.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Uploading, -1, TimeSpan.Zero) + .RegisterRemoteVolume(indexWriter.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Uploading, -1, TimeSpan.Zero, cancellationToken) .ConfigureAwait(false); var blockvolumeids = new List(); using var h = HashFactory.CreateHasher(options.BlockHashAlgorithm); - await foreach (var blockvolume in db.GetBlockVolumesFromIndexName(originalVolume.Name).ConfigureAwait(false)) + await foreach (var blockvolume in db.GetBlockVolumesFromIndexName(originalVolume.Name, cancellationToken).ConfigureAwait(false)) { indexWriter.StartVolume(blockvolume.Name); var volumeid = await db - .GetRemoteVolumeID(blockvolume.Name) + .GetRemoteVolumeID(blockvolume.Name, cancellationToken) .ConfigureAwait(false); - await foreach (var b in db.GetBlocks(volumeid).ConfigureAwait(false)) + await foreach (var b in db.GetBlocks(volumeid, cancellationToken).ConfigureAwait(false)) indexWriter.AddBlock(b.Hash, b.Size); indexWriter.FinishVolume(blockvolume.Hash, blockvolume.Size); blockvolumeids.Add(volumeid); if (options.IndexfilePolicy == Options.IndexFileStrategy.Full) - await foreach (var b in db.GetBlocklists(volumeid, options.Blocksize, options.BlockhashSize).ConfigureAwait(false)) + await foreach (var b in db.GetBlocklists(volumeid, options.Blocksize, options.BlockhashSize, cancellationToken).ConfigureAwait(false)) { var bh = Convert.ToBase64String(h.ComputeHash(b.Buffer, 0, b.Size)); if (bh != b.Hash) @@ -760,7 +760,7 @@ namespace Duplicati.Library.Main.Operation foreach (var blockvolumeid in blockvolumeids) await db - .AddIndexBlockLink(indexWriter.VolumeID, blockvolumeid) + .AddIndexBlockLink(indexWriter.VolumeID, blockvolumeid, cancellationToken) .ConfigureAwait(false); indexWriter.Close(); @@ -770,7 +770,7 @@ namespace Duplicati.Library.Main.Operation else { await db - .UpdateRemoteVolume(originalVolume.Name, RemoteVolumeState.Deleted, originalVolume.Size, originalVolume.Hash, false, TimeSpan.FromHours(2), null) + .UpdateRemoteVolume(originalVolume.Name, RemoteVolumeState.Deleted, originalVolume.Size, originalVolume.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) .ConfigureAwait(false); await backendManager.FlushPendingMessagesAsync(db, cancellationToken).ConfigureAwait(false); await backendManager.PutAsync(indexWriter, null, null, false, null, cancellationToken).ConfigureAwait(false); @@ -779,7 +779,7 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); if (!options.Dryrun) await db.Transaction - .CommitAsync("CommitRepairTransaction") + .CommitAsync("CommitRepairTransaction", true, cancellationToken) .ConfigureAwait(false); } @@ -835,12 +835,13 @@ namespace Duplicati.Library.Main.Operation /// /// The options for the current operation. /// The database to use for registering the initial volume. + /// Cancellation token to cancel the operation. /// A task that completes with the new instance of . - public static async Task CreateAsync(Options options, LocalDatabase db) + public static async Task CreateAsync(Options options, LocalDatabase db, CancellationToken cancellationToken) { var ipdv = new InProgressDblockVolumes(options, db); ipdv.m_activeWriter.VolumeID = await db - .RegisterRemoteVolume(ipdv.m_activeWriter.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, -1, TimeSpan.Zero) + .RegisterRemoteVolume(ipdv.m_activeWriter.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, -1, TimeSpan.Zero, cancellationToken) .ConfigureAwait(false); return ipdv; } @@ -879,21 +880,24 @@ namespace Duplicati.Library.Main.Operation /// The offset in the buffer where the block data starts. /// The size of the block data. /// The compression hint for the block. - public async Task AddBlock(string hash, byte[] buffer, int offset, int size, CompressionHint hint) + /// Cancellation token to cancel the operation. + /// A task that completes when the block is added. + public async Task AddBlock(string hash, byte[] buffer, int offset, int size, CompressionHint hint, CancellationToken cancellationToken) { await m_activeWriter .AddBlock(hash, buffer, offset, size, hint) .ConfigureAwait(false); m_anyData = true; if (IsFull) - await StartNewVolume().ConfigureAwait(false); + await StartNewVolume(cancellationToken).ConfigureAwait(false); } /// /// Starts a new volume for the current operation. /// + /// Cancellation token to cancel the operation. /// A task that completes when the new volume is started. - public async Task StartNewVolume() + public async Task StartNewVolume(CancellationToken cancellationToken) { if (!m_anyData) return; @@ -902,7 +906,7 @@ namespace Duplicati.Library.Main.Operation m_activeWriter = new BlockVolumeWriter(m_options); m_activeWriter.VolumeID = await m_database - .RegisterRemoteVolume(m_activeWriter.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, -1, TimeSpan.Zero) + .RegisterRemoteVolume(m_activeWriter.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, -1, TimeSpan.Zero, cancellationToken) .ConfigureAwait(false); m_anyData = false; } @@ -920,7 +924,7 @@ namespace Duplicati.Library.Main.Operation private async Task RunRepairDblocks(IBackendManager backendManager, LocalRepairDatabase db, IEnumerable missingDblockFiles, Action incrementProgress, CancellationToken cancellationToken) { var currentVolume = - await InProgressDblockVolumes.CreateAsync(m_options, db) + await InProgressDblockVolumes.CreateAsync(m_options, db, cancellationToken) .ConfigureAwait(false); foreach (var n in missingDblockFiles) @@ -932,7 +936,7 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) await db.Transaction - .CommitAsync("CommitEarlyExit", false) + .CommitAsync("CommitEarlyExit", false, cancellationToken) .ConfigureAwait(false); return; } @@ -953,13 +957,13 @@ namespace Duplicati.Library.Main.Operation // If we have any data in the volume, complete it so it will be uploaded if (currentVolume.AnyData) - await currentVolume.StartNewVolume().ConfigureAwait(false); + await currentVolume.StartNewVolume(cancellationToken).ConfigureAwait(false); // Complete any pending uploads await UploadCompletedBlockVolumes(backendManager, currentVolume, db, cancellationToken).ConfigureAwait(false); // After reset, we need to remove the temporary volume from the database await db - .RemoveRemoteVolume(currentVolume.RemoteFilename) + .RemoveRemoteVolume(currentVolume.RemoteFilename, cancellationToken) .ConfigureAwait(false); } @@ -976,7 +980,7 @@ namespace Duplicati.Library.Main.Operation foreach (var completedVolume in activeVolume.CompletedWriters) { await db - .UpdateRemoteVolume(completedVolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null) + .UpdateRemoteVolume(completedVolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null, cancellationToken) .ConfigureAwait(false); // Create a new index file that points to the new volume @@ -986,19 +990,19 @@ namespace Duplicati.Library.Main.Operation { newvolindex = new IndexVolumeWriter(m_options); newvolindex.VolumeID = await db - .RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary) + .RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, cancellationToken) .ConfigureAwait(false); newvolindex.StartVolume(completedVolume.RemoteFilename); - await foreach (var b in db.GetBlocks(completedVolume.VolumeID).ConfigureAwait(false)) + await foreach (var b in db.GetBlocks(completedVolume.VolumeID, cancellationToken).ConfigureAwait(false)) newvolindex.AddBlock(b.Hash, b.Size); await db - .AddIndexBlockLink(newvolindex.VolumeID, completedVolume.VolumeID) + .AddIndexBlockLink(newvolindex.VolumeID, completedVolume.VolumeID, cancellationToken) .ConfigureAwait(false); if (m_options.IndexfilePolicy == Options.IndexFileStrategy.Full) indexVolumeFinished = async () => { - await foreach (var blocklist in db.GetBlocklists(completedVolume.VolumeID, m_options.Blocksize, m_options.BlockhashSize).ConfigureAwait(false)) + await foreach (var blocklist in db.GetBlocklists(completedVolume.VolumeID, m_options.Blocksize, m_options.BlockhashSize, cancellationToken).ConfigureAwait(false)) newvolindex.WriteBlocklist(blocklist.Item1, blocklist.Item2, 0, blocklist.Item3); }; } @@ -1010,7 +1014,7 @@ namespace Duplicati.Library.Main.Operation else { await db.Transaction - .CommitAsync("PostRepairPreUploadBlockVolume") + .CommitAsync("PostRepairPreUploadBlockVolume", token: cancellationToken) .ConfigureAwait(false); await backendManager.PutAsync(completedVolume, newvolindex, indexVolumeFinished, false, null, cancellationToken).ConfigureAwait(false); } @@ -1022,13 +1026,13 @@ namespace Duplicati.Library.Main.Operation // Prepare for deleting the old stuff foreach (var vol in activeVolume.VolumesToDelete) await db - .UpdateRemoteVolume(vol.Name, RemoteVolumeState.Deleting, vol.Size, vol.Hash, false, TimeSpan.FromHours(2), null) + .UpdateRemoteVolume(vol.Name, RemoteVolumeState.Deleting, vol.Size, vol.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) .ConfigureAwait(false); // Persist desired state prior to deleting the old files if (!m_options.Dryrun) await db.Transaction - .CommitAsync("PostRepairBlockVolume") + .CommitAsync("PostRepairBlockVolume", token: cancellationToken) .ConfigureAwait(false); // Delete the old files @@ -1042,7 +1046,7 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) await db.Transaction - .CommitAsync("PostRepairBlockVolume") + .CommitAsync("PostRepairBlockVolume", token: cancellationToken) .ConfigureAwait(false); activeVolume.CompletedWriters.Clear(); @@ -1064,18 +1068,18 @@ namespace Duplicati.Library.Main.Operation // as data can be either file contents, metadata or blocklist hashes // We attempt to recover all three source parts in the steps below await using var mbl = await db - .CreateBlockList(originalVolume.Name) + .CreateBlockList(originalVolume.Name, cancellationToken) .ConfigureAwait(false); var originalMissingBlockCount = await mbl - .GetMissingBlockCount() + .GetMissingBlockCount(cancellationToken) .ConfigureAwait(false); var recoveredSourceBlocks = 0L; // First we grab all known blocks from local files string lastRestoredHash = null; long lastRestoredSize = -1; - await foreach (var block in mbl.GetSourceFilesWithBlocks(m_options.Blocksize).ConfigureAwait(false)) + await foreach (var block in mbl.GetSourceFilesWithBlocks(m_options.Blocksize, cancellationToken).ConfigureAwait(false)) { if (block.Hash == lastRestoredHash && block.Size == lastRestoredSize) continue; @@ -1111,11 +1115,11 @@ namespace Duplicati.Library.Main.Operation // Found it, no need to look again lastRestoredHash = block.Hash; lastRestoredSize = block.Size; - if (await mbl.SetBlockRestored(block.Hash, block.Size, pendingVolume.VolumeID).ConfigureAwait(false)) + if (await mbl.SetBlockRestored(block.Hash, block.Size, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) { recoveredSourceBlocks++; await pendingVolume - .AddBlock(block.Hash, buffer, 0, size, CompressionHint.Default) + .AddBlock(block.Hash, buffer, 0, size, CompressionHint.Default, cancellationToken) .ConfigureAwait(false); } } @@ -1126,7 +1130,7 @@ namespace Duplicati.Library.Main.Operation } // Then grab all blocks with metadata - await foreach (var block in mbl.GetSourceItemsWithMetadataBlocks().ConfigureAwait(false)) + await foreach (var block in mbl.GetSourceItemsWithMetadataBlocks(cancellationToken).ConfigureAwait(false)) { if (block.Hash == lastRestoredHash && block.Size == lastRestoredSize) continue; @@ -1162,11 +1166,11 @@ namespace Duplicati.Library.Main.Operation // Found it, no need to look again lastRestoredHash = block.Hash; lastRestoredSize = block.Size; - if (await mbl.SetBlockRestored(block.Hash, block.Size, pendingVolume.VolumeID).ConfigureAwait(false)) + if (await mbl.SetBlockRestored(block.Hash, block.Size, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) { recoveredSourceBlocks++; await pendingVolume - .AddBlock(block.Hash, metahash.Blob, 0, metahash.Blob.Length, CompressionHint.Default) + .AddBlock(block.Hash, metahash.Blob, 0, metahash.Blob.Length, CompressionHint.Default, cancellationToken) .ConfigureAwait(false); } } @@ -1201,9 +1205,9 @@ namespace Duplicati.Library.Main.Operation if (resulthash == lastBlocklist.BlocklistHash && targetsize == blocklistHash.Length) { - if (await mbl.SetBlockRestored(resulthash, blocklistHash.Length, pendingVolume.VolumeID).ConfigureAwait(false)) + if (await mbl.SetBlockRestored(resulthash, blocklistHash.Length, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) await pendingVolume - .AddBlock(resulthash, blocklistHash.ToArray(), 0, (int)blocklistHash.Length, CompressionHint.Default) + .AddBlock(resulthash, blocklistHash.ToArray(), 0, (int)blocklistHash.Length, CompressionHint.Default, cancellationToken) .ConfigureAwait(false); } else @@ -1214,7 +1218,7 @@ namespace Duplicati.Library.Main.Operation blocklistHash.SetLength(0); } - await foreach (var blocklist in mbl.GetBlocklistHashes(hashesPerBlock).ConfigureAwait(false)) + await foreach (var blocklist in mbl.GetBlocklistHashes(hashesPerBlock, cancellationToken).ConfigureAwait(false)) { lastBlocklist ??= blocklist; @@ -1231,7 +1235,7 @@ namespace Duplicati.Library.Main.Operation await EmitBlockListBlock().ConfigureAwait(false); //Then we grab all remote volumes that have the missing blocks - await foreach (var (tmpfile, _, _, name) in backendManager.GetFilesOverlappedAsync(await mbl.GetMissingBlockSources().ToListAsync(cancellationToken: cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false)) + await foreach (var (tmpfile, _, _, name) in backendManager.GetFilesOverlappedAsync(await mbl.GetMissingBlockSources(cancellationToken).ToListAsync(cancellationToken: cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false)) { try { @@ -1239,10 +1243,10 @@ namespace Duplicati.Library.Main.Operation using (tmpfile) using (var f = new BlockVolumeReader(RestoreHandler.GetCompressionModule(name), tmpfile, m_options)) foreach (var b in f.Blocks) - if (await mbl.SetBlockRestored(b.Key, b.Value, pendingVolume.VolumeID).ConfigureAwait(false)) + if (await mbl.SetBlockRestored(b.Key, b.Value, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) if (f.ReadBlock(b.Key, buffer) == b.Value) await pendingVolume - .AddBlock(b.Key, buffer, 0, (int)b.Value, CompressionHint.Default) + .AddBlock(b.Key, buffer, 0, (int)b.Value, CompressionHint.Default, cancellationToken) .ConfigureAwait(false); } catch (Exception ex) @@ -1253,13 +1257,13 @@ namespace Duplicati.Library.Main.Operation } var missingBlocks = await mbl - .GetMissingBlockCount() + .GetMissingBlockCount(cancellationToken) .ConfigureAwait(false); var recoveredBlocks = originalMissingBlockCount - missingBlocks; if (recoveredBlocks == 0 || (m_options.DisablePartialDblockRecovery && missingBlocks > 0)) { Logging.Log.WriteInformationMessage(LOGTAG, "RepairMissingBlocks", "Repair cannot acquire {0} required blocks for volume {1}, which are required by the following filesets: ", missingBlocks, originalVolume.Name); - await foreach (var f in mbl.GetFilesetsUsingMissingBlocks().ConfigureAwait(false)) + await foreach (var f in mbl.GetFilesetsUsingMissingBlocks(cancellationToken).ConfigureAwait(false)) Logging.Log.WriteInformationMessage(LOGTAG, "AffectedFilesetName", f.Name); var recoverymsg = string.Format("If you want to continue working with the database, you can use the \"{0}\" and \"{1}\" commands to purge the missing data from the database and the remote storage.", "list-broken-files", "purge-broken-files"); @@ -1280,7 +1284,7 @@ namespace Duplicati.Library.Main.Operation { pendingVolume.VolumesToDelete.Add(originalVolume); var oldIndexFiles = await db - .GetIndexFilesReferencingBlockFile(originalVolume.ID) + .GetIndexFilesReferencingBlockFile(originalVolume.ID, cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -1288,12 +1292,12 @@ namespace Duplicati.Library.Main.Operation foreach (var oldIndexFile in oldIndexFiles) { var oldVolume = await db - .GetRemoteVolume(oldIndexFile) + .GetRemoteVolume(oldIndexFile, cancellationToken) .ConfigureAwait(false); if (oldVolume.State == RemoteVolumeState.Uploading || oldVolume.State == RemoteVolumeState.Uploaded || oldVolume.State == RemoteVolumeState.Verified) { var blockVolumesReferenced = await db - .GetBlockVolumesFromIndexName(oldIndexFile) + .GetBlockVolumesFromIndexName(oldIndexFile, cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); if (blockVolumesReferenced.Any(x => x.Name != originalVolume.Name)) @@ -1322,12 +1326,12 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); await using var db = - await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, m_options.SqlitePageCache) + await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var sets = await db - .GetFilesetsWithMissingFiles() - .ToListAsync() + .GetFilesetsWithMissingFiles(m_result.TaskControl.ProgressToken) + .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (sets.Count == 0) @@ -1340,7 +1344,7 @@ namespace Duplicati.Library.Main.Operation ix++; Logging.Log.WriteInformationMessage(LOGTAG, "RepairingBrokenFileset", "Repairing broken fileset {0} of {1}: {2}", ix, sets.Count, entry.Value); var volume = await db - .GetRemoteVolumeFromFilesetID(entry.Key) + .GetRemoteVolumeFromFilesetID(entry.Key, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var parsed = VolumeBase.ParseFilename(volume.Name); using var tmpfile = await backendManager.GetAsync(volume.Name, volume.Hash, volume.Size, CancellationToken.None).ConfigureAwait(false); @@ -1350,15 +1354,17 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(string.Format("Failed to load compression module: {0}", parsed.CompressionModule), "FailedToLoadCompressionModule"); // Clear out the old fileset - await db.DeleteFilesetEntries(entry.Key).ConfigureAwait(false); - await using (var rdb = await LocalRecreateDatabase.CreateAsync(db, m_options).ConfigureAwait(false)) + await db + .DeleteFilesetEntries(entry.Key, m_result.TaskControl.ProgressToken) + .ConfigureAwait(false); + await using (var rdb = await LocalRecreateDatabase.CreateAsync(db, m_options, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await RecreateDatabaseHandler.RecreateFilesetFromRemoteList(rdb, compressor, entry.Key, m_options, new FilterExpression()) + await RecreateDatabaseHandler.RecreateFilesetFromRemoteList(rdb, compressor, entry.Key, m_options, new FilterExpression(), m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } await db.Transaction - .CommitAsync("PostRepairFileset") + .CommitAsync("PostRepairFileset", true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -1377,22 +1383,22 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress(0); await using var db = - await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, m_options.SqlitePageCache) + await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options) + await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - if (await db.RepairInProgress().ConfigureAwait(false) || await db.PartiallyRecreated().ConfigureAwait(false)) + if (await db.RepairInProgress(m_result.TaskControl.ProgressToken).ConfigureAwait(false) || await db.PartiallyRecreated(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) Logging.Log.WriteWarningMessage(LOGTAG, "InProgressDatabase", null, "The database is marked as \"in-progress\" and may be incomplete."); - await db.FixDuplicateMetahash().ConfigureAwait(false); - await db.FixDuplicateFileentries().ConfigureAwait(false); + await db.FixDuplicateMetahash(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await db.FixDuplicateFileentries(m_result.TaskControl.ProgressToken).ConfigureAwait(false); await db - .FixDuplicateBlocklistHashes(m_options.Blocksize, m_options.BlockhashSize) + .FixDuplicateBlocklistHashes(m_options.Blocksize, m_options.BlockhashSize, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db - .FixMissingBlocklistHashes(m_options.BlockHashAlgorithm, m_options.Blocksize) + .FixMissingBlocklistHashes(m_options.BlockHashAlgorithm, m_options.Blocksize, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } diff --git a/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs b/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs index d3d18639e..80658a033 100644 --- a/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs @@ -46,7 +46,14 @@ namespace Duplicati.Library.Main.Operation Directory.CreateDirectory(m_options.Restorepath); using var tmpdb = new TempFile(); - await using var db = await Database.LocalDatabase.CreateLocalDatabaseAsync(File.Exists(m_options.Dbpath) ? m_options.Dbpath : (string)tmpdb, "RestoreControlFiles", true, m_options.SqlitePageCache).ConfigureAwait(false); + await using var db = await Database.LocalDatabase.CreateLocalDatabaseAsync( + File.Exists(m_options.Dbpath) ? m_options.Dbpath : (string)tmpdb, "RestoreControlFiles", + true, + m_options.SqlitePageCache, + null, + m_result.TaskControl.ProgressToken + ).ConfigureAwait(false); + var filter = JoinedFilterExpression.Join(new FilterExpression(filterstrings), compositefilter); try @@ -68,7 +75,7 @@ namespace Duplicati.Library.Main.Operation var file = fileversion.Value.File; var entry = await db - .GetRemoteVolume(file.Name) + .GetRemoteVolume(file.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var res = new List(); diff --git a/Duplicati/Library/Main/Operation/RestoreHandler.cs b/Duplicati/Library/Main/Operation/RestoreHandler.cs index c6c9af53e..6d3b26311 100644 --- a/Duplicati/Library/Main/Operation/RestoreHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreHandler.cs @@ -115,7 +115,7 @@ namespace Duplicati.Library.Main.Operation { if (!m_options.NoLocalDb && SystemIO.IO_OS.FileExists(m_options.Dbpath)) { - db = await LocalRestoreDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache) + db = await LocalRestoreDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } else @@ -123,7 +123,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "NoLocalDatabase", "No local database, building a temporary database"); tmpdb = new TempFile(); RecreateDatabaseHandler.NumberedFilterFilelistDelegate filelistfilter = FilterNumberedFilelist(m_options.Time, m_options.Version); - db = await LocalRestoreDatabase.CreateAsync(tmpdb, m_options.SqlitePageCache) + db = await LocalRestoreDatabase.CreateAsync(tmpdb, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); m_result.RecreateDatabaseResults = new RecreateDatabaseResults(m_result); using (new Logging.Timer(LOGTAG, "RecreateTempDbForRestore", "Recreate temporary database for restore")) @@ -152,16 +152,16 @@ namespace Duplicati.Library.Main.Operation } } - private static async Task PatchWithBlocklist(LocalRestoreDatabase database, BlockVolumeReader blocks, Options options, RestoreResults result, byte[] blockbuffer, RestoreHandlerMetadataStorage metadatastorage) + private static async Task PatchWithBlocklist(LocalRestoreDatabase database, BlockVolumeReader blocks, Options options, RestoreResults result, byte[] blockbuffer, RestoreHandlerMetadataStorage metadatastorage, CancellationToken cancellationToken) { var blocksize = options.Blocksize; var updateCounter = 0L; var fullblockverification = options.FullBlockVerification; using var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm); - await using var blockmarker = await database.CreateBlockMarkerAsync().ConfigureAwait(false); - await using var volumekeeper = await database.GetMissingBlockData(blocks, options.Blocksize).ConfigureAwait(false); - await foreach (var restorelist in volumekeeper.FilesWithMissingBlocks().ConfigureAwait(false)) + await using var blockmarker = await database.CreateBlockMarkerAsync(cancellationToken).ConfigureAwait(false); + await using var volumekeeper = await database.GetMissingBlockData(blocks, options.Blocksize, cancellationToken).ConfigureAwait(false); + await foreach (var restorelist in volumekeeper.FilesWithMissingBlocks(cancellationToken).ConfigureAwait(false)) { var targetpath = restorelist.Path; @@ -185,7 +185,7 @@ namespace Duplicati.Library.Main.Operation // TODO: Much faster if we iterate the volume and checks what blocks are used, // because the compressors usually like sequential reading using (var file = SystemIO.IO_OS.FileOpenWrite(targetpath)) - await foreach (var targetblock in restorelist.Blocks().ConfigureAwait(false)) + await foreach (var targetblock in restorelist.Blocks(cancellationToken).ConfigureAwait(false)) { file.Position = targetblock.Offset; var size = blocks.ReadBlock(targetblock.Key, blockbuffer); @@ -205,7 +205,7 @@ namespace Duplicati.Library.Main.Operation { file.Write(blockbuffer, 0, size); await blockmarker - .SetBlockRestored(restorelist.FileID, targetblock.Offset / blocksize, targetblock.Key, size, false) + .SetBlockRestored(restorelist.FileID, targetblock.Offset / blocksize, targetblock.Key, size, false, cancellationToken) .ConfigureAwait(false); } } @@ -217,7 +217,7 @@ namespace Duplicati.Library.Main.Operation if ((++updateCounter) % 20 == 0) await blockmarker - .UpdateProcessed(result.OperationProgressUpdater) + .UpdateProcessed(result.OperationProgressUpdater, cancellationToken) .ConfigureAwait(false); } catch (Exception ex) @@ -231,7 +231,7 @@ namespace Duplicati.Library.Main.Operation if (!options.SkipMetadata) { - await foreach (var restoremetadata in volumekeeper.MetadataWithMissingBlocks().ConfigureAwait(false)) + await foreach (var restoremetadata in volumekeeper.MetadataWithMissingBlocks(cancellationToken).ConfigureAwait(false)) { var targetpath = restoremetadata.Path; Logging.Log.WriteVerboseMessage(LOGTAG, "RecordingMetadata", "Recording metadata from remote data: {0}", targetpath); @@ -241,7 +241,7 @@ namespace Duplicati.Library.Main.Operation // TODO: When we support multi-block metadata this needs to deal with it using (var ms = new System.IO.MemoryStream()) { - await foreach (var targetblock in restoremetadata.Blocks().ConfigureAwait(false)) + await foreach (var targetblock in restoremetadata.Blocks(cancellationToken).ConfigureAwait(false)) { ms.Position = targetblock.Offset; var size = blocks.ReadBlock(targetblock.Key, blockbuffer); @@ -249,7 +249,7 @@ namespace Duplicati.Library.Main.Operation { ms.Write(blockbuffer, 0, size); await blockmarker - .SetBlockRestored(restoremetadata.FileID, targetblock.Offset / blocksize, targetblock.Key, size, true) + .SetBlockRestored(restoremetadata.FileID, targetblock.Offset / blocksize, targetblock.Key, size, true, cancellationToken) .ConfigureAwait(false); } } @@ -268,9 +268,9 @@ namespace Duplicati.Library.Main.Operation } } await blockmarker - .UpdateProcessed(result.OperationProgressUpdater) + .UpdateProcessed(result.OperationProgressUpdater, cancellationToken) .ConfigureAwait(false); - await blockmarker.CommitAsync().ConfigureAwait(false); + await blockmarker.CommitAsync(cancellationToken).ConfigureAwait(false); } private static void ApplyStoredMetadata(Options options, RestoreHandlerMetadataStorage metadatastorage) @@ -316,16 +316,16 @@ namespace Duplicati.Library.Main.Operation private async Task DoRunNewAsync(IBackendManager backendManager, LocalRestoreDatabase database, Library.Utility.IFilter filter, CancellationToken cancellationToken) { // Perform initial setup - await Utility.UpdateOptionsFromDb(database, m_options) + await Utility.UpdateOptionsFromDb(database, m_options, cancellationToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(database, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(database, m_options, cancellationToken) .ConfigureAwait(false); // Verify the backend if necessary if (!m_options.NoBackendverification) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PreRestoreVerify); - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, cancellationToken).ConfigureAwait(false); } // Prepare the block and file list and create the directory structure @@ -339,7 +339,7 @@ namespace Duplicati.Library.Main.Operation // At this point, there should be no more writes to the database, so we have to unlock the database: await database.Transaction - .CommitAsync("CommitBeforeRestore") + .CommitAsync("CommitBeforeRestore", token: cancellationToken) .ConfigureAwait(false); using var setup_log_timer = new Logging.Timer(LOGTAG, "RestoreNetworkSetup", "RestoreNetworkSetup"); @@ -391,7 +391,7 @@ namespace Duplicati.Library.Main.Operation } await database.Transaction - .CommitAsync("CommitAfterRestore") + .CommitAsync("CommitAfterRestore", token: cancellationToken) .ConfigureAwait(false); await database.DisposePoolAsync().ConfigureAwait(false); @@ -422,7 +422,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteWarningMessage(LOGTAG, "NoFilesRestored", null, "Restore completed without errors but no files were restored"); // Drop the temp tables - await database.DropRestoreTable().ConfigureAwait(false); + await database.DropRestoreTable(cancellationToken).ConfigureAwait(false); await backendManager.WaitForEmptyAsync(database, m_result.TaskControl.ProgressToken).ConfigureAwait(false); // Report that the restore is complete @@ -443,16 +443,16 @@ namespace Duplicati.Library.Main.Operation //using (var database = new LocalRestoreDatabase(dbparent)) using (var metadatastorage = new RestoreHandlerMetadataStorage()) { - await Utility.UpdateOptionsFromDb(database, m_options) + await Utility.UpdateOptionsFromDb(database, m_options, cancellationToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(database, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(database, m_options, cancellationToken) .ConfigureAwait(false); m_blockbuffer = new byte[m_options.Blocksize]; if (!m_options.NoBackendverification) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PreRestoreVerify); - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, cancellationToken).ConfigureAwait(false); } //Figure out what files are to be patched, and what blocks are needed @@ -510,8 +510,8 @@ namespace Duplicati.Library.Main.Operation List volumes; using (new Logging.Timer(LOGTAG, "GetMissingVolumes", "GetMissingVolumes")) volumes = await database - .GetMissingVolumes() - .ToListAsync() + .GetMissingVolumes(cancellationToken) + .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); if (volumes.Count > 0) @@ -535,7 +535,7 @@ namespace Duplicati.Library.Main.Operation using (tmpfile) using (var blocks = new BlockVolumeReader(GetCompressionModule(name), tmpfile, m_options)) - await PatchWithBlocklist(database, blocks, m_options, m_result, m_blockbuffer, metadatastorage) + await PatchWithBlocklist(database, blocks, m_options, m_result, m_blockbuffer, metadatastorage, cancellationToken) .ConfigureAwait(false); } catch (Exception ex) @@ -550,7 +550,7 @@ namespace Duplicati.Library.Main.Operation var fileErrors = 0L; // Restore empty files. They might not have any blocks so don't appear in any volume. - await foreach (var file in database.GetFilesToRestore(true).Where(item => item.Length == 0).ConfigureAwait(false)) + await foreach (var file in database.GetFilesToRestore(true, cancellationToken).Where(item => item.Length == 0).ConfigureAwait(false)) { Logging.Log.WriteVerboseMessage(LOGTAG, "RestoreEmptyFile", "Restoring empty file \"{0}\"", file.Path); @@ -589,7 +589,7 @@ namespace Duplicati.Library.Main.Operation // After all blocks in the files are restored, verify the file hash using (var filehasher = HashFactory.CreateHasher(m_options.FileHashAlgorithm)) using (new Logging.Timer(LOGTAG, "RestoreVerification", "RestoreVerification")) - await foreach (var file in database.GetFilesToRestore(true).ConfigureAwait(false)) + await foreach (var file in database.GetFilesToRestore(true, cancellationToken).ConfigureAwait(false)) { try { @@ -632,7 +632,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteWarningMessage(LOGTAG, "NoFilesRestored", null, "Restore completed without errors but no files were restored"); // Drop the temp tables - await database.DropRestoreTable().ConfigureAwait(false); + await database.DropRestoreTable(cancellationToken).ConfigureAwait(false); await backendManager.WaitForEmptyAsync(database, cancellationToken).ConfigureAwait(false); } @@ -712,9 +712,9 @@ namespace Duplicati.Library.Main.Operation private static async Task ScanForExistingSourceBlocksFast(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, RestoreResults result) { // Fill BLOCKS with data from known local source files - await using var blockmarker = await database.CreateBlockMarkerAsync().ConfigureAwait(false); + await using var blockmarker = await database.CreateBlockMarkerAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); var updateCount = 0L; - await foreach (var entry in database.GetFilesAndSourceBlocksFast(options.Blocksize).ConfigureAwait(false)) + await foreach (var entry in database.GetFilesAndSourceBlocksFast(options.Blocksize, result.TaskControl.ProgressToken).ConfigureAwait(false)) { var targetpath = entry.TargetPath; var targetfileid = entry.TargetFileID; @@ -737,7 +737,7 @@ namespace Duplicati.Library.Main.Operation try { using var sourcestream = SystemIO.IO_OS.FileOpenRead(sourcepath); - await foreach (var block in entry.Blocks().ConfigureAwait(false)) + await foreach (var block in entry.Blocks(result.TaskControl.ProgressToken).ConfigureAwait(false)) { if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) return; @@ -761,7 +761,7 @@ namespace Duplicati.Library.Main.Operation targetstream.Write(blockbuffer, 0, size); } - await blockmarker.SetBlockRestored(targetfileid, block.Index, key, block.Size, false) + await blockmarker.SetBlockRestored(targetfileid, block.Index, key, block.Size, false, result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -779,7 +779,7 @@ namespace Duplicati.Library.Main.Operation if ((++updateCount) % 20 == 0) { await blockmarker - .UpdateProcessed(result.OperationProgressUpdater) + .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) return; @@ -810,17 +810,17 @@ namespace Duplicati.Library.Main.Operation } await blockmarker - .UpdateProcessed(result.OperationProgressUpdater) + .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); - await blockmarker.CommitAsync().ConfigureAwait(false); + await blockmarker.CommitAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); } private static async Task ScanForExistingSourceBlocks(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, RestoreResults result, RestoreHandlerMetadataStorage metadatastorage) { // Fill BLOCKS with data from known local source files - await using var blockmarker = await database.CreateBlockMarkerAsync().ConfigureAwait(false); + await using var blockmarker = await database.CreateBlockMarkerAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); var updateCount = 0L; - await foreach (var restorelist in database.GetFilesAndSourceBlocks(options.SkipMetadata, options.Blocksize).ConfigureAwait(false)) + await foreach (var restorelist in database.GetFilesAndSourceBlocks(options.SkipMetadata, options.Blocksize, result.TaskControl.ProgressToken).ConfigureAwait(false)) { var targetpath = restorelist.TargetPath; var targetfileid = restorelist.TargetFileID; @@ -838,9 +838,9 @@ namespace Duplicati.Library.Main.Operation } using (var file = options.Dryrun ? null : SystemIO.IO_OS.FileOpenWrite(targetpath)) - await foreach (var targetblock in restorelist.Blocks().ConfigureAwait(false)) + await foreach (var targetblock in restorelist.Blocks(result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await foreach (var source in targetblock.BlockSources().ConfigureAwait(false)) + await foreach (var source in targetblock.BlockSources(result.TaskControl.ProgressToken).ConfigureAwait(false)) { try { @@ -878,7 +878,7 @@ namespace Duplicati.Library.Main.Operation } await blockmarker - .SetBlockRestored(targetfileid, targetblock.Index, key, targetblock.Size, false) + .SetBlockRestored(targetfileid, targetblock.Index, key, targetblock.Size, false, result.TaskControl.ProgressToken) .ConfigureAwait(false); patched = true; break; @@ -898,7 +898,7 @@ namespace Duplicati.Library.Main.Operation if ((++updateCount) % 20 == 0) await blockmarker - .UpdateProcessed(result.OperationProgressUpdater) + .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); } catch (Exception ex) @@ -918,9 +918,9 @@ namespace Duplicati.Library.Main.Operation } await blockmarker - .UpdateProcessed(result.OperationProgressUpdater) + .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); - await blockmarker.CommitAsync().ConfigureAwait(false); + await blockmarker.CommitAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); } private static async Task PrepareBlockAndFileList(LocalRestoreDatabase database, Options options, Library.Utility.IFilter filter, RestoreResults result) @@ -930,7 +930,7 @@ namespace Duplicati.Library.Main.Operation using (new Logging.Timer(LOGTAG, "PrepareRestoreFileList", "PrepareRestoreFileList")) { var c = await database - .PrepareRestoreFilelist(options.Time, options.Version, filter) + .PrepareRestoreFilelist(options.Time, options.Version, filter, result.TaskControl.ProgressToken) .ConfigureAwait(false); result.OperationProgressUpdater.UpdatefileCount(c.Item1, c.Item2, true); } @@ -941,30 +941,30 @@ namespace Duplicati.Library.Main.Operation // Find the largest common prefix var largest_prefix = options.DontCompressRestorePaths ? "" : - await database.GetLargestPrefix().ConfigureAwait(false); + await database.GetLargestPrefix(result.TaskControl.ProgressToken).ConfigureAwait(false); Logging.Log.WriteVerboseMessage(LOGTAG, "MappingRestorePath", "Mapping restore path prefix to \"{0}\" to \"{1}\"", largest_prefix, Util.AppendDirSeparator(options.Restorepath)); // Set the target paths, special care with C:\ and / await database - .SetTargetPaths(largest_prefix, Util.AppendDirSeparator(options.Restorepath)) + .SetTargetPaths(largest_prefix, Util.AppendDirSeparator(options.Restorepath), result.TaskControl.ProgressToken) .ConfigureAwait(false); } else { - await database.SetTargetPaths("", "").ConfigureAwait(false); + await database.SetTargetPaths("", "", result.TaskControl.ProgressToken).ConfigureAwait(false); } // Create a temporary table BLOCKS that lists all blocks that needs to be recovered using (new Logging.Timer(LOGTAG, "FindMissingBlocks", "FindMissingBlocks")) await database - .FindMissingBlocks(options.SkipMetadata) + .FindMissingBlocks(options.SkipMetadata, result.TaskControl.ProgressToken) .ConfigureAwait(false); // Create temporary tables and triggers that automatically track progress using (new Logging.Timer(LOGTAG, "CreateProgressTracker", "CreateProgressTracker")) await database - .CreateProgressTracker(false) + .CreateProgressTracker(false, result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -983,7 +983,7 @@ namespace Duplicati.Library.Main.Operation SystemIO.IO_OS.DirectoryCreate(options.Restorepath); } - await foreach (var folder in database.GetTargetFolders().ConfigureAwait(false)) + await foreach (var folder in database.GetTargetFolders(result.TaskControl.ProgressToken).ConfigureAwait(false)) { try { @@ -1014,9 +1014,9 @@ namespace Duplicati.Library.Main.Operation private static async Task ScanForExistingTargetBlocks(LocalRestoreDatabase database, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm blockhasher, System.Security.Cryptography.HashAlgorithm filehasher, Options options, RestoreResults result) { // Scan existing files for existing BLOCKS - await using var blockmarker = await database.CreateBlockMarkerAsync().ConfigureAwait(false); + await using var blockmarker = await database.CreateBlockMarkerAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); var updateCount = 0L; - await foreach (var restorelist in database.GetExistingFilesWithBlocks().ConfigureAwait(false)) + await foreach (var restorelist in database.GetExistingFilesWithBlocks(result.TaskControl.ProgressToken).ConfigureAwait(false)) { var rename = !options.Overwrite; var targetpath = restorelist.TargetPath; @@ -1069,7 +1069,7 @@ namespace Duplicati.Library.Main.Operation using (var file = SystemIO.IO_OS.FileOpenRead(targetpath)) using (var block = new Blockprocessor(file, blockbuffer)) - await foreach (var targetblock in restorelist.Blocks().ConfigureAwait(false)) + await foreach (var targetblock in restorelist.Blocks(result.TaskControl.ProgressToken).ConfigureAwait(false)) { var size = block.Readblock(); if (size <= 0) @@ -1095,7 +1095,7 @@ namespace Duplicati.Library.Main.Operation if (key == targetblock.Hash) { await blockmarker - .SetBlockRestored(targetfileid, targetblock.Index, key, size, false) + .SetBlockRestored(targetfileid, targetblock.Index, key, size, false, result.TaskControl.ProgressToken) .ConfigureAwait(false); blockhashmatch = true; } @@ -1132,7 +1132,7 @@ namespace Duplicati.Library.Main.Operation { //TODO: Check metadata to trigger rename? If metadata changed, it will still be restored for the file in-place. await blockmarker - .SetFileDataVerified(targetfileid) + .SetFileDataVerified(targetfileid, result.TaskControl.ProgressToken) .ConfigureAwait(false); Logging.Log.WriteVerboseMessage(LOGTAG, "TargetExistsInCorrectVersion", "Target file exists{1} and is correct version: {0}", targetpath, wasTruncated ? " (but was truncated)" : ""); rename = false; @@ -1142,7 +1142,7 @@ namespace Duplicati.Library.Main.Operation // The new file will have none of the correct blocks, // even if the scanned file had some await blockmarker - .SetAllBlocksMissing(targetfileid) + .SetAllBlocksMissing(targetfileid, result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -1150,7 +1150,7 @@ namespace Duplicati.Library.Main.Operation if ((++updateCount) % 20 == 0) { await blockmarker - .UpdateProcessed(result.OperationProgressUpdater) + .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) return; @@ -1199,10 +1199,10 @@ namespace Duplicati.Library.Main.Operation //TODO: Also needs metadata check to make correct decision. // We stick to the policy to restore metadata in place, if data ok. So, metadata block may be restored. await blockmarker - .SetAllBlocksRestored(targetfileid, false) + .SetAllBlocksRestored(targetfileid, false, result.TaskControl.ProgressToken) .ConfigureAwait(false); await blockmarker - .SetFileDataVerified(targetfileid) + .SetFileDataVerified(targetfileid, result.TaskControl.ProgressToken) .ConfigureAwait(false); break; } @@ -1220,17 +1220,17 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteVerboseMessage(LOGTAG, "TargetFileRetargeted", "Target file exists and will be restored to: {0}", newname); await database - .UpdateTargetPath(targetfileid, newname) + .UpdateTargetPath(targetfileid, newname, result.TaskControl.ProgressToken) .ConfigureAwait(false); } } await blockmarker - .UpdateProcessed(result.OperationProgressUpdater) + .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); await blockmarker - .CommitAsync() + .CommitAsync(result.TaskControl.ProgressToken) .ConfigureAwait(false); } } diff --git a/Duplicati/Library/Main/Operation/SearchEntriesHandler.cs b/Duplicati/Library/Main/Operation/SearchEntriesHandler.cs index 6f09c175b..fe66207c2 100644 --- a/Duplicati/Library/Main/Operation/SearchEntriesHandler.cs +++ b/Duplicati/Library/Main/Operation/SearchEntriesHandler.cs @@ -50,14 +50,14 @@ internal static class SearchEntriesHandler throw new UserInformationException("No local database found, this operation requires a local database", "NoLocalDatabase"); await using var db = - await Database.LocalListDatabase.CreateAsync(options.Dbpath, options.SqlitePageCache) + await Database.LocalListDatabase.CreateAsync(options.Dbpath, options.SqlitePageCache, null, result.TaskControl.ProgressToken) .ConfigureAwait(false); long[]? filesetIds = null; if (!options.AllVersions) { filesetIds = await db - .GetFilesetIDs(options.Time, options.Version) - .ToArrayAsync() + .GetFilesetIDs(options.Time, options.Version, false, result.TaskControl.ProgressToken) + .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); if (filesetIds.Length == 0) @@ -68,7 +68,7 @@ internal static class SearchEntriesHandler paths = paths.Select(path => Util.AppendDirSeparator(path)).ToArray(); result.FileVersions = await db - .SearchEntries(paths, filter, filesetIds, offset, limit) + .SearchEntries(paths, filter, filesetIds, offset, limit, result.TaskControl.ProgressToken) .ConfigureAwait(false); } } diff --git a/Duplicati/Library/Main/Operation/TestHandler.cs b/Duplicati/Library/Main/Operation/TestHandler.cs index 58852c7a6..523398465 100644 --- a/Duplicati/Library/Main/Operation/TestHandler.cs +++ b/Duplicati/Library/Main/Operation/TestHandler.cs @@ -39,12 +39,12 @@ namespace Duplicati.Library.Main.Operation private static readonly string LOGTAG = Logging.Log.LogTagFromType(); private readonly Options m_options; - private readonly TestResults m_results; + private readonly TestResults m_result; public TestHandler(Options options, TestResults results) { m_options = options; - m_results = results; + m_result = results; } public async Task RunAsync(long samples, IBackendManager backendManager) @@ -52,17 +52,17 @@ namespace Duplicati.Library.Main.Operation if (!System.IO.File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); - await using var db = await LocalTestDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache).ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options) + await using var db = await LocalTestDatabase.CreateAsync(m_options.Dbpath, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options) + await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, !m_options.DisableFilelistConsistencyChecks) + .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, !m_options.DisableFilelistConsistencyChecks, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_results.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await DoRunAsync(samples, db, backendManager).ConfigureAwait(false); await db.Transaction .CommitAsync("TestHandlerComplete") @@ -72,35 +72,35 @@ namespace Duplicati.Library.Main.Operation public async Task DoRunAsync(long samples, LocalTestDatabase db, IBackendManager backend) { var files = await db - .SelectTestTargets(samples, m_options) - .ToListAsync() + .SelectTestTargets(samples, m_options, m_result.TaskControl.ProgressToken) + .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - m_results.OperationProgressUpdater.UpdatePhase(OperationPhase.Verify_Running); - m_results.OperationProgressUpdater.UpdateProgress(0); + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Verify_Running); + m_result.OperationProgressUpdater.UpdateProgress(0); var progress = 0L; if (m_options.FullRemoteVerification != Options.RemoteTestStrategy.False) { var faultyIndexFiles = new List(); - await foreach (var (tf, hash, size, name) in backend.GetFilesOverlappedAsync(files, m_results.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var (tf, hash, size, name) in backend.GetFilesOverlappedAsync(files, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { var vol = new RemoteVolume(name, hash, size); try { - if (!await m_results.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - await backend.WaitForEmptyAsync(db, m_results.TaskControl.ProgressToken).ConfigureAwait(false); - m_results.EndTime = DateTime.UtcNow; + await backend.WaitForEmptyAsync(db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + m_result.EndTime = DateTime.UtcNow; return; } progress++; - m_results.OperationProgressUpdater.UpdateProgress((float)progress / files.Count); + m_result.OperationProgressUpdater.UpdateProgress((float)progress / files.Count); KeyValuePair>> res; using (tf) - res = await TestVolumeInternals(db, vol, tf, m_options, m_options.FullBlockVerification ? 1.0 : 0.2) + res = await TestVolumeInternals(db, vol, tf, m_options, m_options.FullBlockVerification ? 1.0 : 0.2, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var parsedInfo = VolumeBase.ParseFilename(vol.Name); @@ -119,14 +119,14 @@ namespace Duplicati.Library.Main.Operation faultyIndexFiles.Add(vol); } - m_results.AddResult(res.Key, res.Value); + m_result.AddResult(res.Key, res.Value); if (!string.IsNullOrWhiteSpace(vol.Hash) && vol.Size > 0) { if (res.Value == null || !res.Value.Any()) { var rv = await db - .GetRemoteVolume(vol.Name) + .GetRemoteVolume(vol.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (rv.ID < 0) @@ -141,7 +141,7 @@ namespace Duplicati.Library.Main.Operation { Logging.Log.WriteInformationMessage(LOGTAG, "CaptureHashAndSize", "Successfully captured hash and size for {0}, updating database", vol.Name); await db - .UpdateRemoteVolume(vol.Name, RemoteVolumeState.Verified, vol.Size, vol.Hash) + .UpdateRemoteVolume(vol.Name, RemoteVolumeState.Verified, vol.Size, vol.Hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -150,16 +150,16 @@ namespace Duplicati.Library.Main.Operation } await db - .UpdateVerificationCount(vol.Name) + .UpdateVerificationCount(vol.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } catch (Exception ex) { - m_results.AddResult(vol.Name, [new KeyValuePair(TestEntryStatus.Error, ex.Message)]); + m_result.AddResult(vol.Name, [new KeyValuePair(TestEntryStatus.Error, ex.Message)]); Logging.Log.WriteErrorMessage(LOGTAG, "RemoteFileProcessingFailed", ex, "Failed to process file {0}", vol.Name); if (ex.IsAbortException()) { - m_results.EndTime = DateTime.UtcNow; + m_result.EndTime = DateTime.UtcNow; throw; } } @@ -170,7 +170,7 @@ namespace Duplicati.Library.Main.Operation if (m_options.ReplaceFaultyIndexFiles) { Logging.Log.WriteWarningMessage(LOGTAG, "FaultyIndexFiles", null, "Found {0} faulty index files, repairing now", faultyIndexFiles.Count); - await ReplaceFaultyIndexFilesAsync(faultyIndexFiles, backend, db, m_results.TaskControl.ProgressToken).ConfigureAwait(false); + await ReplaceFaultyIndexFilesAsync(faultyIndexFiles, backend, db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); } else Logging.Log.WriteWarningMessage(LOGTAG, "FaultyIndexFiles", null, "Found {0} faulty index files, use the option {1} to repair them", faultyIndexFiles.Count, "--replace-faulty-index-files"); @@ -183,26 +183,26 @@ namespace Duplicati.Library.Main.Operation { try { - if (!await m_results.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - m_results.EndTime = DateTime.UtcNow; + m_result.EndTime = DateTime.UtcNow; return; } progress++; - m_results.OperationProgressUpdater.UpdateProgress((float)progress / files.Count); + m_result.OperationProgressUpdater.UpdateProgress((float)progress / files.Count); if (f.Size <= 0 || string.IsNullOrWhiteSpace(f.Hash)) { Logging.Log.WriteInformationMessage(LOGTAG, "MissingRemoteHash", "No hash or size recorded for {0}, performing full verification", f.Name); KeyValuePair>> res; - (var tf, var hash, var size) = await backend.GetWithInfoAsync(f.Name, f.Hash, f.Size, m_results.TaskControl.ProgressToken).ConfigureAwait(false); + (var tf, var hash, var size) = await backend.GetWithInfoAsync(f.Name, f.Hash, f.Size, m_result.TaskControl.ProgressToken).ConfigureAwait(false); using (tf) - res = await TestVolumeInternals(db, f, tf, m_options, 1) + res = await TestVolumeInternals(db, f, tf, m_options, 1, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - m_results.AddResult(res.Key, res.Value); + m_result.AddResult(res.Key, res.Value); if (!string.IsNullOrWhiteSpace(hash) && size > 0) { @@ -216,7 +216,7 @@ namespace Duplicati.Library.Main.Operation { Logging.Log.WriteInformationMessage(LOGTAG, "CapturedHashAndSize", "Successfully captured hash and size for {0}, updating database", f.Name); await db - .UpdateRemoteVolume(f.Name, RemoteVolumeState.Verified, size, hash) + .UpdateRemoteVolume(f.Name, RemoteVolumeState.Verified, size, hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -224,39 +224,39 @@ namespace Duplicati.Library.Main.Operation } else { - using (var tf = await backend.GetAsync(f.Name, f.Hash, f.Size, m_results.TaskControl.ProgressToken).ConfigureAwait(false)) + using (var tf = await backend.GetAsync(f.Name, f.Hash, f.Size, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { } } await db - .UpdateVerificationCount(f.Name) + .UpdateVerificationCount(f.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - m_results.AddResult(f.Name, []); + m_result.AddResult(f.Name, []); } catch (Exception ex) { - m_results.AddResult(f.Name, [new KeyValuePair(TestEntryStatus.Error, ex.Message)]); + m_result.AddResult(f.Name, [new KeyValuePair(TestEntryStatus.Error, ex.Message)]); Logging.Log.WriteErrorMessage(LOGTAG, "FailedToProcessFile", ex, "Failed to process file {0}", f.Name); if (ex.IsAbortOrCancelException()) { - m_results.EndTime = DateTime.UtcNow; + m_result.EndTime = DateTime.UtcNow; throw; } } } } - m_results.EndTime = DateTime.UtcNow; + m_result.EndTime = DateTime.UtcNow; // generate a backup error status when any test is failing - except for 'extra' status // because these problems don't block database rebuilding. - var filtered = from n in m_results.Verifications where n.Value.Any(x => x.Key != TestEntryStatus.Extra) select n; + var filtered = from n in m_result.Verifications where n.Value.Any(x => x.Key != TestEntryStatus.Extra) select n; if (!filtered.Any()) { - Logging.Log.WriteInformationMessage(LOGTAG, "Test results", "Successfully verified {0} remote files", m_results.VerificationsActualLength); + Logging.Log.WriteInformationMessage(LOGTAG, "Test results", "Successfully verified {0} remote files", m_result.VerificationsActualLength); } else { - Logging.Log.WriteErrorMessage(LOGTAG, "Test results", null, "Verified {0} remote files with {1} problem(s)", m_results.VerificationsActualLength, filtered.Count()); + Logging.Log.WriteErrorMessage(LOGTAG, "Test results", null, "Verified {0} remote files with {1} problem(s)", m_result.VerificationsActualLength, filtered.Count()); } } @@ -268,8 +268,9 @@ namespace Duplicati.Library.Main.Operation /// The path to the downloaded copy of the file. /// The options to use for the test. /// A value between 0 and 1 that indicates how many blocks are tested in a dblock file. + /// A cancellation token to cancel the operation. /// A task that when awaited returns a key-value pair where the key is the volume name and the value is a list of test results. - public static async Task>>> TestVolumeInternals(LocalTestDatabase db, IRemoteVolume vol, string tf, Options options, double sample_percent) + public static async Task>>> TestVolumeInternals(LocalTestDatabase db, IRemoteVolume vol, string tf, Options options, double sample_percent, CancellationToken cancellationToken) { var hashsize = HashFactory.HashSizeBytes(options.BlockHashAlgorithm); var parsedInfo = Volumes.VolumeBase.ParseFilename(vol.Name); @@ -280,15 +281,15 @@ namespace Duplicati.Library.Main.Operation case RemoteVolumeType.Files: //Compare with db and see if all files are accounted for // with correct file hashes and blocklist hashes - await using (var fl = await db.CreateFilelist(vol.Name).ConfigureAwait(false)) + await using (var fl = await db.CreateFilelist(vol.Name, cancellationToken).ConfigureAwait(false)) { using (var rd = new Volumes.FilesetVolumeReader(parsedInfo.CompressionModule, tf, options)) foreach (var f in rd.Files) await fl - .Add(f.Path, f.Size, f.Hash, f.Metasize, f.Metahash, f.BlocklistHashes, f.Type, f.Time) + .Add(f.Path, f.Size, f.Hash, f.Metasize, f.Metahash, f.BlocklistHashes, f.Type, f.Time, cancellationToken) .ConfigureAwait(false); - return new KeyValuePair>>(vol.Name, await fl.Compare().ToListAsync().ConfigureAwait(false)); + return new KeyValuePair>>(vol.Name, await fl.Compare(cancellationToken).ToListAsync(cancellationToken: cancellationToken).ConfigureAwait(false)); } case RemoteVolumeType.Index: @@ -301,17 +302,17 @@ namespace Duplicati.Library.Main.Operation foreach (var v in rd.Volumes) { blocklinks.Add(new Tuple(v.Filename, v.Hash, v.Length)); - await using (var bl = await db.CreateBlocklist(v.Filename).ConfigureAwait(false)) + await using (var bl = await db.CreateBlocklist(v.Filename, cancellationToken).ConfigureAwait(false)) { foreach (var h in v.Blocks) await bl - .AddBlock(h.Key, h.Value) + .AddBlock(h.Key, h.Value, cancellationToken) .ConfigureAwait(false); combined.AddRange( await bl - .Compare() - .ToListAsync() + .Compare(cancellationToken) + .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ); } @@ -320,17 +321,17 @@ namespace Duplicati.Library.Main.Operation if (options.IndexfilePolicy == Options.IndexFileStrategy.Full) { var hashesPerBlock = options.Blocksize / options.BlockhashSize; - await using (var bl = await db.CreateBlocklistHashList(vol.Name).ConfigureAwait(false)) + await using (var bl = await db.CreateBlocklistHashList(vol.Name, cancellationToken).ConfigureAwait(false)) { foreach (var b in rd.BlockLists) await bl - .AddBlockHash(b.Hash, b.Length) + .AddBlockHash(b.Hash, b.Length, cancellationToken) .ConfigureAwait(false); combined.AddRange( await bl - .Compare(hashesPerBlock, options.BlockhashSize, options.Blocksize) - .ToListAsync() + .Compare(hashesPerBlock, options.BlockhashSize, options.Blocksize, cancellationToken) + .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ); } @@ -338,17 +339,17 @@ namespace Duplicati.Library.Main.Operation } // Compare with db and see that all blocklists are listed - await using (var il = await db.CreateIndexlist(vol.Name).ConfigureAwait(false)) + await using (var il = await db.CreateIndexlist(vol.Name, cancellationToken).ConfigureAwait(false)) { foreach (var t in blocklinks) await il - .AddBlockLink(t.Item1, t.Item2, t.Item3) + .AddBlockLink(t.Item1, t.Item2, t.Item3, cancellationToken) .ConfigureAwait(false); combined.AddRange( await il - .Compare() - .ToArrayAsync() + .Compare(cancellationToken) + .ToArrayAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ); } @@ -356,13 +357,13 @@ namespace Duplicati.Library.Main.Operation return new KeyValuePair>>(vol.Name, combined); case RemoteVolumeType.Blocks: using (var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm)) - await using (var bl = await db.CreateBlocklist(vol.Name).ConfigureAwait(false)) + await using (var bl = await db.CreateBlocklist(vol.Name, cancellationToken).ConfigureAwait(false)) using (var rd = new Volumes.BlockVolumeReader(parsedInfo.CompressionModule, tf, options)) { //Verify that all blocks are in the file foreach (var b in rd.Blocks) await bl - .AddBlock(b.Key, b.Value) + .AddBlock(b.Key, b.Value, cancellationToken) .ConfigureAwait(false); //Select random blocks and verify their hashes match the filename and size @@ -392,8 +393,8 @@ namespace Duplicati.Library.Main.Operation vol.Name, changes.Union( await bl - .Compare() - .ToListAsync() + .Compare(cancellationToken) + .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ) ); @@ -407,14 +408,14 @@ namespace Duplicati.Library.Main.Operation private async Task ReplaceFaultyIndexFilesAsync(List faultyIndexFiles, IBackendManager backendManager, LocalTestDatabase db, CancellationToken cancellationToken) { await using var repairdb = - await LocalRepairDatabase.CreateAsync(db) + await LocalRepairDatabase.CreateAsync(db, null, cancellationToken) .ConfigureAwait(false); foreach (var vol in faultyIndexFiles) { - if (!await m_results.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - m_results.EndTime = DateTime.UtcNow; + m_result.EndTime = DateTime.UtcNow; return; } @@ -429,8 +430,8 @@ namespace Duplicati.Library.Main.Operation } else { - await backendManager.DeleteAsync(vol.Name, vol.Size, true, m_results.TaskControl.ProgressToken).ConfigureAwait(false); - await backendManager.WaitForEmptyAsync(repairdb, m_results.TaskControl.ProgressToken).ConfigureAwait(false); + await backendManager.DeleteAsync(vol.Name, vol.Size, true, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await backendManager.WaitForEmptyAsync(repairdb, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await repairdb.Transaction .CommitAsync("ReplaceFaultyIndexFileCommit") .ConfigureAwait(false); diff --git a/Duplicati/Library/Main/Operation/VacuumHandler.cs b/Duplicati/Library/Main/Operation/VacuumHandler.cs index dfc62247c..9556363fa 100644 --- a/Duplicati/Library/Main/Operation/VacuumHandler.cs +++ b/Duplicati/Library/Main/Operation/VacuumHandler.cs @@ -38,10 +38,10 @@ namespace Duplicati.Library.Main.Operation public virtual async Task RunAsync() { await using var db = - await Database.LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, "Vacuum", false, m_options.SqlitePageCache) + await Database.LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, "Vacuum", false, m_options.SqlitePageCache, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Vacuum_Running); - await db.Vacuum().ConfigureAwait(false); + await db.Vacuum(m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; } } diff --git a/Duplicati/Library/Main/Utility.cs b/Duplicati/Library/Main/Utility.cs index feaf14dbc..0d01d04b7 100644 --- a/Duplicati/Library/Main/Utility.cs +++ b/Duplicati/Library/Main/Utility.cs @@ -28,6 +28,7 @@ using System.Threading.Tasks; using Newtonsoft.Json; using Duplicati.Library.Main.Database; using Duplicati.Library.Utility; +using System.Threading; namespace Duplicati.Library.Main { @@ -103,11 +104,12 @@ namespace Duplicati.Library.Main /// /// The database to read from. /// The options to update. + /// A cancellation token to cancel the operation. /// A task that completes when the options have been updated. - internal static async Task UpdateOptionsFromDb(LocalDatabase db, Options options) + internal static async Task UpdateOptionsFromDb(LocalDatabase db, Options options, CancellationToken cancellationToken) { string n = null; - var opts = await db.GetDbOptions().ConfigureAwait(false); + var opts = await db.GetDbOptions(cancellationToken).ConfigureAwait(false); if (opts.ContainsKey("blocksize") && (!options.RawOptions.TryGetValue("blocksize", out n) || string.IsNullOrEmpty(n))) options.RawOptions["blocksize"] = opts["blocksize"] + "b"; @@ -121,11 +123,12 @@ namespace Duplicati.Library.Main /// Checks if the database contains options that need to be verified, such as the blocksize. /// /// The database to check. + /// A cancellation token to cancel the operation. /// A task that when awaited returns true if the database contains options that need to be verified; false otherwise. - internal static async Task ContainsOptionsForVerification(LocalDatabase db) + internal static async Task ContainsOptionsForVerification(LocalDatabase db, CancellationToken cancellationToken) { - var opts = await db.GetDbOptions().ConfigureAwait(false); - await db.Transaction.CommitAsync().ConfigureAwait(false); + var opts = await db.GetDbOptions(cancellationToken).ConfigureAwait(false); + await db.Transaction.CommitAsync(cancellationToken).ConfigureAwait(false); return new[] { "blocksize", "blockhash", @@ -140,8 +143,9 @@ namespace Duplicati.Library.Main /// /// The database to check. /// The options to verify. + /// A cancellation token to cancel the operation. /// A task that completes when the options have been verified and the database has been updated if needed. - internal static async Task VerifyOptionsAndUpdateDatabase(LocalDatabase db, Options options) + internal static async Task VerifyOptionsAndUpdateDatabase(LocalDatabase db, Options options, CancellationToken cancellationToken) { var newDict = new Dictionary { @@ -149,7 +153,7 @@ namespace Duplicati.Library.Main { "blockhash", options.BlockHashAlgorithm }, { "filehash", options.FileHashAlgorithm } }; - var opts = await db.GetDbOptions().ConfigureAwait(false); + var opts = await db.GetDbOptions(cancellationToken).ConfigureAwait(false); if (options.NoEncryption) { @@ -199,7 +203,7 @@ namespace Duplicati.Library.Main } //Extra sanity check - if (await db.GetBlocksLargerThan(options.Blocksize).ConfigureAwait(false) > 0) + if (await db.GetBlocksLargerThan(options.Blocksize, cancellationToken).ConfigureAwait(false) > 0) throw new Duplicati.Library.Interface.UserInformationException("You have attempted to change the block-size on an existing backup, which is not supported. Please configure a new clean backup if you want to change the block-size.", "BlockSizeChangeNotSupported"); if (needsUpdate) @@ -209,7 +213,7 @@ namespace Duplicati.Library.Main if (!newDict.ContainsKey(k.Key)) newDict[k.Key] = k.Value; - await db.SetDbOptions(newDict).ConfigureAwait(false); + await db.SetDbOptions(newDict, cancellationToken).ConfigureAwait(false); } } }