2025-03-13 23:06:32 +01:00
// Copyright (C) 2025, 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
2024-02-28 15:45:30 +01:00
// DEALINGS IN THE SOFTWARE.
2013-05-11 12:03:15 +02:00
using System ;
2025-03-14 14:34:56 +01:00
using System.Data ;
2013-05-11 12:03:15 +02:00
using System.Linq ;
using System.Collections.Generic ;
2019-08-02 16:42:18 -04:00
using Duplicati.Library.Common.IO ;
2025-03-14 14:34:56 +01:00
using Duplicati.Library.Utility ;
2019-08-02 16:42:18 -04:00
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
}
2025-03-14 12:13:17 +01:00
2013-05-11 12:03:15 +02:00
public interface IFileversion
{
string Path { get ; }
IEnumerable < long > Sizes { get ; }
}
2025-03-14 12:13:17 +01:00
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 ; }
}
2025-03-14 12:13:17 +01:00
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 ; }
2025-03-14 14:34:56 +01:00
IEnumerable < IFileversion > SelectFiles ( IFilter filter );
IEnumerable < IFileversion > GetLargestPrefix ( IFilter filter );
IEnumerable < IFileversion > SelectFolderContents ( IFilter filter );
2025-03-14 12:13:17 +01:00
void TakeFirst ();
2013-05-11 12:03:15 +02:00
}
2019-08-16 21:59:46 -04:00
2013-05-11 12:03:15 +02:00
private class FileSets : IFileSets
{
2025-03-14 14:34:56 +01:00
private readonly 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 ;
2025-03-14 12:13:17 +01:00
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 );
2025-03-14 14:34:56 +01:00
var query = tmp . Item1 ;
2016-09-15 11:39:27 +02:00
var args = tmp . Item2 ;
2025-03-14 12:13:17 +01:00
using ( var cmd = m_connection . CreateCommand ())
2015-11-16 12:57:34 +01:00
{
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( $@"CREATE TEMPORARY TABLE ""{m_tablename}"" AS SELECT DISTINCT ""ID"" AS ""FilesetID"", ""IsFullBackup"" AS ""IsFullBackup"" , ""Timestamp"" AS ""Timestamp"" FROM ""Fileset"" {query}" ), args );
cmd . ExecuteNonQuery ( FormatInvariant ( $@"CREATE INDEX ""{m_tablename}_FilesetIDTimestampIndex"" ON ""{m_tablename}"" (""FilesetID"", ""Timestamp"" DESC)" ));
2015-11-16 12:57:34 +01:00
}
2013-05-11 12:03:15 +02:00
}
2025-03-14 12:13:17 +01: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 ; }
2025-03-14 12:13:17 +01:00
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
{
2025-03-14 14:34:56 +01:00
Version = version ;
IsFullBackup = isFullBackup ;
Time = time ;
FileCount = filecount ;
FileSizes = filesizes ;
2013-05-30 21:54:43 +02:00
}
}
2025-03-14 12:13:17 +01:00
2013-05-11 12:03:15 +02:00
private class Fileversion : IFileversion
{
2025-03-14 14:34:56 +01:00
private readonly IDataReader m_reader ;
2013-05-11 12:03:15 +02:00
public string Path { get ; private set ; }
public bool More { get ; private set ; }
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
public Fileversion ( IDataReader reader )
2025-03-14 12:13:17 +01:00
{
m_reader = reader ;
2025-03-14 14:34:56 +01:00
Path = reader . GetValue ( 0 ). ToString ();
More = true ;
2013-05-11 12:03:15 +02:00
}
2025-03-14 12:13:17 +01:00
2013-05-11 12:03:15 +02:00
public IEnumerable < long > Sizes
{
get
{
2025-03-14 14:34:56 +01:00
while ( More && Path == m_reader . GetValue ( 0 ). ToString ())
2013-05-11 12:03:15 +02:00
{
2015-01-24 21:59:53 +01:00
yield return m_reader . ConvertValueToInt64 ( 1 , - 1 );
2025-03-14 14:34:56 +01:00
More = m_reader . Read ();
2013-05-11 12:03:15 +02:00
}
}
}
}
2025-03-14 12:13:17 +01:00
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
2025-03-14 14:34:56 +01:00
public IEnumerable < IFileversion > GetLargestPrefix ( IFilter filter )
2016-04-19 10:04:22 +02:00
{
return GetLargestPrefix ( filter , null );
}
2025-03-14 14:34:56 +01:00
private IEnumerable < IFileversion > GetLargestPrefix ( IFilter filter , string prefixrule )
2014-03-15 01:03:15 +01:00
{
2025-03-14 12:13:17 +01:00
using ( var tmpnames = new FilteredFilenameTable ( m_connection , filter , null ))
using ( var cmd = m_connection . CreateCommand ())
2014-03-15 01:03:15 +01:00
{
//First we trim the filelist to exclude filenames not found in any of the filesets
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( $@"DELETE FROM ""{tmpnames.Tablename}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{m_tablename}"") ) " ));
2016-04-19 10:04:22 +02:00
//If we have a prefix rule, apply it
if (! string . IsNullOrWhiteSpace ( prefixrule ))
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( $@"DELETE FROM ""{tmpnames.Tablename}"" WHERE SUBSTR(""Path"", 1, {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
2025-03-14 12:13:17 +01:00
cmd . CommandText = FormatInvariant ( $@"SELECT ""Path"" FROM ""{tmpnames.Tablename}"" ORDER BY LENGTH(""Path"") DESC LIMIT 1" );
2014-03-15 01:03:15 +01:00
var v0 = cmd . ExecuteScalar ();
2025-03-14 14:34:56 +01:00
var maxpath = "" ;
2014-03-15 01:03:15 +01:00
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 );
2025-03-14 12:13:17 +01:00
cmd . CommandText = FormatInvariant ( $@"SELECT COUNT(*) FROM ""{tmpnames.Tablename}""" );
2015-01-24 21:59:53 +01:00
var filecount = cmd . ExecuteScalarInt64 ( 0 );
2025-03-14 14:34:56 +01:00
var foundfiles = - 1L ;
2025-03-14 12:13:17 +01:00
2014-03-15 01:03:15 +01:00
//TODO: Handle FS case-sensitive?
2025-03-14 12:13:17 +01:00
cmd . CommandText = FormatInvariant ( $@"SELECT COUNT(*) FROM ""{tmpnames.Tablename}"" WHERE SUBSTR(""Path"", 1, ?) = ?" );
2014-03-15 01:03:15 +01:00
cmd . AddParameter ();
cmd . AddParameter ();
2025-03-14 12:13:17 +01:00
2014-03-15 01:03:15 +01:00
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 );
2025-03-14 12:13:17 +01: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 ))
{
2025-03-14 12:13:17 +01:00
var paths = cmd . ExecuteReaderEnumerable ( FormatInvariant ( $@"SELECT Path FROM ""{tmpnames.Tablename}""" )). Select ( x => x . ConvertValueToString ( 0 )). ToArray ();
2019-01-12 01:12:51 +01:00
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
2025-03-14 14:34:56 +01:00
return [ new FileversionFixed { Path = maxpath == "" ? "" : Util . AppendDirSeparator ( maxpath , dirsep ) }];
2014-03-15 01:03:15 +01:00
}
2014-03-21 15:01:49 +01:00
}
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
private IEnumerable < string > SelectFolderEntries ( 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 ));
2025-03-14 12:13:17 +01:00
2014-03-21 17:29:58 +01:00
var ppl = prefix . Length ;
2025-03-14 12:13:17 +01:00
using ( var rd = cmd . ExecuteReader ( FormatInvariant ( $@"SELECT DISTINCT ""Path"" FROM ""{table}"" " )))
2014-03-21 17:29:58 +01:00
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 ;
}
}
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
public IEnumerable < IFileversion > SelectFolderContents ( IFilter filter )
2014-03-21 17:29:58 +01:00
{
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 = "" ;
2025-03-14 14:34:56 +01:00
else if ( filter as FilterExpression == null || (( FilterExpression ) filter ). Type != FilterType . Simple || (( 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
2025-03-14 14:34:56 +01:00
pathprefix = (( 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 );
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
using ( var tmpnames = new FilteredFilenameTable ( m_connection , new FilterExpression ( new string [] { pathprefix + "*" }, true ), null ))
2025-03-14 12:13:17 +01:00
using ( var cmd = m_connection . CreateCommand ())
2014-03-21 17:29:58 +01:00
{
//First we trim the filelist to exclude filenames not found in any of the filesets
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( $@"DELETE FROM ""{tmpnames.Tablename}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{m_tablename}"") ) " ));
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
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( $@"CREATE TEMPORARY TABLE ""{tbname}"" (""Path"" TEXT NOT NULL)" ));
using ( var c2 = m_connection . CreateCommand ())
2014-03-21 15:01:49 +01:00
{
2025-03-14 12:13:17 +01:00
c2 . CommandText = FormatInvariant ( $@"INSERT INTO ""{tbname}"" (""Path"") VALUES (?)" );
2014-03-21 17:29:58 +01:00
c2 . AddParameter ();
2025-03-14 12:13:17 +01:00
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
2025-03-14 12:13:17 +01:00
c2 . ExecuteNonQuery ( FormatInvariant ( $@"CREATE INDEX ""{tbname}_PathIndex"" ON ""{tbname}"" (""Path"")" ));
2014-03-21 15:01:49 +01:00
}
2025-03-14 12:13:17 +01:00
2014-03-21 17:29:58 +01:00
//Then we select the matching results
2025-03-14 12:13:17 +01:00
var filesets = FormatInvariant ( $@"SELECT ""FilesetID"", ""Timestamp"" FROM ""{m_tablename}"" ORDER BY ""Timestamp"" DESC" );
var cartesianPathFileset = FormatInvariant ( $@"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{tbname}"" A, ({filesets}) B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC" );
2017-11-15 15:16:25 -02:00
2025-03-14 12:13:17 +01:00
var filesWithSizes = FormatInvariant ( $@"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 ""{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""" ;
2025-03-14 12:13:17 +01:00
cmd . AddParameter ( pathprefix , "1" );
2014-03-21 17:29:58 +01:00
cmd . AddParameter ( pathprefix . Length + 1 , "2" );
2025-03-14 12:13:17 +01:00
using ( var rd = cmd . ExecuteReader ( query ))
2014-03-21 17:29:58 +01:00
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 ();
}
2025-03-14 12:13:17 +01:00
} while ( more );
2014-03-21 17:29:58 +01:00
}
2025-03-14 12:13:17 +01:00
}
2014-03-21 17:29:58 +01:00
}
finally
{
try
{
2025-03-14 12:13:17 +01:00
using ( var c = m_connection . CreateCommand ())
c . ExecuteNonQuery ( FormatInvariant ( $@"DROP TABLE IF EXISTS ""{tbname}""" ));
2014-03-21 17:29:58 +01:00
}
catch
{
}
2014-03-21 15:01:49 +01:00
}
}
2025-03-14 12:13:17 +01:00
2025-03-14 14:34:56 +01:00
public IEnumerable < IFileversion > SelectFiles ( IFilter filter )
2013-05-11 12:03:15 +02:00
{
2025-03-14 12:13:17 +01:00
using ( var tmpnames = new FilteredFilenameTable ( m_connection , filter , null ))
using ( var cmd = m_connection . CreateCommand ())
2013-05-11 12:03:15 +02:00
{
//First we trim the filelist to exclude filenames not found in any of the filesets
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( $@"DELETE FROM ""{tmpnames.Tablename}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{m_tablename}"") ) " ));
2013-05-11 12:03:15 +02:00
//Then we select the matching results
2025-03-14 12:13:17 +01:00
var filesets = FormatInvariant ( $@"SELECT ""FilesetID"", ""Timestamp"" FROM ""{m_tablename}"" ORDER BY ""Timestamp"" DESC" );
var cartesianPathFileset = FormatInvariant ( $@"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{tmpnames.Tablename}"" A, ({filesets}) B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC" );
var filesWithSizes = FormatInvariant ( $@"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 ""{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""" ;
2025-03-14 12:13:17 +01:00
using ( var rd = cmd . ExecuteReader ( query ))
if ( rd . Read ())
2013-05-11 12:03:15 +02:00
{
bool more ;
do
{
var f = new Fileversion ( rd );
yield return f ;
more = f . More ;
2025-03-14 12:13:17 +01:00
} while ( more );
2013-05-11 12:03:15 +02:00
}
}
}
2025-03-14 12:13:17 +01:00
2013-05-11 12:03:15 +02:00
public void TakeFirst ()
{
2025-03-14 12:13:17 +01:00
using ( var cmd = m_connection . CreateCommand ())
cmd . ExecuteNonQuery ( FormatInvariant ( $@"DELETE FROM ""{m_tablename}"" WHERE ""FilesetID"" NOT IN (SELECT ""FilesetID"" FROM ""{m_tablename}"" ORDER BY ""Timestamp"" DESC LIMIT 1 )" ));
2013-05-11 12:03:15 +02:00
}
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
using ( var cmd = m_connection . CreateCommand ())
2025-03-14 14:34:56 +01:00
using ( var rd = cmd . ExecuteReader ( @"SELECT DISTINCT ""ID"", ""IsFullBackup"" FROM ""Fileset"" ORDER BY ""Timestamp"" DESC " ))
while ( rd . Read ())
2014-08-19 20:24:54 +02:00
{
2025-03-14 14:34:56 +01:00
var id = rd . GetInt64 ( 0 );
var backupType = rd . GetInt32 ( 1 );
var e = dict [ id ];
yield return new Fileset ( e , backupType , m_filesets [ e ]. Value , - 1L , - 1L );
2014-08-19 20:24:54 +02:00
}
}
}
2025-03-14 12:13:17 +01: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 ;
2025-03-14 12:13:17 +01:00
2025-03-13 23:06:32 +01:00
var summation = FormatInvariant (
$@"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"" " );
2019-08-05 20:14:05 -04:00
using ( var cmd = m_connection . CreateCommand ())
2025-03-14 14:34:56 +01:00
using ( var rd = cmd . ExecuteReader ( FormatInvariant (
$@"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 " ))
)
while ( rd . Read ())
2013-05-11 12:03:15 +02:00
{
2025-03-14 14:34:56 +01:00
var id = rd . GetInt64 ( 0 );
var isFullBackup = rd . GetInt32 ( 1 );
var e = dict [ id ];
var filecount = rd . ConvertValueToInt64 ( 2 , - 1L );
var filesizes = rd . ConvertValueToInt64 ( 3 , - 1L );
2019-08-05 20:14:05 -04:00
2025-03-14 14:34:56 +01:00
yield return new Fileset ( e , isFullBackup , m_filesets [ e ]. Value , filecount , filesizes );
2013-05-11 12:03:15 +02:00
}
}
}
2025-03-14 12:13:17 +01:00
2013-05-11 12:03:15 +02:00
public void Dispose ()
{
if ( m_tablename != null )
{
2025-03-14 12:13:17 +01:00
try
2019-08-21 12:55:24 -04:00
{
using ( var cmd = m_connection . CreateCommand ())
2025-03-14 12:13:17 +01:00
cmd . ExecuteNonQuery ( FormatInvariant ( @ $"DROP TABLE IF EXISTS ""{m_tablename}"" " ));
2013-05-11 12:03:15 +02:00
}
2025-03-14 12:13:17 +01:00
catch { }
2013-05-11 12:03:15 +02:00
finally { m_tablename = null ; }
}
2025-03-14 12:13:17 +01:00
2013-05-11 12:03:15 +02:00
}
}
2025-03-14 12:13:17 +01:00
2013-05-11 12:03:15 +02:00
public IFileSets SelectFileSets ( DateTime time , long [] versions )
{
return new FileSets ( this , time , versions );
}
2025-03-14 12:13:17 +01:00
2013-05-11 12:03:15 +02:00
}
}