Enable backfilling of attachments without message_attachment rows
This commit is contained in:
parent
f4c1d9334f
commit
37ec000831
4 changed files with 109 additions and 8 deletions
|
@ -1238,6 +1238,8 @@ type WritableInterface = {
|
||||||
|
|
||||||
processGroupCallRingCancellation(ringId: bigint): void;
|
processGroupCallRingCancellation(ringId: bigint): void;
|
||||||
cleanExpiredGroupCallRingCancellations(): void;
|
cleanExpiredGroupCallRingCancellations(): void;
|
||||||
|
|
||||||
|
_testOnlyRemoveMessageAttachments(timestamp: number): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adds a database argument
|
// Adds a database argument
|
||||||
|
|
|
@ -705,6 +705,8 @@ export const DataWriter: ServerWritableInterface = {
|
||||||
disableFSync,
|
disableFSync,
|
||||||
enableFSyncAndCheckpoint,
|
enableFSyncAndCheckpoint,
|
||||||
|
|
||||||
|
_testOnlyRemoveMessageAttachments,
|
||||||
|
|
||||||
// Server-only
|
// Server-only
|
||||||
|
|
||||||
removeKnownStickers,
|
removeKnownStickers,
|
||||||
|
@ -2720,6 +2722,17 @@ function saveMessageAttachment({
|
||||||
).run(values);
|
).run(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _testOnlyRemoveMessageAttachments(
|
||||||
|
db: WritableDB,
|
||||||
|
timestamp: number
|
||||||
|
): void {
|
||||||
|
const [query, params] = sql`
|
||||||
|
DELETE FROM message_attachments
|
||||||
|
WHERE sentAt = ${timestamp};`;
|
||||||
|
|
||||||
|
db.prepare(query).run(params);
|
||||||
|
}
|
||||||
|
|
||||||
function saveMessage(
|
function saveMessage(
|
||||||
db: WritableDB,
|
db: WritableDB,
|
||||||
message: ReadonlyDeep<MessageType>,
|
message: ReadonlyDeep<MessageType>,
|
||||||
|
|
|
@ -23,13 +23,20 @@ import {
|
||||||
sql,
|
sql,
|
||||||
sqlJoin,
|
sqlJoin,
|
||||||
} from './util';
|
} from './util';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import { type AttachmentType } from '../types/Attachment';
|
||||||
import { IMAGE_JPEG, stringToMIMEType } from '../types/MIME';
|
import {
|
||||||
|
APPLICATION_OCTET_STREAM,
|
||||||
|
IMAGE_JPEG,
|
||||||
|
IMAGE_PNG,
|
||||||
|
stringToMIMEType,
|
||||||
|
} from '../types/MIME';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
import type { MessageAttributesType } from '../model-types';
|
import type { MessageAttributesType } from '../model-types';
|
||||||
|
import { createLogger } from '../logging/log';
|
||||||
|
|
||||||
export const ROOT_MESSAGE_ATTACHMENT_EDIT_HISTORY_INDEX = -1;
|
export const ROOT_MESSAGE_ATTACHMENT_EDIT_HISTORY_INDEX = -1;
|
||||||
|
|
||||||
|
const log = createLogger('hydrateMessage');
|
||||||
function toBoolean(value: number | null): boolean | undefined {
|
function toBoolean(value: number | null): boolean | undefined {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -48,9 +55,12 @@ export function hydrateMessages(
|
||||||
db: ReadableDB,
|
db: ReadableDB,
|
||||||
unhydratedMessages: Array<MessageTypeUnhydrated>
|
unhydratedMessages: Array<MessageTypeUnhydrated>
|
||||||
): Array<MessageType> {
|
): Array<MessageType> {
|
||||||
const messagesWithColumnsHydrated = unhydratedMessages.map(
|
const messagesWithColumnsHydrated = unhydratedMessages.map(msg => ({
|
||||||
hydrateMessageTableColumns
|
...hydrateMessageTableColumns(msg),
|
||||||
);
|
hasAttachments: msg.hasAttachments === 1,
|
||||||
|
hasFileAttachments: msg.hasFileAttachments === 1,
|
||||||
|
hasVisualMediaAttachments: msg.hasVisualMediaAttachments === 1,
|
||||||
|
}));
|
||||||
|
|
||||||
return hydrateMessagesWithAttachments(db, messagesWithColumnsHydrated);
|
return hydrateMessagesWithAttachments(db, messagesWithColumnsHydrated);
|
||||||
}
|
}
|
||||||
|
@ -142,7 +152,13 @@ export function getAttachmentReferencesForMessages(
|
||||||
|
|
||||||
function hydrateMessagesWithAttachments(
|
function hydrateMessagesWithAttachments(
|
||||||
db: ReadableDB,
|
db: ReadableDB,
|
||||||
messagesWithoutAttachments: Array<MessageType>
|
messagesWithoutAttachments: Array<
|
||||||
|
MessageType & {
|
||||||
|
hasAttachments: boolean;
|
||||||
|
hasVisualMediaAttachments: boolean;
|
||||||
|
hasFileAttachments: boolean;
|
||||||
|
}
|
||||||
|
>
|
||||||
): Array<MessageType> {
|
): Array<MessageType> {
|
||||||
const attachmentReferencesForAllMessages = getAttachmentReferencesForMessages(
|
const attachmentReferencesForAllMessages = getAttachmentReferencesForMessages(
|
||||||
db,
|
db,
|
||||||
|
@ -153,10 +169,56 @@ function hydrateMessagesWithAttachments(
|
||||||
'messageId'
|
'messageId'
|
||||||
);
|
);
|
||||||
|
|
||||||
return messagesWithoutAttachments.map(msg => {
|
return messagesWithoutAttachments.map(msgWithExtraFields => {
|
||||||
|
const {
|
||||||
|
hasAttachments,
|
||||||
|
hasFileAttachments,
|
||||||
|
hasVisualMediaAttachments,
|
||||||
|
...msg
|
||||||
|
} = msgWithExtraFields;
|
||||||
|
|
||||||
const attachmentReferences = attachmentReferencesByMessage[msg.id] ?? [];
|
const attachmentReferences = attachmentReferencesByMessage[msg.id] ?? [];
|
||||||
|
|
||||||
if (!attachmentReferences.length) {
|
if (!attachmentReferences.length) {
|
||||||
return msg;
|
if (msg.attachments?.length) {
|
||||||
|
// legacy message, attachments still on JSON
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.isErased || msg.deletedForEveryone || msg.isViewOnce) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.type !== 'incoming' && msg.type !== 'outgoing') {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!hasAttachments &&
|
||||||
|
!hasFileAttachments &&
|
||||||
|
!hasVisualMediaAttachments
|
||||||
|
) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn(
|
||||||
|
`Retrieved message that should have attachments but missing message_attachment rows, timestamp: ${msg.timestamp}`
|
||||||
|
);
|
||||||
|
// Add an empty attachment to the message to enable backfilling in the UI
|
||||||
|
return {
|
||||||
|
...msg,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
error: true,
|
||||||
|
size: 0,
|
||||||
|
width: hasVisualMediaAttachments ? 150 : undefined,
|
||||||
|
height: hasVisualMediaAttachments ? 150 : undefined,
|
||||||
|
contentType: hasVisualMediaAttachments
|
||||||
|
? IMAGE_PNG
|
||||||
|
: APPLICATION_OCTET_STREAM,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachmentsByEditHistoryIndex = groupBy(
|
const attachmentsByEditHistoryIndex = groupBy(
|
||||||
|
|
|
@ -688,4 +688,28 @@ describe('normalizes attachment references', () => {
|
||||||
omit(attachmentWithoutKey, 'randomKey')
|
omit(attachmentWithoutKey, 'randomKey')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('adds a placeholder attachment when attachments had been deleted', async () => {
|
||||||
|
const message = composeMessage(Date.now(), {
|
||||||
|
attachments: [composeAttachment(), composeAttachment()],
|
||||||
|
});
|
||||||
|
|
||||||
|
await DataWriter.saveMessage(message, {
|
||||||
|
forceSave: true,
|
||||||
|
ourAci: generateAci(),
|
||||||
|
postSaveUpdates: () => Promise.resolve(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await DataWriter._testOnlyRemoveMessageAttachments(message.timestamp);
|
||||||
|
|
||||||
|
const messageFromDB = await DataReader.getMessageById(message.id);
|
||||||
|
assert(messageFromDB, 'message was saved');
|
||||||
|
assert.deepEqual(messageFromDB.attachments?.[0], {
|
||||||
|
size: 0,
|
||||||
|
contentType: IMAGE_PNG,
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
error: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue