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-07-22 16:54:19 +02:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Text ;
namespace Duplicati.Library.Main.Database
{
2018-10-13 16:53:28 -07:00
internal class LocalRecreateDatabase : LocalRestoreDatabase
2013-07-22 16:54:19 +02:00
{
2018-03-12 14:07:11 +01:00
/// <summary>
/// The tag used for logging
/// </summary>
private static readonly string LOGTAG = Logging . Log . LogTagFromType ( typeof ( LocalRecreateDatabase ));
2013-09-07 21:33:59 +02:00
private class PathEntryKeeper
{
private struct KeyValueComparer : IComparer < KeyValuePair < long , long >>
{
public int Compare ( KeyValuePair < long , long > x , KeyValuePair < long , long > y )
{
return x . Key == y . Key ?
( x . Value == y . Value ?
0
: ( x . Value < y . Value ? - 1 : 1 ))
: ( x . Key < y . Key ? - 1 : 1 );
}
}
}
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDbCommand m_insertFileCommand ;
private readonly System . Data . IDbCommand m_insertFilesetEntryCommand ;
private readonly System . Data . IDbCommand m_insertMetadatasetCommand ;
private readonly System . Data . IDbCommand m_insertBlocksetCommand ;
private readonly System . Data . IDbCommand m_insertBlocklistHashCommand ;
private readonly System . Data . IDbCommand m_updateBlockVolumeCommand ;
2023-11-05 18:19:08 +01:00
private readonly System . Data . IDbCommand m_insertTempBlockListHash ;
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDbCommand m_insertSmallBlockset ;
private readonly System . Data . IDbCommand m_findBlocksetCommand ;
private readonly System . Data . IDbCommand m_findMetadatasetCommand ;
private readonly System . Data . IDbCommand m_findFilesetCommand ;
2023-11-05 18:19:08 +01:00
private readonly System . Data . IDbCommand m_findTempBlockListHashCommand ;
2018-05-23 21:18:01 -07:00
private readonly System . Data . IDbCommand m_findHashBlockCommand ;
private readonly System . Data . IDbCommand m_insertBlockCommand ;
private readonly System . Data . IDbCommand m_insertDuplicateBlockCommand ;
2023-11-05 18:19:08 +01:00
2013-07-22 16:54:19 +02:00
private string m_tempblocklist ;
2016-04-03 23:10:47 +02:00
private string m_tempsmalllist ;
2023-11-05 18:19:08 +01:00
2013-07-22 16:54:19 +02:00
/// <summary>
/// A lookup table that prevents multiple downloads of the same volume
/// </summary>
private Dictionary < long , long > m_proccessedVolumes ;
// SQL that finds index and block size for all blocklist hashes, based on the temporary hash list
2016-03-26 13:14:05 +01:00
// with vars Used:
// {0} --> Blocksize
// {1} --> BlockHash-Size
// {2} --> Temp-Table
// {3} --> FullBlocklist-BlockCount [equals ({0} / {1}), if SQLite pays respect to ints]
private const string SELECT_BLOCKLIST_ENTRIES =
2016-03-15 21:02:13 +01:00
@"
2017-01-19 10:53:32 +01:00
SELECT
2016-03-17 21:17:22 +01:00
""E"".""BlocksetID"",
2016-03-26 13:14:05 +01:00
""F"".""Index"" + (""E"".""BlocklistIndex"" * {3}) AS ""FullIndex"",
2016-03-17 21:17:22 +01:00
""F"".""BlockHash"",
2016-03-27 12:50:40 +02:00
MIN({0}, ""E"".""Length"" - ((""F"".""Index"" + (""E"".""BlocklistIndex"" * {3})) * {0})) AS ""BlockSize"",
2016-03-17 21:17:22 +01:00
""E"".""Hash"",
""E"".""BlocklistSize"",
""E"".""BlocklistHash""
FROM
(
SELECT * FROM
(
SELECT
""A"".""BlocksetID"",
""A"".""Index"" AS ""BlocklistIndex"",
2016-03-26 13:14:05 +01:00
MIN({3} * {1}, (((""B"".""Length"" + {0} - 1) / {0}) - (""A"".""Index"" * ({3}))) * {1}) AS ""BlocklistSize"",
2016-03-17 21:17:22 +01:00
""A"".""Hash"" AS ""BlocklistHash"",
""B"".""Length""
FROM
""BlocklistHash"" A,
""Blockset"" B
WHERE
""B"".""ID"" = ""A"".""BlocksetID""
) C,
""Block"" D
WHERE
""C"".""BlocklistHash"" = ""D"".""Hash""
AND
""C"".""BlocklistSize"" = ""D"".""Size""
) E,
""{2}"" F
WHERE
""F"".""BlocklistHash"" = ""E"".""Hash""
ORDER BY
""E"".""BlocksetID"",
""FullIndex""
2016-03-15 21:02:13 +01:00
" ;
2013-07-22 16:54:19 +02:00
public LocalRecreateDatabase ( LocalDatabase parentdb , Options options )
2015-04-08 20:33:30 +02:00
: base ( parentdb )
2013-07-22 16:54:19 +02:00
{
2023-11-05 18:19:08 +01:00
m_tempblocklist = "TempBlocklist_" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
m_tempsmalllist = "TempSmalllist_" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2016-02-11 13:29:12 +01:00
using ( var cmd = m_connection . CreateCommand ())
{
2013-07-22 16:54:19 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" (""BlockListHash"" TEXT NOT NULL, ""BlockHash"" TEXT NOT NULL, ""Index"" INTEGER NOT NULL)" , m_tempblocklist ));
2016-02-13 22:13:40 +01:00
cmd . ExecuteNonQuery ( string . Format ( @"CREATE INDEX ""Index_{0}"" ON ""{0}"" (""BlockListHash"");" , m_tempblocklist ));
2016-04-03 23:10:47 +02:00
cmd . ExecuteNonQuery ( string . Format ( @"CREATE TEMPORARY TABLE ""{0}"" (""FileHash"" TEXT NOT NULL, ""BlockHash"" TEXT NOT NULL, ""BlockSize"" INTEGER NOT NULL)" , m_tempsmalllist ));
cmd . ExecuteNonQuery ( string . Format ( @"CREATE UNIQUE INDEX ""Index_File_{0}"" ON ""{0}"" (""FileHash"", ""BlockSize"");" , m_tempsmalllist ));
cmd . ExecuteNonQuery ( string . Format ( @"CREATE UNIQUE INDEX ""Index_Block_{0}"" ON ""{0}"" (""BlockHash"", ""BlockSize"");" , m_tempsmalllist ));
2016-02-11 13:29:12 +01:00
}
2013-07-22 16:54:19 +02:00
m_insertFileCommand = m_connection . CreateCommand ();
m_insertFilesetEntryCommand = m_connection . CreateCommand ();
m_insertMetadatasetCommand = m_connection . CreateCommand ();
m_insertBlocksetCommand = m_connection . CreateCommand ();
m_insertBlocklistHashCommand = m_connection . CreateCommand ();
m_updateBlockVolumeCommand = m_connection . CreateCommand ();
2023-11-05 18:19:08 +01:00
m_insertTempBlockListHash = m_connection . CreateCommand ();
2016-04-03 23:10:47 +02:00
m_insertSmallBlockset = m_connection . CreateCommand ();
2013-07-22 16:54:19 +02:00
m_findBlocksetCommand = m_connection . CreateCommand ();
m_findMetadatasetCommand = m_connection . CreateCommand ();
m_findFilesetCommand = m_connection . CreateCommand ();
2023-11-05 18:19:08 +01:00
m_findTempBlockListHashCommand = m_connection . CreateCommand ();
2013-07-22 16:54:19 +02:00
m_findHashBlockCommand = m_connection . CreateCommand ();
m_insertBlockCommand = m_connection . CreateCommand ();
m_insertDuplicateBlockCommand = m_connection . CreateCommand ();
2018-06-14 10:12:24 +02:00
m_insertFileCommand . CommandText = @"INSERT INTO ""FileLookup"" (""PrefixID"", ""Path"", ""BlocksetID"", ""MetadataID"") VALUES (?,?,?,?); SELECT last_insert_rowid();" ;
m_insertFileCommand . AddParameters ( 4 );
2013-07-22 16:54:19 +02:00
2015-01-24 21:59:53 +01:00
m_insertFilesetEntryCommand . CommandText = @"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") VALUES (?,?,?)" ;
2013-07-22 16:54:19 +02:00
m_insertFilesetEntryCommand . AddParameters ( 3 );
m_insertMetadatasetCommand . CommandText = @"INSERT INTO ""Metadataset"" (""BlocksetID"") VALUES (?); SELECT last_insert_rowid();" ;
m_insertMetadatasetCommand . AddParameters ( 1 );
m_insertBlocksetCommand . CommandText = @"INSERT INTO ""Blockset"" (""Length"", ""FullHash"") VALUES (?,?); SELECT last_insert_rowid();" ;
m_insertBlocksetCommand . AddParameters ( 2 );
m_insertBlocklistHashCommand . CommandText = @"INSERT INTO ""BlocklistHash"" (""BlocksetID"", ""Index"", ""Hash"") VALUES (?,?,?)" ;
m_insertBlocklistHashCommand . AddParameters ( 3 );
m_updateBlockVolumeCommand . CommandText = @"UPDATE ""Block"" SET ""VolumeID"" = ? WHERE ""Hash"" = ? AND ""Size"" = ?" ;
m_updateBlockVolumeCommand . AddParameters ( 3 );
2023-11-05 18:19:08 +01:00
m_insertTempBlockListHash . CommandText = string . Format ( @"INSERT INTO ""{0}"" (""BlocklistHash"", ""BlockHash"", ""Index"") VALUES (?,?,?) " , m_tempblocklist );
m_insertTempBlockListHash . AddParameters ( 3 );
2016-04-03 23:10:47 +02:00
m_insertSmallBlockset . CommandText = string . Format ( @"INSERT OR IGNORE INTO ""{0}"" (""FileHash"", ""BlockHash"", ""BlockSize"") VALUES (?,?,?) " , m_tempsmalllist );
m_insertSmallBlockset . AddParameters ( 3 );
2014-11-10 10:31:26 +01:00
m_findBlocksetCommand . CommandText = @"SELECT ""ID"" FROM ""Blockset"" WHERE ""Length"" = ? AND ""FullHash"" = ? " ;
2013-07-22 16:54:19 +02:00
m_findBlocksetCommand . AddParameters ( 2 );
2015-07-12 21:36:48 +02:00
m_findMetadatasetCommand . CommandText = @"SELECT ""Metadataset"".""ID"" FROM ""Metadataset"",""Blockset"" WHERE ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" AND ""Blockset"".""FullHash"" = ? AND ""Blockset"".""Length"" = ? " ;
2013-07-22 16:54:19 +02:00
m_findMetadatasetCommand . AddParameters ( 2 );
2018-06-14 10:12:24 +02:00
m_findFilesetCommand . CommandText = @"SELECT ""ID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ? AND ""BlocksetID"" = ? AND ""MetadataID"" = ? " ;
m_findFilesetCommand . AddParameters ( 4 );
2013-07-22 16:54:19 +02:00
2023-11-05 18:19:08 +01:00
m_findTempBlockListHashCommand . CommandText = string . Format ( @"SELECT DISTINCT ""BlockListHash"" FROM ""{0}"" WHERE ""BlockListHash"" = ? " , m_tempblocklist );
m_findTempBlockListHashCommand . AddParameters ( 1 );
2013-07-22 16:54:19 +02:00
m_findHashBlockCommand . CommandText = @"SELECT ""VolumeID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ? " ;
m_findHashBlockCommand . AddParameters ( 2 );
m_insertBlockCommand . CommandText = @"INSERT INTO ""Block"" (""Hash"", ""Size"", ""VolumeID"") VALUES (?,?,?)" ;
m_insertBlockCommand . AddParameters ( 3 );
2014-11-11 22:29:38 +01:00
m_insertDuplicateBlockCommand . CommandText = @"INSERT INTO ""DuplicateBlock"" (""BlockID"", ""VolumeID"") VALUES ((SELECT ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?), ?)" ;
2013-07-22 16:54:19 +02:00
m_insertDuplicateBlockCommand . AddParameters ( 3 );
}
2015-04-08 20:33:30 +02:00
public void FindMissingBlocklistHashes ( long hashsize , long blocksize , System . Data . IDbTransaction transaction )
2013-07-22 16:54:19 +02:00
{
using ( var cmd = m_connection . CreateCommand ())
{
cmd . Transaction = transaction ;
//Update all small blocklists and matching blocks
2016-04-03 23:10:47 +02:00
var selectSmallBlocks = string . Format ( @"SELECT ""BlockHash"", ""BlockSize"" FROM ""{0}""" , m_tempsmalllist );
2013-07-22 16:54:19 +02:00
var selectBlockHashes = string . Format (
@"SELECT ""BlockHash"" AS ""FullHash"", ""BlockSize"" AS ""Length"" FROM ( " +
SELECT_BLOCKLIST_ENTRIES +
@" )" ,
2015-04-08 20:33:30 +02:00
blocksize ,
2013-07-22 16:54:19 +02:00
hashsize ,
2016-03-26 13:14:05 +01:00
m_tempblocklist ,
blocksize / hashsize
2013-07-22 16:54:19 +02:00
);
var selectAllBlocks = @"SELECT DISTINCT ""FullHash"", ""Length"" FROM (" + selectBlockHashes + " UNION " + selectSmallBlocks + " )" ;
var selectNewBlocks = string . Format (
@"SELECT ""FullHash"" AS ""Hash"", ""Length"" AS ""Size"", -1 AS ""VolumeID"" " +
@" FROM (SELECT ""A"".""FullHash"", ""A"".""Length"", CASE WHEN ""B"".""Hash"" IS NULL THEN '' ELSE ""B"".""Hash"" END AS ""Hash"", CASE WHEN ""B"".""Size"" is NULL THEN -1 ELSE ""B"".""Size"" END AS ""Size"" FROM ({0}) A" +
2013-07-24 21:25:02 +02:00
@" LEFT OUTER JOIN ""Block"" B ON ""B"".""Hash"" = ""A"".""FullHash"" AND ""B"".""Size"" = ""A"".""Length"" )" +
2013-07-22 16:54:19 +02:00
@" WHERE ""FullHash"" != ""Hash"" AND ""Length"" != ""Size"" " ,
selectAllBlocks
);
var insertBlocksCommand =
@"INSERT INTO ""Block"" (""Hash"", ""Size"", ""VolumeID"") " +
selectNewBlocks ;
// Insert all known blocks into block table with volumeid = -1
cmd . ExecuteNonQuery ( insertBlocksCommand );
var selectBlocklistBlocksetEntries = string . Format (
@"SELECT ""E"".""BlocksetID"" AS ""BlocksetID"", ""D"".""FullIndex"" AS ""Index"", ""F"".""ID"" AS ""BlockID"" FROM ( " +
SELECT_BLOCKLIST_ENTRIES +
2017-01-19 09:55:36 +01:00
@") D, ""BlocklistHash"" E, ""Block"" F, ""Block"" G WHERE ""D"".""BlocksetID"" = ""E"".""BlocksetID"" AND ""D"".""BlocklistHash"" = ""E"".""Hash"" AND ""D"".""BlocklistSize"" = ""G"".""Size"" AND ""D"".""BlocklistHash"" = ""G"".""Hash"" AND ""D"".""Blockhash"" = ""F"".""Hash"" AND ""D"".""BlockSize"" = ""F"".""Size"" " ,
2017-01-19 10:53:32 +01:00
// TODO: The BlocklistHash join seems to be unnecessary, but the join might be required to work around a from really old versions of Duplicati
// this could be used instead
//@") D, ""Block"" WHERE ""BlockQuery"".""BlockHash"" = ""Block"".""Hash"" AND ""BlockQuery"".""BlockSize"" = ""Block"".""Size"" ";
2015-04-08 20:33:30 +02:00
blocksize ,
2013-07-22 16:54:19 +02:00
hashsize ,
2016-03-26 13:14:05 +01:00
m_tempblocklist ,
blocksize / hashsize
2013-07-22 16:54:19 +02:00
);
2016-04-03 23:10:47 +02:00
2013-07-22 16:54:19 +02:00
var selectBlocksetEntries = string . Format (
2016-04-03 23:10:47 +02:00
@"SELECT ""Blockset"".""ID"" AS ""BlocksetID"", 0 AS ""Index"", ""Block"".""ID"" AS ""BlockID"" FROM ""Blockset"", ""Block"", ""{1}"" S WHERE ""Blockset"".""Fullhash"" = ""S"".""FileHash"" AND ""S"".""BlockHash"" = ""Block"".""Hash"" AND ""S"".""BlockSize"" = ""Block"".""Size"" AND ""Blockset"".""Length"" = ""S"".""BlockSize"" AND ""Blockset"".""Length"" <= {0} " ,
blocksize ,
m_tempsmalllist
2013-07-22 16:54:19 +02:00
);
var selectAllBlocksetEntries =
selectBlocklistBlocksetEntries +
@" UNION " +
selectBlocksetEntries ;
var selectFiltered =
2017-01-15 23:08:21 +01:00
@"SELECT DISTINCT ""H"".""BlocksetID"", ""H"".""Index"", ""H"".""BlockID"" FROM (" +
2013-07-22 16:54:19 +02:00
selectAllBlocksetEntries +
2017-01-15 23:08:21 +01:00
@") H WHERE (""H"".""BlocksetID"" || ':' || ""H"".""Index"") NOT IN (SELECT (""ExistingBlocksetEntries"".""BlocksetID"" || ':' || ""ExistingBlocksetEntries"".""Index"") FROM ""BlocksetEntry"" ""ExistingBlocksetEntries"" )" ;
2013-07-22 16:54:19 +02:00
var insertBlocksetEntriesCommand =
2017-01-05 21:41:21 +01:00
@"INSERT INTO ""BlocksetEntry"" (""BlocksetID"", ""Index"", ""BlockID"") " + selectFiltered ;
try
{
cmd . ExecuteNonQuery ( insertBlocksetEntriesCommand );
}
catch ( Exception ex )
{
2019-09-09 18:28:25 -04:00
Logging . Log . WriteErrorMessage ( LOGTAG , "BlocksetInsertFailed" , ex , "Blockset insert failed, committing temporary tables for trace purposes" );
2017-01-05 21:41:21 +01:00
2017-01-15 23:08:21 +01:00
using ( var fixcmd = m_connection . CreateCommand ())
2017-01-05 21:41:21 +01:00
{
2017-01-15 23:08:21 +01:00
fixcmd . ExecuteNonQuery ( string . Format ( @"CREATE TABLE ""{0}-Failure"" AS SELECT * FROM ""{0}"" " , m_tempblocklist ));
fixcmd . ExecuteNonQuery ( string . Format ( @"CREATE TABLE ""{0}-Failure"" AS SELECT * FROM ""{0}"" " , m_tempsmalllist ));
2017-01-05 21:41:21 +01:00
}
2017-01-19 09:55:36 +01:00
throw new Exception ( "The recreate failed, please create a bug-report from this database and send it to the developers for further analysis" );
2017-01-05 21:41:21 +01:00
}
2013-07-22 16:54:19 +02:00
}
}
2023-11-05 18:19:08 +01:00
/// <summary>
/// From the temporary tables 1) insert new blocks into Block (VolumeID to be set at a later stage)
/// and 2) add missing BlocksetEntry lines
///
/// hashsize and blocksize: global database parameters
/// hashOnly: do not take in account small blocks - these have been added at the
/// end of the index handling and are not changed in the dblock handling so we can ignore them
/// </summary>
/// Notes:
///
/// temp block list structure: blocklist hash, block hash, index relative to the
/// beginning of the blocklist hash (NOT the file)
///
/// temp small list structure: filehash, blockhash, blocksize: as the small files are defined
/// by the fact that they are contained in a single block, blockhash is the same as the filehash,
/// and blocksize can vary from 0 to the configured block size for the backup
public void AddBlockAndBlockSetEntryFromTemp ( long hashsize , long blocksize , System . Data . IDbTransaction transaction , bool hashOnly = false )
{
using ( var cmd = m_connection . CreateCommand ())
{
cmd . Transaction = transaction ;
var insertBlocksCommand = string . Format (
@"INSERT INTO BLOCK (Hash, Size, VolumeID) " +
@"SELECT DISTINCT BlockHash AS Hash, BlockSize AS Size, -1 AS VolumeID FROM " +
@"(" +
@"SELECT NB.BlockHash, " +
@"MIN({0}, BS.Length - ((NB.""Index"" + (BH.""Index"" * {1})) * {0})) AS BlockSize " +
@"FROM (" +
@" SELECT TBL.BlockListHash, TBL.BlockHash, TBL.""Index"" FROM {2} TBL " +
@" LEFT OUTER JOIN Block B ON (B.Hash = TBL.BlockHash) " +
@" WHERE B.Hash IS NULL " +
@" ) NB " +
@"JOIN BlocklistHash BH ON (BH.Hash = NB.BlocklistHash) " +
@"JOIN Blockset BS ON (BS.ID = BH.Blocksetid) " +
@"" ,
blocksize ,
blocksize / hashsize ,
m_tempblocklist
);
if (! hashOnly ) {
insertBlocksCommand += string . Format (
@" UNION " +
@"" +
@"SELECT TS.BlockHash, TS.BlockSize FROM " +
@"{0} TS " +
@"WHERE NOT EXISTS (SELECT ""X"" FROM Block AS B WHERE " +
@" B.Hash = TS.BlockHash AND " +
@" B.Size = TS.BlockSize) " +
@")" ,
m_tempsmalllist
);
}
var insertBlocksetEntriesCommand = string . Format (
@"INSERT INTO BlocksetEntry (BlocksetID, ""Index"", BlockID) " +
@"SELECT DISTINCT BH.blocksetid, (BH.""Index"" * {0})+TBL.""Index"" as FullIndex, BK.ID AS BlockID " +
@"FROM {1} TBL " +
@" JOIN blocklisthash BH ON (BH.hash = TBL.blocklisthash) " +
@" JOIN block BK ON (BK.Hash = TBL.BlockHash) " +
@" LEFT OUTER JOIN BlocksetEntry BE ON (BE.BlockSetID = BH.BlocksetID AND BE.""Index"" = (BH.""Index"" * {0})+TBL.""Index"") " +
@"WHERE " +
@"BE.BlockSetID IS NULL " ,
blocksize / hashsize ,
m_tempblocklist
);
if (! hashOnly ) {
insertBlocksetEntriesCommand += string . Format (
@" UNION " +
@"SELECT BS.ID AS BlocksetID, 0 AS ""Index"", BL.ID AS BlockID " +
@"FROM {1} TS " +
@" JOIN Blockset BS ON (BS.FullHash = TS.FileHash AND " +
@" BS.Length = TS.BlockSize AND " +
@" BS.Length <= {0}) " +
@" JOIN Block BL ON (BL.Hash = TS.BlockHash AND " +
@" BL.Size = TS.BlockSize) " +
@" LEFT OUTER JOIN BlocksetEntry BE ON (BE.BlocksetID = BS.ID AND BE.""Index"" = 0) " +
@"WHERE " +
@"BE.BlocksetID IS NULL " ,
blocksize ,
m_tempsmalllist
);
}
try
{
// Insert discovered new blocks into block table with volumeid = -1
cmd . ExecuteNonQuery ( insertBlocksCommand );
// Insert corresponding entries into blockset
cmd . ExecuteNonQuery ( insertBlocksetEntriesCommand );
}
catch ( Exception ex )
{
Logging . Log . WriteErrorMessage ( LOGTAG , "BlockOrBlocksetInsertFailed" , ex , "Block or Blockset insert failed, committing temporary tables for trace purposes" );
using ( var fixcmd = m_connection . CreateCommand ())
{
fixcmd . ExecuteNonQuery ( string . Format ( @"CREATE TABLE ""{0}_Failure"" AS SELECT * FROM ""{0}"" " , m_tempblocklist ));
fixcmd . ExecuteNonQuery ( string . Format ( @"CREATE TABLE ""{0}_Failure"" AS SELECT * FROM ""{0}"" " , m_tempsmalllist ));
}
throw new Exception ( "The recreate failed, please create a bug-report from this database and send it to the developers for further analysis" );
}
}
}
2018-06-14 10:12:24 +02:00
public void AddDirectoryEntry ( long filesetid , long pathprefixid , string path , DateTime time , long metadataid , System . Data . IDbTransaction transaction )
2013-07-22 16:54:19 +02:00
{
2018-10-23 11:33:35 +02:00
AddEntry ( filesetid , pathprefixid , path , time , FOLDER_BLOCKSET_ID , metadataid , transaction );
2013-07-22 16:54:19 +02:00
}
2018-06-14 10:12:24 +02:00
public void AddSymlinkEntry ( long filesetid , long pathprefixid , string path , DateTime time , long metadataid , System . Data . IDbTransaction transaction )
2013-07-22 16:54:19 +02:00
{
2018-10-23 11:33:35 +02:00
AddEntry ( filesetid , pathprefixid , path , time , SYMLINK_BLOCKSET_ID , metadataid , transaction );
2013-07-22 16:54:19 +02:00
}
2018-06-14 10:12:24 +02:00
public void AddFileEntry ( long filesetid , long pathprefixid , string path , DateTime time , long blocksetid , long metadataid , System . Data . IDbTransaction transaction )
2013-07-22 16:54:19 +02:00
{
2018-10-23 11:33:35 +02:00
AddEntry ( filesetid , pathprefixid , path , time , blocksetid , metadataid , transaction );
2013-07-22 16:54:19 +02:00
}
2018-10-23 11:33:35 +02:00
private void AddEntry ( long filesetid , long pathprefixid , string path , DateTime time , long blocksetid , long metadataid , System . Data . IDbTransaction transaction )
2013-07-22 16:54:19 +02:00
{
var fileid = - 1L ;
2013-09-07 21:33:59 +02:00
2018-06-14 10:12:24 +02:00
m_findFilesetCommand . Transaction = transaction ;
m_findFilesetCommand . SetParameterValue ( 0 , pathprefixid );
m_findFilesetCommand . SetParameterValue ( 1 , path );
m_findFilesetCommand . SetParameterValue ( 2 , blocksetid );
m_findFilesetCommand . SetParameterValue ( 3 , metadataid );
fileid = m_findFilesetCommand . ExecuteScalarInt64 (- 1 );
2013-07-22 16:54:19 +02:00
if ( fileid < 0 )
{
m_insertFileCommand . Transaction = transaction ;
2018-06-14 10:12:24 +02:00
m_insertFileCommand . SetParameterValue ( 0 , pathprefixid );
m_insertFileCommand . SetParameterValue ( 1 , path );
m_insertFileCommand . SetParameterValue ( 2 , blocksetid );
m_insertFileCommand . SetParameterValue ( 3 , metadataid );
2015-01-24 21:59:53 +01:00
fileid = m_insertFileCommand . ExecuteScalarInt64 (- 1 );
2013-07-22 16:54:19 +02:00
}
m_insertFilesetEntryCommand . Transaction = transaction ;
m_insertFilesetEntryCommand . SetParameterValue ( 0 , filesetid );
m_insertFilesetEntryCommand . SetParameterValue ( 1 , fileid );
2015-01-24 21:59:53 +01:00
m_insertFilesetEntryCommand . SetParameterValue ( 2 , time . ToUniversalTime (). Ticks );
2013-07-22 16:54:19 +02:00
m_insertFilesetEntryCommand . ExecuteNonQuery ();
}
2016-04-04 18:11:48 +02:00
public long AddMetadataset ( string metahash , long metahashsize , IEnumerable < string > metablocklisthashes , long expectedmetablocklisthashes , System . Data . IDbTransaction transaction )
2013-07-22 16:54:19 +02:00
{
var metadataid = - 1L ;
if ( metahash == null )
return metadataid ;
2016-12-16 17:30:39 +01:00
m_findMetadatasetCommand . Transaction = transaction ;
m_findMetadatasetCommand . SetParameterValue ( 0 , metahash );
m_findMetadatasetCommand . SetParameterValue ( 1 , metahashsize );
metadataid = m_findMetadatasetCommand . ExecuteScalarInt64 (- 1 );
if ( metadataid != - 1 )
return metadataid ;
2016-04-03 23:10:47 +02:00
2016-04-04 18:11:48 +02:00
var blocksetid = AddBlockset ( metahash , metahashsize , metablocklisthashes , expectedmetablocklisthashes , transaction );
2013-07-22 16:54:19 +02:00
m_insertMetadatasetCommand . Transaction = transaction ;
m_insertMetadatasetCommand . SetParameterValue ( 0 , blocksetid );
2015-01-24 21:59:53 +01:00
metadataid = m_insertMetadatasetCommand . ExecuteScalarInt64 (- 1 );
2016-12-16 17:30:39 +01:00
2013-07-22 16:54:19 +02:00
return metadataid ;
}
2016-03-15 21:02:13 +01:00
public long AddBlockset ( string fullhash , long size , IEnumerable < string > blocklisthashes , long expectedblocklisthashes , System . Data . IDbTransaction transaction )
2013-07-22 16:54:19 +02:00
{
2016-12-16 17:30:39 +01:00
m_findBlocksetCommand . Transaction = transaction ;
m_findBlocksetCommand . SetParameterValue ( 0 , size );
m_findBlocksetCommand . SetParameterValue ( 1 , fullhash );
var blocksetid = m_findBlocksetCommand . ExecuteScalarInt64 (- 1 );
if ( blocksetid != - 1 )
return blocksetid ;
2013-07-22 16:54:19 +02:00
m_insertBlocksetCommand . Transaction = transaction ;
m_insertBlocksetCommand . SetParameterValue ( 0 , size );
m_insertBlocksetCommand . SetParameterValue ( 1 , fullhash );
2015-01-24 21:59:53 +01:00
blocksetid = m_insertBlocksetCommand . ExecuteScalarInt64 (- 1 );
2013-07-22 16:54:19 +02:00
2016-04-04 18:11:48 +02:00
long c = 0 ;
2013-07-22 16:54:19 +02:00
if ( blocklisthashes != null )
{
var index = 0L ;
m_insertBlocklistHashCommand . Transaction = transaction ;
m_insertBlocklistHashCommand . SetParameterValue ( 0 , blocksetid );
2016-03-15 21:02:13 +01:00
2013-07-22 16:54:19 +02:00
foreach ( var hash in blocklisthashes )
{
if (! string . IsNullOrEmpty ( hash ))
{
2016-03-15 21:02:13 +01:00
c ++;
if ( c <= expectedblocklisthashes )
{
m_insertBlocklistHashCommand . SetParameterValue ( 1 , index ++);
m_insertBlocklistHashCommand . SetParameterValue ( 2 , hash );
m_insertBlocklistHashCommand . ExecuteNonQuery ();
}
2013-07-22 16:54:19 +02:00
}
}
}
2016-04-04 18:11:48 +02:00
if ( c != expectedblocklisthashes )
2018-03-12 14:07:11 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "MismatchInBlocklistHashCount" , null , "Mismatching number of blocklist hashes detected on blockset {2}. Expected {0} blocklist hashes, but found {1}" , expectedblocklisthashes , c , blocksetid );
2016-04-04 18:11:48 +02:00
2013-07-22 16:54:19 +02:00
return blocksetid ;
}
2023-11-05 18:19:08 +01:00
public bool UpdateBlock ( string hash , long size , long volumeID , System . Data . IDbTransaction transaction , ref bool anyChange )
2013-07-22 16:54:19 +02:00
{
2016-12-16 17:30:39 +01:00
m_findHashBlockCommand . Transaction = transaction ;
m_findHashBlockCommand . SetParameterValue ( 0 , hash );
m_findHashBlockCommand . SetParameterValue ( 1 , size );
var currentVolumeId = m_findHashBlockCommand . ExecuteScalarInt64 (- 2 );
2013-07-22 16:54:19 +02:00
if ( currentVolumeId == volumeID )
2016-03-17 21:43:09 +01:00
return false ;
2023-11-05 18:19:08 +01:00
anyChange = true ;
2013-07-22 16:54:19 +02:00
if ( currentVolumeId == - 2 )
{
//Insert
m_insertBlockCommand . Transaction = transaction ;
m_insertBlockCommand . SetParameterValue ( 0 , hash );
m_insertBlockCommand . SetParameterValue ( 1 , size );
m_insertBlockCommand . SetParameterValue ( 2 , volumeID );
m_insertBlockCommand . ExecuteNonQuery ();
2016-03-17 21:43:09 +01:00
return true ;
2013-07-22 16:54:19 +02:00
}
else if ( currentVolumeId == - 1 )
{
//Update
m_updateBlockVolumeCommand . Transaction = transaction ;
m_updateBlockVolumeCommand . SetParameterValue ( 0 , volumeID );
m_updateBlockVolumeCommand . SetParameterValue ( 1 , hash );
m_updateBlockVolumeCommand . SetParameterValue ( 2 , size );
var c = m_updateBlockVolumeCommand . ExecuteNonQuery ();
if ( c != 1 )
throw new Exception ( string . Format ( "Failed to update table, found {0} entries for key {1} with size {2}" , c , hash , size ));
2016-03-17 21:43:09 +01:00
return true ;
2013-07-22 16:54:19 +02:00
}
else
{
m_insertDuplicateBlockCommand . Transaction = transaction ;
m_insertDuplicateBlockCommand . SetParameterValue ( 0 , hash );
m_insertDuplicateBlockCommand . SetParameterValue ( 1 , size );
m_insertDuplicateBlockCommand . SetParameterValue ( 2 , volumeID );
m_insertDuplicateBlockCommand . ExecuteNonQuery ();
2016-03-17 21:43:09 +01:00
return false ;
}
2013-07-22 16:54:19 +02:00
}
2016-04-03 23:10:47 +02:00
public void AddSmallBlocksetLink ( string filehash , string blockhash , long blocksize , System . Data . IDbTransaction transaction )
{
m_insertSmallBlockset . Transaction = transaction ;
m_insertSmallBlockset . SetParameterValue ( 0 , filehash );
m_insertSmallBlockset . SetParameterValue ( 1 , blockhash );
m_insertSmallBlockset . SetParameterValue ( 2 , blocksize );
m_insertSmallBlockset . ExecuteNonQuery ();
}
2023-11-05 18:19:08 +01:00
public bool AddTempBlockListHash ( string hash , IEnumerable < string > blocklisthashes , System . Data . IDbTransaction transaction )
2013-07-22 16:54:19 +02:00
{
2023-11-05 18:19:08 +01:00
m_findTempBlockListHashCommand . Transaction = transaction ;
m_findTempBlockListHashCommand . SetParameterValue ( 0 , hash );
var r = m_findTempBlockListHashCommand . ExecuteScalar ();
2016-12-16 17:30:39 +01:00
if ( r != null && r != DBNull . Value )
return false ;
2013-07-22 16:54:19 +02:00
2023-11-05 18:19:08 +01:00
m_insertTempBlockListHash . Transaction = transaction ;
m_insertTempBlockListHash . SetParameterValue ( 0 , hash );
2013-07-22 16:54:19 +02:00
var index = 0L ;
foreach ( var s in blocklisthashes )
{
2023-11-05 18:19:08 +01:00
m_insertTempBlockListHash . SetParameterValue ( 1 , s );
m_insertTempBlockListHash . SetParameterValue ( 2 , index ++);
m_insertTempBlockListHash . ExecuteNonQuery ();
2013-07-22 16:54:19 +02:00
}
2016-03-17 21:43:09 +01:00
return true ;
2013-07-22 16:54:19 +02:00
}
public IEnumerable < string > GetBlockLists ( long volumeid )
{
using ( var cmd = m_connection . CreateCommand ())
{
2018-05-24 20:16:46 -07:00
cmd . CommandText = @"SELECT DISTINCT ""BlocklistHash"".""Hash"" FROM ""BlocklistHash"", ""Block"" WHERE ""Block"".""Hash"" = ""BlocklistHash"".""Hash"" AND ""Block"".""VolumeID"" = ?" ;
2013-07-22 16:54:19 +02:00
cmd . AddParameter ( volumeid );
using ( var rd = cmd . ExecuteReader ())
while ( rd . Read ())
yield return rd . GetValue ( 0 ). ToString ();
}
}
2023-11-05 18:19:08 +01:00
public IEnumerable < IRemoteVolume > GetMissingBlockListVolumes ( int passNo , long blocksize , long hashsize , bool forceBlockUse )
2013-07-22 16:54:19 +02:00
{
using ( var cmd = m_connection . CreateCommand ())
{
var selectCommand = @"SELECT DISTINCT ""RemoteVolume"".""Name"", ""RemoteVolume"".""Hash"", ""RemoteVolume"".""Size"", ""RemoteVolume"".""ID"" FROM ""RemoteVolume""" ;
var missingBlocklistEntries =
2016-09-15 11:39:27 +02:00
string . Format (
@"SELECT ""BlocklistHash"".""Hash"" FROM ""BlocklistHash"" LEFT OUTER JOIN ""BlocksetEntry"" ON ""BlocksetEntry"".""Index"" = (""BlocklistHash"".""Index"" * {0}) AND ""BlocksetEntry"".""BlocksetID"" = ""BlocklistHash"".""BlocksetID"" WHERE ""BlocksetEntry"".""BlocksetID"" IS NULL" ,
blocksize / hashsize
);
2016-05-02 20:30:38 +02:00
2019-05-05 02:20:32 +02:00
var missingBlockInfo =
@"SELECT ""VolumeID"" FROM ""Block"" WHERE ""VolumeID"" < 0 AND SIZE > 0" ;
2013-07-22 16:54:19 +02:00
var missingBlocklistVolumes = string . Format (
@"SELECT ""VolumeID"" FROM ""Block"", (" +
missingBlocklistEntries +
@") A WHERE ""A"".""Hash"" = ""Block"".""Hash"" "
);
var countMissingInformation = string . Format (
@"SELECT COUNT(*) FROM (SELECT DISTINCT ""VolumeID"" FROM ({0} UNION {1}))" ,
missingBlockInfo ,
2018-05-17 08:11:13 -06:00
missingBlocklistVolumes );
2013-07-22 16:54:19 +02:00
if ( passNo == 0 )
{
// On the first pass, we select all the volumes we know we need,
// which may be an empty list
cmd . CommandText = string . Format ( selectCommand + @" WHERE ""ID"" IN ({0})" , missingBlocklistVolumes );
// Reset the list
m_proccessedVolumes = new Dictionary < long , long >();
}
else
{
//On anything but the first pass, we check if we are done
2015-01-24 21:59:53 +01:00
var r = cmd . ExecuteScalarInt64 ( countMissingInformation , 0 );
2023-11-05 18:19:08 +01:00
if ( r == 0 && ! forceBlockUse )
2013-07-22 16:54:19 +02:00
yield break ;
if ( passNo == 1 )
{
// On the second pass, we select all volumes that are not mentioned in the db
var mentionedVolumes =
@"SELECT DISTINCT ""VolumeID"" FROM ""Block"" " ;
cmd . CommandText = string . Format ( selectCommand + @" WHERE ""ID"" NOT IN ({0}) AND ""Type"" = ? " , mentionedVolumes );
cmd . AddParameter ( RemoteVolumeType . Blocks . ToString ());
}
else
{
// On the final pass, we select all volumes
// the filter will ensure that we do not download anything twice
cmd . CommandText = selectCommand + @" WHERE ""Type"" = ?" ;
cmd . AddParameter ( RemoteVolumeType . Blocks . ToString ());
}
}
using ( var rd = cmd . ExecuteReader ())
{
while ( rd . Read ())
{
2015-01-24 21:59:53 +01:00
var volumeID = rd . GetInt64 ( 3 );
2013-07-22 16:54:19 +02:00
// Guard against multiple downloads of the same file
if (! m_proccessedVolumes . ContainsKey ( volumeID ))
{
m_proccessedVolumes . Add ( volumeID , volumeID );
yield return new RemoteVolume (
2015-01-24 21:59:53 +01:00
rd . GetString ( 0 ),
2015-05-17 13:38:25 +02:00
rd . ConvertValueToString ( 1 ),
2015-01-24 21:59:53 +01:00
rd . ConvertValueToInt64 ( 2 , - 1 )
2013-07-22 16:54:19 +02:00
);
}
}
}
}
2020-03-14 15:40:14 -07:00
}
public void CleanupMissingVolumes ()
2018-05-10 00:57:51 +02:00
{
2018-05-11 11:34:48 +02:00
var tablename = "SwapBlocks-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2018-05-10 00:57:51 +02:00
2018-05-11 11:34:48 +02:00
// The first part of this query swaps out blocks for non-present remote files with
// existing ones (as recorded in the DuplicateBlock table)
// The second part removes references to the non-present remote files,
// and marks the index files that pointed to them, such that they will be removed later on
2018-12-31 14:24:14 -08:00
var sql = $@"
CREATE TEMPORARY TABLE ""{tablename}"" AS
2018-12-31 14:29:44 -08:00
SELECT ""A"".""ID"" AS ""BlockID"", ""A"".""VolumeID"" AS ""SourceVolumeID"", ""A"".""State"" AS ""SourceVolumeState"", ""B"".""VolumeID"" AS ""TargetVolumeID"", ""B"".""State"" AS ""TargetVolumeState"" FROM (SELECT ""Block"".""ID"", ""Block"".""VolumeID"", ""Remotevolume"".""State"" FROM ""Block"", ""Remotevolume"" WHERE ""Block"".""VolumeID"" = ""Remotevolume"".""ID"" and ""Remotevolume"".""State"" = ""{RemoteVolumeState.Temporary}"") A, (SELECT ""DuplicateBlock"".""BlockID"", ""DuplicateBlock"".""VolumeID"", ""Remotevolume"".""State"" FROM ""DuplicateBlock"", ""Remotevolume"" WHERE ""DuplicateBlock"".""VolumeID"" = ""Remotevolume"".""ID"" and ""Remotevolume"".""State"" = ""{RemoteVolumeState.Verified}"") B WHERE ""A"".""ID"" = ""B"".""BlockID"";
2018-05-10 00:57:51 +02:00
2018-12-31 14:24:14 -08:00
UPDATE ""Block"" SET ""VolumeID"" = (SELECT ""TargetVolumeID"" FROM ""{tablename}"" WHERE ""Block"".""ID"" = ""{tablename}"".""BlockID"") WHERE ""Block"".""ID"" IN (SELECT ""BlockID"" FROM ""{tablename}"");
2018-05-10 00:57:51 +02:00
2018-12-31 14:24:14 -08:00
UPDATE ""DuplicateBlock"" SET ""VolumeID"" = (SELECT ""SourceVolumeID"" FROM ""{tablename}"" WHERE ""DuplicateBlock"".""BlockID"" = ""{tablename}"".""BlockID"") WHERE ""DuplicateBlock"".""BlockID"" IN (SELECT ""BlockID"" FROM ""{tablename}"");
DROP TABLE ""{tablename}"";
2018-05-10 00:57:51 +02:00
2018-12-31 14:29:44 -08:00
DELETE FROM ""IndexBlockLink"" WHERE ""BlockVolumeID"" IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = ""{RemoteVolumeType.Blocks}"" AND ""State"" = ""{RemoteVolumeState.Temporary}"" AND ""ID"" NOT IN (SELECT DISTINCT ""VolumeID"" FROM ""Block""));
DELETE FROM ""DuplicateBlock"" WHERE ""VolumeID"" IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = ""Blocks"" AND ""State"" = ""{RemoteVolumeState.Temporary}"" AND ""ID"" NOT IN (SELECT DISTINCT ""VolumeID"" FROM ""Block""));
DELETE FROM ""RemoteVolume"" WHERE ""Type"" = ""{RemoteVolumeType.Blocks}"" AND ""State"" = ""{RemoteVolumeState.Temporary}"" AND ""ID"" NOT IN (SELECT DISTINCT ""VolumeID"" FROM ""Block"");
2018-12-31 14:24:14 -08:00
" ;
2018-05-11 11:34:48 +02:00
2018-06-11 11:20:12 +02:00
// We could delete these, but we don't have to, so we keep them around until the next compact is done
// UPDATE ""RemoteVolume"" SET ""State"" = ""{3}"" WHERE ""Type"" = ""{5}"" AND ""ID"" NOT IN (SELECT ""IndexVolumeID"" FROM ""IndexBlockLink"");
2018-05-11 11:34:48 +02:00
var countsql = @"SELECT COUNT(*) FROM ""RemoteVolume"" WHERE ""State"" = ""Temporary"" AND ""Type"" = ""Blocks"" " ;
2018-05-10 00:57:51 +02:00
using ( var cmd = m_connection . CreateCommand ())
{
var cnt = cmd . ExecuteScalarInt64 ( countsql );
if ( cnt > 0 )
{
2020-03-14 15:40:14 -07:00
Logging . Log . WriteWarningMessage ( LOGTAG , "MissingVolumesDetected" , null , "Found {0} missing volumes; attempting to replace blocks from existing volumes" , cnt );
2018-05-11 11:34:48 +02:00
cmd . ExecuteNonQuery ( sql );
2018-05-10 00:57:51 +02:00
var cnt2 = cmd . ExecuteScalarInt64 ( countsql );
Logging . Log . WriteVerboseMessage ( LOGTAG , "ReplacedMissingVolumes" , "Replaced blocks for {0} missing volumes; there are now {1} missing volumes" , cnt , cnt2 );
}
}
2020-03-14 15:40:14 -07:00
}
2018-05-10 00:57:51 +02:00
public override void Dispose ()
2013-07-22 16:54:19 +02:00
{
using ( var cmd = m_connection . CreateCommand ())
{
if ( m_tempblocklist != null )
try
{
2013-08-23 23:36:25 +02:00
cmd . CommandText = string . Format ( @"DROP TABLE IF EXISTS ""{0}""" , m_tempblocklist );
2013-07-22 16:54:19 +02:00
cmd . ExecuteNonQuery ();
}
2013-08-23 23:36:25 +02:00
catch { }
2013-07-22 16:54:19 +02:00
finally { m_tempblocklist = null ; }
2016-04-03 23:10:47 +02:00
if ( m_tempsmalllist != null )
try
2020-11-21 13:58:16 -08:00
{
cmd . CommandText = string . Format ( @"DROP TABLE IF EXISTS ""{0}""" , m_tempsmalllist );
cmd . ExecuteNonQuery ();
}
catch { }
finally { m_tempsmalllist = null ; }
2016-04-03 23:10:47 +02:00
2013-07-22 16:54:19 +02:00
}
2016-10-17 15:32:30 +02:00
2013-07-22 16:54:19 +02:00
base . Dispose ();
}
}
}