Improve ref counting when deduplicating attachments on disk

Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
automated-signal
2026-02-13 08:22:45 -06:00
committed by GitHub
parent 6a5182eaa8
commit 95fd8bccb4
11 changed files with 374 additions and 53 deletions

View File

@@ -1444,12 +1444,20 @@ type WritableInterface = {
plaintextHash,
version,
contentType,
messageId,
}: {
plaintextHash: string;
version: number;
contentType: MIMEType;
messageId: string;
}) => ExistingAttachmentData | undefined;
_protectAttachmentPathFromDeletion: (path: string) => void;
_protectAttachmentPathFromDeletion: ({
path,
messageId,
}: {
path: string;
messageId: string;
}) => void;
resetProtectedAttachmentPaths: () => void;
removeAll: () => void;

View File

@@ -2932,12 +2932,19 @@ function getAndProtectExistingAttachmentPath(
plaintextHash,
version,
contentType,
}: { plaintextHash: string; version: number; contentType: string }
messageId,
}: {
plaintextHash: string;
version: number;
contentType: string;
messageId: string;
}
): ExistingAttachmentData | undefined {
if (!isValidPlaintextHash(plaintextHash)) {
logger.error('getAndProtectExistingAttachmentPath: Invalid plaintextHash');
return;
}
if (version < 2) {
logger.error(
'getAndProtectExistingAttachmentPath: Invalid version',
@@ -2985,8 +2992,8 @@ function getAndProtectExistingAttachmentPath(
(${existingData.thumbnailPath}),
(${existingData.screenshotPath})
)
INSERT OR REPLACE INTO attachments_protected_from_deletion(path)
SELECT path
INSERT OR REPLACE INTO attachments_protected_from_deletion(path, messageId)
SELECT path, ${messageId}
FROM existingMessageAttachmentPaths
WHERE path IS NOT NULL;
`;
@@ -2997,11 +3004,13 @@ function getAndProtectExistingAttachmentPath(
function _protectAttachmentPathFromDeletion(
db: WritableDB,
path: string
{ path, messageId }: { path: string; messageId: string }
): void {
const [protectQuery, protectParams] = sql`
INSERT OR REPLACE INTO attachments_protected_from_deletion(path)
VALUES (${path});
INSERT OR REPLACE INTO attachments_protected_from_deletion
(path, messageId)
VALUES
(${path}, ${messageId});
`;
db.prepare(protectQuery).run(protectParams);
}

View File

@@ -0,0 +1,58 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { WritableDB } from '../Interface.std.js';
export default function updateToSchemaVersion1660(db: WritableDB): void {
db.exec(`
DROP TABLE attachments_protected_from_deletion;
CREATE TABLE attachments_protected_from_deletion (
path TEXT NOT NULL,
messageId TEXT NOT NULL,
PRIMARY KEY (path, messageId)
) STRICT;
`);
db.exec(`
DROP TRIGGER stop_protecting_attachments_after_update;
CREATE TRIGGER stop_protecting_attachments_after_update
AFTER UPDATE OF path, thumbnailPath, screenshotPath, backupThumbnailPath
ON message_attachments
WHEN
OLD.path IS NOT NEW.path OR
OLD.thumbnailPath IS NOT NEW.thumbnailPath OR
OLD.screenshotPath IS NOT NEW.screenshotPath OR
OLD.backupThumbnailPath IS NOT NEW.backupThumbnailPath
BEGIN
DELETE FROM attachments_protected_from_deletion
WHERE
messageId IS NEW.messageId
AND path IN (
NEW.path,
NEW.thumbnailPath,
NEW.screenshotPath,
NEW.backupThumbnailPath
);
END;
`);
db.exec(`
DROP TRIGGER stop_protecting_attachments_after_insert;
CREATE TRIGGER stop_protecting_attachments_after_insert
AFTER INSERT
ON message_attachments
BEGIN
DELETE FROM attachments_protected_from_deletion
WHERE
messageId IS NEW.messageId
AND path IN (
NEW.path,
NEW.thumbnailPath,
NEW.screenshotPath,
NEW.backupThumbnailPath
);
END;
`);
}

View File

@@ -142,6 +142,7 @@ import updateToSchemaVersion1620 from './1620-sort-bigger-media.std.js';
import updateToSchemaVersion1630 from './1630-message-pin-message-data.std.js';
import updateToSchemaVersion1640 from './1640-key-transparency.std.js';
import updateToSchemaVersion1650 from './1650-protected-attachments.std.js';
import updateToSchemaVersion1660 from './1660-protected-attachments-non-unique.std.js';
import { DataWriter } from '../Server.node.js';
@@ -1644,6 +1645,7 @@ export const SCHEMA_VERSIONS: ReadonlyArray<SchemaUpdateType> = [
{ version: 1630, update: updateToSchemaVersion1630 },
{ version: 1640, update: updateToSchemaVersion1640 },
{ version: 1650, update: updateToSchemaVersion1650 },
{ version: 1660, update: updateToSchemaVersion1660 },
];
export class DBVersionFromFutureError extends Error {