diff --git a/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs b/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs
index 50cdb25e3..8434c8842 100644
--- a/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs
+++ b/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs
@@ -23,6 +23,8 @@
using System;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Threading;
using System.Threading.Tasks;
using Duplicati.Library.Utility;
using Microsoft.Data.Sqlite;
@@ -40,13 +42,14 @@ namespace Duplicati.Library.Main.Database
/// The path to the database file.
/// The size of the page cache.
/// The 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, LocalPurgeDatabase? dbnew = null)
+ public static async Task CreateAsync(string path, long pagecachesize, LocalPurgeDatabase? dbnew, CancellationToken token)
{
dbnew ??= new LocalPurgeDatabase();
dbnew = (LocalPurgeDatabase)
- await LocalDeleteDatabase.CreateAsync(path, "Purge", pagecachesize, dbnew)
+ await LocalDeleteDatabase.CreateAsync(path, "Purge", pagecachesize, dbnew, token)
.ConfigureAwait(false);
dbnew.ShouldCloseConnection = true;
@@ -59,13 +62,14 @@ namespace Duplicati.Library.Main.Database
///
/// The parent database to use.
/// 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, LocalPurgeDatabase? dbnew = null)
+ public static async Task CreateAsync(LocalDatabase dbparent, LocalPurgeDatabase? dbnew, CancellationToken token)
{
dbnew ??= new LocalPurgeDatabase();
dbnew = (LocalPurgeDatabase)
- await LocalDeleteDatabase.CreateAsync(dbparent, dbnew)
+ await LocalDeleteDatabase.CreateAsync(dbparent, dbnew, token)
.ConfigureAwait(false);
return dbnew;
@@ -75,10 +79,11 @@ namespace Duplicati.Library.Main.Database
/// Creates a new instance of the class.
///
/// The ID of the parent fileset.
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains a new instance of .
- public async Task CreateTemporaryFileset(long parentid)
+ public async Task CreateTemporaryFileset(long parentid, CancellationToken token)
{
- return await TemporaryFileset.CreateAsync(parentid, this)
+ return await TemporaryFileset.CreateAsync(parentid, this, token)
.ConfigureAwait(false);
}
@@ -86,8 +91,9 @@ namespace Duplicati.Library.Main.Database
/// Gets the remote volume name for a fileset with the specified ID.
///
/// The ID of the fileset.
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains the name of the remote volume.
- public async Task GetRemoteVolumeNameForFileset(long id)
+ public async Task GetRemoteVolumeNameForFileset(long id, CancellationToken token)
{
await using var cmd = m_connection.CreateCommand(@"
SELECT
@@ -101,8 +107,8 @@ namespace Duplicati.Library.Main.Database
")
.SetParameterValue("@FilesetId", id);
- await using var rd = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
- if (!await rd.ReadAsync().ConfigureAwait(false))
+ await using var rd = await cmd.ExecuteReaderAsync(token).ConfigureAwait(false);
+ if (!await rd.ReadAsync(token).ConfigureAwait(false))
throw new Exception($"No remote volume found for fileset with id {id}");
else
return rd.ConvertValueToString(0) ?? throw new Exception($"Remote volume name for fileset with id {id} is null");
@@ -111,8 +117,9 @@ namespace Duplicati.Library.Main.Database
///
/// Counts the number of orphan files in the database.
///
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains the count of orphan files.
- internal async Task CountOrphanFiles()
+ internal async Task CountOrphanFiles(CancellationToken token)
{
await using var cmd = m_connection.CreateCommand(m_rtr);
cmd.SetCommandAndParameters(@"
@@ -123,8 +130,8 @@ namespace Duplicati.Library.Main.Database
FROM ""FilesetEntry""
)
");
- await using var rd = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
- if (await rd.ReadAsync().ConfigureAwait(false))
+ await using var rd = await cmd.ExecuteReaderAsync(token).ConfigureAwait(false);
+ if (await rd.ReadAsync(token).ConfigureAwait(false))
return rd.ConvertValueToInt64(0, 0);
else
return 0;
@@ -156,27 +163,31 @@ namespace Duplicati.Library.Main.Database
/// Applies a filter to the temporary fileset.
///
/// The filter to apply.
+ /// A cancellation token to cancel the operation.
/// A task that completes when the filter has been applied.
- Task ApplyFilter(Library.Utility.IFilter filter);
+ Task ApplyFilter(Library.Utility.IFilter filter, CancellationToken token);
///
/// Applies a filter using a custom command.
///
/// The command to execute for filtering.
+ /// A cancellation token to cancel the operation.
/// A task that completes when the filter has been applied.
- Task ApplyFilter(Func> filtercommand);
+ Task ApplyFilter(Func> filtercommand, CancellationToken token);
///
/// Converts the temporary fileset to a permanent fileset.
///
/// The name of the new fileset.
/// The timestamp for the new fileset.
/// Indicates if this is a full backup.
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains a tuple with the remote volume ID and the new fileset ID.
- Task> ConvertToPermanentFileset(string name, DateTime timestamp, bool isFullBackup);
+ Task> ConvertToPermanentFileset(string name, DateTime timestamp, bool isFullBackup, CancellationToken token);
///
/// Lists all deleted files in the temporary fileset.
///
+ /// A cancellation token to cancel the operation.
/// An asynchronous enumerable of key-value pairs where the key is the file path and the value is the file size.
- IAsyncEnumerable> ListAllDeletedFiles();
+ IAsyncEnumerable> ListAllDeletedFiles(CancellationToken token);
}
///
@@ -217,8 +228,9 @@ namespace Duplicati.Library.Main.Database
///
/// The ID of the parent fileset.
/// The database instance to use.
+ /// A cancellation token to cancel the operation.
/// A task that when awaited contains a new instance of .
- public static async Task CreateAsync(long parentid, LocalDatabase db)
+ public static async Task CreateAsync(long parentid, LocalDatabase db, CancellationToken token)
{
var tempf = new TemporaryFileset()
{
@@ -232,7 +244,7 @@ namespace Duplicati.Library.Main.Database
CREATE TEMPORARY TABLE ""{tempf.m_tablename}"" (
""FileID"" INTEGER PRIMARY KEY
)
- ")
+ ", token)
.ConfigureAwait(false);
return tempf;
@@ -242,23 +254,25 @@ namespace Duplicati.Library.Main.Database
/// Applies a filter to the temporary fileset using a custom command.
///
/// The command to execute for filtering.
+ /// A cancellation token to cancel the operation.
/// A task that completes when the filter has been applied.
- public async Task ApplyFilter(Func> filtercommand)
+ public async Task ApplyFilter(Func> filtercommand, CancellationToken token)
{
int updated;
await using (var cmd = m_db.Connection.CreateCommand())
updated = await filtercommand(cmd, ParentID, m_tablename)
.ConfigureAwait(false);
- await PostFilterChecks(updated).ConfigureAwait(false);
+ await PostFilterChecks(updated, token).ConfigureAwait(false);
}
///
/// Applies a filter to the temporary fileset.
///
/// The filter to apply.
+ /// A cancellation token to cancel the operation.
/// A task that completes when the filter has been applied.
- public async Task ApplyFilter(Library.Utility.IFilter filter)
+ public async Task ApplyFilter(Library.Utility.IFilter filter, CancellationToken token)
{
if (Library.Utility.Utility.IsFSCaseSensitive && filter is FilterExpression expression && expression.Type == Duplicati.Library.Utility.FilterType.Simple)
{
@@ -272,19 +286,19 @@ namespace Duplicati.Library.Main.Database
CREATE TEMPORARY TABLE ""{filenamestable}"" (
""Path"" TEXT NOT NULL
)
- ")
+ ", token)
.ConfigureAwait(false);
await cmd.SetCommandAndParameters($@"
INSERT INTO ""{filenamestable}"" (""Path"")
VALUES (@Path)
")
- .PrepareAsync()
+ .PrepareAsync(token)
.ConfigureAwait(false);
foreach (var s in p)
await cmd.SetParameterValue("@Path", s)
- .ExecuteNonQueryAsync()
+ .ExecuteNonQueryAsync(token)
.ConfigureAwait(false);
await cmd.SetCommandAndParameters($@"
@@ -299,11 +313,11 @@ namespace Duplicati.Library.Main.Database
AND ""B"".""Path"" IN ""{filenamestable}""
")
.SetParameterValue("@FilesetId", ParentID)
- .ExecuteNonQueryAsync()
+ .ExecuteNonQueryAsync(token)
.ConfigureAwait(false);
await cmd
- .ExecuteNonQueryAsync($@"DROP TABLE IF EXISTS ""{filenamestable}"" ")
+ .ExecuteNonQueryAsync($@"DROP TABLE IF EXISTS ""{filenamestable}"" ", token)
.ConfigureAwait(false);
}
else
@@ -316,7 +330,7 @@ namespace Duplicati.Library.Main.Database
INSERT INTO ""{m_tablename}"" (""FileID"")
VALUES (@FileId)
")
- .PrepareAsync()
+ .PrepareAsync(token)
.ConfigureAwait(false);
cmd.SetCommandAndParameters(@"
@@ -332,8 +346,8 @@ namespace Duplicati.Library.Main.Database
")
.SetParameterValue("@FilesetId", ParentID);
- await using var rd = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
- while (await rd.ReadAsync().ConfigureAwait(false))
+ await using var rd = await cmd.ExecuteReaderAsync(token).ConfigureAwait(false);
+ while (await rd.ReadAsync(token).ConfigureAwait(false))
{
rd.GetValues(values);
var path = values[0] as string;
@@ -341,21 +355,22 @@ namespace Duplicati.Library.Main.Database
{
await cmd2
.SetParameterValue("@FileId", values[1])
- .ExecuteNonQueryAsync()
+ .ExecuteNonQueryAsync(token)
.ConfigureAwait(false);
}
}
}
- await PostFilterChecks(0).ConfigureAwait(false);
+ await PostFilterChecks(0, token).ConfigureAwait(false);
}
///
/// Applies the filter checks after filtering.
///
/// The number of files updated by the filter.
+ /// A cancellation token to cancel the operation.
/// A task that completes when the checks have been applied.
- private async Task PostFilterChecks(int updated)
+ private async Task PostFilterChecks(int updated, CancellationToken token)
{
await using var cmd = m_db.Connection.CreateCommand();
UpdatedFileCount = updated;
@@ -363,7 +378,7 @@ namespace Duplicati.Library.Main.Database
RemovedFileCount = await cmd.ExecuteScalarInt64Async($@"
SELECT COUNT(*)
FROM ""{m_tablename}""
- ", 0)
+ ", 0, token)
.ConfigureAwait(false);
RemovedFileSize = await cmd.ExecuteScalarInt64Async($@"
@@ -382,7 +397,7 @@ namespace Duplicati.Library.Main.Database
AND ""D"".""BlocksetID"" = ""C"".""ID""
)
)
- ", 0)
+ ", 0, token)
.ConfigureAwait(false);
var filesetcount = await cmd.SetCommandAndParameters($@"
@@ -391,23 +406,27 @@ namespace Duplicati.Library.Main.Database
WHERE ""FilesetID"" = @ParentID
")
.SetParameterValue("@ParentID", ParentID)
- .ExecuteScalarInt64Async(0)
+ .ExecuteScalarInt64Async(0, token)
.ConfigureAwait(false);
if (filesetcount == RemovedFileCount)
throw new Interface.UserInformationException($"Refusing to purge {RemovedFileCount} files from fileset with ID {ParentID}, as that would remove the entire fileset.\nTo delete a fileset, use the \"delete\" command.", "PurgeWouldRemoveEntireFileset");
}
- public async Task> ConvertToPermanentFileset(string name, DateTime timestamp, bool isFullBackup)
+ ///
+ public async Task> ConvertToPermanentFileset(string name, DateTime timestamp, bool isFullBackup, CancellationToken token)
{
var remotevolid =
- await m_db.RegisterRemoteVolume(name, RemoteVolumeType.Files, RemoteVolumeState.Temporary)
+ await m_db
+ .RegisterRemoteVolume(name, RemoteVolumeType.Files, RemoteVolumeState.Temporary, token)
.ConfigureAwait(false);
- var filesetid = await m_db.CreateFileset(remotevolid, timestamp)
+ var filesetid = await m_db
+ .CreateFileset(remotevolid, timestamp, token)
.ConfigureAwait(false);
- await m_db.UpdateFullBackupStateInFileset(filesetid, isFullBackup)
+ await m_db
+ .UpdateFullBackupStateInFileset(filesetid, isFullBackup, token)
.ConfigureAwait(false);
await using (var cmd = m_db.Connection.CreateCommand(m_db.Transaction))
@@ -428,13 +447,14 @@ namespace Duplicati.Library.Main.Database
")
.SetParameterValue("@TargetFilesetId", filesetid)
.SetParameterValue("@SourceFilesetId", ParentID)
- .ExecuteNonQueryAsync()
+ .ExecuteNonQueryAsync(token)
.ConfigureAwait(false);
return new Tuple(remotevolid, filesetid);
}
- public async IAsyncEnumerable> ListAllDeletedFiles()
+ ///
+ public async IAsyncEnumerable> ListAllDeletedFiles([EnumeratorCancellation] CancellationToken token)
{
await using var cmd = m_db.Connection.CreateCommand();
await using var rd = await cmd.ExecuteReaderAsync($@"
@@ -448,8 +468,10 @@ namespace Duplicati.Library.Main.Database
WHERE
""A"".""FileID"" = ""B"".""ID""
AND ""B"".""BlocksetID"" = ""C"".""ID""
- ").ConfigureAwait(false);
- while (await rd.ReadAsync().ConfigureAwait(false))
+ ", token)
+ .ConfigureAwait(false);
+
+ while (await rd.ReadAsync(token).ConfigureAwait(false))
yield return new KeyValuePair(
rd.ConvertValueToString(0) ?? "",
rd.ConvertValueToInt64(1)
@@ -467,7 +489,7 @@ namespace Duplicati.Library.Main.Database
{
await using var cmd = m_db.Connection.CreateCommand();
await cmd
- .ExecuteNonQueryAsync($@"DROP TABLE IF EXISTS ""{m_tablename}""")
+ .ExecuteNonQueryAsync($@"DROP TABLE IF EXISTS ""{m_tablename}""", default)
.ConfigureAwait(false);
}
catch { }