summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/pixelated/adapter/listeners/mailbox_indexer_listener.py2
-rw-r--r--service/pixelated/adapter/mailstore/leap_mailstore.py10
-rw-r--r--service/pixelated/adapter/mailstore/mailstore.py2
-rw-r--r--service/pixelated/adapter/search/__init__.py3
-rw-r--r--service/pixelated/adapter/services/mail_service.py3
-rw-r--r--service/pixelated/bitmask_libraries/session.py2
-rw-r--r--service/pixelated/resources/__init__.py2
-rw-r--r--service/pixelated/resources/mails_resource.py30
-rw-r--r--service/pixelated/support/__init__.py39
-rw-r--r--service/requirements.txt1
-rw-r--r--service/test/unit/adapter/test_mailbox_indexer_listener.py8
-rw-r--r--service/test/unit/bitmask_libraries/test_session.py4
-rw-r--r--web-ui/app/js/mail_view/ui/attachment_list.js22
-rw-r--r--web-ui/app/js/mail_view/ui/draft_box.js17
-rw-r--r--web-ui/app/js/mail_view/ui/forward_box.js17
-rw-r--r--web-ui/app/js/page/events.js3
-rw-r--r--web-ui/app/scss/_read.scss15
-rw-r--r--web-ui/app/templates/compose/attachment_item.hbs5
-rw-r--r--web-ui/app/templates/compose/attachments_list.hbs1
-rw-r--r--web-ui/test/spec/mail_view/ui/attachment_list.spec.js22
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);
- });
-
});
-
});