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;
|
||||
cleanExpiredGroupCallRingCancellations(): void;
|
||||
|
||||
_testOnlyRemoveMessageAttachments(timestamp: number): void;
|
||||
};
|
||||
|
||||
// Adds a database argument
|
||||
|
|
|
@ -705,6 +705,8 @@ export const DataWriter: ServerWritableInterface = {
|
|||
disableFSync,
|
||||
enableFSyncAndCheckpoint,
|
||||
|
||||
_testOnlyRemoveMessageAttachments,
|
||||
|
||||
// Server-only
|
||||
|
||||
removeKnownStickers,
|
||||
|
@ -2720,6 +2722,17 @@ function saveMessageAttachment({
|
|||
).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(
|
||||
db: WritableDB,
|
||||
message: ReadonlyDeep<MessageType>,
|
||||
|
|
|
@ -23,13 +23,20 @@ import {
|
|||
sql,
|
||||
sqlJoin,
|
||||
} from './util';
|
||||
import type { AttachmentType } from '../types/Attachment';
|
||||
import { IMAGE_JPEG, stringToMIMEType } from '../types/MIME';
|
||||
import { type AttachmentType } from '../types/Attachment';
|
||||
import {
|
||||
APPLICATION_OCTET_STREAM,
|
||||
IMAGE_JPEG,
|
||||
IMAGE_PNG,
|
||||
stringToMIMEType,
|
||||
} from '../types/MIME';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import type { MessageAttributesType } from '../model-types';
|
||||
import { createLogger } from '../logging/log';
|
||||
|
||||
export const ROOT_MESSAGE_ATTACHMENT_EDIT_HISTORY_INDEX = -1;
|
||||
|
||||
const log = createLogger('hydrateMessage');
|
||||
function toBoolean(value: number | null): boolean | undefined {
|
||||
if (value == null) {
|
||||
return undefined;
|
||||
|
@ -48,9 +55,12 @@ export function hydrateMessages(
|
|||
db: ReadableDB,
|
||||
unhydratedMessages: Array<MessageTypeUnhydrated>
|
||||
): Array<MessageType> {
|
||||
const messagesWithColumnsHydrated = unhydratedMessages.map(
|
||||
hydrateMessageTableColumns
|
||||
);
|
||||
const messagesWithColumnsHydrated = unhydratedMessages.map(msg => ({
|
||||
...hydrateMessageTableColumns(msg),
|
||||
hasAttachments: msg.hasAttachments === 1,
|
||||
hasFileAttachments: msg.hasFileAttachments === 1,
|
||||
hasVisualMediaAttachments: msg.hasVisualMediaAttachments === 1,
|
||||
}));
|
||||
|
||||
return hydrateMessagesWithAttachments(db, messagesWithColumnsHydrated);
|
||||
}
|
||||
|
@ -142,7 +152,13 @@ export function getAttachmentReferencesForMessages(
|
|||
|
||||
function hydrateMessagesWithAttachments(
|
||||
db: ReadableDB,
|
||||
messagesWithoutAttachments: Array<MessageType>
|
||||
messagesWithoutAttachments: Array<
|
||||
MessageType & {
|
||||
hasAttachments: boolean;
|
||||
hasVisualMediaAttachments: boolean;
|
||||
hasFileAttachments: boolean;
|
||||
}
|
||||
>
|
||||
): Array<MessageType> {
|
||||
const attachmentReferencesForAllMessages = getAttachmentReferencesForMessages(
|
||||
db,
|
||||
|
@ -153,12 +169,58 @@ function hydrateMessagesWithAttachments(
|
|||
'messageId'
|
||||
);
|
||||
|
||||
return messagesWithoutAttachments.map(msg => {
|
||||
return messagesWithoutAttachments.map(msgWithExtraFields => {
|
||||
const {
|
||||
hasAttachments,
|
||||
hasFileAttachments,
|
||||
hasVisualMediaAttachments,
|
||||
...msg
|
||||
} = msgWithExtraFields;
|
||||
|
||||
const attachmentReferences = attachmentReferencesByMessage[msg.id] ?? [];
|
||||
|
||||
if (!attachmentReferences.length) {
|
||||
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(
|
||||
attachmentReferences,
|
||||
'editHistoryIndex'
|
||||
|
|
|
@ -688,4 +688,28 @@ describe('normalizes attachment references', () => {
|
|||
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