Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
280 lines
9.2 KiB
JavaScript
Executable file
280 lines
9.2 KiB
JavaScript
Executable file
'use strict';
|
|
/**
|
|
* This module exposes a single helper method,
|
|
* `sendNextAvailableOutboxMessage`, which is used by the
|
|
* sendOutboxMessages job in jobmixins.js.
|
|
*/
|
|
define(function(require) {
|
|
|
|
|
|
/**
|
|
* Send the next available outbox message. Returns a promise that
|
|
* resolves to the following:
|
|
*
|
|
* {
|
|
* moreExpected: (Boolean),
|
|
* messageNamer: { date, suid }
|
|
* }
|
|
*
|
|
* If there might be more messages left to send after this one,
|
|
* moreExpected will be `true`.
|
|
*
|
|
* If we attempted to send a message, messageNamer will point to it.
|
|
* This can then be passed to a subsequent invocation of this, to
|
|
* send the next available message after the given messageNamer.
|
|
*
|
|
* @param {CompositeAccount|ActiveSyncAccount} account
|
|
* @param {FolderStorage} storage
|
|
* @param {MessageNamer|null} beforeMessage
|
|
* Send the first message chronologically preceding `beforeMessage`.
|
|
* @param {Boolean} emitNotifications
|
|
* If true, we will emit backgroundSendStatus notifications
|
|
* for this message.
|
|
* @param {Boolean} outboxNeedsFreshSync
|
|
* If true, ignore any potentially stale "sending" state,
|
|
* as in when we restore the app from a crash.
|
|
* @param {SmartWakeLock} wakeLock
|
|
* A SmartWakeLock to be held open during the sending process.
|
|
* @return {Promise}
|
|
* @public
|
|
*/
|
|
function sendNextAvailableOutboxMessage(
|
|
account, storage, beforeMessage, emitNotifications,
|
|
outboxNeedsFreshSync, wakeLock) {
|
|
|
|
return getNextHeader(storage, beforeMessage).then(function(header) {
|
|
// If there are no more messages to send, resolve `null`. This
|
|
// should ordinarily not happen, because clients should pay
|
|
// attention to the `moreExpected` results from earlier sends;
|
|
// but job scheduling might introduce edge cases where this
|
|
// happens, so better to be safe.
|
|
if (!header) {
|
|
return {
|
|
moreExpected: false,
|
|
messageNamer: null
|
|
};
|
|
}
|
|
|
|
// Figure out if this is the last message to consider sending in the
|
|
// outbox. (We are moving from newest to oldest, so this is the last one
|
|
// if it is the oldest. We need to figure this out before the send
|
|
// process completes since we will delete the header once it's all sent.)
|
|
var moreExpected = !storage.headerIsOldestKnown(header.date,
|
|
header.id);
|
|
|
|
if (!header.sendStatus) {
|
|
header.sendStatus = {};
|
|
}
|
|
|
|
// If the header has not been sent, or we've been instructed to
|
|
// ignore any existing sendStatus, clear it out.
|
|
if (header.sendStatus.state !== 'sending' || outboxNeedsFreshSync) {
|
|
// If this message is not already being sent, send it.
|
|
return constructComposer(account, storage, header, wakeLock)
|
|
.then(sendMessage.bind(null, account, storage, emitNotifications))
|
|
.then(function(header) {
|
|
return {
|
|
moreExpected: moreExpected,
|
|
messageNamer: {
|
|
suid: header.suid,
|
|
date: header.date
|
|
}
|
|
};
|
|
});
|
|
} else {
|
|
// If this message is currently being sent, advance to the
|
|
// next header.
|
|
return sendNextAvailableOutboxMessage(account, storage, {
|
|
suid: header.suid,
|
|
date: header.date
|
|
}, emitNotifications, outboxNeedsFreshSync, wakeLock);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// The following functions are internal helpers.
|
|
|
|
/**
|
|
* Resolve to the header immediately preceding `beforeMessage` in
|
|
* time. If beforeMessage is null, resolve the most recent message.
|
|
* If no message could be found, resolve `null`.
|
|
*
|
|
* @param {FolderStorage} storage
|
|
* @param {MessageNamer} beforeMessage
|
|
* @return {Promise(MailHeader)}
|
|
*/
|
|
function getNextHeader(storage, /* optional */ beforeMessage) {
|
|
return new Promise(function(resolve) {
|
|
if (beforeMessage) {
|
|
// getMessagesBeforeMessage expects an 'id', not a 'suid'.
|
|
var id = parseInt(beforeMessage.suid.substring(
|
|
beforeMessage.suid.lastIndexOf('/') + 1));
|
|
storage.getMessagesBeforeMessage(
|
|
beforeMessage.date,
|
|
id,
|
|
/* limit = */ 1,
|
|
function(headers, moreExpected) {
|
|
// There may be no headers, and that's okay.
|
|
resolve(headers[0] || null);
|
|
});
|
|
} else {
|
|
storage.getMessagesInImapDateRange(
|
|
0,
|
|
null,
|
|
/* min */ 1,
|
|
/* max */ 1,
|
|
function(headers, moreExpected) {
|
|
resolve(headers[0]);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Build a Composer instance pointing to the given header.
|
|
*
|
|
* @param {MailAccount} account
|
|
* @param {FolderStorage} storage
|
|
* @param {MailHeader} header
|
|
* @param {SmartWakeLock} wakeLock
|
|
* @return {Promise(Composer)}
|
|
*/
|
|
function constructComposer(account, storage, header, wakeLock) {
|
|
return new Promise(function(resolve, reject) {
|
|
storage.getMessage(header.suid, header.date, function(msg) {
|
|
|
|
// If for some reason the message doesn't have a body, we
|
|
// can't construct a composer for this header.
|
|
if (!msg || !msg.body) {
|
|
console.error('Failed to create composer; no body available.');
|
|
reject();
|
|
return;
|
|
}
|
|
|
|
require(['../drafts/composer'], function(cmp) {
|
|
var composer = new cmp.Composer(msg, account, account.identities[0]);
|
|
composer.setSmartWakeLock(wakeLock);
|
|
|
|
resolve(composer);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Attempt to send the given message from the outbox.
|
|
*
|
|
* During the sending process, post status updates to the universe,
|
|
* so that the frontend can display status notifications if it
|
|
* desires.
|
|
*
|
|
* If the message successfully sends, remove it from the outbox;
|
|
* otherwise, its `sendStatus.state` will equal 'error', with
|
|
* details about the failure.
|
|
*
|
|
* Resolves to the header; you can check `header.sendStatus` to see
|
|
* the result of this send attempt.
|
|
*
|
|
* @param {MailAccount} account
|
|
* @param {FolderStorage} storage
|
|
* @param {Composer} composer
|
|
* @return {Promise(MailHeader)}
|
|
*/
|
|
function sendMessage(account, storage, emitNotifications, composer) {
|
|
var header = composer.header;
|
|
var progress = publishStatus.bind(
|
|
null, account, storage, composer, header, emitNotifications);
|
|
|
|
// As part of the progress notification, the client would like to
|
|
// know whether or not they can expect us to immediately send more
|
|
// messages after this one. If there are messages in the outbox
|
|
// older than this one, the answer is yes.
|
|
var oldestDate = storage.getOldestMessageTimestamp();
|
|
var willSendMore = oldestDate > 0 && oldestDate < header.date.valueOf();
|
|
|
|
// Send the initial progress information.
|
|
progress({
|
|
state: 'sending',
|
|
err: null,
|
|
badAddresses: null,
|
|
sendFailures: header.sendStatus && header.sendStatus.sendFailures || 0
|
|
});
|
|
|
|
return new Promise(function(resolve) {
|
|
account.sendMessage(composer, function(err, badAddresses) {
|
|
if (err) {
|
|
console.log('Message failed to send (' + err + ')');
|
|
|
|
progress({
|
|
state: 'error',
|
|
err: err,
|
|
badAddresses: badAddresses,
|
|
sendFailures: (header.sendStatus.sendFailures || 0) + 1
|
|
});
|
|
|
|
resolve(composer.header);
|
|
} else {
|
|
console.log('Message sent; deleting from outbox.');
|
|
|
|
progress({
|
|
state: 'success',
|
|
err: null,
|
|
badAddresses: null
|
|
});
|
|
storage.deleteMessageHeaderAndBodyUsingHeader(header, function() {
|
|
resolve(composer.header);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Publish a universe notification with the message's current send
|
|
* status, and queue it for persistence in the database.
|
|
*
|
|
* NOTE: Currently, we do not checkpoint our state, so the
|
|
* intermediary "sending" steps will not actually get written to
|
|
* disk. That is generally fine, since sendStatus is invalid upon a
|
|
* restart. However, when we address bug 1032451 (sendMessage is not
|
|
* actually atomic), we will want to checkpoint state during the
|
|
* sending process.
|
|
*/
|
|
function publishStatus(account, storage, composer,
|
|
header, emitNotifications, status) {
|
|
header.sendStatus = {
|
|
state: status.state,
|
|
err: status.err,
|
|
badAddresses: status.badAddresses,
|
|
sendFailures: status.sendFailures
|
|
};
|
|
|
|
account.universe.__notifyBackgroundSendStatus({
|
|
// Status information (also stored on the header):
|
|
state: status.state,
|
|
err: status.err,
|
|
badAddresses: status.badAddresses,
|
|
sendFailures: status.sendFailures,
|
|
// Message/Account Information (for notifications):
|
|
accountId: account.id,
|
|
suid: header.suid,
|
|
emitNotifications: emitNotifications,
|
|
// Unit test support:
|
|
messageId: composer.messageId,
|
|
sentDate: composer.sentDate
|
|
});
|
|
|
|
storage.updateMessageHeader(
|
|
header.date,
|
|
header.id,
|
|
/* partOfSync */ false,
|
|
header,
|
|
/* body hint */ null);
|
|
}
|
|
|
|
return {
|
|
sendNextAvailableOutboxMessage: sendNextAvailableOutboxMessage
|
|
};
|
|
});
|