Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
280 lines
8.9 KiB
JavaScript
Executable file
280 lines
8.9 KiB
JavaScript
Executable file
define(
|
|
[
|
|
'../imapchew',
|
|
'../../allback',
|
|
'exports'
|
|
],
|
|
function(
|
|
$imapchew,
|
|
allback,
|
|
exports
|
|
) {
|
|
|
|
/**
|
|
* Fetch parameters to get the headers / bodystructure; exists to reuse the
|
|
* object since every fetch is the same.
|
|
*
|
|
* We are intentionally not using ENVELOPE because Thunderbird intentionally
|
|
* defaults to to not using ENVELOPE. Per bienvenu in
|
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=402594#c33 "We stopped using it
|
|
* by default because servers often had issues with it so it was more trouble
|
|
* than it was worth."
|
|
*/
|
|
var INITIAL_FETCH_PARAMS = [
|
|
'BODYSTRUCTURE',
|
|
'INTERNALDATE',
|
|
'FLAGS',
|
|
'BODY.PEEK[HEADER.FIELDS (FROM TO CC BCC SUBJECT REPLY-TO MESSAGE-ID REFERENCES)]',
|
|
];
|
|
|
|
/**
|
|
* Fetch parameters to just get the flags.
|
|
*/
|
|
var FLAG_FETCH_PARAMS = ['FLAGS'];
|
|
|
|
// Number of bytes to fetch for snippet.
|
|
var SNIPPET_BYTES = 256;
|
|
|
|
// See the `ImapFolderConn` block comment for rationale.
|
|
var KNOWN_HEADERS_AGGR_COST = 20,
|
|
KNOWN_HEADERS_PER_COST = 1,
|
|
NEW_HEADERS_AGGR_COST = 20,
|
|
NEW_HEADERS_PER_COST = 5,
|
|
NEW_BODIES_PER_COST = 30;
|
|
|
|
/**
|
|
* Given a list of new-to-us UIDs and known-to-us UIDs and their corresponding
|
|
* headers, synchronize the flags for the known UIDs' headers and fetch and
|
|
* create the header and body objects for the new UIDS.
|
|
*
|
|
* First we fetch the headers/bodystructures for the new UIDs all in one go;
|
|
* all of these headers are going to end up in-memory at the same time, so
|
|
* batching won't let us reduce the overhead right now. We process them
|
|
* to determine the body parts we should fetch as the results come in. Once
|
|
* we have them all, we sort them by date, endTS-to-startTS for the third
|
|
* step and start issuing/pipelining the requests.
|
|
*
|
|
* Second, we issue the flag update requests for the known-to-us UIDs. This
|
|
* is done second so it can help avoid wasting the latency of the round-trip
|
|
* that would otherwise result between steps one and three. (Although we
|
|
* could also mitigate that by issuing some step three requests even as
|
|
* the step one requests are coming in; our sorting doesn't have to be
|
|
* perfect and may already be reasonably well ordered if UIDs correlate
|
|
* with internal date well.)
|
|
*
|
|
* Third, we fetch the body parts in our newest-to-startTS order, adding
|
|
* finalized headers and bodies as we go.
|
|
*
|
|
* == Usage
|
|
*
|
|
* var sync = new ImapUidSync({
|
|
* initialProgress: 0.25,
|
|
* connection: (ImapConnection),
|
|
*
|
|
* storage: (FolderStorage),
|
|
*
|
|
* knownHeaders: [],
|
|
*
|
|
* knownUIDs: [],
|
|
* newUIDs: []
|
|
* });
|
|
*
|
|
*/
|
|
function Sync(options) {
|
|
// storage and connections
|
|
this.storage = options.storage;
|
|
this.connection = options.connection;
|
|
|
|
this.knownHeaders = options.knownHeaders || [];
|
|
|
|
this.knownUIDs = options.knownUIDs || [];
|
|
this.newUIDs = options.newUIDs || [];
|
|
|
|
this._progress = options.initialProgress || 0.25;
|
|
|
|
this._progressCost =
|
|
(this.knownUIDs.length ? KNOWN_HEADERS_AGGR_COST : 0) +
|
|
KNOWN_HEADERS_PER_COST * this.knownUIDs.length +
|
|
(this.newUIDs.length ? NEW_HEADERS_AGGR_COST : 0) +
|
|
NEW_HEADERS_PER_COST * this.newUIDs.length;
|
|
|
|
// events
|
|
this.onprogress = null;
|
|
this.oncomplete = null;
|
|
|
|
this._beginSync();
|
|
}
|
|
|
|
Sync.prototype = {
|
|
_updateProgress: function(newProgress) {
|
|
this._progress += newProgress;
|
|
|
|
if (this.onprogress) {
|
|
this.onprogress(
|
|
0.25 + 0.75 * (this._progress / this._progressCost)
|
|
);
|
|
}
|
|
},
|
|
|
|
_beginSync: function() {
|
|
var latch = allback.latch();
|
|
|
|
if (this.newUIDs.length) {
|
|
this._handleNewUids(latch.defer('new'));
|
|
}
|
|
|
|
if (this.knownUIDs.length) {
|
|
this._handleKnownUids(latch.defer('known'));
|
|
}
|
|
|
|
latch.then(function() {
|
|
if (!this.oncomplete) {
|
|
return;
|
|
}
|
|
|
|
this.oncomplete(this.newUIDs.length, this.knownUIDs.length);
|
|
}.bind(this));
|
|
},
|
|
|
|
_handleNewUids: function(callback) {
|
|
var pendingSnippets = [];
|
|
var self = this;
|
|
|
|
this.connection.listMessages(
|
|
this.newUIDs,
|
|
INITIAL_FETCH_PARAMS,
|
|
{ byUid: true },
|
|
function (err, messages) {
|
|
if (err) {
|
|
// XXX the UID might have disappeared already? we might need to have
|
|
// our initiating command re-do whatever it's up to. Alternatively,
|
|
// we could drop back from a bulk fetch to a one-by-one fetch.
|
|
console.warn('New UIDs fetch error, ideally harmless:', err);
|
|
callback();
|
|
return;
|
|
}
|
|
|
|
var latch = allback.latch();
|
|
messages.forEach(function(msg) {
|
|
// Filter out the \Recent flag; our old imap library didn't
|
|
// pass it along (complaining about it being useless), so we
|
|
// don't either.
|
|
var recentIdx = msg.flags.indexOf('\\Recent');
|
|
if (recentIdx !== -1) {
|
|
msg.flags.splice(recentIdx, 1);
|
|
}
|
|
|
|
try {
|
|
// at this point we can build the first batch of header/body
|
|
// information.
|
|
var chewRep = $imapchew.chewHeaderAndBodyStructure(
|
|
msg,
|
|
self.storage.folderId,
|
|
self.storage._issueNewHeaderId()
|
|
);
|
|
|
|
chewRep.header.bytesToDownloadForBodyDisplay =
|
|
$imapchew.calculateBytesToDownloadForImapBodyDisplay(
|
|
chewRep.bodyInfo);
|
|
|
|
pendingSnippets.push(chewRep);
|
|
|
|
// flush our body/header information ? should we do some sorting,
|
|
// etc.. here or just let the UI update ASAP?
|
|
self.storage.addMessageHeader(
|
|
chewRep.header, chewRep.bodyInfo, latch.defer());
|
|
self.storage.addMessageBody(
|
|
chewRep.header, chewRep.bodyInfo, latch.defer());
|
|
}
|
|
catch (ex) {
|
|
// it's fine for us to not add bad messages to the database
|
|
// XXX do plumb the logger through here eventually
|
|
console.warn('message problem, skipping message', ex, '\n',
|
|
ex.stack);
|
|
}
|
|
}.bind(this));
|
|
|
|
latch.then(callback); // our caller doesn't care about its args
|
|
}.bind(this));
|
|
},
|
|
|
|
_handleKnownUids: function(callback) {
|
|
var self = this;
|
|
this.connection.listMessages(
|
|
self.knownUIDs,
|
|
FLAG_FETCH_PARAMS,
|
|
{ byUid: true },
|
|
function(err, messages) {
|
|
if (err) {
|
|
console.warn('Known UIDs fetch error, ideally harmless:', err);
|
|
callback();
|
|
return;
|
|
}
|
|
|
|
var latch = allback.latch();
|
|
messages.forEach(function(msg, i) {
|
|
console.log('FETCHED', i, 'known id', self.knownHeaders[i].id,
|
|
'known srvid', self.knownHeaders[i].srvid,
|
|
'actual id', msg.uid);
|
|
|
|
// Filter out the \Recent flag; our old imap library didn't
|
|
// pass it along (complaining about it being useless), so we
|
|
// don't either.
|
|
var recentIdx = msg.flags.indexOf('\\Recent');
|
|
if (recentIdx !== -1) {
|
|
msg.flags.splice(recentIdx, 1);
|
|
}
|
|
|
|
// RFC 3501 doesn't require that we get results in the order we
|
|
// request them, so use indexOf if things don't line up. (In fact,
|
|
// dovecot sorts them, so we might just want to sort ours too.)
|
|
if (self.knownHeaders[i].srvid !== msg.uid) {
|
|
i = self.knownUIDs.indexOf(msg.uid);
|
|
// If it's telling us about a message we don't know about, run away.
|
|
if (i === -1) {
|
|
console.warn("Server fetch reports unexpected message:", msg.uid);
|
|
return;
|
|
}
|
|
}
|
|
var header = self.knownHeaders[i];
|
|
|
|
// msg.flags is not sorted in browserbox; sort here before comparing.
|
|
var sortedExistingFlags = header.flags.slice();
|
|
sortedExistingFlags.sort();
|
|
msg.flags.sort();
|
|
|
|
// Update number of unread messages
|
|
if (header.flags.indexOf('\\Seen') === -1 &&
|
|
msg.flags.indexOf('\\Seen') !== -1) {
|
|
self.storage.folderMeta.unreadCount--;
|
|
} else if (header.flags.indexOf('\\Seen') !== -1 &&
|
|
msg.flags.indexOf('\\Seen') === -1) {
|
|
self.storage.folderMeta.unreadCount++;
|
|
}
|
|
|
|
if (header.flags.toString() !== msg.flags.toString()) {
|
|
console.warn(' FLAGS: "' + header.flags.toString() + '" VS "' +
|
|
msg.flags.toString() + '"');
|
|
header.flags = msg.flags;
|
|
self.storage.updateMessageHeader(
|
|
header.date, header.id, true, header, /* body hint */ null,
|
|
latch.defer());
|
|
}
|
|
else {
|
|
self.storage.unchangedMessageHeader(header);
|
|
}
|
|
});
|
|
|
|
// the fetch results will be bursty, so just update all at once
|
|
self._updateProgress(KNOWN_HEADERS_AGGR_COST +
|
|
KNOWN_HEADERS_PER_COST * self.knownUIDs.length);
|
|
|
|
latch.then(callback); // our caller doesn't care about its args
|
|
}.bind(this));
|
|
}
|
|
|
|
};
|
|
|
|
exports.Sync = Sync;
|
|
|
|
});
|