2025-01-28 08:54:50 +01:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
2025-05-21 15:34:10 +02:00
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
2025-01-28 08:54:50 +01:00
// Software is furnished to do so, subject to the following conditions:
2025-05-21 15:34:10 +02:00
//
// The above copyright notice and this permission notice shall be included in
2025-01-28 08:54:50 +01:00
// all copies or substantial portions of the Software.
2025-05-21 15:34:10 +02:00
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
2024-02-28 15:45:30 +01:00
// DEALINGS IN THE SOFTWARE.
2013-06-26 21:59:01 +02:00
using System ;
using Duplicati.Library.Main.Database ;
using System.Linq ;
using System.Collections.Generic ;
2014-08-19 20:20:45 +02:00
using Duplicati.Library.Interface ;
2021-04-04 11:17:13 -07:00
using Duplicati.Library.Utility ;
2025-01-28 08:54:50 +01:00
using System.Threading.Tasks ;
2025-05-27 17:15:02 +02:00
using Duplicati.Library.Main.Volumes ;
using System.Threading ;
2025-06-12 10:38:07 +02:00
using Duplicati.Library.Localization.Short ;
2013-06-26 21:59:01 +02:00
namespace Duplicati.Library.Main.Operation
{
internal class TestHandler
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType < TestHandler >();
2013-06-26 21:59:01 +02:00
private readonly Options m_options ;
2025-06-19 09:47:03 +02:00
private readonly TestResults m_result ;
2024-05-21 15:48:29 +02:00
2025-01-28 08:54:50 +01:00
public TestHandler ( Options options , TestResults results )
2013-06-26 21:59:01 +02:00
{
m_options = options ;
2025-06-19 09:47:03 +02:00
m_result = results ;
2013-06-26 21:59:01 +02:00
}
2024-05-21 15:48:29 +02:00
2025-03-14 10:41:14 +01:00
public async Task RunAsync ( long samples , IBackendManager backendManager )
2013-06-26 21:59:01 +02:00
{
if (! System . IO . File . Exists ( m_options . Dbpath ))
2025-06-12 10:38:07 +02:00
throw new UserInformationException ( LC . L ( "Database file does not exist: {0}" , m_options . Dbpath ), "DatabaseDoesNotExist" );
2024-05-21 15:48:29 +02:00
2025-06-19 11:41:39 +02:00
await using var db = await LocalTestDatabase . CreateAsync ( m_options . Dbpath , null , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2025-06-19 09:47:03 +02:00
await Utility . UpdateOptionsFromDb ( db , m_options , m_result . TaskControl . ProgressToken )
2025-06-18 11:18:25 +02:00
. ConfigureAwait ( false );
2025-06-19 09:47:03 +02:00
await Utility . VerifyOptionsAndUpdateDatabase ( db , m_options , m_result . TaskControl . ProgressToken )
2025-06-18 11:18:25 +02:00
. ConfigureAwait ( false );
2025-06-12 08:34:29 +02:00
2025-06-18 11:18:25 +02:00
await db
2025-06-19 09:47:03 +02:00
. VerifyConsistency ( m_options . Blocksize , m_options . BlockhashSize , ! m_options . DisableFilelistConsistencyChecks , m_result . TaskControl . ProgressToken )
2025-06-18 11:18:25 +02:00
. ConfigureAwait ( false );
2025-06-12 08:34:29 +02:00
2025-06-19 09:47:03 +02:00
await FilelistProcessor . VerifyRemoteList ( backendManager , m_options , db , m_result . BackendWriter , latestVolumesOnly : true , verifyMode : FilelistProcessor . VerifyMode . VerifyOnly , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2025-06-18 11:18:25 +02:00
await DoRunAsync ( samples , db , backendManager ). ConfigureAwait ( false );
await db . Transaction
. CommitAsync ( "TestHandlerComplete" )
. ConfigureAwait ( false );
2013-08-24 20:59:09 +02:00
}
2024-05-21 15:48:29 +02:00
2025-05-21 15:34:40 +02:00
public async Task DoRunAsync ( long samples , LocalTestDatabase db , IBackendManager backend )
2013-08-24 20:59:09 +02:00
{
2025-06-12 08:34:29 +02:00
var files = await db
2025-06-19 09:47:03 +02:00
. SelectTestTargets ( samples , m_options , m_result . TaskControl . ProgressToken )
. ToListAsync ( cancellationToken : m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2014-08-26 15:01:56 +02:00
2025-06-19 09:47:03 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Verify_Running );
m_result . OperationProgressUpdater . UpdateProgress ( 0 );
2014-08-26 15:01:56 +02:00
var progress = 0L ;
2024-05-21 15:48:29 +02:00
2023-12-10 00:24:07 +01:00
if ( m_options . FullRemoteVerification != Options . RemoteTestStrategy . False )
2013-08-24 20:59:09 +02:00
{
2025-05-27 17:15:02 +02:00
var faultyIndexFiles = new List < IRemoteVolume >();
2025-06-19 09:47:03 +02:00
await foreach ( var ( tf , hash , size , name ) in backend . GetFilesOverlappedAsync ( files , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
2013-06-26 21:59:01 +02:00
{
2025-01-28 08:54:50 +01:00
var vol = new RemoteVolume ( name , hash , size );
2014-03-10 14:16:27 +01:00
try
2013-06-26 21:59:01 +02:00
{
2025-06-19 09:47:03 +02:00
if (! await m_result . TaskControl . ProgressRendevouz (). ConfigureAwait ( false ))
2014-05-15 12:47:16 +02:00
{
2025-06-19 09:47:03 +02:00
await backend . WaitForEmptyAsync ( db , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
m_result . EndTime = DateTime . UtcNow ;
2014-05-15 12:47:16 +02:00
return ;
2024-05-21 15:48:29 +02:00
}
2014-08-26 12:51:09 +02:00
2014-08-26 15:01:56 +02:00
progress ++;
2025-06-19 09:47:03 +02:00
m_result . OperationProgressUpdater . UpdateProgress (( float ) progress / files . Count );
2014-08-26 15:01:56 +02:00
2014-08-26 12:51:09 +02:00
KeyValuePair < string , IEnumerable < KeyValuePair < TestEntryStatus , string >>> res ;
2025-01-28 08:54:50 +01:00
using ( tf )
2025-06-19 09:47:03 +02:00
res = await TestVolumeInternals ( db , vol , tf , m_options , m_options . FullBlockVerification ? 1.0 : 0.2 , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-05-27 17:15:02 +02:00
var parsedInfo = VolumeBase . ParseFilename ( vol . Name );
if ( parsedInfo . FileType == RemoteVolumeType . Index )
{
if ( res . Value . Any ( x => x . Key == TestEntryStatus . Extra ))
{
// Bad hack, but for now, the index files sometimes have extra blocklist hashes
2025-06-12 10:38:07 +02:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "IndexFileExtraBlocks" , null , LC . L ( "Index file {0} has extra blocks" , vol . Name ));
2025-05-27 17:15:02 +02:00
res = new KeyValuePair < string , IEnumerable < KeyValuePair < TestEntryStatus , string >>>(
res . Key , res . Value . Where ( x => x . Key != TestEntryStatus . Extra ). ToList ()
);
}
if ( res . Value . Any ( x => x . Key == TestEntryStatus . Missing || x . Key == TestEntryStatus . Modified ))
faultyIndexFiles . Add ( vol );
}
2025-06-19 09:47:03 +02:00
m_result . AddResult ( res . Key , res . Value );
2017-02-27 00:43:06 +01:00
if (! string . IsNullOrWhiteSpace ( vol . Hash ) && vol . Size > 0 )
{
if ( res . Value == null || ! res . Value . Any ())
{
2025-06-12 08:34:29 +02:00
var rv = await db
2025-06-19 09:47:03 +02:00
. GetRemoteVolume ( vol . Name , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2017-02-27 00:43:06 +01:00
2017-05-30 00:35:31 +02:00
if ( rv . ID < 0 )
2017-02-27 00:43:06 +01:00
{
2017-05-30 00:35:31 +02:00
if ( string . IsNullOrWhiteSpace ( rv . Hash ) || rv . Size <= 0 )
2017-02-27 00:43:06 +01:00
{
if ( m_options . Dryrun )
{
2025-06-12 10:38:07 +02:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "CaptureHashAndSize" , LC . L ( "Successfully captured hash and size for {0}, would update database" , vol . Name ));
2017-02-27 00:43:06 +01:00
}
else
{
2025-06-12 10:38:07 +02:00
Logging . Log . WriteInformationMessage ( LOGTAG , "CaptureHashAndSize" , LC . L ( "Successfully captured hash and size for {0}, updating database" , vol . Name ));
2025-06-12 08:34:29 +02:00
await db
2025-06-19 09:47:03 +02:00
. UpdateRemoteVolume ( vol . Name , RemoteVolumeState . Verified , vol . Size , vol . Hash , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2017-02-27 00:43:06 +01:00
}
}
}
}
}
2024-05-21 15:48:29 +02:00
2025-06-12 08:34:29 +02:00
await db
2025-06-19 09:47:03 +02:00
. UpdateVerificationCount ( vol . Name , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2014-03-10 14:16:27 +01:00
}
catch ( Exception ex )
{
2025-06-19 09:47:03 +02:00
m_result . AddResult ( vol . Name , [ new KeyValuePair < TestEntryStatus , string >( TestEntryStatus . Error , ex . Message )]);
2025-06-12 10:38:07 +02:00
Logging . Log . WriteErrorMessage ( LOGTAG , "RemoteFileProcessingFailed" , ex , LC . L ( "Failed to process file {0}" , vol . Name ));
2025-03-20 22:32:04 +01:00
if ( ex . IsAbortException ())
2016-12-01 23:59:54 +01:00
{
2025-06-19 09:47:03 +02:00
m_result . EndTime = DateTime . UtcNow ;
2014-05-15 12:47:16 +02:00
throw ;
2016-12-01 23:59:54 +01:00
}
2013-06-26 21:59:01 +02:00
}
2013-08-24 20:59:09 +02:00
}
2025-05-27 17:15:02 +02:00
if ( faultyIndexFiles . Any ())
{
if ( m_options . ReplaceFaultyIndexFiles )
{
2025-06-12 10:38:07 +02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FaultyIndexFiles" , null , LC . L ( "Found {0} faulty index files, repairing now" , faultyIndexFiles . Count ));
2025-06-19 09:47:03 +02:00
await ReplaceFaultyIndexFilesAsync ( faultyIndexFiles , backend , db , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2025-05-27 17:15:02 +02:00
}
else
2025-06-12 10:38:07 +02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FaultyIndexFiles" , null , LC . L ( "Found {0} faulty index files, remove the option {1} to repair them" , faultyIndexFiles . Count , "--dont-replace-faulty-index-files" ));
2025-05-27 17:15:02 +02:00
}
2014-03-10 14:16:27 +01:00
}
else
{
2024-05-21 15:48:29 +02:00
foreach ( var f in files )
2013-08-24 20:59:09 +02:00
{
2014-03-10 14:16:27 +01:00
try
2016-12-01 23:59:54 +01:00
{
2025-06-19 09:47:03 +02:00
if (! await m_result . TaskControl . ProgressRendevouz (). ConfigureAwait ( false ))
2016-12-01 23:59:54 +01:00
{
2025-06-19 09:47:03 +02:00
m_result . EndTime = DateTime . UtcNow ;
2014-05-15 12:47:16 +02:00
return ;
2016-12-01 23:59:54 +01:00
}
2014-08-19 20:20:45 +02:00
2014-08-26 15:01:56 +02:00
progress ++;
2025-06-19 09:47:03 +02:00
m_result . OperationProgressUpdater . UpdateProgress (( float ) progress / files . Count );
2014-08-26 15:01:56 +02:00
2017-02-27 00:43:06 +01:00
if ( f . Size <= 0 || string . IsNullOrWhiteSpace ( f . Hash ))
2014-08-19 20:20:45 +02:00
{
2025-06-12 10:38:07 +02:00
Logging . Log . WriteInformationMessage ( LOGTAG , "MissingRemoteHash" , LC . L ( "No hash or size recorded for {0}, performing full verification" , f . Name ));
2014-08-19 20:20:45 +02:00
KeyValuePair < string , IEnumerable < KeyValuePair < TestEntryStatus , string >>> res ;
2025-06-19 09:47:03 +02:00
( var tf , var hash , var size ) = await backend . GetWithInfoAsync ( f . Name , f . Hash , f . Size , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2025-01-28 08:54:50 +01:00
using ( tf )
2025-06-19 09:47:03 +02:00
res = await TestVolumeInternals ( db , f , tf , m_options , 1 , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-06-19 09:47:03 +02:00
m_result . AddResult ( res . Key , res . Value );
2017-02-27 00:43:06 +01:00
if (! string . IsNullOrWhiteSpace ( hash ) && size > 0 )
2014-08-19 20:20:45 +02:00
{
2017-02-27 00:43:06 +01:00
if ( res . Value == null || ! res . Value . Any ())
2014-08-19 20:20:45 +02:00
{
2017-02-27 00:43:06 +01:00
if ( m_options . Dryrun )
{
2025-06-12 10:38:07 +02:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "CapturedHashAndSize" , LC . L ( "Successfully captured hash and size for {0}, would update database" , f . Name ));
2017-02-27 00:43:06 +01:00
}
else
{
2025-06-12 10:38:07 +02:00
Logging . Log . WriteInformationMessage ( LOGTAG , "CapturedHashAndSize" , LC . L ( "Successfully captured hash and size for {0}, updating database" , f . Name ));
2025-06-12 08:34:29 +02:00
await db
2025-06-19 09:47:03 +02:00
. UpdateRemoteVolume ( f . Name , RemoteVolumeState . Verified , size , hash , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2017-02-27 00:43:06 +01:00
}
2014-08-19 20:20:45 +02:00
}
}
}
else
2025-01-28 08:54:50 +01:00
{
2025-06-19 09:47:03 +02:00
using ( var tf = await backend . GetAsync ( f . Name , f . Hash , f . Size , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false ))
2025-01-28 08:54:50 +01:00
{ }
}
2024-05-21 15:48:29 +02:00
2025-06-12 08:34:29 +02:00
await db
2025-06-19 09:47:03 +02:00
. UpdateVerificationCount ( f . Name , m_result . TaskControl . ProgressToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-06-19 09:47:03 +02:00
m_result . AddResult ( f . Name , []);
2014-03-10 14:16:27 +01:00
}
catch ( Exception ex )
{
2025-06-19 09:47:03 +02:00
m_result . AddResult ( f . Name , [ new KeyValuePair < TestEntryStatus , string >( TestEntryStatus . Error , ex . Message )]);
2025-06-12 10:38:07 +02:00
Logging . Log . WriteErrorMessage ( LOGTAG , "FailedToProcessFile" , ex , LC . L ( "Failed to process file {0}" , f . Name ));
2025-03-20 22:32:04 +01:00
if ( ex . IsAbortOrCancelException ())
2016-12-01 23:59:54 +01:00
{
2025-06-19 09:47:03 +02:00
m_result . EndTime = DateTime . UtcNow ;
2014-05-15 12:47:16 +02:00
throw ;
2016-12-01 23:59:54 +01:00
}
2014-03-10 14:16:27 +01:00
}
2013-06-26 21:59:01 +02:00
}
}
2016-12-01 23:59:54 +01:00
2025-06-19 09:47:03 +02:00
m_result . EndTime = DateTime . UtcNow ;
2023-12-23 14:25:28 +01:00
// generate a backup error status when any test is failing - except for 'extra' status
// because these problems don't block database rebuilding.
2025-06-19 09:47:03 +02:00
var filtered = from n in m_result . Verifications where n . Value . Any ( x => x . Key != TestEntryStatus . Extra ) select n ;
2023-12-10 00:23:16 +01:00
if (! filtered . Any ())
{
2025-06-19 12:17:30 +02:00
Logging . Log . WriteInformationMessage ( LOGTAG , "Test results" , LC . L ( "Successfully verified {0} remote files" , m_result . VerificationsActualLength ));
2023-12-10 00:23:16 +01:00
}
else
{
2025-06-19 12:17:30 +02:00
Logging . Log . WriteErrorMessage ( LOGTAG , "Test results" , null , LC . L ( "Verified {0} remote files with {1} problem(s)" , m_result . VerificationsActualLength , filtered . Count ()));
2023-12-10 00:23:16 +01:00
}
2013-06-26 21:59:01 +02:00
}
2014-08-19 20:20:45 +02:00
/// <summary>
2025-06-18 08:26:20 +02:00
/// Tests the volume by examining the internal contents.
2014-08-19 20:20:45 +02:00
/// </summary>
2025-06-18 08:26:20 +02:00
/// <param name="db">The local test database to use for verification.</param>
/// <param name="vol">The remote volume being examined.</param>
/// <param name="tf">The path to the downloaded copy of the file.</param>
/// <param name="options">The options to use for the test.</param>
/// <param name="sample_percent">A value between 0 and 1 that indicates how many blocks are tested in a dblock file.</param>
2025-06-19 09:47:03 +02:00
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
2025-06-18 08:26:20 +02:00
/// <returns>A task that when awaited returns a key-value pair where the key is the volume name and the value is a list of test results.</returns>
2025-06-19 09:47:03 +02:00
public static async Task < KeyValuePair < string , IEnumerable < KeyValuePair < TestEntryStatus , string >>>> TestVolumeInternals ( LocalTestDatabase db , IRemoteVolume vol , string tf , Options options , double sample_percent , CancellationToken cancellationToken )
2014-08-19 20:20:45 +02:00
{
2021-04-04 11:17:13 -07:00
var hashsize = HashFactory . HashSizeBytes ( options . BlockHashAlgorithm );
2014-08-19 20:20:45 +02:00
var parsedInfo = Volumes . VolumeBase . ParseFilename ( vol . Name );
sample_percent = Math . Min ( 1 , Math . Max ( sample_percent , 0.01 ));
2017-09-26 20:20:14 -07:00
switch ( parsedInfo . FileType )
2014-08-19 20:20:45 +02:00
{
2017-09-26 20:20:14 -07:00
case RemoteVolumeType . Files :
2025-05-21 15:34:40 +02:00
//Compare with db and see if all files are accounted for
2017-09-26 20:20:14 -07:00
// with correct file hashes and blocklist hashes
2025-06-19 09:47:03 +02:00
await using ( var fl = await db . CreateFilelist ( vol . Name , cancellationToken ). ConfigureAwait ( false ))
2014-08-19 20:20:45 +02:00
{
2017-09-26 20:20:14 -07:00
using ( var rd = new Volumes . FilesetVolumeReader ( parsedInfo . CompressionModule , tf , options ))
foreach ( var f in rd . Files )
2025-06-12 08:34:29 +02:00
await fl
2025-06-19 09:47:03 +02:00
. Add ( f . Path , f . Size , f . Hash , f . Metasize , f . Metahash , f . BlocklistHashes , f . Type , f . Time , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2017-09-26 20:20:14 -07:00
2025-06-19 09:47:03 +02:00
return new KeyValuePair < string , IEnumerable < KeyValuePair < TestEntryStatus , string >>>( vol . Name , await fl . Compare ( cancellationToken ). ToListAsync ( cancellationToken : cancellationToken ). ConfigureAwait ( false ));
2017-09-26 20:20:14 -07:00
}
case RemoteVolumeType . Index :
var blocklinks = new List < Tuple < string , string , long >>();
2025-05-27 17:15:02 +02:00
var combined = new List < KeyValuePair < Duplicati . Library . Interface . TestEntryStatus , string >>();
2017-09-26 20:20:14 -07:00
//Compare with db and see that all hashes and volumes are listed
using ( var rd = new Volumes . IndexVolumeReader ( parsedInfo . CompressionModule , tf , options , hashsize ))
2025-05-27 17:15:02 +02:00
{
2017-09-26 20:20:14 -07:00
foreach ( var v in rd . Volumes )
2014-08-19 20:20:45 +02:00
{
2017-09-26 20:20:14 -07:00
blocklinks . Add ( new Tuple < string , string , long >( v . Filename , v . Hash , v . Length ));
2025-06-19 09:47:03 +02:00
await using ( var bl = await db . CreateBlocklist ( v . Filename , cancellationToken ). ConfigureAwait ( false ))
2017-09-26 20:20:14 -07:00
{
foreach ( var h in v . Blocks )
2025-06-12 08:34:29 +02:00
await bl
2025-06-19 09:47:03 +02:00
. AddBlock ( h . Key , h . Value , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
combined . AddRange (
await bl
2025-06-19 09:47:03 +02:00
. Compare ( cancellationToken )
. ToListAsync ( cancellationToken : cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false )
);
2017-09-26 20:20:14 -07:00
}
2014-08-19 20:20:45 +02:00
}
2017-09-26 20:20:14 -07:00
2025-05-27 17:15:02 +02:00
if ( options . IndexfilePolicy == Options . IndexFileStrategy . Full )
{
var hashesPerBlock = options . Blocksize / options . BlockhashSize ;
2025-06-19 09:47:03 +02:00
await using ( var bl = await db . CreateBlocklistHashList ( vol . Name , cancellationToken ). ConfigureAwait ( false ))
2025-05-27 17:15:02 +02:00
{
foreach ( var b in rd . BlockLists )
2025-06-12 08:34:29 +02:00
await bl
2025-06-19 09:47:03 +02:00
. AddBlockHash ( b . Hash , b . Length , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
combined . AddRange (
await bl
2025-06-19 09:47:03 +02:00
. Compare ( hashesPerBlock , options . BlockhashSize , options . Blocksize , cancellationToken )
. ToListAsync ( cancellationToken : cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false )
);
2025-05-27 17:15:02 +02:00
}
}
}
// Compare with db and see that all blocklists are listed
2025-06-19 09:47:03 +02:00
await using ( var il = await db . CreateIndexlist ( vol . Name , cancellationToken ). ConfigureAwait ( false ))
2017-09-26 20:20:14 -07:00
{
foreach ( var t in blocklinks )
2025-06-12 08:34:29 +02:00
await il
2025-06-19 09:47:03 +02:00
. AddBlockLink ( t . Item1 , t . Item2 , t . Item3 , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
combined . AddRange (
await il
2025-06-19 09:47:03 +02:00
. Compare ( cancellationToken )
. ToArrayAsync ( cancellationToken : cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false )
);
2014-08-19 20:20:45 +02:00
}
2017-09-26 20:20:14 -07:00
2025-05-27 17:15:02 +02:00
return new KeyValuePair < string , IEnumerable < KeyValuePair < TestEntryStatus , string >>>( vol . Name , combined );
2017-09-26 20:20:14 -07:00
case RemoteVolumeType . Blocks :
2021-04-04 11:17:13 -07:00
using ( var blockhasher = HashFactory . CreateHasher ( options . BlockHashAlgorithm ))
2025-06-19 09:47:03 +02:00
await using ( var bl = await db . CreateBlocklist ( vol . Name , cancellationToken ). ConfigureAwait ( false ))
2017-09-26 20:20:14 -07:00
using ( var rd = new Volumes . BlockVolumeReader ( parsedInfo . CompressionModule , tf , options ))
2014-08-19 20:20:45 +02:00
{
2017-09-26 20:20:14 -07:00
//Verify that all blocks are in the file
foreach ( var b in rd . Blocks )
2025-06-12 08:34:29 +02:00
await bl
2025-06-19 09:47:03 +02:00
. AddBlock ( b . Key , b . Value , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2017-09-26 20:20:14 -07:00
//Select random blocks and verify their hashes match the filename and size
var hashsamples = new List < KeyValuePair < string , long >>( rd . Blocks );
var sampleCount = Math . Min ( Math . Max ( 0 , ( int )( hashsamples . Count * sample_percent )), hashsamples . Count - 1 );
var rnd = new Random ();
while ( hashsamples . Count > sampleCount )
hashsamples . RemoveAt ( rnd . Next ( hashsamples . Count ));
var blockbuffer = new byte [ options . Blocksize ];
var changes = new List < KeyValuePair < Library . Interface . TestEntryStatus , string >>();
foreach ( var s in hashsamples )
2014-08-19 20:20:45 +02:00
{
2017-09-26 20:20:14 -07:00
var size = rd . ReadBlock ( s . Key , blockbuffer );
if ( size != s . Value )
2014-08-19 20:20:45 +02:00
changes . Add ( new KeyValuePair < Library . Interface . TestEntryStatus , string >( Library . Interface . TestEntryStatus . Modified , s . Key ));
2017-09-26 20:20:14 -07:00
else
{
var hash = Convert . ToBase64String ( blockhasher . ComputeHash ( blockbuffer , 0 , size ));
if ( hash != s . Key )
changes . Add ( new KeyValuePair < Library . Interface . TestEntryStatus , string >( Library . Interface . TestEntryStatus . Modified , s . Key ));
}
2014-08-19 20:20:45 +02:00
}
2017-09-26 20:20:14 -07:00
2025-06-12 08:34:29 +02:00
return new KeyValuePair < string , IEnumerable < KeyValuePair < TestEntryStatus , string >>>(
vol . Name ,
changes . Union (
await bl
2025-06-19 09:47:03 +02:00
. Compare ( cancellationToken )
. ToListAsync ( cancellationToken : cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false )
)
);
2014-08-19 20:20:45 +02:00
}
}
2025-06-12 10:38:07 +02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "UnexpectedFileType" , null , LC . L ( "Unexpected file type {0} for {1}" , parsedInfo . FileType , vol . Name ));
2014-08-19 20:20:45 +02:00
return new KeyValuePair < string , IEnumerable < KeyValuePair < TestEntryStatus , string >>>( vol . Name , null );
}
2025-05-27 17:15:02 +02:00
2025-06-02 11:50:35 +02:00
private async Task ReplaceFaultyIndexFilesAsync ( List < IRemoteVolume > faultyIndexFiles , IBackendManager backendManager , LocalTestDatabase db , CancellationToken cancellationToken )
2025-05-27 17:15:02 +02:00
{
2025-06-18 11:53:11 +02:00
await using var repairdb =
2025-06-19 09:47:03 +02:00
await LocalRepairDatabase . CreateAsync ( db , null , cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-06-02 11:50:35 +02:00
2025-05-27 17:15:02 +02:00
foreach ( var vol in faultyIndexFiles )
{
2025-06-19 09:47:03 +02:00
if (! await m_result . TaskControl . ProgressRendevouz (). ConfigureAwait ( false ))
2025-05-27 17:15:02 +02:00
{
2025-06-19 09:47:03 +02:00
m_result . EndTime = DateTime . UtcNow ;
2025-05-27 17:15:02 +02:00
return ;
}
IndexVolumeWriter newEntry = null ;
try
{
var w = newEntry = new IndexVolumeWriter ( m_options );
2025-06-02 11:50:35 +02:00
await RepairHandler . RunRepairDindex ( backendManager , repairdb , w , vol , m_options , cancellationToken ). ConfigureAwait ( false );
2025-05-27 17:15:02 +02:00
if ( m_options . Dryrun )
{
2025-06-12 10:38:07 +02:00
Logging . Log . WriteDryrunMessage ( LOGTAG , "ReplaceFaultyIndexFile" , LC . L ( "Would replace faulty index file {0} with {1}" , vol . Name , w . RemoteFilename ));
2025-05-27 17:15:02 +02:00
}
else
{
2025-06-19 09:47:03 +02:00
await backendManager . DeleteAsync ( vol . Name , vol . Size , true , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
await backendManager . WaitForEmptyAsync ( repairdb , m_result . TaskControl . ProgressToken ). ConfigureAwait ( false );
2025-06-12 08:34:29 +02:00
await repairdb . Transaction
2025-06-24 14:38:58 +02:00
. CommitAsync ( "ReplaceFaultyIndexFileCommit" , token : cancellationToken )
2025-06-12 08:34:29 +02:00
. ConfigureAwait ( false );
2025-06-24 14:38:58 +02:00
m_result . RemoveResult ( vol . Name );
2025-05-27 17:15:02 +02:00
}
}
catch ( Exception ex )
{
newEntry ?. Dispose ();
2025-06-12 10:38:07 +02:00
Logging . Log . WriteErrorMessage ( LOGTAG , "FailedToReplaceFaultyIndexFile" , ex , LC . L ( "Failed to replace faulty index file {0}" , vol . Name ));
2025-05-27 17:15:02 +02:00
if ( ex . IsAbortException ())
throw ;
}
}
}
2013-06-26 21:59:01 +02:00
}
}