2013-03-08 22:24:54 +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.Volumes ;
2013-03-08 22:24:54 +01:00
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Database
2013-03-08 22:24:54 +01:00
{
2013-05-25 16:40:15 +02:00
internal partial class LocalRestoreDatabase : LocalDatabase
2013-03-08 22:24:54 +01:00
{
protected string m_tempfiletable ;
protected string m_tempblocktable ;
protected long m_blocksize ;
2013-05-20 13:48:44 +02:00
protected DateTime m_restoreTime ;
public DateTime RestoreTime { get { return m_restoreTime ; } }
2013-03-08 22:24:54 +01:00
2013-03-30 15:23:09 +01:00
public LocalRestoreDatabase ( string path , long blocksize )
2013-04-04 20:34:26 +02:00
: this ( new LocalDatabase ( path , "Restore" ), blocksize )
2013-03-08 22:24:54 +01:00
{
}
2013-04-04 20:34:26 +02:00
public LocalRestoreDatabase ( LocalDatabase dbparent , long blocksize )
: base ( dbparent )
2013-03-08 22:24:54 +01:00
{
//TODO: Should read this from DB?
m_blocksize = blocksize ;
}
2013-08-23 22:18:13 +02:00
public Tuple < long , long > PrepareRestoreFilelist ( DateTime restoretime , long [] versions , Library . Utility . IFilter filter , ILogWriter log )
2013-05-29 22:15:28 +02:00
{
var guid = Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2013-03-08 22:24:54 +01:00
2013-05-29 22:15:28 +02:00
m_tempfiletable = "Fileset-" + guid ;
m_tempblocktable = "Blocks-" + guid ;
2013-03-08 22:24:54 +01:00
2013-05-29 22:15:28 +02:00
using ( var cmd = m_connection . CreateCommand ())
2013-03-08 22:24:54 +01:00
{
2013-08-24 22:27:30 +02:00
var filesetIds = GetFilesetIDs ( NormalizeDateTime ( restoretime ), versions ). ToList ();
while ( filesetIds . Count > 0 )
2013-03-08 22:24:54 +01:00
{
2013-08-24 22:27:30 +02:00
var filesetId = filesetIds [ 0 ];
filesetIds . RemoveAt ( 0 );
m_restoreTime = ParseFromEpochSeconds ( Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" = ?" , filesetId )));
var ix = this . FilesetTimes . Select (( value , index ) => new { value . Key , index })
. Where ( n => n . Key == filesetId )
. Select ( pair => pair . index + 1 )
. FirstOrDefault () - 1 ;
log . AddMessage ( string . Format ( "Searching backup {0} ({1}) ..." , ix , m_restoreTime ));
cmd . Parameters . Clear ();
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_tempfiletable ));
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_tempblocktable ));
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY, ""Path"" TEXT NOT NULL, ""BlocksetID"" INTEGER NOT NULL, ""MetadataID"" INTEGER NOT NULL, ""Targetpath"" TEXT NULL ) " , m_tempfiletable ));
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY, ""FileID"" INTEGER NOT NULL, ""Index"" INTEGER NOT NULL, ""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Restored"" BOOLEAN NOT NULL)" , m_tempblocktable ));
if ( filter == null || filter . Empty )
2013-03-08 22:24:54 +01:00
{
2013-08-24 22:27:30 +02:00
// Simple case, restore everything
cmd . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Path"", ""BlocksetID"", ""MetadataID"") SELECT ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"" FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetEntry"".""FilesetID"" = ? " , m_tempfiletable );
cmd . AddParameter ( filesetId );
cmd . ExecuteNonQuery ();
}
else if ( filter is Library . Utility . FilterExpression && ( filter as Library . Utility . FilterExpression ). Type == Duplicati . Library . Utility . FilterType . Simple )
{
// If we get a list of filenames, the lookup table is faster
using ( var tr = m_connection . BeginTransaction ())
2013-03-15 21:16:29 +01:00
{
2013-08-24 22:27:30 +02:00
var p = ( filter as Library . Utility . FilterExpression ). GetSimpleList ();
var m_filenamestable = "Filenames-" + guid ;
cmd . Transaction = tr ;
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL) " , m_filenamestable ));
cmd . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Path"") VALUES (?)" , m_filenamestable );
cmd . AddParameter ();
foreach ( var s in p )
{
cmd . SetParameterValue ( 0 , s );
cmd . ExecuteNonQuery ();
}
//TODO: Handle case-insensitive filename lookup
cmd . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Path"", ""BlocksetID"", ""MetadataID"") SELECT ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"" FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetEntry"".""FilesetID"" = ? AND ""Path"" IN (SELECT DISTINCT ""Path"" FROM ""{1}"") " , m_tempfiletable , m_filenamestable );
cmd . SetParameterValue ( 0 , filesetId );
var c = cmd . ExecuteNonQuery ();
2013-03-15 21:16:29 +01:00
cmd . Parameters . Clear ();
2013-08-24 22:27:30 +02:00
if ( c != p . Length && c != 0 )
{
var sb = new StringBuilder ();
sb . AppendLine ();
using ( var rd = cmd . ExecuteReader ( string . Format ( @"SELECT ""Path"" FROM ""{0}"" WHERE ""Path"" NOT IN (SELECT ""Path"" FROM ""{1}"")" , m_filenamestable , m_tempfiletable )))
while ( rd . Read ())
sb . AppendLine ( rd . GetValue ( 0 ). ToString ());
var actualrestoretime = ParseFromEpochSeconds ( Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" = ?" , filesetId )));
log . AddWarning ( string . Format ( "{0} File(s) were not found in list of files for backup at {1}, will not be restored: {2}" , p . Length - c , actualrestoretime . ToLocalTime (), sb ), null );
cmd . Parameters . Clear ();
}
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_filenamestable ));
using ( new Logging . Timer ( "CommitPrepareFileset" ))
tr . Commit ();
2013-03-15 21:16:29 +01:00
}
2013-03-08 22:24:54 +01:00
}
2013-08-24 22:27:30 +02:00
else
2013-03-08 22:24:54 +01:00
{
2013-08-24 22:27:30 +02:00
// Restore but filter elements based on the filter expression
// If this is too slow, we could add a special handler for wildcard searches too
cmd . CommandText = string . Format ( @"SELECT ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"" FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetID"" = ?" );
cmd . AddParameter ( filesetId );
object [] values = new object [ 3 ];
using ( var cmd2 = m_connection . CreateCommand ())
{
cmd2 . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Path"", ""BlocksetID"", ""MetadataID"") VALUES (?,?,?)" , m_tempfiletable );
cmd2 . AddParameter ();
cmd2 . AddParameter ();
cmd2 . AddParameter ();
using ( var rd = cmd . ExecuteReader ())
while ( rd . Read ())
2013-03-08 22:24:54 +01:00
{
2013-08-24 22:27:30 +02:00
rd . GetValues ( values );
if ( values [ 0 ] != null && values [ 0 ] != DBNull . Value && Library . Utility . FilterExpression . Matches ( filter , values [ 0 ]. ToString ()))
{
cmd2 . SetParameterValue ( 0 , values [ 0 ]);
cmd2 . SetParameterValue ( 1 , values [ 1 ]);
cmd2 . SetParameterValue ( 2 , values [ 2 ]);
cmd2 . ExecuteNonQuery ();
}
2013-03-08 22:24:54 +01:00
}
2013-08-24 22:27:30 +02:00
}
2013-03-08 22:24:54 +01:00
}
2013-08-23 22:18:13 +02:00
2013-08-24 22:27:30 +02:00
using ( var rd = cmd . ExecuteReader ( string . Format ( @"SELECT COUNT(DISTINCT ""{0}"".""Path""), SUM(""Blockset"".""Length"") FROM ""{0}"", ""Blockset"" WHERE ""{0}"".""BlocksetID"" = ""Blockset"".""ID"" " , m_tempfiletable )))
{
var filecount = 0L ;
var filesize = 0L ;
2013-08-25 13:16:12 +02:00
if ( rd . Read ())
{
var r0 = rd . GetValue ( 0 );
var r1 = rd . GetValue ( 1 );
if ( r0 != null && r0 != DBNull . Value )
filecount = Convert . ToInt64 ( r0 );
if ( r1 != null && r1 != DBNull . Value )
filesize = Convert . ToInt64 ( r1 );
}
2013-08-24 22:27:30 +02:00
if ( filecount > 0 )
{
log . AddVerboseMessage ( "Needs to restore {0} files ({1})" , filecount , Library . Utility . Utility . FormatSizeString ( filesize ));
return new Tuple < long , long >( filecount , filesize );
}
}
}
2013-03-08 22:24:54 +01:00
}
2013-08-24 22:27:30 +02:00
return new Tuple < long , long >( 0 , 0 );
2013-03-08 22:24:54 +01:00
}
public string GetLargestPrefix ()
{
using ( var cmd = m_connection . CreateCommand ())
{
cmd . CommandText = string . Format ( @"SELECT ""Path"" FROM ""{0}"" ORDER BY LENGTH(""Path"") DESC LIMIT 1" , m_tempfiletable );
var v0 = cmd . ExecuteScalar ();
string maxpath = "" ;
if ( v0 != null )
maxpath = v0 . ToString ();
cmd . CommandText = string . Format ( @"SELECT COUNT(*) FROM ""{0}""" , m_tempfiletable );
var filecount = Convert . ToInt64 ( cmd . ExecuteScalar ());
long foundfiles = - 1 ;
//TODO: Handle FS case-sensitive?
cmd . CommandText = string . Format ( @"SELECT COUNT(*) FROM ""{0}"" WHERE SUBSTR(""Path"", 1, ?) = ?" , m_tempfiletable );
cmd . AddParameter ();
cmd . AddParameter ();
while ( filecount != foundfiles && maxpath . Length > 0 )
{
2013-05-08 21:29:59 +02:00
var mp = Library . Utility . Utility . AppendDirSeparator ( maxpath );
2013-03-08 22:24:54 +01:00
cmd . SetParameterValue ( 0 , mp . Length );
cmd . SetParameterValue ( 1 , mp );
foundfiles = Convert . ToInt64 ( cmd . ExecuteScalar ());
if ( filecount != foundfiles )
{
var oldlen = maxpath . Length ;
maxpath = System . IO . Path . GetDirectoryName ( maxpath );
if ( maxpath . Length == oldlen )
maxpath = "" ;
}
}
2013-05-08 21:29:59 +02:00
return maxpath == "" ? "" : Library . Utility . Utility . AppendDirSeparator ( maxpath );
2013-03-08 22:24:54 +01:00
}
}
public void SetTargetPaths ( string largest_prefix , string destination )
2013-05-21 21:14:13 +02:00
{
using ( var cmd = m_connection . CreateCommand ())
{
if ( string . IsNullOrEmpty ( destination ))
{
//The string fixing here is meant to provide some non-random
// defaults when restoring cross OS, e.g. backup on Linux, restore on Windows
//This is mostly meaningless, and the user really should use --restore-path
if ( Library . Utility . Utility . IsClientLinux )
// For Win -> Linux, we remove the colon from the drive letter, and use the drive letter as root folder
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 2, 1) == "":"" THEN ""/"" || SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END" , m_tempfiletable ));
else
// For Linux -> Win, we use the temporary folder's drive as the root path
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 1) == ""/"" THEN ? || SUBSTR(""Path"", 2) ELSE ""Path"" END" , m_tempfiletable ), Library . Utility . Utility . AppendDirSeparator ( System . IO . Path . GetPathRoot ( Library . Utility . TempFolder . SystemTempPath )));
}
else
{
if ( string . IsNullOrEmpty ( largest_prefix ))
{
//Special case, restoring to new folder, but files are from different drives
// So we use the format <restore path> / <drive letter> / <source path>
// To avoid generating paths with a colon
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""Targetpath"" = ? || CASE WHEN SUBSTR(""Path"", 2, 1) == "":"" THEN SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END" , m_tempfiletable ), destination );
}
else
{
largest_prefix = Library . Utility . Utility . AppendDirSeparator ( largest_prefix );
cmd . CommandText = string . Format ( @"UPDATE ""{0}"" SET ""Targetpath"" = ? || SUBSTR(""Path"", ?)" , m_tempfiletable );
cmd . AddParameter ( destination );
cmd . AddParameter ( largest_prefix . Length + 1 );
cmd . ExecuteNonQuery ();
}
}
2013-03-08 22:24:54 +01:00
}
}
2013-05-29 22:15:13 +02:00
public void FindMissingBlocks ( ILogWriter log )
2013-03-08 22:24:54 +01:00
{
2013-05-29 22:15:13 +02:00
using ( var cmd = m_connection . CreateCommand ())
2013-03-08 22:24:54 +01:00
{
cmd . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""FileID"", ""Index"", ""Hash"", ""Size"", ""Restored"") SELECT DISTINCT ""{1}"".""ID"", ""BlocksetEntry"".""Index"", ""Block"".""Hash"", ""Block"".""Size"", 0 FROM ""{1}"", ""BlocksetEntry"", ""Block"" WHERE ""{1}"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" " , m_tempblocktable , m_tempfiletable );
var p = cmd . ExecuteNonQuery ();
2013-05-29 22:15:13 +02:00
if ( log . VerboseOutput )
{
var size = Convert . ToInt64 ( cmd . ExecuteScalar ( string . Format ( @"SELECT SUM(""Size"") FROM ""{0}"" " , m_tempblocktable )));
log . AddVerboseMessage ( "Restore list contains {0} blocks with a total size of {1}" , p , Library . Utility . Utility . FormatSizeString ( size ));
}
2013-03-08 22:24:54 +01:00
}
}
2013-05-20 13:48:44 +02:00
public void UpdateTargetPath ( long ID , string newname )
{
using ( var cmd = m_connection . CreateCommand ())
2013-06-01 14:43:34 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""TargetPath"" = ? WHERE ""ID"" = ?" , m_tempfiletable ), newname , ID );
2013-05-20 13:48:44 +02:00
}
2013-03-08 22:24:54 +01:00
public interface IExistingFileBlock
{
string Hash { get ; }
long Index { get ; }
long Size { get ; }
}
public interface IExistingFile
{
string TargetPath { get ; }
2013-06-01 14:22:10 +02:00
string TargetHash { get ; }
2013-04-27 10:20:15 +02:00
long TargetFileID { get ; }
2013-03-08 22:24:54 +01:00
long Length { get ; }
IEnumerable < IExistingFileBlock > Blocks { get ; }
}
public interface IBlockSource
{
string Path { get ; }
long Size { get ; }
long Offset { get ; }
}
public interface IBlockDescriptor
{
string Hash { get ; }
long Size { get ; }
long Offset { get ; }
long Index { get ; }
IEnumerable < IBlockSource > Blocksources { get ; }
}
public interface ILocalBlockSource
{
string TargetPath { get ; }
2013-04-27 10:20:15 +02:00
long TargetFileID { get ; }
2013-03-08 22:24:54 +01:00
IEnumerable < IBlockDescriptor > Blocks { get ; }
}
public interface IFileToRestore
{
string Path { get ; }
string Hash { get ; }
2013-05-20 13:48:44 +02:00
long ID { get ; }
2013-03-08 22:24:54 +01:00
}
public interface IPatchBlock
{
long Offset { get ; }
long Size { get ; }
string Key { get ; }
}
public interface IVolumePatch
{
string Path { get ; }
2013-08-23 22:18:13 +02:00
long FileID { get ; }
2013-03-08 22:24:54 +01:00
IEnumerable < IPatchBlock > Blocks { get ; }
}
public IEnumerable < IExistingFile > GetExistingFilesWithBlocks ()
{
return new ExistingFileEnumerable ( m_connection , m_tempfiletable );
}
public IEnumerable < ILocalBlockSource > GetFilesAndSourceBlocks ()
{
return new LocalBlockSourceEnumerable ( m_connection , m_tempfiletable , m_tempblocktable , m_blocksize );
}
2013-07-01 11:58:33 +02:00
public IEnumerable < IRemoteVolume > GetMissingVolumes ()
2013-03-08 22:24:54 +01:00
{
using ( var cmd = m_connection . CreateCommand ())
{
2013-03-22 21:56:00 +01:00
cmd . CommandText = string . Format ( @"SELECT DISTINCT ""RemoteVolume"".""Name"", ""RemoteVolume"".""Hash"", ""RemoteVolume"".""Size"" FROM ""RemoteVolume"" WHERE ""ID"" IN (SELECT DISTINCT ""Block"".""VolumeID"" FROM ""Block"" WHERE ""Block"".""Hash"" IN (SELECT DISTINCT ""{0}"".""Hash"" FROM ""{0}"" WHERE ""{0}"".""Restored"" = 0))" , m_tempblocktable );
2013-03-08 22:24:54 +01:00
using ( var rd = cmd . ExecuteReader ())
{
object [] r = new object [ 3 ];
while ( rd . Read ())
{
rd . GetValues ( r );
2013-07-01 11:58:33 +02:00
yield return new RemoteVolume (
2013-03-08 22:24:54 +01:00
r [ 0 ] == null ? null : r [ 0 ]. ToString (),
r [ 1 ] == null ? null : r [ 1 ]. ToString (),
r [ 2 ] == null ? - 1 : Convert . ToInt64 ( r [ 2 ])
2013-07-01 11:58:33 +02:00
);
2013-03-08 22:24:54 +01:00
}
}
}
}
public IEnumerable < IVolumePatch > GetFilesWithMissingBlocks ( BlockVolumeReader curvolume )
{
return new VolumePatchEnumerable ( m_connection , m_tempfiletable , m_tempblocktable , m_blocksize , curvolume );
}
2013-05-20 13:46:58 +02:00
private class FileToRestore : IFileToRestore
{
public string Path { get ; private set ; }
public string Hash { get ; private set ; }
public long ID { get ; private set ; }
public FileToRestore ( long id , string path , string hash )
{
this . ID = id ;
this . Path = path ;
this . Hash = hash ;
}
}
2013-03-08 22:24:54 +01:00
public IEnumerable < IFileToRestore > GetFilesToRestore ()
{
2013-05-20 13:46:58 +02:00
using ( var cmd = m_connection . CreateCommand ())
using ( var rd = cmd . ExecuteReader ( string . Format ( @"SELECT ""{0}"".""ID"", ""{0}"".""TargetPath"", ""Blockset"".""FullHash"" FROM ""{0}"",""Blockset"" WHERE ""{0}"".""BlocksetID"" = ""Blockset"".""ID"" " , m_tempfiletable )))
while ( rd . Read ())
yield return new FileToRestore ( Convert . ToInt64 ( rd . GetValue ( 0 )), rd . GetValue ( 1 ). ToString (), rd . GetValue ( 2 ). ToString ());
2013-03-08 22:24:54 +01:00
}
public void DropRestoreTable ()
{
using ( var cmd = m_connection . CreateCommand ())
{
if ( m_tempfiletable != null )
try
{
2013-08-22 20:52:54 +02:00
cmd . CommandText = string . Format ( @"DROP TABLE IF EXISTS ""{0}""" , m_tempfiletable );
2013-03-08 22:24:54 +01:00
cmd . ExecuteNonQuery ();
}
2013-08-22 20:52:54 +02:00
catch ( Exception ex ) { if ( m_result != null ) m_result . AddWarning ( string . Format ( "Cleanup error: {0}" , ex . Message ), ex ); }
2013-03-08 22:24:54 +01:00
finally { m_tempfiletable = null ; }
if ( m_tempblocktable != null )
try
{
2013-08-22 20:52:54 +02:00
cmd . CommandText = string . Format ( @"DROP TABLE IF EXISTS ""{0}""" , m_tempblocktable );
2013-03-08 22:24:54 +01:00
cmd . ExecuteNonQuery ();
}
2013-08-22 20:52:54 +02:00
catch ( Exception ex ) { if ( m_result != null ) m_result . AddWarning ( string . Format ( "Cleanup error: {0}" , ex . Message ), ex ); }
2013-03-08 22:24:54 +01:00
finally { m_tempblocktable = null ; }
}
}
public interface IBlockMarker : IDisposable
{
2013-04-27 10:20:15 +02:00
void SetBlockRestored ( long targetfileid , long index , string hash , long blocksize );
2013-08-22 20:52:54 +02:00
void SetAllBlocksMissing ( long targetfileid );
void SetAllBlocksRestored ( long targetfileid );
2013-08-23 23:37:17 +02:00
void Commit ( ILogWriter log );
2013-08-23 22:18:13 +02:00
void UpdateProcessed ( IOperationProgressUpdater writer );
2013-04-27 10:20:15 +02:00
System . Data . IDbTransaction Transaction { get ; }
2013-03-08 22:24:54 +01:00
}
private class BlockMarker : IBlockMarker
{
2013-05-20 13:48:44 +02:00
private System . Data . IDbCommand m_insertblockCommand ;
2013-08-22 20:52:54 +02:00
private System . Data . IDbCommand m_resetfileCommand ;
private System . Data . IDbCommand m_updateAsRestoredCommand ;
2013-08-23 22:18:13 +02:00
private System . Data . IDbCommand m_statUpdateCommand ;
private bool m_hasUpdates = false ;
2013-08-22 20:52:54 +02:00
2013-04-27 10:20:15 +02:00
private string m_updateTable ;
private string m_blocktablename ;
2013-05-20 13:48:44 +02:00
public System . Data . IDbTransaction Transaction { get { return m_insertblockCommand . Transaction ; } }
2013-03-08 22:24:54 +01:00
public BlockMarker ( System . Data . IDbConnection connection , string blocktablename , string filetablename )
{
2013-05-20 13:48:44 +02:00
m_insertblockCommand = connection . CreateCommand ();
2013-08-22 20:52:54 +02:00
m_resetfileCommand = connection . CreateCommand ();
m_updateAsRestoredCommand = connection . CreateCommand ();
2013-08-23 22:18:13 +02:00
m_statUpdateCommand = connection . CreateCommand ();
2013-05-20 13:48:44 +02:00
2013-08-22 20:52:54 +02:00
m_insertblockCommand . Transaction = connection . BeginTransaction ();
m_resetfileCommand . Transaction = m_insertblockCommand . Transaction ;
m_updateAsRestoredCommand . Transaction = m_insertblockCommand . Transaction ;
2013-08-23 22:18:13 +02:00
m_statUpdateCommand . Transaction = m_insertblockCommand . Transaction ;
2013-04-27 10:20:15 +02:00
m_blocktablename = blocktablename ;
2013-05-08 21:29:59 +02:00
m_updateTable = "UpdatedBlocks-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2013-04-27 10:20:15 +02:00
2013-05-20 13:48:44 +02:00
m_insertblockCommand . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" (""FileID"" INTEGER NOT NULL, ""Index"" INTEGER NOT NULL, ""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL)" , m_updateTable ));
m_insertblockCommand . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""FileID"", ""Index"", ""Hash"", ""Size"") VALUES (?, ?, ?, ?) " , m_updateTable );
m_insertblockCommand . AddParameters ( 4 );
2013-08-22 20:52:54 +02:00
m_resetfileCommand . CommandText = string . Format ( @"DELETE FROM ""{0}"" WHERE ""FileID"" = ?" , m_updateTable );
m_resetfileCommand . AddParameters ( 1 );
2013-05-20 13:48:44 +02:00
2013-08-22 20:52:54 +02:00
m_updateAsRestoredCommand . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""FileID"", ""Index"", ""Hash"", ""Size"") SELECT ""FileID"", ""Index"", ""Hash"", ""Size"" FROM ""{1}"" WHERE ""{1}"".""FileID"" = ?" , m_updateTable , m_blocktablename );
m_updateAsRestoredCommand . AddParameters ( 1 );
2013-08-23 22:18:13 +02:00
m_statUpdateCommand . CommandText = string . Format ( @"SELECT COUNT(DISTINCT ""FileID""), SUM(""Size"") FROM ""{0}"" WHERE ""Restored"" = 1 OR ""ID"" IN (SELECT ""{0}"".""ID"" FROM ""{0}"", ""{1}"" WHERE ""{0}"".""FileID"" = ""{1}"".""FileID"" AND ""{0}"".""Index"" = ""{1}"".""Index"" AND ""{0}"".""Hash"" = ""{1}"".""Hash"" AND ""{0}"".""Size"" = ""{1}"".""Size"" )" , m_blocktablename , m_updateTable );
}
public void UpdateProcessed ( IOperationProgressUpdater updater )
{
if (! m_hasUpdates )
return ;
m_hasUpdates = false ;
using ( var rd = m_statUpdateCommand . ExecuteReader ())
2013-08-27 10:23:21 +02:00
{
2013-08-23 22:18:13 +02:00
var filesprocessed = 0L ;
var processedsize = 0L ;
2013-08-27 10:23:21 +02:00
if ( rd . Read ())
{
var r0 = rd . GetValue ( 0 );
var r1 = rd . GetValue ( 1 );
2013-08-23 22:18:13 +02:00
2013-08-27 10:23:21 +02:00
if ( r0 != null && r0 != DBNull . Value )
filesprocessed = Convert . ToInt64 ( r0 );
if ( r1 != null && r1 != DBNull . Value )
processedsize = Convert . ToInt64 ( r1 );
}
2013-08-23 22:18:13 +02:00
updater . UpdatefilesProcessed ( filesprocessed , processedsize );
}
2013-08-22 20:52:54 +02:00
}
public void SetAllBlocksMissing ( long targetfileid )
{
2013-08-23 22:18:13 +02:00
m_hasUpdates = true ;
2013-08-22 20:52:54 +02:00
m_resetfileCommand . SetParameterValue ( 0 , targetfileid );
var r = m_resetfileCommand . ExecuteNonQuery ();
if ( r <= 0 )
throw new Exception ( "Unexpected reset result" );
2013-03-08 22:24:54 +01:00
}
2013-08-22 20:52:54 +02:00
public void SetAllBlocksRestored ( long targetfileid )
{
2013-08-23 22:18:13 +02:00
m_hasUpdates = true ;
2013-08-22 20:52:54 +02:00
m_updateAsRestoredCommand . SetParameterValue ( 0 , targetfileid );
var r = m_updateAsRestoredCommand . ExecuteNonQuery ();
if ( r <= 0 )
throw new Exception ( "Unexpected reset result" );
}
2013-04-27 10:20:15 +02:00
public void SetBlockRestored ( long targetfileid , long index , string hash , long size )
2013-03-08 22:24:54 +01:00
{
2013-08-23 22:18:13 +02:00
m_hasUpdates = true ;
2013-05-20 13:48:44 +02:00
m_insertblockCommand . SetParameterValue ( 0 , targetfileid );
m_insertblockCommand . SetParameterValue ( 1 , index );
m_insertblockCommand . SetParameterValue ( 2 , hash );
m_insertblockCommand . SetParameterValue ( 3 , size );
var r = m_insertblockCommand . ExecuteNonQuery ();
2013-03-08 22:24:54 +01:00
if ( r != 1 )
2013-04-27 10:20:15 +02:00
throw new Exception ( "Unexpected insert result" );
2013-03-08 22:24:54 +01:00
}
2013-05-20 13:48:44 +02:00
2013-08-23 23:37:17 +02:00
public void Commit ( ILogWriter log )
2013-03-08 22:24:54 +01:00
{
2013-08-23 23:37:17 +02:00
m_insertblockCommand . Parameters . Clear ();
var rc = m_insertblockCommand . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""Restored"" = 1 WHERE ""ID"" IN (SELECT ""{0}"".""ID"" FROM ""{0}"", ""{1}"" WHERE ""{0}"".""FileID"" = ""{1}"".""FileID"" AND ""{0}"".""Index"" = ""{1}"".""Index"" AND ""{0}"".""Hash"" = ""{1}"".""Hash"" AND ""{0}"".""Size"" = ""{1}"".""Size"" )" , m_blocktablename , m_updateTable ));
var nc = Convert . ToInt64 ( m_insertblockCommand . ExecuteScalar ( string . Format ( @"SELECT COUNT(*) FROM ""{0}"" " , m_updateTable )));
if ( rc != nc )
log . AddWarning ( string . Format ( "Inconsistency while marking blocks as updated. Updated blocks: {0}, Registered blocks: {1}" , rc , nc ), null );
m_insertblockCommand . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_updateTable ));
m_updateTable = null ;
2013-04-27 10:20:15 +02:00
2013-05-20 13:48:44 +02:00
var tr = m_insertblockCommand . Transaction ;
m_insertblockCommand . Dispose ();
m_insertblockCommand = null ;
2013-06-09 16:26:08 +02:00
using ( new Logging . Timer ( "CommitBlockMarker" ))
tr . Commit ();
2013-03-08 22:24:54 +01:00
tr . Dispose ();
}
public void Dispose ()
{
2013-04-27 10:20:15 +02:00
if ( m_updateTable != null )
{
try
{
2013-05-20 13:48:44 +02:00
m_insertblockCommand . Parameters . Clear ();
2013-08-23 23:36:25 +02:00
m_insertblockCommand . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_updateTable ));
2013-04-27 10:20:15 +02:00
}
catch { }
finally { m_updateTable = null ; }
}
2013-08-23 23:36:25 +02:00
2013-05-20 13:48:44 +02:00
if ( m_insertblockCommand != null )
2013-08-23 23:36:25 +02:00
try { m_insertblockCommand . Dispose (); }
catch { }
finally { m_insertblockCommand = null ; }
if ( m_resetfileCommand != null )
try { m_resetfileCommand . Dispose (); }
catch { }
finally { m_resetfileCommand = null ; }
if ( m_updateAsRestoredCommand != null )
try { m_updateAsRestoredCommand . Dispose (); }
catch { }
finally { m_updateAsRestoredCommand = null ; }
if ( m_statUpdateCommand != null )
try { m_statUpdateCommand . Dispose (); }
catch { }
finally { m_statUpdateCommand = null ; }
2013-03-08 22:24:54 +01:00
}
}
public IBlockMarker CreateBlockMarker ()
{
return new BlockMarker ( m_connection , m_tempblocktable , m_tempfiletable );
}
public override void Dispose ()
{
base . Dispose ();
DropRestoreTable ();
}
public IEnumerable < string > GetTargetFolders ()
{
2013-05-21 21:11:28 +02:00
using ( var cmd = m_connection . CreateCommand ())
using ( var rd = cmd . ExecuteReader ( string . Format ( @"SELECT ""TargetPath"" FROM ""{0}"" WHERE ""BlocksetID"" == ?" , m_tempfiletable ), FOLDER_BLOCKSET_ID ))
while ( rd . Read ())
yield return rd . GetValue ( 0 ). ToString ();
2013-03-08 22:24:54 +01:00
}
2013-04-28 12:19:21 +02:00
public interface IFastSource
{
string TargetPath { get ; }
long TargetFileID { get ; }
string SourcePath { get ; }
IEnumerable < IBlockEntry > Blocks { get ; }
}
public interface IBlockEntry
{
long Offset { get ; }
long Size { get ; }
long Index { get ; }
string Hash { get ; }
}
private class FastSource : IFastSource
{
private class BlockEntry : IBlockEntry
{
private System . Data . IDataReader m_rd ;
private long m_blocksize ;
public BlockEntry ( System . Data . IDataReader rd , long blocksize ) { m_rd = rd ; m_blocksize = blocksize ; }
public long Offset { get { return Convert . ToInt64 ( m_rd . GetValue ( 3 )) * m_blocksize ; } }
public long Index { get { return Convert . ToInt64 ( m_rd . GetValue ( 3 )); } }
public long Size { get { return Convert . ToInt64 ( m_rd . GetValue ( 5 )); } }
public string Hash { get { return m_rd . GetValue ( 4 ). ToString (); } }
}
private System . Data . IDataReader m_rd ;
private long m_blocksize ;
public FastSource ( System . Data . IDataReader rd , long blocksize ) { m_rd = rd ; m_blocksize = blocksize ; MoreData = true ; }
public bool MoreData { get ; private set ; }
public string TargetPath { get { return m_rd . GetValue ( 0 ). ToString (); } }
public long TargetFileID { get { return Convert . ToInt64 ( m_rd . GetValue ( 2 )); } }
public string SourcePath { get { return m_rd . GetValue ( 1 ). ToString (); } }
public IEnumerable < IBlockEntry > Blocks
{
get
{
var tid = this . TargetFileID ;
do
{
yield return new BlockEntry ( m_rd , m_blocksize );
} while (( MoreData = m_rd . Read ()) && tid == this . TargetFileID );
}
}
}
public IEnumerable < IFastSource > GetFilesAndSourceBlocksFast ()
{
var whereclause = string . Format ( @" ""{0}"".""ID"" = ""{1}"".""FileID"" AND ""{1}"".""Restored"" = 0 AND ""{0}"".""TargetPath"" != ""{0}"".""Path"" " , m_tempfiletable , m_tempblocktable );
var sourepaths = string . Format ( @"SELECT DISTINCT ""{0}"".""Path"" FROM ""{0}"", ""{1}"" WHERE " + whereclause , m_tempfiletable , m_tempblocktable );
var latestBlocksetIds = @"SELECT ""File"".""Path"", ""File"".""BlocksetID"", MAX(""FilesetEntry"".""Scantime"") FROM ""FilesetEntry"", ""File"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""File"".""Path"" IN (" + sourepaths + @") GROUP BY ""File"".""Path"" " ;
var sources = string . Format ( @"SELECT DISTINCT ""{0}"".""TargetPath"", ""{0}"".""Path"", ""{0}"".""ID"", ""{1}"".""Index"", ""{1}"".""Hash"", ""{1}"".""Size"" FROM ""{0}"", ""{1}"", ""File"", (" + latestBlocksetIds + @") S, ""Block"", ""BlocksetEntry"" WHERE ""BlocksetEntry"".""BlocksetID"" = ""S"".""BlocksetID"" AND ""BlocksetEntry"".""BlocksetID"" = ""File"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" AND ""{1}"".""Index"" = ""BlocksetEntry"".""Index"" AND ""{1}"".""Hash"" = ""Block"".""Hash"" AND ""{1}"".""Size"" = ""Block"".""Size"" AND ""S"".""Path"" = ""{0}"".""Path"" AND " + whereclause + @" ORDER BY ""{0}"".""ID"", ""{1}"".""Index"" " , m_tempfiletable , m_tempblocktable );
using ( var cmd = m_connection . CreateCommand ())
using ( var rd = cmd . ExecuteReader ( sources ))
{
if ( rd . Read ())
{
var more = false ;
do
{
var n = new FastSource ( rd , m_blocksize );
2013-04-29 23:56:01 +02:00
var tid = n . TargetFileID ;
2013-04-28 12:19:21 +02:00
yield return n ;
2013-04-29 23:56:01 +02:00
2013-04-28 12:19:21 +02:00
more = n . MoreData ;
2013-04-29 23:56:01 +02:00
while ( more && n . TargetFileID == tid )
more = rd . Read ();
2013-04-28 12:19:21 +02:00
} while ( more );
}
}
}
2013-03-08 22:24:54 +01:00
}
}