diff --git a/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs b/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs
index d3e59fae9..ad7f494b3 100644
--- a/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs
+++ b/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs
@@ -24,6 +24,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading;
using System.Threading.Tasks;
namespace Duplicati.Library.Main.Database
@@ -165,13 +167,14 @@ namespace Duplicati.Library.Main.Database
/// The path to the database file.
/// The size of the page cache.
/// An optional existing database instance to use. Used to mimic constructor chaining.
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains a new instance of .
- public static async Task CreateAsync(string path, long pagecachesize, LocalListBrokenFilesDatabase? dbnew = null)
+ public static async Task CreateAsync(string path, long pagecachesize, LocalListBrokenFilesDatabase? dbnew, CancellationToken token)
{
dbnew ??= new LocalListBrokenFilesDatabase();
dbnew = (LocalListBrokenFilesDatabase)
- await CreateLocalDatabaseAsync(path, "ListBrokenFiles", false, pagecachesize, dbnew)
+ await CreateLocalDatabaseAsync(path, "ListBrokenFiles", false, pagecachesize, dbnew, token)
.ConfigureAwait(false);
dbnew.ShouldCloseConnection = true;
@@ -183,13 +186,14 @@ namespace Duplicati.Library.Main.Database
///
/// The parent database to use for the new database.
/// An optional existing database instance to use. Used to mimic constructor chaining.
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains a new instance of .
- public static async Task CreateAsync(LocalDatabase dbparent, LocalListBrokenFilesDatabase? dbnew = null)
+ public static async Task CreateAsync(LocalDatabase dbparent, LocalListBrokenFilesDatabase? dbnew, CancellationToken token)
{
dbnew ??= new LocalListBrokenFilesDatabase();
dbnew = (LocalListBrokenFilesDatabase)
- await CreateLocalDatabaseAsync(dbparent, dbnew)
+ await CreateLocalDatabaseAsync(dbparent, dbnew, token)
.ConfigureAwait(false);
dbnew.ShouldCloseConnection = false;
@@ -201,11 +205,12 @@ namespace Duplicati.Library.Main.Database
///
/// The time to filter filesets by.
/// Optional array of versions to filter filesets by. If null, all versions are considered.
+ /// A cancellation token to cancel the operation.
/// An asynchronous enumerable of broken file IDs.
- public async IAsyncEnumerable<(DateTime FilesetTime, long FilesetID, long RemoveFileCount)> GetBrokenFilesets(DateTime time, long[]? versions)
+ public async IAsyncEnumerable<(DateTime FilesetTime, long FilesetID, long RemoveFileCount)> GetBrokenFilesets(DateTime time, long[]? versions, [EnumeratorCancellation] CancellationToken token)
{
var query = BROKEN_FILE_SETS;
- var clause = await GetFilelistWhereClause(time, versions)
+ var clause = await GetFilelistWhereClause(time, versions, null, false, token)
.ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(clause.Query))
@@ -223,7 +228,7 @@ namespace Duplicati.Library.Main.Database
.SetCommandAndParameters(query)
.SetParameterValues(clause.Values);
- await foreach (var rd in cmd.ExecuteReaderEnumerableAsync().ConfigureAwait(false))
+ await foreach (var rd in cmd.ExecuteReaderEnumerableAsync(token).ConfigureAwait(false))
if (!rd.IsDBNull(0))
yield return (
ParseFromEpochSeconds(rd.ConvertValueToInt64(0, 0)),
@@ -236,14 +241,15 @@ namespace Duplicati.Library.Main.Database
/// Returns all broken file IDs, i.e., files that reference blocksets or blocks that are not available.
///
/// The fileset ID to filter by.
+ /// A cancellation token to cancel the operation.
/// An asynchronous enumerable of broken file IDs.
- public async IAsyncEnumerable> GetBrokenFilenames(long filesetid)
+ public async IAsyncEnumerable> GetBrokenFilenames(long filesetid, [EnumeratorCancellation] CancellationToken token)
{
await using var cmd = Connection.CreateCommand(m_rtr)
.SetCommandAndParameters(BROKEN_FILE_NAMES)
.SetParameterValue("@FilesetId", filesetid);
- await foreach (var rd in cmd.ExecuteReaderEnumerableAsync().ConfigureAwait(false))
+ await foreach (var rd in cmd.ExecuteReaderEnumerableAsync(token).ConfigureAwait(false))
if (!rd.IsDBNull(0))
yield return new Tuple(
rd.ConvertValueToString(0) ?? throw new Exception("Filename was null"),
@@ -254,8 +260,9 @@ namespace Duplicati.Library.Main.Database
///
/// Returns all index files that are orphaned, i.e., not referenced by any block files.
///
+ /// A cancellation token to cancel the operation.
/// An asynchronous enumerable of representing the orphaned index files.
- public async IAsyncEnumerable GetOrphanedIndexFiles()
+ public async IAsyncEnumerable GetOrphanedIndexFiles([EnumeratorCancellation] CancellationToken token)
{
await using var cmd = Connection.CreateCommand($@"
SELECT
@@ -272,7 +279,7 @@ namespace Duplicati.Library.Main.Database
")
.SetTransaction(m_rtr);
- await foreach (var rd in cmd.ExecuteReaderEnumerableAsync().ConfigureAwait(false))
+ await foreach (var rd in cmd.ExecuteReaderEnumerableAsync(token).ConfigureAwait(false))
yield return new RemoteVolume(
rd.ConvertValueToString(0) ?? throw new Exception("Filename was null"),
rd.ConvertValueToString(1) ?? throw new Exception("Hash was null"),
@@ -286,14 +293,15 @@ namespace Duplicati.Library.Main.Database
/// The filset id for the current operation.
/// The name of the table to insert into.
/// The name of the ID field in the table.
+ /// A cancellation token to cancel the operation.
/// A task that completes when the insertion is finished.
- public async Task InsertBrokenFileIDsIntoTable(long filesetid, string tablename, string IDfieldname)
+ public async Task InsertBrokenFileIDsIntoTable(long filesetid, string tablename, string IDfieldname, CancellationToken token)
{
await using var cmd = Connection.CreateCommand(m_rtr)
.SetCommandAndParameters(INSERT_BROKEN_IDS(tablename, IDfieldname))
.SetParameterValue("@FilesetId", filesetid);
- await cmd.ExecuteNonQueryAsync()
+ await cmd.ExecuteNonQueryAsync(token)
.ConfigureAwait(false);
}
@@ -304,8 +312,9 @@ namespace Duplicati.Library.Main.Database
/// The volume ids to ignore when searching for a suitable metadata block.
/// The hash of the empty blockset.
/// The size of the empty blockset.
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains the ID of the empty metadata blockset, or -1 if no suitable blockset is found
- public async Task GetEmptyMetadataBlocksetId(IEnumerable blockVolumeIds, string emptyHash, long emptyHashSize)
+ public async Task GetEmptyMetadataBlocksetId(IEnumerable blockVolumeIds, string emptyHash, long emptyHashSize, CancellationToken token)
{
await using var cmd = Connection.CreateCommand(@"
SELECT ""ID""
@@ -328,7 +337,7 @@ namespace Duplicati.Library.Main.Database
.SetParameterValue("@EmptyHash", emptyHash)
.SetParameterValue("@EmptyHashSize", emptyHashSize);
- var res = await cmd.ExecuteScalarInt64Async(-1)
+ var res = await cmd.ExecuteScalarInt64Async(-1, token)
.ConfigureAwait(false);
// No empty block found, try to find a zero-length block instead
@@ -350,7 +359,7 @@ namespace Duplicati.Library.Main.Database
")
.ExpandInClauseParameterMssqlite("@BlockVolumeIds", blockVolumeIds)
.SetParameterValue("@EmptyHashSize", 0)
- .ExecuteScalarInt64Async(-1)
+ .ExecuteScalarInt64Async(-1, token)
.ConfigureAwait(false);
// No empty block found, pick the smallest one
@@ -371,7 +380,7 @@ namespace Duplicati.Library.Main.Database
LIMIT 1
")
.ExpandInClauseParameterMssqlite("@BlockVolumeIds", blockVolumeIds)
- .ExecuteScalarInt64Async(-1)
+ .ExecuteScalarInt64Async(-1, token)
.ConfigureAwait(false);
return res;
@@ -383,8 +392,9 @@ namespace Duplicati.Library.Main.Database
///
/// The filesetId to target.
/// The empty blockset ID to replace with.
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains the number of rows affected
- public async Task ReplaceMetadata(long filesetId, long emptyBlocksetId)
+ public async Task ReplaceMetadata(long filesetId, long emptyBlocksetId, CancellationToken token)
{
await using var cmd = m_connection.CreateCommand(@"
UPDATE ""Metadataset""
@@ -411,15 +421,16 @@ namespace Duplicati.Library.Main.Database
.SetParameterValue("@EmptyBlocksetID", emptyBlocksetId)
.SetParameterValue("@FilesetID", filesetId);
- return await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
+ return await cmd.ExecuteNonQueryAsync(token).ConfigureAwait(false);
}
///
/// Removes all blocks, blocksets, and index links that are missing from the specified volumes.
///
/// The names of the volumes to check for missing blocks.
+ /// A cancellation token to cancel the operation.
/// A task that completes when the removal is finished.
- public async Task RemoveMissingBlocks(IEnumerable names)
+ public async Task RemoveMissingBlocks(IEnumerable names, CancellationToken token)
{
if (names == null || !names.Any()) return;
@@ -432,10 +443,10 @@ namespace Duplicati.Library.Main.Database
CREATE TEMP TABLE ""{volidstable}"" (
""ID"" INTEGER PRIMARY KEY
)
- ")
+ ", token)
.ConfigureAwait(false);
- await using (var tmptable = await TemporaryDbValueList.CreateAsync(this, names).ConfigureAwait(false))
+ await using (var tmptable = await TemporaryDbValueList.CreateAsync(this, names, token).ConfigureAwait(false))
await (
await deletecmd.SetCommandAndParameters($@"
INSERT OR IGNORE INTO ""{volidstable}"" (""ID"")
@@ -443,10 +454,10 @@ namespace Duplicati.Library.Main.Database
FROM ""RemoteVolume""
WHERE ""Name"" IN (@Names)
")
- .ExpandInClauseParameterMssqliteAsync("@Names", tmptable)
+ .ExpandInClauseParameterMssqliteAsync("@Names", tmptable, token)
.ConfigureAwait(false)
)
- .ExecuteNonQueryAsync()
+ .ExecuteNonQueryAsync(token)
.ConfigureAwait(false);
var volIdsSubQuery = $@"
@@ -459,39 +470,39 @@ namespace Duplicati.Library.Main.Database
WHERE
""BlockVolumeID"" IN ({volIdsSubQuery})
OR ""IndexVolumeID"" IN ({volIdsSubQuery})
- ")
+ ", token)
.ConfigureAwait(false);
await deletecmd.ExecuteNonQueryAsync($@"
DELETE FROM ""Block""
WHERE ""VolumeID"" IN ({volIdsSubQuery})
- ")
+ ", token)
.ConfigureAwait(false);
await deletecmd.ExecuteNonQueryAsync($@"
DELETE FROM ""DeletedBlock""
WHERE ""VolumeID"" IN ({volIdsSubQuery})
- ")
+ ", token)
.ConfigureAwait(false);
await deletecmd.ExecuteNonQueryAsync($@"
DELETE FROM ""DuplicateBlock""
WHERE ""VolumeID"" IN ({volIdsSubQuery})
- ")
+ ", token)
.ConfigureAwait(false);
// Clean up temp tables for subqueries. We truncate content and then try to delete.
// Drop in try-block, as it fails in nested transactions (SQLite problem)
// SQLite.SQLiteException (0x80004005): database table is locked
await deletecmd
- .ExecuteNonQueryAsync($@"DELETE FROM ""{volidstable}"" ")
+ .ExecuteNonQueryAsync($@"DELETE FROM ""{volidstable}"" ", token)
.ConfigureAwait(false);
try
{
deletecmd.CommandTimeout = 2;
await deletecmd
- .ExecuteNonQueryAsync($@"DROP TABLE IF EXISTS ""{volidstable}"" ")
+ .ExecuteNonQueryAsync($@"DROP TABLE IF EXISTS ""{volidstable}"" ", token)
.ConfigureAwait(false);
}
catch { /* Ignore, will be deleted on close anyway. */ }
@@ -501,8 +512,9 @@ namespace Duplicati.Library.Main.Database
/// Gets the count of files in a specific fileset.
///
/// The ID of the fileset to count files in.
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains the count of files in the specified fileset.
- public async Task GetFilesetFileCount(long filesetid)
+ public async Task GetFilesetFileCount(long filesetid, CancellationToken token)
{
await using var cmd = m_connection.CreateCommand(@"
SELECT COUNT(*)
@@ -513,7 +525,7 @@ namespace Duplicati.Library.Main.Database
.SetParameterValue("@FilesetId", filesetid);
return await cmd
- .ExecuteScalarInt64Async(0)
+ .ExecuteScalarInt64Async(0, token)
.ConfigureAwait(false);
}
}