2013-03-26 19:27:26 +01:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
2013-05-08 20:17:07 +02:00
using Duplicati.Library.Main.Database ;
using Duplicati.Library.Main.Volumes ;
2013-03-26 19:27:26 +01:00
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Operation
2013-03-26 19:27:26 +01:00
{
2013-05-25 16:40:15 +02:00
internal class RestoreHandler
{
2013-03-26 19:27:26 +01:00
private string m_backendurl ;
2013-05-06 09:58:19 +02:00
private Options m_options ;
2013-03-26 19:27:26 +01:00
private byte [] m_blockbuffer ;
2013-05-25 16:40:15 +02:00
private RestoreResults m_result ;
2014-03-18 10:59:06 +01:00
private static readonly Snapshots . ISystemIO m_systemIO = Duplicati . Library . Utility . Utility . IsClientLinux ? ( Snapshots . ISystemIO ) new Snapshots . SystemIOLinux () : ( Snapshots . ISystemIO ) new Snapshots . SystemIOWindows ();
2013-03-26 19:27:26 +01:00
2013-05-25 16:40:15 +02:00
public RestoreHandler ( string backendurl , Options options , RestoreResults result )
2013-03-08 22:24:54 +01:00
{
2013-03-26 19:27:26 +01:00
m_options = options ;
m_backendurl = backendurl ;
2013-05-25 16:40:15 +02:00
m_result = result ;
2013-03-26 19:27:26 +01:00
2013-05-08 19:57:13 +02:00
m_blockbuffer = new byte [ m_options . Blocksize ];
2013-03-26 19:27:26 +01:00
}
/// <summary>
/// Gets the compression module by parsing the filename
/// </summary>
/// <param name="filename">The filename to parse</param>
/// <returns>The compression module</returns>
public static string GetCompressionModule ( string filename )
{
var tmp = VolumeBase . ParseFilename ( filename );
if ( tmp == null )
throw new Exception ( string . Format ( "Unable to parse filename to valid entry: {0}" , filename ));
return tmp . CompressionModule ;
}
2013-05-11 12:03:15 +02:00
public static RecreateDatabaseHandler . NumberedFilterFilelistDelegate FilterNumberedFilelist ( DateTime time , long [] versions )
{
if ( time . Kind == DateTimeKind . Unspecified )
throw new Exception ( "Unspecified datetime instance, must be either local or UTC" );
// Make sure the resolution is the same (i.e. no milliseconds)
time = Library . Utility . Utility . DeserializeDateTime ( Library . Utility . Utility . SerializeDateTime ( time )). ToUniversalTime ();
return
_lst =>
{
// Unwrap, so we do not query the remote storage twice
var lst = ( from n in _lst
2013-06-26 21:52:59 +02:00
where n . FileType == RemoteVolumeType . Files
2013-05-11 12:03:15 +02:00
orderby n . Time descending
select n ). ToArray ();
2013-06-26 21:52:59 +02:00
var numbers = lst . Zip ( Enumerable . Range ( 0 , lst . Length ), ( a , b ) => new KeyValuePair < long , IParsedVolume >( b , a )). ToList ();
2013-05-11 12:03:15 +02:00
if ( time . Ticks > 0 && versions != null && versions . Length > 0 )
return from n in numbers
2013-06-26 21:52:59 +02:00
where n . Value . Time <= time && versions . Contains ( n . Key )
2013-05-11 12:03:15 +02:00
select n ;
else if ( time . Ticks > 0 )
return from n in numbers
where n . Value . Time <= time
select n ;
else if ( versions != null && versions . Length > 0 )
return from n in numbers
where versions . Contains ( n . Key )
select n ;
else
return numbers ;
};
}
2013-05-13 22:32:05 +02:00
public void Run ( string [] paths , Library . Utility . IFilter filter = null )
2013-08-22 20:52:54 +02:00
{
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_Begin );
2013-06-29 11:41:53 +02:00
// If we have both target paths and a filter, combine into a single filter
2013-08-22 20:52:54 +02:00
filter = Library . Utility . JoinedFilterExpression . Join ( new Library . Utility . FilterExpression ( paths ), filter );
2013-05-13 22:32:05 +02:00
2014-03-18 10:59:06 +01:00
if (! m_options . NoLocalDb && m_systemIO . FileExists ( m_options . Dbpath ))
2013-05-13 22:32:05 +02:00
{
2013-08-22 20:52:54 +02:00
using ( var db = new LocalRestoreDatabase ( m_options . Dbpath , m_options . Blocksize ))
{
db . SetResult ( m_result );
DoRun ( db , filter , m_result );
2014-07-16 00:57:57 +02:00
db . WriteResults ();
2013-08-22 20:52:54 +02:00
}
2013-05-25 16:40:15 +02:00
2013-05-13 22:32:05 +02:00
return ;
}
2013-05-25 16:40:15 +02:00
2013-08-22 20:52:54 +02:00
2013-05-29 22:15:13 +02:00
m_result . AddMessage ( "No local database, building a temporary database" );
2013-08-22 20:52:54 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_RecreateDatabase );
2013-03-26 19:27:26 +01:00
2014-01-19 23:25:38 +01:00
using ( var tmpdb = new Library . Utility . TempFile ())
2013-03-26 19:27:26 +01:00
{
2013-05-13 22:32:05 +02:00
RecreateDatabaseHandler . NumberedFilterFilelistDelegate filelistfilter = FilterNumberedFilelist ( m_options . Time , m_options . Version );
2013-03-26 19:27:26 +01:00
// Simultaneously with downloading blocklists, we patch as much as we can from the blockvolumes
// This prevents repeated downloads, except for cases where the blocklists refer blocks
// that have been previously handled. A local blockvolume cache can reduce this issue
2014-01-19 23:25:38 +01:00
using ( var database = new LocalRestoreDatabase ( tmpdb , m_options . Blocksize ))
2013-03-26 19:27:26 +01:00
{
2013-05-08 19:57:13 +02:00
var blockhasher = System . Security . Cryptography . HashAlgorithm . Create ( m_options . BlockHashAlgorithm );
2013-06-01 14:22:10 +02:00
var filehasher = System . Security . Cryptography . HashAlgorithm . Create ( m_options . FileHashAlgorithm );
if ( blockhasher == null )
throw new Exception ( string . Format ( Strings . Foresthash . InvalidHashAlgorithm , m_options . BlockHashAlgorithm ));
2013-03-27 16:06:45 +01:00
if (! blockhasher . CanReuseTransform )
2013-05-08 19:57:13 +02:00
throw new Exception ( string . Format ( Strings . Foresthash . InvalidCryptoSystem , m_options . BlockHashAlgorithm ));
2013-06-01 14:22:10 +02:00
if ( filehasher == null )
throw new Exception ( string . Format ( Strings . Foresthash . InvalidHashAlgorithm , m_options . FileHashAlgorithm ));
if (! filehasher . CanReuseTransform )
throw new Exception ( string . Format ( Strings . Foresthash . InvalidCryptoSystem , m_options . FileHashAlgorithm ));
2013-03-26 19:27:26 +01:00
bool first = true ;
RecreateDatabaseHandler . BlockVolumePostProcessor localpatcher =
( key , rd ) =>
{
if ( first )
{
//Figure out what files are to be patched, and what blocks are needed
2013-05-25 16:40:15 +02:00
PrepareBlockAndFileList ( database , m_options , filter , m_result );
2013-03-26 19:27:26 +01:00
// Don't run this again
first = false ;
}
else
{
// Patch the missing blocks list to include the newly discovered blocklists
//UpdateMissingBlocksTable(key);
}
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2013-05-25 16:40:15 +02:00
CreateDirectoryStructure ( database , m_options , m_result );
2013-03-26 19:27:26 +01:00
//If we are patching an existing target folder, do not touch stuff that is already updated
2013-06-01 14:22:10 +02:00
ScanForExistingTargetBlocks ( database , m_blockbuffer , blockhasher , filehasher , m_options , m_result );
2013-03-26 19:27:26 +01:00
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2013-03-26 19:27:26 +01:00
#if DEBUG
if (! m_options . NoLocalBlocks )
#endif
// If other local files already have the blocks we want, we use them instead of downloading
2013-05-25 16:40:15 +02:00
ScanForExistingSourceBlocks ( database , m_options , m_blockbuffer , blockhasher , m_result );
2013-03-26 19:27:26 +01:00
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2013-03-26 19:27:26 +01:00
//Update files with data
2013-05-25 16:40:15 +02:00
PatchWithBlocklist ( database , rd , m_options , m_result , m_blockbuffer );
2013-03-26 19:27:26 +01:00
};
2014-01-19 23:26:14 +01:00
// TODO: When UpdateMissingBlocksTable is implemented, the localpatcher can be activated
2013-03-26 19:27:26 +01:00
// and this will reduce the need for multiple downloads of the same volume
2013-04-04 20:34:26 +02:00
// TODO: This will need some work to preserve the missing block list for use with --fh-dryrun
2013-05-25 16:40:15 +02:00
m_result . RecreateDatabaseResults = new RecreateDatabaseResults ( m_result );
2013-05-30 22:27:03 +02:00
using ( new Logging . Timer ( "Recreate temporary database for restore" ))
new RecreateDatabaseHandler ( m_backendurl , m_options , ( RecreateDatabaseResults ) m_result . RecreateDatabaseResults )
2014-01-19 23:26:02 +01:00
. DoRun ( database , filter , filelistfilter , /*localpatcher*/ null );
2013-04-04 20:34:26 +02:00
2014-01-19 23:26:59 +01:00
//If we have --version set, we need to adjust, as the db has only the required versions
//TODO: Bit of a hack to set options that way
if ( m_options . Version != null && m_options . Version . Length > 0 )
m_options . RawOptions [ "version" ] = string . Join ( "," , Enumerable . Range ( 0 , m_options . Version . Length ). Select ( x => x . ToString ()));
2013-05-25 16:40:15 +02:00
DoRun ( database , filter , m_result );
2013-03-26 19:27:26 +01:00
}
}
}
2013-08-23 22:18:13 +02:00
private static void PatchWithBlocklist ( LocalRestoreDatabase database , BlockVolumeReader blocks , Options options , RestoreResults result , byte [] blockbuffer )
2013-03-26 19:27:26 +01:00
{
2013-08-23 22:18:13 +02:00
var blocksize = options . Blocksize ;
2013-09-07 21:35:23 +02:00
var updateCounter = 0L ;
2013-08-23 22:18:13 +02:00
using ( var blockmarker = database . CreateBlockMarker ())
2013-03-26 19:27:26 +01:00
{
2013-08-23 22:18:13 +02:00
foreach ( var restorelist in database . GetFilesWithMissingBlocks ( blocks ))
2013-03-26 19:27:26 +01:00
{
2013-08-23 22:18:13 +02:00
var targetpath = restorelist . Path ;
result . AddVerboseMessage ( "Patching file with remote data: {0}" , targetpath );
if ( options . Dryrun )
{
result . AddDryrunMessage ( string . Format ( "Would patch file with remote data: {0}" , targetpath ));
}
else
{
try
{
2014-03-18 10:59:06 +01:00
var folderpath = m_systemIO . PathGetDirectoryName ( targetpath );
if (! options . Dryrun && ! m_systemIO . DirectoryExists ( folderpath ))
2013-08-23 22:18:13 +02:00
{
result . AddWarning ( string . Format ( "Creating missing folder {0} for file {1}" , folderpath , targetpath ), null );
2014-03-18 10:59:06 +01:00
m_systemIO . DirectoryCreate ( folderpath );
2013-08-23 22:18:13 +02:00
}
// TODO: Much faster if we iterate the volume and checks what blocks are used,
// because the compressors usually like sequential reading
2014-03-18 10:59:06 +01:00
using ( var file = m_systemIO . FileOpenReadWrite ( targetpath ))
2013-08-23 22:18:13 +02:00
foreach ( var targetblock in restorelist . Blocks )
{
file . Position = targetblock . Offset ;
var size = blocks . ReadBlock ( targetblock . Key , blockbuffer );
if ( targetblock . Size == size )
{
file . Write ( blockbuffer , 0 , size );
2013-08-23 23:38:06 +02:00
blockmarker . SetBlockRestored ( restorelist . FileID , targetblock . Offset / blocksize , targetblock . Key , size );
2013-08-23 22:18:13 +02:00
}
}
2013-09-07 21:35:23 +02:00
if ( updateCounter ++ % 20 == 0 )
2013-08-23 22:18:13 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
}
catch ( Exception ex )
2013-05-25 22:20:32 +02:00
{
2013-08-23 22:18:13 +02:00
result . AddWarning ( string . Format ( "Failed to patch file: \"{0}\", message: {1}, message: {1}" , targetpath , ex . Message ), ex );
2013-05-25 22:20:32 +02:00
}
2013-08-23 22:18:13 +02:00
try
{
ApplyMetadata ( targetpath , database );
}
catch ( Exception ex )
{
result . AddWarning ( string . Format ( "Failed to apply metadata to file: \"{0}\", message: {1}" , targetpath , ex . Message ), ex );
}
}
2013-03-26 19:27:26 +01:00
}
2013-08-23 22:18:13 +02:00
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2013-08-23 23:37:17 +02:00
blockmarker . Commit ( result );
2013-03-26 19:27:26 +01:00
}
}
2013-05-25 16:40:15 +02:00
private void DoRun ( LocalDatabase dbparent , Library . Utility . IFilter filter , RestoreResults result )
2013-08-22 20:52:54 +02:00
{
//In this case, we check that the remote storage fits with the database.
//We can then query the database and find the blocks that we need to do the restore
using ( var database = new LocalRestoreDatabase ( dbparent , m_options . Blocksize ))
using ( var backend = new BackendManager ( m_backendurl , m_options , result . BackendWriter , database ))
{
2014-01-19 23:26:59 +01:00
database . SetResult ( m_result );
2013-08-22 20:52:54 +02:00
Utility . VerifyParameters ( database , m_options );
2013-03-31 19:28:46 +02:00
2013-08-22 20:52:54 +02:00
var blockhasher = System . Security . Cryptography . HashAlgorithm . Create ( m_options . BlockHashAlgorithm );
var filehasher = System . Security . Cryptography . HashAlgorithm . Create ( m_options . FileHashAlgorithm );
if ( blockhasher == null )
throw new Exception ( string . Format ( Strings . Foresthash . InvalidHashAlgorithm , m_options . BlockHashAlgorithm ));
if (! blockhasher . CanReuseTransform )
throw new Exception ( string . Format ( Strings . Foresthash . InvalidCryptoSystem , m_options . BlockHashAlgorithm ));
if ( filehasher == null )
throw new Exception ( string . Format ( Strings . Foresthash . InvalidHashAlgorithm , m_options . FileHashAlgorithm ));
if (! filehasher . CanReuseTransform )
throw new Exception ( string . Format ( Strings . Foresthash . InvalidCryptoSystem , m_options . FileHashAlgorithm ));
if (! m_options . NoBackendverification )
{
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_PreRestoreVerify );
FilelistProcessor . VerifyRemoteList ( backend , m_options , database , result . BackendWriter );
}
//Figure out what files are to be patched, and what blocks are needed
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_CreateFileList );
using ( new Logging . Timer ( "PrepareBlockList" ))
PrepareBlockAndFileList ( database , m_options , filter , result );
//Make the entire output setup
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_CreateTargetFolders );
using ( new Logging . Timer ( "CreateDirectory" ))
CreateDirectoryStructure ( database , m_options , result );
2013-05-20 13:48:44 +02:00
2013-05-29 22:15:13 +02:00
//If we are patching an existing target folder, do not touch stuff that is already updated
2013-08-22 20:52:54 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_ScanForExistingFiles );
using ( new Logging . Timer ( "ScanForexistingTargetBlocks" ))
ScanForExistingTargetBlocks ( database , m_blockbuffer , blockhasher , filehasher , m_options , result );
2013-03-26 19:27:26 +01:00
2013-05-29 22:15:13 +02:00
//Look for existing blocks in the original source files only
2013-08-22 20:52:54 +02:00
using ( new Logging . Timer ( "ScanForExistingSourceBlocksFast" ))
2013-04-28 12:19:21 +02:00
#if DEBUG
2013-08-22 20:52:54 +02:00
if (! m_options . NoLocalBlocks && ! string . IsNullOrEmpty ( m_options . Restorepath ))
2013-05-21 21:15:03 +02:00
#else
2013-05-29 22:15:13 +02:00
if (! string . IsNullOrEmpty ( m_options . Restorepath ))
2013-04-28 12:19:21 +02:00
#endif
2013-08-22 20:52:54 +02:00
{
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_ScanForLocalBlocks );
ScanForExistingSourceBlocksFast ( database , m_options , m_blockbuffer , blockhasher , result );
}
2013-04-28 12:19:21 +02:00
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( database , null );
return ;
}
2013-08-22 20:52:54 +02:00
// If other local files already have the blocks we want, we use them instead of downloading
if ( m_options . PatchWithLocalBlocks )
{
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_PatchWithLocalBlocks );
2013-05-29 22:15:13 +02:00
using ( new Logging . Timer ( "PatchWithLocalBlocks" ))
2013-08-22 20:52:54 +02:00
ScanForExistingSourceBlocks ( database , m_options , m_blockbuffer , blockhasher , result );
}
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( database , null );
return ;
}
2013-08-22 20:52:54 +02:00
// Fill BLOCKS with remote sources
var volumes = database . GetMissingVolumes (). ToList ();
2013-03-26 19:27:26 +01:00
2013-08-22 20:52:54 +02:00
if ( volumes . Count > 0 )
{
m_result . AddMessage ( string . Format ( "{0} remote files are required to restore" , volumes . Count ));
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_DownloadingRemoteFiles );
}
2013-03-26 19:27:26 +01:00
2013-08-23 22:18:13 +02:00
var brokenFiles = new List < string >();
2013-05-20 15:02:36 +02:00
foreach ( var blockvolume in new AsyncDownloader ( volumes , backend ))
try
{
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( database , null );
return ;
}
2013-05-20 15:02:36 +02:00
using ( var tmpfile = blockvolume . TempFile )
using ( var blocks = new BlockVolumeReader ( GetCompressionModule ( blockvolume . Name ), tmpfile , m_options ))
2013-05-25 16:40:15 +02:00
PatchWithBlocklist ( database , blocks , m_options , result , m_blockbuffer );
2013-05-20 15:02:36 +02:00
}
catch ( Exception ex )
{
2013-08-23 22:18:13 +02:00
brokenFiles . Add ( blockvolume . Name );
2013-05-25 16:40:15 +02:00
result . AddError ( string . Format ( "Failed to patch with remote file: \"{0}\", message: {1}" , blockvolume . Name , ex . Message ), ex );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2013-05-20 15:02:36 +02:00
}
2013-08-23 22:18:13 +02:00
2013-06-09 15:24:31 +02:00
// Reset the filehasher if it was used to verify existing files
filehasher . Initialize ();
2013-03-27 16:06:45 +01:00
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2013-08-22 20:52:54 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_PostRestoreVerify );
2013-08-23 22:18:13 +02:00
var fileErrors = 0L ;
2013-05-20 13:48:44 +02:00
// After all blocks in the files are restored, verify the file hash
2013-05-29 22:15:13 +02:00
using ( new Logging . Timer ( "RestoreVerification" ))
foreach ( var file in database . GetFilesToRestore ())
2013-03-26 19:27:26 +01:00
{
2013-05-29 22:15:13 +02:00
try
{
2014-05-15 12:47:16 +02:00
if ( m_result . TaskControlRendevouz () == TaskControlState . Stop )
{
backend . WaitForComplete ( database , null );
return ;
}
2013-05-29 22:15:13 +02:00
result . AddVerboseMessage ( "Testing restored file integrity: {0}" , file . Path );
string key ;
long size ;
2014-03-18 10:59:06 +01:00
using ( var fs = m_systemIO . FileOpenRead ( file . Path ))
2013-05-29 22:15:13 +02:00
{
size = fs . Length ;
key = Convert . ToBase64String ( filehasher . ComputeHash ( fs ));
}
if ( key != file . Hash )
throw new Exception ( string . Format ( "Failed to restore file: \"{0}\". File hash is {1}, expected hash is {2}" , file . Path , key , file . Hash ));
result . FilesRestored ++;
result . SizeOfRestoredFiles += size ;
}
catch ( Exception ex )
2013-05-25 16:40:15 +02:00
{
2013-08-23 22:18:13 +02:00
fileErrors ++;
2013-08-22 20:52:54 +02:00
result . AddWarning ( ex . Message , ex );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2013-05-25 16:40:15 +02:00
}
2013-03-26 19:27:26 +01:00
}
2013-08-23 22:18:13 +02:00
if ( fileErrors > 0 && brokenFiles . Count > 0 )
m_result . AddMessage ( string . Format ( "Failed to restore {0} files, additionally the following files failed to download, which may be the cause:{1}" , fileErrors , Environment . NewLine , string . Join ( Environment . NewLine , brokenFiles )));
2013-03-26 19:27:26 +01:00
// Drop the temp tables
database . DropRestoreTable ();
2013-04-09 20:43:27 +02:00
backend . WaitForComplete ( database , null );
2013-03-26 19:27:26 +01:00
}
2013-05-25 16:40:15 +02:00
2013-08-22 20:52:54 +02:00
m_result . OperationProgressUpdater . UpdatePhase ( OperationPhase . Restore_Complete );
2014-04-22 12:36:21 +02:00
result . EndTime = DateTime . UtcNow ;
2013-03-26 19:27:26 +01:00
}
2013-03-30 15:23:09 +01:00
private static void ApplyMetadata ( string path , LocalRestoreDatabase database )
2013-03-26 19:27:26 +01:00
{
//TODO: Implement writing metadata
}
2013-05-25 16:40:15 +02:00
private static void ScanForExistingSourceBlocksFast ( LocalRestoreDatabase database , Options options , byte [] blockbuffer , System . Security . Cryptography . HashAlgorithm hasher , RestoreResults result )
2013-04-28 12:19:21 +02:00
{
// Fill BLOCKS with data from known local source files
using ( var blockmarker = database . CreateBlockMarker ())
{
2013-09-07 21:35:23 +02:00
var updateCount = 0L ;
2013-04-28 12:19:21 +02:00
foreach ( var entry in database . GetFilesAndSourceBlocksFast ())
{
var targetpath = entry . TargetPath ;
var targetfileid = entry . TargetFileID ;
var sourcepath = entry . SourcePath ;
var patched = false ;
try
{
2014-03-18 10:59:06 +01:00
if ( m_systemIO . FileExists ( sourcepath ))
2013-04-29 23:56:01 +02:00
{
2014-03-18 10:59:06 +01:00
var folderpath = m_systemIO . PathGetDirectoryName ( targetpath );
if (! options . Dryrun && ! m_systemIO . DirectoryExists ( folderpath ))
2013-05-21 21:15:03 +02:00
{
2013-05-25 16:40:15 +02:00
result . AddWarning ( string . Format ( "Creating missing folder {0} for file {1}" , folderpath , targetpath ), null );
2014-03-18 10:59:06 +01:00
m_systemIO . DirectoryCreate ( folderpath );
2013-05-21 21:15:03 +02:00
}
2014-03-18 10:59:06 +01:00
using ( var targetstream = options . Dryrun ? null : m_systemIO . FileCreate ( targetpath ))
2013-04-29 23:56:01 +02:00
{
try
{
2014-03-18 10:59:06 +01:00
using ( var sourcestream = m_systemIO . FileOpenRead ( sourcepath ))
2013-04-29 23:56:01 +02:00
{
foreach ( var block in entry . Blocks )
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2013-04-29 23:56:01 +02:00
if ( sourcestream . Length > block . Offset )
{
sourcestream . Position = block . Offset ;
var size = sourcestream . Read ( blockbuffer , 0 , blockbuffer . Length );
if ( size == block . Size )
{
var key = Convert . ToBase64String ( hasher . ComputeHash ( blockbuffer , 0 , size ));
if ( key == block . Hash )
{
2013-05-29 22:15:13 +02:00
patched = true ;
if (! options . Dryrun )
2013-04-29 23:56:01 +02:00
{
targetstream . Position = block . Offset ;
targetstream . Write ( blockbuffer , 0 , size );
}
blockmarker . SetBlockRestored ( targetfileid , block . Index , key , block . Size );
}
}
}
2013-04-28 12:19:21 +02:00
}
}
2013-04-29 23:56:01 +02:00
}
catch ( Exception ex )
{
2013-05-25 16:40:15 +02:00
result . AddWarning ( string . Format ( "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}" , targetpath , sourcepath , ex . Message ), ex );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2013-04-29 23:56:01 +02:00
}
2013-08-23 22:18:13 +02:00
}
2013-09-07 21:35:23 +02:00
if ( updateCount ++ % 20 == 0 )
2014-05-15 12:47:16 +02:00
{
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
}
2013-08-23 22:18:13 +02:00
2013-04-29 23:56:01 +02:00
}
2013-05-29 22:15:13 +02:00
else
{
result . AddVerboseMessage ( "Local source file not found: {0}" , sourcepath );
}
2013-04-29 23:56:01 +02:00
}
2013-04-28 12:19:21 +02:00
catch ( Exception ex )
{
2013-05-25 16:40:15 +02:00
result . AddWarning ( string . Format ( "Failed to patch file: \"{0}\" with local data, message: {1}" , targetpath , ex . Message ), ex );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2013-04-28 12:19:21 +02:00
}
2013-05-29 22:15:13 +02:00
if ( patched )
result . AddVerboseMessage ( "Target file is patched with some local data: {0}" , targetpath );
else
result . AddVerboseMessage ( "Target file is not patched any local data: {0}" , targetpath );
2013-05-08 19:57:13 +02:00
if ( patched && options . Dryrun )
2013-05-25 17:32:39 +02:00
result . AddDryrunMessage ( string . Format ( "Would patch file with local data: {0}" , targetpath ));
2013-04-28 12:19:21 +02:00
}
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2013-08-23 23:37:17 +02:00
blockmarker . Commit ( result );
2013-04-28 12:19:21 +02:00
}
}
2013-05-25 16:40:15 +02:00
private static void ScanForExistingSourceBlocks ( LocalRestoreDatabase database , Options options , byte [] blockbuffer , System . Security . Cryptography . HashAlgorithm hasher , RestoreResults result )
2013-03-26 19:27:26 +01:00
{
// Fill BLOCKS with data from known local source files
using ( var blockmarker = database . CreateBlockMarker ())
{
2013-09-07 21:35:23 +02:00
var updateCount = 0L ;
2013-03-26 19:27:26 +01:00
foreach ( var restorelist in database . GetFilesAndSourceBlocks ())
{
var targetpath = restorelist . TargetPath ;
2013-04-27 10:20:15 +02:00
var targetfileid = restorelist . TargetFileID ;
2013-03-29 14:13:31 +01:00
var patched = false ;
2013-03-26 19:27:26 +01:00
try
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2014-03-18 10:59:06 +01:00
var folderpath = m_systemIO . PathGetDirectoryName ( targetpath );
if (! options . Dryrun && ! m_systemIO . DirectoryExists ( folderpath ))
2013-05-21 21:15:03 +02:00
{
2013-05-25 16:40:15 +02:00
result . AddWarning ( string . Format ( "Creating missing folder {0} for file {1}" , folderpath , targetpath ), null );
2014-03-18 10:59:06 +01:00
m_systemIO . DirectoryCreate ( folderpath );
2013-05-21 21:15:03 +02:00
}
2014-03-18 10:59:06 +01:00
using ( var file = options . Dryrun ? null : m_systemIO . FileOpenReadWrite ( targetpath ))
2013-03-26 19:27:26 +01:00
using ( var block = new Blockprocessor ( file , blockbuffer ))
foreach ( var targetblock in restorelist . Blocks )
{
2013-05-08 19:57:13 +02:00
if (! options . Dryrun )
2013-03-29 14:13:31 +01:00
file . Position = targetblock . Offset ;
2013-03-26 19:27:26 +01:00
foreach ( var source in targetblock . Blocksources )
{
try
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2014-03-18 10:59:06 +01:00
if ( m_systemIO . FileExists ( source . Path ))
using ( var sourcefile = m_systemIO . FileOpenRead ( source . Path ))
2013-03-26 19:27:26 +01:00
{
sourcefile . Position = source . Offset ;
var size = sourcefile . Read ( blockbuffer , 0 , blockbuffer . Length );
if ( size == targetblock . Size )
{
var key = Convert . ToBase64String ( hasher . ComputeHash ( blockbuffer , 0 , size ));
if ( key == targetblock . Hash )
{
2013-05-29 22:15:13 +02:00
patched = true ;
if (! options . Dryrun )
2013-03-29 14:13:31 +01:00
file . Write ( blockbuffer , 0 , size );
2013-04-27 10:20:15 +02:00
blockmarker . SetBlockRestored ( targetfileid , targetblock . Index , key , targetblock . Size );
2013-03-26 19:27:26 +01:00
break ;
}
}
}
}
catch ( Exception ex )
{
2013-05-25 16:40:15 +02:00
result . AddWarning ( string . Format ( "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}" , targetpath , source . Path , ex . Message ), ex );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2013-03-26 19:27:26 +01:00
}
}
}
2013-08-23 22:18:13 +02:00
2013-09-07 21:35:23 +02:00
if ( updateCount ++ % 20 == 0 )
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2013-03-26 19:27:26 +01:00
}
catch ( Exception ex )
{
2013-05-25 16:40:15 +02:00
result . AddWarning ( string . Format ( "Failed to patch file: \"{0}\" with local data, message: {1}" , targetpath , ex . Message ), ex );
2013-03-26 19:27:26 +01:00
}
2013-03-29 14:13:31 +01:00
2013-05-29 22:15:13 +02:00
if ( patched )
result . AddVerboseMessage ( "Target file is patched with some local data: {0}" , targetpath );
else
result . AddVerboseMessage ( "Target file is not patched any local data: {0}" , targetpath );
2013-05-08 19:57:13 +02:00
if ( patched && options . Dryrun )
2013-05-25 17:32:39 +02:00
result . AddDryrunMessage ( string . Format ( "Would patch file with local data: {0}" , targetpath ));
2013-03-26 19:27:26 +01:00
}
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2013-08-23 23:37:17 +02:00
blockmarker . Commit ( result );
2013-03-26 19:27:26 +01:00
}
}
2013-05-25 16:40:15 +02:00
private static void PrepareBlockAndFileList ( LocalRestoreDatabase database , Options options , Library . Utility . IFilter filter , RestoreResults result )
2013-08-23 22:18:13 +02:00
{
// Create a temporary table FILES by selecting the files from fileset that matches a specific operation id
// Delete all entries from the temp table that are excluded by the filter(s)
using ( new Logging . Timer ( "PrepareRestoreFileList" ))
{
var c = database . PrepareRestoreFilelist ( options . Time , options . Version , filter , result );
result . OperationProgressUpdater . UpdatefileCount ( c . Item1 , c . Item2 , true );
}
2013-05-13 22:32:05 +02:00
2013-05-25 16:40:15 +02:00
using ( new Logging . Timer ( "SetTargetPaths" ))
2013-05-29 22:15:13 +02:00
if (! string . IsNullOrEmpty ( options . Restorepath ))
{
// Find the largest common prefix
string largest_prefix = database . GetLargestPrefix ();
2013-05-30 22:36:24 +02:00
result . AddVerboseMessage ( "Mapping restore path prefix to \"{0}\" to \"{1}\"" , largest_prefix , Library . Utility . Utility . AppendDirSeparator ( options . Restorepath ));
2013-05-29 22:15:13 +02:00
// Set the target paths, special care with C:\ and /
2013-05-30 22:36:24 +02:00
database . SetTargetPaths ( largest_prefix , Library . Utility . Utility . AppendDirSeparator ( options . Restorepath ));
2013-05-29 22:15:13 +02:00
}
else
{
database . SetTargetPaths ( "" , "" );
}
2013-03-26 19:27:26 +01:00
// Create a temporary table BLOCKS that lists all blocks that needs to be recovered
2013-05-25 16:40:15 +02:00
using ( new Logging . Timer ( "FindMissingBlocks" ))
2013-05-29 22:15:13 +02:00
database . FindMissingBlocks ( result );
2013-03-26 19:27:26 +01:00
}
2013-05-25 16:40:15 +02:00
private static void CreateDirectoryStructure ( LocalRestoreDatabase database , Options options , RestoreResults result )
2013-05-21 21:15:03 +02:00
{
// This part is not protected by try/catch as we need the target folder to exist
if (! string . IsNullOrEmpty ( options . Restorepath ))
2014-03-18 10:59:06 +01:00
if (! m_systemIO . DirectoryExists ( options . Restorepath ))
2013-05-29 22:15:13 +02:00
{
if ( options . Verbose )
result . AddVerboseMessage ( "Creating folder: {0}" , options . Restorepath );
2013-05-21 21:15:03 +02:00
if ( options . Dryrun )
2013-05-25 17:32:39 +02:00
result . AddDryrunMessage ( string . Format ( "Would create folder: {0}" , options . Restorepath ));
2013-05-21 21:15:03 +02:00
else
2014-03-18 10:59:06 +01:00
m_systemIO . DirectoryCreate ( options . Restorepath );
2013-05-29 22:15:13 +02:00
}
2013-05-21 21:15:03 +02:00
2013-03-26 19:27:26 +01:00
foreach ( var folder in database . GetTargetFolders ())
{
try
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2014-03-18 10:59:06 +01:00
if (! m_systemIO . DirectoryExists ( folder ))
2013-05-25 16:40:15 +02:00
{
result . FoldersRestored ++;
2013-05-29 22:15:13 +02:00
if ( options . Verbose )
result . AddVerboseMessage ( "Creating folder: {0}" , folder );
2013-05-08 19:57:13 +02:00
if ( options . Dryrun )
2013-05-25 17:32:39 +02:00
result . AddDryrunMessage ( string . Format ( "Would create folder: {0}" , folder ));
2013-03-29 14:13:31 +01:00
else
2014-03-18 10:59:06 +01:00
m_systemIO . DirectoryCreate ( folder );
2013-05-25 16:40:15 +02:00
}
2013-03-26 19:27:26 +01:00
}
catch ( Exception ex )
{
2013-05-25 16:40:15 +02:00
result . AddWarning ( string . Format ( "Failed to create folder: \"{0}\", message: {1}" , folder , ex . Message ), ex );
2013-03-26 19:27:26 +01:00
}
try
{
2013-05-08 19:57:13 +02:00
if (! options . Dryrun )
2013-03-29 14:13:31 +01:00
ApplyMetadata ( folder , database );
2013-03-26 19:27:26 +01:00
}
catch ( Exception ex )
{
2013-05-25 16:40:15 +02:00
result . AddWarning ( string . Format ( "Failed to set folder metadata: \"{0}\", message: {1}" , folder , ex . Message ), ex );
2013-03-26 19:27:26 +01:00
}
}
}
2013-06-01 14:22:10 +02:00
private static void ScanForExistingTargetBlocks ( LocalRestoreDatabase database , byte [] blockbuffer , System . Security . Cryptography . HashAlgorithm blockhasher , System . Security . Cryptography . HashAlgorithm filehasher , Options options , RestoreResults result )
2013-03-26 19:27:26 +01:00
{
// Scan existing files for existing BLOCKS
2013-08-22 20:52:54 +02:00
using ( var blockmarker = database . CreateBlockMarker ())
2013-03-26 19:27:26 +01:00
{
2013-09-07 21:35:23 +02:00
var updateCount = 0L ;
2013-08-22 20:52:54 +02:00
foreach ( var restorelist in database . GetExistingFilesWithBlocks ())
2013-03-26 19:27:26 +01:00
{
2013-06-01 14:22:10 +02:00
var rename = ! options . Overwrite ;
2013-03-26 19:27:26 +01:00
var targetpath = restorelist . TargetPath ;
2013-04-27 10:20:15 +02:00
var targetfileid = restorelist . TargetFileID ;
2013-06-01 14:22:10 +02:00
var targetfilehash = restorelist . TargetHash ;
2014-03-18 10:59:06 +01:00
if ( m_systemIO . FileExists ( targetpath ))
2013-03-26 19:27:26 +01:00
{
try
{
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
2013-06-01 14:22:10 +02:00
if ( rename )
filehasher . Initialize ();
2014-03-18 10:59:06 +01:00
using ( var file = m_systemIO . FileOpenReadWrite ( targetpath ))
2013-08-22 20:52:54 +02:00
using ( var block = new Blockprocessor ( file , blockbuffer ))
foreach ( var targetblock in restorelist . Blocks )
2013-03-26 19:27:26 +01:00
{
var size = block . Readblock ();
if ( size <= 0 )
break ;
if ( size == targetblock . Size )
{
2013-06-01 14:22:10 +02:00
var key = Convert . ToBase64String ( blockhasher . ComputeHash ( blockbuffer , 0 , size ));
2013-03-26 19:27:26 +01:00
if ( key == targetblock . Hash )
{
2013-04-27 10:20:15 +02:00
blockmarker . SetBlockRestored ( targetfileid , targetblock . Index , key , size );
2013-03-26 19:27:26 +01:00
}
}
2013-06-01 14:22:10 +02:00
if ( rename )
filehasher . TransformBlock ( blockbuffer , 0 , size , blockbuffer , 0 );
2013-03-26 19:27:26 +01:00
}
2013-06-01 14:22:10 +02:00
if ( rename )
{
filehasher . TransformFinalBlock ( blockbuffer , 0 , 0 );
var filekey = Convert . ToBase64String ( filehasher . Hash );
if ( filekey == targetfilehash )
{
result . AddVerboseMessage ( "Target file exists and is correct version: {0}" , targetpath );
rename = false ;
}
2013-08-22 20:52:54 +02:00
else
{
// The new file will have none of the correct blocks,
// even if the scanned file had some
blockmarker . SetAllBlocksMissing ( targetfileid );
}
2013-06-01 14:22:10 +02:00
}
2013-08-23 22:18:13 +02:00
2013-09-07 21:35:23 +02:00
if ( updateCount ++ % 20 == 0 )
2014-05-15 12:47:16 +02:00
{
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2014-05-15 12:47:16 +02:00
if ( result . TaskControlRendevouz () == TaskControlState . Stop )
return ;
}
2013-03-26 19:27:26 +01:00
}
catch ( Exception ex )
{
2013-05-25 16:40:15 +02:00
result . AddWarning ( string . Format ( "Failed to read target file: \"{0}\", message: {1}" , targetpath , ex . Message ), ex );
2014-05-15 12:47:16 +02:00
if ( ex is System . Threading . ThreadAbortException )
throw ;
2013-06-01 14:22:10 +02:00
}
}
else
{
result . AddVerboseMessage ( "Target file does not exist: {0}" , targetpath );
rename = false ;
2013-03-26 19:27:26 +01:00
}
2013-06-01 14:22:10 +02:00
if ( rename )
{
//Select a new filename
2014-03-18 10:59:06 +01:00
var ext = m_systemIO . PathGetExtension ( targetpath ) ?? "" ;
2013-06-01 14:43:34 +02:00
if (! string . IsNullOrEmpty ( ext ) && ! ext . StartsWith ( "." ))
2013-06-01 14:22:10 +02:00
ext = "." + ext ;
// First we try with a simple date append, assuming that there are not many conflicts there
2014-03-18 10:59:06 +01:00
var newname = m_systemIO . PathChangeExtension ( targetpath , null ) + "." + database . RestoreTime . ToLocalTime (). ToString ( "yyyy-MM-dd" );
2013-08-22 20:52:54 +02:00
var tr = newname + ext ;
var c = 0 ;
2014-03-18 10:59:06 +01:00
while ( m_systemIO . FileExists ( tr ) && c < 1000 )
2013-08-22 20:52:54 +02:00
{
try
{
// If we have a file with the correct name,
// it is most likely the file we want
filehasher . Initialize ();
string key ;
2014-03-18 10:59:06 +01:00
using ( var file = m_systemIO . FileOpenReadWrite ( tr ))
2013-08-22 20:52:54 +02:00
key = Convert . ToBase64String ( filehasher . ComputeHash ( file ));
if ( key == targetfilehash )
{
blockmarker . SetAllBlocksRestored ( targetfileid );
break ;
}
}
catch ( Exception ex )
{
result . AddWarning ( string . Format ( "Failed to read candidate restore target {0}" , tr ), ex );
}
tr = newname + " (" + ( c ++). ToString () + ")" + ext ;
2013-06-01 14:22:10 +02:00
}
2013-08-22 20:52:54 +02:00
newname = tr ;
2013-06-01 14:22:10 +02:00
result . AddVerboseMessage ( "Target file exists and will be restored to: {0}" , newname );
database . UpdateTargetPath ( targetfileid , newname );
}
2013-03-26 19:27:26 +01:00
}
2013-09-07 21:35:23 +02:00
blockmarker . UpdateProcessed ( result . OperationProgressUpdater );
2013-08-23 23:37:17 +02:00
blockmarker . Commit ( result );
2013-03-26 19:27:26 +01:00
}
}
}
}