[client-sync] Use IMAPConnectionPool for downloading files

Summary: See title

Test Plan: Run locally, verify downloading files works

Reviewers: evan, spang, juan

Reviewed By: juan

Differential Revision: https://phab.nylas.com/D4004
This commit is contained in:
Mark Hahnenberg 2017-02-21 15:40:30 -08:00
parent 44f5cc580b
commit c4ccd8aac4

View file

@ -1,6 +1,7 @@
const base64 = require('base64-stream'); const base64 = require('base64-stream');
const {IMAPConnection} = require('isomorphic-core') const {IMAPConnectionPool} = require('isomorphic-core')
const {QuotedPrintableStreamDecoder} = require('../shared/stream-decoders') const {QuotedPrintableStreamDecoder} = require('../shared/stream-decoders')
const {Actions} = require('nylas-exports')
module.exports = (sequelize, Sequelize) => { module.exports = (sequelize, Sequelize) => {
return sequelize.define('file', { return sequelize.define('file', {
@ -26,20 +27,14 @@ module.exports = (sequelize, Sequelize) => {
}, },
instanceMethods: { instanceMethods: {
async fetch({account, db, logger}) { async fetch({account, db, logger}) {
let numAttempts = 0; const message = await this.getMessage()
const maxAttempts = 5; const folder = await message.getFolder()
let currentTimeout = 30 * 1000; let numTimeoutErrors = 0;
const maxTimeout = 2 * 60 * 1000; let result = null;
while (numAttempts <= maxAttempts) { await IMAPConnectionPool.withConnectionsForAccount(account, {
const settings = Object.assign({}, desiredCount: 1,
account.connectionSettings, logger,
account.decryptedCredentials(), onConnected: async ([connection], done) => {
{socketTimeout: currentTimeout})
const message = await this.getMessage()
let connection = null;
try {
connection = await IMAPConnection.connect({db, settings, logger})
const folder = await message.getFolder()
const imapBox = await connection.openBox(folder.name) const imapBox = await connection.openBox(folder.name)
const stream = await imapBox.fetchMessageStream(message.folderImapUID, { const stream = await imapBox.fetchMessageStream(message.folderImapUID, {
fetchOptions: { fetchOptions: {
@ -47,16 +42,20 @@ module.exports = (sequelize, Sequelize) => {
struct: true, struct: true,
}, },
onFetchComplete() { onFetchComplete() {
connection.end() done();
}, },
}) });
if (!stream) { if (!stream) {
throw new Error(`Unable to fetch binary data for File ${this.id}`) throw new Error(`Unable to fetch binary data for File ${this.id}`)
} }
if (/quoted-printable/i.test(this.encoding)) { if (/quoted-printable/i.test(this.encoding)) {
return stream.pipe(new QuotedPrintableStreamDecoder({charset: this.charset})) result = stream.pipe(new QuotedPrintableStreamDecoder({charset: this.charset}));
return true;
} else if (/base64/i.test(this.encoding)) { } else if (/base64/i.test(this.encoding)) {
return stream.pipe(base64.decode()); result = stream.pipe(base64.decode());
return true;
} }
// If there is no encoding, or the encoding is something like // If there is no encoding, or the encoding is something like
@ -64,20 +63,20 @@ module.exports = (sequelize, Sequelize) => {
// stream will be written directly to disk. It's then up to the // stream will be written directly to disk. It's then up to the
// user's computer to decide how to interpret the bytes we've // user's computer to decide how to interpret the bytes we've
// dumped to disk. // dumped to disk.
return stream result = stream;
} catch (err) { return true;
if (connection) { },
connection.end(); onTimeout: (socketTimeout) => {
} numTimeoutErrors += 1;
if (numAttempts <= maxAttempts) { Actions.recordUserEvent('Timeout error downloading file', {
console.error('Error trying to fetch file:', err); accountId: account.id,
numAttempts += 1; provider: account.provider,
currentTimeout = Math.min(maxTimeout, currentTimeout * 2); socketTimeout,
continue; numTimeoutErrors,
} });
throw err },
} });
} return result;
}, },
toJSON() { toJSON() {