From 267e88987257aa93958d830bf784fc8937187841 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Sat, 28 Sep 2013 19:04:15 +0200 Subject: [PATCH] list and store only encrypted emails in local db --- src/js/controller/mail-list.js | 1 + src/js/dao/devicestorage-dao.js | 64 ++++++++++++++++++++++------- src/js/dao/email-dao.js | 32 ++++++++++++--- src/js/dao/lawnchair-dao.js | 31 +++++++++++++- src/sass/views/_mail-list.scss | 4 +- test/new-unit/email-dao-test.js | 32 ++++++++++++++- test/unit/devicestorage-dao-test.js | 10 ++++- test/unit/lawnchair-dao-test.js | 39 +++++++++++++----- 8 files changed, 178 insertions(+), 35 deletions(-) diff --git a/src/js/controller/mail-list.js b/src/js/controller/mail-list.js index d13b209..65979aa 100644 --- a/src/js/controller/mail-list.js +++ b/src/js/controller/mail-list.js @@ -52,6 +52,7 @@ define(function(require) { // development createDummyMails(function(emails) { + updateStatus('Last update: ', new Date()); $scope.emails = emails; $scope.select($scope.emails[0]); }); diff --git a/src/js/dao/devicestorage-dao.js b/src/js/dao/devicestorage-dao.js index f8bfd1b..e3c1a9c 100644 --- a/src/js/dao/devicestorage-dao.js +++ b/src/js/dao/devicestorage-dao.js @@ -30,7 +30,6 @@ define(function(require) { callback(); return; } - // validate type if (!type) { callback({ @@ -41,16 +40,7 @@ define(function(require) { // format items for batch storing in dao list.forEach(function(i) { - // put uid in key if available... for easy querying - if (i.uid) { - key = type + '_' + i.uid; - } else if (i.sentDate && i.id) { - key = type + '_' + i.sentDate + '_' + i.id; - } else if (i.id) { - key = type + '_' + i.id; - } else { - key = type; - } + key = createKey(i, type); items.push({ key: key, @@ -63,6 +53,23 @@ define(function(require) { }); }; + /** + * Deletes items of a certain type from storage + */ + DeviceStorageDAO.prototype.removeList = function(type, callback) { + // validate type + if (!type) { + callback({ + errMsg: 'Type is not set!' + }); + return; + } + + jsonDao.removeList(type, function() { + callback(); + }); + }; + /** * List stored items of a given type * @param type [String] The type of item e.g. 'email' @@ -70,10 +77,18 @@ define(function(require) { * @param num [Number] The number of items to fetch (null means fetch all) */ DeviceStorageDAO.prototype.listItems = function(type, offset, num, callback) { - // fetch all items of a certain type from the data-store - jsonDao.list(type, offset, num, function(encryptedList) { - callback(null, encryptedList); + // validate type + if (!type || typeof offset === 'undefined' || typeof num === 'undefined') { + callback({ + errMsg: 'Args not is not set!' + }); + return; + } + + // fetch all items of a certain type from the data-store + jsonDao.list(type, offset, num, function(matchingList) { + callback(null, matchingList); }); }; @@ -84,5 +99,26 @@ define(function(require) { jsonDao.clear(callback); }; + // + // helper functions + // + + function createKey(i, type) { + var key; + + // put uid in key if available... for easy querying + if (i.uid) { + key = type + '_' + i.uid; + } else if (i.sentDate && i.id) { + key = type + '_' + i.sentDate + '_' + i.id; + } else if (i.id) { + key = type + '_' + i.id; + } else { + key = type; + } + + return key; + } + return DeviceStorageDAO; }); \ No newline at end of file diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index 5669629..8cb5df9 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -400,7 +400,8 @@ define(function(require) { * High level sync operation for the delta from the user's IMAP inbox */ EmailDAO.prototype.imapSync = function(options, callback) { - var self = this; + var self = this, + dbType = 'email_' + options.folder; // validate options if (!options.folder || typeof options.offset === 'undefined' || typeof options.num === 'undefined') { @@ -411,13 +412,22 @@ define(function(require) { } fetchList(options, function(emails) { - // persist encrypted list in device storage - self._devicestorage.storeList(emails, 'email_' + options.folder, function() { - callback(); + // delete old items from db + self._devicestorage.removeList(dbType, function(err) { + if (err) { + callback(err); + return; + } + // persist encrypted list in device storage + self._devicestorage.storeList(emails, dbType, function(err) { + callback(err); + }); }); }); function fetchList(folder, callback) { + var encryptedList = []; + // fetch imap folder's message list self.imapListMessages({ folder: options.folder, @@ -429,8 +439,15 @@ define(function(require) { return; } + // find encrypted messages by subject + emails.forEach(function(i) { + if (i.subject === str.subject) { + encryptedList.push(i); + } + }); + // fetch message bodies - fetchBodies(emails, folder, function(messages) { + fetchBodies(encryptedList, folder, function(messages) { callback(messages); }); }); @@ -439,6 +456,11 @@ define(function(require) { function fetchBodies(messageList, folder, callback) { var emails = []; + if (messageList.length < 1) { + callback(emails); + return; + } + var after = _.after(messageList.length, function() { callback(emails); }); diff --git a/src/js/dao/lawnchair-dao.js b/src/js/dao/lawnchair-dao.js index 1623d8d..2eb124e 100644 --- a/src/js/dao/lawnchair-dao.js +++ b/src/js/dao/lawnchair-dao.js @@ -1,7 +1,7 @@ /** * Handles generic caching of JSON objects in a lawnchair adapter */ -define(['lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(Lawnchair) { +define(['underscore', 'lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(_, Lawnchair) { 'use strict'; var self = {}, @@ -118,6 +118,35 @@ define(['lawnchair', 'lawnchairSQL', 'lawnchairIDB'], function(Lawnchair) { db.remove(key, callback); }; + /** + * Removes an object liter from local storage by its key (delete) + */ + self.removeList = function(type, callback) { + var matchingKeys = [], + after; + + // get all keys + db.keys(function(keys) { + // check if key begins with type + keys.forEach(function(key) { + if (key.indexOf(type) === 0) { + matchingKeys.push(key); + } + }); + + if (matchingKeys.length < 1) { + callback(); + return; + } + + // remove all matching keys + after = _.after(matchingKeys.length, callback); + _.each(matchingKeys, function(key) { + db.remove(key, after); + }); + }); + }; + /** * Clears the whole local storage cache */ diff --git a/src/sass/views/_mail-list.scss b/src/sass/views/_mail-list.scss index 85e22ac..9c553d0 100755 --- a/src/sass/views/_mail-list.scss +++ b/src/sass/views/_mail-list.scss @@ -35,8 +35,8 @@ } ul { - padding: 0 $padding-horizontal; - max-height: 100%; + padding: 0 $padding-horizontal 90px $padding-horizontal; + height: 100%; overflow-y: scroll; } diff --git a/test/new-unit/email-dao-test.js b/test/new-unit/email-dao-test.js index be7f00e..979429d 100644 --- a/test/new-unit/email-dao-test.js +++ b/test/new-unit/email-dao-test.js @@ -367,7 +367,7 @@ define(function(require) { }); describe('IMAP: sync messages to local storage', function() { - it('should work', function(done) { + it('should not list unencrypted messages', function(done) { imapClientStub.listMessages.yields(null, [{ uid: 413, }, { @@ -376,6 +376,35 @@ define(function(require) { imapClientStub.getMessage.yields(null, { body: 'asdf' }); + devicestorageStub.removeList.yields(); + devicestorageStub.storeList.yields(); + + emailDao.imapSync({ + folder: 'INBOX', + offset: 0, + num: 2 + }, function(err) { + expect(err).to.not.exist; + expect(imapClientStub.listMessages.calledOnce).to.be.true; + expect(imapClientStub.getMessage.called).to.be.false; + expect(devicestorageStub.removeList.calledOnce).to.be.true; + expect(devicestorageStub.storeList.calledOnce).to.be.true; + done(); + }); + }); + + it('should work', function(done) { + imapClientStub.listMessages.yields(null, [{ + uid: 413, + subject: app.string.subject + }, { + uid: 414, + subject: app.string.subject + }]); + imapClientStub.getMessage.yields(null, { + body: 'asdf' + }); + devicestorageStub.removeList.yields(); devicestorageStub.storeList.yields(); emailDao.imapSync({ @@ -386,6 +415,7 @@ define(function(require) { expect(err).to.not.exist; expect(imapClientStub.listMessages.calledOnce).to.be.true; expect(imapClientStub.getMessage.calledTwice).to.be.true; + expect(devicestorageStub.removeList.calledOnce).to.be.true; expect(devicestorageStub.storeList.calledOnce).to.be.true; done(); }); diff --git a/test/unit/devicestorage-dao-test.js b/test/unit/devicestorage-dao-test.js index 0cc9dbc..0088db7 100644 --- a/test/unit/devicestorage-dao-test.js +++ b/test/unit/devicestorage-dao-test.js @@ -71,7 +71,6 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/devicestorag }); asyncTest("List items", 4, function() { - var senderPubkeys = [devicestorageTest.generatedKeypair.publicKey]; var offset = 2, @@ -94,4 +93,13 @@ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/devicestorag }); }); + asyncTest("Delete List items", 1, function() { + // list encrypted items from storage + storage.removeList('email_inbox', function(err) { + ok(!err); + + start(); + }); + }); + }); \ No newline at end of file diff --git a/test/unit/lawnchair-dao-test.js b/test/unit/lawnchair-dao-test.js index ec9b72a..daabdcd 100644 --- a/test/unit/lawnchair-dao-test.js +++ b/test/unit/lawnchair-dao-test.js @@ -21,12 +21,18 @@ define(['js/dao/lawnchair-dao'], function(jsonDao) { }); }); - asyncTest("CRUD object literal", 4, function() { + asyncTest("CRUD object literal", 5, function() { - var key = 'type_asdf'; + var key = 'type_1'; var data = { - name: 'testName', - type: 'testType' + name: 'testName1', + type: 'testType1' + }; + + var key2 = 'type_2'; + var data2 = { + name: 'testName2', + type: 'testType2' }; // create @@ -49,17 +55,28 @@ define(['js/dao/lawnchair-dao'], function(jsonDao) { jsonDao.read(key, function(updated) { equal(updated.name, newName, 'Update'); - // delete - jsonDao.remove(key, function() { + // persist 2nd type + jsonDao.persist(key2, data2, function() { - // should read empty - jsonDao.read(key, function(lastRead) { - equal(lastRead, undefined, 'Delete'); + // delete all items of 2nd type + jsonDao.removeList(key2, function() { - start(); + jsonDao.list('type', 0, null, function(newList) { + ok(newList.length === 1, 'List'); + + // delete + jsonDao.remove(key, function() { + + // should read empty + jsonDao.read(key, function(lastRead) { + equal(lastRead, undefined, 'Delete'); + + start(); + }); + }); + }); }); }); - }); }); });