2024-02-28 15:45:30 +01:00
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
2013-05-11 12:03:15 +02:00
using System ;
using System.Linq ;
using System.Collections.Generic ;
using System.Text ;
2019-08-02 16:42:18 -04:00
using Duplicati.Library.Common.IO ;
2013-05-11 12:03:15 +02:00
namespace Duplicati.Library.Main.Database
{
2013-08-22 20:52:54 +02:00
internal class LocalListDatabase : LocalDatabase
2013-05-11 12:03:15 +02:00
{
public LocalListDatabase ( string path )
2016-04-06 20:40:34 +02:00
: base ( path , "List" , false )
2013-05-11 12:03:15 +02:00
{
2016-04-06 20:40:34 +02:00
ShouldCloseConnection = true ;
2013-05-11 12:03:15 +02:00
}
2013-05-11 22:56:21 +02:00
2013-05-11 12:03:15 +02:00
public interface IFileversion
{
string Path { get ; }
IEnumerable < long > Sizes { get ; }
}
2013-05-30 21:54:43 +02:00
public interface IFileset
{
long Version { get ; }
2019-08-05 20:14:05 -04:00
int IsFullBackup { get ; set ; }
2013-05-30 21:54:43 +02:00
DateTime Time { get ; }
long FileCount { get ; }
long FileSizes { get ; }
}
2013-05-11 12:03:15 +02:00
public interface IFileSets : IDisposable
{
2013-05-30 21:54:43 +02:00
IEnumerable < IFileset > Sets { get ; }
2014-08-19 20:24:54 +02:00
IEnumerable < IFileset > QuickSets { get ; }
2013-05-13 22:32:05 +02:00
IEnumerable < IFileversion > SelectFiles ( Library . Utility . IFilter filter );
2014-03-15 01:03:15 +01:00
IEnumerable < IFileversion > GetLargestPrefix ( Library . Utility . IFilter filter );
2014-03-21 15:01:49 +01:00
IEnumerable < IFileversion > SelectFolderContents ( Library . Utility . IFilter filter );
2013-05-11 12:03:15 +02:00
void TakeFirst ();
}
2019-08-16 21:59:46 -04:00
2013-05-11 12:03:15 +02:00
private class FileSets : IFileSets
{
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDbConnection m_connection ;
2013-05-11 12:03:15 +02:00
private string m_tablename ;
2018-05-23 21:18:01 -07:00
private readonly KeyValuePair < long , DateTime >[] m_filesets ;
2013-05-11 12:03:15 +02:00
public FileSets ( LocalListDatabase owner , DateTime time , long [] versions )
{
m_connection = owner . m_connection ;
m_filesets = owner . FilesetTimes . ToArray ();
m_tablename = "Filesets-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2016-09-15 11:39:27 +02:00
var tmp = owner . GetFilelistWhereClause ( time , versions , m_filesets );
string query = tmp . Item1 ;
var args = tmp . Item2 ;
2013-05-11 12:03:15 +02:00
using ( var cmd = m_connection . CreateCommand ())
2015-11-16 12:57:34 +01:00
{
2019-08-05 20:14:05 -04:00
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" AS SELECT DISTINCT ""ID"" AS ""FilesetID"", ""IsFullBackup"" AS ""IsFullBackup"" , ""Timestamp"" AS ""Timestamp"" FROM ""Fileset"" " + query , m_tablename ), args );
2015-11-16 12:57:34 +01:00
cmd . ExecuteNonQuery ( string . Format ( @"CREATE INDEX ""{0}_FilesetIDTimestampIndex"" ON ""{0}"" (""FilesetID"", ""Timestamp"" DESC)" , m_tablename ));
}
2013-05-11 12:03:15 +02:00
}
2013-05-30 21:54:43 +02:00
private class Fileset : IFileset
{
public long Version { get ; private set ; }
2019-08-05 20:14:05 -04:00
public int IsFullBackup { get ; set ; }
2013-05-30 21:54:43 +02:00
public DateTime Time { get ; private set ; }
public long FileCount { get ; private set ; }
public long FileSizes { get ; private set ; }
2019-08-05 20:14:05 -04:00
public Fileset ( long version , int isFullBackup , DateTime time , long filecount , long filesizes )
2013-05-30 21:54:43 +02:00
{
this . Version = version ;
2019-08-05 20:14:05 -04:00
this . IsFullBackup = isFullBackup ;
2013-05-30 21:54:43 +02:00
this . Time = time ;
this . FileCount = filecount ;
this . FileSizes = filesizes ;
}
}
2013-05-11 12:03:15 +02:00
private class Fileversion : IFileversion
{
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDataReader m_reader ;
2013-05-11 12:03:15 +02:00
public string Path { get ; private set ; }
public bool More { get ; private set ; }
2014-03-15 01:03:15 +01:00
2013-05-11 12:03:15 +02:00
public Fileversion ( System . Data . IDataReader reader )
{
m_reader = reader ;
this . Path = reader . GetValue ( 0 ). ToString ();
this . More = true ;
}
public IEnumerable < long > Sizes
{
get
{
while ( this . More && this . Path == m_reader . GetValue ( 0 ). ToString ())
{
2015-01-24 21:59:53 +01:00
yield return m_reader . ConvertValueToInt64 ( 1 , - 1 );
2013-05-11 12:03:15 +02:00
this . More = m_reader . Read ();
}
}
}
}
2014-03-15 01:03:15 +01:00
private class FileversionFixed : IFileversion
{
public string Path { get ; internal set ; }
public IEnumerable < long > Sizes { get { return new long [ 0 ]; } }
}
2016-04-19 10:04:22 +02:00
2014-03-15 01:03:15 +01:00
public IEnumerable < IFileversion > GetLargestPrefix ( Library . Utility . IFilter filter )
2016-04-19 10:04:22 +02:00
{
return GetLargestPrefix ( filter , null );
}
private IEnumerable < IFileversion > GetLargestPrefix ( Library . Utility . IFilter filter , string prefixrule )
2014-03-15 01:03:15 +01:00
{
using ( var tmpnames = new FilteredFilenameTable ( m_connection , filter , null ))
using ( var cmd = m_connection . CreateCommand ())
{
//First we trim the filelist to exclude filenames not found in any of the filesets
cmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""{0}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{1}"") ) " , tmpnames . Tablename , m_tablename ));
2016-04-19 10:04:22 +02:00
//If we have a prefix rule, apply it
if (! string . IsNullOrWhiteSpace ( prefixrule ))
2019-01-12 01:12:51 +01:00
cmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""{0}"" WHERE SUBSTR(""Path"", 1, {1}) != ?" , tmpnames . Tablename , prefixrule . Length ), prefixrule );
2016-04-19 10:04:22 +02:00
2014-03-15 01:03:15 +01:00
// Then we recursively find the largest prefix
cmd . CommandText = string . Format ( @"SELECT ""Path"" FROM ""{0}"" ORDER BY LENGTH(""Path"") DESC LIMIT 1" , tmpnames . Tablename );
var v0 = cmd . ExecuteScalar ();
string maxpath = "" ;
if ( v0 != null )
maxpath = v0 . ToString ();
2017-01-06 23:01:54 +01:00
2018-10-27 12:17:07 +02:00
var dirsep = Util . GuessDirSeparator ( maxpath );
2014-03-15 01:03:15 +01:00
cmd . CommandText = string . Format ( @"SELECT COUNT(*) FROM ""{0}""" , tmpnames . Tablename );
2015-01-24 21:59:53 +01:00
var filecount = cmd . ExecuteScalarInt64 ( 0 );
2014-03-15 01:03:15 +01:00
long foundfiles = - 1 ;
//TODO: Handle FS case-sensitive?
cmd . CommandText = string . Format ( @"SELECT COUNT(*) FROM ""{0}"" WHERE SUBSTR(""Path"", 1, ?) = ?" , tmpnames . Tablename );
cmd . AddParameter ();
cmd . AddParameter ();
while ( filecount != foundfiles && maxpath . Length > 0 )
{
2019-10-05 11:34:49 -07:00
var mp = Util . AppendDirSeparator ( maxpath , dirsep );
cmd . SetParameterValue ( 0 , mp . Length );
cmd . SetParameterValue ( 1 , mp );
2019-08-03 19:16:52 -04:00
2015-01-24 21:59:53 +01:00
foundfiles = cmd . ExecuteScalarInt64 ( 0 );
2019-08-02 16:42:18 -04:00
2014-03-15 01:03:15 +01:00
if ( filecount != foundfiles )
{
var oldlen = maxpath . Length ;
2017-01-06 23:01:54 +01:00
var lix = maxpath . LastIndexOf ( dirsep , maxpath . Length - 2 , StringComparison . Ordinal );
2019-01-12 01:12:51 +01:00
2017-01-06 23:01:54 +01:00
maxpath = maxpath . Substring ( 0 , lix + 1 );
2017-06-06 17:23:19 +02:00
if ( string . IsNullOrWhiteSpace ( maxpath ) || maxpath . Length == oldlen || maxpath == "\\\\" )
2014-03-15 01:03:15 +01:00
maxpath = "" ;
}
}
2016-04-19 10:04:22 +02:00
2019-01-12 01:12:51 +01:00
// Special handling for Windows and multi-drive/UNC backups as they do not have a single common root
2016-04-19 10:04:22 +02:00
if ( string . IsNullOrWhiteSpace ( maxpath ) && string . IsNullOrWhiteSpace ( prefixrule ))
{
2019-01-12 01:12:51 +01:00
var paths = cmd . ExecuteReaderEnumerable ( string . Format ( @"SELECT Path FROM ""{0}""" , tmpnames . Tablename )). Select ( x => x . ConvertValueToString ( 0 )). ToArray ();
var roots = paths . Select ( x => x . Substring ( 0 , 1 )). Distinct (). Where ( x => x != "\\" ). ToArray ();
//unc path like \\server.domain\
var regexUNCPrefix = new System . Text . RegularExpressions . Regex ( @"^\\\\.*?\\" );
var rootsUNC = paths . Select ( x => regexUNCPrefix . Match ( x )). Where ( x => x . Success ). Select ( x => x . Value ). Distinct (). ToArray ();
return roots . Concat ( rootsUNC ). Select ( x => GetLargestPrefix ( filter , x ). First ()). Distinct (). ToArray ();
2016-04-19 10:04:22 +02:00
}
2019-08-02 16:42:18 -04:00
return
2014-03-15 01:03:15 +01:00
new IFileversion [] {
2018-10-29 12:11:56 +01:00
new FileversionFixed { Path = maxpath == "" ? "" : Util . AppendDirSeparator ( maxpath , dirsep ) }
2014-03-15 01:03:15 +01:00
};
}
2014-03-21 15:01:49 +01:00
}
2014-03-21 17:29:58 +01:00
private IEnumerable < string > SelectFolderEntries ( System . Data . IDbCommand cmd , string prefix , string table )
2014-03-21 15:01:49 +01:00
{
2014-03-21 17:29:58 +01:00
if (! string . IsNullOrEmpty ( prefix ))
2018-10-27 12:17:07 +02:00
prefix = Util . AppendDirSeparator ( prefix , Util . GuessDirSeparator ( prefix ));
2014-03-21 15:01:49 +01:00
2014-03-21 17:29:58 +01:00
var ppl = prefix . Length ;
using ( var rd = cmd . ExecuteReader ( string . Format ( @"SELECT DISTINCT ""Path"" FROM ""{0}"" " , table )))
while ( rd . Read ())
{
var s = rd . GetString ( 0 );
2017-11-26 10:53:14 -08:00
if (! s . StartsWith ( prefix , StringComparison . Ordinal ))
2014-03-21 17:29:58 +01:00
continue ;
2017-01-06 23:01:54 +01:00
2018-10-27 12:17:07 +02:00
var dirsep = Util . GuessDirSeparator ( s );
2017-01-06 23:01:54 +01:00
2014-03-21 17:29:58 +01:00
s = s . Substring ( ppl );
2017-01-06 23:01:54 +01:00
var ix = s . IndexOf ( dirsep , StringComparison . Ordinal );
2014-03-21 17:29:58 +01:00
if ( ix > 0 && ix != s . Length - 1 )
s = s . Substring ( 0 , ix + 1 );
yield return prefix + s ;
}
}
public IEnumerable < IFileversion > SelectFolderContents ( Library . Utility . IFilter filter )
{
var tbname = "Filenames-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
try
2014-03-21 15:01:49 +01:00
{
2014-03-21 19:23:54 +01:00
string pathprefix ;
if ( filter == null || filter . Empty )
pathprefix = "" ;
else if ( filter as Library . Utility . FilterExpression == null || (( Library . Utility . FilterExpression ) filter ). Type != Duplicati . Library . Utility . FilterType . Simple || (( Library . Utility . FilterExpression ) filter ). GetSimpleList (). Length != 1 )
2017-09-25 19:43:26 -07:00
throw new ArgumentException ( "Filter for list-folder-contents must be a path prefix with no wildcards" , nameof ( filter ));
2014-03-21 19:23:54 +01:00
else
pathprefix = (( Library . Utility . FilterExpression ) filter ). GetSimpleList (). First ();
2017-01-06 23:01:54 +01:00
2018-10-27 12:17:07 +02:00
var dirsep = Util . GuessDirSeparator ( pathprefix );
2017-01-06 23:01:54 +01:00
if ( pathprefix . Length > 0 || dirsep == "/" )
2018-10-27 12:17:07 +02:00
pathprefix = Util . AppendDirSeparator ( pathprefix , dirsep );
2014-03-21 15:01:49 +01:00
2017-01-06 23:01:54 +01:00
using ( var tmpnames = new FilteredFilenameTable ( m_connection , new Library . Utility . FilterExpression ( new string [] { pathprefix + "*" }, true ), null ))
2014-03-21 17:29:58 +01:00
using ( var cmd = m_connection . CreateCommand ())
{
//First we trim the filelist to exclude filenames not found in any of the filesets
cmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""{0}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{1}"") ) " , tmpnames . Tablename , m_tablename ));
2014-03-21 15:01:49 +01:00
2014-03-21 17:29:58 +01:00
// If we had instr support this would work:
/*var distinctPaths = @"SELECT DISTINCT :1 || " +
2024-04-15 08:24:01 +02:00
@"CASE(INSTR(SUBSTR(""Path"", :2), '/')) " +
2014-03-21 17:29:58 +01:00
@"WHEN 0 THEN SUBSTR(""Path"", :2) " +
2024-04-15 08:24:01 +02:00
@"ELSE SUBSTR(""Path"", :2, INSTR(SUBSTR(path, :2), '/')) " +
2014-03-21 17:29:58 +01:00
@"END AS ""Path"", ""FilesetID"" " +
@" FROM (" + cartesianPathFileset + @")";*/
2024-04-15 08:24:01 +02:00
2014-03-21 17:29:58 +01:00
// Instead we manually iterate the paths
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL)" , tbname ));
using ( var c2 = m_connection . CreateCommand ())
2014-03-21 15:01:49 +01:00
{
2014-03-21 17:29:58 +01:00
c2 . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""Path"") VALUES (?)" , tbname );
c2 . AddParameter ();
foreach ( var n in SelectFolderEntries ( cmd , pathprefix , tmpnames . Tablename ). Distinct ())
2014-03-21 15:01:49 +01:00
{
2014-03-21 17:29:58 +01:00
c2 . SetParameterValue ( 0 , n );
c2 . ExecuteNonQuery ();
}
2015-11-16 12:57:34 +01:00
c2 . ExecuteNonQuery ( string . Format ( @"CREATE INDEX ""{0}_PathIndex"" ON ""{0}"" (""Path"")" , tbname ));
2014-03-21 15:01:49 +01:00
}
2014-03-21 17:29:58 +01:00
//Then we select the matching results
var filesets = string . Format ( @"SELECT ""FilesetID"", ""Timestamp"" FROM ""{0}"" ORDER BY ""Timestamp"" DESC" , m_tablename );
var cartesianPathFileset = string . Format ( @"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{0}"" A, (" + filesets + @") B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC" , tbname , m_tablename );
2017-11-15 15:16:25 -02:00
var filesWithSizes = string . Format ( @"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND FilesetEntry.""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{0}"") " , m_tablename );
2014-03-21 17:29:58 +01:00
var query = @"SELECT ""C"".""Path"", ""D"".""Length"", ""C"".""FilesetID"" FROM (" + cartesianPathFileset + @") C LEFT OUTER JOIN (" + filesWithSizes + @") D ON ""C"".""FilesetID"" = ""D"".""FilesetID"" AND ""C"".""Path"" = ""D"".""Path""" ;
cmd . AddParameter ( pathprefix , "1" );
cmd . AddParameter ( pathprefix . Length + 1 , "2" );
using ( var rd = cmd . ExecuteReader ( query ))
if ( rd . Read ())
{
bool more ;
do
{
var f = new Fileversion ( rd );
if (!( string . IsNullOrWhiteSpace ( f . Path ) || f . Path == pathprefix ))
{
yield return f ;
more = f . More ;
}
else
{
more = rd . Read ();
}
} while ( more );
}
}
}
finally
{
try
{
using ( var c = m_connection . CreateCommand ())
c . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}""" , tbname ));
}
catch
{
}
2014-03-21 15:01:49 +01:00
}
}
2014-03-15 01:03:15 +01:00
2013-05-13 22:32:05 +02:00
public IEnumerable < IFileversion > SelectFiles ( Library . Utility . IFilter filter )
2013-05-11 12:03:15 +02:00
{
using ( var tmpnames = new FilteredFilenameTable ( m_connection , filter , null ))
using ( var cmd = m_connection . CreateCommand ())
{
//First we trim the filelist to exclude filenames not found in any of the filesets
cmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""{0}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{1}"") ) " , tmpnames . Tablename , m_tablename ));
//Then we select the matching results
var filesets = string . Format ( @"SELECT ""FilesetID"", ""Timestamp"" FROM ""{0}"" ORDER BY ""Timestamp"" DESC" , m_tablename );
var cartesianPathFileset = string . Format ( @"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{0}"" A, (" + filesets + @") B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC" , tmpnames . Tablename , m_tablename );
2017-11-15 15:16:25 -02:00
var filesWithSizes = string . Format ( @"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND FilesetEntry.""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{0}"") " , m_tablename );
2013-05-11 12:03:15 +02:00
var query = @"SELECT ""C"".""Path"", ""D"".""Length"", ""C"".""FilesetID"" FROM (" + cartesianPathFileset + @") C LEFT OUTER JOIN (" + filesWithSizes + @") D ON ""C"".""FilesetID"" = ""D"".""FilesetID"" AND ""C"".""Path"" = ""D"".""Path""" ;
using ( var rd = cmd . ExecuteReader ( query ))
if ( rd . Read ())
{
bool more ;
do
{
var f = new Fileversion ( rd );
yield return f ;
more = f . More ;
} while ( more );
}
}
}
public void TakeFirst ()
{
using ( var cmd = m_connection . CreateCommand ())
cmd . ExecuteNonQuery ( string . Format ( @"DELETE FROM ""{0}"" WHERE ""FilesetID"" NOT IN (SELECT ""FilesetID"" FROM ""{0}"" ORDER BY ""Timestamp"" DESC LIMIT 1 )" , m_tablename ));
}
2014-08-19 20:24:54 +02:00
public IEnumerable < IFileset > QuickSets
{
get
{
var dict = new Dictionary < long , long >();
2019-08-05 20:14:05 -04:00
for ( var i = 0 ; i < m_filesets . Length ; i ++)
{
2014-08-19 20:24:54 +02:00
dict [ m_filesets [ i ]. Key ] = i ;
2019-08-05 20:14:05 -04:00
}
2014-08-19 20:24:54 +02:00
2019-08-05 20:14:05 -04:00
using ( var cmd = m_connection . CreateCommand ())
{
2019-11-17 09:35:42 -08:00
using ( var rd = cmd . ExecuteReader ( @"SELECT DISTINCT ""ID"", ""IsFullBackup"" FROM ""Fileset"" ORDER BY ""Timestamp"" DESC " ))
2014-08-19 20:24:54 +02:00
{
2019-08-05 20:14:05 -04:00
while ( rd . Read ())
{
var id = rd . GetInt64 ( 0 );
2019-08-18 14:41:37 -04:00
var backupType = rd . GetInt32 ( 1 );
2019-08-05 20:14:05 -04:00
var e = dict [ id ];
2019-08-18 14:41:37 -04:00
yield return new Fileset ( e , backupType , m_filesets [ e ]. Value , - 1L , - 1L );
2019-08-05 20:14:05 -04:00
}
2014-08-19 20:24:54 +02:00
}
2019-08-05 20:14:05 -04:00
}
2014-08-19 20:24:54 +02:00
}
}
2013-05-30 21:54:43 +02:00
public IEnumerable < IFileset > Sets
2013-05-11 12:03:15 +02:00
{
get
{
var dict = new Dictionary < long , long >();
2019-08-05 20:14:05 -04:00
for ( var i = 0 ; i < m_filesets . Length ; i ++)
{
2013-05-11 12:03:15 +02:00
dict [ m_filesets [ i ]. Key ] = i ;
2019-08-05 20:14:05 -04:00
}
2013-05-11 12:03:15 +02:00
2019-08-05 20:14:05 -04:00
var summation =
$@"SELECT ""A"".""FilesetID"" AS ""FilesetID"", COUNT(*) AS ""FileCount"", SUM(""C"".""Length"") AS ""FileSizes"" FROM ""FilesetEntry"" A, ""File"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" AND ""A"".""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{m_tablename}"") GROUP BY ""A"".""FilesetID"" " ;
using ( var cmd = m_connection . CreateCommand ())
{
using ( var rd = cmd . ExecuteReader (
$@"SELECT DISTINCT ""A"".""FilesetID"", ""A"".""IsFullBackup"", ""B"".""FileCount"", ""B"".""FileSizes"" FROM ""{m_tablename}"" A LEFT OUTER JOIN ( {summation} ) B ON ""A"".""FilesetID"" = ""B"".""FilesetID"" ORDER BY ""A"".""Timestamp"" DESC " )
)
2013-05-11 12:03:15 +02:00
{
2019-08-05 20:14:05 -04:00
while ( rd . Read ())
{
var id = rd . GetInt64 ( 0 );
var isFullBackup = rd . GetInt32 ( 1 );
var e = dict [ id ];
2019-08-31 18:44:07 -04:00
var filecount = rd . ConvertValueToInt64 ( 2 , - 1L );
var filesizes = rd . ConvertValueToInt64 ( 3 , - 1L );
2019-08-05 20:14:05 -04:00
yield return new Fileset ( e , isFullBackup , m_filesets [ e ]. Value , filecount , filesizes );
}
2013-05-11 12:03:15 +02:00
}
2019-08-05 20:14:05 -04:00
}
2013-05-11 12:03:15 +02:00
}
}
public void Dispose ()
{
if ( m_tablename != null )
{
try
2019-08-21 12:55:24 -04:00
{
using ( var cmd = m_connection . CreateCommand ())
{
2013-08-23 23:36:25 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"DROP TABLE IF EXISTS ""{0}"" " , m_tablename ));
2019-08-21 12:55:24 -04:00
}
2013-05-11 12:03:15 +02:00
}
catch {}
finally { m_tablename = null ; }
}
}
}
public IFileSets SelectFileSets ( DateTime time , long [] versions )
{
return new FileSets ( this , time , versions );
}
}
}