1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-10 11:15:01 -05:00

Merge remote-tracking branch 'origin/dev/sliding-window-sync'

This commit is contained in:
Tankred Hase 2013-12-12 14:27:20 +01:00
commit df34db590b
2 changed files with 697 additions and 176 deletions

View File

@ -386,31 +386,29 @@ define(function(require) {
} }
function doImapDelta() { function doImapDelta() {
self._imapListMessages({ self._imapSearch({
folder: folder.path folder: folder.path
}, function(err, headers) { }, function(err, uids) {
if (err) { if (err) {
self._account.busy = false; self._account.busy = false;
callback(err); callback(err);
return; return;
} }
// ignore non-whitelisted mails // uidWrappers is just to wrap the bare uids in an object { uid: 123 } so
var nonWhitelisted = _.filter(headers, function(header) { // the checkDelta function can treat it like something that resembles a stripped down email object...
return header.subject.indexOf(str.subjectPrefix) === -1; var uidWrappers = _.map(uids, function(uid) {
}); return {
nonWhitelisted.forEach(function(i) { uid: uid
headers.splice(headers.indexOf(i), 1); };
}); });
/* /*
* delta3: memory > imap => we deleted messages directly from the remote, remove from memory and storage * delta3: memory > imap => we deleted messages directly from the remote, remove from memory and storage
* delta4: imap > memory => we have new messages available, fetch to memory and storage * delta4: imap > memory => we have new messages available, fetch to memory and storage
* deltaF4: imap > memory => we changed flags directly on the remote, sync them to the storage and memory
*/ */
delta3 = checkDelta(folder.messages, headers); delta3 = checkDelta(folder.messages, uidWrappers);
delta4 = checkDelta(headers, folder.messages); delta4 = checkDelta(uidWrappers, folder.messages);
deltaF4 = checkFlags(headers, folder.messages);
doDelta3(); doDelta3();
@ -451,22 +449,36 @@ define(function(require) {
// we have new messages available, fetch to memory and storage // we have new messages available, fetch to memory and storage
// (downstream sync) // (downstream sync)
function doDelta4() { function doDelta4() {
// eliminate uids smaller than the biggest local uid, i.e. just fetch everything
// that came in AFTER the most recent email we have in memory. Keep in mind that
// uids are strictly ascending, so there can't be a NEW mail in the mailbox with a
// uid smaller than anything we've encountered before.
if (!_.isEmpty(folder.messages)) {
var localUids = _.pluck(folder.messages, 'uid'),
maxLocalUid = Math.max.apply(null, localUids);
// eliminate everything prior to maxLocalUid
delta4 = _.filter(delta4, function(uidWrapper) {
return uidWrapper.uid > maxLocalUid;
});
}
syncNextItem();
function syncNextItem() {
// no delta, we're done here // no delta, we're done here
if (_.isEmpty(delta4)) { if (_.isEmpty(delta4)) {
doDeltaF4(); doDeltaF4();
return; return;
} }
var after = _.after(delta4.length, function() {
doDeltaF4();
});
// delta4 contains the headers that are newly available on the remote // delta4 contains the headers that are newly available on the remote
delta4.forEach(function(imapHeader) { var nextUidWrapper = delta4.shift();
// get the whole message // get the whole message
self._imapGetMessage({ self._imapGetMessage({
folder: folder.path, folder: folder.path,
uid: imapHeader.uid uid: nextUidWrapper.uid
}, function(err, message) { }, function(err, message) {
if (err) { if (err) {
self._account.busy = false; self._account.busy = false;
@ -474,22 +486,22 @@ define(function(require) {
return; return;
} }
// create a bastard child of smtp and imap. // imap filtering may not be sufficient, since google filters out
// before thinking this is stupid, talk to the guys who wrote this. // non-alphabetical characters
imapHeader.id = message.id; if (message.subject.indexOf(str.subjectPrefix) === -1) {
imapHeader.body = message.body; syncNextItem();
imapHeader.html = message.html; return;
imapHeader.attachments = message.attachments; }
if (isVerificationMail(imapHeader)) { if (isVerificationMail(message)) {
verify(imapHeader, function(err) { verify(message, function(err) {
if (err) { if (err) {
self._account.busy = false; self._account.busy = false;
callback(err); callback(err);
return; return;
} }
after(); syncNextItem();
}); });
return; return;
} }
@ -497,7 +509,7 @@ define(function(require) {
// add the encrypted message to the local storage // add the encrypted message to the local storage
self._localStoreMessages({ self._localStoreMessages({
folder: folder.path, folder: folder.path,
emails: [imapHeader] emails: [message]
}, function(err) { }, function(err) {
if (err) { if (err) {
self._account.busy = false; self._account.busy = false;
@ -506,7 +518,7 @@ define(function(require) {
} }
// decrypt and add to folder in memory // decrypt and add to folder in memory
handleMessage(imapHeader, function(err, cleartextMessage) { handleMessage(message, function(err, cleartextMessage) {
if (err) { if (err) {
self._account.busy = false; self._account.busy = false;
callback(err); callback(err);
@ -514,40 +526,109 @@ define(function(require) {
} }
folder.messages.push(cleartextMessage); folder.messages.push(cleartextMessage);
after(); syncNextItem();
});
}); });
}); });
}); });
} }
}
});
// we have a mismatch concerning flags between imap and memory.
// pull changes from imap.
function doDeltaF4() { function doDeltaF4() {
function finishSync() { var answeredUids, unreadUids;
getUnreadUids();
// find all the relevant unread mails
function getUnreadUids() {
self._imapSearch({
folder: folder.path,
unread: true
}, function(err, uids) {
if (err) {
self._account.busy = false; self._account.busy = false;
folder.count = _.filter(folder.messages, function(msg) { callback(err);
return msg.unread === true; return;
}).length;
callback();
} }
// we're done here, let's get all the answered mails
unreadUids = uids;
getAnsweredUids();
});
}
// find all the relevant answered mails
function getAnsweredUids() {
// find all the relevant answered mails
self._imapSearch({
folder: folder.path,
answered: true
}, function(err, uids) {
if (err) {
self._account.busy = false;
callback(err);
return;
}
// we're done here, let's update what we have in memory and persist that!
answeredUids = uids;
updateFlags();
});
}
function updateFlags() {
// deltaF4: imap > memory => we changed flags directly on the remote, sync them to the storage and memory
deltaF4 = [];
folder.messages.forEach(function(msg) {
// if the message's uid is among the uids that should be unread,
// AND the message is not unread, we clearly have to change that
var shouldBeUnread = _.contains(unreadUids, msg.uid);
if (msg.unread === shouldBeUnread) {
// everything is in order, we're good here
return;
}
msg.unread = shouldBeUnread;
deltaF4.push(msg);
});
folder.messages.forEach(function(msg) {
// if the message's uid is among the uids that should be answered,
// AND the message is not answered, we clearly have to change that
var shouldBeAnswered = _.contains(answeredUids, msg.uid);
if (msg.answered === shouldBeAnswered) {
// everything is in order, we're good here
return;
}
msg.answered = shouldBeAnswered;
deltaF4.push(msg);
});
// maybe a mail had BOTH flags wrong, so let's create
// a duplicate-free version of deltaF4
deltaF4 = _.uniq(deltaF4);
// everything up to date? fine, we're done!
if (_.isEmpty(deltaF4)) { if (_.isEmpty(deltaF4)) {
finishSync(); finishSync();
return; return;
} }
var after = _.after(deltaF4.length, function() { var after = _.after(deltaF4.length, function() {
// we're doing updating everything
finishSync(); finishSync();
}); });
// deltaF4 contains the imap headers that have changed flags // alright, so let's sync the corrected messages
deltaF4.forEach(function(imapHeader) { deltaF4.forEach(function(inMemoryMessage) {
// do a short round trip to the database to avoid re-encrypting, // do a short round trip to the database to avoid re-encrypting,
// instead use the encrypted object in the storage // instead use the encrypted object in the storage
self._localListMessages({ self._localListMessages({
folder: folder.path, folder: folder.path,
uid: imapHeader.uid uid: inMemoryMessage.uid
}, function(err, storedMessages) { }, function(err, storedMessages) {
if (err) { if (err) {
self._account.busy = false; self._account.busy = false;
@ -556,9 +637,10 @@ define(function(require) {
} }
var storedMessage = storedMessages[0]; var storedMessage = storedMessages[0];
storedMessage.unread = imapHeader.unread; storedMessage.unread = inMemoryMessage.unread;
storedMessage.answered = imapHeader.answered; storedMessage.answered = inMemoryMessage.answered;
// persist the modified object
self._localStoreMessages({ self._localStoreMessages({
folder: folder.path, folder: folder.path,
emails: [storedMessage] emails: [storedMessage]
@ -569,19 +651,24 @@ define(function(require) {
return; return;
} }
// after the metadata of the encrypted object has changed, proceed with the live object // and we're done.
var inMemoryMessage = _.findWhere(folder.messages, {
uid: imapHeader.uid
});
inMemoryMessage.unread = imapHeader.unread;
inMemoryMessage.answered = imapHeader.answered;
after(); after();
}); });
}); });
}); });
} }
});
function finishSync() {
// after all the tags are up to date, let's adjust the unread mail count
folder.count = _.filter(folder.messages, function(msg) {
return msg.unread === true;
}).length;
// allow the next sync to take place
self._account.busy = false;
callback();
}
}
} }
/* /*
@ -959,10 +1046,13 @@ define(function(require) {
}; };
/** /**
* List messages from an imap folder. This will not yet fetch the email body. * Returns the relevant messages corresponding to the search terms in the options
* @param {String} options.folderName The name of the imap folder. * @param {String} options.folder The folder's path
* @param {Boolean} options.answered (optional) Mails with or without the \Answered flag set.
* @param {Boolean} options.unread (optional) Mails with or without the \Seen flag set.
* @param {Function} callback(error, uids) invoked with the uids of messages matching the search terms, or an error object if an error occurred
*/ */
EmailDAO.prototype._imapListMessages = function(options, callback) { EmailDAO.prototype._imapSearch = function(options, callback) {
if (!this._imapClient) { if (!this._imapClient) {
callback({ callback({
errMsg: 'Client is currently offline!', errMsg: 'Client is currently offline!',
@ -971,11 +1061,19 @@ define(function(require) {
return; return;
} }
this._imapClient.listMessages({ var o = {
path: options.folder, path: options.folder,
offset: 0, subject: str.subjectPrefix
length: 100 };
}, callback);
if (typeof options.answered !== 'undefined') {
o.answered = options.answered;
}
if (typeof options.unread !== 'undefined') {
o.unread = options.unread;
}
this._imapClient.search(o, callback);
}; };
EmailDAO.prototype._imapDeleteMessage = function(options, callback) { EmailDAO.prototype._imapDeleteMessage = function(options, callback) {
@ -998,6 +1096,8 @@ define(function(require) {
* @param {String} options.messageId The * @param {String} options.messageId The
*/ */
EmailDAO.prototype._imapGetMessage = function(options, callback) { EmailDAO.prototype._imapGetMessage = function(options, callback) {
var self = this;
if (!this._imapClient) { if (!this._imapClient) {
callback({ callback({
errMsg: 'Client is currently offline!', errMsg: 'Client is currently offline!',
@ -1006,10 +1106,37 @@ define(function(require) {
return; return;
} }
this._imapClient.getMessagePreview({ self._imapClient.listMessagesByUid({
path: options.folder,
firstUid: options.uid,
lastUid: options.uid
}, function(err, imapHeaders) {
if (err) {
callback(err);
return;
}
var imapHeader = imapHeaders[0];
self._imapClient.getMessagePreview({
path: options.folder, path: options.folder,
uid: options.uid uid: options.uid
}, callback); }, function(err, message) {
if (err) {
callback(err);
return;
}
// create a bastard child of smtp and imap. before thinking this is stupid, talk to the guys who wrote this.
// p.s. it's a parsing issue.
imapHeader.id = message.id;
imapHeader.body = message.body;
imapHeader.html = message.html;
imapHeader.attachments = message.attachments;
callback(null, imapHeader);
});
});
}; };
/** /**

File diff suppressed because it is too large Load Diff