2024-04-15 08:24:01 +02:00
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
2024-02-28 15:45:30 +01:00
using System ;
2013-03-08 22:24:54 +01:00
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
2019-09-01 09:47:04 -07:00
using Duplicati.Library.Common ;
using Duplicati.Library.Common.IO ;
2013-05-08 20:17:07 +02:00
using Duplicati.Library.Main.Volumes ;
2019-09-29 20:16:28 -07:00
using Duplicati.Library.Utility ;
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
{
2018-10-13 16:53:28 -07:00
internal class LocalRestoreDatabase : LocalDatabase
2013-03-08 22:24:54 +01:00
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType ( typeof ( LocalRestoreDatabase ));
2019-10-19 10:56:21 -07:00
protected readonly string m_temptabsetguid = Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2013-03-08 22:24:54 +01:00
protected string m_tempfiletable ;
protected string m_tempblocktable ;
2016-02-27 22:04:48 +01:00
protected string m_fileprogtable ;
protected string m_totalprogtable ;
protected string m_filesnewlydonetable ;
2013-05-20 13:48:44 +02:00
protected DateTime m_restoreTime ;
2016-09-15 11:39:27 +02:00
public DateTime RestoreTime { get { return m_restoreTime ; } }
2013-03-08 22:24:54 +01:00
2015-04-08 20:33:30 +02:00
public LocalRestoreDatabase ( string path )
2016-04-06 20:40:34 +02:00
: this ( new LocalDatabase ( path , "Restore" , false ))
2013-03-08 22:24:54 +01:00
{
2016-04-06 20:40:34 +02:00
ShouldCloseConnection = true ;
2013-03-08 22:24:54 +01:00
}
2015-04-08 20:33:30 +02:00
public LocalRestoreDatabase ( LocalDatabase dbparent )
2013-04-04 20:34:26 +02:00
: base ( dbparent )
2013-03-08 22:24:54 +01:00
{
}
2016-02-27 22:04:48 +01:00
/// <summary>
/// Create tables and triggers for automatic tracking of progress during a restore operation.
/// This replaces continuous requerying of block progress by iterating over blocks table.
/// SQLite is much faster keeping information up to date with internal triggers.
/// </summary>
/// <param name="createFilesNewlyDoneTracker"> This allows to create another table that keeps track
/// of all files that are done (all data blocks restored). </param>
/// <remarks>
/// The method is prepared to create a table that keeps track of all files being done completely.
/// That means, it fires for files where the number of restored blocks equals the number of all blocks.
/// It is intended to be used for fast identification of fully restored files to trigger their verification.
/// It should be read after a commit and truncated after putting the files to a verification queue.
/// Note: If a file is done once and then set back to a none restored state, the file is not automatically removed.
2019-11-30 11:35:43 -08:00
/// But if it reaches a restored state later, it will be re-added (trigger will fire)
2016-02-27 22:04:48 +01:00
/// </remarks>
public void CreateProgressTracker ( bool createFilesNewlyDoneTracker )
{
m_fileprogtable = "FileProgress-" + this . m_temptabsetguid ;
m_totalprogtable = "TotalProgress-" + this . m_temptabsetguid ;
m_filesnewlydonetable = createFilesNewlyDoneTracker ? "FilesNewlyDone-" + this . m_temptabsetguid : null ;
using ( var cmd = m_connection . CreateCommand ())
{
// How to handle METADATA?
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_fileprogtable ));
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" ("
+ @" ""FileId"" INTEGER PRIMARY KEY "
+ @", ""TotalBlocks"" INTEGER NOT NULL, ""TotalSize"" INTEGER NOT NULL "
+ @", ""BlocksRestored"" INTEGER NOT NULL, ""SizeRestored"" INTEGER NOT NULL "
+ @")" , m_fileprogtable ));
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_totalprogtable ));
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" ("
+ @" ""TotalFiles"" INTEGER NOT NULL, ""TotalBlocks"" INTEGER NOT NULL, ""TotalSize"" INTEGER NOT NULL "
+ @", ""FilesFullyRestored"" INTEGER NOT NULL, ""FilesPartiallyRestored"" INTEGER NOT NULL "
+ @", ""BlocksRestored"" INTEGER NOT NULL, ""SizeRestored"" INTEGER NOT NULL "
+ @")" , m_totalprogtable ));
if ( createFilesNewlyDoneTracker )
{
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_filesnewlydonetable ));
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" ("
+ @" ""ID"" INTEGER PRIMARY KEY "
+ @")" , m_filesnewlydonetable ));
}
try
{
// Initialize statistics with File- and Block-Data (it is valid to already have restored blocks in files)
// A rebuild with this function should be valid anytime.
// Note: FilesNewlyDone is NOT initialized, as in initialization nothing is really new.
string sql ;
sql = string . Format (
@" INSERT INTO ""{0}"" (""FileId"", ""TotalBlocks"", ""TotalSize"", ""BlocksRestored"", ""SizeRestored"") "
2016-03-04 19:30:32 +01:00
+ @" SELECT ""F"".""ID"", IFNULL(COUNT(""B"".""ID""), 0), IFNULL(SUM(""B"".""Size""), 0)"
+ @" , IFNULL(COUNT(CASE ""B"".""Restored"" WHEN 1 THEN ""B"".""ID"" ELSE NULL END), 0) "
+ @" , IFNULL(SUM(CASE ""B"".""Restored"" WHEN 1 THEN ""B"".""Size"" ELSE 0 END), 0) "
2019-11-30 11:35:43 -08:00
+ @" FROM ""{1}"" ""F"" LEFT JOIN ""{2}"" ""B""" // allow for empty files (no data Blocks)
2016-02-27 22:04:48 +01:00
+ @" ON ""B"".""FileID"" = ""F"".""ID"" "
+ @" WHERE ""B"".""Metadata"" IS NOT 1 " // Use "IS" because of Left Join
+ @" GROUP BY ""F"".""ID"" "
, m_fileprogtable , m_tempfiletable , m_tempblocktable );
// Will be one row per file.
2018-09-26 21:12:13 -07:00
cmd . ExecuteNonQuery ( sql );
2016-02-27 22:04:48 +01:00
sql = string . Format (
@"INSERT INTO ""{0}"" ("
+ @" ""TotalFiles"", ""TotalBlocks"", ""TotalSize"" "
+ @", ""FilesFullyRestored"", ""FilesPartiallyRestored"", ""BlocksRestored"", ""SizeRestored"""
+ @" ) "
2016-03-04 19:30:32 +01:00
+ @" SELECT IFNULL(COUNT(""P"".""FileId""), 0), IFNULL(SUM(""P"".""TotalBlocks""), 0), IFNULL(SUM(""P"".""TotalSize""), 0) "
+ @" , IFNULL(COUNT(CASE WHEN ""P"".""BlocksRestored"" = ""P"".""TotalBlocks"" THEN 1 ELSE NULL END), 0) "
+ @" , IFNULL(COUNT(CASE WHEN ""P"".""BlocksRestored"" BETWEEN 1 AND ""P"".""TotalBlocks"" - 1 THEN 1 ELSE NULL END), 0) "
+ @" , IFNULL(SUM(""P"".""BlocksRestored""), 0), IFNULL(SUM(""P"".""SizeRestored""), 0) "
2016-02-27 22:04:48 +01:00
+ @" FROM ""{1}"" ""P"" "
, m_totalprogtable , m_fileprogtable );
// Will result in a single line (no support to also track metadata)
2018-09-26 21:12:13 -07:00
cmd . ExecuteNonQuery ( sql );
2016-02-27 22:04:48 +01:00
// Finally we create TRIGGERs to keep all our statistics up to date.
// This is lightning fast, as SQLite uses internal hooks and our indices to do the update magic.
// Note: We do assume that neither files nor blocks will be added or deleted during restore process
// and that the size of each block stays constant so there is no need to track that information
// with additional INSERT and DELETE triggers.
// A trigger to update the file-stat entry each time a block changes restoration state.
sql = string . Format (
@"CREATE TEMPORARY TRIGGER ""TrackRestoredBlocks_{1}"" AFTER UPDATE OF ""Restored"" ON ""{1}"" "
+ @" WHEN OLD.""Restored"" != NEW.""Restored"" AND NEW.""Metadata"" = 0 "
+ @" BEGIN UPDATE ""{0}"" "
+ @" SET ""BlocksRestored"" = ""{0}"".""BlocksRestored"" + (NEW.""Restored"" - OLD.""Restored"") "
+ @" , ""SizeRestored"" = ""{0}"".""SizeRestored"" + ((NEW.""Restored"" - OLD.""Restored"") * NEW.Size) "
+ @" WHERE ""{0}"".""FileId"" = NEW.""FileID"" "
+ @" ; END "
, m_fileprogtable , m_tempblocktable );
cmd . ExecuteNonQuery ( sql );
// A trigger to update total stats each time a file stat changed (nested triggering by file-stats)
sql = string . Format (
@"CREATE TEMPORARY TRIGGER ""UpdateTotalStats_{1}"" AFTER UPDATE ON ""{1}"" "
+ @" BEGIN UPDATE ""{0}"" "
+ @" SET ""FilesFullyRestored"" = ""{0}"".""FilesFullyRestored"" "
+ @" + (CASE WHEN NEW.""BlocksRestored"" = NEW.""TotalBlocks"" THEN 1 ELSE 0 END) "
+ @" - (CASE WHEN OLD.""BlocksRestored"" = OLD.""TotalBlocks"" THEN 1 ELSE 0 END) "
+ @" , ""FilesPartiallyRestored"" = ""{0}"".""FilesPartiallyRestored"" "
+ @" + (CASE WHEN NEW.""BlocksRestored"" BETWEEN 1 AND NEW.""TotalBlocks"" - 1 THEN 1 ELSE 0 END) "
+ @" - (CASE WHEN OLD.""BlocksRestored"" BETWEEN 1 AND OLD.""TotalBlocks"" - 1 THEN 1 ELSE 0 END) "
+ @" , ""BlocksRestored"" = ""{0}"".""BlocksRestored"" + NEW.""BlocksRestored"" - OLD.""BlocksRestored"" " // simple delta
+ @" , ""SizeRestored"" = ""{0}"".""SizeRestored"" + NEW.""SizeRestored"" - OLD.""SizeRestored"" " // simple delta
+ @" ; END "
, m_totalprogtable , m_fileprogtable );
cmd . ExecuteNonQuery ( sql );
if ( createFilesNewlyDoneTracker )
{
// A trigger checking if a file is done (all blocks restored in file-stat) (nested triggering by file-stats)
sql = string . Format (
@"CREATE TEMPORARY TRIGGER ""UpdateFilesNewlyDone_{1}"" AFTER UPDATE OF ""BlocksRestored"", ""TotalBlocks"" ON ""{1}"" "
+ @" WHEN NEW.""BlocksRestored"" = NEW.""TotalBlocks"" "
+ @" BEGIN "
+ @" INSERT OR IGNORE INTO ""{0}"" (""ID"") VALUES (NEW.""FileId""); "
+ @" END "
, m_filesnewlydonetable , m_fileprogtable );
cmd . ExecuteNonQuery ( sql );
}
}
catch ( Exception ex )
{
m_fileprogtable = null ;
m_totalprogtable = null ;
2018-05-15 11:29:08 +02:00
Logging . Log . WriteWarningMessage ( LOGTAG , "ProgressTrackerSetupError" , ex , "Failed to set up progress tracking tables" );
2016-02-27 22:04:48 +01:00
throw ;
}
}
}
2018-03-12 14:07:11 +01:00
public Tuple < long , long > PrepareRestoreFilelist ( DateTime restoretime , long [] versions , Library . Utility . IFilter filter )
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
{
2019-09-01 09:47:04 -07:00
var filesetIds = GetFilesetIDs ( Library . Utility . Utility . NormalizeDateTime ( restoretime ), versions ). ToList ();
2013-08-24 22:27:30 +02:00
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 );
2015-01-24 21:59:53 +01:00
m_restoreTime = ParseFromEpochSeconds ( cmd . ExecuteScalarInt64 ( @"SELECT ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" = ?" , 0 , filesetId ));
2013-08-24 22:27:30 +02:00
var ix = this . FilesetTimes . Select (( value , index ) => new { value . Key , index })
. Where ( n => n . Key == filesetId )
. Select ( pair => pair . index + 1 )
. FirstOrDefault () - 1 ;
2018-03-12 14:07:11 +01:00
Logging . Log . WriteInformationMessage ( LOGTAG , "SearchingBackup" , "Searching backup {0} ({1}) ..." , ix , m_restoreTime );
2013-08-24 22:27:30 +02:00
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 ));
2017-01-06 23:01:54 +01:00
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, ""DataVerified"" BOOLEAN NOT NULL) " , m_tempfiletable ));
2014-11-05 21:43:08 +01:00
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, ""Metadata"" BOOLEAN NOT NULL)" , m_tempblocktable ));
2015-11-16 12:57:34 +01:00
cmd . ExecuteNonQuery ( string . Format ( @"CREATE INDEX ""{0}_Index"" ON ""{0}"" (""TargetPath"")" , m_tempfiletable ));
cmd . ExecuteNonQuery ( string . Format ( @"CREATE INDEX ""{0}_HashSizeIndex"" ON ""{0}"" (""Hash"", ""Size"")" , m_tempblocktable ));
2016-02-18 19:31:17 +01:00
// better suited to speed up commit on UpdateBlocks
cmd . ExecuteNonQuery ( string . Format ( @"CREATE INDEX ""{0}_FileIdIndexIndex"" ON ""{0}"" (""FileId"", ""Index"")" , m_tempblocktable ));
2015-11-16 12:57:34 +01:00
2018-06-14 10:12:24 +02:00
// TODO: Optimize to use the path prefix
2013-08-24 22:27:30 +02:00
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
2016-03-05 02:00:28 +01:00
cmd . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Path"", ""BlocksetID"", ""MetadataID"", ""DataVerified"") SELECT ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"", 0 FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetEntry"".""FilesetID"" = ? " , m_tempfiletable );
2013-08-24 22:27:30 +02:00
cmd . AddParameter ( filesetId );
cmd . ExecuteNonQuery ();
}
2019-09-29 20:16:28 -07:00
else if ( Library . Utility . Utility . IsFSCaseSensitive && filter is FilterExpression expression && expression . Type == Duplicati . Library . Utility . FilterType . Simple )
2013-08-24 22:27:30 +02:00
{
// If we get a list of filenames, the lookup table is faster
2015-09-15 19:29:58 +02:00
// unfortunately we cannot do this if the filesystem is case sensitive as
// SQLite only supports ASCII compares
2013-08-24 22:27:30 +02:00
using ( var tr = m_connection . BeginTransaction ())
2013-03-15 21:16:29 +01:00
{
2019-09-29 20:16:28 -07:00
var p = expression . GetSimpleList ();
2013-08-24 22:27:30 +02:00
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 ();
}
2016-03-05 02:00:28 +01:00
cmd . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Path"", ""BlocksetID"", ""MetadataID"", ""DataVerified"") SELECT ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"", 0 FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetEntry"".""FilesetID"" = ? AND ""Path"" IN (SELECT DISTINCT ""Path"" FROM ""{1}"") " , m_tempfiletable , m_filenamestable );
2013-08-24 22:27:30 +02:00
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 ());
2015-01-24 21:59:53 +01:00
var actualrestoretime = ParseFromEpochSeconds ( cmd . ExecuteScalarInt64 ( @"SELECT ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" = ?" , 0 , filesetId ));
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "FilesNotFoundInBackupList" , null , "{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 );
2013-08-24 22:27:30 +02:00
cmd . Parameters . Clear ();
}
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_filenamestable ));
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "CommitPrepareFileset" , "CommitPrepareFileset" ))
2013-08-24 22:27:30 +02:00
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
2018-05-24 20:16:46 -07:00
cmd . CommandText = @"SELECT ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"" FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetID"" = ?" ;
2013-08-24 22:27:30 +02:00
cmd . AddParameter ( filesetId );
object [] values = new object [ 3 ];
using ( var cmd2 = m_connection . CreateCommand ())
{
2016-03-05 02:00:28 +01:00
cmd2 . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Path"", ""BlocksetID"", ""MetadataID"", ""DataVerified"") VALUES (?,?,?,0)" , m_tempfiletable );
2013-08-24 22:27:30 +02:00
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 ())
{
2015-01-24 21:59:53 +01:00
filecount = rd . ConvertValueToInt64 ( 0 , 0 );
filesize = rd . ConvertValueToInt64 ( 1 , 0 );
2013-08-25 13:16:12 +02:00
}
2013-08-24 22:27:30 +02:00
if ( filecount > 0 )
{
2018-03-12 14:07:11 +01:00
Logging . Log . WriteVerboseMessage ( LOGTAG , "RestoreTargetFileCount" , "Needs to restore {0} files ({1})" , filecount , Library . Utility . Utility . FormatSizeString ( filesize ));
2013-08-24 22:27:30 +02:00
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
}
2017-01-06 23:01:54 +01:00
public string GetFirstPath ()
{
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 ();
if ( v0 == null || v0 == DBNull . Value )
return null ;
return v0 . ToString ();
}
}
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 = "" ;
2017-01-06 23:01:54 +01:00
if ( v0 != null && v0 != DBNull . Value )
2013-03-08 22:24:54 +01:00
maxpath = v0 . ToString ();
2018-10-27 12:17:07 +02:00
var dirsep = Util . GuessDirSeparator ( maxpath );
2017-01-06 23:01:54 +01:00
2013-03-08 22:24:54 +01:00
cmd . CommandText = string . Format ( @"SELECT COUNT(*) FROM ""{0}""" , m_tempfiletable );
2015-01-24 21:59:53 +01:00
var filecount = cmd . ExecuteScalarInt64 (- 1 );
2013-03-08 22:24:54 +01:00
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 )
{
2018-10-27 12:17:07 +02:00
var mp = Util . AppendDirSeparator ( maxpath , dirsep );
2013-03-08 22:24:54 +01:00
cmd . SetParameterValue ( 0 , mp . Length );
cmd . SetParameterValue ( 1 , mp );
2015-01-24 21:59:53 +01:00
foundfiles = cmd . ExecuteScalarInt64 (- 1 );
2013-03-08 22:24:54 +01:00
if ( filecount != foundfiles )
{
var oldlen = maxpath . Length ;
2017-01-06 23:01:54 +01:00
var lix = maxpath . LastIndexOf ( dirsep , maxpath . Length - 2 , StringComparison . Ordinal );
maxpath = maxpath . Substring ( 0 , lix + 1 );
2014-03-24 11:49:25 +01:00
if ( string . IsNullOrWhiteSpace ( maxpath ) || maxpath . Length == oldlen )
2013-03-08 22:24:54 +01:00
maxpath = "" ;
}
}
2018-10-27 12:17:07 +02:00
return maxpath == "" ? "" : Util . AppendDirSeparator ( maxpath , dirsep );
2013-03-08 22:24:54 +01:00
}
}
public void SetTargetPaths ( string largest_prefix , string destination )
2016-09-15 11:39:27 +02:00
{
2018-10-27 12:17:07 +02:00
var dirsep = Util . GuessDirSeparator ( string . IsNullOrWhiteSpace ( largest_prefix ) ? GetFirstPath () : largest_prefix );
2017-01-06 23:01:54 +01:00
2016-09-15 11:39:27 +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
2017-01-06 23:01:54 +01:00
2018-11-03 09:26:04 +01:00
if ( Platform . IsClientPosix && dirsep == "\\" )
2017-01-06 23:01:54 +01:00
{
2016-09-15 11:39:27 +02:00
// For Win -> Linux, we remove the colon from the drive letter, and use the drive letter as root folder
2024-04-15 08:24:01 +02:00
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 ));
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == '\\' THEN '\\' || SUBSTR(""Path"", 2) ELSE ""Path"" END" , m_tempfiletable ));
2017-01-06 23:01:54 +01:00
}
2018-11-02 22:13:25 +01:00
else if ( Platform . IsClientWindows && dirsep == "/" )
2017-01-06 23:01:54 +01:00
{
2016-09-15 11:39:27 +02:00
// For Linux -> Win, we use the temporary folder's drive as the root path
2024-04-15 08:24:01 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 1) == '/' THEN ? || SUBSTR(""Path"", 2) ELSE ""Path"" END" , m_tempfiletable ), Util . AppendDirSeparator ( System . IO . Path . GetPathRoot ( Library . Utility . TempFolder . SystemTempPath )). Replace ( "\\" , "/" ));
2017-01-06 23:01:54 +01:00
}
2017-01-14 12:46:59 +01:00
else
{
// Same OS, just use the path directly
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""Targetpath"" = ""Path"" " , m_tempfiletable ));
}
2016-09-15 11:39:27 +02:00
}
else
{
if ( string . IsNullOrEmpty ( largest_prefix ))
{
2017-01-06 23:01:54 +01:00
//Special case, restoring to new folder, but files are from different drives (no shared root on Windows)
// We use the format <restore path> / <drive letter> / <source path>
2024-04-15 08:24:01 +02:00
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 ));
2017-01-06 23:01:54 +01:00
// For UNC paths, we use \\server\folder -> <restore path> / <servername> / <source path>
2024-04-15 08:24:01 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""TargetPath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == '\\' THEN SUBSTR(""Path"", 2) ELSE ""TargetPath"" END" , m_tempfiletable ));
2016-09-15 11:39:27 +02:00
}
else
{
2018-10-27 12:17:07 +02:00
largest_prefix = Util . AppendDirSeparator ( largest_prefix , dirsep );
2017-01-06 23:01:54 +01:00
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""TargetPath"" = SUBSTR(""Path"", ?)" , m_tempfiletable ), largest_prefix . Length + 1 );
2016-09-15 11:39:27 +02:00
}
2017-01-06 23:01:54 +01:00
}
// Cross-os path remapping support
2018-11-03 09:26:04 +01:00
if ( Platform . IsClientPosix && dirsep == "\\" )
2017-01-06 23:01:54 +01:00
// For Win paths on Linux
2024-04-15 08:24:01 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""TargetPath"" = REPLACE(""TargetPath"", '\', '/')" , m_tempfiletable ));
2018-11-02 22:13:25 +01:00
else if ( Platform . IsClientWindows && dirsep == "/" )
2017-01-06 23:01:54 +01:00
// For Linux paths on Windows
2024-04-15 08:24:01 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""TargetPath"" = REPLACE(REPLACE(""TargetPath"", '\', '_'), '/', '\')" , m_tempfiletable ));
2017-01-06 23:01:54 +01:00
if (! string . IsNullOrEmpty ( destination ))
{
// Paths are now relative with target-os naming system
// so we prefix them with the target path
2018-10-27 12:17:07 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"UPDATE ""{0}"" SET ""TargetPath"" = ? || ""TargetPath"" " , m_tempfiletable ), Util . AppendDirSeparator ( destination ));
2017-01-06 23:01:54 +01:00
}
2013-03-08 22:24:54 +01:00
}
}
2018-03-12 14:07:11 +01:00
public void FindMissingBlocks ( bool skipMetadata )
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
{
2014-11-05 21:43:08 +01:00
cmd . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""FileID"", ""Index"", ""Hash"", ""Size"", ""Restored"", ""Metadata"") SELECT DISTINCT ""{1}"".""ID"", ""BlocksetEntry"".""Index"", ""Block"".""Hash"", ""Block"".""Size"", 0, 0 FROM ""{1}"", ""BlocksetEntry"", ""Block"" WHERE ""{1}"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" " , m_tempblocktable , m_tempfiletable );
var p1 = cmd . ExecuteNonQuery ();
2014-11-19 14:15:11 +01:00
int p2 = 0 ;
if (! skipMetadata )
{
cmd . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""FileID"", ""Index"", ""Hash"", ""Size"", ""Restored"", ""Metadata"") SELECT DISTINCT ""{1}"".""ID"", ""BlocksetEntry"".""Index"", ""Block"".""Hash"", ""Block"".""Size"", 0, 1 FROM ""{1}"", ""BlocksetEntry"", ""Block"", ""Metadataset"" WHERE ""{1}"".""MetadataID"" = ""Metadataset"".""ID"" AND ""Metadataset"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" " , m_tempblocktable , m_tempfiletable );
p2 = cmd . ExecuteNonQuery ();
}
2014-11-05 21:43:08 +01:00
2018-03-12 14:07:11 +01:00
var size = cmd . ExecuteScalarInt64 ( string . Format ( @"SELECT SUM(""Size"") FROM ""{0}"" " , m_tempblocktable ), 0 );
Logging . Log . WriteVerboseMessage ( LOGTAG , "RestoreSourceSize" , "Restore list contains {0} blocks with a total size of {1}" , p1 + p2 , Library . Utility . Utility . FormatSizeString ( size ));
2013-03-08 22:24:54 +01:00
}
}
2016-09-15 11:39:27 +02:00
public void UpdateTargetPath ( long ID , string newname )
{
2013-05-20 13:48:44 +02:00
using ( var cmd = m_connection . CreateCommand ())
2016-09-15 11:39:27 +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 Offset { get ; }
2014-11-05 21:43:08 +01:00
bool IsMetadata { get ; }
2013-03-08 22:24:54 +01:00
}
public interface IBlockDescriptor
{
string Hash { get ; }
long Size { get ; }
long Offset { get ; }
long Index { get ; }
2014-11-05 21:43:08 +01:00
bool IsMetadata { get ; }
2013-03-08 22:24:54 +01:00
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 ; }
2015-03-04 16:23:44 +01:00
long Length { 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 ; }
}
2014-11-04 15:34:20 +01:00
private class ExistingFile : IExistingFile
{
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_reader ;
2014-11-04 15:34:20 +01:00
2014-11-05 21:43:08 +01:00
public ExistingFile ( System . Data . IDataReader rd ) { m_reader = rd ; HasMore = true ; }
2014-11-04 15:34:20 +01:00
public string TargetPath { get { return m_reader . ConvertValueToString ( 0 ); } }
public string TargetHash { get { return m_reader . ConvertValueToString ( 1 ); } }
2015-01-24 21:59:53 +01:00
public long TargetFileID { get { return m_reader . ConvertValueToInt64 ( 2 ); } }
2014-11-04 15:34:20 +01:00
public long Length { get { return m_reader . ConvertValueToInt64 ( 3 ); } }
public bool HasMore { get ; private set ; }
private class ExistingFileBlock : IExistingFileBlock
{
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_reader ;
2014-11-04 15:34:20 +01:00
public ExistingFileBlock ( System . Data . IDataReader rd ) { m_reader = rd ; }
public string Hash { get { return m_reader . ConvertValueToString ( 4 ); } }
public long Index { get { return m_reader . ConvertValueToInt64 ( 5 ); } }
public long Size { get { return m_reader . ConvertValueToInt64 ( 6 ); } }
}
public IEnumerable < IExistingFileBlock > Blocks
{
get
{
string p = this . TargetPath ;
while ( HasMore && p == this . TargetPath )
{
yield return new ExistingFileBlock ( m_reader );
HasMore = m_reader . Read ();
}
}
}
public static IEnumerable < IExistingFile > GetExistingFilesWithBlocks ( System . Data . IDbConnection connection , string tablename )
{
using ( var cmd = connection . CreateCommand ())
{
2014-11-05 21:43:08 +01:00
cmd . CommandText = string . Format ( @"SELECT ""{0}"".""TargetPath"", ""Blockset"".""FullHash"", ""{0}"".""ID"", ""Blockset"".""Length"", ""Block"".""Hash"", ""BlocksetEntry"".""Index"", ""Block"".""Size"" FROM ""{0}"", ""Blockset"", ""BlocksetEntry"", ""Block"" WHERE ""{0}"".""BlocksetID"" = ""Blockset"".""ID"" AND ""BlocksetEntry"".""BlocksetID"" = ""{0}"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" ORDER BY ""{0}"".""TargetPath"", ""BlocksetEntry"".""Index""" , tablename );
2014-11-04 15:34:20 +01:00
using ( var rd = cmd . ExecuteReader ())
if ( rd . Read ())
{
var more = true ;
while ( more )
{
var f = new ExistingFile ( rd );
string current = f . TargetPath ;
yield return f ;
more = f . HasMore ;
while ( more && current == f . TargetPath )
more = rd . Read ();
}
}
}
}
}
2013-03-08 22:24:54 +01:00
public IEnumerable < IExistingFile > GetExistingFilesWithBlocks ()
{
2014-11-04 15:34:20 +01:00
return ExistingFile . GetExistingFilesWithBlocks ( m_connection , m_tempfiletable );
}
private class LocalBlockSource : ILocalBlockSource
{
private class BlockDescriptor : IBlockDescriptor
{
private class BlockSource : IBlockSource
{
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_reader ;
2014-11-04 15:34:20 +01:00
public BlockSource ( System . Data . IDataReader rd ) { m_reader = rd ; }
public string Path { get { return m_reader . ConvertValueToString ( 6 ); } }
public long Offset { get { return m_reader . ConvertValueToInt64 ( 7 ); } }
2014-11-05 21:43:08 +01:00
public bool IsMetadata { get { return false ; } }
2014-11-04 15:34:20 +01:00
}
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_reader ;
2014-11-05 21:43:08 +01:00
public BlockDescriptor ( System . Data . IDataReader rd ) { m_reader = rd ; HasMore = true ; }
2014-11-04 15:34:20 +01:00
private string TargetPath { get { return m_reader . ConvertValueToString ( 0 ); } }
public string Hash { get { return m_reader . ConvertValueToString ( 2 ); } }
public long Offset { get { return m_reader . ConvertValueToInt64 ( 3 ); } }
public long Index { get { return m_reader . ConvertValueToInt64 ( 4 ); } }
public long Size { get { return m_reader . ConvertValueToInt64 ( 5 ); } }
2014-11-05 21:43:08 +01:00
public bool IsMetadata { get { return !( m_reader . ConvertValueToInt64 ( 9 ) == 0 ); } }
2014-11-04 15:34:20 +01:00
public bool HasMore { get ; private set ; }
public IEnumerable < IBlockSource > Blocksources
{
get
{
var p = this . TargetPath ;
var h = this . Hash ;
var s = this . Size ;
while ( HasMore && p == this . TargetPath && h == this . Hash && s == this . Size )
{
yield return new BlockSource ( m_reader );
HasMore = m_reader . Read ();
}
}
}
}
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_reader ;
2014-11-05 21:43:08 +01:00
public LocalBlockSource ( System . Data . IDataReader rd ) { m_reader = rd ; HasMore = true ; }
2014-11-04 15:34:20 +01:00
public string TargetPath { get { return m_reader . ConvertValueToString ( 0 ); } }
public long TargetFileID { get { return m_reader . ConvertValueToInt64 ( 1 ); } }
public bool HasMore { get ; private set ; }
public IEnumerable < IBlockDescriptor > Blocks
{
get
{
var p = this . TargetPath ;
while ( HasMore && p == this . TargetPath )
{
var c = new BlockDescriptor ( m_reader );
var h = c . Hash ;
var s = c . Size ;
yield return c ;
HasMore = c . HasMore ;
while ( HasMore && c . Hash == h && c . Size == s && this . TargetPath == p )
HasMore = m_reader . Read ();
}
}
}
2014-11-19 14:15:11 +01:00
public static IEnumerable < ILocalBlockSource > GetFilesAndSourceBlocks ( System . Data . IDbConnection connection , string filetablename , string blocktablename , long blocksize , bool skipMetadata )
2014-11-04 15:34:20 +01:00
{
using ( var cmd = connection . CreateCommand ())
{
2014-11-23 22:14:24 +01:00
// TODO: Skip metadata as required
2014-11-05 21:43:08 +01:00
cmd . CommandText = string . Format ( @"SELECT DISTINCT ""A"".""TargetPath"", ""A"".""ID"", ""B"".""Hash"", (""B"".""Index"" * {2}), ""B"".""Index"", ""B"".""Size"", ""C"".""Path"", (""D"".""Index"" * {2}), ""E"".""Size"", ""B"".""Metadata"" FROM ""{0}"" ""A"", ""{1}"" ""B"", ""File"" ""C"", ""BlocksetEntry"" ""D"", ""Block"" E WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""C"".""BlocksetID"" = ""D"".""BlocksetID"" AND ""D"".""BlockID"" = ""E"".""ID"" AND ""B"".""Hash"" = ""E"".""Hash"" AND ""B"".""Size"" = ""E"".""Size"" AND ""B"".""Restored"" = 0" , filetablename , blocktablename , blocksize );
2014-11-04 15:34:20 +01:00
using ( var rd = cmd . ExecuteReader ())
{
if ( rd . Read ())
{
var more = true ;
while ( more )
{
var f = new LocalBlockSource ( rd );
string current = f . TargetPath ;
yield return f ;
more = f . HasMore ;
while ( more && current == f . TargetPath )
more = rd . Read ();
}
}
}
}
}
2013-03-08 22:24:54 +01:00
}
2015-04-08 20:33:30 +02:00
public IEnumerable < ILocalBlockSource > GetFilesAndSourceBlocks ( bool skipMetadata , long blocksize )
2013-03-08 22:24:54 +01:00
{
2015-04-08 20:33:30 +02:00
return LocalBlockSource . GetFilesAndSourceBlocks ( m_connection , m_tempfiletable , m_tempblocktable , blocksize , skipMetadata );
2013-03-08 22:24:54 +01:00
}
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 ())
{
2016-03-03 20:15:27 +01:00
// Return order from SQLite-DISTINCT is likely to be sorted by Name, which is bad for restore.
// If the end of very large files (e.g. iso's) is restored before the beginning, most OS write out zeros to fill the file.
// If we manage to get the volumes in an order restoring front blocks first, this can save time.
2019-11-30 11:35:43 -08:00
// An optimal algorithm would build a dependency net with cycle resolution to find the best near topological
2016-03-03 20:15:27 +01:00
// order of volumes, but this is a bit too fancy here.
2019-11-30 11:35:43 -08:00
// We will just put a very simple heuristic to work, that will try to prefer volumes containing lower block indexes:
2016-03-03 23:18:47 +01:00
// We just order all volumes by the maximum block index they contain. This query is slow, but should be worth the effort.
// Now it is likely to restore all files from front to back. Large files will always be done last.
2016-03-03 20:15:27 +01:00
// One could also use like the average block number in a volume, that needs to be measured.
cmd . CommandText = string . Format (
@"SELECT ""RV"".""Name"", ""RV"".""Hash"", ""RV"".""Size"", ""BB"".""MaxIndex"" "
+ @" FROM ""RemoteVolume"" ""RV"" INNER JOIN "
+ @" (SELECT ""B"".""VolumeID"", MAX(""TB"".""Index"") as ""MaxIndex"" "
+ @" FROM ""Block"" ""B"", ""{0}"" ""TB"" "
2016-03-03 23:18:47 +01:00
+ @" WHERE ""TB"".""Restored"" = 0 "
+ @" AND ""B"".""Hash"" = ""TB"".""Hash"" "
2016-03-03 20:15:27 +01:00
+ @" AND ""B"".""Size"" = ""TB"".""Size"" "
+ @" GROUP BY ""B"".""VolumeID"" "
+ @" ) as ""BB"" ON ""RV"".""ID"" = ""BB"".""VolumeID"" "
+ @" ORDER BY ""BB"".""MaxIndex"" "
, 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 (
2015-01-24 21:59:53 +01:00
rd . ConvertValueToString ( 0 ),
rd . ConvertValueToString ( 1 ),
rd . ConvertValueToInt64 ( 2 , - 1 )
2013-07-01 11:58:33 +02:00
);
2013-03-08 22:24:54 +01:00
}
}
}
}
2014-11-05 21:43:08 +01:00
public interface IFilesAndMetadata : IDisposable
{
IEnumerable < IVolumePatch > FilesWithMissingBlocks { get ; }
IEnumerable < IVolumePatch > MetadataWithMissingBlocks { get ; }
}
private class FilesAndMetadata : IFilesAndMetadata
{
2018-05-23 21:18:01 -07:00
private readonly string m_tmptable ;
private readonly string m_filetablename ;
private readonly string m_blocktablename ;
private readonly long m_blocksize ;
2014-11-05 21:43:08 +01:00
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDbConnection m_connection ;
2014-11-05 21:43:08 +01:00
public FilesAndMetadata ( System . Data . IDbConnection connection , string filetablename , string blocktablename , long blocksize , BlockVolumeReader curvolume )
{
m_filetablename = filetablename ;
m_blocktablename = blocktablename ;
m_blocksize = blocksize ;
m_connection = connection ;
using ( var c = m_connection . CreateCommand ())
{
m_tmptable = "VolumeFiles-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
c . CommandText = string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" ( ""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL )" , m_tmptable );
c . ExecuteNonQuery ();
2015-11-16 12:57:34 +01:00
2014-11-05 21:43:08 +01:00
c . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Hash"", ""Size"") VALUES (?,?)" , m_tmptable );
c . AddParameters ( 2 );
foreach ( var s in curvolume . Blocks )
{
c . SetParameterValue ( 0 , s . Key );
c . SetParameterValue ( 1 , s . Value );
c . ExecuteNonQuery ();
}
2015-11-16 12:57:34 +01:00
2016-02-18 19:31:17 +01:00
// The index _HashSizeIndex is not needed anymore. Index on "Blocks-..." is used on Join in GetMissingBlocks
2015-11-16 12:57:34 +01:00
2014-11-05 21:43:08 +01:00
}
}
public void Dispose ()
{
if ( m_tmptable != null )
using ( var c = m_connection . CreateCommand ())
{
c . CommandText = string . Format ( @"DROP TABLE IF EXISTS ""{0}""" , m_tmptable );
c . ExecuteNonQuery ();
}
}
private class VolumePatch : IVolumePatch
{
private class PatchBlock : IPatchBlock
{
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_reader ;
2014-11-05 21:43:08 +01:00
public PatchBlock ( System . Data . IDataReader rd ) { m_reader = rd ; }
public long Offset { get { return m_reader . ConvertValueToInt64 ( 2 ); } }
public long Size { get { return m_reader . ConvertValueToInt64 ( 3 ); } }
public string Key { get { return m_reader . ConvertValueToString ( 4 ); } }
}
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_reader ;
2014-11-05 21:43:08 +01:00
public VolumePatch ( System . Data . IDataReader rd ) { m_reader = rd ; HasMore = true ; }
public string Path { get { return m_reader . ConvertValueToString ( 0 ); } }
public long FileID { get { return m_reader . ConvertValueToInt64 ( 1 ); } }
public bool HasMore { get ; private set ; }
public IEnumerable < IPatchBlock > Blocks
{
get
{
string p = this . Path ;
while ( HasMore && p == this . Path )
{
yield return new PatchBlock ( m_reader );
HasMore = m_reader . Read ();
}
}
}
}
public IEnumerable < IVolumePatch > FilesWithMissingBlocks
{
get
{
using ( var cmd = m_connection . CreateCommand ())
{
2016-02-18 19:31:17 +01:00
// The IN-clause with subquery enables SQLite to use indexes better. Three way join (A,B,C) is slow here!
cmd . CommandText = string . Format (
@" SELECT DISTINCT ""A"".""TargetPath"", ""BB"".""FileID"", (""BB"".""Index"" * {3}), ""BB"".""Size"", ""BB"".""Hash"" "
+ @" FROM ""{0}"" ""A"", ""{1}"" ""BB"" "
+ @" WHERE ""A"".""ID"" = ""BB"".""FileID"" AND ""BB"".""Restored"" = 0 AND ""BB"".""Metadata"" = {4}"
+ @" AND ""BB"".""ID"" IN (SELECT ""B"".""ID"" FROM ""{1}"" ""B"", ""{2}"" ""C"" WHERE ""B"".""Hash"" = ""C"".""Hash"" AND ""B"".""Size"" = ""C"".""Size"") "
+ @" ORDER BY ""A"".""TargetPath"", ""BB"".""Index"""
, m_filetablename , m_blocktablename , m_tmptable , m_blocksize , "0" );
2014-11-05 21:43:08 +01:00
using ( var rd = cmd . ExecuteReader ())
{
if ( rd . Read ())
{
var more = true ;
while ( more )
{
var f = new VolumePatch ( rd );
string current = f . Path ;
yield return f ;
more = f . HasMore ;
while ( more && current == f . Path )
more = rd . Read ();
}
}
}
}
}
}
public IEnumerable < IVolumePatch > MetadataWithMissingBlocks
{
get
{
using ( var cmd = m_connection . CreateCommand ())
{
2016-02-18 19:31:17 +01:00
// The IN-clause with subquery enables SQLite to use indexes better. Three way join (A,B,C) is slow here!
cmd . CommandText = string . Format (
@" SELECT DISTINCT ""A"".""TargetPath"", ""BB"".""FileID"", (""BB"".""Index"" * {3}), ""BB"".""Size"", ""BB"".""Hash"" "
+ @" FROM ""{0}"" ""A"", ""{1}"" ""BB"" "
+ @" WHERE ""A"".""ID"" = ""BB"".""FileID"" AND ""BB"".""Restored"" = 0 AND ""BB"".""Metadata"" = {4}"
+ @" AND ""BB"".""ID"" IN (SELECT ""B"".""ID"" FROM ""{1}"" ""B"", ""{2}"" ""C"" WHERE ""B"".""Hash"" = ""C"".""Hash"" AND ""B"".""Size"" = ""C"".""Size"") "
+ @" ORDER BY ""A"".""TargetPath"", ""BB"".""Index"""
, m_filetablename , m_blocktablename , m_tmptable , m_blocksize , "1" );
2014-11-05 21:43:08 +01:00
using ( var rd = cmd . ExecuteReader ())
2015-01-25 14:26:54 +01:00
{
if ( rd . Read ())
{
var more = true ;
while ( more )
{
var f = new VolumePatch ( rd );
string current = f . Path ;
yield return f ;
more = f . HasMore ;
while ( more && current == f . Path )
more = rd . Read ();
}
}
}
2014-11-05 21:43:08 +01:00
}
}
}
}
2015-04-08 20:33:30 +02:00
public IFilesAndMetadata GetMissingBlockData ( BlockVolumeReader curvolume , long blocksize )
2013-03-08 22:24:54 +01:00
{
2015-04-08 20:33:30 +02:00
return new FilesAndMetadata ( m_connection , m_tempfiletable , m_tempblocktable , blocksize , curvolume );
2013-03-08 22:24:54 +01:00
}
2016-09-15 11:39:27 +02:00
private class FileToRestore : IFileToRestore
{
public string Path { get ; private set ; }
public string Hash { get ; private set ; }
2015-03-04 16:23:44 +01:00
public long Length { get ; private set ; }
2016-09-15 11:39:27 +02:00
2015-03-04 16:23:44 +01:00
public FileToRestore ( long id , string path , string hash , long length )
2016-09-15 11:39:27 +02:00
{
this . Path = path ;
this . Hash = hash ;
2015-03-04 16:23:44 +01:00
this . Length = length ;
2016-09-15 11:39:27 +02:00
}
}
2013-05-20 13:46:58 +02:00
2016-03-05 02:00:28 +01:00
public IEnumerable < IFileToRestore > GetFilesToRestore ( bool onlyNonVerified )
2013-03-08 22:24:54 +01:00
{
2016-03-05 02:00:28 +01:00
using ( var cmd = m_connection . CreateCommand ())
{
cmd . AddParameter (! onlyNonVerified );
using ( var rd = cmd . ExecuteReader ( string . Format ( @"SELECT ""{0}"".""ID"", ""{0}"".""TargetPath"", ""Blockset"".""FullHash"", ""Blockset"".""Length"" FROM ""{0}"",""Blockset"" WHERE ""{0}"".""BlocksetID"" = ""Blockset"".""ID"" AND ""{0}"".""DataVerified"" <= ?" , m_tempfiletable )))
while ( rd . Read ())
2017-01-14 12:46:59 +01:00
yield return new FileToRestore ( rd . ConvertValueToInt64 ( 0 ), rd . ConvertValueToString ( 1 ), rd . ConvertValueToString ( 2 ), rd . ConvertValueToInt64 ( 3 ));
2016-03-05 02:00:28 +01:00
}
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 ();
}
2018-03-12 14:07:11 +01:00
catch ( Exception ex ) { Logging . Log . WriteWarningMessage ( LOGTAG , "CleanupError" , ex , "Cleanup error: {0}" , ex . Message ); }
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 ();
}
2018-03-12 14:07:11 +01:00
catch ( Exception ex ) { Logging . Log . WriteWarningMessage ( LOGTAG , "CleanupError" , ex , "Cleanup error: {0}" , ex . Message ); }
2013-03-08 22:24:54 +01:00
finally { m_tempblocktable = null ; }
2016-02-27 22:04:48 +01:00
if ( m_fileprogtable != null )
try
{
cmd . CommandText = string . Format ( @"DROP TABLE IF EXISTS ""{0}""" , m_fileprogtable );
cmd . ExecuteNonQuery ();
}
2018-03-12 14:07:11 +01:00
catch ( Exception ex ) { Logging . Log . WriteWarningMessage ( LOGTAG , "CleanupError" , ex , "Cleanup error: {0}" , ex . Message ); }
2016-02-27 22:04:48 +01:00
finally { m_fileprogtable = null ; }
if ( m_totalprogtable != null )
try
{
cmd . CommandText = string . Format ( @"DROP TABLE IF EXISTS ""{0}""" , m_totalprogtable );
cmd . ExecuteNonQuery ();
}
2018-03-12 14:07:11 +01:00
catch ( Exception ex ) { Logging . Log . WriteWarningMessage ( LOGTAG , "CleanupError" , ex , "Cleanup error: {0}" , ex . Message ); }
2016-02-27 22:04:48 +01:00
finally { m_totalprogtable = null ; }
if ( m_filesnewlydonetable != null )
try
{
cmd . CommandText = string . Format ( @"DROP TABLE IF EXISTS ""{0}""" , m_filesnewlydonetable );
cmd . ExecuteNonQuery ();
}
2018-03-12 14:07:11 +01:00
catch ( Exception ex ) { Logging . Log . WriteWarningMessage ( LOGTAG , "CleanupError" , ex , "Cleanup error: {0}" , ex . Message ); }
2016-02-27 22:04:48 +01:00
finally { m_filesnewlydonetable = null ; }
2013-03-08 22:24:54 +01:00
}
}
public interface IBlockMarker : IDisposable
{
2014-11-05 21:43:08 +01:00
void SetBlockRestored ( long targetfileid , long index , string hash , long blocksize , bool metadata );
2013-08-22 20:52:54 +02:00
void SetAllBlocksMissing ( long targetfileid );
2016-02-27 22:04:48 +01:00
void SetAllBlocksRestored ( long targetfileid , bool includeMetadata );
2016-03-05 02:00:28 +01:00
void SetFileDataVerified ( long targetfileid );
2018-03-12 14:07:11 +01:00
void Commit ();
2013-08-23 22:18:13 +02:00
void UpdateProcessed ( IOperationProgressUpdater writer );
2013-03-08 22:24:54 +01:00
}
2016-02-27 22:04:48 +01:00
/// <summary>
/// A new implementation of IBlockMarker, marking the blocks directly in the blocks table as restored
/// and reading statistics about progress from DB (kept up-to-date by triggers).
/// There is no negative influence on performance, esp. since the block table is temporary anyway.
/// </summary>
private class DirectBlockMarker : IBlockMarker
2013-03-08 22:24:54 +01:00
{
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 ;
2016-03-05 02:00:28 +01:00
private System . Data . IDbCommand m_updateFileAsDataVerifiedCommand ;
2013-08-23 22:18:13 +02:00
private System . Data . IDbCommand m_statUpdateCommand ;
private bool m_hasUpdates = false ;
2016-02-27 22:04:48 +01:00
2018-05-23 21:18:01 -07:00
private readonly string m_blocktablename ;
private readonly string m_filetablename ;
2016-02-27 22:04:48 +01:00
public DirectBlockMarker ( System . Data . IDbConnection connection , string blocktablename , string filetablename , string statstablename )
2013-03-08 22:24:54 +01:00
{
2013-05-20 13:48:44 +02:00
m_insertblockCommand = connection . CreateCommand ();
2016-02-27 22:04:48 +01:00
m_resetfileCommand = connection . CreateCommand ();
2013-08-22 20:52:54 +02:00
m_updateAsRestoredCommand = connection . CreateCommand ();
2016-03-05 02:00:28 +01:00
m_updateFileAsDataVerifiedCommand = connection . CreateCommand ();
2013-08-23 22:18:13 +02:00
m_statUpdateCommand = connection . CreateCommand ();
2016-02-27 22:04:48 +01: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 ;
2016-03-05 02:00:28 +01:00
m_updateFileAsDataVerifiedCommand . Transaction = m_insertblockCommand . Transaction ;
2013-08-23 22:18:13 +02:00
m_statUpdateCommand . Transaction = m_insertblockCommand . Transaction ;
2016-02-27 22:04:48 +01:00
2013-04-27 10:20:15 +02:00
m_blocktablename = blocktablename ;
2016-03-05 02:00:28 +01:00
m_filetablename = filetablename ;
2015-11-16 12:57:34 +01:00
2016-02-27 22:04:48 +01:00
m_insertblockCommand . CommandText = string . Format (
@"UPDATE ""{0}"" SET ""Restored"" = 1 "
+ @" WHERE ""FileID"" = ? AND ""Index"" = ? AND ""Hash"" = ? AND ""Size"" = ? AND ""Metadata"" = ? AND ""Restored"" = 0 "
, m_blocktablename );
2014-11-05 21:43:08 +01:00
m_insertblockCommand . AddParameters ( 5 );
2016-02-27 22:04:48 +01:00
m_resetfileCommand . CommandText = string . Format (
@"UPDATE ""{0}"" SET ""Restored"" = 0 WHERE ""FileID"" = ? "
, m_blocktablename );
2013-08-22 20:52:54 +02:00
m_resetfileCommand . AddParameters ( 1 );
2016-02-27 22:04:48 +01:00
m_updateAsRestoredCommand . CommandText = string . Format (
@"UPDATE ""{0}"" SET ""Restored"" = 1 WHERE ""FileID"" = ? AND ""Metadata"" <= ? "
, m_blocktablename );
m_updateAsRestoredCommand . AddParameters ( 2 );
2016-03-05 02:00:28 +01:00
m_updateFileAsDataVerifiedCommand . CommandText = string . Format (
@"UPDATE ""{0}"" SET ""DataVerified"" = 1 WHERE ""ID"" = ?"
, m_filetablename );
m_updateFileAsDataVerifiedCommand . AddParameters ( 1 );
2016-02-27 22:04:48 +01:00
if ( statstablename != null )
{
// Fields in Stats: TotalFiles, TotalBlocks, TotalSize
// FilesFullyRestored, FilesPartiallyRestored, BlocksRestored, SizeRestored
m_statUpdateCommand . CommandText = string . Format ( @"SELECT SUM(""FilesFullyRestored""), SUM(""SizeRestored"") FROM ""{0}"" " , statstablename );
}
2016-02-28 00:16:00 +01:00
else // very slow fallback if stats tables were not created
2016-02-27 22:04:48 +01:00
m_statUpdateCommand . CommandText = string . Format ( @"SELECT COUNT(DISTINCT ""FileID""), SUM(""Size"") FROM ""{0}"" WHERE ""Restored"" = 1 " , m_blocktablename );
2013-08-23 22:18:13 +02:00
}
2016-02-27 22:04:48 +01:00
2013-08-23 22:18:13 +02:00
public void UpdateProcessed ( IOperationProgressUpdater updater )
{
if (! m_hasUpdates )
return ;
2016-02-27 22:04:48 +01:00
2013-08-23 22:18:13 +02:00
m_hasUpdates = false ;
2016-02-27 22:04:48 +01:00
using ( var rd = m_statUpdateCommand . ExecuteReader ())
{
2013-08-23 22:18:13 +02:00
var filesprocessed = 0L ;
var processedsize = 0L ;
2016-02-27 22:04:48 +01:00
2013-08-27 10:23:21 +02:00
if ( rd . Read ())
{
2016-02-27 22:04:48 +01:00
filesprocessed += rd . ConvertValueToInt64 ( 0 , 0 );
processedsize += rd . ConvertValueToInt64 ( 1 , 0 );
2013-08-27 10:23:21 +02:00
}
2013-08-23 22:18:13 +02:00
updater . UpdatefilesProcessed ( filesprocessed , processedsize );
}
2013-08-22 20:52:54 +02:00
}
2016-02-27 22:04:48 +01:00
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
}
2016-02-27 22:04:48 +01:00
public void SetAllBlocksRestored ( long targetfileid , bool includeMetadata )
2013-08-22 20:52:54 +02:00
{
2013-08-23 22:18:13 +02:00
m_hasUpdates = true ;
2013-08-22 20:52:54 +02:00
m_updateAsRestoredCommand . SetParameterValue ( 0 , targetfileid );
2016-02-27 22:04:48 +01:00
m_updateAsRestoredCommand . SetParameterValue ( 1 , includeMetadata ? 1 : 0 );
2013-08-22 20:52:54 +02:00
var r = m_updateAsRestoredCommand . ExecuteNonQuery ();
if ( r <= 0 )
throw new Exception ( "Unexpected reset result" );
}
2016-02-27 22:04:48 +01:00
2016-03-05 02:00:28 +01:00
public void SetFileDataVerified ( long targetfileid )
{
m_hasUpdates = true ;
m_updateFileAsDataVerifiedCommand . SetParameterValue ( 0 , targetfileid );
var r = m_updateFileAsDataVerifiedCommand . ExecuteNonQuery ();
2016-03-12 12:48:41 +01:00
if ( r != 1 )
throw new Exception ( "Unexpected result when marking file as verified." );
2016-03-05 02:00:28 +01:00
}
2014-11-05 21:43:08 +01:00
public void SetBlockRestored ( long targetfileid , long index , string hash , long size , bool metadata )
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 );
2014-11-05 21:43:08 +01:00
m_insertblockCommand . SetParameterValue ( 4 , metadata );
2013-05-20 13:48:44 +02:00
var r = m_insertblockCommand . ExecuteNonQuery ();
2013-03-08 22:24:54 +01:00
if ( r != 1 )
2016-02-27 22:04:48 +01:00
throw new Exception ( "Unexpected result when marking block." );
2013-03-08 22:24:54 +01:00
}
2016-02-27 22:04:48 +01:00
2018-03-12 14:07:11 +01:00
public void Commit ()
2013-03-08 22:24:54 +01:00
{
2013-05-20 13:48:44 +02:00
var tr = m_insertblockCommand . Transaction ;
m_insertblockCommand . Dispose ();
m_insertblockCommand = null ;
2018-03-12 14:07:11 +01:00
using ( new Logging . Timer ( LOGTAG , "CommitBlockMarker" , "CommitBlockMarker" ))
2013-06-09 16:26:08 +02:00
tr . Commit ();
2013-03-08 22:24:54 +01:00
tr . Dispose ();
}
public void Dispose ()
{
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 ; }
2016-02-27 22:04:48 +01:00
2013-08-23 23:36:25 +02:00
if ( m_resetfileCommand != null )
try { m_resetfileCommand . Dispose (); }
catch { }
finally { m_resetfileCommand = null ; }
2016-02-27 22:04:48 +01:00
2013-08-23 23:36:25 +02:00
if ( m_updateAsRestoredCommand != null )
try { m_updateAsRestoredCommand . Dispose (); }
catch { }
finally { m_updateAsRestoredCommand = null ; }
2016-02-27 22:04:48 +01:00
2016-04-06 20:40:34 +02:00
if ( m_updateFileAsDataVerifiedCommand != null )
try { m_updateFileAsDataVerifiedCommand . Dispose (); }
catch { }
finally { m_updateFileAsDataVerifiedCommand = null ; }
2013-08-23 23:36:25 +02:00
if ( m_statUpdateCommand != null )
try { m_statUpdateCommand . Dispose (); }
catch { }
finally { m_statUpdateCommand = null ; }
2013-03-08 22:24:54 +01:00
}
}
public IBlockMarker CreateBlockMarker ()
{
2016-02-27 22:04:48 +01:00
return new DirectBlockMarker ( m_connection , m_tempblocktable , m_tempfiletable , m_totalprogtable );
2013-03-08 22:24:54 +01:00
}
public override void Dispose ()
{
DropRestoreTable ();
2016-04-06 20:40:34 +02:00
base . Dispose ();
2013-03-08 22:24:54 +01:00
}
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 ))
2016-09-15 11:39:27 +02:00
while ( rd . Read ())
yield return rd . GetValue ( 0 ). ToString ();
}
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 ; }
2013-03-08 22:24:54 +01:00
}
2013-04-28 12:19:21 +02:00
2016-09-15 11:39:27 +02:00
private class FastSource : IFastSource
{
private class BlockEntry : IBlockEntry
{
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_rd ;
private readonly long m_blocksize ;
2016-09-15 11:39:27 +02:00
public BlockEntry ( System . Data . IDataReader rd , long blocksize ) { m_rd = rd ; m_blocksize = blocksize ; }
2015-01-24 21:59:53 +01:00
public long Offset { get { return m_rd . GetInt64 ( 3 ) * m_blocksize ; } }
public long Index { get { return m_rd . GetInt64 ( 3 ); } }
public long Size { get { return m_rd . GetInt64 ( 5 ); } }
public string Hash { get { return m_rd . GetString ( 4 ); } }
2016-09-15 11:39:27 +02:00
}
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_rd ;
private readonly long m_blocksize ;
2016-09-15 11:39:27 +02:00
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 (); } }
2015-01-24 21:59:53 +01:00
public long TargetFileID { get { return m_rd . GetInt64 ( 2 ); } }
2016-09-15 11:39:27 +02:00
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 );
}
}
}
2013-04-28 12:19:21 +02:00
2015-04-08 20:33:30 +02:00
public IEnumerable < IFastSource > GetFilesAndSourceBlocksFast ( long blocksize )
2016-09-15 11:39:27 +02:00
{
2020-08-26 10:10:54 +01:00
var latestBlockTable = "LatestBlocksetIds-" + m_temptabsetguid ;
2016-09-15 11:39:27 +02:00
var whereclause = string . Format ( @" ""{0}"".""ID"" = ""{1}"".""FileID"" AND ""{1}"".""Restored"" = 0 AND ""{1}"".""Metadata"" = 0 AND ""{0}"".""TargetPath"" != ""{0}"".""Path"" " , m_tempfiletable , m_tempblocktable );
2020-11-15 12:23:46 -08:00
var sourcePaths = string . Format ( @"SELECT DISTINCT ""{0}"".""Path"" FROM ""{0}"", ""{1}"" WHERE " + whereclause , m_tempfiletable , m_tempblocktable );
2020-08-26 10:10:54 +01:00
var sources = string . Format ( @"SELECT DISTINCT ""{0}"".""TargetPath"", ""{0}"".""Path"", ""{0}"".""ID"", ""{1}"".""Index"", ""{1}"".""Hash"", ""{1}"".""Size"" FROM ""{0}"", ""{1}"", ""{2}"" S, ""Block"", ""BlocksetEntry"" WHERE ""BlocksetEntry"".""BlocksetID"" = ""S"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" AND ""{1}"".""Hash"" = ""Block"".""Hash"" AND ""{1}"".""Size"" = ""Block"".""Size"" AND ""S"".""Path"" = ""{0}"".""Path"" AND ""{1}"".""Index"" = ""BlocksetEntry"".""Index"" AND " + whereclause + @" ORDER BY ""{0}"".""ID"", ""{1}"".""Index"" " , m_tempfiletable , m_tempblocktable , latestBlockTable );
2020-12-29 17:50:24 -08:00
var latestBlocksetIds = @"SELECT ""File"".""Path"" AS ""PATH"", ""File"".""BlocksetID"" AS ""BlocksetID"", MAX(""Fileset"".""Timestamp"") AS ""Timestamp"" FROM ""Fileset"", ""FilesetEntry"", ""File"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" = ""Fileset"".""ID"" AND ""File"".""Path"" IN (" + sourcePaths + @") GROUP BY ""File"".""Path"" " ;
2020-08-26 10:10:54 +01:00
using ( var cmd = m_connection . CreateCommand ())
{
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , latestBlockTable ));
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" AS {1}" , latestBlockTable , latestBlocksetIds ));
}
using ( var cmd = m_connection . CreateCommand ())
2016-09-15 11:39:27 +02:00
using ( var rd = cmd . ExecuteReader ( sources ))
{
if ( rd . Read ())
{
var more = false ;
do
{
var n = new FastSource ( rd , blocksize );
var tid = n . TargetFileID ;
yield return n ;
more = n . MoreData ;
while ( more && n . TargetFileID == tid )
more = rd . Read ();
} while ( more );
}
}
}
2013-04-28 12:19:21 +02:00
2013-03-08 22:24:54 +01:00
}
}