From 6ba956dfcd73a8edcd7166c8efdbefac590d1dd0 Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Tue, 3 Dec 2024 08:35:14 +0100 Subject: [PATCH] Moved all database operations to the LocalRestoreDatabase. Also changed the old changes, so that the functions related to the legacy restore remains untouched. --- .../Main/Database/LocalRestoreDatabase.cs | 124 +++++++++++++++--- .../Main/Operation/Restore/BlockManager.cs | 28 +--- .../Main/Operation/Restore/Channels.cs | 2 +- .../Main/Operation/Restore/FileProcessor.cs | 96 +++++--------- .../Main/Operation/Restore/Interfaces.cs | 19 +++ .../Main/Operation/Restore/VolumeManager.cs | 9 +- 6 files changed, 163 insertions(+), 115 deletions(-) diff --git a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs index b1608efeb..877c5d23c 100644 --- a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs @@ -20,10 +20,11 @@ // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Text; -using Duplicati.Library.Common; using Duplicati.Library.Common.IO; +using Duplicati.Library.Main.Operation.Restore; using Duplicati.Library.Main.Volumes; using Duplicati.Library.Utility; @@ -36,11 +37,11 @@ namespace Duplicati.Library.Main.Database /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalRestoreDatabase)); - public readonly string m_temptabsetguid = Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); + protected readonly string m_temptabsetguid = Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); /// /// The name of the temporary table in the database, which is used to store the list of files to restore. /// - public string m_tempfiletable { get; private set; } + protected string m_tempfiletable; protected string m_tempblocktable; protected string m_fileprogtable; protected string m_totalprogtable; @@ -544,12 +545,9 @@ namespace Duplicati.Library.Main.Database /// public interface IFileToRestore { - public long ID { get; } - public string Name { get; } - public string Path { get; } - public string Hash { get; } - public long Length { get; } - public long BlocksetID { get; } + string Path { get; } + string Hash { get; } + long Length { get; } } public interface IPatchBlock @@ -943,21 +941,15 @@ namespace Duplicati.Library.Main.Database private class FileToRestore : IFileToRestore { - public long ID { get; private set; } - public string Name { get; private set; } public string Path { get; private set; } public string Hash { get; private set; } public long Length { get; private set; } - public long BlocksetID { get; private set; } - public FileToRestore(long id, string name, string path, string hash, long length, long blocksetid) + public FileToRestore(long id, string path, string hash, long length) { - this.ID = id; - this.Name = name; this.Path = path; this.Hash = hash; this.Length = length; - this.BlocksetID = blocksetid; } } @@ -966,9 +958,9 @@ namespace Duplicati.Library.Main.Database using (var cmd = m_connection.CreateCommand()) { cmd.AddParameter(!onlyNonVerified); - using (var rd = cmd.ExecuteReader(string.Format(@"SELECT ""{0}"".ID, ""{0}"".""Path"", ""{0}"".""TargetPath"", ""Blockset"".""FullHash"", ""Blockset"".""Length"", ""Blockset"".""ID"" FROM ""{0}"",""Blockset"" WHERE ""{0}"".""BlocksetID"" = ""Blockset"".""ID"" AND ""{0}"".""DataVerified"" <= ?", m_tempfiletable))) + using (var rd = cmd.ExecuteReader(string.Format(@"SELECT ""{0}"".""ID"", ""{0}"".""TargetPath"", ""Blockset"".""FullHash"", ""Blockset"".""Length"" FROM ""{0}"",""Blockset"" WHERE ""{0}"".""BlocksetID"" = ""Blockset"".""ID"" AND ""{0}"".""DataVerified"" <= ?", m_tempfiletable))) while (rd.Read()) - yield return new FileToRestore(rd.ConvertValueToInt64(0), rd.ConvertValueToString(1), rd.ConvertValueToString(2), rd.ConvertValueToString(3), rd.ConvertValueToInt64(4), rd.ConvertValueToInt64(5)); + yield return new FileToRestore(rd.ConvertValueToInt64(0), rd.ConvertValueToString(1), rd.ConvertValueToString(2), rd.ConvertValueToInt64(3)); } } @@ -977,7 +969,7 @@ namespace Duplicati.Library.Main.Database /// /// Flag to indicate if only files with non-verified data should be returned. /// A list of files and symlinks to restore. - public IEnumerable GetFilesAndSymlinksToRestore(bool onlyNonVerified) + public IEnumerable GetFilesAndSymlinksToRestore(bool onlyNonVerified) { using var cmd = m_connection.CreateCommand(); cmd.AddParameter(!onlyNonVerified); @@ -985,9 +977,99 @@ namespace Duplicati.Library.Main.Database SELECT F.ID, F.Path, F.TargetPath, IFNULL(B.FullHash, ''), IFNULL(B.Length, 0), F.BlocksetID FROM ""{m_tempfiletable}"" F LEFT JOIN Blockset B ON F.BlocksetID = B.ID - WHERE F.BlocksetID != {LocalDatabase.FOLDER_BLOCKSET_ID}"); + WHERE F.BlocksetID != {FOLDER_BLOCKSET_ID}"); while (rd.Read()) - yield return new FileToRestore(rd.ConvertValueToInt64(0), rd.ConvertValueToString(1), rd.ConvertValueToString(2), rd.ConvertValueToString(3), rd.ConvertValueToInt64(4), rd.ConvertValueToInt64(5)); + yield return new FileRequest(rd.ConvertValueToInt64(0), rd.ConvertValueToString(1), rd.ConvertValueToString(2), rd.ConvertValueToString(3), rd.ConvertValueToInt64(4), rd.ConvertValueToInt64(5)); + } + + /// + /// Returns a list of blocks and their volume IDs. Used by the to keep track of blocks and volumes to automatically evict them from the respective caches. + /// + /// Flag indicating whether the returned blocks should exclude the metadata blocks. + /// A list of tuples containing the block ID and the volume ID of the block. + public IEnumerable<(long, long)> GetBlocksAndVolumeIDs(bool skipMetadata) + { + using var cmd = Connection.CreateCommand(); + using var reader = cmd.ExecuteReader($@" + SELECT Block.ID, Block.VolumeID + FROM BlocksetEntry + INNER JOIN ""{m_tempfiletable}"" ON BlocksetEntry.BlocksetID = ""{m_tempfiletable}"".BlocksetID + INNER JOIN Block ON BlocksetEntry.BlockID = Block.ID + " + + (skipMetadata ? "" : $@" + UNION ALL + SELECT Block.ID, Block.VolumeID + FROM ""{m_tempfiletable}"" + INNER JOIN Metadataset ON ""{m_tempfiletable}"".MetadataID = Metadataset.ID + INNER JOIN BlocksetEntry ON Metadataset.BlocksetID = BlocksetEntry.BlocksetID + INNER JOIN Block ON BlocksetEntry.BlockID = Block.ID + WHERE ""{m_tempfiletable}"".BlocksetID IS NOT {FOLDER_BLOCKSET_ID} + ")); + while (reader.Read()) + yield return (reader.ConvertValueToInt64(0), reader.ConvertValueToInt64(1)); + } + + /// + /// Returns a list of for the given blockset ID. It is used by the to restore the blocks of a file. + /// + /// The BlocksetID of the file. + /// A list of needed to restore the given file. + public IEnumerable GetBlocksFromFile(long blocksetID) + { + using var cmd = m_connection.CreateCommand(); + cmd.CommandText = @$" + SELECT Block.ID, Block.Hash, Block.Size, Block.VolumeID + FROM BlocksetEntry INNER JOIN Block + ON BlocksetEntry.BlockID = Block.ID + WHERE BlocksetEntry.BlocksetID = ?"; + cmd.AddParameter(); + cmd.SetParameterValue(0, blocksetID); + using var reader = cmd.ExecuteReader(); + for (long i = 0; reader.Read(); i++) + { + yield return new BlockRequest(reader.GetInt64(0), i, reader.GetString(1), reader.GetInt64(2), reader.GetInt64(3), false); + } + } + + /// + /// Returns a list of for the metadata blocks of the given file. It is used by the to restore the metadata of a file. + /// + /// The ID of the file. + /// A list of needed to restore the metadata of the given file. + public IEnumerable GetMetadataBlocksFromFile(long fileID) + { + using var cmd = m_connection.CreateCommand(); + cmd.CommandText = $@" + SELECT Block.ID, Block.Hash, Block.Size, Block.VolumeID + FROM ""{m_tempfiletable}"" + INNER JOIN Metadataset ON ""{m_tempfiletable}"".MetadataID = Metadataset.ID + INNER JOIN BlocksetEntry ON Metadataset.BlocksetID = BlocksetEntry.BlocksetID + INNER JOIN Block ON BlocksetEntry.BlockID = Block.ID + WHERE ""{m_tempfiletable}"".ID = ? + "; + cmd.AddParameter(); + cmd.SetParameterValue(0, fileID); + using var reader = cmd.ExecuteReader(); + for (long i = 0; reader.Read(); i++) + { + yield return new BlockRequest(reader.GetInt64(0), i, reader.GetString(1), reader.GetInt64(2), reader.GetInt64(3), false); + } + } + + /// + /// Returns the volume information for the given volume ID. It is used by the to get the volume information for the given volume ID. + /// + /// The ID of the volume. + /// A tuple containing the name, size, and hash of the volume. + public IEnumerable<(string,long,string)> GetVolumeInfo(long VolumeID) + { + using var cmd = m_connection.CreateCommand(); + cmd.CommandText = "SELECT Name, Size, Hash FROM RemoteVolume WHERE ID = ?"; + cmd.AddParameter(); + cmd.SetParameterValue(0, VolumeID); + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + yield return (reader.GetString(0), reader.GetInt64(1), reader.GetString(2)); } public void DropRestoreTable() diff --git a/Duplicati/Library/Main/Operation/Restore/BlockManager.cs b/Duplicati/Library/Main/Operation/Restore/BlockManager.cs index 94364b445..9a1a08daf 100644 --- a/Duplicati/Library/Main/Operation/Restore/BlockManager.cs +++ b/Duplicati/Library/Main/Operation/Restore/BlockManager.cs @@ -114,30 +114,12 @@ namespace Duplicati.Library.Main.Operation.Restore sw_checkcounts = options.InternalProfiling ? new () : null; sw_get_wait = options.InternalProfiling ? new () : null; - var cmd = db.Connection.CreateCommand(); - var blockcounts = cmd.ExecuteReaderEnumerable($@" - SELECT Block.ID, Block.VolumeID - FROM BlocksetEntry - INNER JOIN ""{db.m_tempfiletable}"" ON BlocksetEntry.BlocksetID = ""{db.m_tempfiletable}"".BlocksetID - INNER JOIN Block ON BlocksetEntry.BlockID = Block.ID - " - + (options.SkipMetadata ? "" : $@" - UNION ALL - SELECT Block.ID, Block.VolumeID - FROM ""{db.m_tempfiletable}"" - INNER JOIN Metadataset ON ""{db.m_tempfiletable}"".MetadataID = Metadataset.ID - INNER JOIN BlocksetEntry ON Metadataset.BlocksetID = BlocksetEntry.BlocksetID - INNER JOIN Block ON BlocksetEntry.BlockID = Block.ID - WHERE ""{db.m_tempfiletable}"".BlocksetID IS NOT {LocalDatabase.FOLDER_BLOCKSET_ID} - ")); - foreach (var row in blockcounts) + foreach (var (block_id, volume_id) in db.GetBlocksAndVolumeIDs(options.SkipMetadata)) { - var bid = row.GetInt64(0); - var bc = m_blockcount.TryGetValue(bid, out var c); - m_blockcount[bid] = bc ? c + 1 : 1; - var vid = row.GetInt64(1); - var vc = m_volumecount.TryGetValue(vid, out var v); - m_volumecount[vid] = vc ? v + 1 : 1; + var bc = m_blockcount.TryGetValue(block_id, out var c); + m_blockcount[block_id] = bc ? c + 1 : 1; + var vc = m_volumecount.TryGetValue(volume_id, out var v); + m_volumecount[volume_id] = vc ? v + 1 : 1; } } diff --git a/Duplicati/Library/Main/Operation/Restore/Channels.cs b/Duplicati/Library/Main/Operation/Restore/Channels.cs index 517cb5277..d46ad3e50 100644 --- a/Duplicati/Library/Main/Operation/Restore/Channels.cs +++ b/Duplicati/Library/Main/Operation/Restore/Channels.cs @@ -41,7 +41,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// /// Channel between and . /// - public static readonly ChannelMarkerWrapper FilesToRestore = new(new ChannelNameAttribute("FilesToRestore", BufferSize)); + public static readonly ChannelMarkerWrapper FilesToRestore = new(new ChannelNameAttribute("FilesToRestore", BufferSize)); /// /// Channel between and . diff --git a/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs index e440c4064..c95384944 100644 --- a/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs +++ b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs @@ -81,24 +81,6 @@ namespace Duplicati.Library.Main.Operation.Restore { using var filehasher = HashFactory.CreateHasher(options.FileHashAlgorithm); using var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm); - using var findBlocksCmd = db.Connection.CreateCommand(); - findBlocksCmd.CommandText = @$" - SELECT Block.ID, Block.Hash, Block.Size, Block.VolumeID - FROM BlocksetEntry INNER JOIN Block - ON BlocksetEntry.BlockID = Block.ID - WHERE BlocksetEntry.BlocksetID = ?"; - findBlocksCmd.AddParameter(); - - var findMetaBlocksCmd = db.Connection.CreateCommand(); - findMetaBlocksCmd.CommandText = $@" - SELECT Block.ID, Block.Hash, Block.Size, Block.VolumeID - FROM ""{db.m_tempfiletable}"" - INNER JOIN Metadataset ON ""{db.m_tempfiletable}"".MetadataID = Metadataset.ID - INNER JOIN BlocksetEntry ON Metadataset.BlocksetID = BlocksetEntry.BlocksetID - INNER JOIN Block ON BlocksetEntry.BlockID = Block.ID - WHERE ""{db.m_tempfiletable}"".ID = ? - "; - findMetaBlocksCmd.AddParameter(); while (true) { @@ -109,12 +91,7 @@ namespace Duplicati.Library.Main.Operation.Restore // Get information about the blocks for the file sw_block?.Start(); - findBlocksCmd.SetParameterValue(0, file.BlocksetID); - var blocks = findBlocksCmd.ExecuteReaderEnumerable() - .Select((b, i) => - new BlockRequest(b.GetInt64(0), i, b.GetString(1), b.GetInt64(2), b.GetInt64(3), false) - ) - .ToArray(); + var blocks = db.GetBlocksFromFile(file.BlocksetID).ToArray(); sw_block?.Stop(); sw_work?.Start(); @@ -135,18 +112,18 @@ namespace Duplicati.Library.Main.Operation.Restore { if (options.Dryrun) { - Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have created empty file ""{file.Path}"""); + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have created empty file ""{file.TargetPath}"""); } else { - var foldername = SystemIO.IO_OS.PathGetDirectoryName(file.Path); + var foldername = SystemIO.IO_OS.PathGetDirectoryName(file.TargetPath); if (!Directory.Exists(foldername)) { Directory.CreateDirectory(foldername); } // Create an empty file, or truncate to 0 - using var fs = new FileStream(file.Path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); + using var fs = new FileStream(file.TargetPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); fs.SetLength(0); if (missing_blocks.Count != 0) { @@ -159,7 +136,7 @@ namespace Duplicati.Library.Main.Operation.Restore { if (missing_blocks.Any(x => x.VolumeID < 0)) { - Logging.Log.WriteWarningMessage(LOGTAG, "NegativeVolumeID", null, $"{file.Path} has a negative volume ID, skipping"); + Logging.Log.WriteWarningMessage(LOGTAG, "NegativeVolumeID", null, $"{file.TargetPath} has a negative volume ID, skipping"); continue; } @@ -172,14 +149,14 @@ namespace Duplicati.Library.Main.Operation.Restore if (options.Dryrun) { // If dryrun, open the file for read only to verify the file hash. - if (File.Exists(file.Path)) + if (File.Exists(file.TargetPath)) { - fs = new FileStream(file.Path, FileMode.Open, FileAccess.Read); + fs = new FileStream(file.TargetPath, FileMode.Open, FileAccess.Read); } } else { - fs = new FileStream(file.Path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + fs = new FileStream(file.TargetPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); } sw_work?.Stop(); @@ -239,14 +216,14 @@ namespace Duplicati.Library.Main.Operation.Restore if (options.Dryrun) { - Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have restored {bytes_written} bytes of ""{file.Path}"""); + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have restored {bytes_written} bytes of ""{file.TargetPath}"""); } // Verify the file hash filehasher.TransformFinalBlock([], 0, 0); if (Convert.ToBase64String(filehasher.Hash) != file.Hash) { - Logging.Log.WriteErrorMessage(LOGTAG, "FileHashMismatch", null, $"File hash mismatch for {file.Path} - expected: {file.Hash}, actual: {Convert.ToBase64String(filehasher.Hash)}"); + Logging.Log.WriteErrorMessage(LOGTAG, "FileHashMismatch", null, $"File hash mismatch for {file.TargetPath} - expected: {file.Hash}, actual: {Convert.ToBase64String(filehasher.Hash)}"); throw new Exception("File hash mismatch"); } @@ -255,7 +232,7 @@ namespace Duplicati.Library.Main.Operation.Restore { if (options.Dryrun) { - Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.Path}"" from {fs.Length} to {file.Length}"); + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.TargetPath}"" from {fs.Length} to {file.Length}"); } else { @@ -267,7 +244,7 @@ namespace Duplicati.Library.Main.Operation.Restore { lock (results) { - results.BrokenLocalFiles.Add(file.Path); + results.BrokenLocalFiles.Add(file.TargetPath); } throw; } @@ -278,7 +255,7 @@ namespace Duplicati.Library.Main.Operation.Restore } if (!options.SkipMetadata) { - await RestoreMetadata(findMetaBlocksCmd, file, block_request, block_response, options, sw_meta, sw_work, sw_req, sw_resp); + await RestoreMetadata(db, file, block_request, block_response, options, sw_meta, sw_work, sw_req, sw_resp); } // Keep track of the restored files and their sizes @@ -324,16 +301,11 @@ namespace Duplicati.Library.Main.Operation.Restore /// The stopwatch for internal profiling of the general processing. /// The stopwatch for internal profiling of the block requests. /// The stopwatch for internal profiling of the block responses. - private static async Task RestoreMetadata(IDbCommand cmd, LocalRestoreDatabase.IFileToRestore file, IChannel block_request, IChannel block_response, Options options, Stopwatch sw_meta, Stopwatch sw_work, Stopwatch sw_req, Stopwatch sw_resp) + private static async Task RestoreMetadata(LocalRestoreDatabase db, FileRequest file, IChannel block_request, IChannel block_response, Options options, Stopwatch sw_meta, Stopwatch sw_work, Stopwatch sw_req, Stopwatch sw_resp) { sw_work?.Stop(); sw_meta?.Start(); - cmd.SetParameterValue(0, file.ID); - var blocks = cmd.ExecuteReaderEnumerable() - .Select((b, i) => - new BlockRequest(b.GetInt64(0), i, b.GetString(1), b.GetInt64(2), b.GetInt64(3), false) - ) - .ToArray(); + var blocks = db.GetMetadataBlocksFromFile(file.ID).ToArray(); sw_meta?.Stop(); sw_work?.Start(); @@ -353,7 +325,7 @@ namespace Duplicati.Library.Main.Operation.Restore } ms.Seek(0, SeekOrigin.Begin); - RestoreHandler.ApplyMetadata(file.Path, ms, options.RestorePermissions, options.RestoreSymlinkMetadata, options.Dryrun); + RestoreHandler.ApplyMetadata(file.TargetPath, ms, options.RestorePermissions, options.RestoreSymlinkMetadata, options.Dryrun); } /// @@ -367,17 +339,17 @@ namespace Duplicati.Library.Main.Operation.Restore /// The restoration results. /// The channel to request blocks from the block manager. Used to inform the block manager which blocks are already present. /// An awaitable `Task`, which returns a collection of data blocks that are missing. - private static async Task> VerifyTargetBlocks(LocalRestoreDatabase.IFileToRestore file, BlockRequest[] blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, Options options, RestoreResults results, IChannel block_request) + private static async Task> VerifyTargetBlocks(FileRequest file, BlockRequest[] blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, Options options, RestoreResults results, IChannel block_request) { List missing_blocks = []; // Check if the file exists - if (File.Exists(file.Path)) + if (File.Exists(file.TargetPath)) { filehasher.Initialize(); try { - using var f = new FileStream(file.Path, FileMode.Open, FileAccess.Read); + using var f = new FileStream(file.TargetPath, FileMode.Open, FileAccess.Read); var buffer = new byte[options.Blocksize]; long bytes_read = 0; for (int i = 0; i < blocks.Length; i++) @@ -418,7 +390,7 @@ namespace Duplicati.Library.Main.Operation.Restore { lock (results) { - results.BrokenLocalFiles.Add(file.Path); + results.BrokenLocalFiles.Add(file.TargetPath); } } @@ -430,18 +402,18 @@ namespace Duplicati.Library.Main.Operation.Restore if (Convert.ToBase64String(filehasher.Hash) == file.Hash) { // Truncate the file if it is larger than the expected size. - FileInfo fi = new (file.Path); + FileInfo fi = new (file.TargetPath); if (file.Length < fi.Length) { if (options.Dryrun) { - Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.Path}"" from {fi.Length} to {file.Length}"); + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.TargetPath}"" from {fi.Length} to {file.Length}"); } else { // Reopen file with write permission fi.IsReadOnly = false; // The metadata handler will revert this back later. - using var f = new FileStream(file.Path, FileMode.Open, FileAccess.ReadWrite); + using var f = new FileStream(file.TargetPath, FileMode.Open, FileAccess.ReadWrite); f.SetLength(file.Length); } } @@ -469,18 +441,18 @@ namespace Duplicati.Library.Main.Operation.Restore /// The restoration results. /// The channel to request blocks from the block manager. Used to inform the block manager which blocks are already present. /// An awaitable `Task`, which returns a collection of data blocks that are missing. - private static async Task<(long, List)> VerifyLocalBlocks(LocalRestoreDatabase.IFileToRestore file, List blocks, long total_blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, Options options, RestoreResults results, IChannel block_request) + private static async Task<(long, List)> VerifyLocalBlocks(FileRequest file, List blocks, long total_blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, Options options, RestoreResults results, IChannel block_request) { List missing_blocks = []; // Check if the file exists - if (File.Exists(file.Name)) + if (File.Exists(file.OriginalPath)) { filehasher.Initialize(); // Open both files, as the target file is still being read to produce the overall file hash, if all the blocks are present across both the target and original files. - using var f_original = new FileStream(file.Name, FileMode.Open, FileAccess.Read); - using var f_target = new FileStream(file.Path, FileMode.OpenOrCreate, FileAccess.ReadWrite); + using var f_original = new FileStream(file.OriginalPath, FileMode.Open, FileAccess.Read); + using var f_target = new FileStream(file.TargetPath, FileMode.OpenOrCreate, FileAccess.ReadWrite); var buffer = new byte[options.Blocksize]; long bytes_read = 0; long bytes_written = 0; @@ -501,7 +473,7 @@ namespace Duplicati.Library.Main.Operation.Restore { lock (results) { - results.BrokenLocalFiles.Add(file.Path); + results.BrokenLocalFiles.Add(file.TargetPath); } throw; } @@ -524,7 +496,7 @@ namespace Duplicati.Library.Main.Operation.Restore { lock (results) { - results.BrokenLocalFiles.Add(file.Path); + results.BrokenLocalFiles.Add(file.TargetPath); } throw; } @@ -558,7 +530,7 @@ namespace Duplicati.Library.Main.Operation.Restore { lock (results) { - results.BrokenLocalFiles.Add(file.Path); + results.BrokenLocalFiles.Add(file.TargetPath); } throw; } @@ -577,7 +549,7 @@ namespace Duplicati.Library.Main.Operation.Restore { if (options.Dryrun) { - Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.Path}"" from {f_target.Length} to {file.Length}"); + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.TargetPath}"" from {f_target.Length} to {file.Length}"); } else { @@ -589,7 +561,7 @@ namespace Duplicati.Library.Main.Operation.Restore { lock (results) { - results.BrokenLocalFiles.Add(file.Path); + results.BrokenLocalFiles.Add(file.TargetPath); } throw; } @@ -598,10 +570,10 @@ namespace Duplicati.Library.Main.Operation.Restore } else { - Logging.Log.WriteErrorMessage(LOGTAG, "FileHashMismatch", null, $"File hash mismatch for {file.Path} - expected: {file.Hash}, actual: {Convert.ToBase64String(filehasher.Hash)}"); + Logging.Log.WriteErrorMessage(LOGTAG, "FileHashMismatch", null, $"File hash mismatch for {file.TargetPath} - expected: {file.Hash}, actual: {Convert.ToBase64String(filehasher.Hash)}"); lock (results) { - results.BrokenLocalFiles.Add(file.Path); + results.BrokenLocalFiles.Add(file.TargetPath); } } } diff --git a/Duplicati/Library/Main/Operation/Restore/Interfaces.cs b/Duplicati/Library/Main/Operation/Restore/Interfaces.cs index 040db0227..ddedfd363 100644 --- a/Duplicati/Library/Main/Operation/Restore/Interfaces.cs +++ b/Duplicati/Library/Main/Operation/Restore/Interfaces.cs @@ -41,4 +41,23 @@ namespace Duplicati.Library.Main.Operation.Restore public bool CacheDecrEvict { get; set; } = cacheDecrEvict; } + /// + /// Represents a file request that the `FileLister` process sends to the `FileProcessor` process. + /// + /// The File ID. + /// The original path of the file. + /// The target path of the file. + /// The file hash. + /// The length of the file. + /// The BlocksetID of the file. + public class FileRequest(long ID, string OriginalPath, string TargetPath, string Hash, long Length, long BlocksetID) + { + public long ID { get; } = ID; + public string OriginalPath { get; } = OriginalPath; + public string TargetPath { get; } = TargetPath; + public string Hash { get; } = Hash; + public long Length { get; } = Length; + public long BlocksetID { get; } = BlocksetID; + } + } \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs index a1c4b2189..ce0887538 100644 --- a/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs +++ b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs @@ -73,11 +73,6 @@ namespace Duplicati.Library.Main.Operation.Restore Stopwatch sw_request = options.InternalProfiling ? new () : null; Stopwatch sw_wakeup = options.InternalProfiling ? new () : null; - // Prepare the command to get the volume information - using var cmd = db.Connection.CreateCommand(); - cmd.CommandText = "SELECT Name, Size, Hash FROM RemoteVolume WHERE ID = ?"; - cmd.AddParameter(); - try { while (true) @@ -113,9 +108,7 @@ namespace Duplicati.Library.Main.Operation.Restore else { sw_query?.Start(); - cmd.SetParameterValue(0, request.VolumeID); - var (volume_name, volume_size, volume_hash) = cmd.ExecuteReaderEnumerable() - .Select(x => (x.GetString(0), x.GetInt64(1), x.GetString(2))).First(); + var (volume_name, volume_size, volume_hash) = db.GetVolumeInfo(request.VolumeID).First(); sw_query?.Stop(); sw_backend?.Start(); var handle = backend.GetAsync(volume_name, volume_size, volume_hash);