diff --git a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs index 596195dfa..62fd954d4 100644 --- a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs @@ -748,6 +748,7 @@ namespace Duplicati.Library.Main.Database /// /// The block key. /// The size of the block. + /// The ID of the volume to which the block belongs. /// A taskt that when awaited contains true if the block should be added to the current output. public async Task AddBlock(string key, long size, long volumeid) { @@ -828,7 +829,7 @@ namespace Duplicati.Library.Main.Database /// The size of the blockset. /// The size of the blocks in the blockset. /// The list of hashes. - /// The id of the blockset, new or old. + /// The list of hashes for the blocklist, or null if no blocklist is used. /// A task that when awaited contains a tuple with the first value indicating whether the blockset was created, and the second value being the blockset ID. public async Task<(bool, long)> AddBlockset(string filehash, long size, int blocksize, IEnumerable hashes, IEnumerable blocklistHashes) { @@ -954,14 +955,12 @@ namespace Duplicati.Library.Main.Database } /// - /// Adds a metadata set to the database, and returns a value indicating if the record was new + /// Adds a metadata set to the database, and returns a tuple indicating if the record was new and the ID of the metadata set. /// - /// The metadata hash - /// The size of the metadata - /// The transaction to execute under - /// The id of the blockset to add - /// The id of the metadata set - /// True if the set was added to the database, false otherwise + /// The metadata hash. + /// The size of the metadata. + /// The id of the blockset to add. + /// A task that when awaited contains a tuple with the first value indicating if the metadata set was added, and the second value being the metadata ID. public async Task<(bool, long)> AddMetadataset(string filehash, long size, long blocksetid) { var (metadatafound, metadataid) = await GetMetadatasetID(filehash, size) @@ -981,14 +980,14 @@ namespace Duplicati.Library.Main.Database } /// - /// Adds a file record to the database + /// Adds a file record to the database. /// - /// The path prefix ID - /// The path to the file - /// The time the file was modified - /// The ID of the hashkey for the file - /// The ID for the metadata - /// The transaction to use for insertion, or null for no transaction + /// The path prefix ID. + /// The path to the file. + /// The time the file was modified. + /// The ID of the hashkey for the file. + /// The ID for the metadata. + /// A task that completes when the file is added. public async Task AddFile(long pathprefixid, string filename, DateTime lastmodified, long blocksetID, long metadataID) { var fileidobj = await m_findfilesetCommand @@ -1017,13 +1016,13 @@ namespace Duplicati.Library.Main.Database } /// - /// Adds a file record to the database + /// Adds a file record to the database. /// - /// The path to the file - /// The time the file was modified - /// The ID of the hashkey for the file - /// The ID for the metadata - /// The transaction to use for insertion, or null for no transaction + /// The path to the file. + /// The time the file was modified. + /// The ID of the hashkey for the file. + /// The ID for the metadata. + /// A task that completes when the file is added. public async Task AddFile(string filename, DateTime lastmodified, long blocksetID, long metadataID) { var split = SplitIntoPrefixAndName(filename); @@ -1039,11 +1038,11 @@ namespace Duplicati.Library.Main.Database } /// - /// Adds a known file to the fileset + /// Adds a known file to the fileset. /// - /// Id of the file - /// The time the file was modified - /// The transaction to use for insertion, or null for no transaction + /// Id of the file. + /// The time the file was modified. + /// A task that completes when the file is added. public async Task AddKnownFile(long fileid, DateTime lastmodified) { await m_insertfileOperationCommand @@ -1055,18 +1054,40 @@ namespace Duplicati.Library.Main.Database .ConfigureAwait(false); } + /// + /// Adds a directory entry to the fileset. + /// + /// The path to the directory. + /// The ID for the metadata. + /// The time the directory was modified. + /// A task that completes when the directory entry is added. public async Task AddDirectoryEntry(string path, long metadataID, DateTime lastmodified) { await AddFile(path, lastmodified, FOLDER_BLOCKSET_ID, metadataID) .ConfigureAwait(false); } + /// + /// Adds a symlink entry to the fileset. + /// + /// The path to the symlink. + /// The ID for the metadata. + /// The time the symlink was modified. + /// A task that completes when the symlink entry is added. public async Task AddSymlinkEntry(string path, long metadataID, DateTime lastmodified) { await AddFile(path, lastmodified, SYMLINK_BLOCKSET_ID, metadataID) .ConfigureAwait(false); } + /// + /// Gets the ID, last modified time and size of a file in the fileset. + /// + /// The ID of the path prefix. + /// The path to the file. + /// The ID of the fileset. + /// Whether to include the file length in the result. + /// A task that when awaited contains a tuple with the file ID, last modified time, and file length. public async Task<(long, DateTime, long)> GetFileLastModified(long prefixid, string path, long filesetid, bool includeLength) { DateTime oldModified; @@ -1155,6 +1176,11 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Gets the metadata hash and size for a file. + /// + /// The ID of the file. + /// A task that when awaited contains a tuple with the metadata hash and size, or null if the file does not exist. public async Task<(string MetadataHash, long Size)?> GetMetadataHashAndSizeForFile(long fileid) { m_selectfilemetadatahashandsizeCommand @@ -1171,7 +1197,11 @@ namespace Duplicati.Library.Main.Database return null; } - + /// + /// Gets the hash of a file. + /// + /// The ID of the file. + /// A task that when awaited contains the hash of the file, or null if the file does not exist. public async Task GetFileHash(long fileid) { var r = await m_selectfileHashCommand @@ -1209,6 +1239,10 @@ namespace Duplicati.Library.Main.Database await base.DisposeAsync().ConfigureAwait(false); } + /// + /// Gets the size of the last written DBlock volume. + /// + /// A task that when awaited contains the size of the last written DBlock volume, or -1 if no such volume exists. public async Task GetLastWrittenDBlockVolumeSize() { using (var cmd = m_connection.CreateCommand(m_rtr)) @@ -1227,12 +1261,24 @@ namespace Duplicati.Library.Main.Database .ConfigureAwait(false); } + /// + /// Gets the ID of the previous fileset based on the operation timestamp and current fileset ID. + /// + /// The command to use for the query. + /// A task that when awaited contains the ID of the previous fileset, or -1 if no such fileset exists. private async Task GetPreviousFilesetID(SqliteCommand cmd) { return await GetPreviousFilesetID(cmd, OperationTimestamp, m_filesetId) .ConfigureAwait(false); } + /// + /// Gets the ID of the previous fileset based on the operation timestamp and current fileset ID. + /// + /// The command to use for the query. + /// The timestamp to use for the query. + /// The current fileset ID. + /// A task that when awaited contains the ID of the previous fileset, or -1 if no such fileset exists. private async Task GetPreviousFilesetID(SqliteCommand cmd, DateTime timestamp, long filesetid) { return await cmd @@ -1251,6 +1297,10 @@ namespace Duplicati.Library.Main.Database .ConfigureAwait(false); } + /// + /// Gets the count and size of files in the last backup fileset. + /// + /// A task that when awaited contains a tuple with the count of files and the total size of files in the last backup fileset. internal async Task> GetLastBackupFileCountAndSize() { using (var cmd = m_connection.CreateCommand(m_rtr)) @@ -1306,6 +1356,11 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Updates the change statistics for the current fileset based on the results of a backup operation. + /// + /// The results of the backup operation. + /// A task that completes when the change statistics are updated. internal async Task UpdateChangeStatistics(BackupResults results) { using (var cmd = m_connection.CreateCommand(m_rtr)) @@ -1321,8 +1376,8 @@ namespace Duplicati.Library.Main.Database /// Populates FilesetEntry table with files from previous fileset, which aren't /// yet part of the new fileset, and which aren't on the (optional) list of deleted paths. /// - /// Transaction - /// List of deleted paths, or null + /// List of deleted paths, or null. + /// A task that completes when the files are appended. public async Task AppendFilesFromPreviousSet(IEnumerable? deleted = null) { await AppendFilesFromPreviousSet(deleted, m_filesetId, -1, OperationTimestamp) @@ -1333,11 +1388,11 @@ namespace Duplicati.Library.Main.Database /// Populates FilesetEntry table with files from previous fileset, which aren't /// yet part of the new fileset, and which aren't on the (optional) list of deleted paths. /// - /// Transaction - /// List of deleted paths, or null - /// Current file-set ID - /// Source file-set ID - /// If filesetid == -1, used to locate previous file-set + /// List of deleted paths, or null. + /// Current file-set ID. + /// Source file-set ID. + /// If filesetid == -1, used to locate previous file-set. + /// A task that completes when the files are appended. public async Task AppendFilesFromPreviousSet(IEnumerable? deleted, long filesetid, long prevId, DateTime timestamp) { using (var cmd = m_connection.CreateCommand()) @@ -1415,8 +1470,8 @@ namespace Duplicati.Library.Main.Database /// yet part of the new fileset, and which aren't excluded by the (optional) exclusion /// predicate. /// - /// Transaction - /// Optional exclusion predicate (true = exclude file) + /// Optional exclusion predicate (true = exclude file). + /// A task that completes when the files are appended. public async Task AppendFilesFromPreviousSetWithPredicate(Func exclusionPredicate) { await AppendFilesFromPreviousSetWithPredicate(exclusionPredicate, m_filesetId, -1, OperationTimestamp) @@ -1428,11 +1483,11 @@ namespace Duplicati.Library.Main.Database /// yet part of the new fileset, and which aren't excluded by the (optional) exclusion /// predicate. /// - /// Transaction /// Optional exclusion predicate (true = exclude file) /// Current fileset ID /// Source fileset ID /// If prevFileSetId == -1, used to locate previous fileset + /// A task that completes when the files are appended. public async Task AppendFilesFromPreviousSetWithPredicate(Func exclusionPredicate, long fileSetId, long prevFileSetId, DateTime timestamp) { if (exclusionPredicate == null) @@ -1542,15 +1597,20 @@ namespace Duplicati.Library.Main.Database /// /// Creates a timestamped backup operation to correctly associate the fileset with the time it was created. /// - /// The ID of the fileset volume to update - /// The timestamp of the operation to create - /// An optional external transaction + /// The ID of the fileset volume to update. + /// The timestamp of the operation to create. + /// A task that when awaited contains the ID of the created fileset. public override async Task CreateFileset(long volumeid, DateTime timestamp) { return m_filesetId = await base.CreateFileset(volumeid, timestamp) .ConfigureAwait(false); } + /// + /// Retrieves the names of temporary fileset volumes that are incomplete. + /// + /// If true, only the latest incomplete fileset volume will be returned. + /// A task that when awaited contains a list of volume names. public async Task> GetTemporaryFilelistVolumeNames(bool latestOnly) { var incompleteFilesetIDs = GetIncompleteFilesets().OrderBy(x => x.Value).Select(x => x.Key); @@ -1574,6 +1634,10 @@ namespace Duplicati.Library.Main.Database return volumeNames; } + /// + /// Retrieves the names of remote volumes that are missing index files. + /// + /// An asynchronous enumerable of volume names that are missing index files. public async IAsyncEnumerable GetMissingIndexFiles() { using var cmd = m_connection.CreateCommand(m_rtr) @@ -1599,6 +1663,14 @@ namespace Duplicati.Library.Main.Database yield return rd.ConvertValueToString(0) ?? throw new Exception("Unexpected null value for volume name"); } + /// + /// Moves a block from one volume to another. + /// + /// The hash of the block to move. + /// The size of the block to move. + /// The ID of the source volume. + /// The ID of the target volume. + /// A task that completes when the block is moved. public async Task MoveBlockToVolume(string blockkey, long size, long sourcevolumeid, long targetvolumeid) { using (var cmd = m_connection.CreateCommand(m_rtr)) @@ -1623,6 +1695,13 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Safely deletes a remote volume by checking if it has any associated blocks. + /// If it does, an exception is thrown; otherwise, the volume is removed. + /// + /// The name of the remote volume to delete. + /// A task that completes when the remote volume is safely deleted. + /// Thrown if the volume has associated blocks. public async Task SafeDeleteRemoteVolume(string name) { var volumeid = await GetRemoteVolumeID(name).ConfigureAwait(false); @@ -1645,6 +1724,11 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Retrieves the hashes of blocks that are on the blocklist for a given volume. + /// + /// The name of the volume to check. + /// A task that when awaited contains an array of blocklist hashes. public async Task GetBlocklistHashes(string name) { var volumeid = GetRemoteVolumeID(name); @@ -1670,6 +1754,10 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Retrieves the first path in the database, ordered by length in descending order. + /// + /// A task that when awaited contains the first path, or null if no paths exist. public async Task GetFirstPath() { using (var cmd = m_connection.CreateCommand(m_rtr)) @@ -1690,9 +1778,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Retrieves change journal data for file set + /// Retrieves the change journal data for file set. /// - /// Fileset-ID + /// The Fileset-ID. + /// An asynchronous enumerable of USN journal data entries. public async IAsyncEnumerable GetChangeJournalData(long fileSetId) { var data = new List(); @@ -1725,10 +1814,11 @@ namespace Duplicati.Library.Main.Database } /// - /// Adds NTFS change journal data for file set and volume + /// Adds NTFS change journal data for file set and volume. /// - /// Data to add - /// An optional external transaction + /// Data to add. + /// A task that completes when the data is added. + /// Thrown if unable to add change journal entry. public async Task CreateChangeJournalData(IEnumerable data) { foreach (var entry in data) @@ -1768,11 +1858,11 @@ namespace Duplicati.Library.Main.Database } /// - /// Adds NTFS change journal data for file set and volume + /// Adds NTFS change journal data for file set and volume. /// - /// Data to add - /// Existing file set to update - /// An optional external transaction + /// Data to add. + /// Existing file set to update. + /// A task that completes when the data is added. public async Task UpdateChangeJournalData(IEnumerable data, long fileSetId) { foreach (var entry in data) @@ -1801,11 +1891,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Checks if a blocklist hash is known + /// Checks if a blocklist hash is known. /// - /// The hash to check - /// An optional external transaction - /// True if the hash is known, false otherwise + /// The hash to check. + /// A task that when awaited returns true if the hash is known, false otherwise. public async Task IsBlocklistHashKnown(string hash) { var res = await m_getfirstfilesetwithblockinblockset diff --git a/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs b/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs index 6470afa09..3b6674488 100644 --- a/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs @@ -27,6 +27,10 @@ using Duplicati.Library.Common.IO; namespace Duplicati.Library.Main.Database { + + /// + /// A local database for bug reports, which obfuscates sensitive data before generating a bug report. + /// internal class LocalBugReportDatabase : LocalDatabase { /// @@ -34,6 +38,13 @@ namespace Duplicati.Library.Main.Database /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalBugReportDatabase)); + /// + /// Creates a new instance of the class. + /// + /// 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 task that when awaited contains a new instance of . public static async Task CreateAsync(string path, long pagecachesize, LocalBugReportDatabase? dbnew = null) { dbnew ??= new LocalBugReportDatabase(); @@ -46,6 +57,10 @@ namespace Duplicati.Library.Main.Database return dbnew; } + /// + /// Obfuscates sensitive data in the database, readying it for a bug report. + /// + /// A task that completes when the obfuscation is finished. public async Task Fix() { using (var cmd = m_connection.CreateCommand(m_rtr)) diff --git a/Duplicati/Library/Main/Database/LocalDatabase.cs b/Duplicati/Library/Main/Database/LocalDatabase.cs index b78522f23..4f0cc920b 100644 --- a/Duplicati/Library/Main/Database/LocalDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDatabase.cs @@ -40,7 +40,12 @@ using System.Threading; namespace Duplicati.Library.Main.Database { - internal class LocalDatabase : IDisposable + /// + /// Represents a local database for Duplicati operations. + /// This class provides methods to interact with the local SQLite database, including + /// managing remote volumes, logging operations, and handling transactions. + /// + internal class LocalDatabase : IDisposable, IAsyncDisposable { /// /// The tag used for logging @@ -53,39 +58,114 @@ namespace Duplicati.Library.Main.Database /// SQLite has a limit of 999 parameters in a single statement public const int CHUNK_SIZE = 128; - // All of the required fields have been set to null! to ignore the compiler warning, as they will be initialized in the Create* factory functions. + // All of the required fields have been set to null! to ignore the compiler warning, as they will be initialized in the Create* factory methods. + /// + /// The SQLite connection to the local database. + /// protected SqliteConnection m_connection = null!; + /// + /// The operation ID for the current operation. + /// protected long m_operationid = -1; + /// + /// The size of the SQLite page cache. + /// protected long m_pagecachesize; + /// + /// Indicates whether the database has executed a vacuum operation. + /// private bool m_hasExecutedVacuum; + /// + /// The reusable transaction for the current operation. + /// protected ReusableTransaction m_rtr = null!; + /// + /// A read-only property that provides access to the current transaction. + /// public ReusableTransaction Transaction { get { return m_rtr; } } + /// + /// The command used to update a remote volume in the database. + /// private SqliteCommand m_updateremotevolumeCommand = null!; + /// + /// The command used to select remote volumes from the database. + /// private SqliteCommand m_selectremotevolumesCommand = null!; + /// + /// The command used to select a specific remote volume by name. + /// private SqliteCommand m_selectremotevolumeCommand = null!; + /// + /// The command used to remove a remote volume from the database. + /// private SqliteCommand m_removeremotevolumeCommand = null!; + /// + /// The command used to remove deleted remote volumes from the database. + /// private SqliteCommand m_removedeletedremotevolumeCommand = null!; + /// + /// The command used to select the ID of a remote volume by name. + /// private SqliteCommand m_selectremotevolumeIdCommand = null!; + /// + /// The command used to create a new remote volume in the database. + /// private SqliteCommand m_createremotevolumeCommand = null!; + /// + /// The command used to select duplicate remote volumes from the database. + /// private SqliteCommand m_selectduplicateRemoteVolumesCommand = null!; + /// + /// The command used to insert log data into the database. + /// private SqliteCommand m_insertlogCommand = null!; + /// + /// The command used to insert remote operation logs into the database. + /// private SqliteCommand m_insertremotelogCommand = null!; + /// + /// The command used to insert index block links into the database. + /// private SqliteCommand m_insertIndexBlockLink = null!; + /// + /// The command used to find a path prefix in the database. + /// private SqliteCommand m_findpathprefixCommand = null!; + /// + /// The command used to insert a new path prefix into the database. + /// private SqliteCommand m_insertpathprefixCommand = null!; + /// + /// A constant representing the blockset ID for a folder. + /// public const long FOLDER_BLOCKSET_ID = -100; + /// + /// A constant representing the blockset ID for a symlink. + /// public const long SYMLINK_BLOCKSET_ID = -200; + /// + /// The timestamp of the operation being performed. + /// public DateTime OperationTimestamp { get; private set; } + /// + /// A read-only property that provides access to the internal SQLite connection. + /// internal SqliteConnection Connection { get { return m_connection; } } + /// + /// Indicates whether the database has been disposed. + /// public bool IsDisposed { get; private set; } + /// + /// Indicates whether the connection should be closed when the database is disposed. + /// public bool ShouldCloseConnection { get; set; } // Constructor is private to force use of CreateLocalDatabaseAsync @@ -98,6 +178,13 @@ namespace Duplicati.Library.Main.Database throw new NotImplementedException("Use the CreateLocalDatabaseAsync or CreateLocalDatabase functions instead"); } + /// + /// Creates a new SQLite connection to the specified database path with the given page cache size. + /// This method ensures that the directory for the database exists and upgrades the database schema if necessary. + /// + /// The path to the SQLite database file. + /// The size of the SQLite page cache in bytes. + /// A task that, when awaited, returns a new to the specified database. protected static async Task CreateConnectionAsync(string path, long pagecachesize) { path = Path.GetFullPath(path); @@ -121,6 +208,16 @@ namespace Duplicati.Library.Main.Database return c; } + /// + /// Creates a new instance of with the specified parameters. + /// This method initializes the database connection, sets the operation timestamp, and prepares the necessary commands for database operations. + /// + /// The path to the SQLite database file. + /// The description of the operation being performed. + /// Indicates whether the connection should be closed when the database is disposed. + /// The size of the SQLite page cache in bytes. + /// An optional existing instance to use. If not provided, a new instance will be created. + /// A task that, when awaited, returns a new instance of . public static async Task CreateLocalDatabaseAsync(string path, string operation, bool shouldclose, long pagecachesize, LocalDatabase? db = null) { db ??= new LocalDatabase(); @@ -136,6 +233,12 @@ namespace Duplicati.Library.Main.Database return db; } + /// + /// Creates a new instance of based on an existing database instance. + /// This method copies the connection and transaction from the parent database and initializes the new database with the same operation timestamp and ID. + /// + /// The parent instance to copy from. + /// An optional existing instance to use. If not provided, a new instance will be created. public static async Task CreateLocalDatabaseAsync(LocalDatabase dbparent, LocalDatabase? dbnew = null) { dbnew ??= new LocalDatabase(); @@ -152,6 +255,14 @@ namespace Duplicati.Library.Main.Database return dbnew; } + /// + /// Creates a new instance of with the specified SQLite connection and operation description. + /// This method initializes the database connection, sets the operation timestamp, and prepares the necessary commands for database operations. + /// + /// The SQLite connection to use for the database operations. + /// The description of the operation being performed. + /// An optional existing instance to use. If not provided, a new instance will be created. + /// A task that, when awaited, returns a new instance of . public static async Task CreateLocalDatabaseAsync(SqliteConnection connection, string operation, LocalDatabase? dbnew = null) { dbnew ??= new LocalDatabase(); @@ -206,6 +317,13 @@ namespace Duplicati.Library.Main.Database return dbnew; } + /// + /// Creates a new instance of with the specified SQLite connection. + /// This method initializes the database connection, sets up the reusable transaction, and prepares the necessary commands for database operations. + /// + /// The SQLite connection to use for the database operations. + /// An optional existing instance to use. If not provided, a new instance will be created. + /// A task that, when awaited, returns a new instance of . private static async Task CreateLocalDatabaseAsync(SqliteConnection connection, LocalDatabase? dbnew = null) { dbnew ??= new LocalDatabase(); @@ -382,25 +500,55 @@ namespace Duplicati.Library.Main.Database } /// - /// Creates a DateTime instance by adding the specified number of seconds to the EPOCH value + /// Creates a DateTime instance by adding the specified number of seconds to the EPOCH value. /// + /// The number of seconds since the EPOCH (January 1, 1970). + /// A DateTime instance representing the specified number of seconds since the EPOCH. public static DateTime ParseFromEpochSeconds(long seconds) { return Library.Utility.Utility.EPOCH.AddSeconds(seconds); } + /// + /// Updates the state of a remote volume in the database. + /// + /// The name of the remote volume to update. + /// The new state of the remote volume. + /// The size of the remote volume in bytes. + /// The hash of the remote volume, or null if not applicable. + /// A task that completes when the remote volume has been updated. public async Task UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string? hash) { await UpdateRemoteVolume(name, state, size, hash, false) .ConfigureAwait(false); } + /// + /// Updates the state of a remote volume in the database. + /// + /// The name of the remote volume to update. + /// The new state of the remote volume. + /// The size of the remote volume in bytes. + /// The hash of the remote volume, or null if not applicable. + /// If true, suppresses cleanup of the remote volume after updating. + /// A task that completes when the remote volume has been updated. public async Task UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string? hash, bool suppressCleanup) { await UpdateRemoteVolume(name, state, size, hash, suppressCleanup, new TimeSpan(0), null) .ConfigureAwait(false); } + /// + /// Updates the state of a remote volume in the database. + /// + /// The name of the remote volume to update. + /// The new state of the remote volume. + /// The size of the remote volume in bytes. + /// The hash of the remote volume, or null if not applicable. + /// If true, suppresses cleanup of the remote volume after updating. + /// The time after which the remote volume can be deleted. + /// If true, sets the remote volume as archived. + /// A task that completes when the remote volume has been updated. public async Task UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string? hash, bool suppressCleanup, TimeSpan deleteGraceTime, bool? setArchived) { var c = await m_updateremotevolumeCommand.SetTransaction(m_rtr) @@ -461,6 +609,10 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Gets the ID and timestamp of all filesets in the database, ordered by timestamp in descending order. + /// + /// An asynchronous enumerable of key-value pairs, where each pair contains the fileset ID and its timestamp. public async IAsyncEnumerable> FilesetTimes() { using var cmd = await m_connection.CreateCommandAsync(@" @@ -480,6 +632,15 @@ namespace Duplicati.Library.Main.Database ); } + /// + /// Generates a SQL WHERE clause for filtering file lists based on a specified time and versions. + /// + /// The time to filter files by. If Ticks is 0, it will not be used in the query. + /// An array of versions to filter files by. If null or empty, it will not be used in the query. + /// An optional list of filesets to use for filtering. If null, it will fetch all filesets from the database. + /// If true, matches files with the exact timestamp; otherwise, matches files with timestamps less than or equal to the specified time. + /// A task that, when awaited, returns a tuple containing the SQL WHERE clause and a dictionary of parameter values. + /// Thrown if the provided time is unspecified. public async Task<(string Query, Dictionary Values)> GetFilelistWhereClause(DateTime time, long[]? versions, IEnumerable>? filesetslist = null, bool singleTimeMatch = false) { KeyValuePair[] filesets; @@ -540,6 +701,11 @@ namespace Duplicati.Library.Main.Database return (query.ToString(), args); } + /// + /// Gets the ID of a remote volume by its name. + /// + /// The name of the remote volume. + /// A task that, when awaited, returns the ID of the remote volume. If the volume does not exist, it returns -1. public async Task GetRemoteVolumeID(string file) { return await m_selectremotevolumeIdCommand @@ -549,6 +715,11 @@ namespace Duplicati.Library.Main.Database .ConfigureAwait(false); } + /// + /// Gets the IDs of remote volumes for a list of files. + /// + /// An enumerable collection of file names. + /// An asynchronous enumerable of key-value pairs, where each pair contains the file name and its corresponding remote volume ID. public async IAsyncEnumerable> GetRemoteVolumeIDs(IEnumerable files) { using var cmd = await m_connection.CreateCommandAsync(@" @@ -571,6 +742,11 @@ namespace Duplicati.Library.Main.Database yield return new KeyValuePair(rd.ConvertValueToString(0) ?? "", rd.ConvertValueToInt64(1)); } + /// + /// Gets a remote volume entry by its file name. + /// + /// The name of the remote volume file. + /// A task that, when awaited, returns a representing the remote volume. If the volume does not exist, it returns an empty entry. public async Task GetRemoteVolume(string file) { m_selectremotevolumeCommand @@ -593,6 +769,10 @@ namespace Duplicati.Library.Main.Database return RemoteVolumeEntry.Empty; } + /// + /// Gets remote volumes that are duplicates, meaning they have the same name but different states. + /// + /// An asynchronous enumerable of key-value pairs, where each pair contains the name of the remote volume and its state. public async IAsyncEnumerable> DuplicateRemoteVolumes() { m_selectduplicateRemoteVolumesCommand.SetTransaction(m_rtr); @@ -608,6 +788,10 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Gets all remote volumes from the database. + /// + /// An asynchronous enumerable of representing all remote volumes. public async IAsyncEnumerable GetRemoteVolumes() { m_selectremotevolumesCommand.SetTransaction(m_rtr); @@ -631,11 +815,12 @@ namespace Duplicati.Library.Main.Database } /// - /// Log an operation performed on the remote backend + /// Log an operation performed on the remote backend. /// - /// The operation performed - /// The path involved - /// Any data relating to the operation + /// The operation performed. + /// The path involved. + /// Any data relating to the operation. + /// A task that completes when the operation log has been recorded. public async Task LogRemoteOperation(string operation, string path, string? data) { await m_insertremotelogCommand @@ -650,11 +835,12 @@ namespace Duplicati.Library.Main.Database } /// - /// Log a debug message + /// Log a debug message. /// - /// The message type - /// The message - /// An optional exception + /// The message type. + /// The message. + /// An optional exception. + /// A task that completes when the log message has been recorded. public async Task LogMessage(string type, string message, Exception? exception) { await m_insertlogCommand @@ -668,6 +854,14 @@ namespace Duplicati.Library.Main.Database .ConfigureAwait(false); } + /// + /// Unlinks a remote volume from the database. + /// This operation removes the remote volume entry from the database without deleting the actual volume. + /// + /// The name of the remote volume to unlink. + /// The state of the remote volume to unlink. + /// A task that completes when the remote volume has been unlinked. + /// Thrown if the number of unlinked remote volumes is not equal to 1. public async Task UnlinkRemoteVolume(string name, RemoteVolumeState state) { using var cmd = await m_connection.CreateCommandAsync(@" @@ -688,11 +882,21 @@ namespace Duplicati.Library.Main.Database throw new Exception($"Unexpected number of remote volumes deleted: {c}, expected {1}"); } + /// + /// Removes a remote volume from the database. + /// + /// The name of the remote volume to remove. + /// A task that completes when the remote volume has been removed. public async Task RemoveRemoteVolume(string name) { await RemoveRemoteVolumes([name]).ConfigureAwait(false); } + /// + /// Removes multiple remote volumes from the database. + /// + /// An enumerable collection of names of remote volumes to remove. + /// A task that completes when the remote volumes have been removed. public async Task RemoveRemoteVolumes(IEnumerable names) { if (names == null || !names.Any()) return; @@ -1031,6 +1235,12 @@ namespace Duplicati.Library.Main.Database throw new ConstraintException($"Detected {nonAttachedFiles} file(s) in FilesetEntry without corresponding FileLookup entry"); } + /// + /// Performs a VACUUM operation on the database to reclaim unused space. + /// This operation can help optimize the database performance by defragmenting it. + /// Note: This operation can take a significant amount of time depending on the size of the database. + /// + /// A task that completes when the VACUUM operation has finished. public async Task Vacuum() { m_hasExecutedVacuum = true; @@ -1039,24 +1249,56 @@ namespace Duplicati.Library.Main.Database await cmd.ExecuteNonQueryAsync("VACUUM").ConfigureAwait(false); } + /// + /// Registers a new remote volume in the database. + /// + /// The name of the remote volume. + /// The type of the remote volume. + /// The size of the remote volume in bytes. Use -1 for unknown size. + /// The state of the remote volume. + /// A task that, when awaited, returns the ID of the newly registered remote volume. public async Task RegisterRemoteVolume(string name, RemoteVolumeType type, long size, RemoteVolumeState state) { return await RegisterRemoteVolume(name, type, state, size, new TimeSpan(0)) .ConfigureAwait(false); } + /// + /// Registers a new remote volume in the database. + /// + /// The name of the remote volume. + /// The type of the remote volume. + /// The state of the remote volume. + /// A task that, when awaited, returns the ID of the newly registered remote volume. public async Task RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state) { return await RegisterRemoteVolume(name, type, state, new TimeSpan(0)) .ConfigureAwait(false); } + /// + /// Registers a new remote volume in the database. + /// + /// The name of the remote volume. + /// The type of the remote volume. + /// The state of the remote volume. + /// The time after which the remote volume can be deleted. + /// A task that, when awaited, returns the ID of the newly registered remote volume. public async Task RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, TimeSpan deleteGraceTime) { return await RegisterRemoteVolume(name, type, state, -1, deleteGraceTime) .ConfigureAwait(false); } + /// + /// Registers a new remote volume in the database. + /// + /// The name of the remote volume. + /// The type of the remote volume. + /// The state of the remote volume. + /// The size of the remote volume in bytes. Use -1 for unknown size. + /// The time after which the remote volume can be deleted. + /// A task that, when awaited, returns the ID of the newly registered remote volume. public async Task RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, long size, TimeSpan deleteGraceTime) { var r = await m_createremotevolumeCommand @@ -1075,6 +1317,16 @@ namespace Duplicati.Library.Main.Database return r; } + /// + /// Retrieves the IDs of filesets that match a specific restore time and optional versions. + /// If no filesets match the criteria, it returns the newest fileset ID. + /// + /// The time to restore from. + /// Optional array of versions to match against the filesets. + /// If true, only match filesets that exactly match the restore time. + /// An asynchronous enumerable of fileset IDs that match the criteria. + /// Thrown if the provided DateTime is unspecified. + /// Thrown if no backups are found at the specified date. public async IAsyncEnumerable GetFilesetIDs(DateTime restoretime, long[]? versions, bool singleTimeMatch = false) { if (restoretime.Kind == DateTimeKind.Unspecified) @@ -1118,6 +1370,12 @@ namespace Duplicati.Library.Main.Database yield return el; } + /// + /// Finds filesets that match a specific restore time and optional versions. + /// + /// The time to restore from. + /// Optional array of versions to match against the filesets. + /// An asynchronous task that returns a collection of fileset IDs that match the criteria. public async Task> FindMatchingFilesets(DateTime restoretime, long[]? versions) { if (restoretime.Kind == DateTimeKind.Unspecified) @@ -1143,6 +1401,12 @@ namespace Duplicati.Library.Main.Database return res; } + /// + /// Checks if a fileset is a full backup. + /// A fileset is considered a full backup if its "IsFullBackup" field is set to FULL_BACKUP. + /// + /// The timestamp of the fileset to check. + /// A task that, when awaited, returns true if the fileset is a full backup, otherwise false. public async Task IsFilesetFullBackup(DateTime filesetTime) { using var cmd = m_connection.CreateCommand(); @@ -1161,6 +1425,10 @@ namespace Duplicati.Library.Main.Database return isFullBackup == BackupType.FULL_BACKUP; } + /// + /// Retrieves a list of database options from the "Configuration" table. + /// + /// An asynchronous enumerable of key-value pairs representing the database options. private async IAsyncEnumerable> GetDbOptionList() { using var cmd = m_connection.CreateCommand(@" @@ -1179,6 +1447,10 @@ namespace Duplicati.Library.Main.Database ); } + /// + /// Retrieves all database options as a dictionary. + /// + /// A task that, when awaited, returns a dictionary containing all database options. public async Task> GetDbOptions() { var res = await GetDbOptionList() @@ -1189,10 +1461,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Updates a database option + /// Updates a database option. /// - /// The key to update - /// The value to set + /// The key to update. + /// The value to set. private async Task UpdateDbOption(string key, bool value) { var opts = await GetDbOptions().ConfigureAwait(false); @@ -1207,8 +1479,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Flag indicating if a repair is in progress + /// Flag indicating if a repair is in progress. /// + /// Optional value to set the flag to. If null, the current value in the database is returned. + /// A task that, when awaited, returns true if a repair is in progress, otherwise false. public async Task RepairInProgress(bool? value = null) { if (value is bool v) @@ -1224,8 +1498,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Flag indicating if a repair is in progress + /// Flag indicating if the database has been partially recreated. /// + /// Optional value to set the flag to. If null, the current value in the database is returned. + /// A task that, when awaited, returns true if the database has been partially recreated, otherwise false. public async Task PartiallyRecreated(bool? value = null) { if (value is bool v) @@ -1241,8 +1517,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Flag indicating if the database can contain partial uploads + /// Flag indicating if the database has been terminated with active uploads. /// + /// Optional value to set the flag to. If null, the current value in the database is returned. + /// A task that, when awaited, returns true if the database has been terminated with active uploads, otherwise false. public async Task TerminatedWithActiveUploads(bool? value = null) { if (value is bool v) @@ -1258,10 +1536,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Sets the database options + /// Sets the database options. /// - /// The options to set - /// An optional transaction + /// The options to set. + /// A task that completes when the options have been set. public async Task SetDbOptions(IDictionary options) { using var cmd = m_connection.CreateCommand(); @@ -1289,6 +1567,11 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Counts the number of blocks larger than a specified size. + /// + /// The size in bytes to compare against. + /// A task that, when awaited, returns the count of blocks larger than the specified size. public async Task GetBlocksLargerThan(long fhblocksize) { using var cmd = await m_connection.CreateCommandAsync(@" @@ -1305,35 +1588,35 @@ namespace Duplicati.Library.Main.Database } /// - /// Verifies the consistency of the database + /// Verifies the consistency of the database. /// - /// The block size in bytes - /// The hash size in byts - /// Also verify filelists (can be slow) - /// The transaction to run in + /// The block size in bytes. + /// The hash size in bytes. + /// Also verify filelists (can be slow). + /// A task that completes when the consistency check is finished. public async Task VerifyConsistency(long blocksize, long hashsize, bool verifyfilelists) => await VerifyConsistencyInner(blocksize, hashsize, verifyfilelists, false) .ConfigureAwait(false); /// - /// Verifies the consistency of the database prior to repair + /// Verifies the consistency of the database prior to repair. /// - /// The block size in bytes - /// The hash size in byts - /// Also verify filelists (can be slow) - /// The transaction to run in + /// The block size in bytes. + /// The hash size in bytes. + /// Also verify filelists (can be slow). + /// A task that completes when the consistency check is finished. public async Task VerifyConsistencyForRepair(long blocksize, long hashsize, bool verifyfilelists) => await VerifyConsistencyInner(blocksize, hashsize, verifyfilelists, true) .ConfigureAwait(false); /// - /// Verifies the consistency of the database + /// Verifies the consistency of the database. /// - /// The block size in bytes - /// The hash size in byts - /// Also verify filelists (can be slow) - /// Disable verify for errors that will be fixed by repair - /// The transaction to run in + /// The block size in bytes. + /// The hash size in bytes. + /// Also verify filelists (can be slow). + /// Disable verify for errors that will be fixed by repair. + /// A task that completes when the consistency check is finished. private async Task VerifyConsistencyInner(long blocksize, long hashsize, bool verifyfilelists, bool laxVerifyForRepair) { using var cmd = m_connection.CreateCommand() @@ -1692,18 +1975,37 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Represents a block in the database. + /// public interface IBlock { + /// + /// Gets the hash of the block. + /// string Hash { get; } + /// + /// Gets the size of the block in bytes. + /// long Size { get; } } + /// + /// Represents a block in the database with its hash and size. + /// + /// The hash of the block. + /// The size of the block in bytes. internal class Block(string hash, long size) : IBlock { public string Hash { get; private set; } = hash; public long Size { get; private set; } = size; } + /// + /// Retrieves all blocks associated with a specific volume ID. + /// + /// The ID of the volume to retrieve blocks for. + /// An asynchronous enumerable of blocks associated with the specified volume ID. public async IAsyncEnumerable GetBlocks(long volumeid) { using var cmd = await m_connection.CreateCommandAsync(@" @@ -1726,25 +2028,55 @@ namespace Duplicati.Library.Main.Database ); } + /// + /// An asynchronous enumerable that retrieves blocklist hashes for files in a specific fileset. + /// private class BlocklistHashEnumerable : IAsyncEnumerable { + /// + /// An asynchronous enumerator for the blocklist hashes. + /// private class BlocklistHashEnumerator : IDisposable, IAsyncEnumerator { + /// + /// The data reader used to read blocklist hashes. + /// private readonly SqliteDataReader m_reader; + /// + /// The parent enumerable that this enumerator belongs to. + /// private readonly BlocklistHashEnumerable m_parent; + /// + /// The path of the current file being processed. + /// private string? m_path = null; + /// + /// Indicates if this is the first entry being processed. + /// private bool m_first = true; + /// + /// The current blocklist hash being processed. + /// private string? m_current = null; + /// + /// Initializes a new instance of the class. + /// + /// The parent enumerable that this enumerator belongs to. + /// The data reader used to read blocklist hashes. public BlocklistHashEnumerator(BlocklistHashEnumerable parent, SqliteDataReader reader) { m_reader = reader; m_parent = parent; } + /// + /// Gets the current blocklist hash. + /// public string Current { get { return m_current!; } } public void Dispose() { } + // The warning is suppressed because the interface requires the method, but there's nothing to dispose. #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public async ValueTask DisposeAsync() { } #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously @@ -1783,6 +2115,9 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Resets the enumerator to its initial state. + /// public void Reset() { if (!m_first) @@ -1792,16 +2127,28 @@ namespace Duplicati.Library.Main.Database } } + /// + /// The data reader used to read blocklist hashes. + /// private readonly SqliteDataReader m_reader; + /// + /// Initializes a new instance of the class. + /// public BlocklistHashEnumerable(SqliteDataReader reader) { m_reader = reader; MoreData = true; } + /// + /// Indicates if there is more data to read. + /// public bool MoreData { get; protected set; } + /// + /// Gets an enumerator that iterates through the blocklist hashes. + /// public IAsyncEnumerator GetEnumerator() { return new BlocklistHashEnumerator(this, m_reader); @@ -1813,6 +2160,9 @@ namespace Duplicati.Library.Main.Database } } + /// + /// SQL query for listing all filesets in the database, including their metadata and blocklist hashes. + /// public const string LIST_FILESETS = @" SELECT ""L"".""Path"", @@ -1894,6 +2244,9 @@ namespace Duplicati.Library.Main.Database ON ""M"".""BlocksetID"" = ""L"".""MetablocksetID"" "; + /// + /// SQL query for listing folders and symlinks in a specific fileset, including their metadata and blocklist hashes. + /// public const string LIST_FOLDERS_AND_SYMLINKS = @" SELECT ""G"".""BlocksetID"", @@ -1941,6 +2294,12 @@ namespace Duplicati.Library.Main.Database ""H"".""Index"" "; + /// + /// Writes the contents of a fileset to a specified volume writer. + /// + /// The volume writer to which the fileset will be written. + /// The ID of the fileset to write. + /// A task that completes when the fileset has been written. public async Task WriteFileset(Volumes.FilesetVolumeWriter filesetvolume, long filesetId) { using var cmd = m_connection.CreateCommand() @@ -2027,6 +2386,12 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Links a fileset to a specific volume by updating the VolumeID in the Fileset table. + /// + /// The ID of the fileset to link. + /// The ID of the volume to link the fileset to. + /// A task that completes when the link operation is finished. public async Task LinkFilesetToVolume(long filesetid, long volumeid) { using var cmd = await m_connection.CreateCommandAsync(@" @@ -2046,6 +2411,11 @@ namespace Duplicati.Library.Main.Database throw new Exception($"Failed to link filesetid {filesetid} to volumeid {volumeid}"); } + /// + /// Pushes timestamp changes from the latest version of a fileset to the previous version. + /// + /// The ID of the fileset whose timestamp changes will be pushed. + /// A task that completes when the timestamp changes have been pushed. public async Task PushTimestampChangesToPreviousVersion(long filesetId) { var query = @" @@ -2073,13 +2443,23 @@ namespace Duplicati.Library.Main.Database } /// - /// Keeps a list of filenames in a temporary table with a single column Path + /// Keeps a list of filenames in a temporary table with a single column Path. /// public class FilteredFilenameTable : IDisposable, IAsyncDisposable { + /// + /// The name of the temporary table that holds the filtered filenames. + /// public string Tablename { get; private set; } + /// + /// The database used to create and manage the temporary table. + /// private readonly LocalDatabase m_db; + /// + /// Initializes a new instance of the class. + /// + /// The database to use for creating the temporary table. private FilteredFilenameTable(LocalDatabase db) { Tablename = "Filenames-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); @@ -2092,6 +2472,12 @@ namespace Duplicati.Library.Main.Database throw new NotImplementedException("Use the Create method instead."); } + /// + /// Creates a new instance of the class asynchronously. + /// + /// The database to use for creating the temporary table. + /// The filter to apply to the filenames. + /// A task that represents the asynchronous operation, with a as the result. public static async Task CreateFilteredFilenameTableAsync(LocalDatabase db, IFilter filter) { var ftt = new FilteredFilenameTable(db); @@ -2228,6 +2614,13 @@ namespace Duplicati.Library.Main.Database } } + /// + /// Renames a remote file in the database, preserving its ID links. + /// + /// The current name of the remote file. + /// The new name for the remote file. + /// A task that completes when the renaming operation is finished. + /// Thrown if the renaming operation does not affect exactly one row. public async Task RenameRemoteFile(string oldname, string newname) { //Rename the old entry, to preserve ID links @@ -2280,9 +2673,9 @@ namespace Duplicati.Library.Main.Database /// /// Creates a timestamped backup operation to correctly associate the fileset with the time it was created. /// - /// The ID of the fileset volume to update - /// The timestamp of the operation to create - /// An optional external transaction + /// The ID of the fileset volume to update. + /// The timestamp of the operation to create. + /// A task that when awaited contains the ID of the newly created fileset. public virtual async Task CreateFileset(long volumeid, DateTime timestamp) { using var cmd = await m_connection.CreateCommandAsync(@" @@ -2319,7 +2712,8 @@ namespace Duplicati.Library.Main.Database /// /// The ID of the index volume. /// The ID of the block volume. - /// An optional transaction. + /// A task that completes when the link has been added. + /// Thrown if either volume ID is less than or equal to 0. public async Task AddIndexBlockLink(long indexVolumeID, long blockVolumeID) { if (indexVolumeID <= 0) @@ -2336,12 +2730,11 @@ namespace Duplicati.Library.Main.Database } /// - /// Returns all unique blocklists for a given volume + /// Returns all unique blocklists for a given volume. /// - /// The volume ID to get blocklists for - /// The blocksize - /// The size of the hash - /// An optional external transaction + /// The volume ID to get blocklists for. + /// The blocksize. + /// The size of the hash. /// An enumerable of tuples containing the blocklist hash, the blocklist data and the length of the data public async IAsyncEnumerable<(string Hash, byte[] Buffer, int Size)> GetBlocklists(long volumeid, long blocksize, int hashsize) { @@ -2435,11 +2828,11 @@ namespace Duplicati.Library.Main.Database } /// - /// Update fileset with full backup state + /// Update fileset with full backup state. /// - /// Existing file set to update - /// Full backup state - /// An optional external transaction + /// Existing file set to update. + /// Full backup state. + /// A task that completes when the update is finished. public async Task UpdateFullBackupStateInFileset(long fileSetId, bool isFullBackup) { using var cmd = await m_connection.CreateCommandAsync(@" @@ -2457,10 +2850,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Removes all entries in the fileset entry table for a given fileset ID + /// Removes all entries in the fileset entry table for a given fileset ID. /// - /// The fileset ID to clear - /// The transaction to use + /// The fileset ID to clear. + /// A task that completes when the entries have been cleared. public async Task ClearFilesetEntries(long filesetId) { using var cmd = await m_connection.CreateCommandAsync(@" @@ -2476,10 +2869,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Gets the last previous fileset that was incomplete + /// Gets the last previous fileset that was incomplete. /// - /// The transaction to use - /// The last incomplete fileset or default + /// The transaction to use. + /// A task that when awaited returns the last incomplete fileset or default public async Task GetLastIncompleteFilesetVolume() { var candidates = GetIncompleteFilesets() @@ -2497,10 +2890,9 @@ namespace Duplicati.Library.Main.Database } /// - /// Gets a list of incomplete filesets + /// Gets a list of incomplete filesets. /// - /// An optional transaction - /// A list of fileset IDs and timestamps + /// An asynchronous enumerable of key-value pairs where the key is the fileset ID and the value is the timestamp of the fileset. public async IAsyncEnumerable> GetIncompleteFilesets() { using var cmd = await m_connection.CreateCommandAsync(@$" @@ -2538,11 +2930,10 @@ namespace Duplicati.Library.Main.Database } /// - /// Gets the remote volume entry from the fileset ID + /// Gets the remote volume entry from the fileset ID. /// - /// The fileset ID - /// An optional transaction - /// The remote volume entry or default + /// The fileset ID. + /// A task that when awaited returns the remote volume entry associated with the fileset ID, or default if not found. public async Task GetRemoteVolumeFromFilesetID(long filesetID) { using var cmd = await m_connection.CreateCommandAsync(@" @@ -2584,6 +2975,11 @@ namespace Duplicati.Library.Main.Database return default(RemoteVolumeEntry); } + /// + /// Purges log data and remote operations older than the specified threshold. + /// + /// The threshold date and time; all log data and remote operations older than this will be purged. + /// A task that completes when the purge operation is finished. public async Task PurgeLogData(DateTime threshold) { using var cmd = m_connection.CreateCommand(m_rtr); @@ -2608,6 +3004,11 @@ namespace Duplicati.Library.Main.Database await m_rtr.CommitAsync().ConfigureAwait(false); } + /// + /// Purges deleted remote volumes that have not been modified since the specified threshold. + /// + /// The threshold date and time; all deleted remote volumes older than this will be purged. + /// A task that completes when the purge operation is finished. public async Task PurgeDeletedVolumes(DateTime threshold) { await m_removedeletedremotevolumeCommand @@ -2665,11 +3066,11 @@ namespace Duplicati.Library.Main.Database } /// - /// Disposes all fields of a certain type, in the instance and its bases + /// Disposes all fields of a certain type, in the instance and its bases. /// - /// The type of fields to find - /// The item to dispose - /// True if an aggregate exception should be thrown, or false if exceptions are silently captured + /// The type of fields to find. + /// The item to dispose. + /// True if an aggregate exception should be thrown, or false if exceptions are silently captured. public static void DisposeAllFields(object item, bool throwExceptions) where T : IDisposable { @@ -2711,6 +3112,11 @@ namespace Duplicati.Library.Main.Database throw new AggregateException(exceptions); } + /// + /// Writes the results of a basic operation to the log. + /// + /// The results to write. + /// A task that completes when the results have been written. public async Task WriteResults(IBasicResults result) { if (IsDisposed) @@ -2735,20 +3141,19 @@ namespace Duplicati.Library.Main.Database } /// - /// The current index into the path prefix buffer + /// The current index into the path prefix buffer. /// private int m_pathPrefixIndex = 0; /// - /// The path prefix lookup list + /// The path prefix lookup list. /// private readonly KeyValuePair[] m_pathPrefixLookup = new KeyValuePair[5]; /// - /// Gets the path prefix ID, optionally creating it in the process. + /// Gets the path prefix ID, optionally creating it in the process.. /// - /// The path prefix ID. /// The path to get the prefix for. - /// The transaction to use for insertion, or null for no transaction + /// A task that when awaited returns the path prefix ID. public async Task GetOrCreatePathPrefix(string prefix) { // Ring-buffer style lookup @@ -2781,7 +3186,7 @@ namespace Duplicati.Library.Main.Database } /// - /// The path separators on this system + /// The path separators on this system. /// private static readonly char[] _pathseparators = [ Path.DirectorySeparatorChar, @@ -2789,10 +3194,10 @@ namespace Duplicati.Library.Main.Database ]; /// - /// Helper method that splits a path on the last path separator + /// Helper method that splits a path on the last path separator. /// - /// The prefix and name. /// The path to split. + /// The prefix and name. public static KeyValuePair SplitIntoPrefixAndName(string path) { if (string.IsNullOrEmpty(path)) @@ -2807,11 +3212,18 @@ namespace Duplicati.Library.Main.Database } /// - /// Defines the backups types + /// Defines the backups types. /// public static class BackupType { + /// + /// Indicates a partial backup. + /// public const int PARTIAL_BACKUP = 0; + /// + /// Indicates a full backup. + /// public const int FULL_BACKUP = 1; } + }