summaryrefslogtreecommitdiff
path: root/service/pixelated/resources
diff options
context:
space:
mode:
authorDuda Dornelles <dudassdornelles@gmail.com>2015-01-21 18:12:25 -0200
committerPixpoa pairing <pixpoapairing@pixelated-project.org>2015-01-21 18:12:25 -0200
commit8abb94a88e40fde249b562a841a5b0398582717e (patch)
treec2cc4c7a00e80fb85423cd3a0b7a25c076a710c4 /service/pixelated/resources
parent8310db57c7f1829497ebadddff65682392a60a27 (diff)
#224 App is working without klein - migration to twisted "complete"
Diffstat (limited to 'service/pixelated/resources')
-rw-r--r--service/pixelated/resources/__init__.py33
-rw-r--r--service/pixelated/resources/attachments_resource.py63
-rw-r--r--service/pixelated/resources/contacts_resource.py35
-rw-r--r--service/pixelated/resources/features_resource.py32
-rw-r--r--service/pixelated/resources/mail_resource.py64
-rw-r--r--service/pixelated/resources/mails_resource.py128
-rw-r--r--service/pixelated/resources/root_resource.py46
-rw-r--r--service/pixelated/resources/sync_info_resource.py46
-rw-r--r--service/pixelated/resources/tags_resource.py38
9 files changed, 485 insertions, 0 deletions
diff --git a/service/pixelated/resources/__init__.py b/service/pixelated/resources/__init__.py
new file mode 100644
index 00000000..a2e4c9d4
--- /dev/null
+++ b/service/pixelated/resources/__init__.py
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+
+
+def respond_json(entity, request, status_code=200):
+ json_response = json.dumps(entity)
+ request.responseHeaders.addRawHeader(b"content-type", b"application/json")
+ request.code = status_code
+ return json_response
+
+
+def respond_json_deferred(entity, request, status_code=200):
+ json_response = json.dumps(entity)
+ request.responseHeaders.addRawHeader(b"content-type", b"application/json")
+ request.code = status_code
+ request.write(json_response)
+ request.finish()
+
+
+import json
diff --git a/service/pixelated/resources/attachments_resource.py b/service/pixelated/resources/attachments_resource.py
new file mode 100644
index 00000000..0ab214b9
--- /dev/null
+++ b/service/pixelated/resources/attachments_resource.py
@@ -0,0 +1,63 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# 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 io
+
+import re
+from twisted.protocols.basic import FileSender
+from twisted.python.log import err
+from twisted.web.resource import Resource
+
+
+class AttachmentResource(Resource):
+ def __init__(self, attachment_id, querier):
+ Resource.__init__(self)
+ self.attachment_id = attachment_id
+ self.querier = querier
+
+ def render_GET(self, request):
+ encoding = request.args.get('encoding', [None])[0]
+ filename = request.args.get('filename', [self.attachment_id])[0]
+ attachment = self.querier.attachment(self.attachment_id, encoding)
+
+ request.setHeader(b'Content-Type', b'application/force-download')
+ request.setHeader(b'Content-Disposition', bytes('attachment; filename=' + filename))
+ bytes_io = io.BytesIO(attachment['content'])
+ d = FileSender().beginFileTransfer(bytes_io, request)
+
+ def cb_finished(_):
+ bytes_io.close()
+ request.finish()
+
+ d.addErrback(err).addCallback(cb_finished)
+
+ return d
+
+ def _extract_mimetype(self, content_type):
+ match = re.compile('([A-Za-z-]+\/[A-Za-z-]+)').search(content_type)
+ return match.group(1)
+
+
+class AttachmentsResource(Resource):
+
+ isLeaf = True
+
+ def __init__(self, querier):
+ Resource.__init__(self)
+ self.querier = querier
+
+ def getChild(self, attachment_id, request):
+ return AttachmentResource(attachment_id, self.querier)
diff --git a/service/pixelated/resources/contacts_resource.py b/service/pixelated/resources/contacts_resource.py
new file mode 100644
index 00000000..94468a63
--- /dev/null
+++ b/service/pixelated/resources/contacts_resource.py
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# 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.resources import respond_json_deferred
+from twisted.internet.threads import deferToThread
+from twisted.web.resource import Resource
+
+
+class ContactsResource(Resource):
+
+ isLeaf = True
+
+ def __init__(self, search_engine):
+ Resource.__init__(self)
+ self._search_engine = search_engine
+
+ def render_GET(self, request):
+ query = request.args.get('q', [''])[0]
+ d = deferToThread(lambda: self._search_engine.contacts(query))
+ d.addCallback(lambda tags: respond_json_deferred(tags, request))
+
+ return d
diff --git a/service/pixelated/resources/features_resource.py b/service/pixelated/resources/features_resource.py
new file mode 100644
index 00000000..1784e463
--- /dev/null
+++ b/service/pixelated/resources/features_resource.py
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# 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.resources import respond_json
+import os
+from twisted.web.resource import Resource
+
+
+class FeaturesResource(Resource):
+ DISABLED_FEATURES = ['draftReply', 'encryptionStatus']
+
+ isLeaf = True
+
+ def render_GET(self, request):
+ try:
+ disabled_features = {'logout': os.environ['DISPATCHER_LOGOUT_URL']}
+ except KeyError:
+ disabled_features = {}
+ return respond_json({'disabled_features': self.DISABLED_FEATURES, 'dispatcher_features': disabled_features}, request)
diff --git a/service/pixelated/resources/mail_resource.py b/service/pixelated/resources/mail_resource.py
new file mode 100644
index 00000000..03873ffb
--- /dev/null
+++ b/service/pixelated/resources/mail_resource.py
@@ -0,0 +1,64 @@
+import json
+from pixelated.resources import respond_json
+from twisted.web.resource import Resource
+
+
+class MailTags(Resource):
+
+ isLeaf = True
+
+ def __init__(self, mail_id, mail_service, search_engine):
+ Resource.__init__(self)
+ self._search_engine = search_engine
+ self._mail_service = mail_service
+ self._mail_id = mail_id
+
+ def render_POST(self, request):
+ content_dict = json.loads(request.content.read())
+ new_tags = map(lambda tag: tag.lower(), content_dict['newtags'])
+ try:
+ self._mail_service.update_tags(self._mail_id, new_tags)
+ mail = self._mail_service.mail(self._mail_id)
+ self._search_engine.index_mail(mail)
+ except ValueError as ve:
+ return respond_json(ve.message, request, 403)
+ return respond_json(mail.as_dict(), request)
+
+
+class Mail(Resource):
+
+ def __init__(self, mail_id, mail_service, search_engine):
+ Resource.__init__(self)
+ self.putChild('tags', MailTags(mail_id, mail_service, search_engine))
+
+ self._search_engine = search_engine
+ self._mail_id = mail_id
+ self._mail_service = mail_service
+
+ def render_GET(self, request):
+ mail = self._mail_service.mail(self._mail_id)
+ return respond_json(mail.as_dict(), request)
+
+ def render_DELETE(self, request):
+ self._delete_mail(self._mail_id)
+ return respond_json(None, request)
+
+ def _delete_mail(self, mail_id):
+ mail = self._mail_service.mail(mail_id)
+ if mail.mailbox_name == 'TRASH':
+ self._mail_service.delete_permanent(mail_id)
+ self._search_engine.remove_from_index(mail_id)
+ else:
+ trashed_mail = self._mail_service.delete_mail(mail_id)
+ self._search_engine.index_mail(trashed_mail)
+
+
+class MailResource(Resource):
+
+ def __init__(self, mail_service, search_engine):
+ Resource.__init__(self)
+ self._mail_service = mail_service
+ self._search_engine = search_engine
+
+ def getChild(self, mail_id, request):
+ return Mail(mail_id, self._mail_service, self._search_engine)
diff --git a/service/pixelated/resources/mails_resource.py b/service/pixelated/resources/mails_resource.py
new file mode 100644
index 00000000..75c73349
--- /dev/null
+++ b/service/pixelated/resources/mails_resource.py
@@ -0,0 +1,128 @@
+import json
+from pixelated.adapter.model.mail import InputMail
+from pixelated.resources import respond_json
+from twisted.web.resource import Resource
+
+
+def _format_exception(e):
+ exception_info = map(str, list(e.args))
+ return '\n'.join(exception_info)
+
+
+class MailsUnreadResource(Resource):
+
+ isLeaf = True
+
+ def __init__(self, mail_service, search_engine):
+ Resource.__init__(self)
+ self._search_engine = search_engine
+ self._mail_service = mail_service
+
+ def render_POST(self, request):
+ content_dict = json.load(request.content)
+ idents = content_dict.get('idents')
+ for ident in idents:
+ mail = self._mail_service.mark_as_unread(ident)
+ self._search_engine.index_mail(mail)
+ return ""
+
+
+class MailsReadResource(Resource):
+
+ isLeaf = True
+
+ def __init__(self, mail_service, search_engine):
+ Resource.__init__(self)
+ self._search_engine = search_engine
+ self._mail_service = mail_service
+
+ def render_POST(self, request):
+ content_dict = json.load(request.content)
+ idents = content_dict.get('idents')
+ for ident in idents:
+ mail = self._mail_service.mark_as_read(ident)
+ self._search_engine.index_mail(mail)
+ return ""
+
+
+class MailsDeleteResource(Resource):
+
+ isLeaf = True
+
+ def __init__(self, mail_service, search_engine):
+ Resource.__init__(self)
+ self._mail_service = mail_service
+ self._search_engine = search_engine
+
+ def render_POST(self, request):
+ idents = json.loads(request.content.read())['idents']
+ for ident in idents:
+ self._delete_mail(ident)
+ return respond_json(None, request)
+
+ def _delete_mail(self, mail_id):
+ mail = self._mail_service.mail(mail_id)
+ if mail.mailbox_name == 'TRASH':
+ self._mail_service.delete_permanent(mail_id)
+ self._search_engine.remove_from_index(mail_id)
+ else:
+ trashed_mail = self._mail_service.delete_mail(mail_id)
+ self._search_engine.index_mail(trashed_mail)
+
+
+class MailsResource(Resource):
+
+ def __init__(self, search_engine, mail_service, draft_service):
+ Resource.__init__(self)
+ self.putChild('delete', MailsDeleteResource(mail_service, search_engine))
+ self.putChild('read', MailsReadResource(mail_service, search_engine))
+ self.putChild('unread', MailsUnreadResource(mail_service, search_engine))
+
+ self._draft_service = draft_service
+ self._mail_service = mail_service
+ self._search_engine = search_engine
+
+ def render_GET(self, request):
+ mail_ids, total = self._search_engine.search(request.args.get('q')[0], request.args.get('w')[0], request.args.get('p')[0])
+ mails = self._mail_service.mails(mail_ids)
+
+ response = {
+ "stats": {
+ "total": total,
+ },
+ "mails": [mail.as_dict() for mail in mails]
+ }
+
+ return json.dumps(response)
+
+ def render_POST(self, request):
+ try:
+ content_dict = json.loads(request.content.read())
+ _mail = InputMail.from_dict(content_dict)
+ draft_id = content_dict.get('ident')
+ if draft_id:
+ self._search_engine.remove_from_index(draft_id)
+ _mail = self._mail_service.send(draft_id, _mail)
+ self._search_engine.index_mail(_mail)
+
+ return respond_json(_mail.as_dict(), request)
+ except Exception as error:
+ return respond_json({'message': _format_exception(error)}, request, status_code=422)
+
+ def render_PUT(self, request):
+ content_dict = json.loads(request.content.read())
+ _mail = InputMail.from_dict(content_dict)
+ draft_id = content_dict.get('ident')
+
+ if draft_id:
+ if not self._mail_service.mail_exists(draft_id):
+ return respond_json("", request, status_code=422)
+ pixelated_mail = self._draft_service.update_draft(draft_id, _mail)
+ self._search_engine.remove_from_index(draft_id)
+ else:
+ pixelated_mail = self._draft_service.create_draft(_mail)
+ self._search_engine.index_mail(pixelated_mail)
+ return respond_json({'ident': pixelated_mail.ident}, request)
+
+
+
diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py
new file mode 100644
index 00000000..326654eb
--- /dev/null
+++ b/service/pixelated/resources/root_resource.py
@@ -0,0 +1,46 @@
+import os
+from pixelated.resources.attachments_resource import AttachmentsResource
+from pixelated.resources.contacts_resource import ContactsResource
+from pixelated.resources.features_resource import FeaturesResource
+from pixelated.resources.mail_resource import MailResource
+from pixelated.resources.mails_resource import MailsResource
+from pixelated.resources.sync_info_resource import SyncInfoResource
+from pixelated.resources.tags_resource import TagsResource
+from twisted.web.resource import Resource
+from twisted.web.static import File
+
+
+class RootResource(Resource):
+
+ def __init__(self):
+ Resource.__init__(self)
+ self._static_folder = self._get_static_folder()
+
+ def getChild(self, path, request):
+ if path == '':
+ return self
+ return Resource.getChild(self, path, request)
+
+ def initialize(self, querier, search_engine, mail_service, draft_service):
+ self.putChild('assets', File(self._static_folder))
+ self.putChild('attachments', AttachmentsResource(querier))
+ self.putChild('contacts', ContactsResource(search_engine))
+ self.putChild('features', FeaturesResource())
+ self.putChild('sync_info', SyncInfoResource())
+ self.putChild('tags', TagsResource(search_engine))
+ self.putChild('mails', MailsResource(search_engine, mail_service, draft_service))
+ self.putChild('mail', MailResource(mail_service, search_engine))
+
+ def _get_static_folder(self):
+
+ static_folder = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", "..", "web-ui", "app"))
+ # this is a workaround for packaging
+ if not os.path.exists(static_folder):
+ static_folder = os.path.abspath(
+ os.path.join(os.path.abspath(__file__), "..", "..", "..", "..", "web-ui", "app"))
+ if not os.path.exists(static_folder):
+ static_folder = os.path.join('/', 'usr', 'share', 'pixelated-user-agent')
+ return static_folder
+
+ def render_GET(self, request):
+ return open(os.path.join(self._static_folder, 'index.html')).read() \ No newline at end of file
diff --git a/service/pixelated/resources/sync_info_resource.py b/service/pixelated/resources/sync_info_resource.py
new file mode 100644
index 00000000..5aa94218
--- /dev/null
+++ b/service/pixelated/resources/sync_info_resource.py
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# 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.resources import respond_json
+from twisted.web.resource import Resource
+
+
+class SyncInfoResource(Resource):
+
+ isLeaf = True
+
+ def __init__(self):
+ Resource.__init__(self)
+ self.current = 0
+ self.total = 0
+
+ def _get_progress(self):
+ if self.total == 0:
+ return 0
+ return self.current / float(self.total)
+
+ def set_sync_info(self, soledad_sync_status):
+ self.current, self.total = map(int, soledad_sync_status.content.split('/'))
+
+ def render_GET(self, request):
+ _sync_info = {
+ 'is_syncing': self.current != self.total,
+ 'count': {
+ 'current': self.current,
+ 'total': self.total,
+ 'progress': self._get_progress()
+ }
+ }
+ return respond_json(_sync_info, request)
diff --git a/service/pixelated/resources/tags_resource.py b/service/pixelated/resources/tags_resource.py
new file mode 100644
index 00000000..8a8ab81f
--- /dev/null
+++ b/service/pixelated/resources/tags_resource.py
@@ -0,0 +1,38 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# 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.resources import respond_json_deferred
+from twisted.internet.threads import deferToThread
+from twisted.web.resource import Resource
+from twisted.web.server import NOT_DONE_YET
+
+
+class TagsResource(Resource):
+
+ isLeaf = True
+
+ def __init__(self, search_engine):
+ Resource.__init__(self)
+ self._search_engine = search_engine
+
+ def render_GET(self, request):
+ query = request.args.get('q', [''])[0]
+ skip_default_tags = request.args.get('skipDefaultTags', [False])[0]
+
+ d = deferToThread(lambda: self._search_engine.tags(query=query, skip_default_tags=skip_default_tags))
+ d.addCallback(lambda tags: respond_json_deferred(tags, request))
+
+ return NOT_DONE_YET