2025-01-10 09:24:54 +01:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
2025-05-13 08:41:17 +02:00
//
// 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
2025-01-10 09:24:54 +01:00
// Software is furnished to do so, subject to the following conditions:
2025-05-13 08:41:17 +02:00
//
// The above copyright notice and this permission notice shall be included in
2025-01-10 09:24:54 +01:00
// all copies or substantial portions of the Software.
2025-05-13 08:41:17 +02:00
//
// 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-10-05 17:36:42 +02:00
// DEALINGS IN THE SOFTWARE.
2025-04-03 15:46:20 +02:00
#nullable enable
2013-07-22 16:54:19 +02:00
using System ;
using System.Collections.Generic ;
2025-05-13 08:46:24 +02:00
using System.Threading.Tasks ;
using Duplicati.Library.Utility ;
using Microsoft.Data.Sqlite ;
2013-07-22 16:54:19 +02:00
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 >>
{
2025-05-13 08:46:24 +02:00
public readonly int Compare ( KeyValuePair < long , long > x , KeyValuePair < long , long > y )
2013-09-07 21:33:59 +02:00
{
2024-05-22 00:11:03 +02:00
return x . Key == y . Key ?
( x . Value == y . Value ?
0
: ( x . Value < y . Value ? - 1 : 1 ))
2013-09-07 21:33:59 +02:00
: ( x . Key < y . Key ? - 1 : 1 );
}
}
}
2024-05-22 00:11:03 +02:00
2025-05-21 07:36:44 +02:00
private SqliteCommand m_insertFileCommand = null !;
private SqliteCommand m_insertFilesetEntryCommand = null !;
private SqliteCommand m_insertMetadatasetCommand = null !;
private SqliteCommand m_insertBlocksetCommand = null !;
private SqliteCommand m_insertBlocklistHashCommand = null !;
private SqliteCommand m_updateBlockVolumeCommand = null !;
private SqliteCommand m_insertTempBlockListHash = null !;
private SqliteCommand m_insertSmallBlockset = null !;
private SqliteCommand m_findBlocksetCommand = null !;
private SqliteCommand m_findMetadatasetCommand = null !;
private SqliteCommand m_findFilesetCommand = null !;
private SqliteCommand m_findTempBlockListHashCommand = null !;
private SqliteCommand m_findHashBlockCommand = null !;
private SqliteCommand m_insertBlockCommand = null !;
private SqliteCommand m_insertDuplicateBlockCommand = null !;
private string m_tempblocklist = null !;
private string m_tempsmalllist = null !;
// TODO why was this "new" ?
//private new readonly SqliteConnection m_connection;
2025-05-13 08:46:24 +02:00
//public new bool RepairInProgress { get; set; } = false;
2013-07-22 16:54:19 +02:00
/// <summary>
/// A lookup table that prevents multiple downloads of the same volume
/// </summary>
2025-04-03 15:46:20 +02:00
private readonly Dictionary < long , long > m_proccessedVolumes = new Dictionary < long , long >();
2024-05-22 00:11:03 +02:00
2013-07-22 16:54:19 +02:00
// 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]
2025-05-21 08:42:42 +02:00
private static string SELECT_BLOCKLIST_ENTRIES ( long blocksize , long blockhashsize , string temptable , long fullBlockListBlockCount ) => $@"
SELECT
""E"".""BlocksetID"",
""F"".""Index"" + (""E"".""BlocklistIndex"" * {fullBlockListBlockCount}) AS ""FullIndex"",
""F"".""BlockHash"",
MIN({blocksize}, ""E"".""Length"" - ((""F"".""Index"" + (""E"".""BlocklistIndex"" * {fullBlockListBlockCount})) * {blocksize})) AS ""BlockSize"",
""E"".""Hash"",
""E"".""BlocklistSize"",
""E"".""BlocklistHash""
FROM
(
SELECT *
FROM
(
SELECT
""A"".""BlocksetID"",
""A"".""Index"" AS ""BlocklistIndex"",
MIN({fullBlockListBlockCount} * {blockhashsize}, (((""B"".""Length"" + {blocksize} - 1) / {blocksize}) - (""A"".""Index"" * ({fullBlockListBlockCount}))) * {blockhashsize}) AS ""BlocklistSize"",
""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,
""{temptable}"" F
WHERE
""F"".""BlocklistHash"" = ""E"".""Hash""
ORDER BY
""E"".""BlocksetID"",
""FullIndex""
" ;
2013-07-22 16:54:19 +02:00
2025-05-21 07:36:44 +02:00
public static async Task < LocalRecreateDatabase > CreateAsync ( LocalDatabase parentdb , Options options , LocalRecreateDatabase ? dbnew = null )
2013-07-22 16:54:19 +02:00
{
2025-05-21 07:36:44 +02:00
dbnew ??= new LocalRecreateDatabase ();
2023-11-05 18:19:08 +01:00
2025-05-21 08:46:18 +02:00
dbnew = ( LocalRecreateDatabase ) await LocalRestoreDatabase . CreateAsync ( parentdb , dbnew );
2016-04-03 23:10:47 +02:00
2025-05-21 07:36:44 +02:00
dbnew . m_tempblocklist = "TempBlocklist_" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
dbnew . m_tempsmalllist = "TempSmalllist_" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
if ( dbnew . m_connection == null )
throw new Exception ( "Connection is null" );
using var cmd = dbnew . m_connection . CreateCommand ();
2025-05-21 08:42:42 +02:00
await cmd . ExecuteNonQueryAsync ( $@"
CREATE TEMPORARY TABLE ""{dbnew.m_tempblocklist}"" (
""BlockListHash"" TEXT NOT NULL,
""BlockHash"" TEXT NOT NULL,
""Index"" INTEGER NOT NULL
)
" );
await cmd . ExecuteNonQueryAsync ( $@"
CREATE INDEX ""Index_{dbnew.m_tempblocklist}""
ON ""{dbnew.m_tempblocklist}"" (""BlockListHash"");
" );
await cmd . ExecuteNonQueryAsync ( $@"
CREATE TEMPORARY TABLE ""{dbnew.m_tempsmalllist}"" (
""FileHash"" TEXT NOT NULL,
""BlockHash"" TEXT NOT NULL,
""BlockSize"" INTEGER NOT NULL
)
" );
await cmd . ExecuteNonQueryAsync ( $@"
CREATE UNIQUE INDEX ""Index_File_{dbnew.m_tempsmalllist}""
ON ""{dbnew.m_tempsmalllist}"" (
""FileHash"",
""BlockSize""
);
" );
await cmd . ExecuteNonQueryAsync ( $@"
CREATE UNIQUE INDEX ""Index_Block_{dbnew.m_tempsmalllist}""
ON ""{dbnew.m_tempsmalllist}"" (
""BlockHash"",
""BlockSize""
);
" );
dbnew . m_insertFileCommand = await dbnew . m_connection . CreateCommandAsync ( @"
INSERT INTO ""FileLookup"" (
""PrefixID"",
""Path"",
""BlocksetID"",
""MetadataID""
)
VALUES (
@PrefixId,
@Path,
@BlocksetId,
@MetadataId
);
SELECT last_insert_rowid();
" );
dbnew . m_insertFilesetEntryCommand = await dbnew . m_connection . CreateCommandAsync ( @"
INSERT INTO ""FilesetEntry"" (
""FilesetID"",
""FileID"",
""Lastmodified""
)
VALUES (
@FilesetId,
@FileId,
@LastModified
)
" );
dbnew . m_insertMetadatasetCommand = await dbnew . m_connection . CreateCommandAsync ( @"
INSERT INTO ""Metadataset"" (""BlocksetID"")
VALUES (@BlocksetId);
SELECT last_insert_rowid();
" );
dbnew . m_insertBlocksetCommand = await dbnew . m_connection . CreateCommandAsync ( @"
INSERT INTO ""Blockset"" (
""Length"",
""FullHash""
)
VALUES (
@Length,
@FullHash
);
SELECT last_insert_rowid();
" );
dbnew . m_insertBlocklistHashCommand = await dbnew . m_connection . CreateCommandAsync ( @"
INSERT INTO ""BlocklistHash"" (
""BlocksetID"",
""Index"",
""Hash""
)
VALUES (
@BlocksetId,
@Index,
@Hash
)
" );
dbnew . m_updateBlockVolumeCommand = await dbnew . m_connection . CreateCommandAsync ( @"
UPDATE ""Block""
SET ""VolumeID"" = @VolumeId
WHERE
""Hash"" = @Hash
AND ""Size"" = @Size
" );
dbnew . m_insertTempBlockListHash = await dbnew . m_connection . CreateCommandAsync ( $@"
INSERT INTO ""{dbnew.m_tempblocklist}"" (
""BlocklistHash"",
""BlockHash"",
""Index""
)
VALUES (
@BlocklistHash,
@BlockHash,
@Index
)
" );
dbnew . m_insertSmallBlockset = await dbnew . m_connection . CreateCommandAsync ( $@"
INSERT OR IGNORE INTO ""{dbnew.m_tempsmalllist}"" (
""FileHash"",
""BlockHash"",
""BlockSize""
)
VALUES (
@FileHash,
@BlockHash,
@BlockSize
)
" );
dbnew . m_findBlocksetCommand = await dbnew . m_connection . CreateCommandAsync ( @"
SELECT ""ID""
FROM ""Blockset""
WHERE
""Length"" = @Length
AND ""FullHash"" = @FullHash
" );
dbnew . m_findMetadatasetCommand = await dbnew . m_connection . CreateCommandAsync ( @"
SELECT ""Metadataset"".""ID""
FROM ""Metadataset"",""Blockset""
WHERE
""Metadataset"".""BlocksetID"" = ""Blockset"".""ID""
AND ""Blockset"".""FullHash"" = @FullHash
AND ""Blockset"".""Length"" = @Length
" );
dbnew . m_findFilesetCommand = await dbnew . m_connection . CreateCommandAsync ( @"
SELECT ""ID""
FROM ""FileLookup""
WHERE
""PrefixID"" = @PrefixId
AND ""Path"" = @Path
AND ""BlocksetID"" = @BlocksetId
AND ""MetadataID"" = @MetadataId
" );
dbnew . m_findTempBlockListHashCommand = await dbnew . m_connection . CreateCommandAsync ( $@"
SELECT DISTINCT ""BlockListHash""
FROM ""{dbnew.m_tempblocklist}""
WHERE ""BlockListHash"" = @BlocklistHash
" );
dbnew . m_findHashBlockCommand = await dbnew . m_connection . CreateCommandAsync ( @"
SELECT ""VolumeID""
FROM ""Block""
WHERE
""Hash"" = @Hash
AND ""Size"" = @Size
" );
dbnew . m_insertBlockCommand = await dbnew . m_connection . CreateCommandAsync ( @"
INSERT INTO ""Block"" (
""Hash"",
""Size"",
""VolumeID""
)
VALUES (
@Hash,
@Size,
@VolumeId
)
" );
dbnew . m_insertDuplicateBlockCommand = await dbnew . m_connection . CreateCommandAsync ( @"
INSERT OR IGNORE INTO ""DuplicateBlock"" (
""BlockID"",
""VolumeID""
)
VALUES (
(
SELECT ""ID""
FROM ""Block""
WHERE
""Hash"" = @Hash
AND ""Size"" = @Size
),
@VolumeId
)
" );
2025-05-21 07:36:44 +02:00
return dbnew ;
2013-07-22 16:54:19 +02:00
}
2025-05-22 06:14:16 +02:00
public async Task FindMissingBlocklistHashes ( long hashsize , long blocksize )
2013-07-22 16:54:19 +02:00
{
2025-05-21 07:57:13 +02:00
using var cmd = m_connection . CreateCommand ( m_rtr );
2025-05-13 08:46:24 +02:00
//Update all small blocklists and matching blocks
2016-04-03 23:10:47 +02:00
2025-05-21 08:42:42 +02:00
var selectSmallBlocks = $@"
SELECT
""BlockHash"",
""BlockSize""
FROM ""{m_tempsmalllist}""
" ;
var selectBlockHashes = $@"
SELECT
""BlockHash"" AS ""FullHash"",
""BlockSize"" AS ""Length""
FROM (
{SELECT_BLOCKLIST_ENTRIES(blocksize, hashsize, m_tempblocklist, blocksize / hashsize)}
)
" ;
var selectAllBlocks = @ $"
SELECT DISTINCT
""FullHash"",
""Length""
FROM (
{selectBlockHashes}
UNION {selectSmallBlocks}
)
" ;
var selectNewBlocks = $@"
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 ({selectAllBlocks}) A
LEFT OUTER JOIN ""Block"" B
ON ""B"".""Hash"" = ""A"".""FullHash""
AND ""B"".""Size"" = ""A"".""Length""
)
WHERE
""FullHash"" != ""Hash""
AND ""Length"" != ""Size""
" ;
var insertBlocksCommand = @ $"
INSERT INTO ""Block"" (
""Hash"",
""Size"",
""VolumeID""
)
{selectNewBlocks}
" ;
2024-05-22 00:11:03 +02:00
2025-05-13 08:46:24 +02:00
// Insert all known blocks into block table with volumeid = -1
await cmd . ExecuteNonQueryAsync ( insertBlocksCommand );
2024-05-22 00:11:03 +02:00
2025-05-21 08:42:42 +02: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"" ";
var selectBlocklistBlocksetEntries = $@"
SELECT
""E"".""BlocksetID"" AS ""BlocksetID"",
""D"".""FullIndex"" AS ""Index"",
""F"".""ID"" AS ""BlockID""
FROM
({SELECT_BLOCKLIST_ENTRIES(blocksize, hashsize, m_tempblocklist, blocksize / hashsize)}) 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""
" ;
var selectBlocksetEntries = $@"
SELECT
""Blockset"".""ID"" AS ""BlocksetID"",
0 AS ""Index"",
""Block"".""ID"" AS ""BlockID""
FROM
""Blockset"",
""Block"",
""{m_tempsmalllist}"" S
WHERE
""Blockset"".""Fullhash"" = ""S"".""FileHash""
AND ""S"".""BlockHash"" = ""Block"".""Hash""
AND ""S"".""BlockSize"" = ""Block"".""Size""
AND ""Blockset"".""Length"" = ""S"".""BlockSize""
AND ""Blockset"".""Length"" <= {blocksize}
" ;
var selectAllBlocksetEntries = @ $"
{selectBlocklistBlocksetEntries}
UNION
{selectBlocksetEntries}
" ;
var selectFiltered = @ $"
SELECT DISTINCT
""H"".""BlocksetID"",
""H"".""Index"",
""H"".""BlockID""
FROM ({selectAllBlocksetEntries}) H
WHERE (
""H"".""BlocksetID""
|| ':'
|| ""H"".""Index""
) NOT IN (
SELECT (
""ExistingBlocksetEntries"".""BlocksetID""
|| ':'
|| ""ExistingBlocksetEntries"".""Index""
)
FROM ""BlocksetEntry"" ""ExistingBlocksetEntries""
)
" ;
var insertBlocksetEntriesCommand = @ $"
INSERT INTO ""BlocksetEntry"" (
""BlocksetID"",
""Index"",
""BlockID""
)
{selectFiltered}
" ;
2017-01-05 21:41:21 +01:00
2025-05-13 08:46:24 +02:00
try
{
await cmd . ExecuteNonQueryAsync ( insertBlocksetEntriesCommand );
}
catch ( Exception ex )
{
Logging . Log . WriteErrorMessage ( LOGTAG , "BlocksetInsertFailed" , ex , "Blockset insert failed, committing temporary tables for trace purposes" );
2025-05-21 07:57:13 +02:00
using ( var fixcmd = m_connection . CreateCommand ( m_rtr ))
2017-01-05 21:41:21 +01:00
{
2025-05-21 08:42:42 +02:00
await fixcmd . ExecuteNonQueryAsync ( $@"
CREATE TABLE ""{m_tempblocklist}-Failure"" AS
SELECT *
FROM ""{m_tempblocklist}""
" );
await fixcmd . ExecuteNonQueryAsync ( $@"
CREATE TABLE ""{m_tempsmalllist}-Failure"" AS
SELECT *
FROM ""{m_tempsmalllist}""
" );
2017-01-05 21:41:21 +01:00
}
2025-05-13 08:46:24 +02:00
throw new Exception ( "The recreate failed, please create a bug-report from this database and send it to the developers for further analysis" );
2013-07-22 16:54:19 +02:00
}
}
2023-11-05 18:19:08 +01:00
2024-05-22 00:11:03 +02:00
/// <summary>
2023-11-05 18:19:08 +01:00
/// From the temporary tables 1) insert new blocks into Block (VolumeID to be set at a later stage)
/// and 2) add missing BlocksetEntry lines
2024-05-22 00:11:03 +02:00
///
/// 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
2023-11-05 18:19:08 +01:00
/// </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
2025-05-22 06:14:16 +02:00
public async Task AddBlockAndBlockSetEntryFromTemp ( long hashsize , long blocksize , bool hashOnly = false )
2023-11-05 18:19:08 +01:00
{
2025-05-21 07:57:13 +02:00
using var cmd = m_connection . CreateCommand ( m_rtr );
2025-05-21 08:42:42 +02:00
var extra = hashOnly ? "" : $@"
UNION
SELECT
TS.BlockHash,
TS.BlockSize
FROM {m_tempsmalllist} TS
WHERE NOT EXISTS (
SELECT ""X""
FROM Block AS B
WHERE
B.Hash = TS.BlockHash
AND B.Size = TS.BlockSize
)
" ;
var insertBlocksCommand = $@"
INSERT INTO BLOCK (
Hash,
Size,
VolumeID
)
SELECT DISTINCT
BlockHash AS Hash,
BlockSize AS Size,
-1 AS VolumeID
FROM (
SELECT
NB.BlockHash,
MIN({blocksize}, BS.Length - ((NB.""Index"" + (BH.""Index"" * {blocksize / hashsize})) * {blocksize})) AS BlockSize
FROM (
SELECT
TBL.BlockListHash,
TBL.BlockHash,
TBL.""Index"" FROM {m_tempblocklist} 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)
{extra}
)
" ;
extra = hashOnly ? "" : $@"
UNION
SELECT
BS.ID AS BlocksetID,
0 AS ""Index"",
BL.ID AS BlockID
FROM {m_tempsmalllist} TS
JOIN Blockset BS
ON (
BS.FullHash = TS.FileHash
AND BS.Length = TS.BlockSize
AND BS.Length <= {blocksize}
)
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
" ;
var insertBlocksetEntriesCommand = $@"
INSERT INTO BlocksetEntry (
BlocksetID,
""Index"",
BlockID
)
SELECT DISTINCT
BH.blocksetid,
(BH.""Index"" * {blocksize / hashsize})+TBL.""Index"" as FullIndex,
BK.ID AS BlockID
FROM {m_tempblocklist} 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"" * {blocksize / hashsize})+TBL.""Index""
)
WHERE BE.BlockSetID IS NULL
{extra}" ;
2023-11-05 18:19:08 +01:00
2025-05-13 08:46:24 +02:00
try
{
// Insert discovered new blocks into block table with volumeid = -1
await cmd . ExecuteNonQueryAsync ( insertBlocksCommand );
// Insert corresponding entries into blockset
await cmd . ExecuteNonQueryAsync ( insertBlocksetEntriesCommand );
}
catch ( Exception ex )
{
Logging . Log . WriteErrorMessage ( LOGTAG , "BlockOrBlocksetInsertFailed" , ex , "Block or Blockset insert failed, committing temporary tables for trace purposes" );
2025-05-21 07:57:13 +02:00
using ( var fixcmd = m_connection . CreateCommand ( m_rtr ))
2023-11-05 18:19:08 +01:00
{
2025-05-21 08:42:42 +02:00
await fixcmd . ExecuteNonQueryAsync ( $@"
CREATE TABLE ""{m_tempblocklist}_Failure"" AS
SELECT *
FROM ""{m_tempblocklist}""
" );
await fixcmd . ExecuteNonQueryAsync ( $@"
CREATE TABLE ""{m_tempsmalllist}_Failure""
AS SELECT *
FROM ""{m_tempsmalllist}""
" );
2023-11-05 18:19:08 +01:00
}
2025-05-13 08:46:24 +02:00
throw new Exception ( "The recreate failed, please create a bug-report from this database and send it to the developers for further analysis" );
2023-11-05 18:19:08 +01:00
}
}
2025-05-22 06:14:16 +02:00
public async Task AddDirectoryEntry ( long filesetid , long pathprefixid , string path , DateTime time , long metadataid )
2013-07-22 16:54:19 +02:00
{
2025-05-22 06:14:16 +02:00
await AddEntry ( filesetid , pathprefixid , path , time , FOLDER_BLOCKSET_ID , metadataid );
2013-07-22 16:54:19 +02:00
}
2025-05-22 06:14:16 +02:00
public async Task AddSymlinkEntry ( long filesetid , long pathprefixid , string path , DateTime time , long metadataid )
2013-07-22 16:54:19 +02:00
{
2025-05-22 06:14:16 +02:00
await AddEntry ( filesetid , pathprefixid , path , time , SYMLINK_BLOCKSET_ID , metadataid );
2013-07-22 16:54:19 +02:00
}
2024-05-22 00:11:03 +02:00
2025-05-22 06:14:16 +02:00
public async Task AddFileEntry ( long filesetid , long pathprefixid , string path , DateTime time , long blocksetid , long metadataid )
2013-07-22 16:54:19 +02:00
{
2025-05-22 06:14:16 +02:00
await AddEntry ( filesetid , pathprefixid , path , time , blocksetid , metadataid );
2013-07-22 16:54:19 +02:00
}
2018-10-23 11:33:35 +02:00
2025-05-22 06:14:16 +02:00
private async Task AddEntry ( long filesetid , long pathprefixid , string path , DateTime time , long blocksetid , long metadataid )
2013-07-22 16:54:19 +02:00
{
2025-05-21 07:57:13 +02:00
var fileid = await m_findFilesetCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@FilesetId" , filesetid )
. SetParameterValue ( "@PrefixId" , pathprefixid )
. SetParameterValue ( "@Path" , path )
. SetParameterValue ( "@BlocksetId" , blocksetid )
. SetParameterValue ( "@MetadataId" , metadataid )
. ExecuteScalarInt64Async (- 1 );
2018-06-14 10:12:24 +02:00
2013-07-22 16:54:19 +02:00
if ( fileid < 0 )
{
2025-05-21 07:57:13 +02:00
fileid = await m_insertFileCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@PrefixId" , pathprefixid )
. SetParameterValue ( "@Path" , path )
. SetParameterValue ( "@BlocksetId" , blocksetid )
. SetParameterValue ( "@MetadataId" , metadataid )
. ExecuteScalarInt64Async (- 1 );
2013-07-22 16:54:19 +02:00
}
2024-05-22 00:11:03 +02:00
2025-05-21 07:57:13 +02:00
await m_insertFilesetEntryCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@FilesetId" , filesetid )
. SetParameterValue ( "@FileId" , fileid )
. SetParameterValue ( "@LastModified" , time . ToUniversalTime (). Ticks )
. ExecuteNonQueryAsync ();
2013-07-22 16:54:19 +02:00
}
2024-05-22 00:11:03 +02:00
2025-05-22 06:14:16 +02:00
public async Task < long > AddMetadataset ( string metahash , long metahashsize , IEnumerable < string > metablocklisthashes , long expectedmetablocklisthashes )
2013-07-22 16:54:19 +02:00
{
var metadataid = - 1L ;
if ( metahash == null )
return metadataid ;
2024-05-22 00:11:03 +02:00
2025-05-21 07:57:13 +02:00
metadataid = await m_findMetadatasetCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@FullHash" , metahash )
. SetParameterValue ( "@Length" , metahashsize )
. ExecuteScalarInt64Async (- 1 );
2025-05-13 08:46:24 +02:00
2016-12-16 17:30:39 +01:00
if ( metadataid != - 1 )
return metadataid ;
2016-04-03 23:10:47 +02:00
2025-05-22 06:14:16 +02:00
var blocksetid = await AddBlockset ( metahash , metahashsize , metablocklisthashes , expectedmetablocklisthashes );
2024-05-22 00:11:03 +02:00
2025-05-21 07:57:13 +02:00
metadataid = await m_insertMetadatasetCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@BlocksetId" , blocksetid )
. ExecuteScalarInt64Async (- 1 );
2025-05-13 08:46:24 +02:00
return metadataid ;
2013-07-22 16:54:19 +02:00
}
2024-05-22 00:11:03 +02:00
2025-05-22 06:14:16 +02:00
public async Task < long > AddBlockset ( string fullhash , long size , IEnumerable < string > blocklisthashes , long expectedblocklisthashes )
2013-07-22 16:54:19 +02:00
{
2025-05-21 07:57:13 +02:00
var blocksetid = await m_findBlocksetCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@Length" , size )
. SetParameterValue ( "@FullHash" , fullhash )
. ExecuteScalarInt64Async (- 1 );
2025-05-13 08:46:24 +02:00
2016-12-16 17:30:39 +01:00
if ( blocksetid != - 1 )
2024-05-22 00:11:03 +02:00
return blocksetid ;
2025-05-21 07:57:13 +02:00
blocksetid = await m_insertBlocksetCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@Length" , size )
. SetParameterValue ( "@FullHash" , fullhash )
. ExecuteScalarInt64Async (- 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 ;
2025-05-21 07:57:13 +02:00
m_insertBlocklistHashCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@BlocksetId" , blocksetid );
2016-03-15 21:02:13 +01:00
2024-05-22 00:11:03 +02:00
foreach ( var hash in blocklisthashes )
2013-07-22 16:54:19 +02:00
{
if (! string . IsNullOrEmpty ( hash ))
{
2016-03-15 21:02:13 +01:00
c ++;
if ( c <= expectedblocklisthashes )
{
2025-05-21 07:57:13 +02:00
await m_insertBlocklistHashCommand
. SetParameterValue ( "@Index" , index )
. SetParameterValue ( "@Hash" , hash )
. ExecuteNonQueryAsync ();
2016-03-15 21:02:13 +01:00
}
2013-07-22 16:54:19 +02:00
}
}
}
2024-05-22 00:11:03 +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 );
2024-05-22 00:11:03 +02:00
2013-07-22 16:54:19 +02:00
return blocksetid ;
}
2025-05-22 06:14:16 +02:00
public async Task <( bool , bool )> UpdateBlock ( string hash , long size , long volumeID )
2013-07-22 16:54:19 +02:00
{
2025-05-13 08:46:24 +02:00
var anyChange = false ;
2025-05-21 07:57:13 +02:00
var currentVolumeId = await m_findHashBlockCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@Hash" , hash )
. SetParameterValue ( "@Size" , size )
. ExecuteScalarInt64Async (- 2 );
2016-12-16 17:30:39 +01:00
2013-07-22 16:54:19 +02:00
if ( currentVolumeId == volumeID )
2025-05-13 08:46:24 +02:00
return ( anyChange , false );
2023-11-05 18:19:08 +01:00
anyChange = true ;
2013-07-22 16:54:19 +02:00
if ( currentVolumeId == - 2 )
{
//Insert
2025-05-21 07:57:13 +02:00
await m_insertBlockCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@Hash" , hash )
. SetParameterValue ( "@Size" , size )
. SetParameterValue ( "@VolumeId" , volumeID )
. ExecuteNonQueryAsync ();
2024-05-22 00:11:03 +02:00
2025-05-13 08:46:24 +02:00
return ( anyChange , true );
2013-07-22 16:54:19 +02:00
}
else if ( currentVolumeId == - 1 )
{
//Update
2025-05-21 07:57:13 +02:00
var c = await m_updateBlockVolumeCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@VolumeId" , volumeID )
. SetParameterValue ( "@Hash" , hash )
. SetParameterValue ( "@Size" , size )
. ExecuteNonQueryAsync ();
2013-07-22 16:54:19 +02:00
if ( c != 1 )
2025-03-14 12:13:17 +01:00
throw new Exception ( $"Failed to update table, found {c} entries for key {hash} with size {size}" );
2024-05-22 00:11:03 +02:00
2025-05-13 08:46:24 +02:00
return ( anyChange , true );
2013-07-22 16:54:19 +02:00
}
else
{
2025-05-21 07:57:13 +02:00
await m_insertDuplicateBlockCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@Hash" , hash )
. SetParameterValue ( "@Size" , size )
. SetParameterValue ( "@VolumeId" , currentVolumeId )
. ExecuteNonQueryAsync ();
2016-03-17 21:43:09 +01:00
2025-05-13 08:46:24 +02:00
return ( anyChange , false );
2024-05-22 00:11:03 +02:00
}
2013-07-22 16:54:19 +02:00
}
2016-04-03 23:10:47 +02:00
2025-05-22 06:14:16 +02:00
public async Task AddSmallBlocksetLink ( string filehash , string blockhash , long blocksize )
2016-04-03 23:10:47 +02:00
{
2025-05-21 07:57:13 +02:00
await m_insertSmallBlockset
. SetTransaction ( m_rtr )
. SetParameterValue ( "@FileHash" , filehash )
. SetParameterValue ( "@BlockHash" , blockhash )
. SetParameterValue ( "@BlockSize" , blocksize )
. ExecuteNonQueryAsync ();
2016-04-03 23:10:47 +02:00
}
2023-11-05 18:19:08 +01:00
2025-05-22 06:14:16 +02:00
public async Task < bool > AddTempBlockListHash ( string hash , IEnumerable < string > blocklisthashes )
2013-07-22 16:54:19 +02:00
{
2025-05-21 07:57:13 +02:00
var r = await m_findTempBlockListHashCommand
. SetTransaction ( m_rtr )
. SetParameterValue ( "@BlocklistHash" , hash )
. ExecuteScalarAsync ();
2025-05-13 08:46:24 +02:00
2016-12-16 17:30:39 +01:00
if ( r != null && r != DBNull . Value )
return false ;
2024-05-22 00:11:03 +02:00
2025-05-21 07:57:13 +02:00
m_insertTempBlockListHash
. SetTransaction ( m_rtr )
. SetParameterValue ( "@BlocklistHash" , hash );
2024-05-22 00:11:03 +02:00
2013-07-22 16:54:19 +02:00
var index = 0L ;
2024-05-22 00:11:03 +02:00
foreach ( var s in blocklisthashes )
2013-07-22 16:54:19 +02:00
{
2025-05-21 07:57:13 +02:00
await m_insertTempBlockListHash
. SetParameterValue ( "@BlockHash" , s )
. SetParameterValue ( "@Index" , index ++)
. ExecuteNonQueryAsync ();
2013-07-22 16:54:19 +02:00
}
2016-03-17 21:43:09 +01:00
return true ;
2024-05-22 00:11:03 +02:00
}
2013-07-22 16:54:19 +02:00
2025-05-13 08:46:24 +02:00
public async IAsyncEnumerable < string > GetBlockLists ( long volumeid )
2013-07-22 16:54:19 +02:00
{
2025-05-21 08:42:42 +02:00
using var cmd = m_connection . CreateCommand ( @"
SELECT DISTINCT ""BlocklistHash"".""Hash""
FROM
""BlocklistHash"",
""Block""
WHERE
""Block"".""Hash"" = ""BlocklistHash"".""Hash""
AND ""Block"".""VolumeID"" = @VolumeId
" )
2025-05-21 07:57:13 +02:00
. SetTransaction ( m_rtr )
. SetParameterValue ( "@VolumeId" , volumeid );
2025-05-13 08:46:24 +02:00
using var rd = await cmd . ExecuteReaderAsync ();
while ( rd . Read ())
yield return rd . ConvertValueToString ( 0 ) ?? "" ;
2013-07-22 16:54:19 +02:00
}
2025-05-13 08:46:24 +02:00
public async IAsyncEnumerable < IRemoteVolume > GetMissingBlockListVolumes ( int passNo , long blocksize , long hashsize , bool forceBlockUse )
2013-07-22 16:54:19 +02:00
{
2024-05-22 00:11:03 +02:00
using ( var cmd = m_connection . CreateCommand ())
2013-07-22 16:54:19 +02:00
{
2025-05-21 08:42:42 +02:00
var selectCommand = @"
SELECT DISTINCT
""RemoteVolume"".""Name"",
""RemoteVolume"".""Hash"",
""RemoteVolume"".""Size"",
""RemoteVolume"".""ID""
FROM ""RemoteVolume""
" ;
var missingBlocklistEntries = $@"
SELECT ""BlocklistHash"".""Hash""
FROM ""BlocklistHash""
LEFT OUTER JOIN ""BlocksetEntry""
ON ""BlocksetEntry"".""Index"" = (""BlocklistHash"".""Index"" * {blocksize / hashsize})
AND ""BlocksetEntry"".""BlocksetID"" = ""BlocklistHash"".""BlocksetID""
WHERE ""BlocksetEntry"".""BlocksetID"" IS NULL
" ;
var missingBlockInfo = @"
SELECT ""VolumeID""
FROM ""Block""
WHERE
""VolumeID"" < 0
AND SIZE > 0
" ;
var missingBlocklistVolumes = $@"
SELECT ""VolumeID""
FROM
""Block"",
({missingBlocklistEntries}) A
WHERE ""A"".""Hash"" = ""Block"".""Hash""
" ;
var countMissingInformation = $@"
SELECT COUNT(*)
FROM (
SELECT DISTINCT ""VolumeID""
FROM (
{missingBlockInfo}
UNION {missingBlocklistVolumes}
)
)
" ;
2018-05-17 08:11:13 -06:00
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
2025-05-21 08:42:42 +02:00
cmd . SetCommandAndParameters ( $@"
{selectCommand}
WHERE ""ID"" IN ({missingBlocklistVolumes})
" );
2024-05-22 00:11:03 +02:00
2013-07-22 16:54:19 +02:00
// Reset the list
2025-04-03 15:46:20 +02:00
m_proccessedVolumes . Clear ();
2013-07-22 16:54:19 +02:00
}
else
{
//On anything but the first pass, we check if we are done
2025-05-21 07:57:13 +02:00
var r = await cmd
. SetCommandAndParameters ( countMissingInformation )
. ExecuteScalarInt64Async ( 0 );
2023-11-05 18:19:08 +01:00
if ( r == 0 && ! forceBlockUse )
2013-07-22 16:54:19 +02:00
yield break ;
2024-05-22 00:11:03 +02:00
2013-07-22 16:54:19 +02:00
if ( passNo == 1 )
{
// On the second pass, we select all volumes that are not mentioned in the db
2024-05-22 00:11:03 +02:00
2025-05-21 08:42:42 +02:00
var mentionedVolumes = @"
SELECT DISTINCT ""VolumeID""
FROM ""Block""
" ;
2024-05-22 00:11:03 +02:00
2025-05-21 07:57:13 +02:00
cmd
2025-05-21 08:42:42 +02:00
. SetCommandAndParameters ( $@"
{selectCommand}
WHERE
""ID"" IN ({mentionedVolumes})
AND ""Type"" = @Type
" )
2025-05-21 07:57:13 +02:00
. SetParameterValue ( "@Type" , RemoteVolumeType . Blocks . ToString ());
2025-05-13 08:46:24 +02:00
2025-04-02 16:54:16 +02:00
2013-07-22 16:54:19 +02:00
}
else
{
// On the final pass, we select all volumes
// the filter will ensure that we do not download anything twice
2025-05-21 07:57:13 +02:00
cmd
2025-05-21 08:42:42 +02:00
. SetCommandAndParameters ( $@"
{selectCommand}
WHERE ""Type"" = @Type
" )
2025-05-21 07:57:13 +02:00
. SetParameterValue ( "@Type" , RemoteVolumeType . Blocks . ToString ());
2013-07-22 16:54:19 +02:00
}
}
2024-05-22 00:11:03 +02:00
2025-05-13 08:46:24 +02:00
using var rd = await cmd . ExecuteReaderAsync ();
while ( await rd . ReadAsync ())
2013-07-22 16:54:19 +02:00
{
2025-05-13 08:46:24 +02:00
var volumeID = rd . ConvertValueToInt64 ( 3 );
2024-05-22 00:11:03 +02:00
2025-05-13 08:46:24 +02:00
// Guard against multiple downloads of the same file
if ( m_proccessedVolumes . TryAdd ( volumeID , volumeID ))
{
yield return new RemoteVolume (
rd . ConvertValueToString ( 0 ),
rd . ConvertValueToString ( 1 ),
rd . ConvertValueToInt64 ( 2 , - 1 )
);
2013-07-22 16:54:19 +02:00
}
}
}
2020-03-14 15:40:14 -07:00
}
2025-05-13 08:46:24 +02:00
public async Task CleanupMissingVolumes ()
2024-05-22 00:11:03 +02:00
{
var tablename = "SwapBlocks-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
// TODO: either hardcode all string constants or none
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
2024-05-22 00:11:03 +02:00
// existing ones (as recorded in the DuplicateBlock table)
2018-05-11 11:34:48 +02:00
// 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
2025-05-21 07:40:39 +02:00
var sql = $@"
2025-05-21 08:42:42 +02:00
CREATE TEMPORARY TABLE ""{tablename}"" AS
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"",
MIN(""DuplicateBlock"".""VolumeID"") AS ""VolumeID"",
""Remotevolume"".""State""
FROM
""DuplicateBlock"",
""Remotevolume""
WHERE
""DuplicateBlock"".""VolumeID"" = ""Remotevolume"".""ID""
AND ""Remotevolume"".""State"" = '{RemoteVolumeState.Verified}'
GROUP BY
""DuplicateBlock"".""BlockID"",
""Remotevolume"".""State""
) B
WHERE ""A"".""ID"" = ""B"".""BlockID"";
UPDATE ""Block""
SET ""VolumeID"" = (
SELECT ""TargetVolumeID""
FROM ""{tablename}""
WHERE ""Block"".""ID"" = ""{tablename}"".""BlockID""
)
WHERE ""Block"".""ID"" IN (
SELECT ""BlockID""
FROM ""{tablename}""
);
UPDATE ""DuplicateBlock""
SET ""VolumeID"" = (
SELECT ""SourceVolumeID""
FROM ""{tablename}""
WHERE ""DuplicateBlock"".""BlockID"" = ""{tablename}"".""BlockID""
)
WHERE (
""DuplicateBlock"".""BlockID"",
""DuplicateBlock"".""VolumeID""
) IN (
SELECT
""BlockID"",
""TargetVolumeID""
FROM ""{tablename}""
);
DROP TABLE ""{tablename}"";
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"" = '{RemoteVolumeType.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-05-11 11:34:48 +02:00
2024-05-22 00:11:03 +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-06-11 11:20:12 +02:00
2025-05-21 08:42:42 +02:00
var countsql = $@"
SELECT COUNT(*)
FROM ""RemoteVolume""
WHERE
""State"" = '{RemoteVolumeState.Temporary}'
AND ""Type"" = '{RemoteVolumeType.Blocks}'
" ;
2018-05-10 00:57:51 +02:00
2025-05-21 07:57:13 +02:00
using ( var cmd = m_connection . CreateCommand ( m_rtr ))
2024-05-22 00:11:03 +02:00
{
2025-05-13 08:46:24 +02:00
var cnt = await cmd . ExecuteScalarInt64Async ( countsql );
2024-05-22 00:11:03 +02:00
if ( cnt > 0 )
{
2025-03-20 15:59:14 +01:00
try
{
2025-05-13 08:46:24 +02:00
await cmd . ExecuteNonQueryAsync ( sql );
var cnt2 = await cmd . ExecuteScalarInt64Async ( countsql );
2025-03-20 15:59:14 +01:00
Logging . Log . WriteWarningMessage ( LOGTAG , "MissingVolumesDetected" , null , "Replaced blocks for {0} missing volumes; there are now {1} missing volumes" , cnt , cnt2 );
}
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "MissingVolumesDetected" , ex , "Found {0} missing volumes; failed while attempting to replace blocks from existing volumes" , cnt );
throw ;
}
2024-05-22 00:11:03 +02:00
}
}
}
2020-03-14 15:40:14 -07:00
2024-11-01 15:56:14 +01:00
/// <summary>
/// Move blocks that are not referenced by any files to DeletedBlock table.
/// </summary>
/// Needs to be called after the last FindMissingBlocklistHashes, otherwise the tables are not up to date.
2025-05-22 06:14:16 +02:00
public async Task CleanupDeletedBlocks ()
2024-11-01 15:56:14 +01:00
{
// Find out which blocks are deleted and move them into DeletedBlock, so that compact notices these blocks are empty
// Deleted blocks do not appear in the BlocksetEntry and not in the BlocklistHash table
var tmptablename = "DeletedBlocks-" + Library . Utility . Utility . ByteArrayAsHexString ( Guid . NewGuid (). ToByteArray ());
2025-05-21 07:57:13 +02:00
using var cmd = m_connection . CreateCommand ( m_rtr );
2025-05-13 08:46:24 +02:00
// 1. Select blocks not used by any file and not as a blocklist into temporary table
2025-05-21 08:42:42 +02:00
await cmd . ExecuteNonQueryAsync ( $@"
CREATE TEMPORARY TABLE ""{tmptablename}"" AS
SELECT
""Block"".""ID"",
""Block"".""Hash"",
""Block"".""Size"",
""Block"".""VolumeID""
FROM ""Block""
WHERE
""Block"".""ID"" NOT IN (
SELECT ""BlocksetEntry"".""BlockID""
FROM ""BlocksetEntry""
)
AND ""Block"".""Hash"" NOT IN (
SELECT ""BlocklistHash"".""Hash""
FROM ""BlocklistHash""
)
" );
2025-05-13 08:46:24 +02:00
// 2. Insert blocks into DeletedBlock table
2025-05-21 08:42:42 +02:00
await cmd . ExecuteNonQueryAsync ( $@"
INSERT INTO ""DeletedBlock"" (
""Hash"",
""Size"",
""VolumeID""
)
SELECT
""Hash"",
""Size"",
""VolumeID""
FROM ""{tmptablename}""
" );
2025-05-13 08:46:24 +02:00
// 3. Remove blocks from Block table
2025-05-21 08:42:42 +02:00
await cmd . ExecuteNonQueryAsync ( $@"
DELETE FROM ""Block""
WHERE ""ID"" IN (
SELECT ""ID""
FROM ""{tmptablename}""
)
" );
2025-05-21 07:40:39 +02:00
await cmd . ExecuteNonQueryAsync ( $@"DROP TABLE IF EXISTS ""{tmptablename}""" );
2025-05-21 07:57:13 +02:00
await m_rtr . CommitAsync ();
2024-11-01 15:56:14 +01:00
}
2024-05-22 00:11:03 +02:00
public override void Dispose ()
{
2025-05-21 07:57:13 +02:00
DisposeAsync (). Await ();
}
public async override Task DisposeAsync ()
{
using ( var cmd = m_connection . CreateCommand ( m_rtr ))
2024-05-22 00:11:03 +02:00
{
2013-07-22 16:54:19 +02:00
if ( m_tempblocklist != null )
try
{
2025-05-21 07:57:13 +02:00
await cmd . ExecuteNonQueryAsync ( @ $"DROP TABLE IF EXISTS ""{m_tempblocklist}""" );
2013-07-22 16:54:19 +02:00
}
2025-03-10 14:37:30 +01:00
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "FailedToDropTempBlocklist" , ex , "Failed to drop temporary blocklist table {0}" , m_tempblocklist );
}
finally
{
2025-04-03 15:46:20 +02:00
m_tempblocklist = null !;
2025-03-10 14:37:30 +01:00
}
2024-05-22 00:11:03 +02:00
2016-04-03 23:10:47 +02:00
if ( m_tempsmalllist != null )
try
2020-11-21 13:58:16 -08:00
{
2025-05-21 07:57:13 +02:00
await cmd . ExecuteNonQueryAsync ( @ $"DROP TABLE IF EXISTS ""{m_tempsmalllist}""" );
2020-11-21 13:58:16 -08:00
}
2025-03-10 14:37:30 +01:00
catch ( Exception ex )
{
Logging . Log . WriteWarningMessage ( LOGTAG , "FailedToDropTempSmalllist" , ex , "Failed to drop temporary smalllist table {0}" , m_tempsmalllist );
}
finally
{
2025-04-03 15:46:20 +02:00
m_tempsmalllist = null !;
2025-03-10 14:37:30 +01:00
}
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 ();
}
}
2025-05-21 08:42:42 +02:00
}