mirror of
https://github.com/moparisthebest/mail
synced 2024-11-29 12:22:22 -05:00
Merge remote-tracking branch 'origin/dev/sliding-window-sync'
This commit is contained in:
commit
df34db590b
@ -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
Loading…
Reference in New Issue
Block a user