Backup support for quotes & quoted attachments

This commit is contained in:
trevor-signal 2024-06-10 14:44:15 -04:00 committed by GitHub
parent 0f2b71b4a6
commit e0dc4c412d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 653 additions and 289 deletions

View file

@ -8,13 +8,16 @@ import { omit } from 'lodash';
import type { ConversationModel } from '../../models/conversations';
import * as Bytes from '../../Bytes';
import Data from '../../sql/Client';
import { generateAci } from '../../types/ServiceId';
import { type AciString, generateAci } from '../../types/ServiceId';
import { ReadStatus } from '../../messages/MessageReadStatus';
import { SeenStatus } from '../../MessageSeenStatus';
import { loadCallsHistory } from '../../services/callHistoryLoader';
import { setupBasics, asymmetricRoundtripHarness } from './helpers';
import { AUDIO_MP3, IMAGE_JPEG } from '../../types/MIME';
import type { MessageAttributesType } from '../../model-types';
import { AUDIO_MP3, IMAGE_JPEG, IMAGE_PNG, VIDEO_MP4 } from '../../types/MIME';
import type {
MessageAttributesType,
QuotedMessageType,
} from '../../model-types';
import { isVoiceMessage, type AttachmentType } from '../../types/Attachment';
import { strictAssert } from '../../util/assert';
import { SignalService } from '../../protobuf';
@ -58,6 +61,13 @@ describe('backup/attachments', () => {
contentType: IMAGE_JPEG,
path: `/path/to/file${index}.png`,
uploadTimestamp: index,
thumbnail: {
size: 1024,
width: 150,
height: 150,
contentType: IMAGE_PNG,
path: '/path/to/thumbnail.png',
},
...overrides,
};
}
@ -73,7 +83,7 @@ describe('backup/attachments', () => {
received_at: timestamp,
received_at_ms: timestamp,
sourceServiceId: CONTACT_A,
sourceDevice: timestamp,
sourceDevice: 1,
sent_at: timestamp,
timestamp,
readStatus: ReadStatus.Read,
@ -97,8 +107,8 @@ describe('backup/attachments', () => {
[
composeMessage(1, {
attachments: [
omit(attachment1, ['path', 'iv']),
omit(attachment2, ['path', 'iv']),
omit(attachment1, ['path', 'iv', 'thumbnail']),
omit(attachment2, ['path', 'iv', 'thumbnail']),
],
}),
],
@ -121,7 +131,12 @@ describe('backup/attachments', () => {
// but there will be a backupLocator
attachments: [
{
...omit(attachment, ['path', 'iv', 'uploadTimestamp']),
...omit(attachment, [
'path',
'iv',
'thumbnail',
'uploadTimestamp',
]),
backupLocator: { mediaName: attachment.digest },
},
],
@ -148,7 +163,12 @@ describe('backup/attachments', () => {
composeMessage(1, {
attachments: [
{
...omit(attachment, ['path', 'iv', 'uploadTimestamp']),
...omit(attachment, [
'path',
'iv',
'thumbnail',
'uploadTimestamp',
]),
backupLocator: { mediaName: attachment.digest },
},
],
@ -173,7 +193,11 @@ describe('backup/attachments', () => {
[
composeMessage(1, {
preview: [
{ url: 'url', date: 1, image: omit(attachment, ['path', 'iv']) },
{
url: 'url',
date: 1,
image: omit(attachment, ['path', 'iv', 'thumbnail']),
},
],
}),
],
@ -209,7 +233,12 @@ describe('backup/attachments', () => {
image: {
// path, iv, and uploadTimestamp will not be roundtripped,
// but there will be a backupLocator
...omit(attachment, ['path', 'iv', 'uploadTimestamp']),
...omit(attachment, [
'path',
'iv',
'thumbnail',
'uploadTimestamp',
]),
backupLocator: { mediaName: attachment.digest },
},
},
@ -237,7 +266,7 @@ describe('backup/attachments', () => {
contact: [
{
avatar: {
avatar: omit(attachment, ['path', 'iv']),
avatar: omit(attachment, ['path', 'iv', 'thumbnail']),
isProfile: false,
},
},
@ -265,7 +294,12 @@ describe('backup/attachments', () => {
{
avatar: {
avatar: {
...omit(attachment, ['path', 'iv', 'uploadTimestamp']),
...omit(attachment, [
'path',
'iv',
'thumbnail',
'uploadTimestamp',
]),
backupLocator: { mediaName: attachment.digest },
},
isProfile: false,
@ -278,4 +312,155 @@ describe('backup/attachments', () => {
);
});
});
describe('quotes', () => {
it('BackupLevel.Messages, roundtrips quote attachments', async () => {
const attachment = composeAttachment(1);
const authorAci = generateAci();
const quotedMessage: QuotedMessageType = {
authorAci,
isViewOnce: false,
id: Date.now(),
referencedMessageNotFound: false,
messageId: '',
isGiftBadge: true,
attachments: [{ thumbnail: attachment, contentType: VIDEO_MP4 }],
};
await asymmetricRoundtripHarness(
[
composeMessage(1, {
quote: quotedMessage,
}),
],
// path & iv will not be roundtripped
[
composeMessage(1, {
quote: {
...quotedMessage,
referencedMessageNotFound: true,
attachments: [
{
thumbnail: omit(attachment, ['iv', 'path', 'thumbnail']),
contentType: VIDEO_MP4,
},
],
},
}),
],
BackupLevel.Messages
);
});
it('BackupLevel.Media, roundtrips quote attachments', async () => {
const attachment = composeAttachment(1);
strictAssert(attachment.digest, 'digest exists');
const authorAci = generateAci();
const quotedMessage: QuotedMessageType = {
authorAci,
isViewOnce: false,
id: Date.now(),
referencedMessageNotFound: false,
messageId: '',
isGiftBadge: true,
attachments: [{ thumbnail: attachment, contentType: VIDEO_MP4 }],
};
await asymmetricRoundtripHarness(
[
composeMessage(1, {
quote: quotedMessage,
}),
],
[
composeMessage(1, {
quote: {
...quotedMessage,
referencedMessageNotFound: true,
attachments: [
{
thumbnail: {
...omit(attachment, [
'iv',
'path',
'uploadTimestamp',
'thumbnail',
]),
backupLocator: { mediaName: attachment.digest },
},
contentType: VIDEO_MP4,
},
],
},
}),
],
BackupLevel.Media
);
});
it('Copies data from message if it exists', async () => {
const existingAttachment = composeAttachment(1);
const existingMessageTimestamp = Date.now();
const existingMessage = composeMessage(existingMessageTimestamp, {
attachments: [existingAttachment],
});
const quoteAttachment = composeAttachment(2);
delete quoteAttachment.thumbnail;
strictAssert(quoteAttachment.digest, 'digest exists');
strictAssert(existingAttachment.digest, 'digest exists');
const quotedMessage: QuotedMessageType = {
authorAci: existingMessage.sourceServiceId as AciString,
isViewOnce: false,
id: existingMessageTimestamp,
referencedMessageNotFound: false,
messageId: '',
isGiftBadge: false,
attachments: [{ thumbnail: quoteAttachment, contentType: VIDEO_MP4 }],
};
const quoteMessage = composeMessage(existingMessageTimestamp + 1, {
quote: quotedMessage,
});
await asymmetricRoundtripHarness(
[existingMessage, quoteMessage],
[
{
...existingMessage,
attachments: [
{
...omit(existingAttachment, [
'path',
'iv',
'uploadTimestamp',
'thumbnail',
]),
backupLocator: { mediaName: existingAttachment.digest },
},
],
},
{
...quoteMessage,
quote: {
...quotedMessage,
referencedMessageNotFound: false,
attachments: [
{
// The thumbnail will not have been copied over yet since it has not yet
// been downloaded
thumbnail: {
...omit(quoteAttachment, ['iv', 'path', 'uploadTimestamp']),
backupLocator: { mediaName: quoteAttachment.digest },
},
contentType: VIDEO_MP4,
},
],
},
},
],
BackupLevel.Media
);
});
});
});