diff options
20 files changed, 155 insertions, 53 deletions
diff --git a/service/pixelated/adapter/listeners/mailbox_indexer_listener.py b/service/pixelated/adapter/listeners/mailbox_indexer_listener.py index e2f46769..74b4f5af 100644 --- a/service/pixelated/adapter/listeners/mailbox_indexer_listener.py +++ b/service/pixelated/adapter/listeners/mailbox_indexer_listener.py @@ -47,7 +47,7 @@ class MailboxIndexerListener(object): missing_idents = soledad_idents.difference(indexed_idents) - self.search_engine.index_mails((yield self.mail_store.get_mails(missing_idents))) + self.search_engine.index_mails((yield self.mail_store.get_mails(missing_idents, include_body=True))) except Exception, e: # this is a event handler, don't let exceptions escape logger.error(e) diff --git a/service/pixelated/adapter/mailstore/leap_mailstore.py b/service/pixelated/adapter/mailstore/leap_mailstore.py index 9b62b3ba..6cbbe10a 100644 --- a/service/pixelated/adapter/mailstore/leap_mailstore.py +++ b/service/pixelated/adapter/mailstore/leap_mailstore.py @@ -15,7 +15,6 @@ # along with Pixelated. If not, see <http://www.gnu.org/licenses/>. import re from email.header import decode_header -from email.utils import parseaddr from uuid import uuid4 from leap.mail.adaptors.soledad import SoledadMailAdaptor @@ -26,6 +25,7 @@ from twisted.internet.defer import FirstError, DeferredList from pixelated.adapter.mailstore.body_parser import BodyParser from pixelated.adapter.mailstore.mailstore import MailStore, underscore_uuid from pixelated.adapter.model.mail import Mail, InputMail +from pixelated.support import log_time_deferred from pixelated.support.functional import to_unicode @@ -204,11 +204,12 @@ class LeapMailStore(MailStore): defer.returnValue(leap_mail) + @log_time_deferred @defer.inlineCallbacks - def get_mails(self, mail_ids, gracefully_ignore_errors=False): + def get_mails(self, mail_ids, gracefully_ignore_errors=False, include_body=False): deferreds = [] for mail_id in mail_ids: - deferreds.append(self.get_mail(mail_id, include_body=True)) + deferreds.append(self.get_mail(mail_id, include_body=include_body)) if gracefully_ignore_errors: results = yield DeferredList(deferreds, consumeErrors=True) @@ -224,13 +225,14 @@ class LeapMailStore(MailStore): message.get_wrapper().set_flags(tuple(mail.flags)) yield self._update_mail(message) # TODO assert this is yielded (otherwise asynchronous) + @log_time_deferred @defer.inlineCallbacks def all_mails(self, gracefully_ignore_errors=False): mdocs = yield self.soledad.get_from_index('by-type', 'meta') mail_ids = map(lambda doc: doc.doc_id, mdocs) - mails = yield self.get_mails(mail_ids, gracefully_ignore_errors=gracefully_ignore_errors) + mails = yield self.get_mails(mail_ids, gracefully_ignore_errors=gracefully_ignore_errors, include_body=True) defer.returnValue(mails) @defer.inlineCallbacks diff --git a/service/pixelated/adapter/mailstore/mailstore.py b/service/pixelated/adapter/mailstore/mailstore.py index 60716dfe..fbd7fc9e 100644 --- a/service/pixelated/adapter/mailstore/mailstore.py +++ b/service/pixelated/adapter/mailstore/mailstore.py @@ -22,7 +22,7 @@ class MailStore(object): def get_mail_attachment(self, attachment_id): pass - def get_mails(self, mail_ids): + def get_mails(self, mail_ids, gracefully_ignore_errors=False, include_body=False): pass def all_mails(self): diff --git a/service/pixelated/adapter/search/__init__.py b/service/pixelated/adapter/search/__init__.py index 35087101..e137b392 100644 --- a/service/pixelated/adapter/search/__init__.py +++ b/service/pixelated/adapter/search/__init__.py @@ -13,7 +13,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see <http://www.gnu.org/licenses/>. - +from pixelated.support import log_time from pixelated.support.encrypted_file_storage import EncryptedFileStorage import os @@ -172,6 +172,7 @@ class SearchEngine(object): results = searcher.search(query, **options) return results + @log_time def search(self, query, window=25, page=1, all_mails=False): query = self.prepare_query(query) return self._search_all_mails(query) if all_mails else self._paginated_search_mails(query, window, page) diff --git a/service/pixelated/adapter/services/mail_service.py b/service/pixelated/adapter/services/mail_service.py index bfe45cad..2da2cca6 100644 --- a/service/pixelated/adapter/services/mail_service.py +++ b/service/pixelated/adapter/services/mail_service.py @@ -25,6 +25,8 @@ from pixelated.adapter.model.status import Status from pixelated.adapter.services.tag_service import extract_reserved_tags from leap.mail.adaptors.soledad import SoledadMailAdaptor +from pixelated.support import log_time_deferred + class MailService(object): @@ -43,6 +45,7 @@ class MailService(object): def save_attachment(self, content, content_type): return self.attchment_store.add_attachment(content, content_type) + @log_time_deferred @defer.inlineCallbacks def mails(self, query, window_size, page): mail_ids, total = self.search_engine.search(query, window_size, page) diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py index 4b4d693a..4276c0c3 100644 --- a/service/pixelated/bitmask_libraries/session.py +++ b/service/pixelated/bitmask_libraries/session.py @@ -78,7 +78,7 @@ class LeapSession(object): account = IMAPAccount(user_mail, soledad) return account - def _set_fresh_account(self, email_address): + def _set_fresh_account(self, event, email_address): log.debug('Key for email %s has been generated' % email_address) if email_address == self.account_email(): self.fresh_account = True diff --git a/service/pixelated/resources/__init__.py b/service/pixelated/resources/__init__.py index 06fcbe54..2e451a69 100644 --- a/service/pixelated/resources/__init__.py +++ b/service/pixelated/resources/__init__.py @@ -21,6 +21,7 @@ from twisted.web.resource import Resource # from pixelated.resources.login_resource import LoginResource from pixelated.resources.session import IPixelatedSession +from pixelated.support import log_time class SetEncoder(json.JSONEncoder): @@ -37,6 +38,7 @@ def respond_json(entity, request, status_code=200): return json_response +@log_time def respond_json_deferred(entity, request, status_code=200): json_response = json.dumps(entity, cls=SetEncoder) request.responseHeaders.addRawHeader(b"content-type", b"application/json") diff --git a/service/pixelated/resources/mails_resource.py b/service/pixelated/resources/mails_resource.py index 5de707d7..61ada60e 100644 --- a/service/pixelated/resources/mails_resource.py +++ b/service/pixelated/resources/mails_resource.py @@ -1,4 +1,6 @@ +import time import json +import logging from pixelated.adapter.services.mail_sender import SMTPDownException from pixelated.adapter.model.mail import InputMail from twisted.web.server import NOT_DONE_YET @@ -9,9 +11,13 @@ from twisted.internet import defer from twisted.python.log import err from leap.common import events +from pixelated.support import log_time from pixelated.support.functional import to_unicode +log = logging.getLogger(__name__) + + class MailsUnreadResource(Resource): isLeaf = True @@ -139,19 +145,31 @@ class MailsResource(BaseResource): if action == 'unread': return MailsUnreadResource(_mail_service) + @log_time + def _build_mails_response(self, (mails, total)): + return { + "stats": { + "total": total, + }, + "mails": [mail.as_dict() for mail in mails] + } + def render_GET(self, request): + start = time.clock() + + def log_after_completion(result, start): + end = time.clock() + log.info('Needed %f ms to render response' % (end - start)) + return result + _mail_service = self.mail_service(request) query, window_size, page = request.args.get('q')[0], request.args.get('w')[0], request.args.get('p')[0] unicode_query = to_unicode(query) d = _mail_service.mails(unicode_query, window_size, page) - d.addCallback(lambda (mails, total): { - "stats": { - "total": total, - }, - "mails": [mail.as_dict() for mail in mails] - }) + d.addCallback(self._build_mails_response) d.addCallback(lambda res: respond_json_deferred(res, request)) + d.addCallback(log_after_completion, start=start) def error_handler(error): print error diff --git a/service/pixelated/support/__init__.py b/service/pixelated/support/__init__.py index 2756a319..80ecaa2e 100644 --- a/service/pixelated/support/__init__.py +++ b/service/pixelated/support/__init__.py @@ -13,3 +13,42 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see <http://www.gnu.org/licenses/>. +import time +import logging +from functools import wraps +from twisted.internet import defer + + +log = logging.getLogger(__name__) + + +def log_time(f): + + @wraps(f) + def wrapper(*args, **kwds): + start = time.clock() + result = f(*args, **kwds) + log.info('Needed %f ms to execute %s' % ((time.clock() - start), f)) + + return result + + return wrapper + + +def log_time_deferred(f): + + def log_time(result, start): + log.info('after callback: Needed %f ms to execute %s' % ((time.clock() - start), f)) + return result + + @wraps(f) + def wrapper(*args, **kwds): + start = time.clock() + result = f(*args, **kwds) + if isinstance(result, defer.Deferred): + result.addCallback(log_time, start=start) + else: + log.warn('No Deferred returned, perhaps need to re-order annotations?') + return result + + return wrapper diff --git a/service/requirements.txt b/service/requirements.txt index 71e0b105..1966f09a 100644 --- a/service/requirements.txt +++ b/service/requirements.txt @@ -7,6 +7,7 @@ pyasn1==0.1.8 requests==2.0.0 srp==1.0.4 whoosh==2.5.7 +pycryptopp -e 'git+https://github.com/pixelated-project/leap_pycommon.git@develop#egg=leap.common' -e 'git+https://github.com/pixelated-project/leap_auth.git#egg=leap.auth' -e 'git+https://github.com/pixelated-project/soledad.git@develop#egg=leap.soledad.common&subdirectory=common/' diff --git a/service/test/unit/adapter/test_mailbox_indexer_listener.py b/service/test/unit/adapter/test_mailbox_indexer_listener.py index 9d5f4c30..c88ba035 100644 --- a/service/test/unit/adapter/test_mailbox_indexer_listener.py +++ b/service/test/unit/adapter/test_mailbox_indexer_listener.py @@ -42,17 +42,17 @@ class MailboxListenerTest(unittest.TestCase): self.assertIn(MailboxIndexerListener('INBOX', self.mail_store, mock()), mailbox.listeners) def test_reindex_missing_idents(self): + mail = mock() search_engine = mock() when(search_engine).search('tag:inbox', all_mails=True).thenReturn(['ident1', 'ident2']) listener = MailboxIndexerListener('INBOX', self.mail_store, search_engine) when(self.mail_store).get_mailbox_mail_ids('INBOX').thenReturn({'ident1', 'ident2', 'missing_ident'}) - self.mail_store.used_arguments = [] - self.mail_store.get_mails = lambda x: self.mail_store.used_arguments.append(x) + when(self.mail_store).get_mails({'missing_ident'}, include_body=True).thenReturn([mail]) listener.newMessages(10, 5) - verify(self.mail_store, times=1).get_mails('INBOX') - self.assertIn({'missing_ident'}, self.mail_store.used_arguments) + verify(self.mail_store, times=1).get_mails({'missing_ident'}, include_body=True) + verify(search_engine).index_mails([mail]) @defer.inlineCallbacks def test_catches_exceptions_to_not_break_other_listeners(self): diff --git a/service/test/unit/bitmask_libraries/test_session.py b/service/test/unit/bitmask_libraries/test_session.py index eb969e3c..6e0e6204 100644 --- a/service/test/unit/bitmask_libraries/test_session.py +++ b/service/test/unit/bitmask_libraries/test_session.py @@ -80,7 +80,7 @@ class SessionTest(AbstractLeapTest): session = self._create_session() self.provider.address_for.return_value = 'someone@somedomain.tld' - session._set_fresh_account('someone@somedomain.tld') + session._set_fresh_account(None, 'someone@somedomain.tld') self.assertTrue(session.fresh_account) @@ -89,7 +89,7 @@ class SessionTest(AbstractLeapTest): session = self._create_session() self.provider.address_for.return_value = 'someone@somedomain.tld' - session._set_fresh_account('another_email@somedomain.tld') + session._set_fresh_account(None, 'another_email@somedomain.tld') self.assertFalse(session.fresh_account) diff --git a/web-ui/app/js/mail_view/ui/attachment_list.js b/web-ui/app/js/mail_view/ui/attachment_list.js index eb515865..3def6870 100644 --- a/web-ui/app/js/mail_view/ui/attachment_list.js +++ b/web-ui/app/js/mail_view/ui/attachment_list.js @@ -38,6 +38,9 @@ define( uploadFileButton: '#upload-file-button' }); + var ONE_MEGABYTE = 1024*1024; + var ATTACHMENT_SIZE_LIMIT = ONE_MEGABYTE; + this.showAttachment = function (ev, data) { this.trigger(document, events.mail.appendAttachment, data); this.renderAttachmentListView(data); @@ -50,15 +53,25 @@ define( this.renderAttachmentListView = function (data) { var currentHtml = this.select('attachmentListItem').html(); var item = this.buildAttachmentListItem(data); - this.select('attachmentListItem').html(currentHtml + item); + this.select('attachmentListItem').append(item); }; this.buildAttachmentListItem = function (attachment) { var attachmentData = {ident: attachment.ident, encoding: attachment.encoding, name: attachment.name, - size: attachment.size}; - return templates.compose.attachmentItem(attachmentData); + size: attachment.size, + removable: true}; + + var element = $(templates.compose.attachmentItem(attachmentData)); + var self = this; + element.find('i.remove-icon').bind('click', function(event) { + var element = $(this); + var ident = element.closest('li').attr('data-ident'); + self.trigger(document, events.mail.removeAttachment, {ident: ident}); + event.preventDefault(); + }); + return element; }; this.checkAttachmentSize = function(e, data) { @@ -89,8 +102,7 @@ define( self.trigger(document, events.mail.startUploadAttachment); }; - var ONE_MEGABYTE = 1000000; - if (data.originalFiles[0].size > ONE_MEGABYTE) { + if (data.originalFiles[0].size > ATTACHMENT_SIZE_LIMIT) { uploadErrors.push('Filesize is too big'); } if (uploadErrors.length > 0) { diff --git a/web-ui/app/js/mail_view/ui/draft_box.js b/web-ui/app/js/mail_view/ui/draft_box.js index 88051f30..07a7e472 100644 --- a/web-ui/app/js/mail_view/ui/draft_box.js +++ b/web-ui/app/js/mail_view/ui/draft_box.js @@ -64,7 +64,15 @@ define( }, subject: mail.header.subject, body: body, - attachments: mail.attachments + attachments: this.convertToRemovableAttachments(mail.attachments) + }); + + var self = this; + this.$node.find('i.remove-icon').bind('click', function(event) { + var element = $(this); + var ident = element.closest('li').attr('data-ident'); + self.trigger(document, events.mail.removeAttachment, {ident: ident}); + event.preventDefault(); }); this.enableFloatlabel('input.floatlabel'); @@ -77,6 +85,13 @@ define( this.on(this.select('closeMailButton'), 'click', this.showNoMessageSelected); }; + this.convertToRemovableAttachments = function(attachments) { + return attachments.map(function(attachment) { + attachment.removable = true; + return attachment; + }); + }; + this.mailDeleted = function(event, data) { if (_.contains(_.pluck(data.mails, 'ident'), this.attr.ident)) { this.trigger(events.dispatchers.rightPane.openNoMessageSelected); diff --git a/web-ui/app/js/mail_view/ui/forward_box.js b/web-ui/app/js/mail_view/ui/forward_box.js index a1b8dc41..3d643b2f 100644 --- a/web-ui/app/js/mail_view/ui/forward_box.js +++ b/web-ui/app/js/mail_view/ui/forward_box.js @@ -46,14 +46,29 @@ define( subject: this.attr.subject, recipients: { to: [], cc: []}, body: viewHelper.quoteMail(mail), - attachments: mail.attachments + attachments: this.convertToRemovableAttachments(mail.attachments) }); + var self = this; + this.$node.find('i.remove-icon').bind('click', function(event) { + var element = $(this); + var ident = element.closest('li').attr('data-ident'); + self.trigger(document, events.mail.removeAttachment, {ident: ident}); + event.preventDefault(); + }); + this.on(this.select('subjectDisplay'), 'click', this.showSubjectInput); this.select('recipientsDisplay').hide(); this.select('recipientsFields').show(); }; + this.convertToRemovableAttachments = function(attachments) { + return attachments.map(function(attachment) { + attachment.removable = true; + return attachment; + }); + }; + this.showSubjectInput = function() { this.select('subjectDisplay').hide(); this.select('subjectInput').show(); diff --git a/web-ui/app/js/page/events.js b/web-ui/app/js/page/events.js index f6186d41..57dd346a 100644 --- a/web-ui/app/js/page/events.js +++ b/web-ui/app/js/page/events.js @@ -147,7 +147,8 @@ define(function () { uploadingAttachment: 'mail:uploading:attachment', startUploadAttachment: 'mail:start:upload:attachment', appendAttachment: 'mail:append:attachment', - resetAttachments: 'mail:reset:attachments' + resetAttachments: 'mail:reset:attachments', + removeAttachment: 'mail:remove:attachment' }, mails: { available: 'mails:available', diff --git a/web-ui/app/scss/_read.scss b/web-ui/app/scss/_read.scss index 93e33c27..1b6f8334 100644 --- a/web-ui/app/scss/_read.scss +++ b/web-ui/app/scss/_read.scss @@ -31,7 +31,7 @@ height: 27px; margin-right: 3px; } - + .full-view-header { display:inline-block; padding-top: 5px; @@ -81,14 +81,14 @@ border-radius: 2px; background-color: #F5F5F5; margin-bottom: 8px; - + a { color: $attachment_text; display: block; text-decoration: none; line-height: inherit; padding: 4px 5px; - + &:hover, &:focus { i.download-icon { color: lighten($attachment_icon, 15); @@ -104,6 +104,13 @@ right: 0; padding: 7px 10px; } + + i.remove-icon { + float: right; + padding-right: 5px; + padding-top: 7px; + color: #999; + } } } } @@ -154,5 +161,3 @@ } } } - - diff --git a/web-ui/app/templates/compose/attachment_item.hbs b/web-ui/app/templates/compose/attachment_item.hbs index a69f209e..6fefda2f 100644 --- a/web-ui/app/templates/compose/attachment_item.hbs +++ b/web-ui/app/templates/compose/attachment_item.hbs @@ -1,8 +1,11 @@ -<li> +<li data-ident="{{ this.ident }}"> <a href="/attachment/{{ this.ident }}?encoding={{ this.encoding }}&filename={{ this.name }}"> {{ this.name }} <span class="attachment-size">({{ formatSize this.size}})</span> {{#if received}} <i class="fa fa-arrow-down download-icon"></i> {{/if}} + {{#if removable}} + <i class="fa fa-close remove-icon"></i> + {{/if}} </a> </li> diff --git a/web-ui/app/templates/compose/attachments_list.hbs b/web-ui/app/templates/compose/attachments_list.hbs index 73113023..936db2c9 100644 --- a/web-ui/app/templates/compose/attachments_list.hbs +++ b/web-ui/app/templates/compose/attachments_list.hbs @@ -21,4 +21,3 @@ <br> </div> - diff --git a/web-ui/test/spec/mail_view/ui/attachment_list.spec.js b/web-ui/test/spec/mail_view/ui/attachment_list.spec.js index d0133bc5..20f82704 100644 --- a/web-ui/test/spec/mail_view/ui/attachment_list.spec.js +++ b/web-ui/test/spec/mail_view/ui/attachment_list.spec.js @@ -40,9 +40,10 @@ describeMixin('mail_view/ui/attachment_list', function () { }); describe('Upload files -- max file size -- ', function (){ + var ONE_MEGABYTE = 1024*1024; var submitFile = 'file not submitted', submitted = 'file submitted'; var mockSubmit = function (){ submitFile = submitted; }; - var largeAttachment = {originalFiles: [{size: 4500000}], submit: mockSubmit}; + var largeAttachment = {originalFiles: [{size: ONE_MEGABYTE+1}], submit: mockSubmit}; var dummyEvent = 'whatever, not used'; it('should show error messages on the dom, when uploading files larger than 1MB', function () { @@ -85,27 +86,12 @@ describeMixin('mail_view/ui/attachment_list', function () { expect(largeAttachment.submit).not.toHaveBeenCalled(); }); - it('should upload files smaller than 1MB', function () { - var smallAttachment = {originalFiles: [{size: 450}], submit: mockSubmit}; + it('should upload files less or equal 1MB', function () { + var smallAttachment = {originalFiles: [{size: ONE_MEGABYTE}], submit: mockSubmit}; this.component.checkAttachmentSize(dummyEvent, smallAttachment); expect(submitFile).toEqual(submitted); }); }); - - xit('should start uploading attachments', function () { - var stubAttachment = {ident: 'faked', name: 'haha.txt', size: 4500}; - var mockAjax = spyOn($, 'ajax').and.callFake(function (params) {params.success(stubAttachment);}); - var uploadedAttachment = spyOnEvent(document, Pixelated.events.mail.uploadedAttachment); - var uploading = spyOnEvent(document, Pixelated.events.mail.uploadingAttachment); - - $(document).trigger(Pixelated.events.mail.startUploadAttachment); - - expect(mockAjax).toHaveBeenCalled(); - expect(uploadedAttachment).toHaveBeenTriggeredOnAndWith(document, stubAttachment); - expect(uploading).toHaveBeenTriggeredOn(document); - }); - }); - }); |