Move receipts and view/read syncs to new syncTasks system

Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
automated-signal 2024-06-17 19:36:57 -05:00 committed by GitHub
parent 949104c316
commit b95dd1a70f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1242 additions and 612 deletions

View file

@ -2,13 +2,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer as ipc } from 'electron';
import PQueue from 'p-queue';
import { batch } from 'react-redux';
import { has, get, groupBy, isTypedArray, last, map, omit } from 'lodash';
import { deleteExternalFiles } from '../types/Conversation';
import { expiringMessagesDeletionService } from '../services/expiringMessagesDeletion';
import { update as updateExpiringMessagesService } from '../services/expiringMessagesDeletion';
import { tapToViewMessagesDeletionService } from '../services/tapToViewMessagesDeletionService';
import * as Bytes from '../Bytes';
import { createBatcher } from '../util/batcher';
@ -24,12 +22,7 @@ import * as Errors from '../types/errors';
import type { StoredJob } from '../jobs/types';
import { formatJobForInsert } from '../jobs/formatJobForInsert';
import {
cleanupMessage,
cleanupMessageFromMemory,
deleteMessageData,
} from '../util/cleanup';
import { drop } from '../util/drop';
import { cleanupMessages } from '../util/cleanup';
import { ipcInvoke, doShutdown } from './channels';
import type {
@ -60,12 +53,12 @@ import type {
KyberPreKeyType,
StoredKyberPreKeyType,
} from './Interface';
import { MINUTE } from '../util/durations';
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';
@ -104,6 +97,8 @@ const exclusiveInterface: ClientExclusiveInterface = {
removeConversation,
searchMessages,
removeMessage,
removeMessages,
getRecentStoryReplies,
getOlderMessagesByConversation,
@ -125,8 +120,6 @@ const exclusiveInterface: ClientExclusiveInterface = {
type ClientOverridesType = ClientExclusiveInterface &
Pick<
ServerInterface,
| 'removeMessage'
| 'removeMessages'
| 'saveAttachmentDownloadJob'
| 'saveMessage'
| 'saveMessages'
@ -142,8 +135,6 @@ const channels: ServerInterface = new Proxy({} as ServerInterface, {
const clientExclusiveOverrides: ClientOverridesType = {
...exclusiveInterface,
removeMessage,
removeMessages,
saveAttachmentDownloadJob,
saveMessage,
saveMessages,
@ -562,7 +553,7 @@ async function saveMessage(
softAssert(isValidUuid(id), 'saveMessage: messageId is not a UUID');
void expiringMessagesDeletionService.update();
void updateExpiringMessagesService();
void tapToViewMessagesDeletionService.update();
return id;
@ -577,26 +568,39 @@ async function saveMessages(
options
);
void expiringMessagesDeletionService.update();
void updateExpiringMessagesService();
void tapToViewMessagesDeletionService.update();
return result;
}
async function removeMessage(id: string): Promise<void> {
async function removeMessage(
id: string,
options: {
singleProtoJobQueue: SingleProtoJobQueue;
fromSync?: boolean;
}
): Promise<void> {
const message = await channels.getMessageById(id);
// Note: It's important to have a fully database-hydrated model to delete here because
// it needs to delete all associated on-disk files along with the database delete.
if (message) {
await channels.removeMessage(id);
await cleanupMessage(message);
await cleanupMessages([message], {
...options,
markCallHistoryDeleted: dataInterface.markCallHistoryDeleted,
});
}
}
export async function deleteAndCleanup(
messages: Array<MessageAttributesType>,
logId: string
logId: string,
options: {
fromSync?: boolean;
singleProtoJobQueue: SingleProtoJobQueue;
}
): Promise<void> {
const ids = messages.map(message => message.id);
@ -604,37 +608,26 @@ export async function deleteAndCleanup(
await channels.removeMessages(ids);
log.info(`deleteAndCleanup/${logId}: Cleanup for ${ids.length} messages...`);
await _cleanupMessages(messages);
await cleanupMessages(messages, {
...options,
markCallHistoryDeleted: dataInterface.markCallHistoryDeleted,
});
log.info(`deleteAndCleanup/${logId}: Complete`);
}
async function _cleanupMessages(
messages: ReadonlyArray<MessageAttributesType>
): Promise<void> {
// First, remove messages from memory, so we can batch the updates in redux
batch(() => {
messages.forEach(message => cleanupMessageFromMemory(message));
});
// Then, handle any asynchronous actions (e.g. deleting data from disk)
const queue = new PQueue({ concurrency: 3, timeout: MINUTE * 30 });
drop(
queue.addAll(
messages.map(
(message: MessageAttributesType) => async () =>
deleteMessageData(message)
)
)
);
await queue.onIdle();
}
async function removeMessages(
messageIds: ReadonlyArray<string>
messageIds: ReadonlyArray<string>,
options: {
fromSync?: boolean;
singleProtoJobQueue: SingleProtoJobQueue;
}
): Promise<void> {
const messages = await channels.getMessagesById(messageIds);
await _cleanupMessages(messages);
await cleanupMessages(messages, {
...options,
markCallHistoryDeleted: dataInterface.markCallHistoryDeleted,
});
await channels.removeMessages(messageIds);
}
@ -686,9 +679,13 @@ async function removeMessagesInConversation(
{
logId,
receivedAt,
singleProtoJobQueue,
fromSync,
}: {
fromSync?: boolean;
logId: string;
receivedAt?: number;
singleProtoJobQueue: SingleProtoJobQueue;
}
): Promise<void> {
let messages;
@ -713,7 +710,7 @@ async function removeMessagesInConversation(
}
// eslint-disable-next-line no-await-in-loop
await deleteAndCleanup(messages, logId);
await deleteAndCleanup(messages, logId, { fromSync, singleProtoJobQueue });
} while (messages.length > 0);
}