2013-02-12 21:43:14 +00:00
#region Disclaimer / License
2015-01-20 21:44:52 +01:00
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
2013-02-12 21:43:14 +00:00
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
#if DEBUG
using System ;
using System.Collections.Generic ;
using System.Text ;
using Duplicati.Library.Logging ;
using Duplicati.Library.Utility ;
2013-03-08 22:24:54 +01:00
using System.Linq ;
2013-02-12 21:43:14 +00:00
namespace Duplicati.CommandLine
{
/// <summary>
/// This class encapsulates a simple method for testing the correctness of duplicati.
/// </summary>
public class UnitTest
{
/// <summary>
/// A helper class to write debug messages to the log file
/// </summary>
private class LogHelper : StreamLog
{
private string m_backupset ;
2013-04-16 22:36:37 +02:00
public static long WarningCount = 0 ;
public static long ErrorCount = 0 ;
2013-02-12 21:43:14 +00:00
public string Backupset
{
get { return m_backupset ; }
set { m_backupset = value ; }
}
public LogHelper ( string file )
: base ( file )
{
this . Backupset = "none" ;
}
public override void WriteMessage ( string message , LogMessageType type , Exception exception )
{
2013-04-16 22:36:37 +02:00
if ( type == LogMessageType . Error )
System . Threading . Interlocked . Increment ( ref ErrorCount );
else if ( type == LogMessageType . Warning )
System . Threading . Interlocked . Increment ( ref WarningCount );
2013-02-12 21:43:14 +00:00
base . WriteMessage ( this . Backupset + ", " + message , type , exception );
}
}
/// <summary>
/// Running the unit test confirms the correctness of duplicati
/// </summary>
/// <param name="folders">The folders to backup. Folder at index 0 is the base, all others are incrementals</param>
public static void RunTest ( string [] folders , Dictionary < string , string > options )
{
//Place a file called "unittest_target.txt" in the bin folder, and enter a connection string like "ftp://username:password@example.com"
2013-05-05 16:40:37 +02:00
string auth_password = System . IO . Path . Combine ( System . IO . Path . GetDirectoryName ( System . Reflection . Assembly . GetExecutingAssembly (). Location ), "unittest_authpassword.txt" );
if ( System . IO . File . Exists ( auth_password ))
options [ "auth-password" ] = System . IO . File . ReadAllText ( auth_password ). Trim ();
2013-02-12 21:43:14 +00:00
string alttarget = System . IO . Path . Combine ( System . IO . Path . GetDirectoryName ( System . Reflection . Assembly . GetExecutingAssembly (). Location ), "unittest_target.txt" );
if ( System . IO . File . Exists ( alttarget ))
RunTest ( folders , options , System . IO . File . ReadAllText ( alttarget ). Trim ());
else
RunTest ( folders , options , null );
}
/// <summary>
/// Running the unit test confirms the correctness of duplicati
/// </summary>
/// <param name="folders">The folders to backup. Folder at index 0 is the base, all others are incrementals</param>
/// <param name="target">The target destination for the backups</param>
public static void RunTest ( string [] folders , Dictionary < string , string > options , string target )
{
LogHelper log = new LogHelper ( "unittest.log" );
Log . CurrentLog = log ; ;
Log . LogLevel = Duplicati . Library . Logging . LogMessageType . Profiling ;
string tempdir = System . IO . Path . Combine ( System . IO . Path . GetDirectoryName ( System . Reflection . Assembly . GetExecutingAssembly (). Location ), "tempdir" );
2013-03-08 22:24:54 +01:00
//Filter empty entries, commonly occuring with copy/paste and newlines
folders = ( from x in folders
where ! string . IsNullOrWhiteSpace ( x )
2013-03-15 21:14:57 +01:00
select Environment . ExpandEnvironmentVariables ( x )). ToArray ();
//Expand the tilde to home folder on Linux/OSX
if ( Utility . IsClientLinux )
folders = ( from x in folders
select x . Replace ( "~" , Environment . GetFolderPath ( Environment . SpecialFolder . Personal ))). ToArray ();
foreach ( var f in folders )
foreach ( var n in f . Split ( new char [] { System . IO . Path . PathSeparator }, StringSplitOptions . RemoveEmptyEntries ))
if (! System . IO . Directory . Exists ( n ))
throw new Exception ( string . Format ( "Missing source folder: {0}" , n ));
2013-02-12 21:43:14 +00:00
try
{
if ( System . IO . Directory . Exists ( tempdir ))
System . IO . Directory . Delete ( tempdir , true );
System . IO . Directory . CreateDirectory ( tempdir );
}
catch ( Exception ex )
{
Log . WriteMessage ( "Failed to clean tempdir" , LogMessageType . Error , ex );
}
Duplicati . Library . Utility . TempFolder . SystemTempPath = tempdir ;
//Set some defaults
if (! options . ContainsKey ( "passphrase" ))
options [ "passphrase" ] = "secret password!" ;
2013-05-13 22:32:05 +02:00
if (! options . ContainsKey ( "prefix" ))
options [ "prefix" ] = "duplicati_unittest" ;
2013-02-12 21:43:14 +00:00
//We want all messages in the log
options [ "log-level" ] = LogMessageType . Profiling . ToString ();
//We cannot rely on USN numbering, but we can use USN enumeration
//options["disable-usn-diff-check"] = "true";
//We use precise times
options [ "disable-time-tolerance" ] = "true" ;
//We need all sets, even if they are unchanged
options [ "upload-unchanged-backups" ] = "true" ;
2013-03-16 12:07:06 +01:00
bool skipfullrestore = false ;
bool skippartialrestore = false ;
bool skipverify = false ;
if ( Utility . ParseBoolOption ( options , "unittest-backuponly" ))
{
skipfullrestore = true ;
skippartialrestore = true ;
options . Remove ( "unittest-backuponly" );
}
if ( Utility . ParseBoolOption ( options , "unittest-skip-partial-restore" ))
{
skippartialrestore = true ;
options . Remove ( "unittest-skip-partial-restore" );
}
if ( Utility . ParseBoolOption ( options , "unittest-skip-full-restore" ))
{
skipfullrestore = true ;
options . Remove ( "unittest-skip-full-restore" );
}
if ( Utility . ParseBoolOption ( options , "unittest-skip-verify" ))
{
skipverify = true ;
options . Remove ( "unittest-skip-verify" );
}
2015-01-25 13:42:25 +01:00
var verifymetadata = ! Utility . ParseBoolOption ( options , "skip-metadata" );
2013-03-16 12:07:06 +01:00
2013-02-12 21:43:14 +00:00
using ( new Timer ( "Total unittest" ))
using ( TempFolder tf = new TempFolder ())
{
2013-05-11 12:03:15 +02:00
options [ "dbpath" ] = System . IO . Path . Combine ( tempdir , "unittest.sqlite" );
if ( System . IO . File . Exists ( options [ "dbpath" ]))
System . IO . File . Delete ( options [ "dbpath" ]);
2013-02-12 21:43:14 +00:00
if ( string . IsNullOrEmpty ( target ))
{
target = "file://" + tf ;
}
else
{
Console . WriteLine ( "Removing old backups" );
Dictionary < string , string > tmp = new Dictionary < string , string >( options );
2013-05-15 21:50:16 +02:00
tmp [ "keep-versions" ] = "0" ;
2013-02-12 21:43:14 +00:00
tmp [ "force" ] = "" ;
tmp [ "allow-full-removal" ] = "" ;
using ( new Timer ( "Cleaning up any existing backups" ))
2013-08-18 23:16:58 +02:00
using ( var i = new Duplicati . Library . Main . Controller ( target , tmp , new ConsoleOutput ( options )))
2013-05-08 21:29:59 +02:00
i . Delete ();
2013-02-12 21:43:14 +00:00
}
log . Backupset = "Backup " + folders [ 0 ];
2013-03-15 21:14:57 +01:00
string fhtempsource = null ;
2013-03-16 12:07:06 +01:00
2013-05-05 17:54:59 +02:00
bool usingFHWithRestore = (! skipfullrestore || ! skippartialrestore );
2013-03-09 13:25:04 +01:00
2013-03-16 12:07:06 +01:00
using ( var fhsourcefolder = usingFHWithRestore ? new Library . Utility . TempFolder () : null )
2013-02-12 21:43:14 +00:00
{
2013-03-16 12:07:06 +01:00
if ( usingFHWithRestore )
2013-04-25 22:34:29 +02:00
{
2013-03-15 21:14:57 +01:00
fhtempsource = fhsourcefolder ;
CopyDirectoryRecursive ( folders [ 0 ], fhsourcefolder );
2013-04-25 22:34:29 +02:00
}
2013-03-16 12:07:06 +01:00
RunBackup ( usingFHWithRestore ? ( string ) fhsourcefolder : folders [ 0 ], target , options , folders [ 0 ]);
2013-03-15 21:14:57 +01:00
for ( int i = 1 ; i < folders . Length ; i ++)
{
//options["passphrase"] = "bad password";
//If the backups are too close, we can't pick the right one :(
System . Threading . Thread . Sleep ( 1000 * 5 );
log . Backupset = "Backup " + folders [ i ];
2013-03-16 12:07:06 +01:00
if ( usingFHWithRestore )
2013-03-15 21:14:57 +01:00
{
System . IO . Directory . Delete ( fhsourcefolder , true );
CopyDirectoryRecursive ( folders [ i ], fhsourcefolder );
}
//Call function to simplify profiling
2013-03-16 12:07:06 +01:00
RunBackup ( usingFHWithRestore ? ( string ) fhsourcefolder : folders [ i ], target , options , folders [ i ]);
2013-03-15 21:14:57 +01:00
}
2013-02-12 21:43:14 +00:00
}
Duplicati . Library . Main . Options opts = new Duplicati . Library . Main . Options ( options );
using ( Duplicati . Library . Interface . IBackend bk = Duplicati . Library . DynamicLoader . BackendLoader . GetBackend ( target , options ))
foreach ( Duplicati . Library . Interface . IFileEntry fe in bk . List ())
if ( fe . Size > opts . VolumeSize )
{
string msg = string . Format ( "The file {0} is {1} bytes larger than allowed" , fe . Name , fe . Size - opts . VolumeSize );
Console . WriteLine ( msg );
Log . WriteMessage ( msg , LogMessageType . Error );
}
IList < DateTime > entries ;
2013-08-18 23:16:58 +02:00
using ( var i = new Duplicati . Library . Main . Controller ( target , options , new ConsoleOutput ( options )))
2013-05-30 21:54:43 +02:00
entries = ( from n in i . List (). Filesets select n . Time . ToLocalTime ()). ToList ();
2013-02-12 21:43:14 +00:00
2013-05-05 17:54:59 +02:00
if ( entries . Count != folders . Length )
2013-02-12 21:43:14 +00:00
{
2013-05-05 17:54:59 +02:00
StringBuilder sb = new StringBuilder ();
sb . AppendLine ( "Entry count: " + entries . Count . ToString ());
sb . Append ( string . Format ( "Found {0} filelists but there were {1} source folders" , entries . Count , folders . Length ));
throw new Exception ( "Filename parsing problem, or corrupt storage: " + sb . ToString ());
2013-02-12 21:43:14 +00:00
}
2013-03-16 12:07:06 +01:00
if (! skipfullrestore || ! skippartialrestore )
2013-02-12 21:43:14 +00:00
{
2013-03-16 12:07:06 +01:00
for ( int i = 0 ; i < entries . Count ; i ++)
2013-02-12 21:43:14 +00:00
{
2013-03-16 12:07:06 +01:00
using ( TempFolder ttf = new TempFolder ())
2013-02-12 21:43:14 +00:00
{
2013-03-16 12:07:06 +01:00
log . Backupset = "Restore " + folders [ i ];
Console . WriteLine ( "Restoring the copy: " + folders [ i ]);
2013-05-11 12:03:15 +02:00
options [ "time" ] = entries [ entries . Count - i - 1 ]. ToString ();
2013-03-16 12:07:06 +01:00
2013-05-15 21:50:16 +02:00
string [] actualfolders = folders [ i ]. Split ( System . IO . Path . PathSeparator );
2013-03-16 12:07:06 +01:00
if (! skippartialrestore )
{
Console . WriteLine ( "Partial restore of: " + folders [ i ]);
using ( TempFolder ptf = new TempFolder ())
{
List < string > testfiles = new List < string >();
using ( new Timer ( "Extract list of files from" + folders [ i ]))
2013-02-12 21:43:14 +00:00
{
2013-05-11 12:03:15 +02:00
List < string > sourcefiles ;
2013-08-18 23:16:58 +02:00
using ( var inst = new Library . Main . Controller ( target , options , new ConsoleOutput ( options )))
2013-05-11 12:03:15 +02:00
sourcefiles = ( from n in inst . List ( "*" ). Files select n . Path ). ToList ();
2013-03-16 12:07:06 +01:00
//Remove all folders from list
for ( int j = 0 ; j < sourcefiles . Count ; j ++)
if ( sourcefiles [ j ]. EndsWith ( System . IO . Path . DirectorySeparatorChar . ToString ()))
{
sourcefiles . RemoveAt ( j );
j --;
}
int testfilecount = 15 ;
Random r = new Random ();
while ( testfilecount -- > 0 && sourcefiles . Count > 0 )
{
int rn = r . Next ( 0 , sourcefiles . Count );
testfiles . Add ( sourcefiles [ rn ]);
sourcefiles . RemoveAt ( rn );
}
}
//Add all folders to avoid warnings in restore log
int c = testfiles . Count ;
Dictionary < string , string > partialFolders = new Dictionary < string , string >( Utility . ClientFilenameStringComparer );
for ( int j = 0 ; j < c ; j ++)
{
string f = testfiles [ j ];
2013-05-05 17:54:59 +02:00
if (! f . StartsWith ( usingFHWithRestore ? fhtempsource : folders [ i ], Utility . ClientFilenameStringComparision ))
throw new Exception ( string . Format ( "Unexpected file found: {0}, path is not a subfolder for {1}" , f , folders [ i ]));
2013-03-16 12:07:06 +01:00
2013-05-05 17:54:59 +02:00
f = f . Substring ( Utility . AppendDirSeparator ( usingFHWithRestore ? fhtempsource : folders [ i ]). Length );
2013-03-16 12:07:06 +01:00
do
{
f = System . IO . Path . GetDirectoryName ( f );
partialFolders [ Utility . AppendDirSeparator ( f )] = null ;
} while ( f . IndexOf ( System . IO . Path . DirectorySeparatorChar ) > 0 );
}
if ( partialFolders . ContainsKey ( "" ))
partialFolders . Remove ( "" );
if ( partialFolders . ContainsKey ( System . IO . Path . DirectorySeparatorChar . ToString ()))
partialFolders . Remove ( System . IO . Path . DirectorySeparatorChar . ToString ());
List < string > filterlist ;
2013-05-05 17:54:59 +02:00
var tfe = Utility . AppendDirSeparator ( usingFHWithRestore ? fhtempsource : folders [ i ]);
filterlist = ( from n in partialFolders . Keys
where ! string . IsNullOrWhiteSpace ( n ) && n != System . IO . Path . DirectorySeparatorChar . ToString ()
select Utility . AppendDirSeparator ( System . IO . Path . Combine ( tfe , n )))
. Union ( testfiles ) //Add files with full path
. Union ( new string [] { tfe }) //Ensure root folder is included
. Distinct ()
. ToList ();
testfiles = ( from n in testfiles select n . Substring ( tfe . Length )). ToList ();
2013-05-15 21:50:16 +02:00
2013-03-16 12:07:06 +01:00
//Call function to simplify profiling
2013-05-15 21:50:16 +02:00
RunPartialRestore ( folders [ i ], target , ptf , options , filterlist . ToArray ());
2013-03-16 12:07:06 +01:00
if (! skipverify )
{
//Call function to simplify profiling
Console . WriteLine ( "Verifying partial restore of: " + folders [ i ]);
2015-01-25 13:42:25 +01:00
VerifyPartialRestore ( folders [ i ], testfiles , actualfolders , ptf , folders [ 0 ], verifymetadata );
2013-02-12 21:43:14 +00:00
}
}
}
2013-03-16 12:07:06 +01:00
if (! skipfullrestore )
2013-02-12 21:43:14 +00:00
{
2013-03-16 12:07:06 +01:00
//Call function to simplify profiling
2013-05-15 21:50:16 +02:00
RunRestore ( folders [ i ], target , ttf , options );
2013-03-16 12:07:06 +01:00
if (! skipverify )
2013-02-12 21:43:14 +00:00
{
2013-03-16 12:07:06 +01:00
//Call function to simplify profiling
Console . WriteLine ( "Verifying the copy: " + folders [ i ]);
2015-01-25 13:42:25 +01:00
VerifyFullRestore ( folders [ i ], actualfolders , new string [] { ttf }, verifymetadata );
2013-02-12 21:43:14 +00:00
}
}
}
2013-03-09 13:25:04 +01:00
}
2013-03-16 12:07:06 +01:00
}
2013-04-16 22:36:19 +02:00
foreach ( string s in Utility . EnumerateFiles ( tempdir ))
{
2013-05-11 12:03:15 +02:00
if ( s == options [ "dbpath" ])
2013-04-16 22:36:19 +02:00
continue ;
if ( s . StartsWith ( Utility . AppendDirSeparator ( tf )))
continue ;
Log . WriteMessage ( string . Format ( "Found left-over temp file: {0}" , s . Substring ( tempdir . Length )), LogMessageType . Warning );
Console . WriteLine ( "Found left-over temp file: {0} -> {1}" , s . Substring ( tempdir . Length ), TempFile . GetStackTraceForTempFile ( System . IO . Path . GetFileName ( s )));
}
foreach ( string s in Utility . EnumerateFolders ( tempdir ))
if (! s . StartsWith ( Utility . AppendDirSeparator ( tf )) && Utility . AppendDirSeparator ( s ) != Utility . AppendDirSeparator ( tf ) && Utility . AppendDirSeparator ( s ) != Utility . AppendDirSeparator ( tempdir ))
{
Log . WriteMessage ( string . Format ( "Found left-over temp folder: {0}" , s . Substring ( tempdir . Length )), LogMessageType . Warning );
Console . WriteLine ( "Found left-over temp folder: {0}" , s . Substring ( tempdir . Length ));
}
2013-03-09 13:25:04 +01:00
}
( Log . CurrentLog as StreamLog ). Dispose ();
Log . CurrentLog = null ;
2013-04-16 22:36:37 +02:00
if ( LogHelper . ErrorCount > 0 )
Console . WriteLine ( "Unittest completed, but with {0} errors, see logfile for details" , LogHelper . ErrorCount );
else if ( LogHelper . WarningCount > 0 )
Console . WriteLine ( "Unittest completed, but with {0} warnings, see logfile for details" , LogHelper . WarningCount );
else
Console . WriteLine ( "Unittest completed successfully - Have some cake!" );
2013-03-09 13:25:04 +01:00
}
2015-01-25 13:42:25 +01:00
private static void VerifyPartialRestore ( string source , IEnumerable < string > testfiles , string [] actualfolders , string tempfolder , string rootfolder , bool verifymetadata )
2013-03-09 13:25:04 +01:00
{
using ( new Timer ( "Verification of partial restore from " + source ))
foreach ( string s in testfiles )
{
string restoredname ;
string sourcename ;
if ( actualfolders . Length == 1 )
{
sourcename = System . IO . Path . Combine ( actualfolders [ 0 ], s );
restoredname = System . IO . Path . Combine ( tempfolder , s );
}
else
{
int six = s . IndexOf ( System . IO . Path . DirectorySeparatorChar );
sourcename = System . IO . Path . Combine ( actualfolders [ int . Parse ( s . Substring ( 0 , six ))], s . Substring ( six + 1 ));
restoredname = System . IO . Path . Combine ( System . IO . Path . Combine ( tempfolder , System . IO . Path . GetFileName ( rootfolder . Split ( System . IO . Path . PathSeparator )[ int . Parse ( s . Substring ( 0 , six ))])), s . Substring ( six + 1 ));
}
2013-02-12 21:43:14 +00:00
2013-03-09 13:25:04 +01:00
if (! System . IO . File . Exists ( restoredname ))
{
Log . WriteMessage ( "Partial restore missing file: " + restoredname , LogMessageType . Error );
Console . WriteLine ( "Partial restore missing file: " + restoredname );
}
else
{
if (! System . IO . File . Exists ( sourcename ))
2013-02-12 21:43:14 +00:00
{
2013-03-09 13:25:04 +01:00
Log . WriteMessage ( "Partial restore missing file: " + sourcename , LogMessageType . Error );
Console . WriteLine ( "Partial restore missing file: " + sourcename );
throw new Exception ( "Unittest is broken" );
}
2015-01-25 13:42:25 +01:00
if (! CompareFiles ( sourcename , restoredname , s , verifymetadata ))
2013-03-09 13:25:04 +01:00
{
Log . WriteMessage ( "Partial restore file differs: " + s , LogMessageType . Error );
Console . WriteLine ( "Partial restore file differs: " + s );
2013-02-12 21:43:14 +00:00
}
}
}
2013-03-09 13:25:04 +01:00
}
2013-02-12 21:43:14 +00:00
2015-01-25 13:42:25 +01:00
private static void VerifyFullRestore ( string source , string [] actualfolders , string [] restorefoldernames , bool verifymetadata )
2013-03-09 13:25:04 +01:00
{
using ( new Timer ( "Verification of " + source ))
{
for ( int j = 0 ; j < actualfolders . Length ; j ++)
2015-01-25 13:42:25 +01:00
VerifyDir ( actualfolders [ j ], restorefoldernames [ j ], verifymetadata );
2013-02-12 21:43:14 +00:00
}
2013-03-09 13:25:04 +01:00
}
2013-02-12 21:43:14 +00:00
2013-03-15 21:14:57 +01:00
private static void RunBackup ( string source , string target , Dictionary < string , string > options , string sourcename )
2013-03-09 13:25:04 +01:00
{
2013-05-15 21:50:16 +02:00
Console . WriteLine ( "Backing up the copy: " + sourcename );
using ( new Timer ( "Backup of " + sourcename ))
2013-08-18 23:16:58 +02:00
using ( var i = new Duplicati . Library . Main . Controller ( target , options , new ConsoleOutput ( options )))
2013-05-13 22:32:05 +02:00
Log . WriteMessage ( i . Backup ( source . Split ( System . IO . Path . PathSeparator )). ToString (), LogMessageType . Information );
2013-03-09 13:25:04 +01:00
}
2013-05-15 21:50:16 +02:00
private static void RunRestore ( string source , string target , string tempfolder , Dictionary < string , string > options )
2013-03-09 13:25:04 +01:00
{
2013-05-15 21:50:16 +02:00
var tops = new Dictionary < string , string >( options );
tops [ "restore-path" ] = tempfolder ;
2013-03-09 13:25:04 +01:00
using ( new Timer ( "Restore of " + source ))
2013-08-18 23:16:58 +02:00
using ( var i = new Duplicati . Library . Main . Controller ( target , tops , new ConsoleOutput ( options )))
2013-05-25 16:40:15 +02:00
Log . WriteMessage ( i . Restore ( null ). ToString (), LogMessageType . Information );
2013-03-09 13:25:04 +01:00
}
2013-05-15 21:50:16 +02:00
private static void RunPartialRestore ( string source , string target , string tempfolder , Dictionary < string , string > options , string [] files )
2013-03-09 13:25:04 +01:00
{
2013-05-15 21:50:16 +02:00
var tops = new Dictionary < string , string >( options );
tops [ "restore-path" ] = tempfolder ;
2013-03-09 13:25:04 +01:00
using ( new Timer ( "Partial restore of " + source ))
2013-08-18 23:16:58 +02:00
using ( var i = new Duplicati . Library . Main . Controller ( target , tops , new ConsoleOutput ( options )))
2013-05-25 16:40:15 +02:00
Log . WriteMessage ( i . Restore ( files ). ToString (), LogMessageType . Information );
2013-02-12 21:43:14 +00:00
}
/// <summary>
/// Returns the index of a given string, using the file system case sensitivity
/// </summary>
/// <returns>The index of the entry or -1 if no entry was found</returns>
/// <param name='lst'>The list to search</param>
/// <param name='m'>The string to find</param>
private static int IndexOf ( List < string > lst , string m )
{
StringComparison sc = Duplicati . Library . Utility . Utility . ClientFilenameStringComparision ;
for ( int i = 0 ; i < lst . Count ; i ++)
if ( lst [ i ]. Equals ( m , sc ))
return i ;
return - 1 ;
}
/// <summary>
/// Verifies the existence of all files and folders, and ensures that all
/// files are binary equal.
/// </summary>
/// <param name="f1">One folder</param>
/// <param name="f2">Another folder</param>
2015-01-25 13:42:25 +01:00
private static void VerifyDir ( string f1 , string f2 , bool verifymetadata )
2013-02-12 21:43:14 +00:00
{
f1 = Utility . AppendDirSeparator ( f1 );
f2 = Utility . AppendDirSeparator ( f2 );
2013-05-13 22:32:05 +02:00
var folders1 = Utility . EnumerateFolders ( f1 );
var folders2 = Utility . EnumerateFolders ( f2 ). ToList ();
2013-02-12 21:43:14 +00:00
foreach ( string s in folders1 )
{
string relpath = s . Substring ( f1 . Length );
string target = System . IO . Path . Combine ( f2 , relpath );
int ix = IndexOf ( folders2 , target );
if ( ix < 0 )
{
Log . WriteMessage ( "Missing folder: " + relpath , LogMessageType . Error );
Console . WriteLine ( "Missing folder: " + relpath );
}
else
folders2 . RemoveAt ( ix );
}
foreach ( string s in folders2 )
{
Log . WriteMessage ( "Extra folder: " + s . Substring ( f2 . Length ), LogMessageType . Error );
Console . WriteLine ( "Extra folder: " + s . Substring ( f2 . Length ));
}
2013-05-13 22:32:05 +02:00
var files1 = Utility . EnumerateFiles ( f1 );
var files2 = Utility . EnumerateFiles ( f2 ). ToList ();
2013-02-12 21:43:14 +00:00
foreach ( string s in files1 )
{
string relpath = s . Substring ( f1 . Length );
string target = System . IO . Path . Combine ( f2 , relpath );
int ix = IndexOf ( files2 , target );
if ( ix < 0 )
{
Log . WriteMessage ( "Missing file: " + relpath , LogMessageType . Error );
Console . WriteLine ( "Missing file: " + relpath );
}
else
{
files2 . RemoveAt ( ix );
2015-01-25 13:42:25 +01:00
if (! CompareFiles ( s , target , relpath , verifymetadata ))
2013-02-12 21:43:14 +00:00
{
Log . WriteMessage ( "File differs: " + relpath , LogMessageType . Error );
Console . WriteLine ( "File differs: " + relpath );
}
}
}
foreach ( string s in files2 )
{
Log . WriteMessage ( "Extra file: " + s . Substring ( f2 . Length ), LogMessageType . Error );
Console . WriteLine ( "Extra file: " + s . Substring ( f2 . Length ));
}
}
/// <summary>
/// Compares two files by reading all bytes, and comparing one by one
/// </summary>
/// <param name="f1">One file</param>
/// <param name="f2">Another file</param>
/// <param name="display">File display name</param>
/// <returns>True if they are equal, false otherwise</returns>
2015-01-25 13:42:25 +01:00
private static bool CompareFiles ( string f1 , string f2 , string display , bool verifymetadata )
2013-02-12 21:43:14 +00:00
{
using ( System . IO . FileStream fs1 = System . IO . File . OpenRead ( f1 ))
using ( System . IO . FileStream fs2 = System . IO . File . OpenRead ( f2 ))
if ( fs1 . Length != fs2 . Length )
{
Log . WriteMessage ( "Lengths differ: " + display + ", " + fs1 . Length . ToString () + " vs. " + fs2 . Length . ToString (), LogMessageType . Error );
Console . WriteLine ( "Lengths differ: " + display + ", " + fs1 . Length . ToString () + " vs. " + fs2 . Length . ToString ());
return false ;
}
else
{
long len = fs1 . Length ;
for ( long l = 0 ; l < len ; l ++)
if ( fs1 . ReadByte () != fs2 . ReadByte ())
{
Log . WriteMessage ( "Mismatch in byte " + l . ToString () + " in file " + display , LogMessageType . Error );
Console . WriteLine ( "Mismatch in byte " + l . ToString () + " in file " + display );
return false ;
}
}
2015-01-25 13:42:25 +01:00
if ( verifymetadata )
{
if ( System . IO . File . GetLastWriteTime ( f1 ) != System . IO . File . GetLastWriteTime ( f2 ))
{
Log . WriteMessage ( "Mismatch in lastmodified for " + f2 + ", " + System . IO . File . GetLastWriteTimeUtc ( f1 ) + " vs. " + System . IO . File . GetLastWriteTimeUtc ( f2 ), LogMessageType . Warning );
Console . WriteLine ( "Mismatch in lastmodified for " + f2 + ", " + System . IO . File . GetLastWriteTimeUtc ( f1 ) + " vs. " + System . IO . File . GetLastWriteTimeUtc ( f2 ));
}
if ( System . IO . File . GetCreationTimeUtc ( f1 ) != System . IO . File . GetCreationTimeUtc ( f2 ))
{
Log . WriteMessage ( "Mismatch in create-time for " + f2 + ", " + System . IO . File . GetCreationTimeUtc ( f1 ) + " vs. " + System . IO . File . GetCreationTimeUtc ( f2 ), LogMessageType . Warning );
Console . WriteLine ( "Mismatch in create-time for " + f2 + ", " + System . IO . File . GetCreationTimeUtc ( f1 ) + " vs. " + System . IO . File . GetCreationTimeUtc ( f2 ));
}
}
2013-02-12 21:43:14 +00:00
return true ;
}
2013-03-15 21:14:57 +01:00
/// <summary>
/// Recursively copy a directory to another location.
/// </summary>
/// <param name="source">Source directory path</param>
/// <param name="dest">Destination directory path</param>
private static void CopyDirectoryRecursive ( string source , string dest )
{
if (! System . IO . Directory . Exists ( dest ))
System . IO . Directory . CreateDirectory ( dest );
2015-01-25 13:42:25 +01:00
foreach ( var file in System . IO . Directory . GetFiles ( source ))
{
var t = System . IO . Path . Combine ( dest , System . IO . Path . GetFileName ( file ));
System . IO . File . Copy ( file , t , false );
System . IO . File . SetCreationTimeUtc ( t , System . IO . File . GetCreationTimeUtc ( file ));
System . IO . File . SetLastWriteTimeUtc ( t , System . IO . File . GetLastWriteTimeUtc ( file ));
}
2013-03-15 21:14:57 +01:00
2015-01-25 13:42:25 +01:00
foreach ( var folder in System . IO . Directory . GetDirectories ( source ))
{
var t = System . IO . Path . Combine ( dest , System . IO . Path . GetFileName ( folder ));
CopyDirectoryRecursive ( folder , t );
System . IO . Directory . SetCreationTimeUtc ( t , System . IO . Directory . GetCreationTimeUtc ( folder ));
System . IO . Directory . SetLastWriteTimeUtc ( t , System . IO . Directory . GetLastWriteTimeUtc ( folder ));
}
2013-03-15 21:14:57 +01:00
}
2013-02-12 21:43:14 +00:00
}
}
#endif