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);
}