Moved all database operations to the LocalRestoreDatabase.
Also changed the old changes, so that the functions related to the legacy restore remains untouched.
This commit is contained in:
@@ -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
|
||||
/// </summary>
|
||||
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());
|
||||
/// <summary>
|
||||
/// The name of the temporary table in the database, which is used to store the list of files to restore.
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="onlyNonVerified">Flag to indicate if only files with non-verified data should be returned.</param>
|
||||
/// <returns>A list of files and symlinks to restore.</returns>
|
||||
public IEnumerable<IFileToRestore> GetFilesAndSymlinksToRestore(bool onlyNonVerified)
|
||||
public IEnumerable<FileRequest> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of blocks and their volume IDs. Used by the <see cref="BlockManager"/> to keep track of blocks and volumes to automatically evict them from the respective caches.
|
||||
/// </summary>
|
||||
/// <param name="skipMetadata">Flag indicating whether the returned blocks should exclude the metadata blocks.</param>
|
||||
/// <returns>A list of tuples containing the block ID and the volume ID of the block.</returns>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of <see cref="BlockRequest"/> for the given blockset ID. It is used by the <see cref="FileProcessor"/> to restore the blocks of a file.
|
||||
/// </summary>
|
||||
/// <param name="blocksetID">The BlocksetID of the file.</param>
|
||||
/// <returns>A list of <see cref="BlockRequest"/> needed to restore the given file.</returns>
|
||||
public IEnumerable<BlockRequest> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of <see cref="BlockRequest"/> for the metadata blocks of the given file. It is used by the <see cref="FileProcessor"/> to restore the metadata of a file.
|
||||
/// </summary>
|
||||
/// <param name="fileID">The ID of the file.</param>
|
||||
/// <returns>A list of <see cref="BlockRequest"/> needed to restore the metadata of the given file.</returns>
|
||||
public IEnumerable<BlockRequest> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the volume information for the given volume ID. It is used by the <see cref="VolumeManager"/> to get the volume information for the given volume ID.
|
||||
/// </summary>
|
||||
/// <param name="VolumeID">The ID of the volume.</param>
|
||||
/// <returns>A tuple containing the name, size, and hash of the volume.</returns>
|
||||
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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Duplicati.Library.Main.Operation.Restore
|
||||
/// <summary>
|
||||
/// Channel between <see cref="FileLister"/> and <see cref="FileProcessor"/>.
|
||||
/// </summary>
|
||||
public static readonly ChannelMarkerWrapper<Database.LocalRestoreDatabase.IFileToRestore> FilesToRestore = new(new ChannelNameAttribute("FilesToRestore", BufferSize));
|
||||
public static readonly ChannelMarkerWrapper<FileRequest> FilesToRestore = new(new ChannelNameAttribute("FilesToRestore", BufferSize));
|
||||
|
||||
/// <summary>
|
||||
/// Channel between <see cref="BlockManager"/> and <see cref="VolumeManager"/>.
|
||||
|
||||
@@ -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
|
||||
/// <param name="sw_work">The stopwatch for internal profiling of the general processing.</param>
|
||||
/// <param name="sw_req">The stopwatch for internal profiling of the block requests.</param>
|
||||
/// <param name="sw_resp">The stopwatch for internal profiling of the block responses.</param>
|
||||
private static async Task RestoreMetadata(IDbCommand cmd, LocalRestoreDatabase.IFileToRestore file, IChannel<BlockRequest> block_request, IChannel<byte[]> 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<BlockRequest> block_request, IChannel<byte[]> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -367,17 +339,17 @@ namespace Duplicati.Library.Main.Operation.Restore
|
||||
/// <param name="results">The restoration results.</param>
|
||||
/// <param name="block_request">The channel to request blocks from the block manager. Used to inform the block manager which blocks are already present.</param>
|
||||
/// <returns>An awaitable `Task`, which returns a collection of data blocks that are missing.</returns>
|
||||
private static async Task<List<BlockRequest>> VerifyTargetBlocks(LocalRestoreDatabase.IFileToRestore file, BlockRequest[] blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, Options options, RestoreResults results, IChannel<BlockRequest> block_request)
|
||||
private static async Task<List<BlockRequest>> VerifyTargetBlocks(FileRequest file, BlockRequest[] blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, Options options, RestoreResults results, IChannel<BlockRequest> block_request)
|
||||
{
|
||||
List<BlockRequest> 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
|
||||
/// <param name="results">The restoration results.</param>
|
||||
/// <param name="block_request">The channel to request blocks from the block manager. Used to inform the block manager which blocks are already present.</param>
|
||||
/// <returns>An awaitable `Task`, which returns a collection of data blocks that are missing.</returns>
|
||||
private static async Task<(long, List<BlockRequest>)> VerifyLocalBlocks(LocalRestoreDatabase.IFileToRestore file, List<BlockRequest> blocks, long total_blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, Options options, RestoreResults results, IChannel<BlockRequest> block_request)
|
||||
private static async Task<(long, List<BlockRequest>)> VerifyLocalBlocks(FileRequest file, List<BlockRequest> blocks, long total_blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, Options options, RestoreResults results, IChannel<BlockRequest> block_request)
|
||||
{
|
||||
List<BlockRequest> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,4 +41,23 @@ namespace Duplicati.Library.Main.Operation.Restore
|
||||
public bool CacheDecrEvict { get; set; } = cacheDecrEvict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a file request that the `FileLister` process sends to the `FileProcessor` process.
|
||||
/// </summary>
|
||||
/// <param name="ID">The File ID.</param>
|
||||
/// <param name="OriginalPath">The original path of the file.</param>
|
||||
/// <param name="TargetPath">The target path of the file.</param>
|
||||
/// <param name="Hash">The file hash.</param>
|
||||
/// <param name="Length">The length of the file.</param>
|
||||
/// <param name="BlocksetID">The BlocksetID of the file.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user