Simplify copyQuote logic

This commit is contained in:
Fedor Indutny 2024-09-04 15:59:39 -07:00 committed by GitHub
parent 0d5a480c1b
commit 68223aaa12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 106 additions and 167 deletions

View file

@ -4,16 +4,14 @@
import { omit } from 'lodash';
import * as log from '../logging/log';
import { DataReader, DataWriter } from '../sql/Client';
import type { QuotedMessageType } from '../model-types';
import type { MessageModel } from '../models/messages';
import type { MessageAttributesType } from '../model-types.d';
import { SignalService } from '../protobuf';
import { isGiftBadge, isTapToView } from '../state/selectors/message';
import type { ProcessedQuote } from '../textsecure/Types';
import { IMAGE_JPEG } from '../types/MIME';
import { strictAssert } from '../util/assert';
import { getQuoteBodyText } from '../util/getQuoteBodyText';
import { find } from '../util/iterables';
import { isQuoteAMatch, messageHasPaymentEvent } from './helpers';
import * as Errors from '../types/errors';
import { isDownloadable } from '../types/Attachment';
@ -40,32 +38,13 @@ export const copyFromQuotedMessage = async (
messageId: '',
};
const inMemoryMessages = window.MessageCache.__DEPRECATED$filterBySentAt(id);
const matchingMessage = find(inMemoryMessages, item =>
isQuoteAMatch(item.attributes, conversationId, result)
const queryMessage = await window.MessageCache.findBySentAt(id, attributes =>
isQuoteAMatch(attributes, conversationId, result)
);
let queryMessage: undefined | MessageModel;
if (matchingMessage) {
queryMessage = matchingMessage;
} else {
log.info('copyFromQuotedMessage: db lookup needed', id);
const messages = await DataReader.getMessagesBySentAt(id);
const found = messages.find(item =>
isQuoteAMatch(item, conversationId, result)
);
if (!found) {
result.referencedMessageNotFound = true;
return result;
}
queryMessage = window.MessageCache.__DEPRECATED$register(
found.id,
found,
'copyFromQuotedMessage'
);
if (queryMessage == null) {
result.referencedMessageNotFound = true;
return result;
}
if (queryMessage) {
@ -76,18 +55,20 @@ export const copyFromQuotedMessage = async (
};
export const copyQuoteContentFromOriginal = async (
originalMessage: MessageModel,
providedOriginalMessage: MessageAttributesType,
quote: QuotedMessageType
): Promise<void> => {
let originalMessage = providedOriginalMessage;
const { attachments } = quote;
const firstAttachment = attachments ? attachments[0] : undefined;
if (messageHasPaymentEvent(originalMessage.attributes)) {
if (messageHasPaymentEvent(originalMessage)) {
// eslint-disable-next-line no-param-reassign
quote.payment = originalMessage.get('payment');
quote.payment = originalMessage.payment;
}
if (isTapToView(originalMessage.attributes)) {
if (isTapToView(originalMessage)) {
// eslint-disable-next-line no-param-reassign
quote.text = undefined;
// eslint-disable-next-line no-param-reassign
@ -102,7 +83,7 @@ export const copyQuoteContentFromOriginal = async (
return;
}
const isMessageAGiftBadge = isGiftBadge(originalMessage.attributes);
const isMessageAGiftBadge = isGiftBadge(originalMessage);
if (isMessageAGiftBadge !== quote.isGiftBadge) {
log.warn(
`copyQuoteContentFromOriginal: Quote.isGiftBadge: ${quote.isGiftBadge}, isGiftBadge(message): ${isMessageAGiftBadge}`
@ -123,30 +104,20 @@ export const copyQuoteContentFromOriginal = async (
quote.isViewOnce = false;
// eslint-disable-next-line no-param-reassign
quote.text = getQuoteBodyText(originalMessage.attributes, quote.id);
quote.text = getQuoteBodyText(originalMessage, quote.id);
// eslint-disable-next-line no-param-reassign
quote.bodyRanges = originalMessage.attributes.bodyRanges;
quote.bodyRanges = originalMessage.bodyRanges;
if (!firstAttachment || !firstAttachment.contentType) {
return;
}
try {
const schemaVersion = originalMessage.get('schemaVersion');
if (
schemaVersion &&
schemaVersion < window.Signal.Types.Message.VERSION_NEEDED_FOR_DISPLAY
) {
const upgradedMessage =
await window.Signal.Migrations.upgradeMessageSchema(
originalMessage.attributes
);
originalMessage.set(upgradedMessage);
await DataWriter.saveMessage(upgradedMessage, {
ourAci: window.textsecure.storage.user.getCheckedAci(),
});
}
originalMessage = await window.MessageCache.upgradeSchema(
originalMessage,
window.Signal.Types.Message.VERSION_NEEDED_FOR_DISPLAY
);
} catch (error) {
log.error(
'Problem upgrading message quoted message from database',
@ -155,7 +126,12 @@ export const copyQuoteContentFromOriginal = async (
return;
}
const queryAttachments = originalMessage.get('attachments') || [];
const {
attachments: queryAttachments = [],
preview: queryPreview = [],
sticker,
} = originalMessage;
if (queryAttachments.length > 0) {
const queryFirst = queryAttachments[0];
const { thumbnail } = queryFirst;
@ -175,7 +151,6 @@ export const copyQuoteContentFromOriginal = async (
}
}
const queryPreview = originalMessage.get('preview') || [];
if (queryPreview.length > 0) {
const queryFirst = queryPreview[0];
const { image } = queryFirst;
@ -188,7 +163,6 @@ export const copyQuoteContentFromOriginal = async (
}
}
const sticker = originalMessage.get('sticker');
if (sticker && sticker.data && sticker.data.path) {
firstAttachment.thumbnail = {
...sticker.data,

View file

@ -1932,25 +1932,15 @@ export class ConversationModel extends window.Backbone
`cleanAttributes: Eliminated ${eliminated} messages without an id`
);
}
const ourAci = window.textsecure.storage.user.getCheckedAci();
let upgraded = 0;
const hydrated = await Promise.all(
present.map(async message => {
const { schemaVersion } = message;
const model = window.MessageCache.__DEPRECATED$register(
message.id,
const upgradedMessage = await window.MessageCache.upgradeSchema(
message,
'cleanAttributes'
Message.VERSION_NEEDED_FOR_DISPLAY
);
let upgradedMessage = message;
if ((schemaVersion || 0) < Message.VERSION_NEEDED_FOR_DISPLAY) {
// Yep, we really do want to wait for each of these
upgradedMessage = await upgradeMessageSchema(model.attributes);
model.set(upgradedMessage);
await DataWriter.saveMessage(upgradedMessage, { ourAci });
if (upgradedMessage !== message) {
upgraded += 1;
}

View file

@ -18,7 +18,7 @@ import type {
MessageAttributesType,
MessageReactionType,
} from '../model-types.d';
import { filter, find, map, repeat, zipObject } from '../util/iterables';
import { filter, map, repeat, zipObject } from '../util/iterables';
import * as GoogleChrome from '../util/GoogleChrome';
import type { DeleteAttributesType } from '../messageModifiers/Deletes';
import type { SentEventData } from '../textsecure/messageReceiverEvents';
@ -454,25 +454,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
quoteAttachment: quote.attachments.at(0),
})
) {
const inMemoryMessages = window.MessageCache.__DEPRECATED$filterBySentAt(
Number(sentAt)
const matchingMessage = await window.MessageCache.findBySentAt(
Number(sentAt),
attributes =>
isQuoteAMatch(attributes, this.get('conversationId'), quote)
);
let matchingMessage = find(inMemoryMessages, message =>
isQuoteAMatch(message.attributes, this.get('conversationId'), quote)
);
if (!matchingMessage) {
const messages = await DataReader.getMessagesBySentAt(Number(sentAt));
const found = messages.find(item =>
isQuoteAMatch(item, this.get('conversationId'), quote)
);
if (found) {
matchingMessage = window.MessageCache.__DEPRECATED$register(
found.id,
found,
'doubleCheckMissingQuoteReference'
);
}
}
if (!matchingMessage) {
log.info(
@ -1737,11 +1723,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
const storyQuote = storyQuotes.find(candidateQuote => {
const sendStateByConversationId =
candidateQuote.get('sendStateByConversationId') || {};
candidateQuote.sendStateByConversationId || {};
const sendState = sendStateByConversationId[sender.id];
const storyQuoteIsFromSelf =
candidateQuote.get('sourceServiceId') ===
candidateQuote.sourceServiceId ===
window.storage.user.getCheckedAci();
if (!storyQuoteIsFromSelf) {
@ -1776,9 +1762,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}
if (storyQuote) {
const storyDistributionListId = storyQuote.get(
'storyDistributionListId'
);
const { storyDistributionListId } = storyQuote;
if (storyDistributionListId) {
const storyDistribution =
@ -1904,7 +1888,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
});
if (storyQuote) {
await this.hydrateStoryContext(storyQuote.attributes, {
await this.hydrateStoryContext(storyQuote, {
shouldSave: true,
});
}
@ -1940,10 +1924,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
// expiration timer
if (isGroupStoryReply && storyQuote) {
message.set({
expireTimer: storyQuote.get('expireTimer'),
expirationStartTimestamp: storyQuote.get(
'expirationStartTimestamp'
),
expireTimer: storyQuote.expireTimer,
expirationStartTimestamp: storyQuote.expirationStartTimestamp,
});
}

View file

@ -4,7 +4,10 @@
import cloneDeep from 'lodash/cloneDeep';
import { throttle } from 'lodash';
import LRU from 'lru-cache';
import type { MessageAttributesType } from '../model-types.d';
import type {
MessageAttributesType,
ReadonlyMessageAttributesType,
} from '../model-types.d';
import type { MessageModel } from '../models/messages';
import { DataReader, DataWriter } from '../sql/Client';
import * as Errors from '../types/errors';
@ -14,7 +17,6 @@ import { getMessageConversation } from '../util/getMessageConversation';
import { getMessageModelLogger } from '../util/MessageModelLogger';
import { getSenderIdentifier } from '../util/getSenderIdentifier';
import { isNotNil } from '../util/isNotNil';
import { map } from '../util/iterables';
import { softAssert, strictAssert } from '../util/assert';
import { isStory } from '../messages/helpers';
import type { SendStateByConversationId } from '../messages/MessageSendState';
@ -451,11 +453,47 @@ export class MessageCache {
return this.toModel(data);
}
public async upgradeSchema(
attributes: MessageAttributesType,
minSchemaVersion: number
): Promise<MessageAttributesType> {
const { schemaVersion } = attributes;
if (!schemaVersion || schemaVersion >= minSchemaVersion) {
return attributes;
}
const upgradedAttributes =
await window.Signal.Migrations.upgradeMessageSchema(attributes);
await this.setAttributes({
messageId: upgradedAttributes.id,
messageAttributes: upgradedAttributes,
skipSaveToDatabase: false,
});
return upgradedAttributes;
}
// Finds a message in the cache by sentAt/timestamp
public __DEPRECATED$filterBySentAt(sentAt: number): Iterable<MessageModel> {
public async findBySentAt(
sentAt: number,
predicate: (attributes: ReadonlyMessageAttributesType) => boolean
): Promise<MessageAttributesType | undefined> {
const items = this.state.messageIdsBySentAt.get(sentAt) ?? [];
const attrs = items.map(id => this.accessAttributes(id)).filter(isNotNil);
return map(attrs, data => this.toModel(data));
const inMemory = items
.map(id => this.accessAttributes(id))
.filter(isNotNil)
.find(predicate);
if (inMemory != null) {
return inMemory;
}
log.info(`findBySentAt(${sentAt}): db lookup needed`);
const allOnDisk = await DataReader.getMessagesBySentAt(sentAt);
const onDisk = allOnDisk.find(predicate);
if (onDisk != null) {
this.addMessageToCache(onDisk);
}
return onDisk;
}
// Marks cached model as "should be stale" to discourage continued use.

View file

@ -6,7 +6,7 @@ import type { ReadonlyDeep } from 'type-fest';
import * as log from '../../logging/log';
import * as Errors from '../../types/errors';
import { DataReader, DataWriter } from '../../sql/Client';
import { DataReader } from '../../sql/Client';
import {
CONVERSATION_UNLOADED,
MESSAGE_CHANGED,
@ -17,6 +17,7 @@ import { VERSION_NEEDED_FOR_DISPLAY } from '../../types/Message2';
import { isDownloading, hasFailed } from '../../types/Attachment';
import { isNotNil } from '../../util/isNotNil';
import { getLocalAttachmentUrl } from '../../util/getLocalAttachmentUrl';
import { getMessageIdForLogging } from '../../util/idForLogging';
import { useBoundActions } from '../../hooks/useBoundActions';
import type { AttachmentType } from '../../types/Attachment';
@ -191,34 +192,21 @@ function _cleanFileAttachments(
async function _upgradeMessages(
messages: ReadonlyArray<MessageAttributesType>
): Promise<ReadonlyArray<MessageAttributesType>> {
const { upgradeMessageSchema } = window.Signal.Migrations;
const ourAci = window.textsecure.storage.user.getCheckedAci();
// We upgrade these messages so they are sure to have thumbnails
const upgraded = await Promise.all(
messages.map(async message => {
const { schemaVersion } = message;
const model = window.MessageCache.__DEPRECATED$register(
message.id,
message,
'loadMediaItems'
);
try {
if (schemaVersion && schemaVersion < VERSION_NEEDED_FOR_DISPLAY) {
const upgradedMsgAttributes = await upgradeMessageSchema(message);
model.set(upgradedMsgAttributes);
await DataWriter.saveMessage(upgradedMsgAttributes, { ourAci });
}
return await window.MessageCache.upgradeSchema(
message,
VERSION_NEEDED_FOR_DISPLAY
);
} catch (error) {
log.warn(
`_upgradeMessages: Failed to upgrade message ${model.idForLogging()}: ${Errors.toLogFormat(error)}`
'_upgradeMessages: Failed to upgrade message ' +
`${getMessageIdForLogging(message)}: ${Errors.toLogFormat(error)}`
);
return undefined;
}
return model.attributes;
})
);

View file

@ -18,14 +18,14 @@ describe('MessageCache', () => {
await window.ConversationController.load();
});
describe('filterBySentAt', () => {
it('returns an empty iterable if no messages match', () => {
describe('findBySentAt', () => {
it('returns an empty iterable if no messages match', async () => {
const mc = new MessageCache();
assert.isEmpty([...mc.__DEPRECATED$filterBySentAt(123)]);
assert.isUndefined(await mc.findBySentAt(123, () => true));
});
it('returns all messages that match the timestamp', () => {
it('returns all messages that match the timestamp', async () => {
const mc = new MessageCache();
let message1 = new MessageModel({
@ -62,23 +62,15 @@ describe('MessageCache', () => {
message2 = mc.__DEPRECATED$register(message2.id, message2, 'test');
mc.__DEPRECATED$register(message3.id, message3, 'test');
const filteredMessages = Array.from(
mc.__DEPRECATED$filterBySentAt(1234)
).map(x => x.attributes);
const filteredMessage = await mc.findBySentAt(1234, () => true);
assert.deepEqual(
filteredMessages,
[message1.attributes, message2.attributes],
'first'
);
assert.deepEqual(filteredMessage, message1.attributes, 'first');
mc.__DEPRECATED$unregister(message2.id);
mc.__DEPRECATED$unregister(message1.id);
const filteredMessages2 = Array.from(
mc.__DEPRECATED$filterBySentAt(1234)
).map(x => x.attributes);
const filteredMessage2 = await mc.findBySentAt(1234, () => true);
assert.deepEqual(filteredMessages2, [message1.attributes], 'second');
assert.deepEqual(filteredMessage2, message2.attributes, 'second');
});
});

View file

@ -1,21 +1,22 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReadonlyMessageAttributesType } from '../model-types.d';
import type { MessageModel } from '../models/messages';
import type {
ReadonlyMessageAttributesType,
MessageAttributesType,
} from '../model-types.d';
import type { SignalService as Proto } from '../protobuf';
import type { AciString } from '../types/ServiceId';
import { DataReader } from '../sql/Client';
import * as log from '../logging/log';
import { normalizeAci } from './normalizeAci';
import { filter } from './iterables';
import { getAuthorId } from '../messages/helpers';
import { getTimestampFromLong } from './timestampLongUtils';
export async function findStoryMessages(
conversationId: string,
storyContext?: Proto.DataMessage.IStoryContext
): Promise<Array<MessageModel>> {
): Promise<Array<MessageAttributesType>> {
if (!storyContext) {
return [];
}
@ -32,25 +33,6 @@ export async function findStoryMessages(
const ourConversationId =
window.ConversationController.getOurConversationIdOrThrow();
const inMemoryMessages =
window.MessageCache.__DEPRECATED$filterBySentAt(sentAt);
const matchingMessages = [
...filter(inMemoryMessages, item =>
isStoryAMatch(
item.attributes,
conversationId,
ourConversationId,
authorAci,
sentAt
)
),
];
if (matchingMessages.length > 0) {
return matchingMessages;
}
log.info('findStoryMessages: db lookup needed', sentAt);
const messages = await DataReader.getMessagesBySentAt(sentAt);
const found = messages.filter(item =>
isStoryAMatch(item, conversationId, ourConversationId, authorAci, sentAt)
@ -61,14 +43,7 @@ export async function findStoryMessages(
return [];
}
const result = found.map(attributes =>
window.MessageCache.__DEPRECATED$register(
attributes.id,
attributes,
'findStoryMessages'
)
);
return result;
return found;
}
function isStoryAMatch(
@ -77,7 +52,7 @@ function isStoryAMatch(
ourConversationId: string,
authorAci: AciString,
sentTimestamp: number
): message is ReadonlyMessageAttributesType {
): boolean {
if (!message) {
return false;
}