mirror of
https://github.com/moparisthebest/mail
synced 2024-12-23 15:58:49 -05:00
commit
04ed3dab68
@ -107,7 +107,7 @@ module.exports = function(grunt) {
|
||||
},
|
||||
js: {
|
||||
files: ['src/js/**/*.js'],
|
||||
tasks: ['copy:js']
|
||||
tasks: ['copy:js', 'copy:integration']
|
||||
},
|
||||
lib: {
|
||||
files: ['src/lib/**/*.js'],
|
||||
|
@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-lib": "~0.2.1",
|
||||
"imap-client": "~0.3.7",
|
||||
"imap-client": "~0.4.0",
|
||||
"mailreader": "~0.3.5",
|
||||
"pgpmailer": "~0.3.11",
|
||||
"pgpbuilder": "~0.3.7",
|
||||
@ -41,4 +41,4 @@
|
||||
"grunt-contrib-compress": "~0.5.2",
|
||||
"grunt-node-webkit-builder": "~0.1.17"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,9 +92,10 @@ define(function(require) {
|
||||
iconPath: '/img/icon.png',
|
||||
verificationUrl: '/verify/',
|
||||
verificationUuidLength: 36,
|
||||
dbVersion: 4,
|
||||
dbVersion: 5,
|
||||
appVersion: appVersion,
|
||||
outboxMailboxPath: 'OUTBOX',
|
||||
outboxMailboxName: 'Outbox',
|
||||
outboxMailboxType: 'Outbox'
|
||||
};
|
||||
|
||||
|
@ -533,92 +533,25 @@ define(function(require) {
|
||||
this.cc = [{
|
||||
address: 'john.doe@gmail.com'
|
||||
}]; // list of receivers
|
||||
if (attachments) {
|
||||
// body structure with three attachments
|
||||
this.bodystructure = {
|
||||
"1": {
|
||||
"part": "1",
|
||||
"type": "text/plain",
|
||||
"parameters": {
|
||||
"charset": "us-ascii"
|
||||
},
|
||||
"encoding": "7bit",
|
||||
"size": 9,
|
||||
"lines": 2
|
||||
},
|
||||
"2": {
|
||||
"part": "2",
|
||||
"type": "application/octet-stream",
|
||||
"parameters": {
|
||||
"name": "a.md"
|
||||
},
|
||||
"encoding": "7bit",
|
||||
"size": 123,
|
||||
"disposition": [{
|
||||
"type": "attachment",
|
||||
"filename": "a.md"
|
||||
}]
|
||||
},
|
||||
"3": {
|
||||
"part": "3",
|
||||
"type": "application/octet-stream",
|
||||
"parameters": {
|
||||
"name": "b.md"
|
||||
},
|
||||
"encoding": "7bit",
|
||||
"size": 456,
|
||||
"disposition": [{
|
||||
"type": "attachment",
|
||||
"filename": "b.md"
|
||||
}]
|
||||
},
|
||||
"4": {
|
||||
"part": "4",
|
||||
"type": "application/octet-stream",
|
||||
"parameters": {
|
||||
"name": "c.md"
|
||||
},
|
||||
"encoding": "7bit",
|
||||
"size": 789,
|
||||
"disposition": [{
|
||||
"type": "attachment",
|
||||
"filename": "c.md"
|
||||
}]
|
||||
},
|
||||
"type": "multipart/mixed"
|
||||
};
|
||||
this.attachments = [{
|
||||
"filename": "a.md",
|
||||
"filesize": 123,
|
||||
"mimeType": "text/x-markdown",
|
||||
"part": "2",
|
||||
"content": null
|
||||
}, {
|
||||
"filename": "b.md",
|
||||
"filesize": 456,
|
||||
"mimeType": "text/x-markdown",
|
||||
"part": "3",
|
||||
"content": null
|
||||
}, {
|
||||
"filename": "c.md",
|
||||
"filesize": 789,
|
||||
"mimeType": "text/x-markdown",
|
||||
"part": "4",
|
||||
"content": null
|
||||
}];
|
||||
} else {
|
||||
this.bodystructure = {
|
||||
"part": "1",
|
||||
"type": "text/plain",
|
||||
"parameters": {
|
||||
"charset": "us-ascii"
|
||||
},
|
||||
"encoding": "7bit",
|
||||
"size": 9,
|
||||
"lines": 2
|
||||
};
|
||||
this.attachments = [];
|
||||
}
|
||||
this.attachments = attachments ? [{
|
||||
"filename": "a.md",
|
||||
"filesize": 123,
|
||||
"mimeType": "text/x-markdown",
|
||||
"part": "2",
|
||||
"content": null
|
||||
}, {
|
||||
"filename": "b.md",
|
||||
"filesize": 456,
|
||||
"mimeType": "text/x-markdown",
|
||||
"part": "3",
|
||||
"content": null
|
||||
}, {
|
||||
"filename": "c.md",
|
||||
"filesize": 789,
|
||||
"mimeType": "text/x-markdown",
|
||||
"part": "4",
|
||||
"content": null
|
||||
}] : [];
|
||||
this.unread = unread;
|
||||
this.answered = answered;
|
||||
this.sentDate = new Date('Thu Sep 19 2013 20:41:23 GMT+0200 (CEST)');
|
||||
@ -639,13 +572,14 @@ define(function(require) {
|
||||
this.decrypted = true;
|
||||
};
|
||||
|
||||
var dummys = [new Email(true, true), new Email(true, false, true), new Email(false, true, true), new Email(false, true)];
|
||||
|
||||
for (var i = 0; i < 100; i++) {
|
||||
dummys.push(new Email(false));
|
||||
var dummies = [],
|
||||
i = 100;
|
||||
while (i--) {
|
||||
// every second/third/fourth dummy mail with unread/attachments/answered
|
||||
dummies.push(new Email((i % 2 === 0), (i % 3 === 0), (i % 5 === 0)));
|
||||
}
|
||||
|
||||
return dummys;
|
||||
return dummies;
|
||||
}
|
||||
|
||||
return MailListCtrl;
|
||||
|
@ -6,6 +6,38 @@ define(function(require) {
|
||||
config = require('js/app-config').config,
|
||||
str = require('js/app-config').string;
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// Constants
|
||||
//
|
||||
//
|
||||
|
||||
var FOLDER_DB_TYPE = 'folders';
|
||||
|
||||
var SYNC_TYPE_NEW = 'new';
|
||||
var SYNC_TYPE_DELETED = 'deleted';
|
||||
var SYNC_TYPE_MSGS = 'messages';
|
||||
|
||||
var FOLDER_TYPE_INBOX = 'Inbox';
|
||||
var FOLDER_TYPE_SENT = 'Sent';
|
||||
var FOLDER_TYPE_DRAFTS = 'Drafts';
|
||||
var FOLDER_TYPE_TRASH = 'Trash';
|
||||
|
||||
var MSG_ATTR_UID = 'uid';
|
||||
var MSG_PART_ATTR_CONTENT = 'content';
|
||||
var MSG_PART_TYPE_ATTACHMENT = 'attachment';
|
||||
var MSG_PART_TYPE_ENCRYPTED = 'encrypted';
|
||||
var MSG_PART_TYPE_SIGNED = 'signed';
|
||||
var MSG_PART_TYPE_TEXT = 'text';
|
||||
var MSG_PART_TYPE_HTML = 'html';
|
||||
|
||||
//
|
||||
//
|
||||
// Email Dao
|
||||
//
|
||||
//
|
||||
|
||||
/**
|
||||
* High-level data access object that orchestrates everything around the handling of encrypted mails:
|
||||
* PGP de-/encryption, receiving via IMAP, sending via SMTP, MIME parsing, local db persistence
|
||||
@ -253,8 +285,8 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
var storedUids = _.pluck(storedMessages, 'uid'),
|
||||
memoryUids = _.pluck(folder.messages, 'uid'),
|
||||
var storedUids = _.pluck(storedMessages, MSG_ATTR_UID),
|
||||
memoryUids = _.pluck(folder.messages, MSG_ATTR_UID),
|
||||
newUids = _.difference(storedUids, memoryUids), // uids of messages that are not yet in memory
|
||||
removedUids = _.difference(memoryUids, storedUids); // uids of messages that are no longer stored on the disk
|
||||
|
||||
@ -370,7 +402,7 @@ define(function(require) {
|
||||
// this enables us to already show the attachment clip in the message list ui
|
||||
messages.forEach(function(message) {
|
||||
message.attachments = message.bodyParts.filter(function(bodyPart) {
|
||||
return bodyPart.type === 'attachment';
|
||||
return bodyPart.type === MSG_PART_TYPE_ATTACHMENT;
|
||||
});
|
||||
});
|
||||
|
||||
@ -401,7 +433,7 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
var body = _.pluck(filterBodyParts(parsedBodyParts, 'text'), 'content').join('\n'),
|
||||
var body = _.pluck(filterBodyParts(parsedBodyParts, MSG_PART_TYPE_TEXT), MSG_PART_ATTR_CONTENT).join('\n'),
|
||||
verificationUrlPrefix = config.cloudUrl + config.verificationUrl,
|
||||
uuid = body.split(verificationUrlPrefix).pop().substr(0, config.verificationUuidLength),
|
||||
uuidRegex = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/;
|
||||
@ -645,10 +677,10 @@ define(function(require) {
|
||||
// we need to fetch the content for non-attachment body parts (encrypted, signed, text, html, resources referenced from the html)
|
||||
// but we spare the effort and fetch attachment content later upon explicit user request.
|
||||
var contentParts = localMessage.bodyParts.filter(function(bodyPart) {
|
||||
return bodyPart.type !== "attachment" || (bodyPart.type === "attachment" && bodyPart.id);
|
||||
return bodyPart.type !== MSG_PART_TYPE_ATTACHMENT || (bodyPart.type === MSG_PART_TYPE_ATTACHMENT && bodyPart.id);
|
||||
});
|
||||
var attachmentParts = localMessage.bodyParts.filter(function(bodyPart) {
|
||||
return bodyPart.type === "attachment" && !bodyPart.id;
|
||||
return bodyPart.type === MSG_PART_TYPE_ATTACHMENT && !bodyPart.id;
|
||||
});
|
||||
|
||||
// do we need to fetch content from the imap server?
|
||||
@ -700,7 +732,7 @@ define(function(require) {
|
||||
function extractContent() {
|
||||
if (message.encrypted) {
|
||||
// show the encrypted message
|
||||
message.body = filterBodyParts(message.bodyParts, 'encrypted')[0].content;
|
||||
message.body = filterBodyParts(message.bodyParts, MSG_PART_TYPE_ENCRYPTED)[0].content;
|
||||
return done();
|
||||
}
|
||||
|
||||
@ -708,13 +740,13 @@ define(function(require) {
|
||||
|
||||
if (message.signed) {
|
||||
// PGP/MIME signed
|
||||
var signedRoot = filterBodyParts(message.bodyParts, 'signed')[0]; // in case of a signed message, you only want to show the signed content and ignore the rest
|
||||
var signedRoot = filterBodyParts(message.bodyParts, MSG_PART_TYPE_SIGNED)[0]; // in case of a signed message, you only want to show the signed content and ignore the rest
|
||||
message.signedMessage = signedRoot.signedMessage;
|
||||
message.signature = signedRoot.signature;
|
||||
root = signedRoot.content;
|
||||
}
|
||||
|
||||
var body = _.pluck(filterBodyParts(root, 'text'), 'content').join('\n');
|
||||
var body = _.pluck(filterBodyParts(root, MSG_PART_TYPE_TEXT), MSG_PART_ATTR_CONTENT).join('\n');
|
||||
|
||||
/*
|
||||
* if the message is plain text and contains pgp/inline, we are only interested in the encrypted
|
||||
@ -730,7 +762,7 @@ define(function(require) {
|
||||
|
||||
// replace the bodyParts info with an artificial bodyPart of type "encrypted"
|
||||
message.bodyParts = [{
|
||||
type: 'encrypted',
|
||||
type: MSG_PART_TYPE_ENCRYPTED,
|
||||
content: pgpInlineMatch[0],
|
||||
_isPgpInline: true // used internally to avoid trying to parse non-MIME text with the mailreader
|
||||
}];
|
||||
@ -770,8 +802,8 @@ define(function(require) {
|
||||
function setBody() {
|
||||
message.body = body;
|
||||
if (!message.clearSignedMessage) {
|
||||
message.attachments = filterBodyParts(root, 'attachment');
|
||||
message.html = _.pluck(filterBodyParts(root, 'html'), 'content').join('\n');
|
||||
message.attachments = filterBodyParts(root, MSG_PART_TYPE_ATTACHMENT);
|
||||
message.html = _.pluck(filterBodyParts(root, MSG_PART_TYPE_HTML), MSG_PART_ATTR_CONTENT).join('\n');
|
||||
inlineExternalImages(message);
|
||||
}
|
||||
|
||||
@ -863,7 +895,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
// get the receiver's public key to check the message signature
|
||||
var encryptedNode = filterBodyParts(message.bodyParts, 'encrypted')[0];
|
||||
var encryptedNode = filterBodyParts(message.bodyParts, MSG_PART_TYPE_ENCRYPTED)[0];
|
||||
var senderKey = senderPublicKey ? senderPublicKey.publicKey : undefined;
|
||||
self._pgp.decrypt(encryptedNode.content, senderKey, function(err, decrypted, signaturesValid) {
|
||||
if (err || !decrypted) {
|
||||
@ -897,7 +929,7 @@ define(function(require) {
|
||||
if (!message.signed) {
|
||||
// message had no signature in the ciphertext, so there's a little extra effort to be done here
|
||||
// is there a signed MIME node?
|
||||
var signedRoot = filterBodyParts(root, 'signed')[0];
|
||||
var signedRoot = filterBodyParts(root, MSG_PART_TYPE_SIGNED)[0];
|
||||
if (!signedRoot) {
|
||||
// no signed MIME node, obviously an unsigned PGP/MIME message
|
||||
return setBody();
|
||||
@ -927,9 +959,9 @@ define(function(require) {
|
||||
function setBody() {
|
||||
// we have successfully interpreted the descrypted message,
|
||||
// so let's update the views on the message parts
|
||||
message.body = _.pluck(filterBodyParts(root, 'text'), 'content').join('\n');
|
||||
message.html = _.pluck(filterBodyParts(root, 'html'), 'content').join('\n');
|
||||
message.attachments = _.reject(filterBodyParts(root, 'attachment'), function(attmt) {
|
||||
message.body = _.pluck(filterBodyParts(root, MSG_PART_TYPE_TEXT), MSG_PART_ATTR_CONTENT).join('\n');
|
||||
message.html = _.pluck(filterBodyParts(root, MSG_PART_TYPE_HTML), MSG_PART_ATTR_CONTENT).join('\n');
|
||||
message.attachments = _.reject(filterBodyParts(root, MSG_PART_TYPE_ATTACHMENT), function(attmt) {
|
||||
// remove the pgp-signature from the attachments
|
||||
return attmt.mimeType === "application/pgp-signature";
|
||||
});
|
||||
@ -989,7 +1021,7 @@ define(function(require) {
|
||||
|
||||
// upload the sent message to the sent folder if necessary
|
||||
var sentFolder = _.findWhere(self._account.folders, {
|
||||
type: 'Sent'
|
||||
type: FOLDER_TYPE_SENT
|
||||
});
|
||||
|
||||
if (self.ignoreUploadOnSent || !sentFolder || !rfcText) {
|
||||
@ -1039,7 +1071,7 @@ define(function(require) {
|
||||
|
||||
// upload the sent message to the sent folder if necessary
|
||||
var sentFolder = _.findWhere(self._account.folders, {
|
||||
type: 'Sent'
|
||||
type: FOLDER_TYPE_SENT
|
||||
});
|
||||
|
||||
if (self.ignoreUploadOnSent || !sentFolder || !rfcText) {
|
||||
@ -1130,7 +1162,7 @@ define(function(require) {
|
||||
|
||||
var uids, highestModseq, lastUid;
|
||||
|
||||
uids = _.pluck(folder.messages, 'uid').sort(function(a, b) {
|
||||
uids = _.pluck(folder.messages, MSG_ATTR_UID).sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
lastUid = uids[uids.length - 1];
|
||||
@ -1153,7 +1185,7 @@ define(function(require) {
|
||||
|
||||
// set up the imap client to listen for changes in the inbox
|
||||
var inbox = _.findWhere(self._account.folders, {
|
||||
type: 'Inbox'
|
||||
type: FOLDER_TYPE_INBOX
|
||||
});
|
||||
|
||||
if (!inbox) {
|
||||
@ -1199,14 +1231,14 @@ define(function(require) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.type === 'new') {
|
||||
if (options.type === SYNC_TYPE_NEW) {
|
||||
// new messages available on imap, fetch from imap and store to disk and memory
|
||||
self.fetchMessages({
|
||||
folder: folder,
|
||||
firstUid: Math.min.apply(null, options.list),
|
||||
lastUid: Math.max.apply(null, options.list)
|
||||
}, self.onError.bind(self));
|
||||
} else if (options.type === 'deleted') {
|
||||
} else if (options.type === SYNC_TYPE_DELETED) {
|
||||
// messages have been deleted, remove from local storage and memory
|
||||
options.list.forEach(function(uid) {
|
||||
var message = _.findWhere(folder.messages, {
|
||||
@ -1223,7 +1255,7 @@ define(function(require) {
|
||||
localOnly: true
|
||||
}, self.onError.bind(self));
|
||||
});
|
||||
} else if (options.type === 'messages') {
|
||||
} else if (options.type === SYNC_TYPE_MSGS) {
|
||||
// NB! several possible reasons why this could be called.
|
||||
// if a message in the array has uid value and flag array, it had a possible flag update
|
||||
options.list.forEach(function(changedMsg) {
|
||||
@ -1268,13 +1300,12 @@ define(function(require) {
|
||||
* @param {Function} callback Invoked when the folders are up to date
|
||||
*/
|
||||
EmailDAO.prototype._initFoldersFromDisk = function(callback) {
|
||||
var self = this,
|
||||
folderDbType = 'folders';
|
||||
var self = this;
|
||||
|
||||
self.busy(); // start the spinner
|
||||
|
||||
// fetch list from local cache
|
||||
self._devicestorage.listItems(folderDbType, 0, null, function(err, stored) {
|
||||
self._devicestorage.listItems(FOLDER_DB_TYPE, 0, null, function(err, stored) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@ -1297,8 +1328,7 @@ define(function(require) {
|
||||
* @param {Function} callback Invoked when the folders are up to date
|
||||
*/
|
||||
EmailDAO.prototype._initFoldersFromImap = function(callback) {
|
||||
var self = this,
|
||||
folderDbType = 'folders';
|
||||
var self = this;
|
||||
|
||||
self.busy(); // start the spinner
|
||||
|
||||
@ -1308,57 +1338,73 @@ define(function(require) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
// this array is dropped directly into the ui to create the folder list
|
||||
var folders = [];
|
||||
if (wellKnownFolders.inbox) {
|
||||
folders.push(wellKnownFolders.inbox);
|
||||
}
|
||||
if (wellKnownFolders.sent) {
|
||||
folders.push(wellKnownFolders.sent);
|
||||
}
|
||||
folders.push({
|
||||
type: 'Outbox',
|
||||
// initialize the folders to something meaningful if that hasn't already happened
|
||||
self._account.folders = self._account.folders || [];
|
||||
|
||||
// smuggle the outbox into the well known folders, which is obv not present on imap...
|
||||
wellKnownFolders[config.outboxMailboxType] = [{
|
||||
name: config.outboxMailboxName,
|
||||
type: config.outboxMailboxType,
|
||||
path: config.outboxMailboxPath
|
||||
});
|
||||
if (wellKnownFolders.drafts) {
|
||||
folders.push(wellKnownFolders.drafts);
|
||||
}
|
||||
if (wellKnownFolders.trash) {
|
||||
folders.push(wellKnownFolders.trash);
|
||||
}
|
||||
}];
|
||||
|
||||
var foldersChanged = false; // indicates if are there any new/removed folders?
|
||||
// indicates if we need to persist anything to disk
|
||||
var foldersChanged = false;
|
||||
|
||||
// check for added folders
|
||||
folders.forEach(function(folder) {
|
||||
if (!_.findWhere(self._account.folders, {
|
||||
path: folder.path
|
||||
})) {
|
||||
// add the missing folder
|
||||
self._account.folders.push(folder);
|
||||
// the folders listed in the navigation pane
|
||||
[FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, config.outboxMailboxType, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH].forEach(function(mbxType) {
|
||||
var localFolderWithType, imapFolderWithPath;
|
||||
|
||||
// check if there is a folder of this type locally available
|
||||
localFolderWithType = _.findWhere(self._account.folders, {
|
||||
type: mbxType
|
||||
});
|
||||
|
||||
if (localFolderWithType) {
|
||||
// we have a local folder available, so let's check if this folder still exists on imap
|
||||
|
||||
imapFolderWithPath = _.findWhere(wellKnownFolders[mbxType], {
|
||||
path: localFolderWithType.path
|
||||
});
|
||||
|
||||
if (imapFolderWithPath) {
|
||||
// folder present on imap, no need to update.
|
||||
return;
|
||||
}
|
||||
|
||||
// folder not present on imap, so remove the folder and see if there are any updates for this folder type
|
||||
self._account.folders.splice(self._account.folders.indexOf(localFolderWithType), 1);
|
||||
foldersChanged = true;
|
||||
}
|
||||
});
|
||||
|
||||
// check for deleted folders
|
||||
self._account.folders.forEach(function(folder) {
|
||||
if (!_.findWhere(folders, {
|
||||
path: folder.path
|
||||
})) {
|
||||
// remove the obsolete folder
|
||||
self._account.folders.splice(self._account.folders.indexOf(folder), 1);
|
||||
foldersChanged = true;
|
||||
if (!wellKnownFolders[mbxType] || !wellKnownFolders[mbxType].length) {
|
||||
// no imap folders of the respective mailbox type, so nothing to do here
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* we have no local folder of the type, so do something intelligent,
|
||||
* i.e. take the first folder of the respective type
|
||||
*/
|
||||
self._account.folders.push(wellKnownFolders[mbxType][0]);
|
||||
foldersChanged = true;
|
||||
});
|
||||
|
||||
// if folder have changed, we need to persist them to disk.
|
||||
// if folders have not changed, can fill them with messages directly
|
||||
if (!foldersChanged) {
|
||||
return self._initMessagesFromDisk(done);
|
||||
}
|
||||
|
||||
// persist encrypted list in device storage
|
||||
// NB! persis the array we received from IMAP! do *not* persist self._account.folders with all the messages...
|
||||
self._devicestorage.storeList([folders], folderDbType, function(err) {
|
||||
// note: the folders in the ui also include the messages array, so let's create a clean array here
|
||||
var folders = self._account.folders.map(function(folder) {
|
||||
return {
|
||||
name: folder.name,
|
||||
path: folder.path,
|
||||
type: folder.type
|
||||
};
|
||||
});
|
||||
self._devicestorage.storeList([folders], FOLDER_DB_TYPE, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
@ -1463,7 +1509,7 @@ define(function(require) {
|
||||
}
|
||||
|
||||
var trash = _.findWhere(this._account.folders, {
|
||||
type: 'Trash'
|
||||
type: FOLDER_TYPE_TRASH
|
||||
});
|
||||
|
||||
// there's no known trash folder to move the mail to or we're in the trash folder, so we can purge the message
|
||||
|
@ -5,7 +5,8 @@ define(function(require) {
|
||||
updateV1 = require('js/util/update/update-v1'),
|
||||
updateV2 = require('js/util/update/update-v2'),
|
||||
updateV3 = require('js/util/update/update-v3'),
|
||||
updateV4 = require('js/util/update/update-v4');
|
||||
updateV4 = require('js/util/update/update-v4'),
|
||||
updateV5 = require('js/util/update/update-v5');
|
||||
|
||||
/**
|
||||
* Handles database migration
|
||||
@ -13,7 +14,7 @@ define(function(require) {
|
||||
var UpdateHandler = function(appConfigStorage, userStorage, auth) {
|
||||
this._appConfigStorage = appConfigStorage;
|
||||
this._userStorage = userStorage;
|
||||
this._updateScripts = [updateV1, updateV2, updateV3, updateV4];
|
||||
this._updateScripts = [updateV1, updateV2, updateV3, updateV4, updateV5];
|
||||
this._auth = auth;
|
||||
};
|
||||
|
||||
|
56
src/js/util/update/update-v5.js
Normal file
56
src/js/util/update/update-v5.js
Normal file
@ -0,0 +1,56 @@
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
var FOLDER_TYPE_INBOX = 'Inbox';
|
||||
var FOLDER_TYPE_SENT = 'Sent';
|
||||
var FOLDER_TYPE_DRAFTS = 'Drafts';
|
||||
var FOLDER_TYPE_TRASH = 'Trash';
|
||||
|
||||
var FOLDER_DB_TYPE = 'folders';
|
||||
var VERSION_DB_TYPE = 'dbVersion';
|
||||
|
||||
var POST_UPDATE_DB_VERSION = 5;
|
||||
|
||||
/**
|
||||
* Update handler for transition database version 4 -> 5
|
||||
*
|
||||
* Due to an overlooked issue, there may be multiple folders, e.g. for sent mails.
|
||||
* This removes the "duplicate" folders.
|
||||
*/
|
||||
function update(options, callback) {
|
||||
|
||||
// remove the emails
|
||||
options.userStorage.listItems(FOLDER_DB_TYPE, 0, null, function(err, stored) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var folders = stored[0] || [];
|
||||
[FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH].forEach(function(mbxType) {
|
||||
var foldersForType = folders.filter(function(mbx) {
|
||||
return mbx.type === mbxType;
|
||||
});
|
||||
|
||||
if (foldersForType.length <= 1) {
|
||||
return; // nothing to do here
|
||||
}
|
||||
|
||||
// remove duplicate folders
|
||||
for (var i = 1; i < foldersForType.length; i++) {
|
||||
folders.splice(folders.indexOf(foldersForType[i]), 1);
|
||||
}
|
||||
});
|
||||
|
||||
options.userStorage.storeList([folders], FOLDER_DB_TYPE, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// update the database version to POST_UPDATE_DB_VERSION
|
||||
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return update;
|
||||
});
|
@ -40,30 +40,35 @@ define(function(require) {
|
||||
asymKeySize = 2048;
|
||||
|
||||
inboxFolder = {
|
||||
name: 'Inbox',
|
||||
type: 'Inbox',
|
||||
path: 'INBOX',
|
||||
messages: []
|
||||
};
|
||||
|
||||
sentFolder = {
|
||||
name: 'Sent',
|
||||
type: 'Sent',
|
||||
path: 'SENT',
|
||||
messages: []
|
||||
};
|
||||
|
||||
draftsFolder = {
|
||||
name: 'Drafts',
|
||||
type: 'Drafts',
|
||||
path: 'DRAFTS',
|
||||
messages: []
|
||||
};
|
||||
|
||||
outboxFolder = {
|
||||
name: 'Outbox',
|
||||
type: 'Outbox',
|
||||
path: 'OUTBOX',
|
||||
messages: []
|
||||
};
|
||||
|
||||
trashFolder = {
|
||||
name: 'Trash',
|
||||
type: 'Trash',
|
||||
path: 'TRASH',
|
||||
messages: []
|
||||
@ -1969,18 +1974,76 @@ define(function(require) {
|
||||
it('should initialize from imap if online', function(done) {
|
||||
account.folders = [];
|
||||
imapClientStub.listWellKnownFolders.yieldsAsync(null, {
|
||||
inbox: inboxFolder,
|
||||
sent: sentFolder,
|
||||
drafts: draftsFolder,
|
||||
trash: trashFolder
|
||||
Inbox: [inboxFolder],
|
||||
Sent: [sentFolder],
|
||||
Drafts: [draftsFolder],
|
||||
Trash: [trashFolder]
|
||||
});
|
||||
devicestorageStub.storeList.withArgs(sinon.match(function(arg) {
|
||||
expect(arg[0][0]).to.deep.equal(inboxFolder);
|
||||
expect(arg[0][1]).to.deep.equal(sentFolder);
|
||||
expect(arg[0][0].name).to.deep.equal(inboxFolder.name);
|
||||
expect(arg[0][0].path).to.deep.equal(inboxFolder.path);
|
||||
expect(arg[0][0].type).to.deep.equal(inboxFolder.type);
|
||||
expect(arg[0][1].name).to.deep.equal(sentFolder.name);
|
||||
expect(arg[0][1].path).to.deep.equal(sentFolder.path);
|
||||
expect(arg[0][1].type).to.deep.equal(sentFolder.type);
|
||||
expect(arg[0][2].name).to.deep.equal(outboxFolder.name);
|
||||
expect(arg[0][2].path).to.deep.equal(outboxFolder.path);
|
||||
expect(arg[0][2].type).to.deep.equal(outboxFolder.type);
|
||||
expect(arg[0][3]).to.deep.equal(draftsFolder);
|
||||
expect(arg[0][4]).to.deep.equal(trashFolder);
|
||||
expect(arg[0][3].name).to.deep.equal(draftsFolder.name);
|
||||
expect(arg[0][3].path).to.deep.equal(draftsFolder.path);
|
||||
expect(arg[0][3].type).to.deep.equal(draftsFolder.type);
|
||||
expect(arg[0][4].name).to.deep.equal(trashFolder.name);
|
||||
expect(arg[0][4].path).to.deep.equal(trashFolder.path);
|
||||
expect(arg[0][4].type).to.deep.equal(trashFolder.type);
|
||||
return true;
|
||||
}), 'folders').yieldsAsync();
|
||||
|
||||
dao.refreshFolder.yieldsAsync();
|
||||
|
||||
dao._initFoldersFromImap(function(err) {
|
||||
expect(err).to.not.exist;
|
||||
expect(imapClientStub.listWellKnownFolders.calledOnce).to.be.true;
|
||||
expect(devicestorageStub.storeList.calledOnce).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should update folders from imap', function(done) {
|
||||
account.folders = [inboxFolder, outboxFolder, trashFolder, {
|
||||
name: 'foo',
|
||||
type: 'Sent',
|
||||
path: 'bar',
|
||||
}];
|
||||
|
||||
imapClientStub.listWellKnownFolders.yieldsAsync(null, {
|
||||
Inbox: [inboxFolder],
|
||||
Sent: [sentFolder],
|
||||
Drafts: [draftsFolder],
|
||||
Trash: [trashFolder]
|
||||
});
|
||||
devicestorageStub.storeList.withArgs(sinon.match(function(arg) {
|
||||
expect(arg[0]).to.deep.equal([{
|
||||
name: inboxFolder.name,
|
||||
path: inboxFolder.path,
|
||||
type: inboxFolder.type
|
||||
}, {
|
||||
name: outboxFolder.name,
|
||||
path: outboxFolder.path,
|
||||
type: outboxFolder.type
|
||||
}, {
|
||||
name: trashFolder.name,
|
||||
path: trashFolder.path,
|
||||
type: trashFolder.type
|
||||
}, {
|
||||
name: sentFolder.name,
|
||||
path: sentFolder.path,
|
||||
type: sentFolder.type
|
||||
}, {
|
||||
name: draftsFolder.name,
|
||||
path: draftsFolder.path,
|
||||
type: draftsFolder.type
|
||||
}]);
|
||||
|
||||
return true;
|
||||
}), 'folders').yieldsAsync();
|
||||
|
||||
|
@ -277,7 +277,7 @@ define(function(require) {
|
||||
var SMTP_DB_KEY = 'smtp';
|
||||
var REALNAME_DB_KEY = 'realname';
|
||||
var emailaddress = 'bla@blubb.io';
|
||||
|
||||
|
||||
var imap = config.gmail.imap,
|
||||
smtp = config.gmail.smtp;
|
||||
|
||||
@ -347,6 +347,128 @@ define(function(require) {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('v4 -> v5', function() {
|
||||
var FOLDER_TYPE_INBOX = 'Inbox';
|
||||
var FOLDER_TYPE_SENT = 'Sent';
|
||||
var FOLDER_TYPE_DRAFTS = 'Drafts';
|
||||
var FOLDER_TYPE_TRASH = 'Trash';
|
||||
|
||||
var FOLDER_DB_TYPE = 'folders';
|
||||
var VERSION_DB_TYPE = 'dbVersion';
|
||||
|
||||
var POST_UPDATE_DB_VERSION = 5;
|
||||
|
||||
beforeEach(function() {
|
||||
cfg.dbVersion = 5; // app requires database version 4
|
||||
appConfigStorageStub.listItems.withArgs(VERSION_DB_TYPE).yieldsAsync(null, [4]); // database version is 4
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
// database version is only queried for version checking prior to the update script
|
||||
// so no need to check this in case-specific tests
|
||||
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
userStorageStub.listItems.withArgs(FOLDER_DB_TYPE).yieldsAsync(null, [
|
||||
[{
|
||||
name: 'inbox1',
|
||||
type: FOLDER_TYPE_INBOX
|
||||
}, {
|
||||
name: 'inbox2',
|
||||
type: FOLDER_TYPE_INBOX
|
||||
}, {
|
||||
name: 'sent1',
|
||||
type: FOLDER_TYPE_SENT
|
||||
}, {
|
||||
name: 'sent2',
|
||||
type: FOLDER_TYPE_SENT
|
||||
}, {
|
||||
name: 'drafts1',
|
||||
type: FOLDER_TYPE_DRAFTS
|
||||
}, {
|
||||
name: 'drafts2',
|
||||
type: FOLDER_TYPE_DRAFTS
|
||||
}, {
|
||||
name: 'trash1',
|
||||
type: FOLDER_TYPE_TRASH
|
||||
}, {
|
||||
name: 'trash2',
|
||||
type: FOLDER_TYPE_TRASH
|
||||
}]
|
||||
]);
|
||||
|
||||
userStorageStub.storeList.withArgs([
|
||||
[{
|
||||
name: 'inbox1',
|
||||
type: FOLDER_TYPE_INBOX
|
||||
}, {
|
||||
name: 'sent1',
|
||||
type: FOLDER_TYPE_SENT
|
||||
}, {
|
||||
name: 'drafts1',
|
||||
type: FOLDER_TYPE_DRAFTS
|
||||
}, {
|
||||
name: 'trash1',
|
||||
type: FOLDER_TYPE_TRASH
|
||||
}]
|
||||
], FOLDER_DB_TYPE).yieldsAsync();
|
||||
|
||||
appConfigStorageStub.storeList.withArgs([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE).yieldsAsync();
|
||||
|
||||
updateHandler.update(function(error) {
|
||||
expect(error).to.not.exist;
|
||||
expect(userStorageStub.listItems.calledOnce).to.be.true;
|
||||
expect(userStorageStub.storeList.calledOnce).to.be.true;
|
||||
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when persisting database version fails', function(done) {
|
||||
userStorageStub.listItems.yieldsAsync(null, []);
|
||||
userStorageStub.storeList.yieldsAsync();
|
||||
appConfigStorageStub.storeList.yieldsAsync(new Error());
|
||||
|
||||
updateHandler.update(function(error) {
|
||||
expect(error).to.exist;
|
||||
expect(userStorageStub.listItems.calledOnce).to.be.true;
|
||||
expect(userStorageStub.storeList.calledOnce).to.be.true;
|
||||
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when persisting folders fails', function(done) {
|
||||
userStorageStub.listItems.yieldsAsync(null, []);
|
||||
userStorageStub.storeList.yieldsAsync(new Error());
|
||||
|
||||
updateHandler.update(function(error) {
|
||||
expect(error).to.exist;
|
||||
expect(userStorageStub.listItems.calledOnce).to.be.true;
|
||||
expect(userStorageStub.storeList.calledOnce).to.be.true;
|
||||
expect(appConfigStorageStub.storeList.called).to.be.false;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when listing folders fails', function(done) {
|
||||
userStorageStub.listItems.yieldsAsync(new Error());
|
||||
|
||||
updateHandler.update(function(error) {
|
||||
expect(error).to.exist;
|
||||
expect(userStorageStub.listItems.calledOnce).to.be.true;
|
||||
expect(userStorageStub.storeList.called).to.be.false;
|
||||
expect(appConfigStorageStub.storeList.called).to.be.false;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user