2025-01-07 09:40:39 +01:00
// Copyright (C) 2025, The Duplicati Team
2024-02-28 15:45:30 +01:00
// https://duplicati.com, hello@duplicati.com
//
// 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
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// 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
// DEALINGS IN THE SOFTWARE.
2020-12-26 21:20:46 -08:00
using System ;
2020-01-09 20:12:44 -08:00
using System.Collections.Generic ;
2020-12-26 21:20:46 -08:00
using System.Data ;
2020-01-09 20:12:44 -08:00
using System.IO ;
2024-09-09 17:17:24 +02:00
using System.IO.Compression ;
2020-01-09 20:12:44 -08:00
using System.Linq ;
2025-03-11 08:08:28 +01:00
using System.Threading ;
2020-07-05 15:00:08 -07:00
using Duplicati.Library.Interface ;
2020-01-09 20:12:44 -08:00
using Duplicati.Library.Main ;
2020-12-26 21:20:46 -08:00
using Duplicati.Library.Main.Database ;
2024-09-10 10:02:54 +02:00
using Duplicati.Library.Main.Volumes ;
2020-12-26 21:20:46 -08:00
using Duplicati.Library.SQLiteHelper ;
2024-09-09 17:17:24 +02:00
using Duplicati.Library.Utility ;
2020-01-09 20:12:44 -08:00
using NUnit.Framework ;
namespace Duplicati.UnitTest
{
[TestFixture]
public class RepairHandlerTests : BasicSetupHelper
{
2022-05-05 22:30:15 -07:00
[SetUp]
public void SetUp ()
2020-01-09 20:12:44 -08:00
{
2024-09-10 10:02:54 +02:00
File . WriteAllBytes ( Path . Combine ( this . DATAFOLDER , "file" ), new byte [] { 0 });
2020-01-09 20:12:44 -08:00
}
2025-03-10 14:40:13 +01:00
public void ModifyFile ()
{
File . WriteAllBytes ( Path . Combine ( this . DATAFOLDER , "file" ), new byte [] { 1 });
}
2020-12-26 21:20:46 -08:00
[Test]
[Category("RepairHandler")]
public void RepairMissingBlocklistHashes ()
{
byte [] data = new byte [ 150 * 1024 ];
Random rng = new Random ();
for ( int k = 0 ; k < 2 ; k ++)
{
rng . NextBytes ( data );
File . WriteAllBytes ( Path . Combine ( this . DATAFOLDER , $"{k}" ), data );
}
Dictionary < string , string > options = new Dictionary < string , string >( this . TestOptions );
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
2024-09-10 13:54:28 +02:00
var backupResults = c . Backup ([ this . DATAFOLDER ]);
2020-12-26 21:20:46 -08:00
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
Assert . AreEqual ( 0 , backupResults . Warnings . Count ());
}
// Mimic a damaged database that needs to be repaired.
const string selectStatement = @"SELECT BlocksetID, ""Index"", Hash FROM BlocklistHash ORDER BY Hash ASC" ;
List < int > expectedBlocksetIDs = new List < int >();
List < int > expectedIndexes = new List < int >();
List < string > expectedHashes = new List < string >();
using ( IDbConnection connection = SQLiteLoader . LoadConnection ( options [ "dbpath" ]))
{
// Read the contents of the BlocklistHash table so that we can
// compare them to the contents after the repair operation.
using ( IDbCommand command = connection . CreateCommand ())
{
using ( IDataReader reader = command . ExecuteReader ( selectStatement ))
{
while ( reader . Read ())
{
expectedBlocksetIDs . Add ( reader . GetInt32 ( 0 ));
expectedIndexes . Add ( reader . GetInt32 ( 1 ));
expectedHashes . Add ( reader . GetString ( 2 ));
}
}
}
using ( IDbCommand command = connection . CreateCommand ())
{
command . ExecuteNonQuery ( @"DELETE FROM BlocklistHash" );
using ( IDataReader reader = command . ExecuteReader ( selectStatement ))
{
Assert . IsFalse ( reader . Read ());
}
}
}
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
IRepairResults repairResults = c . Repair ();
Assert . AreEqual ( 0 , repairResults . Errors . Count ());
Assert . AreEqual ( 0 , repairResults . Warnings . Count ());
}
List < int > repairedBlocksetIDs = new List < int >();
List < int > repairedIndexes = new List < int >();
List < string > repairedHashes = new List < string >();
using ( IDbConnection connection = SQLiteLoader . LoadConnection ( options [ "dbpath" ]))
{
using ( IDbCommand command = connection . CreateCommand ())
{
using ( IDataReader reader = command . ExecuteReader ( selectStatement ))
{
while ( reader . Read ())
{
repairedBlocksetIDs . Add ( reader . GetInt32 ( 0 ));
repairedIndexes . Add ( reader . GetInt32 ( 1 ));
repairedHashes . Add ( reader . GetString ( 2 ));
}
}
}
}
CollectionAssert . AreEqual ( expectedBlocksetIDs , repairedBlocksetIDs );
CollectionAssert . AreEqual ( expectedIndexes , repairedIndexes );
CollectionAssert . AreEqual ( expectedHashes , repairedHashes );
// A subsequent backup should run without errors.
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
2024-09-09 15:27:37 +02:00
var backupResults = c . Backup ([ this . DATAFOLDER ]);
2020-12-26 21:20:46 -08:00
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
Assert . AreEqual ( 0 , backupResults . Warnings . Count ());
}
}
2020-01-09 20:12:44 -08:00
[Test]
[Category("RepairHandler")]
[TestCase("true")]
[TestCase("false")]
public void RepairMissingIndexFiles ( string noEncryption )
{
2023-10-08 21:39:13 +02:00
Dictionary < string , string > options = new Dictionary < string , string >( this . TestOptions ) { [ "no-encryption" ] = noEncryption };
2020-01-09 20:12:44 -08:00
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
2024-09-09 15:27:37 +02:00
var backupResults = c . Backup ([ this . DATAFOLDER ]);
2020-07-05 15:00:08 -07:00
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
Assert . AreEqual ( 0 , backupResults . Warnings . Count ());
2020-01-09 20:12:44 -08:00
}
2024-09-09 15:27:37 +02:00
var dindexFiles = Directory . EnumerateFiles ( this . TARGETFOLDER , "*dindex*" ). ToArray ();
2020-01-09 21:32:33 -08:00
Assert . Greater ( dindexFiles . Length , 0 );
2024-09-09 15:27:37 +02:00
foreach ( var f in dindexFiles )
2020-01-09 20:12:44 -08:00
{
File . Delete ( f );
}
2024-09-09 15:27:37 +02:00
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
2020-01-09 20:12:44 -08:00
{
2024-09-09 15:27:37 +02:00
var repairResults = c . Repair ();
2020-07-05 15:00:08 -07:00
Assert . AreEqual ( 0 , repairResults . Errors . Count ());
Assert . AreEqual ( 0 , repairResults . Warnings . Count ());
2020-01-09 20:12:44 -08:00
}
2024-09-09 15:27:37 +02:00
foreach ( var file in dindexFiles )
2020-01-09 20:12:44 -08:00
{
Assert . IsTrue ( File . Exists ( Path . Combine ( this . TARGETFOLDER , file )));
}
}
2023-10-08 21:39:13 +02:00
2023-11-30 21:45:46 +01:00
[Test]
[Category("RepairHandler"), Category("Targeted")]
public void RepairMissingIndexFilesBlocklist ()
{
// See issue #3202
2024-09-09 15:27:37 +02:00
var options = new Dictionary < string , string >( this . TestOptions )
2023-11-30 21:45:46 +01:00
{
2024-09-09 15:27:37 +02:00
["blocksize"] = "1KB" ,
["no-encryption"] = "true"
2023-11-30 21:45:46 +01:00
};
2024-09-09 15:27:37 +02:00
var filename = Path . Combine ( this . DATAFOLDER , "file" );
2023-11-30 21:45:46 +01:00
using ( var s = File . Create ( filename ))
{
2024-09-09 15:27:37 +02:00
var size = 1024 * 32 + 1 ; // Blocklist size + 1
2023-11-30 21:45:46 +01:00
s . Write ( new byte [ size ], 0 , size );
}
2024-09-09 15:27:37 +02:00
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
2023-11-30 21:45:46 +01:00
{
2024-09-09 15:27:37 +02:00
var backupResults = c . Backup ( new [] { this . DATAFOLDER });
2023-11-30 21:45:46 +01:00
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
Assert . AreEqual ( 0 , backupResults . Warnings . Count ());
using ( var s = File . OpenWrite ( filename ))
{
// Change first byte
s . WriteByte ( 1 );
}
backupResults = c . Backup ( new [] { this . DATAFOLDER });
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
Assert . AreEqual ( 0 , backupResults . Warnings . Count ());
}
2024-09-09 15:27:37 +02:00
var dindexFiles = Directory . EnumerateFiles ( this . TARGETFOLDER , "*dindex*" ). ToArray ();
2023-11-30 21:45:46 +01:00
Assert . Greater ( dindexFiles . Length , 0 );
2024-09-09 15:27:37 +02:00
foreach ( var f in dindexFiles )
2023-11-30 21:45:46 +01:00
{
File . Delete ( f );
}
2024-09-09 15:27:37 +02:00
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
2023-11-30 21:45:46 +01:00
{
2024-09-09 15:27:37 +02:00
var repairResults = c . Repair ();
2020-07-05 15:00:08 -07:00
Assert . AreEqual ( 0 , repairResults . Errors . Count ());
Assert . AreEqual ( 0 , repairResults . Warnings . Count ());
2020-01-09 20:12:44 -08:00
}
2024-09-09 15:27:37 +02:00
foreach ( var file in dindexFiles )
2020-01-09 20:12:44 -08:00
{
Assert . IsTrue ( File . Exists ( Path . Combine ( this . TARGETFOLDER , file )));
}
}
2024-09-10 10:02:54 +02:00
2024-09-09 17:17:24 +02:00
[Test]
[Category("RepairHandler"), Category("Targeted")]
public void RecreateWithDefectIndexBlock ()
{
// See issue #3202
var options = new Dictionary < string , string >( this . TestOptions )
{
["blocksize"] = "1KB" ,
["no-encryption"] = "true"
};
var filename = Path . Combine ( this . DATAFOLDER , "file" );
using ( var s = File . Create ( filename ))
{
var size = 1024 * 32 + 1 ; // Blocklist size + 1
s . Write ( new byte [ size ], 0 , size );
}
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
var backupResults = c . Backup ( new [] { this . DATAFOLDER });
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
Assert . AreEqual ( 0 , backupResults . Warnings . Count ());
using ( var s = File . OpenWrite ( filename ))
{
// Change first byte
s . WriteByte ( 1 );
}
backupResults = c . Backup ( new [] { this . DATAFOLDER });
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
Assert . AreEqual ( 0 , backupResults . Warnings . Count ());
}
var dindexFiles = Directory . EnumerateFiles ( this . TARGETFOLDER , "*dindex*" ). ToArray ();
Assert . Greater ( dindexFiles . Length , 0 );
// Corrupt the first index file
using ( var tmp = new TempFile ())
{
using ( var zip = new ZipArchive ( File . Open ( tmp , FileMode . Create , FileAccess . ReadWrite ), ZipArchiveMode . Create ))
using ( var sourceZip = new ZipArchive ( File . Open ( dindexFiles [ 0 ], FileMode . Open , FileAccess . ReadWrite )))
{
foreach ( var entry in sourceZip . Entries )
{
using ( var s = entry . Open ())
{
var newEntry = zip . CreateEntry ( entry . FullName );
using ( var d = newEntry . Open ())
{
if ( entry . FullName . StartsWith ( "list/" ))
{
using ( var ms = new MemoryStream ())
{
s . CopyTo ( ms );
ms . Position = 0 ;
ms . WriteByte ( 42 );
ms . Position = 0 ;
ms . CopyTo ( d );
}
}
else
{
s . CopyTo ( d );
}
}
}
}
}
File . Copy ( tmp , dindexFiles [ 0 ], true );
}
// Delete database and recreate
File . Delete ( options [ "dbpath" ]);
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
var repairResults = c . Repair ();
Assert . AreEqual ( 0 , repairResults . Errors . Count ());
Assert . AreEqual ( 1 , repairResults . Warnings . Count ());
}
File . Delete ( dindexFiles [ 0 ]);
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
var repairResults = c . Repair ();
Assert . AreEqual ( 0 , repairResults . Errors . Count ());
Assert . AreEqual ( 0 , repairResults . Warnings . Count ());
}
// Delete database and recreate
File . Delete ( options [ "dbpath" ]);
// No errors with recreated index file
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
var repairResults = c . Repair ();
Assert . AreEqual ( 0 , repairResults . Errors . Count ());
Assert . AreEqual ( 0 , repairResults . Warnings . Count ());
}
}
2024-09-10 13:54:28 +02:00
2024-09-10 10:02:54 +02:00
[Test]
[Category("RepairHandler"), Category("Targeted")]
public void AutoCleanupRepairDoesNotLockDatabase ()
{
// See issue #3635, #4631
var options = new Dictionary < string , string >( this . TestOptions )
{
["blocksize"] = "1KB" ,
["no-encryption"] = "true" ,
["auto-cleanup"] = "true"
};
2024-09-10 12:47:19 +02:00
var delaytime = TimeSpan . FromSeconds ( 3 );
2024-09-10 10:02:54 +02:00
var filename = Path . Combine ( this . DATAFOLDER , "file" );
using ( var s = File . Create ( filename ))
s . SetLength ( 1024 * 38 ); // Random size
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
var backupResults = c . Backup ([ this . DATAFOLDER ]);
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
Assert . AreEqual ( 0 , backupResults . Warnings . Count ());
}
var dblockFiles = Directory . EnumerateFiles ( this . TARGETFOLDER , "*dblock*" ). ToArray ();
Assert . Greater ( dblockFiles . Length , 0 );
var sourcename = Path . GetFileName ( dblockFiles . First ());
var p = VolumeBase . ParseFilename ( sourcename );
var guid = VolumeWriterBase . GenerateGuid ();
var time = p . Time . Ticks == 0 ? p . Time : p . Time . AddSeconds ( 1 );
var newname = VolumeBase . GenerateFilename ( p . FileType , p . Prefix , guid , time , p . CompressionModule , p . EncryptionModule );
File . Copy ( Path . Combine ( this . TARGETFOLDER , sourcename ), Path . Combine ( this . TARGETFOLDER , newname ));
2024-09-10 12:47:19 +02:00
System . Threading . Thread . Sleep ( delaytime );
2024-09-10 10:02:54 +02:00
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
var backupResults = c . Backup ([ this . DATAFOLDER ]);
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
// 1 extra file + 1 warning
Assert . AreEqual ( 2 , backupResults . Warnings . Count ());
}
// Auto-cleanup should have removed the renamed file
Assert . IsFalse ( File . Exists ( Path . Combine ( this . TARGETFOLDER , newname )));
// Insert the extra file back
File . Copy ( Path . Combine ( this . TARGETFOLDER , sourcename ), Path . Combine ( this . TARGETFOLDER , newname ));
// Delete the database
File . Delete ( options [ "dbpath" ]);
// Recreate with an extra volume
2024-09-10 12:47:19 +02:00
System . Threading . Thread . Sleep ( delaytime );
2024-09-10 10:02:54 +02:00
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
var backupResults = c . Backup ([ this . DATAFOLDER ]);
Assert . AreEqual ( 0 , backupResults . Errors . Count ());
// First will recreate (4 files + 1 warning)
Assert . AreEqual ( 5 , backupResults . Warnings . Count ());
}
}
2024-10-29 16:40:06 +01:00
2023-10-08 21:39:13 +02:00
[Test]
[Category("RepairHandler")]
[TestCase("true")]
[TestCase("false")]
public void RepairExtraIndexFiles ( string noEncryption )
{
// Extra index files will be added to the database and should have a correct link established
Dictionary < string , string > options = new Dictionary < string , string >( this . TestOptions ) { [ "no-encryption" ] = noEncryption };
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
IBackupResults backupResults = c . Backup ( new [] { this . DATAFOLDER });
TestUtils . AssertResults ( backupResults );
}
string [] dindexFiles = Directory . EnumerateFiles ( this . TARGETFOLDER , "*dindex*" ). ToArray ();
Assert . Greater ( dindexFiles . Length , 0 );
string origFile = dindexFiles . First ();
// Duplicate index file
string dupFile = Path . Combine ( TARGETFOLDER , Path . GetFileName ( origFile ). Replace ( ".dindex" , "1.dindex" ));
File . Copy ( origFile , dupFile );
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
IRepairResults repairResults = c . Repair ();
TestUtils . AssertResults ( repairResults );
}
// Check database block link
using ( LocalDatabase db = new LocalDatabase ( DBFILE , "Test" , true ))
{
long indexVolumeId = db . GetRemoteVolumeID ( Path . GetFileName ( origFile ));
long duplicateVolumeId = db . GetRemoteVolumeID ( Path . GetFileName ( dupFile ));
Assert . AreNotEqual (- 1 , indexVolumeId );
Assert . AreNotEqual (- 1 , duplicateVolumeId );
using ( var cmd = db . Connection . CreateCommand ())
{
string sql = @"SELECT ""BlockVolumeID"" FROM ""IndexBlockLink"" WHERE ""IndexVolumeID"" = ?" ;
long linkedIndexId = cmd . ExecuteScalarInt64 ( sql , - 1 , indexVolumeId );
long linkedDuplicateId = cmd . ExecuteScalarInt64 ( sql , - 1 , duplicateVolumeId );
Assert . AreEqual ( linkedIndexId , linkedDuplicateId );
}
}
}
2025-03-10 14:40:13 +01:00
[Test]
[Category("RepairHandler")]
public void RepairMissingDlistFile ()
{
// Make two backups
var options = this . TestOptions ;
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
TestUtils . AssertResults ( c . Backup ( new [] { this . DATAFOLDER }));
ModifyFile ();
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
TestUtils . AssertResults ( c . Backup ( new [] { this . DATAFOLDER }));
// Find and delete a dlist file
var dlistFile = Directory . EnumerateFiles ( this . TARGETFOLDER , "*.dlist.*" ). First ();
File . Delete ( dlistFile );
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
IRepairResults repairResults = c . Repair ();
TestUtils . AssertResults ( repairResults );
Assert . AreEqual ( 2 , Directory . EnumerateFiles ( this . TARGETFOLDER , "*.dlist.*" ). Count ());
}
}
[Test]
[Category("RepairHandler")]
[TestCase("true")]
[TestCase("false")]
public void RepairMissingDlistVolume ( bool deleteRemoteFile )
{
// Make two backups
var options = this . TestOptions ;
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
TestUtils . AssertResults ( c . Backup ( new [] { this . DATAFOLDER }));
ModifyFile ();
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
TestUtils . AssertResults ( c . Backup ( new [] { this . DATAFOLDER }));
// Find a dlist file
var dlistFile = Directory . EnumerateFiles ( this . TARGETFOLDER , "*.dlist.*" ). First ();
if ( deleteRemoteFile )
File . Delete ( dlistFile );
using ( var con = SQLiteLoader . LoadConnection ( options [ "dbpath" ]))
using ( var cmd = con . CreateCommand ())
Assert . AreEqual ( 1 , cmd . ExecuteNonQuery ( "DELETE FROM RemoteVolume WHERE Name = ?" , Path . GetFileName ( dlistFile )));
// Should catch this in validation
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
Assert . Throws < DatabaseInconsistencyException >(() => c . Backup ( new [] { this . DATAFOLDER }));
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
IRepairResults repairResults = c . Repair ();
TestUtils . AssertResults ( repairResults );
Assert . AreEqual ( 2 , Directory . EnumerateFiles ( this . TARGETFOLDER , "*.dlist.*" ). Count ());
Assert . AreEqual ( 2 , c . List (). Filesets . Count ());
}
// Check that the entry was recreated
using ( var con = SQLiteLoader . LoadConnection ( options [ "dbpath" ]))
using ( var cmd = con . CreateCommand ())
Assert . AreEqual ( 2 , cmd . ExecuteScalarInt64 ( "SELECT COUNT(*) FROM RemoteVolume WHERE Name LIKE '%.dlist.%' AND State != ?" , - 1 , RemoteVolumeState . Deleted . ToString ()));
// Delete the database and check that the result is correct
File . Delete ( options [ "dbpath" ]);
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
{
IRepairResults repairResults = c . Repair ();
TestUtils . AssertResults ( repairResults );
Assert . AreEqual ( 2 , c . List (). Filesets . Count ());
var r = c . Test ( long . MaxValue );
Assert . AreEqual ( 0 , r . Errors . Count ());
Assert . AreEqual ( 0 , r . Warnings . Count ());
Assert . IsFalse ( r . Verifications . Any ( p => p . Value . Any ()));
}
}
[Test]
[Category("RepairHandler")]
public void RepairMissingFilesetVolume ()
{
// Make two backups
var options = this . TestOptions ;
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
TestUtils . AssertResults ( c . Backup ( new [] { this . DATAFOLDER }));
2025-03-11 08:08:28 +01:00
// Make room for a new backup
Thread . Sleep ( 2000 );
2025-03-10 14:40:13 +01:00
ModifyFile ();
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
TestUtils . AssertResults ( c . Backup ( new [] { this . DATAFOLDER }));
// Remove a fileset
using ( var con = SQLiteLoader . LoadConnection ( options [ "dbpath" ]))
using ( var cmd = con . CreateCommand ())
{
var filesetId = cmd . ExecuteScalarInt64 ( "SELECT Id FROM Fileset ORDER BY Id DESC LIMIT 1" );
Assert . AreEqual ( 1 , cmd . ExecuteNonQuery ( "DELETE FROM Fileset WHERE Id = ?" , filesetId ));
cmd . ExecuteNonQuery ( "DELETE FROM FilesetEntry WHERE FilesetId = ?" , filesetId );
}
// Should catch this in validation
using ( var c = new Controller ( "file://" + this . TARGETFOLDER , options , null ))
Assert . Throws < DatabaseInconsistencyException >(() => c . Backup ( new [] { this . DATAFOLDER }));
2025-03-11 10:45:44 +01:00
// Since we have removed the entry that is being checked here, we need to disable the check
var repairOptions = new Dictionary < string , string >( options ) { [ "repair-ignore-outdated-database" ] = "true" };
using ( Controller c = new Controller ( "file://" + this . TARGETFOLDER , repairOptions , null ))
2025-03-10 14:40:13 +01:00
{
IRepairResults repairResults = c . Repair ();
2025-03-11 17:00:14 +01:00
Assert . AreEqual ( 0 , repairResults . Errors . Count ());
Assert . AreEqual ( 0 , repairResults . Warnings . Where ( x => x . IndexOf ( "RemoteFilesNewerThanLocalDatabase" , StringComparison . OrdinalIgnoreCase ) < 0 ). Count ());
2025-03-10 14:40:13 +01:00
Assert . AreEqual ( 2 , Directory . EnumerateFiles ( this . TARGETFOLDER , "*.dlist.*" ). Count ());
Assert . AreEqual ( 2 , c . List (). Filesets . Count ());
}
// Check that entry was recreated
using ( var con = SQLiteLoader . LoadConnection ( options [ "dbpath" ]))
using ( var cmd = con . CreateCommand ())
Assert . AreEqual ( 2 , cmd . ExecuteScalarInt64 ( "SELECT COUNT(*) FROM Fileset" ));
}
2020-01-09 20:12:44 -08:00
}
}