diff options
author | Bruno Wagner <bwagner@thoughtworks.com> | 2014-09-15 16:28:31 -0300 |
---|---|---|
committer | Bruno Wagner <bwagner@thoughtworks.com> | 2014-09-15 16:28:31 -0300 |
commit | 5dc16a1e654e78d9b600578a0e2276cba8d94158 (patch) | |
tree | 0b12d78f54a53112e110ecf0e1bcce197f13e3ca /fake-service/app/adapter | |
parent | f2bb13595d67775e8ea89ea595cdbe8b7db96dd8 (diff) |
Moved py-fake-service to fake-service, because we only have one now
Diffstat (limited to 'fake-service/app/adapter')
-rw-r--r-- | fake-service/app/adapter/__init__.py | 16 | ||||
-rw-r--r-- | fake-service/app/adapter/contacts.py | 41 | ||||
-rw-r--r-- | fake-service/app/adapter/mail.py | 91 | ||||
-rw-r--r-- | fake-service/app/adapter/mail_service.py | 128 | ||||
-rw-r--r-- | fake-service/app/adapter/mailset.py | 69 | ||||
-rw-r--r-- | fake-service/app/adapter/tag.py | 40 | ||||
-rw-r--r-- | fake-service/app/adapter/tagsset.py | 57 |
7 files changed, 442 insertions, 0 deletions
diff --git a/fake-service/app/adapter/__init__.py b/fake-service/app/adapter/__init__.py new file mode 100644 index 00000000..55f91e08 --- /dev/null +++ b/fake-service/app/adapter/__init__.py @@ -0,0 +1,16 @@ +# +# 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 mail_service import MailService diff --git a/fake-service/app/adapter/contacts.py b/fake-service/app/adapter/contacts.py new file mode 100644 index 00000000..30ff1253 --- /dev/null +++ b/fake-service/app/adapter/contacts.py @@ -0,0 +1,41 @@ +# +# 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 re + + +class Contacts: + + def __init__(self): + self.contacts = [] + + def add(self, mbox_mail): + contact = mbox_mail.get('From') or mbox_mail.from_addr + self.contacts.append(Contact(contact)) + + def search(self, query): + contacts_query = re.compile(query) + return [ + contact.__dict__ + for contact in self.contacts + if contacts_query.match(contact.addresses[0]) + ] + + +class Contact: + + def __init__(self, contact): + self.addresses = [contact] + self.name = '' diff --git a/fake-service/app/adapter/mail.py b/fake-service/app/adapter/mail.py new file mode 100644 index 00000000..26c00277 --- /dev/null +++ b/fake-service/app/adapter/mail.py @@ -0,0 +1,91 @@ +# +# 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 datetime import datetime +import random +import calendar +from dateutil import parser + +class Mail: + + NOW = calendar.timegm( + datetime.strptime( + datetime.now().isoformat(), + "%Y-%m-%dT%H:%M:%S.%f").timetuple()) + + @staticmethod + def from_json(mail_json): + mail = Mail() + mail.header = mail_json['header'] + mail.header['date'] = datetime.now().isoformat() + mail.ident = mail_json.get('ident', 0) + mail.body = mail_json['body'] + mail.tags = mail_json['tags'] + mail.security_casing = {} + mail.status = [] + mail.draft_reply_for = mail_json.get('draft_reply_for', 0) + return mail + + def __init__(self, mbox_mail=None, ident=None): + if mbox_mail: + self.header = self._get_headers(mbox_mail) + self.ident = ident + self.body = self._get_body(mbox_mail) + self.tags = self._get_tags(mbox_mail) + self.security_casing = {} + self.status = self._get_status() + self.draft_reply_for = -1 + + def _get_body(self, message): + if message.is_multipart(): + boundary = '--{boundary}'.format( + boundary=message.get_boundary().strip()) + body_parts = [x.as_string() for x in message.get_payload()] + + body = boundary + '\n' + body += '{boundary}\n'.format(boundary=boundary).join(body_parts) + body += '{boundary}--\n'.format(boundary=boundary) + + return body + else: + return message.get_payload() + + def _get_status(self): + status = [] + if 'sent' in self.tags: + status.append('read') + + return status + + def _get_headers(self, mbox_mail): + headers = {} + headers['from'] = mbox_mail.get('From') or mbox_mail.from_addr + headers['to'] = [mbox_mail.get('To')] + headers['subject'] = mbox_mail.get('Subject') + headers['date'] = parser.parse(mbox_mail['Date']).isoformat() + headers['content_type'] = mbox_mail.get('Content-Type') + + return headers + + def _get_tags(self, mbox_mail): + return filter(len, mbox_mail.get('X-TW-Pixelated-Tags').split(', ')) + + @property + def subject(self): + return self.header['subject'] + + @property + def date(self): + return self.header['date'] diff --git a/fake-service/app/adapter/mail_service.py b/fake-service/app/adapter/mail_service.py new file mode 100644 index 00000000..2825af9d --- /dev/null +++ b/fake-service/app/adapter/mail_service.py @@ -0,0 +1,128 @@ +# +# 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 os +import re +import mailbox + +from tagsset import TagsSet +from mailset import MailSet +from contacts import Contacts +from mail import Mail + + +class MailService: + MAILSET_PATH = os.path.join(os.environ['HOME'], 'mailsets', 'mediumtagged') + + def __init__(self): + self.mailset = MailSet() + self.tagsset = TagsSet() + self.contacts = Contacts() + + def reset(self): + self.mailset = MailSet() + self.tagsset = TagsSet() + self.contacts = Contacts() + + def _read_file(self, filename): + with open(filename, 'r') as fd: + return fd.read() + + def _create_message_from_file(self, filename): + data = self._read_file(filename) + return self.create_message_from_string(data, filename) + + def create_message_from_string(self, data, filename=None): + if data.startswith('From '): + msg = mailbox.mboxMessage(data) + from_addr = re.sub(r"^From ", "", msg.get_unixfrom()) + msg.from_addr = from_addr + msg.set_from(from_addr) + else: + msg = mailbox.Message(data) + msg.from_addr = msg.get('From') + return msg + + def _create_message_from_string(self, data): + return mailbox.Message(data) + + def load_mailset(self): + mbox_filenames = [ + filename + for filename in os.listdir + (self.MAILSET_PATH) if filename.startswith('mbox')] + messages = (self._create_message_from_file(os.path.join(self.MAILSET_PATH, mbox)) + for mbox in mbox_filenames) + + self.index_messages(messages) + + def index_messages(self, messages): + for message in messages: + self.mailset.add(message) + self.tagsset.add(message) + self.contacts.add(message) + + def mails(self, query, page, window_size): + mails = self.mailset.values() + mails = [mail for mail in mails if query.test(mail)] + return sorted(mails, key=lambda mail: mail.date, reverse=True) + + def mail(self, mail_id): + return self.mailset.get(mail_id) + + def search_contacts(self, query): + return self.contacts.search(query) + + def mark_as_read(self, mail_id): + self.mailset.mark_as_read(mail_id) + self.tagsset.mark_as_read(self.mail(mail_id).tags) + + def delete_mail(self, mail_id): + purged = self.mailset.delete(mail_id) + if not purged: + self.tagsset.increment_tag_total_count('trash') + + def update_tags_for(self, mail_id, new_tags): + mail = self.mail(mail_id) + + new_tags_set = set(new_tags) + old_tags_set = set(mail.tags) + + increment_set = new_tags_set - old_tags_set + decrement_set = old_tags_set - new_tags_set + + map(lambda x: self.tagsset.increment_tag_total_count(x), increment_set) + map(lambda x: self.tagsset.decrement_tag_total_count(x), decrement_set) + + mail.tags = new_tags + + def send(self, mail): + mail = Mail.from_json(mail) + self.mailset.update(mail) + self.tagsset.increment_tag_total_count('sent') + self.tagsset.decrement_tag_total_count('drafts') + return mail.ident + + def save_draft(self, mail): + mail = self.mailset.add_draft(Mail.from_json(mail)) + return mail.ident + + def update_draft(self, mail): + mail = Mail.from_json(mail) + self.mailset.update(mail) + return mail.ident + + def draft_reply_for(self, mail_id): + return self.mailset.find(draft_reply_for=mail_id) diff --git a/fake-service/app/adapter/mailset.py b/fake-service/app/adapter/mailset.py new file mode 100644 index 00000000..bf7e8c67 --- /dev/null +++ b/fake-service/app/adapter/mailset.py @@ -0,0 +1,69 @@ +# +# 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 mail import Mail + + +class MailSet: + + def __init__(self): + self.ident = 0 + self.mails = {} + + def add(self, mbox_mail): + self.mails[self.ident] = Mail(mbox_mail, self.ident) + self.ident += 1 + + def values(self): + return self.mails.values() + + def get(self, mail_id): + return self.mails.get(mail_id) + + def mark_as_read(self, mail_id): + mail = self.get(mail_id) + mail.status.append('read') + + def delete(self, mail_id): + """ + Returns True if the email got purged, + else returns False meaning the email got moved to trash + """ + + mail = self.get(mail_id) + if 'trash' in mail.tags: + del self.mails[mail_id] + return True + mail.tags.append('trash') + return False + + def update(self, mail): + self.mails[mail.ident] = mail + + def add_draft(self, mail): + mail.ident = self.ident + self.mails[mail.ident] = mail + self.ident += 1 + return mail + + def find(self, draft_reply_for): + match = [ + mail + for mail in self.mails.values + () if mail.draft_reply_for == draft_reply_for] + if len(match) == 0: + return None + else: + return match[0] diff --git a/fake-service/app/adapter/tag.py b/fake-service/app/adapter/tag.py new file mode 100644 index 00000000..b866d789 --- /dev/null +++ b/fake-service/app/adapter/tag.py @@ -0,0 +1,40 @@ +# +# 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/>. + + +class Tag: + DEFAULT_TAGS = ["inbox", "sent", "trash", "drafts"] + + def __init__(self, name, ident): + self.counts = { + 'total': 0, + 'read': 0, + 'starred': 0, + 'reply': 0 + } + + self.ident = ident + self.name = name.lower() + self.default = name in self.DEFAULT_TAGS + + def increment_count(self): + self.counts['total'] += 1 + + def increment_read(self): + self.counts['read'] += 1 + + def decrement_count(self): + self.counts['total'] -= 1 diff --git a/fake-service/app/adapter/tagsset.py b/fake-service/app/adapter/tagsset.py new file mode 100644 index 00000000..8e0d7ca3 --- /dev/null +++ b/fake-service/app/adapter/tagsset.py @@ -0,0 +1,57 @@ +# +# 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 tag import Tag + + +class TagsSet: + + DEFAULT_TAGS = ["inbox", "sent", "trash", "drafts"] + + def __init__(self): + self.ident = 0 + self.tags = {} + self.tags = {tag: self._create_new_tag(tag) for tag in self.DEFAULT_TAGS} + + def add(self, mbox_mail): + tags = filter(len, mbox_mail.get('X-TW-Pixelated-Tags').split(', ')) + for tag in tags: + tag = self._create_new_tag(tag) + tag.increment_count() + + def all_tags(self): + return self.tags.values() + + def mark_as_read(self, tags): + for tag in tags: + tag = tag.lower() + tag = self.tags.get(tag) + tag.increment_read() + + def increment_tag_total_count(self, tagname): + tag = self.tags.get(tagname) + if tag: + tag.increment_count() + else: + self._create_new_tag(tagname) + + def decrement_tag_total_count(self, tag): + self.tags.get(tag).decrement_count() + + def _create_new_tag(self, tag): + tag = Tag(tag, self.ident) + tag = self.tags.setdefault(tag.name, tag) + self.ident += 1 + return tag |