From bf67254cc5b18dfb46a5d9f144a582dd6c3987ff Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Tue, 3 Apr 2018 21:10:34 -0400 Subject: [PATCH] Add `Message.createImporter` --- js/modules/types/message.js | 49 +++++++++++++++++++++++- preload.js | 3 ++ test/modules/types/message_test.js | 60 ++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/js/modules/types/message.js b/js/modules/types/message.js index c66579370a..3ce90f56cc 100644 --- a/js/modules/types/message.js +++ b/js/modules/types/message.js @@ -1,4 +1,4 @@ -const { isFunction } = require('lodash'); +const { isFunction, isString, omit } = require('lodash'); const Attachment = require('./attachment'); const Errors = require('./errors'); @@ -176,3 +176,50 @@ exports.upgradeSchema = async (message, { writeNewAttachmentData } = {}) => { { writeNewAttachmentData } ); }; + +// createImporter :: (RelativePath -> IO Unit) +// Message -> +// IO (Promise Message) +exports.createImporter = (writeExistingAttachmentData) => { + if (!isFunction(writeExistingAttachmentData)) { + throw new TypeError('"writeExistingAttachmentData" must be a function'); + } + + return async (message) => { + if (!exports.isValid(message)) { + throw new TypeError('"message" is not valid'); + } + + const { attachments } = message; + const hasAttachments = attachments && attachments.length > 0; + if (!hasAttachments) { + return message; + } + + const lastVersionWithAttachmentDataInMemory = 2; + const willHaveAttachmentsSavedOnFileSystemDuringUpgrade = + message.schemaVersion <= lastVersionWithAttachmentDataInMemory; + if (willHaveAttachmentsSavedOnFileSystemDuringUpgrade) { + return message; + } + + attachments.forEach((attachment) => { + if (!Attachment.hasData(attachment)) { + throw new TypeError('"attachment.data" is required during message import'); + } + + if (!isString(attachment.path)) { + throw new TypeError('"attachment.path" is required'); + } + }); + + const messageWithoutAttachmentData = Object.assign({}, message, { + attachments: await Promise.all(attachments.map(async (attachment) => { + await writeExistingAttachmentData(attachment); + return omit(attachment, ['data']); + })), + }); + + return messageWithoutAttachmentData; + }; +}; diff --git a/preload.js b/preload.js index f4985ecd3b..118627af2d 100644 --- a/preload.js +++ b/preload.js @@ -114,6 +114,7 @@ const attachmentsPath = Attachments.getPath(app.getPath('userData')); const deleteAttachmentData = Attachments.createDeleter(attachmentsPath); const readAttachmentData = Attachments.createReader(attachmentsPath); const writeNewAttachmentData = Attachments.createWriterForNew(attachmentsPath); +const writeExistingAttachmentData = Attachments.createWriterForExisting(attachmentsPath); // Injected context functions to keep `Message` agnostic from Electron: const upgradeSchemaContext = { @@ -137,6 +138,8 @@ window.Signal.Migrations = {}; window.Signal.Migrations.deleteAttachmentData = Attachment.deleteData(deleteAttachmentData); window.Signal.Migrations.getPlaceholderMigrations = getPlaceholderMigrations; +window.Signal.Migrations.importMessage = + Message.createImporter(writeExistingAttachmentData); window.Signal.Migrations.loadAttachmentData = Attachment.loadData(readAttachmentData); window.Signal.Migrations.Migrations0DatabaseWithAttachmentData = require('./js/modules/migrations/migrations_0_database_with_attachment_data'); diff --git a/test/modules/types/message_test.js b/test/modules/types/message_test.js index 4f2e654e05..6c9112e19a 100644 --- a/test/modules/types/message_test.js +++ b/test/modules/types/message_test.js @@ -5,6 +5,66 @@ const { stringToArrayBuffer } = require('../../../js/modules/string_to_array_buf describe('Message', () => { + describe('createImporter', () => { + it('should ignore messages that didn’t go through attachment migration', async () => { + const input = { + body: 'Imagine there is no heaven…', + schemaVersion: 2, + }; + const expected = { + body: 'Imagine there is no heaven…', + schemaVersion: 2, + }; + const writeExistingAttachmentData = () => {}; + + const actual = await Message.createImporter(writeExistingAttachmentData)(input); + assert.deepEqual(actual, expected); + }); + + it('should ignore messages without attachments', async () => { + const input = { + body: 'Imagine there is no heaven…', + schemaVersion: 4, + attachments: [], + }; + const expected = { + body: 'Imagine there is no heaven…', + schemaVersion: 4, + attachments: [], + }; + const writeExistingAttachmentData = () => {}; + + const actual = await Message.createImporter(writeExistingAttachmentData)(input); + assert.deepEqual(actual, expected); + }); + + it('should write attachments to file system on original path', async () => { + const input = { + body: 'Imagine there is no heaven…', + schemaVersion: 4, + attachments: [{ + path: 'ab/abcdefghi', + data: stringToArrayBuffer('It’s easy if you try'), + }], + }; + const expected = { + body: 'Imagine there is no heaven…', + schemaVersion: 4, + attachments: [{ + path: 'ab/abcdefghi', + }], + }; + + const writeExistingAttachmentData = (attachment) => { + assert.equal(attachment.path, 'ab/abcdefghi'); + assert.deepEqual(attachment.data, stringToArrayBuffer('It’s easy if you try')); + }; + + const actual = await Message.createImporter(writeExistingAttachmentData)(input); + assert.deepEqual(actual, expected); + }); + }); + describe('initializeSchemaVersion', () => { it('should ignore messages with previously inherited schema', () => { const input = {