2013-04-29 23:50:38 +02:00
using System ;
2013-03-08 22:24:54 +01:00
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.IO ;
2013-03-15 21:14:00 +01:00
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Database
2013-03-08 22:24:54 +01:00
{
2013-03-27 16:06:45 +01:00
public class LocalBackupDatabase : LocalDatabase
2013-03-08 22:24:54 +01:00
{
2013-03-11 20:13:40 +01:00
/// <summary>
/// An approximate size of a hash-string in memory (44 chars * 2 for unicode + 8 bytes for pointer = 104)
/// </summary>
2013-07-22 16:54:19 +02:00
internal const uint HASH_GUESS_SIZE = 128 ;
2013-03-15 21:14:00 +01:00
/// <summary>
2013-06-01 13:51:23 +02:00
/// An approximate size of a path string in bytes.
/// As all .Net strings are unicode, the average path length,
/// must be half this size. Windows uses MAX_PATH=256, so
/// we guess that the average size is around 128 chars.
/// On linux/OSX this may be wrong, but will only result
/// in slightly more memory being used that what the user
/// specifies.
2013-03-15 21:14:00 +01:00
/// </summary>
2013-07-22 16:54:19 +02:00
internal const uint PATH_STRING_GUESS_SIZE = 256 ;
2013-07-12 10:02:28 +02:00
/// <summary>
/// The usage threshold for a lookup table that triggers a warning
/// </summary>
private const double FULL_HASH_USAGE_THRESHOLD = 0.05 ;
/// <summary>
/// The number of hash misses that triggers a warning
/// </summary>
private const long HASH_MISS_THRESHOLD = 200 ;
2013-03-10 14:27:13 +01:00
2013-03-08 22:24:54 +01:00
private readonly System . Data . IDbCommand m_findblockCommand ;
private readonly System . Data . IDbCommand m_findblocksetCommand ;
private readonly System . Data . IDbCommand m_findfilesetCommand ;
private readonly System . Data . IDbCommand m_findmetadatasetCommand ;
2013-03-09 13:25:50 +01:00
private readonly System . Data . IDbCommand m_findmetadatasetProbeCommand ;
2013-03-08 22:24:54 +01:00
private readonly System . Data . IDbCommand m_insertblockCommand ;
private readonly System . Data . IDbCommand m_insertfileCommand ;
private readonly System . Data . IDbCommand m_insertblocksetCommand ;
private readonly System . Data . IDbCommand m_insertblocksetentryCommand ;
private readonly System . Data . IDbCommand m_insertblocklistHashesCommand ;
private readonly System . Data . IDbCommand m_insertmetadatasetCommand ;
private readonly System . Data . IDbCommand m_selectfileSimpleCommand ;
private readonly System . Data . IDbCommand m_selectfileHashCommand ;
private readonly System . Data . IDbCommand m_selectblocklistHashesCommand ;
private readonly System . Data . IDbCommand m_findremotevolumestateCommand ;
private readonly System . Data . IDbCommand m_updateblockCommand ;
private readonly System . Data . IDbCommand m_insertfileOperationCommand ;
2013-03-22 21:56:00 +01:00
private HashDatabaseProtector < string > m_blockHashLookup ;
2013-03-15 21:14:00 +01:00
private HashDatabaseProtector < string , long > m_fileHashLookup ;
private HashDatabaseProtector < string , long > m_metadataLookup ;
private HashDatabaseProtector < string , KeyValuePair < long , DateTime >> m_fileScantimeLookup ;
private HashDatabaseProtector < Tuple < string , long , long >, long > m_filesetLookup ;
2013-03-10 20:33:30 +01:00
private long m_missingBlockHashes ;
2013-03-11 20:13:40 +01:00
private string m_scantimelookupTablename ;
2013-04-06 13:46:58 +02:00
private long m_filesetId ;
2013-03-10 20:33:30 +01:00
2013-05-06 09:58:19 +02:00
public LocalBackupDatabase ( string path , Options options )
2013-04-04 20:34:26 +02:00
: this ( new LocalDatabase ( path , "Backup" ), options )
2013-03-08 22:24:54 +01:00
{
}
2013-04-04 20:34:26 +02:00
2013-05-06 09:58:19 +02:00
public LocalBackupDatabase ( LocalDatabase db , Options options )
2013-04-04 20:34:26 +02:00
: base ( db )
2013-03-08 22:24:54 +01:00
{
m_findblockCommand = m_connection . CreateCommand ();
m_insertblockCommand = m_connection . CreateCommand ();
m_insertfileCommand = m_connection . CreateCommand ();
m_insertblocksetCommand = m_connection . CreateCommand ();
m_insertmetadatasetCommand = m_connection . CreateCommand ();
m_findblocksetCommand = m_connection . CreateCommand ();
m_findmetadatasetCommand = m_connection . CreateCommand ();
m_findfilesetCommand = m_connection . CreateCommand ();
m_insertblocksetentryCommand = m_connection . CreateCommand ();
m_findremotevolumestateCommand = m_connection . CreateCommand ();
m_updateblockCommand = m_connection . CreateCommand ();
m_insertblocklistHashesCommand = m_connection . CreateCommand ();
m_selectblocklistHashesCommand = m_connection . CreateCommand ();
m_insertfileOperationCommand = m_connection . CreateCommand ();
m_selectfileSimpleCommand = m_connection . CreateCommand ();
m_selectfileHashCommand = m_connection . CreateCommand ();
2013-03-09 13:25:50 +01:00
m_findmetadatasetProbeCommand = m_connection . CreateCommand ();
2013-03-22 21:56:00 +01:00
m_findblockCommand . CommandText = @"SELECT ""VolumeID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?" ;
2013-03-08 22:24:54 +01:00
m_findblockCommand . AddParameters ( 2 );
m_findblocksetCommand . CommandText = @"SELECT ""ID"" FROM ""Blockset"" WHERE ""Fullhash"" = ? AND ""Length"" = ?" ;
m_findblocksetCommand . AddParameters ( 2 );
2013-03-09 13:25:50 +01:00
m_findmetadatasetProbeCommand . CommandText = @"SELECT ""ID"" FROM ""Blockset"" WHERE ""Hash"" = ? AND ""Size"" = ? LIMIT 1" ;
m_findmetadatasetProbeCommand . AddParameters ( 2 );
m_findmetadatasetCommand . CommandText = @"SELECT ""A"".""ID"" FROM ""Metadataset"" A, ""BlocksetEntry"" B, ""Block"" C WHERE ""A"".""BlocksetID"" = ""B"".""BlocksetID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" AND ""C"".""Hash"" = ? AND ""C"".""Size"" = ? LIMIT 1" ;
2013-03-08 22:24:54 +01:00
m_findmetadatasetCommand . AddParameters ( 2 );
2013-07-18 23:14:23 +02:00
m_findfilesetCommand . CommandText = @"SELECT ""ID"" FROM ""File"" WHERE ""BlocksetID"" = ? AND ""MetadataID"" = ? AND ""Path"" = ?" ;
2013-03-15 21:14:00 +01:00
m_findfilesetCommand . AddParameters ( 3 );
2013-03-08 22:24:54 +01:00
2013-03-22 21:56:00 +01:00
m_insertblockCommand . CommandText = @"INSERT INTO ""Block"" (""Hash"", ""VolumeID"", ""Size"") VALUES (?, ?, ?)" ;
2013-03-08 22:24:54 +01:00
m_insertblockCommand . AddParameters ( 3 );
2013-04-06 13:46:58 +02:00
m_insertfileOperationCommand . CommandText = @"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Scantime"") VALUES (?, ?, ?)" ;
2013-04-04 20:34:26 +02:00
m_insertfileOperationCommand . AddParameters ( 3 );
2013-03-08 22:24:54 +01:00
2013-04-06 13:46:58 +02:00
m_insertfileCommand . CommandText = @"INSERT INTO ""File"" (""Path"",""BlocksetID"", ""MetadataID"") VALUES (?, ? ,?); SELECT last_insert_rowid();" ;
2013-03-08 22:24:54 +01:00
m_insertfileCommand . AddParameters ( 3 );
m_insertblocksetCommand . CommandText = @"INSERT INTO ""Blockset"" (""Length"", ""FullHash"") VALUES (?, ?); SELECT last_insert_rowid();" ;
m_insertblocksetCommand . AddParameters ( 2 );
m_insertblocksetentryCommand . CommandText = @"INSERT INTO ""BlocksetEntry"" (""BlocksetID"", ""Index"", ""BlockID"") SELECT ? AS A, ? AS B, ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?" ;
m_insertblocksetentryCommand . AddParameters ( 4 );
m_insertblocklistHashesCommand . CommandText = @"INSERT INTO ""BlocklistHash"" (""BlocksetID"", ""Index"", ""Hash"") VALUES (?, ?, ?)" ;
m_insertblocklistHashesCommand . AddParameters ( 3 );
m_insertmetadatasetCommand . CommandText = @"INSERT INTO ""Metadataset"" (""BlocksetID"") VALUES (?); SELECT last_insert_rowid();" ;
m_insertmetadatasetCommand . AddParameter ();
2013-03-11 20:13:40 +01:00
//Need a temporary table with path/scantime lookups
2013-05-08 21:29:59 +02:00
m_scantimelookupTablename = "ScanTime-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2013-03-11 20:13:40 +01:00
using ( var cmd = m_connection . CreateCommand ())
2013-04-06 13:46:58 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" AS SELECT ""FilesetEntry"".""FileID"" AS ""FileID"", MAX(""FilesetEntry"".""Scantime"") AS ""Scantime"", ""File"".""Path"" AS ""Path"" FROM ""FilesetEntry"" INNER JOIN ""File"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" GROUP BY ""FilesetEntry"".""FileID"", ""File"".""Path"" " , m_scantimelookupTablename ));
2013-03-11 20:13:40 +01:00
2013-07-23 21:28:01 +02:00
m_selectfileSimpleCommand . CommandText = string . Format ( @"SELECT ""FileID"", ""Scantime"" FROM ""{0}"" WHERE ""Path"" = ?" , m_scantimelookupTablename );
2013-03-08 22:24:54 +01:00
m_selectfileSimpleCommand . AddParameters ( 1 );
2013-06-28 14:56:26 +02:00
m_selectfileHashCommand . CommandText = @"SELECT ""Blockset"".""Fullhash"" FROM ""Blockset"", ""File"" WHERE ""Blockset"".""ID"" = ""File"".""BlocksetID"" AND ""File"".""ID"" = ? " ;
2013-03-08 22:24:54 +01:00
m_selectfileHashCommand . AddParameters ( 1 );
m_selectblocklistHashesCommand . CommandText = @"SELECT ""Hash"" FROM ""BlocklistHash"" WHERE ""BlocksetID"" = ? ORDER BY ""Index"" ASC " ;
m_selectblocklistHashesCommand . AddParameters ( 1 );
m_findremotevolumestateCommand . CommandText = @"SELECT ""State"" FROM ""Remotevolume"" WHERE ""Name"" = ?" ;
m_findremotevolumestateCommand . AddParameters ( 1 );
2013-03-22 21:56:00 +01:00
m_updateblockCommand . CommandText = @"UPDATE ""Block"" SET ""VolumeID"" = ? WHERE ""Hash"" = ? AND ""Size"" = ? " ;
2013-03-08 22:24:54 +01:00
m_updateblockCommand . AddParameters ( 3 );
2013-07-22 16:54:19 +02:00
2013-05-08 19:57:13 +02:00
if ( options . BlockHashLookupMemory > 0 )
m_blockHashLookup = new HashDatabaseProtector < string >( HASH_GUESS_SIZE , ( ulong ) options . BlockHashLookupMemory );
if ( options . FileHashLookupMemory > 0 )
m_fileHashLookup = new HashDatabaseProtector < string , long >( HASH_GUESS_SIZE , ( ulong ) options . FileHashLookupMemory );
if ( options . MetadataHashMemory > 0 )
m_metadataLookup = new HashDatabaseProtector < string , long >( HASH_GUESS_SIZE , ( ulong ) options . MetadataHashMemory );
if ( options . FilePathMemory > 0 )
2013-03-15 21:14:00 +01:00
{
2013-05-08 19:57:13 +02:00
m_fileScantimeLookup = new HashDatabaseProtector < string , KeyValuePair < long , DateTime >>( PATH_STRING_GUESS_SIZE , ( ulong ) options . FilePathMemory / 2 );
m_filesetLookup = new HashDatabaseProtector < Tuple < string , long , long >, long >( PATH_STRING_GUESS_SIZE , ( ulong ) options . FilePathMemory / 2 );
2013-03-15 21:14:00 +01:00
}
2013-03-10 14:27:13 +01:00
2013-03-15 21:14:00 +01:00
//Populate the lookup tables
2013-03-10 14:27:13 +01:00
using ( var cmd = m_connection . CreateCommand ())
2013-03-15 21:14:00 +01:00
{
2013-03-16 10:37:57 +01:00
if ( m_blockHashLookup != null )
2013-03-22 21:56:00 +01:00
using ( var rd = cmd . ExecuteReader ( @"SELECT DISTINCT ""Block"".""Hash"" FROM ""Block"", ""RemoteVolume"" WHERE ""RemoteVolume"".""ID"" = ""Block"".""VolumeID"" AND ""RemoteVolume"".""State"" IN (?,?,?,?) " , RemoteVolumeState . Temporary . ToString (), RemoteVolumeState . Uploading . ToString (), RemoteVolumeState . Uploaded . ToString (), RemoteVolumeState . Verified . ToString ()))
2013-03-16 10:37:57 +01:00
while ( rd . Read ())
{
var str = rd . GetValue ( 0 ). ToString ();
var key = HashPrefixLookup . DecodeBase64Hash ( str );
m_blockHashLookup . Add ( key , str );
}
if ( m_fileHashLookup != null )
using ( var rd = cmd . ExecuteReader ( @"SELECT DISTINCT ""FullHash"", ""ID"" FROM ""BlockSet""" ))
while ( rd . Read ())
{
var str = rd . GetValue ( 0 ). ToString ();
var id = Convert . ToInt64 ( rd . GetValue ( 1 ));
var key = HashPrefixLookup . DecodeBase64Hash ( str );
m_fileHashLookup . Add ( key , str , id );
}
2013-03-10 14:27:13 +01:00
2013-03-16 10:37:57 +01:00
if ( m_metadataLookup != null )
2013-04-25 19:46:32 +02:00
using ( var rd = cmd . ExecuteReader ( @"SELECT ""Metadataset"".""ID"", ""Blockset"".""FullHash"" FROM ""Metadataset"", ""Blockset"" WHERE ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" " ))
2013-03-16 10:37:57 +01:00
while ( rd . Read ())
{
var metadataid = Convert . ToInt64 ( rd . GetValue ( 0 ));
var hash = rd . GetValue ( 1 ). ToString ();
var hashdata = HashPrefixLookup . DecodeBase64Hash ( hash );
m_metadataLookup . Add ( hashdata , hash , metadataid );
}
2013-03-15 21:14:00 +01:00
2013-03-16 10:37:57 +01:00
if ( m_fileScantimeLookup != null )
2013-07-23 21:28:01 +02:00
using ( var rd = cmd . ExecuteReader ( string . Format ( @" SELECT ""FileID"", ""Scantime"", ""Path"", ""Scantime"" FROM ""{0}"" " , m_scantimelookupTablename )))
2013-03-15 21:14:00 +01:00
while ( rd . Read ())
{
var id = Convert . ToInt64 ( rd . GetValue ( 0 ));
2013-07-16 14:51:59 +02:00
var scantime = ParseFromEpochSeconds ( Convert . ToInt64 ( rd . GetValue ( 1 )));
2013-03-15 21:14:00 +01:00
var path = rd . GetValue ( 2 ). ToString ();
m_fileScantimeLookup . Add (( ulong ) path . GetHashCode (), path , new KeyValuePair < long , DateTime >( id , scantime ));
}
2013-03-10 14:27:13 +01:00
2013-03-16 10:37:57 +01:00
if ( m_filesetLookup != null )
2013-04-06 13:46:58 +02:00
using ( var rd = cmd . ExecuteReader ( string . Format ( @" SELECT ""Path"", ""BlocksetID"", ""MetadataID"", ""ID"" FROM ""File"" " )))
2013-03-15 21:14:00 +01:00
while ( rd . Read ())
{
var path = rd . GetValue ( 0 ). ToString ();
var blocksetid = Convert . ToInt64 ( rd . GetValue ( 1 ));
var metadataid = Convert . ToInt64 ( rd . GetValue ( 2 ));
var filesetid = Convert . ToInt64 ( rd . GetValue ( 3 ));
m_filesetLookup . Add (( ulong ) blocksetid ^ ( ulong ) metadataid ^ ( ulong ) path . GetHashCode (), new Tuple < string , long , long >( path , blocksetid , metadataid ), filesetid );
}
2013-03-22 21:56:00 +01:00
m_missingBlockHashes = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT (*) FROM (SELECT DISTINCT ""Block"".""Hash"", ""Block"".""Size"" FROM ""Block"", ""RemoteVolume"" WHERE ""RemoteVolume"".""ID"" = ""Block"".""VolumeID"" AND ""RemoteVolume"".""State"" NOT IN (?,?,?,?))" , RemoteVolumeState . Temporary . ToString (), RemoteVolumeState . Uploading . ToString (), RemoteVolumeState . Uploaded . ToString (), RemoteVolumeState . Verified . ToString ()));
2013-03-15 21:14:00 +01:00
}
2013-03-10 20:33:30 +01:00
2013-03-08 22:24:54 +01:00
}
/// <summary>
/// Adds a block to the local database, returning a value indicating if the value presents a new block
/// </summary>
/// <param name="key">The block key</param>
/// <param name="archivename">The name of the archive that holds the data</param>
/// <returns>True if the block should be added to the current output</returns>
2013-03-22 21:56:00 +01:00
public bool AddBlock ( string key , long size , long volumeid , System . Data . IDbTransaction transaction = null )
2013-03-08 22:24:54 +01:00
{
2013-03-10 14:27:13 +01:00
object r = null ;
2013-03-16 10:37:57 +01:00
ulong hashdata = 0 ;
if ( m_blockHashLookup != null )
2013-03-10 14:27:13 +01:00
{
2013-03-16 10:37:57 +01:00
hashdata = HashPrefixLookup . DecodeBase64Hash ( key );
switch ( m_blockHashLookup . HasValue ( hashdata , key ))
{
case HashLookupResult . NotFound :
//We insert it
break ;
case HashLookupResult . Found :
return false ;
default :
m_findblockCommand . Transaction = transaction ;
2013-07-12 10:01:49 +02:00
m_findblockCommand . SetParameterValue ( 0 , key );
m_findblockCommand . SetParameterValue ( 1 , size );
r = m_findblockCommand . ExecuteScalar ();
2013-03-16 10:37:57 +01:00
if ( r == null || r == DBNull . Value ) {
2013-07-12 08:07:10 +02:00
m_blockHashLookup . PositiveMisses ++;
2013-03-16 10:37:57 +01:00
} else {
2013-07-12 08:07:10 +02:00
m_blockHashLookup . NegativeMisses ++;
2013-03-16 10:37:57 +01:00
if ( m_missingBlockHashes == 0 ) {
//Update the lookup table, MRU style
m_blockHashLookup . Add ( hashdata , key );
return false ;
}
2013-03-15 21:14:00 +01:00
}
2013-03-16 10:37:57 +01:00
break ;
}
}
else
{
m_findblockCommand . Transaction = transaction ;
2013-07-12 10:01:49 +02:00
m_findblockCommand . SetParameterValue ( 0 , key );
m_findblockCommand . SetParameterValue ( 1 , size );
r = m_findblockCommand . ExecuteScalar ();
2013-03-10 14:27:13 +01:00
}
2013-03-08 22:24:54 +01:00
if ( r == null || r == DBNull . Value )
{
m_insertblockCommand . Transaction = transaction ;
m_insertblockCommand . SetParameterValue ( 0 , key );
2013-03-22 21:56:00 +01:00
m_insertblockCommand . SetParameterValue ( 1 , volumeid );
2013-03-08 22:24:54 +01:00
m_insertblockCommand . SetParameterValue ( 2 , size );
m_insertblockCommand . ExecuteNonQuery ();
2013-03-16 10:37:57 +01:00
if ( m_blockHashLookup != null )
m_blockHashLookup . Add ( hashdata , key );
2013-03-08 22:24:54 +01:00
return true ;
}
else
{
2013-03-11 20:13:40 +01:00
//We add/update it now
2013-03-16 10:37:57 +01:00
if ( m_blockHashLookup != null )
m_blockHashLookup . Add ( hashdata , key );
2013-03-11 20:13:40 +01:00
2013-03-10 20:33:30 +01:00
//If the block is found and the volume is broken somehow.
2013-03-08 22:24:54 +01:00
m_findremotevolumestateCommand . Transaction = transaction ;
r = m_findremotevolumestateCommand . ExecuteScalar ( null , r );
if ( r != null && ( r . ToString () == RemoteVolumeState . Temporary . ToString () || r . ToString () == RemoteVolumeState . Uploading . ToString () || r . ToString () == RemoteVolumeState . Uploaded . ToString () || r . ToString () == RemoteVolumeState . Verified . ToString ()))
{
return false ;
}
else
{
m_updateblockCommand . Transaction = transaction ;
2013-03-22 21:56:00 +01:00
m_updateblockCommand . ExecuteNonQuery ( null , volumeid , key , size );
2013-03-08 22:24:54 +01:00
return true ;
}
}
}
/// <summary>
/// Adds a blockset to the database, returns a value indicating if the blockset is new
/// </summary>
/// <param name="filehash">The hash of the blockset</param>
/// <param name="size">The size of the blockset</param>
/// <param name="fragmentoffset">The fragmentoffset for the last block</param>
/// <param name="fragmenthash">The hash of the fragment</param>
/// <param name="hashes">The list of hashes</param>
/// <param name="blocksetid">The id of the blockset, new or old</param>
/// <returns>True if the blockset was created, false otherwise</returns>
2013-03-15 21:14:00 +01:00
public bool AddBlockset ( string filehash , long size , int blocksize , IEnumerable < string > hashes , IEnumerable < string > blocklistHashes , out long blocksetid , System . Data . IDbTransaction transaction = null )
2013-03-08 22:24:54 +01:00
{
2013-03-10 14:27:13 +01:00
object r = null ;
2013-03-16 10:37:57 +01:00
ulong hashdata = 0 ;
2013-03-15 21:14:00 +01:00
2013-03-16 10:37:57 +01:00
if ( m_fileHashLookup != null )
2013-03-10 14:27:13 +01:00
{
2013-03-16 10:37:57 +01:00
hashdata = HashPrefixLookup . DecodeBase64Hash ( filehash );
switch ( m_fileHashLookup . HasValue ( hashdata , filehash , out blocksetid ))
{
case HashLookupResult . NotFound :
//We insert it, but avoid looking for it
break ;
2013-03-15 21:14:00 +01:00
2013-03-16 10:37:57 +01:00
case HashLookupResult . Found :
//Got it, just return it
2013-03-15 21:14:00 +01:00
return false ;
2013-03-16 10:37:57 +01:00
default :
m_findblocksetCommand . Transaction = transaction ;
r = m_findblocksetCommand . ExecuteScalar ( null , filehash , size );
if ( r == null || r == DBNull . Value )
2013-07-12 08:07:10 +02:00
m_fileHashLookup . PositiveMisses ++;
2013-03-16 10:37:57 +01:00
else
{
2013-07-12 08:07:10 +02:00
m_fileHashLookup . NegativeMisses ++;
2013-03-16 10:37:57 +01:00
blocksetid = Convert . ToInt64 ( r );
//Update, MRU style
m_fileHashLookup . Add ( hashdata , filehash , blocksetid );
return false ;
}
break ;
}
}
else
{
m_findblocksetCommand . Transaction = transaction ;
r = m_findblocksetCommand . ExecuteScalar ( null , filehash , size );
2013-04-21 14:17:23 +02:00
if ( r != null && r != DBNull . Value )
2013-04-21 19:52:47 +02:00
{
blocksetid = Convert . ToInt64 ( r );
2013-04-21 14:17:23 +02:00
return false ; //Found it
2013-04-21 19:52:47 +02:00
}
2013-03-08 22:24:54 +01:00
}
2013-03-15 21:14:00 +01:00
2013-03-08 22:24:54 +01:00
using ( var tr = new TemporaryTransactionWrapper ( m_connection , transaction ))
{
m_insertblocksetCommand . Transaction = tr . Parent ;
2013-06-09 16:26:08 +02:00
m_insertblocksetCommand . SetParameterValue ( 0 , size );
m_insertblocksetCommand . SetParameterValue ( 1 , filehash );
blocksetid = Convert . ToInt64 ( m_insertblocksetCommand . ExecuteScalar ());
2013-03-16 10:37:57 +01:00
if ( m_fileHashLookup != null )
m_fileHashLookup . Add ( hashdata , filehash , blocksetid );
2013-03-08 22:24:54 +01:00
long ix = 0 ;
if ( blocklistHashes != null )
{
m_insertblocklistHashesCommand . SetParameterValue ( 0 , blocksetid );
m_insertblocklistHashesCommand . Transaction = tr . Parent ;
foreach ( var bh in blocklistHashes )
{
m_insertblocklistHashesCommand . SetParameterValue ( 1 , ix );
m_insertblocklistHashesCommand . SetParameterValue ( 2 , bh );
m_insertblocklistHashesCommand . ExecuteNonQuery ();
ix ++;
}
}
m_insertblocksetentryCommand . SetParameterValue ( 0 , blocksetid );
m_insertblocksetentryCommand . Transaction = tr . Parent ;
ix = 0 ;
long remainsize = size ;
foreach ( var h in hashes )
{
m_insertblocksetentryCommand . SetParameterValue ( 1 , ix );
m_insertblocksetentryCommand . SetParameterValue ( 2 , h );
m_insertblocksetentryCommand . SetParameterValue ( 3 , remainsize < blocksize ? remainsize : blocksize );
var c = m_insertblocksetentryCommand . ExecuteNonQuery ();
if ( c != 1 )
throw new Exception ( string . Format ( "Unexpected result count: {0}, expected {1}" , c , 1 ));
ix ++;
remainsize -= blocksize ;
}
2013-06-10 23:04:44 +02:00
tr . Commit ();
2013-03-08 22:24:54 +01:00
}
return true ;
}
/// <summary>
/// Adds a metadata set to the database, and returns a value indicating if the record was new
/// </summary>
/// <param name="hash">The metadata hash</param>
/// <param name="metadataid">The id of the metadata set</param>
/// <returns>True if the set was added to the database, false otherwise</returns>
public bool AddMetadataset ( string hash , long size , out long metadataid , System . Data . IDbTransaction transaction = null )
{
2013-03-16 10:37:57 +01:00
ulong hashdata = 0 ;
2013-03-08 22:24:54 +01:00
if ( size > 0 )
{
2013-03-16 10:37:57 +01:00
if ( m_metadataLookup != null )
2013-03-08 22:24:54 +01:00
{
2013-03-16 10:37:57 +01:00
hashdata = HashPrefixLookup . DecodeBase64Hash ( hash );
2013-03-15 21:14:00 +01:00
2013-03-16 10:37:57 +01:00
switch ( m_metadataLookup . HasValue ( hashdata , hash , out metadataid ))
{
case HashLookupResult . NotFound :
//We insert it, but avoid looking for it
break ;
case HashLookupResult . Found :
//Got it, just return the result
return false ;
default :
//Highly unlikely that we find matching metadata, due to the timestamp
m_findmetadatasetProbeCommand . Transaction = transaction ;
var r = m_findmetadatasetProbeCommand . ExecuteScalar ( null , hash , size );
2013-03-15 21:14:00 +01:00
if ( r != null && r != DBNull . Value )
{
2013-03-16 10:37:57 +01:00
m_findmetadatasetCommand . Transaction = transaction ;
2013-04-25 19:46:32 +02:00
r = m_findmetadatasetCommand . ExecuteScalar ( null , hash , size );
2013-03-16 10:37:57 +01:00
if ( r != null && r != DBNull . Value )
{
2013-07-12 08:07:10 +02:00
m_metadataLookup . NegativeMisses ++;
2013-03-16 10:37:57 +01:00
//Update the lookup table, MRU style
metadataid = Convert . ToInt64 ( r );
m_metadataLookup . Add ( hashdata , hash , metadataid );
return false ;
}
2013-03-15 21:14:00 +01:00
}
2013-03-16 10:37:57 +01:00
if ( r == null || r == DBNull . Value )
2013-07-12 08:07:10 +02:00
m_metadataLookup . PositiveMisses ++;
2013-03-16 10:37:57 +01:00
else
2013-07-12 08:07:10 +02:00
m_metadataLookup . NegativeMisses ++;
2013-03-16 10:37:57 +01:00
break ;
}
}
else
{
m_findmetadatasetProbeCommand . Transaction = transaction ;
var r = m_findmetadatasetProbeCommand . ExecuteScalar ( null , hash , size );
if ( r != null && r != DBNull . Value )
{
m_findmetadatasetCommand . Transaction = transaction ;
m_findmetadatasetCommand . ExecuteScalar ( null , hash , size );
}
2013-03-08 22:24:54 +01:00
}
2013-03-15 21:14:00 +01:00
2013-03-08 22:24:54 +01:00
long blocksetid ;
AddBlockset ( hash , size , ( int ) size , new string [] { hash }, null , out blocksetid , transaction );
using ( var tr = new TemporaryTransactionWrapper ( m_connection , transaction ))
{
m_insertmetadatasetCommand . Transaction = tr . Parent ;
2013-06-09 16:26:08 +02:00
m_insertmetadatasetCommand . SetParameterValue ( 0 , blocksetid );
metadataid = Convert . ToInt64 ( m_insertmetadatasetCommand . ExecuteScalar ());
2013-06-10 23:04:44 +02:00
tr . Commit ();
2013-03-16 10:37:57 +01:00
if ( m_metadataLookup != null )
m_metadataLookup . Add ( hashdata , hash , metadataid );
2013-03-08 22:24:54 +01:00
}
return true ;
}
metadataid = - 2 ;
return false ;
}
/// <summary>
/// Adds a file record to the database
/// </summary>
/// <param name="filename">The path to the file</param>
/// <param name="scantime">The time the file was scanned</param>
/// <param name="blocksetID">The ID of the hashkey for the file</param>
/// <param name="metadataID">The ID for the metadata</param>
2013-04-04 20:34:26 +02:00
/// <param name="transaction">The transaction to use for insertion, or null for no transaction</param>
/// <param name="operationId">The operationId to use, or -1 to use the current operation</param>
2013-04-06 13:46:58 +02:00
public void AddFile ( string filename , DateTime scantime , long blocksetID , long metadataID , System . Data . IDbTransaction transaction )
2013-03-08 22:24:54 +01:00
{
2013-03-15 21:14:00 +01:00
long fileid ;
object fileidobj = null ;
2013-03-16 10:37:57 +01:00
ulong hashdata = 0 ;
if ( m_filesetLookup != null )
2013-03-15 21:14:00 +01:00
{
2013-03-16 10:37:57 +01:00
hashdata = ( ulong ) blocksetID ^ ( ulong ) metadataID ^ ( ulong ) filename . GetHashCode ();
2013-03-15 21:14:00 +01:00
var tp = new Tuple < string , long , long >( filename , blocksetID , metadataID );
switch ( m_filesetLookup . HasValue ( hashdata , tp , out fileid ))
{
case HashLookupResult . NotFound :
// We insert it, but avoid looking for it
fileidobj = null ;
break ;
case HashLookupResult . Found :
// Have the id, avoid looking for it,
// but update operation filelist
fileidobj = fileid ;
break ;
default :
m_findfilesetCommand . Transaction = transaction ;
2013-06-01 13:51:43 +02:00
m_findfilesetCommand . SetParameterValue ( 0 , blocksetID );
m_findfilesetCommand . SetParameterValue ( 1 , metadataID );
m_findfilesetCommand . SetParameterValue ( 2 , filename );
fileidobj = m_findfilesetCommand . ExecuteScalar ();
2013-03-15 21:14:00 +01:00
if ( fileidobj == null || fileidobj == DBNull . Value )
2013-07-12 08:07:10 +02:00
m_filesetLookup . PositiveMisses ++;
2013-03-15 21:14:00 +01:00
else
2013-07-12 08:07:10 +02:00
m_filesetLookup . NegativeMisses ++;
2013-03-15 21:14:00 +01:00
break ;
}
}
else
{
m_findfilesetCommand . Transaction = transaction ;
2013-06-01 13:51:43 +02:00
m_findfilesetCommand . SetParameterValue ( 0 , blocksetID );
m_findfilesetCommand . SetParameterValue ( 1 , metadataID );
m_findfilesetCommand . SetParameterValue ( 2 , filename );
fileidobj = m_findfilesetCommand . ExecuteScalar ();
2013-03-15 21:14:00 +01:00
}
if ( fileidobj == null || fileidobj == DBNull . Value )
2013-03-08 22:24:54 +01:00
{
using ( var tr = new TemporaryTransactionWrapper ( m_connection , transaction ))
{
m_insertfileCommand . Transaction = tr . Parent ;
m_insertfileCommand . SetParameterValue ( 0 , filename );
m_insertfileCommand . SetParameterValue ( 1 , blocksetID );
m_insertfileCommand . SetParameterValue ( 2 , metadataID );
2013-03-15 21:14:00 +01:00
fileidobj = Convert . ToInt64 ( m_insertfileCommand . ExecuteScalar ());
2013-06-10 23:04:44 +02:00
tr . Commit ();
2013-03-15 21:14:00 +01:00
// We do not need to update this, because we will not ask for the same file twice
2013-03-16 10:37:57 +01:00
if ( m_filesetLookup != null )
m_filesetLookup . Add ( hashdata , new Tuple < string , long , long >( filename , blocksetID , metadataID ), Convert . ToInt64 ( fileidobj ));
2013-03-08 22:24:54 +01:00
}
}
2013-07-09 13:51:08 +02:00
2013-03-08 22:24:54 +01:00
m_insertfileOperationCommand . Transaction = transaction ;
2013-04-06 13:46:58 +02:00
m_insertfileOperationCommand . SetParameterValue ( 0 , m_filesetId );
2013-03-15 21:14:00 +01:00
m_insertfileOperationCommand . SetParameterValue ( 1 , fileidobj );
2013-07-23 21:28:01 +02:00
m_insertfileOperationCommand . SetParameterValue ( 2 , NormalizeDateTimeToEpochSeconds ( scantime ));
2013-03-08 22:24:54 +01:00
m_insertfileOperationCommand . ExecuteNonQuery ();
}
public void AddUnmodifiedFile ( long fileid , DateTime scantime , System . Data . IDbTransaction transaction = null )
{
m_insertfileOperationCommand . Transaction = transaction ;
2013-04-06 13:46:58 +02:00
m_insertfileOperationCommand . SetParameterValue ( 0 , m_filesetId );
2013-03-08 22:24:54 +01:00
m_insertfileOperationCommand . SetParameterValue ( 1 , fileid );
2013-07-23 21:28:01 +02:00
m_insertfileOperationCommand . SetParameterValue ( 2 , NormalizeDateTimeToEpochSeconds ( scantime ));
2013-03-08 22:24:54 +01:00
m_insertfileOperationCommand . ExecuteNonQuery ();
}
2013-04-06 13:46:58 +02:00
public void AddDirectoryEntry ( string path , long metadataID , DateTime scantime , System . Data . IDbTransaction transaction = null )
2013-03-08 22:24:54 +01:00
{
2013-04-06 13:46:58 +02:00
AddFile ( path , scantime , FOLDER_BLOCKSET_ID , metadataID , transaction );
2013-03-08 22:24:54 +01:00
}
2013-03-22 21:56:00 +01:00
2013-04-06 13:46:58 +02:00
public void AddSymlinkEntry ( string path , long metadataID , DateTime scantime , System . Data . IDbTransaction transaction = null )
2013-03-08 22:24:54 +01:00
{
2013-04-06 13:46:58 +02:00
AddFile ( path , scantime , SYMLINK_BLOCKSET_ID , metadataID , transaction );
2013-03-08 22:24:54 +01:00
}
public long GetFileEntry ( string path , out DateTime oldScanned )
{
2013-03-16 10:37:57 +01:00
if ( m_fileScantimeLookup != null )
2013-03-15 21:14:00 +01:00
{
KeyValuePair < long , DateTime > tmp ;
var hashdata = ( ulong ) path . GetHashCode ();
switch ( m_fileScantimeLookup . HasValue ( hashdata , path , out tmp ))
{
case HashLookupResult . NotFound :
oldScanned = DateTime . UtcNow ;
return - 1 ;
case HashLookupResult . Found :
oldScanned = tmp . Value ;
return tmp . Key ;
}
}
2013-06-01 13:51:43 +02:00
m_selectfileSimpleCommand . SetParameterValue ( 0 , path );
using ( var rd = m_selectfileSimpleCommand . ExecuteReader ())
2013-03-08 22:24:54 +01:00
if ( rd . Read ())
{
2013-03-16 10:37:57 +01:00
if ( m_fileScantimeLookup != null )
2013-07-12 08:07:10 +02:00
m_fileScantimeLookup . NegativeMisses ++;
2013-07-09 13:51:08 +02:00
2013-07-16 14:51:59 +02:00
oldScanned = ParseFromEpochSeconds ( Convert . ToInt64 ( rd . GetValue ( 1 )));
2013-03-15 21:14:00 +01:00
var id = Convert . ToInt64 ( rd . GetValue ( 0 ));
// We do not add here, because we will never query the same path twice,
// so it is more likely that the value currently occuping the table
// will be used later
//m_fileScantimeLookup.Add(hashdata, path, new KeyValuePair<long, DateTime>(id, oldScanned));
return id ;
2013-03-08 22:24:54 +01:00
}
else
{
2013-03-16 10:37:57 +01:00
if ( m_fileScantimeLookup != null )
2013-07-12 08:07:10 +02:00
m_fileScantimeLookup . PositiveMisses ++;
2013-03-15 21:14:00 +01:00
2013-03-08 22:24:54 +01:00
oldScanned = DateTime . UtcNow ;
return - 1 ;
}
}
public string GetFileHash ( long fileid )
{
m_selectfileHashCommand . SetParameterValue ( 0 , fileid );
return m_selectfileHashCommand . ExecuteScalar (). ToString ();
}
2013-04-27 15:13:14 +02:00
public void WriteFileset ( Volumes . FilesetVolumeWriter filesetvolume , System . Data . IDbTransaction transaction )
{
WriteFileset ( filesetvolume , transaction , m_filesetId );
}
2013-03-08 22:24:54 +01:00
2013-03-11 20:13:40 +01:00
public override void Dispose ()
{
if ( m_scantimelookupTablename != null )
try
{
using ( var cmd = m_connection . CreateCommand ())
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE ""{0}"" " , m_scantimelookupTablename ));
}
finally
{
m_scantimelookupTablename = null ;
}
2013-03-15 21:14:00 +01:00
if ( m_blockHashLookup != null )
try { m_blockHashLookup . Dispose (); }
finally { m_blockHashLookup = null ; }
if ( m_fileHashLookup != null )
try { m_fileHashLookup . Dispose (); }
finally { m_fileHashLookup = null ; }
if ( m_metadataLookup != null )
try { m_metadataLookup . Dispose (); }
finally { m_metadataLookup = null ; }
if ( m_fileScantimeLookup != null )
try { m_fileScantimeLookup . Dispose (); }
finally { m_fileScantimeLookup = null ; }
if ( m_filesetLookup != null )
try { m_filesetLookup . Dispose (); }
finally { m_filesetLookup = null ; }
2013-03-11 20:13:40 +01:00
base . Dispose ();
}
2013-07-01 17:04:57 +02:00
2013-04-06 13:46:58 +02:00
private long GetPreviousFilesetID ( System . Data . IDbCommand cmd )
2013-07-01 17:04:57 +02:00
{
return GetPreviousFilesetID ( cmd , OperationTimestamp , m_filesetId );
}
private long GetPreviousFilesetID ( System . Data . IDbCommand cmd , DateTime timestamp , long filesetid )
2013-04-03 21:06:27 +02:00
{
2013-04-25 19:45:24 +02:00
long lastFilesetId = - 1 ;
2013-04-03 21:06:27 +02:00
2013-07-23 21:28:01 +02:00
var lastIdObj = cmd . ExecuteScalar ( @"SELECT ""ID"" FROM ""Fileset"" WHERE ""Timestamp"" < ? AND ""ID"" != ? ORDER BY ""Timestamp"" DESC " , NormalizeDateTimeToEpochSeconds ( timestamp ), filesetid );
2013-04-03 21:06:27 +02:00
if ( lastIdObj != null && lastIdObj != DBNull . Value )
2013-04-25 19:45:24 +02:00
lastFilesetId = Convert . ToInt64 ( lastIdObj );
2013-04-03 21:06:27 +02:00
2013-04-25 19:45:24 +02:00
return lastFilesetId ;
2013-04-03 21:06:27 +02:00
}
2013-03-11 20:13:40 +01:00
2013-05-25 16:40:15 +02:00
internal void UpdateChangeStatistics ( BackupResults results )
2013-03-08 22:24:54 +01:00
{
using ( var cmd = m_connection . CreateCommand ())
{
2013-04-06 13:46:58 +02:00
var lastFilesetId = GetPreviousFilesetID ( cmd );
2013-05-13 22:32:05 +02:00
results . AddedFolders = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" = ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)" , m_filesetId , FOLDER_BLOCKSET_ID , lastFilesetId ));
results . AddedSymlinks = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" = ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)" , m_filesetId , SYMLINK_BLOCKSET_ID , lastFilesetId ));
results . AddedFiles = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" != ? AND ""File"".""BlocksetID"" != ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)" , m_filesetId , FOLDER_BLOCKSET_ID , SYMLINK_BLOCKSET_ID , lastFilesetId ));
2013-04-06 13:46:58 +02:00
2013-05-13 22:32:05 +02:00
results . DeletedFolders = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" = ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)" , lastFilesetId , FOLDER_BLOCKSET_ID , m_filesetId ));
results . DeletedSymlinks = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" = ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)" , lastFilesetId , SYMLINK_BLOCKSET_ID , m_filesetId ));
results . DeletedFiles = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" != ? AND ""File"".""BlocksetID"" != ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)" , lastFilesetId , FOLDER_BLOCKSET_ID , SYMLINK_BLOCKSET_ID , m_filesetId ));
2013-04-06 13:46:58 +02:00
2013-04-25 19:49:15 +02:00
var subqueryNonFiles = @"SELECT ""File"".""Path"", ""Blockset"".""Fullhash"" FROM ""File"", ""FilesetEntry"", ""Metadataset"", ""Blockset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""Metadataset"".""ID"" = ""File"".""MetadataID"" AND ""File"".""BlocksetID"" = ? AND ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FilesetID"" = ? " ;
var subqueryFiles = @"SELECT ""File"".""Path"", ""A"".""Fullhash"" AS ""Filehash"", ""B"".""Fullhash"" AS ""Metahash"" FROM ""File"", ""FilesetEntry"", ""Blockset"" A, ""Blockset"" B, ""Metadataset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""A"".""ID"" = ""File"".""BlocksetID"" AND ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""MetadataID"" = ""Metadataset"".""ID"" AND ""Metadataset"".""BlocksetID"" = ""B"".""ID"" " ;
2013-05-13 22:32:05 +02:00
results . ModifiedFolders = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT(*) FROM (" + subqueryNonFiles + @") A, (" + subqueryNonFiles + @") B WHERE ""A"".""Path"" = ""B"".""Path"" AND ""A"".""Fullhash"" != ""B"".""Fullhash"" " , lastFilesetId , FOLDER_BLOCKSET_ID , m_filesetId , FOLDER_BLOCKSET_ID ));
results . ModifiedSymlinks = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT(*) FROM (" + subqueryNonFiles + @") A, (" + subqueryNonFiles + @") B WHERE ""A"".""Path"" = ""B"".""Path"" AND ""A"".""Fullhash"" != ""B"".""Fullhash"" " , lastFilesetId , SYMLINK_BLOCKSET_ID , m_filesetId , SYMLINK_BLOCKSET_ID ));
results . ModifiedFiles = Convert . ToInt64 ( cmd . ExecuteScalar ( @"SELECT COUNT(*) FROM (" + subqueryFiles + @") A, (" + subqueryFiles + @") B WHERE ""A"".""Path"" = ""B"".""Path"" AND (""A"".""Filehash"" != ""B"".""Filehash"" OR ""A"".""Metahash"" != ""B"".""Metahash"")" , lastFilesetId , FOLDER_BLOCKSET_ID , SYMLINK_BLOCKSET_ID , m_filesetId ));
2013-03-08 22:24:54 +01:00
}
2013-03-10 20:33:30 +01:00
2013-07-12 10:02:28 +02:00
if ( m_blockHashLookup != null && m_fileHashLookup != null && (( m_blockHashLookup . PositiveMisses + m_blockHashLookup . NegativeMisses ) > HASH_MISS_THRESHOLD || ( m_fileHashLookup . PositiveMisses + m_fileHashLookup . NegativeMisses ) > HASH_MISS_THRESHOLD ))
2013-07-23 18:54:53 +02:00
results . AddWarning ( string . Format ( "Lookup tables gave false positives, this may indicate too small tables. Block: {0}, File: {0}" , m_blockHashLookup . PositiveMisses + m_blockHashLookup . NegativeMisses , m_fileHashLookup . PositiveMisses + m_fileHashLookup . NegativeMisses ), null );
2013-03-15 21:14:00 +01:00
2013-07-12 10:02:28 +02:00
if ( m_blockHashLookup != null && ( m_blockHashLookup . PositiveMisses + m_blockHashLookup . NegativeMisses ) > HASH_MISS_THRESHOLD && m_blockHashLookup . FullUsageRatio > FULL_HASH_USAGE_THRESHOLD )
results . AddWarning ( string . Format ( "Block hash lookup table is too small, usage is: {0}%" , m_blockHashLookup . FullUsageRatio * 100 ), null );
2013-03-15 21:14:00 +01:00
2013-07-12 10:02:28 +02:00
if ( m_fileHashLookup != null && ( m_fileHashLookup . PositiveMisses + m_fileHashLookup . NegativeMisses ) > HASH_MISS_THRESHOLD && m_fileHashLookup . FullUsageRatio > FULL_HASH_USAGE_THRESHOLD )
results . AddWarning ( string . Format ( "File hash lookup table is too small, usage is: {0}%" , m_fileHashLookup . FullUsageRatio * 100 ), null );
2013-03-15 21:14:00 +01:00
2013-07-12 10:02:28 +02:00
if ( m_metadataLookup != null && ( m_metadataLookup . PositiveMisses + m_metadataLookup . NegativeMisses ) > HASH_MISS_THRESHOLD && m_metadataLookup . FullUsageRatio > FULL_HASH_USAGE_THRESHOLD )
results . AddWarning ( string . Format ( "Metadata hash lookup table is too small, usage is: {0}%" , m_metadataLookup . FullUsageRatio * 100 ), null );
2013-03-15 21:14:00 +01:00
2013-07-12 10:02:28 +02:00
if ( m_fileScantimeLookup != null && ( m_fileScantimeLookup . PositiveMisses + m_fileScantimeLookup . NegativeMisses ) > HASH_MISS_THRESHOLD && m_fileScantimeLookup . FullUsageRatio > FULL_HASH_USAGE_THRESHOLD )
results . AddWarning ( string . Format ( "File scantime lookup table is too small, usage is: {0}%" , m_fileScantimeLookup . FullUsageRatio * 100 ), null );
2013-03-10 20:33:30 +01:00
2013-07-12 10:02:28 +02:00
if ( m_filesetLookup != null && ( m_filesetLookup . PositiveMisses + m_filesetLookup . NegativeMisses ) > HASH_MISS_THRESHOLD && m_filesetLookup . FullUsageRatio > FULL_HASH_USAGE_THRESHOLD )
results . AddWarning ( string . Format ( "File id lookup table is too small, usage is: {0}%" , m_filesetLookup . FullUsageRatio * 100 ), null );
2013-03-15 21:14:00 +01:00
2013-03-11 20:13:40 +01:00
// Good for profiling, but takes some time to calculate these stats
2013-03-16 10:37:57 +01:00
if ( Logging . Log . LogLevel == Logging . LogMessageType . Profiling )
2013-03-11 20:13:40 +01:00
{
2013-03-16 10:37:57 +01:00
if ( m_blockHashLookup != null )
{
2013-07-12 08:07:10 +02:00
Logging . Log . WriteMessage ( string . Format ( "Prefix BlockHash Lookup entries {0}, usage: {1}, positive misses: {2}" , m_blockHashLookup . PrefixBits , m_blockHashLookup . PrefixUsageRatio , m_blockHashLookup . PositiveMisses ), Logging . LogMessageType . Profiling );
Logging . Log . WriteMessage ( string . Format ( "Full BlockHash Lookup entries {0}, usage: {1}, negative misses: {2}" , m_blockHashLookup . FullEntries , m_blockHashLookup . FullUsageRatio , m_blockHashLookup . NegativeMisses ), Logging . LogMessageType . Profiling );
2013-03-16 10:37:57 +01:00
}
2013-03-15 21:14:00 +01:00
2013-03-16 10:37:57 +01:00
if ( m_fileHashLookup != null )
{
2013-07-12 08:07:10 +02:00
Logging . Log . WriteMessage ( string . Format ( "Prefix FileHash Lookup entries {0}, usage: {1}, positive misses: {2}" , m_fileHashLookup . PrefixBits , m_fileHashLookup . PrefixUsageRatio , m_fileHashLookup . PositiveMisses ), Logging . LogMessageType . Profiling );
Logging . Log . WriteMessage ( string . Format ( "Full FileHash Lookup entries {0}, usage: {1}, negative misses: {2}" , m_fileHashLookup . FullEntries , m_fileHashLookup . FullUsageRatio , m_fileHashLookup . NegativeMisses ), Logging . LogMessageType . Profiling );
2013-03-16 10:37:57 +01:00
}
2013-03-15 21:14:00 +01:00
2013-03-16 10:37:57 +01:00
if ( m_metadataLookup != null )
{
2013-07-12 08:07:10 +02:00
Logging . Log . WriteMessage ( string . Format ( "Prefix Metadata Lookup entries {0}, usage: {1}, positive misses: {2}" , m_metadataLookup . PrefixBits , m_metadataLookup . PrefixUsageRatio , m_metadataLookup . PositiveMisses ), Logging . LogMessageType . Profiling );
Logging . Log . WriteMessage ( string . Format ( "Full Metadata Lookup entries {0}, usage: {1}, negative misses: {2}" , m_metadataLookup . FullEntries , m_metadataLookup . FullUsageRatio , m_metadataLookup . NegativeMisses ), Logging . LogMessageType . Profiling );
2013-03-16 10:37:57 +01:00
}
2013-03-15 21:14:00 +01:00
2013-03-16 10:37:57 +01:00
if ( m_fileScantimeLookup != null )
2013-03-15 21:14:00 +01:00
{
2013-07-12 08:07:10 +02:00
Logging . Log . WriteMessage ( string . Format ( "Prefix Scantime Lookup entries {0}, usage: {1}, positive misses: {2}" , m_fileScantimeLookup . PrefixBits , m_fileScantimeLookup . PrefixUsageRatio , m_fileScantimeLookup . PositiveMisses ), Logging . LogMessageType . Profiling );
Logging . Log . WriteMessage ( string . Format ( "Full Scantime Lookup entries {0}, usage: {1}, negative misses: {2}" , m_fileScantimeLookup . FullEntries , m_fileScantimeLookup . FullUsageRatio , m_fileScantimeLookup . NegativeMisses ), Logging . LogMessageType . Profiling );
2013-03-16 10:37:57 +01:00
}
2013-03-15 21:14:00 +01:00
2013-03-16 10:37:57 +01:00
if ( m_filesetLookup != null )
{
2013-07-12 08:07:10 +02:00
Logging . Log . WriteMessage ( string . Format ( "Prefix Fileset Lookup entries {0}, usage: {1}, positive misses: {2}" , m_filesetLookup . PrefixBits , m_filesetLookup . PrefixUsageRatio , m_filesetLookup . PositiveMisses ), Logging . LogMessageType . Profiling );
Logging . Log . WriteMessage ( string . Format ( "Full Fileset Lookup entries {0}, usage: {1}, negative misses: {2}" , m_filesetLookup . FullEntries , m_filesetLookup . FullUsageRatio , m_filesetLookup . NegativeMisses ), Logging . LogMessageType . Profiling );
2013-03-15 21:14:00 +01:00
}
2013-03-10 20:33:30 +01:00
}
2013-03-08 22:24:54 +01:00
}
2013-03-15 22:04:07 +01:00
public void AppendFilesFromPreviousSet ( System . Data . IDbTransaction transaction , IEnumerable < string > deleted )
2013-07-01 17:04:57 +02:00
{
AppendFilesFromPreviousSet ( transaction , deleted , m_filesetId , OperationTimestamp );
}
public void AppendFilesFromPreviousSet ( System . Data . IDbTransaction transaction , IEnumerable < string > deleted , long filesetid , DateTime timestamp )
2013-03-15 22:04:07 +01:00
{
using ( var cmd = m_connection . CreateCommand ())
using ( var tr = new TemporaryTransactionWrapper ( m_connection , transaction ))
{
2013-07-01 17:04:57 +02:00
long lastFilesetId = GetPreviousFilesetID ( cmd , timestamp , filesetid );
2013-03-15 22:04:07 +01:00
cmd . Transaction = tr . Parent ;
2013-07-01 17:04:57 +02:00
cmd . ExecuteNonQuery ( @"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Scantime"") SELECT ? AS ""FilesetID"", ""FileID"", ""Scantime"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ? AND ""FileID"" NOT IN (SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?) " , filesetid , lastFilesetId , filesetid );
2013-03-15 22:04:07 +01:00
if ( deleted != null )
{
2013-04-06 13:46:58 +02:00
cmd . CommandText = @"DELETE FROM ""FilesetEntry"" WHERE ""FilesetID"" = ? AND ""FileID"" IN (SELECT ""ID"" FROM ""File"" WHERE ""Path"" = ?) " ;
2013-07-01 17:04:57 +02:00
cmd . AddParameter ( filesetid );
2013-03-15 22:04:07 +01:00
cmd . AddParameter ();
foreach ( string s in deleted )
{
cmd . SetParameterValue ( 1 , s );
cmd . ExecuteNonQuery ();
}
}
2013-06-10 23:04:44 +02:00
tr . Commit ();
2013-03-15 22:04:07 +01:00
}
}
2013-07-22 16:54:19 +02:00
/// <summary>
/// Creates a timestamped backup operation to correctly associate the fileset with the time it was created.
/// </summary>
/// <param name="volumeid">The ID of the fileset volume to update</param>
/// <param name="timestamp">The timestamp of the operation to create</param>
/// <param name="transaction">An optional external transaction</param>
public override long CreateFileset ( long volumeid , DateTime timestamp , System . Data . IDbTransaction transaction = null )
{
return m_filesetId = base . CreateFileset ( volumeid , timestamp , transaction );
}
2013-07-01 17:23:05 +02:00
public IEnumerable < KeyValuePair < long , DateTime >> GetIncompleteFilesets ( System . Data . IDbTransaction transaction )
{
using ( var cmd = m_connection . CreateCommand ())
{
cmd . Transaction = transaction ;
2013-07-23 21:28:01 +02:00
using ( var rd = cmd . ExecuteReader ( @"SELECT ""ID"", ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" IN (SELECT ""FilesetID"" FROM ""FilesetEntry"") AND ""VolumeID"" NOT IN (SELECT ""ID"" FROM ""RemoteVolume"")" ))
2013-07-01 17:23:05 +02:00
while ( rd . Read ())
{
yield return new KeyValuePair < long , DateTime >(
Convert . ToInt64 ( rd . GetValue ( 0 )),
2013-07-16 14:51:59 +02:00
ParseFromEpochSeconds ( Convert . ToInt64 ( rd . GetValue ( 1 ))). ToLocalTime ()
2013-07-01 17:23:05 +02:00
);
}
}
}
public void LinkFilesetToVolume ( long filesetid , long volumeid , System . Data . IDbTransaction transaction )
{
using ( var cmd = m_connection . CreateCommand ())
{
cmd . Transaction = transaction ;
var c = cmd . ExecuteNonQuery ( @"UPDATE ""Fileset"" SET ""VolumeID"" = ? WHERE ""ID"" = ?" , volumeid , filesetid );
if ( c != 1 )
throw new Exception ( string . Format ( "Failed to link filesetid {0} to volumeid {1}" , filesetid , volumeid ));
}
}
2013-03-08 22:24:54 +01:00
}
}