Use minimal replacement class for MessageModel

This commit is contained in:
Scott Nonnenberg 2025-01-10 08:18:32 +10:00 committed by GitHub
parent 6b00cf756e
commit f846678b90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
95 changed files with 3919 additions and 4457 deletions

View file

@ -2,30 +2,34 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer as ipc } from 'electron';
import { groupBy, isTypedArray, last, map, omit } from 'lodash';
import type { ReadonlyDeep } from 'type-fest';
import { deleteExternalFiles } from '../types/Conversation';
import { update as updateExpiringMessagesService } from '../services/expiringMessagesDeletion';
import { tapToViewMessagesDeletionService } from '../services/tapToViewMessagesDeletionService';
// Note: nothing imported here can come back and require Client.ts, and that includes
// their imports too. That circularity causes problems. Anything that would do that needs
// to be passed in, like cleanupMessages below.
import * as Bytes from '../Bytes';
import * as log from '../logging/log';
import * as Errors from '../types/errors';
import { deleteExternalFiles } from '../types/Conversation';
import { createBatcher } from '../util/batcher';
import { assertDev, softAssert } from '../util/assert';
import { mapObjectWithSpec } from '../util/mapObjectWithSpec';
import type { ObjectMappingSpecType } from '../util/mapObjectWithSpec';
import { cleanDataForIpc } from './cleanDataForIpc';
import type { AciString, ServiceIdString } from '../types/ServiceId';
import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
import * as log from '../logging/log';
import { isValidUuid, isValidUuidV7 } from '../util/isValidUuid';
import * as Errors from '../types/errors';
import type { StoredJob } from '../jobs/types';
import { formatJobForInsert } from '../jobs/formatJobForInsert';
import { cleanupMessages } from '../util/cleanup';
import { AccessType, ipcInvoke, doShutdown, removeDB } from './channels';
import { getMessageIdForLogging } from '../util/idForLogging';
import { incrementMessageCounter } from '../util/incrementMessageCounter';
import { generateSnippetAroundMention } from '../util/search';
import { drop } from '../util/drop';
import type { ObjectMappingSpecType } from '../util/mapObjectWithSpec';
import type { AciString, ServiceIdString } from '../types/ServiceId';
import type { StoredJob } from '../jobs/types';
import type {
ClientInterfaceWrap,
AdjacentMessagesByConversationOptionsType,
@ -58,12 +62,8 @@ import type {
ClientOnlyReadableInterface,
ClientOnlyWritableInterface,
} from './Interface';
import { getMessageIdForLogging } from '../util/idForLogging';
import type { MessageAttributesType } from '../model-types';
import { incrementMessageCounter } from '../util/incrementMessageCounter';
import { generateSnippetAroundMention } from '../util/search';
import type { AttachmentDownloadJobType } from '../types/AttachmentDownload';
import type { SingleProtoJobQueue } from '../jobs/singleProtoJobQueue';
const ERASE_SQL_KEY = 'erase-sql-key';
const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
@ -121,6 +121,10 @@ const clientOnlyWritable: ClientOnlyWritableInterface = {
removeMessage,
removeMessages,
saveMessage,
saveMessages,
saveMessagesIndividually,
// Client-side only
flushUpdateConversationBatcher,
@ -137,17 +141,12 @@ const clientOnlyWritable: ClientOnlyWritableInterface = {
type ClientOverridesType = ClientOnlyWritableInterface &
Pick<
ClientInterfaceWrap<ServerWritableDirectInterface>,
| 'saveAttachmentDownloadJob'
| 'saveMessage'
| 'saveMessages'
| 'updateConversations'
'saveAttachmentDownloadJob' | 'updateConversations'
>;
const clientOnlyWritableOverrides: ClientOverridesType = {
...clientOnlyWritable,
saveAttachmentDownloadJob,
saveMessage,
saveMessages,
updateConversations,
};
@ -595,41 +594,76 @@ async function searchMessages({
async function saveMessage(
data: ReadonlyDeep<MessageType>,
options: {
jobToInsert?: Readonly<StoredJob>;
{
forceSave,
jobToInsert,
ourAci,
postSaveUpdates,
}: {
forceSave?: boolean;
jobToInsert?: Readonly<StoredJob>;
ourAci: AciString;
postSaveUpdates: () => Promise<void>;
}
): Promise<string> {
const id = await writableChannel.saveMessage(_cleanMessageData(data), {
...options,
jobToInsert: options.jobToInsert && formatJobForInsert(options.jobToInsert),
forceSave,
jobToInsert: jobToInsert && formatJobForInsert(jobToInsert),
ourAci,
});
softAssert(
// Older messages still have `UUIDv4` so don't log errors when encountering
// it.
(!options.forceSave && isValidUuid(id)) || isValidUuidV7(id),
(!forceSave && isValidUuid(id)) || isValidUuidV7(id),
'saveMessage: messageId is not a UUID'
);
void updateExpiringMessagesService();
void tapToViewMessagesDeletionService.update();
drop(postSaveUpdates?.());
return id;
}
async function saveMessages(
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
options: { forceSave?: boolean; ourAci: AciString }
{
forceSave,
ourAci,
postSaveUpdates,
}: {
forceSave?: boolean;
ourAci: AciString;
postSaveUpdates: () => Promise<void>;
}
): Promise<Array<string>> {
const result = await writableChannel.saveMessages(
arrayOfMessages.map(message => _cleanMessageData(message)),
options
{ forceSave, ourAci }
);
void updateExpiringMessagesService();
void tapToViewMessagesDeletionService.update();
drop(postSaveUpdates?.());
return result;
}
async function saveMessagesIndividually(
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
{
forceSave,
ourAci,
postSaveUpdates,
}: {
forceSave?: boolean;
ourAci: AciString;
postSaveUpdates: () => Promise<void>;
}
): Promise<{ failedIndices: Array<number> }> {
const result = await writableChannel.saveMessagesIndividually(
arrayOfMessages,
{ forceSave, ourAci }
);
drop(postSaveUpdates?.());
return result;
}
@ -637,7 +671,10 @@ async function saveMessages(
async function removeMessage(
id: string,
options: {
singleProtoJobQueue: SingleProtoJobQueue;
cleanupMessages: (
messages: ReadonlyArray<MessageAttributesType>,
options: { fromSync?: boolean }
) => Promise<void>;
fromSync?: boolean;
}
): Promise<void> {
@ -647,9 +684,8 @@ async function removeMessage(
// it needs to delete all associated on-disk files along with the database delete.
if (message) {
await writableChannel.removeMessage(id);
await cleanupMessages([message], {
...options,
markCallHistoryDeleted: DataWriter.markCallHistoryDeleted,
await options.cleanupMessages([message], {
fromSync: options.fromSync,
});
}
}
@ -659,7 +695,10 @@ export async function deleteAndCleanup(
logId: string,
options: {
fromSync?: boolean;
singleProtoJobQueue: SingleProtoJobQueue;
cleanupMessages: (
messages: ReadonlyArray<MessageAttributesType>,
options: { fromSync?: boolean }
) => Promise<void>;
}
): Promise<void> {
const ids = messages.map(message => message.id);
@ -668,9 +707,8 @@ export async function deleteAndCleanup(
await writableChannel.removeMessages(ids);
log.info(`deleteAndCleanup/${logId}: Cleanup for ${ids.length} messages...`);
await cleanupMessages(messages, {
...options,
markCallHistoryDeleted: DataWriter.markCallHistoryDeleted,
await options.cleanupMessages(messages, {
fromSync: Boolean(options.fromSync),
});
log.info(`deleteAndCleanup/${logId}: Complete`);
@ -680,13 +718,15 @@ async function removeMessages(
messageIds: ReadonlyArray<string>,
options: {
fromSync?: boolean;
singleProtoJobQueue: SingleProtoJobQueue;
cleanupMessages: (
messages: ReadonlyArray<MessageAttributesType>,
options: { fromSync?: boolean }
) => Promise<void>;
}
): Promise<void> {
const messages = await readableChannel.getMessagesById(messageIds);
await cleanupMessages(messages, {
...options,
markCallHistoryDeleted: DataWriter.markCallHistoryDeleted,
await options.cleanupMessages(messages, {
fromSync: Boolean(options.fromSync),
});
await writableChannel.removeMessages(messageIds);
}
@ -743,15 +783,18 @@ async function getConversationRangeCenteredOnMessage(
async function removeMessagesInConversation(
conversationId: string,
{
cleanupMessages,
fromSync,
logId,
receivedAt,
singleProtoJobQueue,
fromSync,
}: {
cleanupMessages: (
messages: ReadonlyArray<MessageAttributesType>,
options: { fromSync?: boolean | undefined }
) => Promise<void>;
fromSync?: boolean;
logId: string;
receivedAt?: number;
singleProtoJobQueue: SingleProtoJobQueue;
}
): Promise<void> {
let messages;
@ -776,7 +819,7 @@ async function removeMessagesInConversation(
}
// eslint-disable-next-line no-await-in-loop
await deleteAndCleanup(messages, logId, { fromSync, singleProtoJobQueue });
await deleteAndCleanup(messages, logId, { fromSync, cleanupMessages });
} while (messages.length > 0);
}

View file

@ -44,7 +44,6 @@ import type {
} from '../types/GroupSendEndorsements';
import type { SyncTaskType } from '../util/syncTasks';
import type { AttachmentBackupJobType } from '../types/AttachmentBackup';
import type { SingleProtoJobQueue } from '../jobs/singleProtoJobQueue';
export type ReadableDB = Database & { __readable_db: never };
export type WritableDB = ReadableDB & { __writable_db: never };
@ -749,23 +748,6 @@ type WritableInterface = {
replaceAllEndorsementsForGroup: (data: GroupSendEndorsementsData) => void;
deleteAllEndorsementsForGroup: (groupId: string) => void;
saveMessage: (
data: ReadonlyDeep<MessageType>,
options: {
jobToInsert?: StoredJob;
forceSave?: boolean;
ourAci: AciString;
}
) => string;
saveMessages: (
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
options: { forceSave?: boolean; ourAci: AciString }
) => Array<string>;
saveMessagesIndividually: (
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
options: { forceSave?: boolean; ourAci: AciString }
) => { failedIndices: Array<number> };
getUnreadByConversationAndMarkRead: (options: {
conversationId: string;
includeStoryReplies: boolean;
@ -1047,6 +1029,22 @@ export type ServerWritableDirectInterface = WritableInterface & {
updateConversation: (data: ConversationType) => void;
removeConversation: (id: Array<string> | string) => void;
saveMessage: (
data: ReadonlyDeep<MessageType>,
options: {
jobToInsert?: StoredJob;
forceSave?: boolean;
ourAci: AciString;
}
) => string;
saveMessages: (
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
options: { forceSave?: boolean; ourAci: AciString }
) => Array<string>;
saveMessagesIndividually: (
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
options: { forceSave?: boolean; ourAci: AciString }
) => { failedIndices: Array<number> };
removeMessage: (id: string) => void;
removeMessages: (ids: ReadonlyArray<string>) => void;
@ -1134,18 +1132,49 @@ export type ClientOnlyWritableInterface = ClientInterfaceWrap<{
removeConversation: (id: string) => void;
flushUpdateConversationBatcher: () => void;
saveMessage: (
data: ReadonlyDeep<MessageType>,
options: {
jobToInsert?: StoredJob;
forceSave?: boolean;
ourAci: AciString;
postSaveUpdates: () => Promise<void>;
}
) => string;
saveMessages: (
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
options: {
forceSave?: boolean;
ourAci: AciString;
postSaveUpdates: () => Promise<void>;
}
) => Array<string>;
saveMessagesIndividually: (
arrayOfMessages: ReadonlyArray<ReadonlyDeep<MessageType>>,
options: {
forceSave?: boolean;
ourAci: AciString;
postSaveUpdates: () => Promise<void>;
}
) => { failedIndices: Array<number> };
removeMessage: (
id: string,
options: {
fromSync?: boolean;
singleProtoJobQueue: SingleProtoJobQueue;
cleanupMessages: (
messages: ReadonlyArray<MessageAttributesType>,
options: { fromSync?: boolean | undefined }
) => Promise<void>;
}
) => void;
removeMessages: (
ids: ReadonlyArray<string>,
options: {
fromSync?: boolean;
singleProtoJobQueue: SingleProtoJobQueue;
cleanupMessages: (
messages: ReadonlyArray<MessageAttributesType>,
options: { fromSync?: boolean | undefined }
) => Promise<void>;
}
) => void;
@ -1170,10 +1199,13 @@ export type ClientOnlyWritableInterface = ClientInterfaceWrap<{
removeMessagesInConversation: (
conversationId: string,
options: {
cleanupMessages: (
messages: ReadonlyArray<MessageAttributesType>,
options: { fromSync?: boolean | undefined }
) => Promise<void>;
fromSync?: boolean;
logId: string;
receivedAt?: number;
singleProtoJobQueue: SingleProtoJobQueue;
}
) => void;
removeOtherData: () => void;