Quotes: The full pipeline into the database

1. MessageReceiver always pulls down thumbnails included in quotes
2. Message.upgradeSchema has a new schema that puts all thumbnails on
   disk just like happens with full attachments.
3. handleDataMessage pipes quote from dataMessage into the final message
   destined for the database
pull/1/head
Scott Nonnenberg 7 years ago
parent e69586200a
commit 054d3887a1
No known key found for this signature in database
GPG Key ID: 5F82280C35134661

@ -447,6 +447,7 @@
body : dataMessage.body,
conversationId : conversation.id,
attachments : dataMessage.attachments,
quote : dataMessage.quote,
decrypted_at : now,
flags : dataMessage.flags,
errors : []

@ -26,7 +26,7 @@ const INITIAL_SCHEMA_VERSION = 0;
// add more upgrade steps, we could design a pipeline that does this
// incrementally, e.g. from version 0 / unknown -> 1, 1 --> 2, etc., similar to
// how we do database migrations:
exports.CURRENT_SCHEMA_VERSION = 3;
exports.CURRENT_SCHEMA_VERSION = 4;
// Public API
@ -149,6 +149,26 @@ exports._mapAttachments = upgradeAttachment => async (message, context) => {
return Object.assign({}, message, { attachments });
};
// _mapQuotedAttachments :: (QuotedAttachment -> Promise QuotedAttachment) ->
// (Message, Context) ->
//
exports._mapQuotedAttachments = upgradeAttachment => async (message, context) => {
if (!message.quote) {
return message;
}
const upgradeWithContext = attachment =>
upgradeAttachment(attachment, context);
const quotedAttachments = (message.quote && message.quote.attachments) || [];
const attachments = await Promise.all(quotedAttachments.map(upgradeWithContext));
return Object.assign({}, message, {
quote: Object.assign({}, message.quote, {
attachments,
}),
});
};
const toVersion0 = async message =>
exports.initializeSchemaVersion(message);
@ -164,17 +184,29 @@ const toVersion3 = exports._withSchemaVersion(
3,
exports._mapAttachments(Attachment.migrateDataToFileSystem)
);
const toVersion4 = exports._withSchemaVersion(
4,
exports._mapQuotedAttachments(Attachment.migrateDataToFileSystem)
);
// UpgradeStep
exports.upgradeSchema = async (message, { writeNewAttachmentData } = {}) => {
exports.upgradeSchema = async (rawMessage, { writeNewAttachmentData } = {}) => {
if (!isFunction(writeNewAttachmentData)) {
throw new TypeError('`context.writeNewAttachmentData` is required');
}
return toVersion3(
await toVersion2(await toVersion1(await toVersion0(message))),
{ writeNewAttachmentData }
);
let message = rawMessage;
const versions = [toVersion0, toVersion1, toVersion2, toVersion3, toVersion4];
for (let i = 0, max = versions.length; i < max; i += 1) {
const currentVersion = versions[i];
// We really do want this intra-loop await because this is a chained async action,
// each step dependent on the previous
// eslint-disable-next-line no-await-in-loop
message = await currentVersion(message, { writeNewAttachmentData });
}
return message;
};
exports.createAttachmentLoader = (loadAttachmentData) => {

@ -1006,6 +1006,18 @@ MessageReceiver.prototype.extend({
const attachment = decrypted.attachments[i];
promises.push(this.handleAttachment(attachment));
}
if (decrypted.quote && decrypted.quote.attachments) {
const { attachments } = decrypted.quote;
for (let i = 0, max = attachments.length; i < max; i += 1) {
const attachment = attachments[i];
if (attachment.thumbnail) {
promises.push(this.handleAttachment(attachment.thumbnail));
}
}
}
return Promise.all(promises).then(() => decrypted);
/* eslint-enable no-bitwise, no-param-reassign */
},

@ -1,4 +1,5 @@
const { assert } = require('chai');
const sinon = require('sinon');
const Message = require('../../../js/modules/types/message');
const { stringToArrayBuffer } = require('../../../js/modules/string_to_array_buffer');
@ -308,4 +309,81 @@ describe('Message', () => {
assert.deepEqual(actual, expected);
});
});
describe('_mapQuotedAttachments', () => {
it('handles message with no quote', async () => {
const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = {
body: 'hey there!',
};
const result = await upgradeVersion(message);
assert.deepEqual(result, message);
});
it('handles quote with no attachments', async () => {
const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = {
body: 'hey there!',
quote: {
text: 'hey!',
},
};
const expected = {
body: 'hey there!',
quote: {
text: 'hey!',
attachments: [],
},
};
const result = await upgradeVersion(message);
assert.deepEqual(result, expected);
});
it('handles zero attachments', async () => {
const upgradeAttachment = sinon.stub().throws(new Error("Shouldn't be called"));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = {
body: 'hey there!',
quote: {
text: 'hey!',
attachments: [],
},
};
const result = await upgradeVersion(message);
assert.deepEqual(result, message);
});
it('calls provided async function for each quoted attachment', async () => {
const upgradeAttachment = sinon.stub().returns(Promise.resolve({
path: '/new/path/on/disk',
}));
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = {
body: 'hey there!',
quote: {
text: 'hey!',
attachments: [{
data: 'data is here',
}],
},
};
const expected = {
body: 'hey there!',
quote: {
text: 'hey!',
attachments: [{
path: '/new/path/on/disk',
}],
},
};
const result = await upgradeVersion(message);
assert.deepEqual(result, expected);
});
});
});

Loading…
Cancel
Save