2013-03-27 16:06:45 +01:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
2013-04-21 20:00:37 +02:00
using System.IO ;
2013-03-27 16:06:45 +01:00
2013-05-08 20:17:07 +02:00
namespace Duplicati.Library.Main.Database
2013-03-27 16:06:45 +01:00
{
2013-08-22 20:52:54 +02:00
internal class LocalDatabase : IDisposable
2016-09-15 11:39:27 +02:00
{
2013-03-27 16:06:45 +01:00
protected readonly System . Data . IDbConnection m_connection ;
protected readonly long m_operationid = - 1 ;
private readonly System . Data . IDbCommand m_updateremotevolumeCommand ;
private readonly System . Data . IDbCommand m_selectremotevolumesCommand ;
private readonly System . Data . IDbCommand m_selectremotevolumeCommand ;
private readonly System . Data . IDbCommand m_removeremotevolumeCommand ;
2016-09-15 11:39:27 +02:00
private readonly System . Data . IDbCommand m_selectremotevolumeIdCommand ;
2013-03-27 16:06:45 +01:00
private readonly System . Data . IDbCommand m_createremotevolumeCommand ;
2016-03-24 16:31:07 +01:00
private readonly System . Data . IDbCommand m_selectduplicateRemoteVolumesCommand ;
2013-03-27 16:06:45 +01:00
private readonly System . Data . IDbCommand m_insertlogCommand ;
2013-03-08 22:24:54 +01:00
private readonly System . Data . IDbCommand m_insertremotelogCommand ;
2013-07-22 16:54:19 +02:00
private readonly System . Data . IDbCommand m_insertIndexBlockLink ;
2013-03-27 16:06:45 +01:00
2013-08-22 20:52:54 +02:00
protected BasicResults m_result ;
2013-05-25 16:40:15 +02:00
2013-03-27 16:06:45 +01:00
public const long FOLDER_BLOCKSET_ID = - 100 ;
public const long SYMLINK_BLOCKSET_ID = - 200 ;
2013-03-08 22:24:54 +01:00
public DateTime OperationTimestamp { get ; private set ; }
internal System . Data . IDbConnection Connection { get { return m_connection ; } }
2013-05-25 16:40:15 +02:00
public bool IsDisposed { get ; private set ; }
2013-03-08 22:24:54 +01:00
2016-04-06 20:40:34 +02:00
public bool ShouldCloseConnection { get ; set ; }
2016-01-28 22:24:51 +01:00
2018-01-21 07:35:07 +01:00
private System . Data . IDbTransaction m_transaction ;
private LocalDatabase m_parent ;
internal System . Data . IDbTransaction Transaction { get { return m_parent ?. Transaction ?? m_transaction ; } }
2013-03-08 22:24:54 +01:00
protected static System . Data . IDbConnection CreateConnection ( string path )
{
2016-09-15 11:39:27 +02:00
path = System . IO . Path . GetFullPath ( path );
2013-03-08 22:24:54 +01:00
if (! System . IO . Directory . Exists ( System . IO . Path . GetDirectoryName ( path )))
System . IO . Directory . CreateDirectory ( System . IO . Path . GetDirectoryName ( path ));
2017-01-15 23:09:47 +01:00
var c = Duplicati . Library . SQLiteHelper . SQLiteLoader . LoadConnection ( path );
2013-03-08 22:24:54 +01:00
2014-04-07 11:59:33 +02:00
Library . SQLiteHelper . DatabaseUpgrader . UpgradeDatabase ( c , path , typeof ( LocalDatabase ));
2013-03-27 16:06:45 +01:00
return c ;
}
/// <summary>
/// Creates a new database instance and starts a new operation
/// </summary>
/// <param name="path">The path to the database</param>
2013-03-08 22:24:54 +01:00
/// <param name="operation">The name of the operation</param>
2016-04-06 20:40:34 +02:00
public LocalDatabase ( string path , string operation , bool shouldclose )
2013-03-08 22:24:54 +01:00
: this ( CreateConnection ( path ), operation )
{
2016-04-06 20:40:34 +02:00
ShouldCloseConnection = shouldclose ;
2018-01-21 07:35:07 +01:00
this . m_transaction = m_connection . BeginTransaction ();
2013-03-27 16:06:45 +01:00
}
2013-04-04 20:34:26 +02:00
/// <summary>
/// Creates a new database instance and starts a new operation
/// </summary>
public LocalDatabase ( LocalDatabase db )
2016-09-15 11:39:27 +02:00
: this ( db . m_connection )
{
this . OperationTimestamp = db . OperationTimestamp ;
this . m_connection = db . m_connection ;
this . m_operationid = db . m_operationid ;
2013-08-22 20:52:54 +02:00
this . m_result = db . m_result ;
2018-01-21 07:35:07 +01:00
this . m_parent = db ;
2016-09-15 11:39:27 +02:00
}
2013-03-27 16:06:45 +01:00
/// <summary>
/// Creates a new database instance and starts a new operation
/// </summary>
2013-03-08 22:24:54 +01:00
/// <param name="operation">The name of the operation</param>
2018-01-21 07:35:07 +01:00
private LocalDatabase ( System . Data . IDbConnection connection , string operation )
2016-09-15 11:39:27 +02:00
: this ( connection )
2013-03-27 16:06:45 +01:00
{
2013-03-08 22:24:54 +01:00
this . OperationTimestamp = DateTime . UtcNow ;
m_connection = connection ;
if ( m_connection . State != System . Data . ConnectionState . Open )
2013-03-27 16:06:45 +01:00
m_connection . Open ();
using ( var cmd = m_connection . CreateCommand ())
2015-01-24 21:59:53 +01:00
m_operationid = cmd . ExecuteScalarInt64 ( @"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES (?, ?); SELECT last_insert_rowid();" , - 1 , operation , NormalizeDateTimeToEpochSeconds ( OperationTimestamp ));
2016-09-15 11:39:27 +02:00
}
private LocalDatabase ( System . Data . IDbConnection connection )
{
2013-04-04 20:34:26 +02:00
m_updateremotevolumeCommand = connection . CreateCommand ();
m_selectremotevolumesCommand = connection . CreateCommand ();
2016-03-24 16:31:07 +01:00
m_selectduplicateRemoteVolumesCommand = connection . CreateCommand ();
2013-04-04 20:34:26 +02:00
m_selectremotevolumeCommand = connection . CreateCommand ();
m_insertlogCommand = connection . CreateCommand ();
m_insertremotelogCommand = connection . CreateCommand ();
m_removeremotevolumeCommand = connection . CreateCommand ();
2016-09-15 11:39:27 +02:00
m_selectremotevolumeIdCommand = connection . CreateCommand ();
m_createremotevolumeCommand = connection . CreateCommand ();
2013-07-22 16:54:19 +02:00
m_insertIndexBlockLink = connection . CreateCommand ();
2013-03-27 16:06:45 +01:00
m_insertlogCommand . CommandText = @"INSERT INTO ""LogData"" (""OperationID"", ""Timestamp"", ""Type"", ""Message"", ""Exception"") VALUES (?, ?, ?, ?, ?)" ;
2013-04-04 20:34:26 +02:00
m_insertlogCommand . AddParameters ( 5 );
2013-03-27 16:06:45 +01:00
m_insertremotelogCommand . CommandText = @"INSERT INTO ""RemoteOperation"" (""OperationID"", ""Timestamp"", ""Operation"", ""Path"", ""Data"") VALUES (?, ?, ?, ?, ?)" ;
2013-04-04 20:34:26 +02:00
m_insertremotelogCommand . AddParameters ( 5 );
2013-03-27 16:06:45 +01:00
m_updateremotevolumeCommand . CommandText = @"UPDATE ""Remotevolume"" SET ""OperationID"" = ?, ""State"" = ?, ""Hash"" = ?, ""Size"" = ? WHERE ""Name"" = ?" ;
2013-04-04 20:34:26 +02:00
m_updateremotevolumeCommand . AddParameters ( 5 );
2013-03-27 16:06:45 +01:00
2016-02-22 21:27:12 +01:00
m_selectremotevolumesCommand . CommandText = @"SELECT ""ID"", ""Name"", ""Type"", ""Size"", ""Hash"", ""State"", ""DeleteGraceTime"" FROM ""Remotevolume""" ;
2013-03-27 16:06:45 +01:00
2016-02-22 21:27:12 +01:00
m_selectremotevolumeCommand . CommandText = m_selectremotevolumesCommand . CommandText + @" WHERE ""Name"" = ?" ;
2016-03-30 01:30:51 +02:00
m_selectduplicateRemoteVolumesCommand . CommandText = string . Format ( @"SELECT DISTINCT ""Name"", ""State"" FROM ""Remotevolume"" WHERE ""Name"" IN (SELECT ""Name"" FROM ""Remotevolume"" WHERE ""State"" IN (""{0}"", ""{1}"")) AND NOT ""State"" IN (""{0}"", ""{1}"")" , RemoteVolumeState . Deleted . ToString (), RemoteVolumeState . Deleting . ToString ());
2016-03-24 16:31:07 +01:00
2013-03-27 16:06:45 +01:00
m_selectremotevolumeCommand . AddParameter ();
2017-09-18 11:53:50 +02:00
m_removeremotevolumeCommand . CommandText = @"DELETE FROM ""Remotevolume"" WHERE ""Name"" = ? AND (""DeleteGraceTime"" < ? OR ""State"" != ?)" ;
m_removeremotevolumeCommand . AddParameters ( 3 );
2013-03-27 16:06:45 +01:00
2016-09-15 11:39:27 +02:00
m_selectremotevolumeIdCommand . CommandText = @"SELECT ""ID"" FROM ""Remotevolume"" WHERE ""Name"" = ?" ;
2013-03-27 16:06:45 +01:00
2016-09-15 11:39:27 +02:00
m_createremotevolumeCommand . CommandText = @"INSERT INTO ""Remotevolume"" (""OperationID"", ""Name"", ""Type"", ""State"", ""Size"", ""VerificationCount"", ""DeleteGraceTime"") VALUES (?, ?, ?, ?, ?, ?, ?); SELECT last_insert_rowid();" ;
2015-08-24 10:50:47 +01:00
m_createremotevolumeCommand . AddParameters ( 7 );
2013-07-22 16:54:19 +02:00
m_insertIndexBlockLink . CommandText = @"INSERT INTO ""IndexBlockLink"" (""IndexVolumeID"", ""BlockVolumeID"") VALUES (?, ?)" ;
m_insertIndexBlockLink . AddParameters ( 2 );
2016-09-15 11:39:27 +02:00
}
2013-05-25 16:40:15 +02:00
internal void SetResult ( BasicResults result )
{
m_result = result ;
}
2016-09-15 11:39:27 +02:00
2013-07-09 13:51:08 +02:00
/// <summary>
/// Normalizes a DateTime instance floor'ed to seconds and in UTC
/// </summary>
/// <returns>The normalised date time</returns>
/// <param name="input">The input time</param>
public static DateTime NormalizeDateTime ( DateTime input )
{
var ticks = input . ToUniversalTime (). Ticks ;
ticks -= ticks % TimeSpan . TicksPerSecond ;
return new DateTime ( ticks , DateTimeKind . Utc );
}
2013-07-23 21:28:01 +02:00
public static long NormalizeDateTimeToEpochSeconds ( DateTime input )
{
return ( long ) Math . Floor (( NormalizeDateTime ( input ) - Library . Utility . Utility . EPOCH ). TotalSeconds );
}
2013-07-16 14:51:59 +02:00
/// <summary>
/// Creates a DateTime instance by adding the specified number of seconds to the EPOCH value
/// </summary>
public static DateTime ParseFromEpochSeconds ( long seconds )
{
return Library . Utility . Utility . EPOCH . AddSeconds ( seconds );
}
2016-03-16 00:49:28 +01:00
2018-01-21 07:35:07 +01:00
public void UpdateRemoteVolume ( string name , RemoteVolumeState state , long size , string hash )
2016-03-24 16:30:19 +01:00
{
2018-01-21 07:35:07 +01:00
UpdateRemoteVolume ( name , state , size , hash , false );
2016-03-24 16:30:19 +01:00
}
2016-03-16 00:49:28 +01:00
2018-01-21 07:35:07 +01:00
public void UpdateRemoteVolume ( string name , RemoteVolumeState state , long size , string hash , bool suppressCleanup )
2016-03-24 16:30:19 +01:00
{
2018-01-21 07:35:07 +01:00
UpdateRemoteVolume ( name , state , size , hash , suppressCleanup , new TimeSpan ( 0 ));
2016-03-24 16:30:19 +01:00
}
2018-01-21 07:35:07 +01:00
public void UpdateRemoteVolume ( string name , RemoteVolumeState state , long size , string hash , bool suppressCleanup , TimeSpan deleteGraceTime )
2013-03-27 16:06:45 +01:00
{
2018-01-21 07:35:07 +01:00
m_updateremotevolumeCommand . Transaction = Transaction ;
2013-04-09 20:43:27 +02:00
m_updateremotevolumeCommand . SetParameterValue ( 0 , m_operationid );
m_updateremotevolumeCommand . SetParameterValue ( 1 , state . ToString ());
m_updateremotevolumeCommand . SetParameterValue ( 2 , hash );
m_updateremotevolumeCommand . SetParameterValue ( 3 , size );
m_updateremotevolumeCommand . SetParameterValue ( 4 , name );
var c = m_updateremotevolumeCommand . ExecuteNonQuery ();
if ( c != 1 )
2014-12-30 18:25:30 +01:00
throw new Exception ( string . Format ( "Unexpected number of remote volumes detected: {0}!" , c ));
2016-03-16 00:49:28 +01:00
2016-03-24 16:30:19 +01:00
if ( deleteGraceTime . Ticks > 0 )
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
2016-03-24 16:30:19 +01:00
if (( c = cmd . ExecuteNonQuery ( @"UPDATE ""RemoteVolume"" SET ""DeleteGraceTime"" = ? WHERE ""Name"" = ? " , ( DateTime . UtcNow + deleteGraceTime ). Ticks , name )) != 1 )
2018-01-21 02:46:17 -06:00
throw new Exception ( string . Format ( "Unexpected number of updates when recording remote volume updates: {0}!" , c ));
2016-03-24 16:30:19 +01:00
2016-03-16 00:49:28 +01:00
if (! suppressCleanup && state == RemoteVolumeState . Deleted )
2018-01-21 07:35:07 +01:00
RemoveRemoteVolume ( name );
2013-03-27 16:06:45 +01:00
}
2013-05-11 22:56:21 +02:00
public IEnumerable < KeyValuePair < long , DateTime >> FilesetTimes
{
get
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
2013-07-23 21:28:01 +02:00
using ( var rd = cmd . ExecuteReader ( @"SELECT ""ID"", ""Timestamp"" FROM ""Fileset"" ORDER BY ""Timestamp"" DESC" ))
2013-05-11 22:56:21 +02:00
while ( rd . Read ())
2015-01-24 21:59:53 +01:00
yield return new KeyValuePair < long , DateTime >( rd . GetInt64 ( 0 ), ParseFromEpochSeconds ( rd . GetInt64 ( 1 )). ToLocalTime ());
2013-05-11 22:56:21 +02:00
}
}
2015-04-08 21:01:36 +02:00
public Tuple < string , object []> GetFilelistWhereClause ( DateTime time , long [] versions , IEnumerable < KeyValuePair < long , DateTime >> filesetslist = null , bool singleTimeMatch = false )
2016-09-15 11:39:27 +02:00
{
var filesets = ( filesetslist ?? this . FilesetTimes ). ToArray ();
string query = "" ;
var args = new List < object >();
2013-05-20 13:48:44 +02:00
if ( time . Ticks > 0 || ( versions != null && versions . Length > 0 ))
{
2013-06-20 20:17:10 +02:00
var hasTime = false ;
2013-05-20 13:48:44 +02:00
if ( time . Ticks > 0 )
{
if ( time . Kind == DateTimeKind . Unspecified )
throw new Exception ( "Invalid DateTime given, must be either local or UTC" );
2013-06-26 21:52:59 +02:00
2015-04-08 21:01:36 +02:00
query += singleTimeMatch ? @" ""Timestamp"" = ?" : @" ""Timestamp"" <= ?" ;
2013-08-20 21:37:30 +02:00
// Make sure the resolution is the same (i.e. no milliseconds)
args . Add ( NormalizeDateTimeToEpochSeconds ( time ));
2013-06-20 20:17:10 +02:00
hasTime = true ;
2013-05-20 13:48:44 +02:00
}
if ( versions != null && versions . Length > 0 )
{
2014-01-19 23:25:17 +01:00
var qs = "" ;
2013-05-20 13:48:44 +02:00
foreach ( var v in versions )
if ( v >= 0 && v < filesets . Length )
{
args . Add ( filesets [ v ]. Key );
qs += "?," ;
}
2013-08-06 22:57:03 +02:00
else
m_result . AddWarning ( string . Format ( "Skipping invalid version: {0}" , v ), null );
2013-05-20 13:48:44 +02:00
if ( qs . Length > 0 )
{
qs = qs . Substring ( 0 , qs . Length - 1 );
2013-06-20 20:17:10 +02:00
if ( hasTime )
2013-06-26 21:52:59 +02:00
query += " OR " ;
2013-05-20 13:48:44 +02:00
query += @" ""ID"" IN (" + qs + ")" ;
}
}
2013-08-06 22:57:03 +02:00
if (! string . IsNullOrEmpty ( query ))
query = " WHERE " + query ;
2013-05-20 13:48:44 +02:00
}
return new Tuple < string , object []>( query , args . ToArray ());
}
2018-01-21 07:35:07 +01:00
public long GetRemoteVolumeID ( string file )
2016-09-15 11:39:27 +02:00
{
2018-01-21 07:35:07 +01:00
m_selectremotevolumeIdCommand . Transaction = Transaction ;
2016-09-15 11:39:27 +02:00
return m_selectremotevolumeIdCommand . ExecuteScalarInt64 ( null , - 1 , file );
}
2013-03-27 16:06:45 +01:00
2018-01-21 07:35:07 +01:00
public RemoteVolumeEntry GetRemoteVolume ( string file )
2013-03-27 16:06:45 +01:00
{
2018-01-21 07:35:07 +01:00
m_selectremotevolumeCommand . Transaction = Transaction ;
2013-03-27 16:06:45 +01:00
m_selectremotevolumeCommand . SetParameterValue ( 0 , file );
2016-02-22 21:27:12 +01:00
using ( var rd = m_selectremotevolumeCommand . ExecuteReader ())
2013-03-27 16:06:45 +01:00
if ( rd . Read ())
2016-02-22 21:27:12 +01:00
return new RemoteVolumeEntry (
rd . ConvertValueToInt64 ( 0 ),
rd . GetValue ( 1 ). ToString (),
( rd . GetValue ( 4 ) == null || rd . GetValue ( 4 ) == DBNull . Value ) ? null : rd . GetValue ( 4 ). ToString (),
rd . ConvertValueToInt64 ( 3 , - 1 ),
( RemoteVolumeType ) Enum . Parse ( typeof ( RemoteVolumeType ), rd . GetValue ( 2 ). ToString ()),
( RemoteVolumeState ) Enum . Parse ( typeof ( RemoteVolumeState ), rd . GetValue ( 5 ). ToString ()),
new DateTime ( rd . ConvertValueToInt64 ( 6 , 0 ), DateTimeKind . Utc )
);
2016-03-30 08:30:09 +02:00
2016-02-22 21:27:12 +01:00
return RemoteVolumeEntry . Empty ;
2013-03-27 16:06:45 +01:00
}
2016-03-30 01:30:51 +02:00
public IEnumerable < KeyValuePair < string , RemoteVolumeState >> DuplicateRemoteVolumes ()
2016-03-24 16:31:07 +01:00
{
2018-01-21 07:35:07 +01:00
m_selectduplicateRemoteVolumesCommand . Transaction = Transaction ;
2016-03-24 16:31:07 +01:00
foreach ( var rd in m_selectduplicateRemoteVolumesCommand . ExecuteReaderEnumerable ( null ))
{
2016-03-30 01:30:51 +02:00
yield return new KeyValuePair < string , RemoteVolumeState >(
2016-03-24 16:31:07 +01:00
rd . GetValue ( 0 ). ToString (),
2016-03-30 01:30:51 +02:00
( RemoteVolumeState ) Enum . Parse ( typeof ( RemoteVolumeState ), rd . GetValue ( 1 ). ToString ())
2016-03-24 16:31:07 +01:00
);
}
}
2018-01-21 07:35:07 +01:00
public IEnumerable < RemoteVolumeEntry > GetRemoteVolumes ()
2013-03-27 16:06:45 +01:00
{
2018-01-21 07:35:07 +01:00
m_selectremotevolumesCommand . Transaction = Transaction ;
2013-03-27 16:06:45 +01:00
using ( var rd = m_selectremotevolumesCommand . ExecuteReader ())
{
while ( rd . Read ())
{
2013-07-01 11:58:33 +02:00
yield return new RemoteVolumeEntry (
2016-02-22 21:27:12 +01:00
rd . ConvertValueToInt64 ( 0 ),
rd . GetValue ( 1 ). ToString (),
( rd . GetValue ( 4 ) == null || rd . GetValue ( 4 ) == DBNull . Value ) ? null : rd . GetValue ( 4 ). ToString (),
rd . ConvertValueToInt64 ( 3 , - 1 ),
( RemoteVolumeType ) Enum . Parse ( typeof ( RemoteVolumeType ), rd . GetValue ( 2 ). ToString ()),
( RemoteVolumeState ) Enum . Parse ( typeof ( RemoteVolumeState ), rd . GetValue ( 5 ). ToString ()),
new DateTime ( rd . ConvertValueToInt64 ( 6 , 0 ), DateTimeKind . Utc )
2013-03-27 16:06:45 +01:00
);
}
}
}
/// <summary>
/// Log an operation performed on the remote backend
/// </summary>
/// <param name="operation">The operation performed</param>
/// <param name="path">The path involved</param>
/// <param name="data">Any data relating to the operation</param>
2018-01-21 07:35:07 +01:00
public void LogRemoteOperation ( string operation , string path , string data )
2013-03-27 16:06:45 +01:00
{
2018-01-21 07:35:07 +01:00
m_insertremotelogCommand . Transaction = Transaction ;
2013-04-09 20:43:27 +02:00
m_insertremotelogCommand . SetParameterValue ( 0 , m_operationid );
2013-07-24 17:21:38 +02:00
m_insertremotelogCommand . SetParameterValue ( 1 , NormalizeDateTimeToEpochSeconds ( DateTime . UtcNow ));
2013-04-09 20:43:27 +02:00
m_insertremotelogCommand . SetParameterValue ( 2 , operation );
m_insertremotelogCommand . SetParameterValue ( 3 , path );
m_insertremotelogCommand . SetParameterValue ( 4 , data );
m_insertremotelogCommand . ExecuteNonQuery ();
2013-03-27 16:06:45 +01:00
}
/// <summary>
/// Log a debug message
/// </summary>
/// <param name="type">The message type</param>
/// <param name="message">The message</param>
/// <param name="exception">An optional exception</param>
2018-01-21 07:35:07 +01:00
public void LogMessage ( string type , string message , Exception exception )
2013-03-27 16:06:45 +01:00
{
2018-01-21 07:35:07 +01:00
m_insertlogCommand . Transaction = Transaction ;
2013-04-09 20:43:27 +02:00
m_insertlogCommand . SetParameterValue ( 0 , m_operationid );
2013-07-24 17:21:38 +02:00
m_insertlogCommand . SetParameterValue ( 1 , NormalizeDateTimeToEpochSeconds ( DateTime . UtcNow ));
2013-04-09 20:43:27 +02:00
m_insertlogCommand . SetParameterValue ( 2 , type );
m_insertlogCommand . SetParameterValue ( 3 , message );
m_insertlogCommand . SetParameterValue ( 4 , exception == null ? null : exception . ToString ());
m_insertlogCommand . ExecuteNonQuery ();
2013-03-27 16:06:45 +01:00
}
2018-01-21 07:35:07 +01:00
public void UnlinkRemoteVolume ( string name , RemoteVolumeState state )
2016-03-24 16:31:07 +01:00
{
using ( var cmd = m_connection . CreateCommand ())
{
2018-01-21 07:35:07 +01:00
cmd . Transaction = Transaction ;
2016-03-24 16:31:07 +01:00
var c = cmd . ExecuteNonQuery ( @"DELETE FROM ""RemoteVolume"" WHERE ""Name"" = ? AND ""State"" = ? " , name , state . ToString ());
if ( c != 1 )
throw new Exception ( string . Format ( "Unexpected number of remote volumes deleted: {0}, expected {1}" , c , 1 ));
}
}
2018-01-21 07:35:07 +01:00
public void RemoveRemoteVolume ( string name )
2013-03-27 16:06:45 +01:00
{
2018-01-21 07:35:07 +01:00
RemoveRemoteVolumes ( new string [] { name });
2016-03-16 00:49:28 +01:00
}
2018-01-21 07:35:07 +01:00
public void RemoveRemoteVolumes ( IEnumerable < string > names )
2016-03-16 00:49:28 +01:00
{
2016-12-24 11:42:26 +01:00
if ( names == null || ! names . Any ()) return ;
2016-03-16 00:49:28 +01:00
2013-04-09 20:43:27 +02:00
using ( var deletecmd = m_connection . CreateCommand ())
2013-03-27 16:06:45 +01:00
{
2018-01-21 07:35:07 +01:00
deletecmd . Transaction = Transaction ;
2016-03-16 00:49:28 +01:00
string temptransguid = Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
var volidstable = "DelVolSetIds-" + temptransguid ;
var blocksetidstable = "DelBlockSetIds-" + temptransguid ;
// Create and fill a temp table with the volids to delete. We avoid using too many parameters that way.
deletecmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMP TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY)" , volidstable ));
deletecmd . CommandText = string . Format ( @"INSERT OR IGNORE INTO ""{0}"" (""ID"") VALUES (?)" , volidstable );
deletecmd . Parameters . Clear ();
deletecmd . AddParameters ( 1 );
foreach ( var name in names )
{
2018-01-21 07:35:07 +01:00
var volumeid = GetRemoteVolumeID ( name );
2016-03-16 00:49:28 +01:00
deletecmd . SetParameterValue ( 0 , volumeid );
deletecmd . ExecuteNonQuery ();
}
var volIdsSubQuery = string . Format ( @"SELECT ""ID"" FROM ""{0}"" " , volidstable );
deletecmd . Parameters . Clear ();
2013-04-22 21:06:52 +02:00
2016-09-15 11:39:27 +02:00
// If the volume is a block or index volume, this will update the crosslink table, otherwise nothing will happen
2016-03-16 00:49:28 +01:00
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""IndexBlockLink"" WHERE ""BlockVolumeID"" IN ({0}) OR ""IndexVolumeID"" IN ({0})" , volIdsSubQuery ));
2016-09-15 11:39:27 +02:00
2013-06-28 14:58:56 +02:00
// If the volume is a fileset, this will remove the fileset, otherwise nothing will happen
2016-03-16 00:49:28 +01:00
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""FilesetEntry"" WHERE ""FilesetID"" IN (SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ({0}))" , volIdsSubQuery ));
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""Fileset"" WHERE ""VolumeID"" IN ({0})" , volIdsSubQuery ));
2013-08-23 22:14:17 +02:00
2016-03-16 00:49:28 +01:00
var bsIdsSubQuery = string . Format (
@"SELECT ""BlocksetEntry"".""BlocksetID"" FROM ""BlocksetEntry"", ""Block"" "
+ @" WHERE ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" AND ""Block"".""VolumeID"" IN ({0}) "
+ @"UNION ALL "
+ @"SELECT ""BlocksetID"" FROM ""BlocklistHash"" "
+ @"WHERE ""Hash"" IN (SELECT ""Hash"" FROM ""Block"" WHERE ""VolumeID"" IN ({0}))"
, volIdsSubQuery );
2013-04-08 22:24:54 +02:00
2016-03-13 13:23:06 +01:00
// Create a temporary table to cache subquery result, as it might take long (SQLite does not cache at all).
deletecmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMP TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY)" , blocksetidstable ));
2016-03-16 00:49:28 +01:00
deletecmd . ExecuteNonQuery ( string . Format ( @"INSERT OR IGNORE INTO ""{0}"" (""ID"") {1}" , blocksetidstable , bsIdsSubQuery ));
bsIdsSubQuery = string . Format ( @"SELECT ""ID"" FROM ""{0}"" " , blocksetidstable );
2016-03-13 13:23:06 +01:00
deletecmd . Parameters . Clear ();
2016-03-16 00:49:28 +01:00
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""File"" WHERE ""BlocksetID"" IN ({0}) OR ""MetadataID"" IN ({0})" , bsIdsSubQuery ));
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""Metadataset"" WHERE ""BlocksetID"" IN ({0})" , bsIdsSubQuery ));
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""Blockset"" WHERE ""ID"" IN ({0})" , bsIdsSubQuery ));
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""BlocksetEntry"" WHERE ""BlocksetID"" IN ({0})" , bsIdsSubQuery ));
2014-12-30 16:15:21 +01:00
2016-03-16 00:49:28 +01:00
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""BlocklistHash"" WHERE ""Hash"" IN (SELECT ""Hash"" FROM ""Block"" WHERE ""VolumeID"" IN ({0}))" , volIdsSubQuery ));
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""Block"" WHERE ""VolumeID"" IN ({0})" , volIdsSubQuery ));
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""DeletedBlock"" WHERE ""VolumeID"" IN ({0})" , volIdsSubQuery ));
2013-03-27 16:06:45 +01:00
2016-03-16 00:49:28 +01:00
// Clean up temp tables for subqueries. We truncate content and then try to delete.
2016-03-13 13:23:06 +01:00
// Drop in try-block, as it fails in nested transactions (SQLite problem)
// System.Data.SQLite.SQLiteException (0x80004005): database table is locked
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""{0}"" " , blocksetidstable ));
2016-03-16 00:49:28 +01:00
deletecmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""{0}"" " , volidstable ));
2016-03-13 13:23:06 +01:00
try
{
deletecmd . CommandTimeout = 2 ;
deletecmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , blocksetidstable ));
2016-03-16 00:49:28 +01:00
deletecmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , volidstable ));
2016-03-13 13:23:06 +01:00
}
2017-09-18 11:53:50 +02:00
catch { /* Ignore, will be deleted on close anyway. */ }
2018-01-21 07:35:07 +01:00
m_removeremotevolumeCommand . Transaction = Transaction ;
2017-09-18 11:53:50 +02:00
m_removeremotevolumeCommand . SetParameterValue ( 1 , DateTime . UtcNow . Ticks );
m_removeremotevolumeCommand . SetParameterValue ( 2 , RemoteVolumeState . Deleted . ToString ());
foreach ( var name in names )
2016-03-16 00:49:28 +01:00
{
m_removeremotevolumeCommand . SetParameterValue ( 0 , name );
m_removeremotevolumeCommand . ExecuteNonQuery ();
}
2013-03-27 16:06:45 +01:00
}
}
2013-08-23 22:15:07 +02:00
public void Vacuum ()
{
using ( var cmd = m_connection . CreateCommand ())
cmd . ExecuteNonQuery ( "VACUUM" );
}
2013-03-27 16:06:45 +01:00
2015-08-24 10:50:47 +01:00
public long RegisterRemoteVolume ( string name , RemoteVolumeType type , long size , RemoteVolumeState state )
2015-04-05 14:33:13 +02:00
{
2018-01-21 07:35:07 +01:00
return RegisterRemoteVolume ( name , type , state , size , new TimeSpan ( 0 ));
2015-04-05 14:33:13 +02:00
}
2018-01-21 07:35:07 +01:00
public long RegisterRemoteVolume ( string name , RemoteVolumeType type , RemoteVolumeState state )
2015-04-05 14:33:13 +02:00
{
2018-01-21 07:35:07 +01:00
return RegisterRemoteVolume ( name , type , state , new TimeSpan ( 0 ));
2015-04-05 14:33:13 +02:00
}
2015-08-24 10:50:47 +01:00
2018-01-21 07:35:07 +01:00
public long RegisterRemoteVolume ( string name , RemoteVolumeType type , RemoteVolumeState state , TimeSpan deleteGraceTime )
2015-08-24 10:50:47 +01:00
{
2018-01-21 07:35:07 +01:00
return RegisterRemoteVolume ( name , type , state , - 1 , deleteGraceTime );
2015-08-24 10:50:47 +01:00
}
2018-01-21 07:35:07 +01:00
public long RegisterRemoteVolume ( string name , RemoteVolumeType type , RemoteVolumeState state , long size , TimeSpan deleteGraceTime )
2016-09-15 11:39:27 +02:00
{
2018-01-21 07:35:07 +01:00
m_createremotevolumeCommand . SetParameterValue ( 0 , m_operationid );
m_createremotevolumeCommand . SetParameterValue ( 1 , name );
m_createremotevolumeCommand . SetParameterValue ( 2 , type . ToString ());
m_createremotevolumeCommand . SetParameterValue ( 3 , state . ToString ());
m_createremotevolumeCommand . SetParameterValue ( 4 , size );
m_createremotevolumeCommand . SetParameterValue ( 5 , 0 );
if ( deleteGraceTime . Ticks <= 0 )
m_createremotevolumeCommand . SetParameterValue ( 6 , 0 );
else
m_createremotevolumeCommand . SetParameterValue ( 6 , ( DateTime . UtcNow + deleteGraceTime ). Ticks );
m_createremotevolumeCommand . Transaction = Transaction ;
return m_createremotevolumeCommand . ExecuteScalarInt64 ();
2013-03-27 16:06:45 +01:00
}
2013-08-24 22:27:30 +02:00
2013-05-20 13:48:44 +02:00
public long GetFilesetID ( DateTime restoretime , long [] versions )
2013-08-24 22:27:30 +02:00
{
return GetFilesetIDs ( restoretime , versions ). First ();
}
public IEnumerable < long > GetFilesetIDs ( DateTime restoretime , long [] versions )
{
if ( restoretime . Kind == DateTimeKind . Unspecified )
throw new Exception ( "Invalid DateTime given, must be either local or UTC" );
2013-03-27 16:06:45 +01:00
2013-08-24 22:27:30 +02:00
var tmp = GetFilelistWhereClause ( restoretime , versions );
string query = tmp . Item1 ;
var args = tmp . Item2 ;
2013-05-20 13:48:44 +02:00
2013-08-24 22:27:30 +02:00
var res = new List < long >();
using ( var cmd = m_connection . CreateCommand ())
{
using ( var rd = cmd . ExecuteReader ( @"SELECT ""ID"" FROM ""Fileset"" " + query + @" ORDER BY ""Timestamp"" DESC" , args ))
while ( rd . Read ())
2015-01-24 21:59:53 +01:00
res . Add ( rd . GetInt64 ( 0 ));
2013-08-24 22:27:30 +02:00
if ( res . Count == 0 )
2013-03-27 16:06:45 +01:00
{
cmd . Parameters . Clear ();
2013-08-24 22:27:30 +02:00
using ( var rd = cmd . ExecuteReader ( @"SELECT ""ID"" FROM ""Fileset"" ORDER BY ""Timestamp"" DESC " ))
while ( rd . Read ())
2017-01-05 09:57:06 +01:00
res . Add ( rd . ConvertValueToInt64 ( 0 ));
2013-08-24 22:27:30 +02:00
if ( res . Count == 0 )
2017-01-09 11:35:38 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( "No backup at the specified date" );
2013-08-06 22:57:03 +02:00
else
m_result . AddWarning ( string . Format ( "Restore time or version did not match any existing backups, selecting newest backup" ), null );
2013-03-27 16:06:45 +01:00
}
2013-08-24 22:27:30 +02:00
return res ;
2013-03-27 16:06:45 +01:00
}
2013-03-08 22:24:54 +01:00
}
2015-04-08 21:01:36 +02:00
public IEnumerable < long > FindMatchingFilesets ( DateTime restoretime , long [] versions )
{
if ( restoretime . Kind == DateTimeKind . Unspecified )
throw new Exception ( "Invalid DateTime given, must be either local or UTC" );
var tmp = GetFilelistWhereClause ( restoretime , versions , singleTimeMatch : true );
string query = tmp . Item1 ;
var args = tmp . Item2 ;
var res = new List < long >();
using ( var cmd = m_connection . CreateCommand ())
using ( var rd = cmd . ExecuteReader ( @"SELECT ""ID"" FROM ""Fileset"" " + query + @" ORDER BY ""Timestamp"" DESC" , args ))
while ( rd . Read ())
res . Add ( rd . GetInt64 ( 0 ));
return res ;
}
2013-03-08 22:24:54 +01:00
2018-01-21 07:35:07 +01:00
/// <summary>
/// Writes the current changes to the database
/// </summary>
/// <returns>An awaitable task.</returns>
/// <param name="message">The message to use for logging the time spent in this operation.</param>
/// <param name="restart">If set to <c>true</c>, a transaction will be started again after this call.</param>
public void CommitTransaction ( string message , bool restart = true )
2013-03-08 22:24:54 +01:00
{
2018-01-21 07:35:07 +01:00
if ( m_parent != null )
m_parent . CommitTransaction ( message , restart );
else
2013-03-08 22:24:54 +01:00
{
2018-01-21 07:35:07 +01:00
using ( new Logging . Timer ( message ))
2013-03-08 22:24:54 +01:00
{
2018-01-21 07:35:07 +01:00
m_transaction . Commit ();
m_transaction = restart ? m_connection . BeginTransaction () : null ;
2013-03-08 22:24:54 +01:00
}
}
2018-01-21 07:35:07 +01:00
}
2013-03-08 22:24:54 +01:00
2018-01-21 07:35:07 +01:00
public void RollbackTransaction ( bool restart = true )
{
if ( m_parent != null )
m_parent . RollbackTransaction ();
else
2013-03-08 22:24:54 +01:00
{
2018-01-21 07:35:07 +01:00
m_transaction . Rollback ();
if ( restart )
m_transaction = m_connection . BeginTransaction ();
2013-03-08 22:24:54 +01:00
}
2018-01-21 07:35:07 +01:00
}
2013-03-08 22:24:54 +01:00
2013-03-31 19:33:34 +02:00
private class LocalFileEntry : ILocalFileEntry
2013-03-27 16:06:45 +01:00
{
2013-03-31 19:33:34 +02:00
private System . Data . IDataReader m_reader ;
public LocalFileEntry ( System . Data . IDataReader reader )
2013-03-27 16:06:45 +01:00
{
2013-03-31 19:33:34 +02:00
m_reader = reader ;
}
2013-03-27 16:06:45 +01:00
2013-03-31 19:33:34 +02:00
public string Path
{
get
2013-03-27 16:06:45 +01:00
{
2013-03-31 19:33:34 +02:00
var c = m_reader . GetValue ( 0 );
if ( c == null || c == DBNull . Value )
return null ;
return c . ToString ();
2013-03-27 16:06:45 +01:00
}
2013-03-31 19:33:34 +02:00
}
2013-03-27 16:06:45 +01:00
2013-03-31 19:33:34 +02:00
public long Length
{
get
2013-03-27 16:06:45 +01:00
{
2015-01-24 21:59:53 +01:00
return m_reader . ConvertValueToInt64 ( 1 );;
2013-03-27 16:06:45 +01:00
}
2013-03-31 19:33:34 +02:00
}
2013-03-27 16:06:45 +01:00
2013-03-31 19:33:34 +02:00
public string Hash
{
get
2013-03-27 16:06:45 +01:00
{
2013-03-31 19:33:34 +02:00
var c = m_reader . GetValue ( 2 );
if ( c == null || c == DBNull . Value )
return null ;
return c . ToString ();
2013-03-27 16:06:45 +01:00
}
}
2013-03-31 19:33:34 +02:00
public string Metahash
2013-03-27 16:06:45 +01:00
{
2013-03-31 19:33:34 +02:00
get
{
var c = m_reader . GetValue ( 3 );
if ( c == null || c == DBNull . Value )
return null ;
return c . ToString ();
}
2013-03-27 16:06:45 +01:00
}
}
2013-03-31 19:33:34 +02:00
2013-04-06 13:46:58 +02:00
public IEnumerable < ILocalFileEntry > GetFiles ( long filesetId )
2013-03-27 16:06:45 +01:00
{
2013-03-31 19:33:34 +02:00
using ( var cmd = m_connection . CreateCommand ())
2013-04-06 13:46:58 +02:00
using ( var rd = cmd . ExecuteReader ( @"SELECT ""A"".""Path"", ""B"".""Length"", ""B"".""FullHash"", ""D"".""FullHash"" FROM ""File"" A, ""Blockset"" B, ""Metadataset"" C, ""Blockset"" D, ""FilesetEntry"" E WHERE ""A"".""BlocksetID"" = ""B"".""ID"" AND ""A"".""MetadataID"" = ""C"".""ID"" AND ""C"".""BlocksetID"" = ""D"".""ID"" AND ""A"".""ID"" = ""E"".""FileID"" AND ""E"".""FilesetID"" = ? " , filesetId ))
2013-03-31 19:33:34 +02:00
while ( rd . Read ())
2016-09-15 11:39:27 +02:00
yield return new LocalFileEntry ( rd );
2013-03-27 16:06:45 +01:00
}
2018-01-21 07:35:07 +01:00
private IEnumerable < KeyValuePair < string , string >> GetDbOptionList ()
2016-09-15 11:39:27 +02:00
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
2013-03-31 19:33:34 +02:00
using ( var rd = cmd . ExecuteReader ( @"SELECT ""Key"", ""Value"" FROM ""Configuration"" " ))
while ( rd . Read ())
2016-09-15 11:39:27 +02:00
yield return new KeyValuePair < string , string >( rd . GetValue ( 0 ). ToString (), rd . GetValue ( 1 ). ToString ());
}
2018-01-21 07:35:07 +01:00
public IDictionary < string , string > GetDbOptions ()
2016-09-15 11:39:27 +02:00
{
2018-01-21 07:35:07 +01:00
return GetDbOptionList (). ToDictionary ( x => x . Key , x => x . Value );
2016-09-15 11:39:27 +02:00
}
2016-03-18 13:26:12 +01:00
public bool RepairInProgress
{
get
{
return GetDbOptions (). ContainsKey ( "repair-in-progress" );
}
set
{
var opts = GetDbOptions ();
if ( value )
opts [ "repair-in-progress" ] = "true" ;
else
opts . Remove ( "repair-in-progress" );
SetDbOptions ( opts );
}
}
2016-09-13 21:55:15 +02:00
2016-09-15 11:39:27 +02:00
public bool PartiallyRecreated
{
get
{
return GetDbOptions (). ContainsKey ( "partially-recreated" );
}
set
{
var opts = GetDbOptions ();
2016-09-13 21:55:15 +02:00
2016-09-15 11:39:27 +02:00
if ( value )
opts [ "partially-recreated" ] = "true" ;
else
opts . Remove ( "partially-recreated" );
2016-09-13 21:55:15 +02:00
2016-09-15 11:39:27 +02:00
SetDbOptions ( opts );
}
}
2018-01-21 07:35:07 +01:00
public void SetDbOptions ( IDictionary < string , string > options )
2016-09-15 11:39:27 +02:00
{
2013-03-31 19:33:34 +02:00
using ( var cmd = m_connection . CreateCommand ())
2016-09-15 11:39:27 +02:00
{
2018-01-21 07:35:07 +01:00
cmd . Transaction = Transaction ;
2016-09-15 11:39:27 +02:00
cmd . ExecuteNonQuery ( @"DELETE FROM ""Configuration"" " );
foreach ( var kp in options )
cmd . ExecuteNonQuery ( @"INSERT INTO ""Configuration"" (""Key"", ""Value"") VALUES (?, ?) " , kp . Key , kp . Value );
}
}
2013-03-31 19:33:34 +02:00
2016-09-15 11:39:27 +02:00
public long GetBlocksLargerThan ( long fhblocksize )
{
2013-03-31 19:33:34 +02:00
using ( var cmd = m_connection . CreateCommand ())
2016-09-15 11:39:27 +02:00
return cmd . ExecuteScalarInt64 ( @"SELECT COUNT(*) FROM ""Block"" WHERE ""Size"" > ?" , - 1 , fhblocksize );
}
2013-03-31 19:33:34 +02:00
2018-01-21 07:35:07 +01:00
public void VerifyConsistency ( long blocksize , long hashsize , bool verifyfilelists )
2013-04-21 20:00:37 +02:00
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
2013-04-21 20:00:37 +02:00
{
2013-09-25 23:31:48 +02:00
// Calculate the lengths for each blockset
2016-04-04 22:47:16 +02:00
var combinedLengths = @"
SELECT
""A"".""ID"" AS ""BlocksetID"",
IFNULL(""B"".""CalcLen"", 0) AS ""CalcLen"",
""A"".""Length""
FROM
""Blockset"" A
LEFT OUTER JOIN
(
SELECT
""BlocksetEntry"".""BlocksetID"",
SUM(""Block"".""Size"") AS ""CalcLen""
FROM
""BlocksetEntry""
LEFT OUTER JOIN
""Block""
ON
""Block"".""ID"" = ""BlocksetEntry"".""BlockID""
GROUP BY ""BlocksetEntry"".""BlocksetID""
) B
ON
""A"".""ID"" = ""B"".""BlocksetID""
" ;
2013-09-25 23:31:48 +02:00
// For each blockset with wrong lengths, fetch the file path
var reportDetails = @"SELECT ""CalcLen"", ""Length"", ""A"".""BlocksetID"", ""File"".""Path"" FROM (" + combinedLengths + @") A, ""File"" WHERE ""A"".""BlocksetID"" = ""File"".""BlocksetID"" AND ""A"".""CalcLen"" != ""A"".""Length"" " ;
using ( var rd = cmd . ExecuteReader ( reportDetails ))
2016-09-15 11:39:27 +02:00
if ( rd . Read ())
{
var sb = new StringBuilder ();
sb . AppendLine ( "Found inconsistency in the following files while validating database: " );
var c = 0 ;
do
{
if ( c < 5 )
sb . AppendFormat ( "{0}, actual size {1}, dbsize {2}, blocksetid: {3}{4}" , rd . GetValue ( 3 ), rd . GetValue ( 1 ), rd . GetValue ( 0 ), rd . GetValue ( 2 ), Environment . NewLine );
c ++;
} while ( rd . Read ());
c -= 5 ;
if ( c > 0 )
sb . AppendFormat ( "... and {0} more" , c );
2015-01-12 23:07:57 +01:00
sb . Append ( ". Run repair to fix it." );
2016-09-15 11:39:27 +02:00
throw new InvalidDataException ( sb . ToString ());
}
2014-12-30 16:13:31 +01:00
2015-01-24 21:59:53 +01:00
var real_count = cmd . ExecuteScalarInt64 ( @"SELECT Count(*) FROM ""BlocklistHash""" , 0 );
var unique_count = cmd . ExecuteScalarInt64 ( @"SELECT Count(*) FROM (SELECT DISTINCT ""BlocksetID"", ""Index"" FROM ""BlocklistHash"")" , 0 );
2014-12-30 16:13:31 +01:00
2015-01-24 21:59:53 +01:00
if ( real_count != unique_count )
throw new InvalidDataException ( string . Format ( "Found {0} blocklist hashes, but there should be {1}. Run repair to fix it." , real_count , unique_count ));
2015-02-03 23:47:33 +01:00
var itemswithnoblocklisthash = cmd . ExecuteScalarInt64 ( string . Format ( @"SELECT COUNT(*) FROM (SELECT * FROM (SELECT ""N"".""BlocksetID"", ((""N"".""BlockCount"" + {0} - 1) / {0}) AS ""BlocklistHashCountExpected"", CASE WHEN ""G"".""BlocklistHashCount"" IS NULL THEN 0 ELSE ""G"".""BlocklistHashCount"" END AS ""BlocklistHashCountActual"" FROM (SELECT ""BlocksetID"", COUNT(*) AS ""BlockCount"" FROM ""BlocksetEntry"" GROUP BY ""BlocksetID"") ""N"" LEFT OUTER JOIN (SELECT ""BlocksetID"", COUNT(*) AS ""BlocklistHashCount"" FROM ""BlocklistHash"" GROUP BY ""BlocksetID"") ""G"" ON ""N"".""BlocksetID"" = ""G"".""BlocksetID"" WHERE ""N"".""BlockCount"" > 1) WHERE ""BlocklistHashCountExpected"" != ""BlocklistHashCountActual"")" , blocksize / hashsize ), 0 );
if ( itemswithnoblocklisthash != 0 )
throw new InvalidDataException ( string . Format ( "Found {0} file(s) with missing blocklist hashes" , itemswithnoblocklisthash ));
2017-08-12 19:14:11 +01:00
if ( cmd . ExecuteScalarInt64 ( @"SELECT COUNT(*) FROM ""Blockset"" WHERE ""Length"" > 0 AND ""ID"" NOT IN (SELECT ""BlocksetId"" FROM ""BlocksetEntry"")" ) != 0 )
2017-08-12 14:09:55 +01:00
{
throw new Exception ( "Detected non-empty blocksets with no associated blocks!" );
}
2016-03-30 12:29:22 +02:00
2017-08-12 14:09:55 +01:00
if ( cmd . ExecuteScalarInt64 ( @"SELECT COUNT(*) FROM ""File"" WHERE ""BlocksetID"" != ? AND ""BlocksetID"" != ? AND NOT ""BlocksetID"" IN (SELECT ""ID"" FROM ""Blockset"")" , 0 , FOLDER_BLOCKSET_ID , SYMLINK_BLOCKSET_ID ) != 0 )
throw new Exception ( "Detected files associated with non-existing blocksets!" );
2016-04-04 18:11:48 +02:00
if ( verifyfilelists )
{
2018-01-21 07:35:07 +01:00
using ( var cmd2 = m_connection . CreateCommand ( Transaction ))
2016-04-04 18:11:48 +02:00
foreach ( var filesetid in cmd . ExecuteReaderEnumerable ( @"SELECT ""ID"" FROM ""Fileset"" " ). Select ( x => x . ConvertValueToInt64 ( 0 , - 1 )))
{
2017-08-12 14:09:55 +01:00
var expandedCmd = string . Format ( @"SELECT COUNT(*) FROM (SELECT DISTINCT ""Path"" FROM ({0}) UNION SELECT DISTINCT ""Path"" FROM ({1}))" , LocalDatabase . LIST_FILESETS , LocalDatabase . LIST_FOLDERS_AND_SYMLINKS );
var expandedlist = cmd2 . ExecuteScalarInt64 ( expandedCmd , 0 , filesetid , FOLDER_BLOCKSET_ID , SYMLINK_BLOCKSET_ID , filesetid );
2016-04-04 18:11:48 +02:00
//var storedfilelist = cmd2.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""FilesetEntry"", ""File"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" != ? AND ""File"".""BlocksetID"" != ?"), 0, filesetid, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID);
var storedlist = cmd2 . ExecuteScalarInt64 ( string . Format ( @"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FilesetEntry"".""FilesetID"" = ?" ), 0 , filesetid );
if ( expandedlist != storedlist )
throw new Exception ( string . Format ( "Unexpected difference in fileset {0}, found {1} entries, but expected {2}" , filesetid , expandedlist , storedlist ));
}
}
2013-04-21 20:00:37 +02:00
}
}
2013-04-27 15:13:14 +02:00
public interface IBlock
{
string Hash { get ; }
long Size { get ; }
}
internal class Block : IBlock
{
public string Hash { get ; private set ; }
public long Size { get ; private set ; }
public Block ( string hash , long size )
{
this . Hash = hash ;
this . Size = size ;
}
}
2018-01-21 07:35:07 +01:00
public IEnumerable < IBlock > GetBlocks ( long volumeid )
2013-04-27 15:13:14 +02:00
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
2013-04-27 15:13:14 +02:00
using ( var rd = cmd . ExecuteReader ( @"SELECT DISTINCT ""Hash"", ""Size"" FROM ""Block"" WHERE ""VolumeID"" = ?" , volumeid ))
while ( rd . Read ())
2015-01-24 21:59:53 +01:00
yield return new Block ( rd . GetValue ( 0 ). ToString (), rd . GetInt64 ( 1 ));
2016-09-15 11:39:27 +02:00
}
2013-04-27 15:13:14 +02:00
private class BlocklistHashEnumerable : IEnumerable < string >
{
private class BlocklistHashEnumerator : IEnumerator < string >
{
private System . Data . IDataReader m_reader ;
private BlocklistHashEnumerable m_parent ;
private string m_path = null ;
private bool m_first = true ;
private string m_current = null ;
public BlocklistHashEnumerator ( BlocklistHashEnumerable parent , System . Data . IDataReader reader )
{
m_reader = reader ;
m_parent = parent ;
}
public string Current { get { return m_current ; } }
public void Dispose ()
{
}
object System . Collections . IEnumerator . Current { get { return this . Current ; } }
public bool MoveNext ()
{
m_first = false ;
if ( m_path == null )
{
m_path = m_reader . GetValue ( 0 ). ToString ();
m_current = m_reader . GetValue ( 6 ). ToString ();
return true ;
}
else
{
if ( m_current == null )
return false ;
if (! m_reader . Read ())
{
m_current = null ;
m_parent . MoreData = false ;
return false ;
}
var np = m_reader . GetValue ( 0 ). ToString ();
if ( m_path != np )
{
m_current = null ;
return false ;
}
m_current = m_reader . GetValue ( 6 ). ToString ();
return true ;
}
}
public void Reset ()
{
if (! m_first )
throw new Exception ( "Iterator reset not supported" );
m_first = false ;
}
}
private System . Data . IDataReader m_reader ;
public BlocklistHashEnumerable ( System . Data . IDataReader reader )
{
m_reader = reader ;
this . MoreData = true ;
}
public bool MoreData { get ; protected set ; }
public IEnumerator < string > GetEnumerator ()
{
return new BlocklistHashEnumerator ( this , m_reader );
}
System . Collections . IEnumerator System . Collections . IEnumerable . GetEnumerator ()
{
return this . GetEnumerator ();
}
}
2016-04-04 18:11:48 +02:00
public const string LIST_FILESETS = @"
SELECT
""L"".""Path"",
""L"".""Lastmodified"",
""L"".""Filelength"",
""L"".""Filehash"",
""L"".""Metahash"",
""L"".""Metalength"",
""L"".""BlocklistHash"",
""L"".""FirstBlockHash"",
""L"".""FirstBlockSize"",
""L"".""FirstMetaBlockHash"",
""L"".""FirstMetaBlockSize"",
""M"".""Hash"" AS ""MetaBlocklistHash""
FROM
(
SELECT
""J"".""Path"",
""J"".""Lastmodified"",
""J"".""Filelength"",
""J"".""Filehash"",
""J"".""Metahash"",
""J"".""Metalength"",
""K"".""Hash"" AS ""BlocklistHash"",
""J"".""FirstBlockHash"",
""J"".""FirstBlockSize"",
""J"".""FirstMetaBlockHash"",
""J"".""FirstMetaBlockSize"",
""J"".""MetablocksetID""
FROM
(
SELECT
2017-08-12 14:09:55 +01:00
""A"".""Path"" AS ""Path"",
""D"".""Lastmodified"" AS ""Lastmodified"",
""B"".""Length"" AS ""Filelength"",
""B"".""FullHash"" AS ""Filehash"",
""E"".""FullHash"" AS ""Metahash"",
""E"".""Length"" AS ""Metalength"",
""A"".""BlocksetID"" AS ""BlocksetID"",
""F"".""Hash"" AS ""FirstBlockHash"",
""F"".""Size"" AS ""FirstBlockSize"",
""H"".""Hash"" AS ""FirstMetaBlockHash"",
""H"".""Size"" AS ""FirstMetaBlockSize"",
""C"".""BlocksetID"" AS ""MetablocksetID""
2016-04-04 18:11:48 +02:00
FROM
2017-08-12 14:09:55 +01:00
""File"" A
LEFT JOIN ""Blockset"" B
ON ""A"".""BlocksetID"" = ""B"".""ID""
LEFT JOIN ""Metadataset"" C
ON ""A"".""MetadataID"" = ""C"".""ID""
LEFT JOIN ""FilesetEntry"" D
ON ""A"".""ID"" = ""D"".""FileID""
LEFT JOIN ""Blockset"" E
ON ""E"".""ID"" = ""C"".""BlocksetID""
LEFT JOIN ""BlocksetEntry"" G
ON ""B"".""ID"" = ""G"".""BlocksetID""
LEFT JOIN ""Block"" F
ON ""G"".""BlockID"" = ""F"".""ID""
LEFT JOIN ""BlocksetEntry"" I
ON ""E"".""ID"" = ""I"".""BlocksetID""
LEFT JOIN ""Block"" H
ON ""I"".""BlockID"" = ""H"".""ID""
2016-04-04 18:11:48 +02:00
WHERE
2017-08-12 14:09:55 +01:00
""A"".""BlocksetId"" >= 0 AND
""D"".""FilesetID"" = ? AND
(""I"".""Index"" = 0 OR ""I"".""Index"" IS NULL) AND
(""G"".""Index"" = 0 OR ""G"".""Index"" IS NULL)
2016-04-04 18:11:48 +02:00
) J
LEFT OUTER JOIN
""BlocklistHash"" K
ON
""K"".""BlocksetID"" = ""J"".""BlocksetID""
ORDER BY ""J"".""Path"", ""K"".""Index""
) L
LEFT OUTER JOIN
""BlocklistHash"" M
ON
""M"".""BlocksetID"" = ""L"".""MetablocksetID""
" ;
public const string LIST_FOLDERS_AND_SYMLINKS = @"
SELECT
""G"".""BlocksetID"",
""G"".""ID"",
""G"".""Path"",
""G"".""Length"",
""G"".""FullHash"",
""G"".""Lastmodified"",
""G"".""FirstMetaBlockHash"",
""H"".""Hash"" AS ""MetablocklistHash""
FROM
(
SELECT
""B"".""BlocksetID"",
""B"".""ID"",
""B"".""Path"",
""D"".""Length"",
""D"".""FullHash"",
""A"".""Lastmodified"",
""F"".""Hash"" AS ""FirstMetaBlockHash"",
""C"".""BlocksetID"" AS ""MetaBlocksetID""
FROM
""FilesetEntry"" A,
""File"" B,
""Metadataset"" C,
""Blockset"" D,
""BlocksetEntry"" E,
""Block"" F
WHERE
""A"".""FileID"" = ""B"".""ID""
AND ""B"".""MetadataID"" = ""C"".""ID""
AND ""C"".""BlocksetID"" = ""D"".""ID""
AND ""E"".""BlocksetID"" = ""C"".""BlocksetID""
AND ""E"".""BlockID"" = ""F"".""ID""
AND ""E"".""Index"" = 0
AND (""B"".""BlocksetID"" = ? OR ""B"".""BlocksetID"" = ?)
AND ""A"".""FilesetID"" = ?
) G
LEFT OUTER JOIN
""BlocklistHash"" H
ON
""H"".""BlocksetID"" = ""G"".""MetaBlocksetID""
ORDER BY
""G"".""Path"", ""H"".""Index""
" ;
2018-01-21 07:35:07 +01:00
public void WriteFileset ( Volumes . FilesetVolumeWriter filesetvolume , long filesetId )
2013-04-27 15:13:14 +02:00
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
2013-04-27 15:13:14 +02:00
{
2016-04-04 18:11:48 +02:00
cmd . CommandText = LIST_FOLDERS_AND_SYMLINKS ;
2013-04-27 15:13:14 +02:00
cmd . AddParameter ( FOLDER_BLOCKSET_ID );
cmd . AddParameter ( SYMLINK_BLOCKSET_ID );
cmd . AddParameter ( filesetId );
2016-04-04 18:11:48 +02:00
string lastpath = null ;
2013-04-27 15:13:14 +02:00
using ( var rd = cmd . ExecuteReader ())
while ( rd . Read ())
{
2016-04-04 18:11:48 +02:00
var blocksetID = rd . ConvertValueToInt64 ( 0 , - 1 );
var path = rd . GetValue ( 2 ). ToString ();
var metalength = rd . ConvertValueToInt64 ( 3 , - 1 );
var metahash = rd . GetValue ( 4 ). ToString ();
var metablockhash = rd . GetValue ( 6 ). ToString ();
var metablocklisthash = rd . GetValue ( 7 ). ToString ();
if ( path == lastpath )
m_result . AddWarning ( string . Format ( "Duplicate path detected: {0}!" , path ), null );
lastpath = path ;
2013-04-27 15:13:14 +02:00
if ( blocksetID == FOLDER_BLOCKSET_ID )
2016-04-04 18:11:48 +02:00
filesetvolume . AddDirectory ( path , metahash , metalength , metablockhash , string . IsNullOrWhiteSpace ( metablocklisthash ) ? null : new string [] { metablocklisthash });
2013-04-27 15:13:14 +02:00
else if ( blocksetID == SYMLINK_BLOCKSET_ID )
2016-04-04 18:11:48 +02:00
filesetvolume . AddSymlink ( path , metahash , metalength , metablockhash , string . IsNullOrWhiteSpace ( metablocklisthash ) ? null : new string [] { metablocklisthash });
2013-04-27 15:13:14 +02:00
}
2016-04-04 18:11:48 +02:00
// TODO: Perhaps run the above query after recreate and compare count(*) with count(*) from filesetentry where id = x
2013-04-27 15:13:14 +02:00
2016-04-04 18:11:48 +02:00
cmd . CommandText = LIST_FILESETS ;
2013-04-27 15:13:14 +02:00
cmd . Parameters . Clear ();
cmd . AddParameter ( filesetId );
using ( var rd = cmd . ExecuteReader ())
if ( rd . Read ())
{
var more = false ;
do
{
var path = rd . GetValue ( 0 ). ToString ();
var filehash = rd . GetValue ( 3 ). ToString ();
2015-01-25 12:33:00 +01:00
var size = rd . ConvertValueToInt64 ( 2 );
var lastmodified = new DateTime ( rd . ConvertValueToInt64 ( 1 , 0 ), DateTimeKind . Utc );
2013-04-27 15:13:14 +02:00
var metahash = rd . GetValue ( 4 ). ToString ();
2015-01-25 12:33:00 +01:00
var metasize = rd . ConvertValueToInt64 ( 5 , - 1 );
2013-07-18 23:20:38 +02:00
var p = rd . GetValue ( 6 );
var blrd = ( p == null || p == DBNull . Value ) ? null : new BlocklistHashEnumerable ( rd );
2016-04-03 23:10:47 +02:00
var blockhash = rd . GetValue ( 7 ). ToString ();
var blocksize = rd . ConvertValueToInt64 ( 8 , - 1 );
var metablockhash = rd . GetValue ( 9 ). ToString ();
//var metablocksize = rd.ConvertValueToInt64(10, -1);
2016-04-04 18:11:48 +02:00
var metablocklisthash = rd . GetValue ( 11 ). ToString ();
if ( blockhash == filehash )
blockhash = null ;
if ( metablockhash == metahash )
metablockhash = null ;
2013-04-27 15:13:14 +02:00
2016-04-04 18:11:48 +02:00
filesetvolume . AddFile ( path , filehash , size , lastmodified , metahash , metasize , metablockhash , blockhash , blocksize , blrd , string . IsNullOrWhiteSpace ( metablocklisthash ) ? null : new string [] { metablocklisthash });
2013-07-18 23:20:38 +02:00
if ( blrd == null )
more = rd . Read ();
else
more = blrd . MoreData ;
2013-04-27 15:13:14 +02:00
} while ( more );
}
}
}
2013-05-11 12:03:15 +02:00
/// <summary>
/// Keeps a list of filenames in a temporary table with a single columne Path
///</summary>
public class FilteredFilenameTable : IDisposable
{
public string Tablename { get ; private set ; }
private System . Data . IDbConnection m_connection ;
2013-05-13 22:32:05 +02:00
public FilteredFilenameTable ( System . Data . IDbConnection connection , Library . Utility . IFilter filter , System . Data . IDbTransaction transaction )
2013-05-11 12:03:15 +02:00
{
m_connection = connection ;
Tablename = "Filenames-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2013-05-13 22:32:05 +02:00
var type = Library . Utility . FilterType . Regexp ;
if ( filter is Library . Utility . FilterExpression )
2016-09-15 11:39:27 +02:00
type = (( Library . Utility . FilterExpression ) filter ). Type ;
2015-05-17 13:24:51 +02:00
// Bugfix: SQLite does not handle case-insensitive LIKE with non-ascii characters
if ( type != Library . Utility . FilterType . Regexp && ! Library . Utility . Utility . IsFSCaseSensitive && filter . ToString (). Any ( x => x > 127 ))
type = Library . Utility . FilterType . Regexp ;
2013-05-13 22:32:05 +02:00
if ( type == Library . Utility . FilterType . Regexp )
2013-05-11 12:03:15 +02:00
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( transaction ))
2013-05-11 12:03:15 +02:00
{
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL)" , Tablename ));
2018-01-21 07:35:07 +01:00
cmd . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Path"") VALUES (?)" , Tablename );
cmd . AddParameter ();
using ( var c2 = m_connection . CreateCommand ( transaction ))
using ( var rd = c2 . ExecuteReader ( @"SELECT DISTINCT ""Path"" FROM ""File"" " ))
while ( rd . Read ())
{
var p = rd . GetValue ( 0 ). ToString ();
if ( Library . Utility . FilterExpression . Matches ( filter , p ))
2013-05-11 12:03:15 +02:00
{
2018-01-21 07:35:07 +01:00
cmd . SetParameterValue ( 0 , p );
cmd . ExecuteNonQuery ();
2013-05-11 12:03:15 +02:00
}
2018-01-21 07:35:07 +01:00
}
2013-05-11 12:03:15 +02:00
}
}
else
{
var sb = new StringBuilder ();
var args = new List < object >();
2013-05-13 22:32:05 +02:00
foreach ( var f in (( Library . Utility . FilterExpression ) filter ). GetSimpleList ())
2013-05-11 12:03:15 +02:00
{
if ( f . Contains ( '*' ) || f . Contains ( '?' ))
{
sb . Append ( @"""Path"" LIKE ? OR " );
args . Add ( f . Replace ( '*' , '%' ). Replace ( '?' , '_' ));
}
else
{
2013-05-11 13:04:01 +02:00
sb . Append ( @"""Path"" = ? OR " );
2013-05-11 12:03:15 +02:00
args . Add ( f );
}
}
2013-05-11 13:04:01 +02:00
sb . Length = sb . Length - " OR " . Length ;
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( transaction ))
2013-11-20 19:16:35 +01:00
{
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL)" , Tablename ));
2017-11-26 15:24:53 -08:00
cmd . ExecuteNonQuery ( string . Format ( @"INSERT INTO ""{0}"" SELECT DISTINCT ""Path"" FROM ""File"" WHERE " + sb , Tablename ), args . ToArray ());
2013-11-20 19:16:35 +01:00
}
2013-05-11 12:03:15 +02:00
}
}
public void Dispose ()
{
if ( Tablename != null )
try
{
using ( var cmd = m_connection . CreateCommand ())
2013-08-23 23:36:25 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , Tablename ));
2013-05-11 12:03:15 +02:00
}
2013-08-23 23:36:25 +02:00
catch { }
2013-05-11 12:03:15 +02:00
finally { Tablename = null ; }
}
}
2018-01-21 07:35:07 +01:00
public void RenameRemoteFile ( string oldname , string newname )
2013-07-23 18:56:01 +02:00
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
2013-07-23 18:56:01 +02:00
{
//Rename the old entry, to preserve ID links
var c = cmd . ExecuteNonQuery ( @"UPDATE ""Remotevolume"" SET ""Name"" = ? WHERE ""Name"" = ?" , newname , oldname );
if ( c != 1 )
throw new Exception ( string . Format ( "Unexpected result from renaming \"{0}\" to \"{1}\", expected {2} got {3}" , oldname , newname , 1 , c ));
// Grab the type of entry
var type = ( RemoteVolumeType ) Enum . Parse ( typeof ( RemoteVolumeType ), cmd . ExecuteScalar ( @"SELECT ""Type"" FROM ""Remotevolume"" WHERE ""Name"" = ?" , newname ). ToString (), true );
//Create a fake new entry with the old name and mark as deleting
// as this ensures we will remove it, if it shows up in some later listing
2018-01-21 07:35:07 +01:00
RegisterRemoteVolume ( oldname , type , RemoteVolumeState . Deleting );
2013-07-23 18:56:01 +02: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>
2018-01-21 07:35:07 +01:00
public virtual long CreateFileset ( long volumeid , DateTime timestamp )
2013-07-22 16:54:19 +02:00
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
return cmd . ExecuteScalarInt64 ( @"INSERT INTO ""Fileset"" (""OperationID"", ""Timestamp"", ""VolumeID"") VALUES (?, ?, ?); SELECT last_insert_rowid();" , - 1 , m_operationid , NormalizeDateTimeToEpochSeconds ( timestamp ), volumeid );
2013-07-22 16:54:19 +02:00
}
2018-01-21 07:35:07 +01:00
public void AddIndexBlockLink ( long indexVolumeID , long blockVolumeID )
2013-07-22 16:54:19 +02:00
{
2018-01-21 07:35:07 +01:00
m_insertIndexBlockLink . Transaction = Transaction ;
2013-07-22 16:54:19 +02:00
m_insertIndexBlockLink . SetParameterValue ( 0 , indexVolumeID );
m_insertIndexBlockLink . SetParameterValue ( 1 , blockVolumeID );
m_insertIndexBlockLink . ExecuteNonQuery ();
}
2014-12-30 18:26:08 +01:00
2018-01-21 07:35:07 +01:00
public IEnumerable < Tuple < string , byte [], int >> GetBlocklists ( long volumeid , long blocksize , int hashsize )
2014-12-30 18:26:08 +01:00
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
2014-12-30 18:26:08 +01:00
{
var sql = string . Format ( @"SELECT ""A"".""Hash"", ""C"".""Hash"" FROM " +
2016-02-18 16:48:35 +01:00
@"(SELECT ""BlocklistHash"".""BlocksetID"", ""Block"".""Hash"", * FROM ""BlocklistHash"",""Block"" WHERE ""BlocklistHash"".""Hash"" = ""Block"".""Hash"" AND ""Block"".""VolumeID"" = ?) A, " +
2014-12-30 18:26:08 +01:00
@" ""BlocksetEntry"" B, ""Block"" C WHERE ""B"".""BlocksetID"" = ""A"".""BlocksetID"" AND " +
2016-03-26 13:14:05 +01:00
@" ""B"".""Index"" >= (""A"".""Index"" * {0}) AND ""B"".""Index"" < ((""A"".""Index"" + 1) * {0}) AND ""C"".""ID"" = ""B"".""BlockID"" " +
2014-12-30 18:26:08 +01:00
@" ORDER BY ""A"".""BlocksetID"", ""B"".""Index""" ,
2016-03-26 13:14:05 +01:00
blocksize / hashsize
2014-12-30 18:26:08 +01:00
);
string curHash = null ;
int index = 0 ;
byte [] buffer = new byte [ blocksize ];
2016-02-18 16:48:35 +01:00
using ( var rd = cmd . ExecuteReader ( sql , volumeid ))
2014-12-30 18:26:08 +01:00
while ( rd . Read ())
{
var blockhash = rd . GetValue ( 0 ). ToString ();
2016-02-23 20:56:24 +01:00
if (( blockhash != curHash && curHash != null ) || index + hashsize > buffer . Length )
2014-12-30 18:26:08 +01:00
{
yield return new Tuple < string , byte [], int >( curHash , buffer , index );
curHash = null ;
index = 0 ;
2018-01-22 21:20:46 +01:00
buffer = new byte [ blocksize ];
2014-12-30 18:26:08 +01:00
}
var hash = Convert . FromBase64String ( rd . GetValue ( 1 ). ToString ());
Array . Copy ( hash , 0 , buffer , index , hashsize );
curHash = blockhash ;
index += hashsize ;
2018-01-22 21:20:46 +01:00
2014-12-30 18:26:08 +01:00
}
2016-02-23 20:56:24 +01:00
if ( curHash != null )
2014-12-30 18:26:08 +01:00
yield return new Tuple < string , byte [], int >( curHash , buffer , index );
}
}
2015-02-15 23:26:52 +01:00
public void PurgeLogData ( DateTime threshold )
{
2018-01-21 07:35:07 +01:00
using ( var cmd = m_connection . CreateCommand ( Transaction ))
2015-02-15 23:26:52 +01:00
{
var t = NormalizeDateTimeToEpochSeconds ( threshold );
cmd . ExecuteNonQuery ( @"DELETE FROM ""LogData"" WHERE ""Timestamp"" < ?" , t );
cmd . ExecuteNonQuery ( @"DELETE FROM ""RemoteOperation"" WHERE ""Timestamp"" < ?" , t );
}
}
2013-07-22 16:54:19 +02:00
2013-03-27 16:06:45 +01:00
public virtual void Dispose ()
{
2013-05-25 16:40:15 +02:00
if ( IsDisposed )
return ;
2016-04-06 20:40:34 +02:00
DisposeAllFields < System . Data . IDbCommand >( this , false );
if ( ShouldCloseConnection && m_connection != null )
{
if ( m_connection . State == System . Data . ConnectionState . Open )
m_connection . Close ();
m_connection . Dispose ();
}
2013-05-25 16:40:15 +02:00
IsDisposed = true ;
2016-04-06 20:40:34 +02:00
}
/// <summary>
/// Disposes all fields of a certain type, in the instance and its bases
/// </summary>
/// <typeparam name="T">The type of fields to find</typeparam>
/// <param name="item">The item to dispose</param>
/// <param name="throwExceptions"><c>True</c> if an aggregate exception should be thrown, or <c>false</c> if exceptions are silently captured</param>
public static void DisposeAllFields < T >( object item , bool throwExceptions )
where T : IDisposable
{
var typechain = new List < Type >();
var cur = item . GetType ();
var exceptions = new List < Exception >();
while ( cur != null && cur != typeof ( object ))
{
typechain . Add ( cur );
cur = cur . BaseType ;
}
var fields =
typechain . SelectMany ( x =>
x . GetFields ( System . Reflection . BindingFlags . NonPublic | System . Reflection . BindingFlags . Public | System . Reflection . BindingFlags . Instance | System . Reflection . BindingFlags . FlattenHierarchy )
). Distinct (). Where ( x => x . FieldType . IsAssignableFrom ( typeof ( T )));
foreach ( var p in fields )
try
{
var val = p . GetValue ( item );
if ( val != null )
(( T ) val ). Dispose ();
}
catch ( Exception ex )
{
if ( throwExceptions )
exceptions . Add ( ex );
}
if ( exceptions . Count > 0 )
throw new AggregateException ( exceptions );
2014-07-16 00:57:57 +02:00
}
public void WriteResults ()
{
if ( IsDisposed )
return ;
2013-05-25 16:40:15 +02:00
if ( m_connection != null && m_result != null )
{
m_result . FlushLog ();
2016-12-01 23:59:54 +01:00
if ( m_result . EndTime . Ticks == 0 )
m_result . EndTime = DateTime . UtcNow ;
2016-09-28 20:20:16 +02:00
LogMessage ( "Result" ,
Library . Utility . Utility . PrintSerializeObject (
m_result ,
( StringBuilder ) null ,
2016-12-02 11:54:20 +01:00
( prop , item ) =>
! typeof ( IBackendProgressUpdater ). IsAssignableFrom ( prop . PropertyType ) &&
! typeof ( IMessageSink ). IsAssignableFrom ( prop . PropertyType ) &&
! typeof ( ILogWriter ). IsAssignableFrom ( prop . PropertyType ) &&
prop . Name != "VerboseOutput" &&
prop . Name != "VerboseErrors" &&
!( prop . Name == "MainOperation" && item is BackendWriter ) &&
!( prop . Name == "EndTime" && item is BackendWriter ) &&
!( prop . Name == "Duration" && item is BackendWriter ) &&
!( prop . Name == "BeginTime" && item is BackendWriter ),
2016-09-28 20:20:16 +02:00
recurseobjects : true ,
collectionlimit : 5
). ToString (),
null
2018-01-21 07:35:07 +01:00
);
2013-05-25 16:40:15 +02:00
}
2018-01-23 10:54:47 +01:00
CommitTransaction ( "WriteMessage" );
2013-03-27 16:06:45 +01:00
}
}
}