Support voice memo backups
This commit is contained in:
parent
348503c7f9
commit
9cbbbe0ef0
4 changed files with 82 additions and 19 deletions
|
@ -87,10 +87,10 @@ import {
|
|||
numberToPhoneType,
|
||||
} from '../../types/EmbeddedContact';
|
||||
import {
|
||||
isVoiceMessage,
|
||||
type AttachmentType,
|
||||
isGIF,
|
||||
isDownloaded,
|
||||
isVoiceMessage as isVoiceMessageAttachment,
|
||||
} from '../../types/Attachment';
|
||||
import {
|
||||
getFilePointerForAttachment,
|
||||
|
@ -1692,7 +1692,7 @@ export class BackupExportStream extends Readable {
|
|||
private getMessageAttachmentFlag(
|
||||
attachment: AttachmentType
|
||||
): Backups.MessageAttachment.Flag {
|
||||
if (isVoiceMessage(attachment)) {
|
||||
if (isVoiceMessageAttachment(attachment)) {
|
||||
return Backups.MessageAttachment.Flag.VOICE_MESSAGE;
|
||||
}
|
||||
if (isGIF([attachment])) {
|
||||
|
@ -1876,6 +1876,9 @@ export class BackupExportStream extends Readable {
|
|||
>,
|
||||
backupLevel: BackupLevel
|
||||
): Promise<Backups.IStandardMessage> {
|
||||
const isVoiceMessage = message.attachments?.some(isVoiceMessageAttachment);
|
||||
const includeText = !isVoiceMessage;
|
||||
|
||||
return {
|
||||
quote: await this.toQuote(message.quote),
|
||||
attachments: message.attachments
|
||||
|
@ -1889,13 +1892,17 @@ export class BackupExportStream extends Readable {
|
|||
})
|
||||
)
|
||||
: undefined,
|
||||
text: {
|
||||
// Note that we store full text on the message model so we have to
|
||||
// trim it before serializing.
|
||||
body: message.body?.slice(0, LONG_ATTACHMENT_LIMIT),
|
||||
bodyRanges: message.bodyRanges?.map(range => this.toBodyRange(range)),
|
||||
},
|
||||
|
||||
text: includeText
|
||||
? {
|
||||
// TODO (DESKTOP-7207): handle long message text attachments
|
||||
// Note that we store full text on the message model so we have to
|
||||
// trim it before serializing.
|
||||
body: message.body?.slice(0, LONG_ATTACHMENT_LIMIT),
|
||||
bodyRanges: message.bodyRanges?.map(range =>
|
||||
this.toBodyRange(range)
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
linkPreview: message.preview
|
||||
? await Promise.all(
|
||||
message.preview.map(async preview => {
|
||||
|
|
|
@ -58,7 +58,10 @@ import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads';
|
|||
import { drop } from '../../util/drop';
|
||||
import { isNotNil } from '../../util/isNotNil';
|
||||
import { isGroup } from '../../util/whatTypeOfConversation';
|
||||
import { convertFilePointerToAttachment } from './util/filePointers';
|
||||
import {
|
||||
convertBackupMessageAttachmentToAttachment,
|
||||
convertFilePointerToAttachment,
|
||||
} from './util/filePointers';
|
||||
|
||||
const MAX_CONCURRENCY = 10;
|
||||
|
||||
|
@ -976,12 +979,7 @@ export class BackupImportStream extends Writable {
|
|||
body: data.text?.body || undefined,
|
||||
attachments: data.attachments?.length
|
||||
? data.attachments
|
||||
.map(attachment => {
|
||||
if (!attachment.pointer) {
|
||||
return null;
|
||||
}
|
||||
return convertFilePointerToAttachment(attachment.pointer);
|
||||
})
|
||||
.map(convertBackupMessageAttachmentToAttachment)
|
||||
.filter(isNotNil)
|
||||
: undefined,
|
||||
preview: data.linkPreview?.length
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
isDecryptable,
|
||||
isReencryptableToSameDigest,
|
||||
} from '../../../types/Attachment';
|
||||
import { Backups } from '../../../protobuf';
|
||||
import { Backups, SignalService } from '../../../protobuf';
|
||||
import * as Bytes from '../../../Bytes';
|
||||
import { getTimestampFromLong } from '../../../util/timestampLongUtils';
|
||||
import {
|
||||
|
@ -36,6 +36,7 @@ import {
|
|||
getMediaNameForAttachment,
|
||||
} from './mediaId';
|
||||
import { redactGenericText } from '../../../util/privacy';
|
||||
import { missingCaseError } from '../../../util/missingCaseError';
|
||||
|
||||
export function convertFilePointerToAttachment(
|
||||
filePointer: Backups.FilePointer
|
||||
|
@ -123,6 +124,35 @@ export function convertFilePointerToAttachment(
|
|||
throw new Error('convertFilePointerToAttachment: mising locator');
|
||||
}
|
||||
|
||||
export function convertBackupMessageAttachmentToAttachment(
|
||||
messageAttachment: Backups.IMessageAttachment
|
||||
): AttachmentType | null {
|
||||
if (!messageAttachment.pointer) {
|
||||
return null;
|
||||
}
|
||||
const result = convertFilePointerToAttachment(messageAttachment.pointer);
|
||||
switch (messageAttachment.flag) {
|
||||
case Backups.MessageAttachment.Flag.VOICE_MESSAGE:
|
||||
result.flags = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE;
|
||||
break;
|
||||
case Backups.MessageAttachment.Flag.BORDERLESS:
|
||||
result.flags = SignalService.AttachmentPointer.Flags.BORDERLESS;
|
||||
break;
|
||||
case Backups.MessageAttachment.Flag.GIF:
|
||||
result.flags = SignalService.AttachmentPointer.Flags.GIF;
|
||||
break;
|
||||
case Backups.MessageAttachment.Flag.NONE:
|
||||
case null:
|
||||
case undefined:
|
||||
result.flags = undefined;
|
||||
break;
|
||||
default:
|
||||
throw missingCaseError(messageAttachment.flag);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some attachments saved on desktop do not include the key used to encrypt the file
|
||||
* originally. This means that we need to encrypt the file in-memory now (at
|
||||
|
|
|
@ -13,10 +13,11 @@ import { ReadStatus } from '../../messages/MessageReadStatus';
|
|||
import { SeenStatus } from '../../MessageSeenStatus';
|
||||
import { loadCallsHistory } from '../../services/callHistoryLoader';
|
||||
import { setupBasics, asymmetricRoundtripHarness } from './helpers';
|
||||
import { IMAGE_JPEG } from '../../types/MIME';
|
||||
import { AUDIO_MP3, IMAGE_JPEG } from '../../types/MIME';
|
||||
import type { MessageAttributesType } from '../../model-types';
|
||||
import type { AttachmentType } from '../../types/Attachment';
|
||||
import { isVoiceMessage, type AttachmentType } from '../../types/Attachment';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { SignalService } from '../../protobuf';
|
||||
|
||||
const CONTACT_A = generateAci();
|
||||
|
||||
|
@ -129,6 +130,33 @@ describe('backup/attachments', () => {
|
|||
BackupLevel.Media
|
||||
);
|
||||
});
|
||||
it('roundtrips voice message attachments', async () => {
|
||||
const attachment = composeAttachment(1);
|
||||
attachment.contentType = AUDIO_MP3;
|
||||
attachment.flags = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE;
|
||||
|
||||
strictAssert(isVoiceMessage(attachment), 'it is a voice attachment');
|
||||
strictAssert(attachment.digest, 'digest exists');
|
||||
|
||||
await asymmetricRoundtripHarness(
|
||||
[
|
||||
composeMessage(1, {
|
||||
attachments: [attachment],
|
||||
}),
|
||||
],
|
||||
[
|
||||
composeMessage(1, {
|
||||
attachments: [
|
||||
{
|
||||
...omit(attachment, ['path', 'iv', 'uploadTimestamp']),
|
||||
backupLocator: { mediaName: attachment.digest },
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
BackupLevel.Media
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Preview attachments', () => {
|
||||
|
|
Loading…
Add table
Reference in a new issue