summaryrefslogtreecommitdiff
path: root/fake-service/app/adapter
diff options
context:
space:
mode:
authorBruno Wagner <bwagner@thoughtworks.com>2014-09-15 16:28:31 -0300
committerBruno Wagner <bwagner@thoughtworks.com>2014-09-15 16:28:31 -0300
commit5dc16a1e654e78d9b600578a0e2276cba8d94158 (patch)
tree0b12d78f54a53112e110ecf0e1bcce197f13e3ca /fake-service/app/adapter
parentf2bb13595d67775e8ea89ea595cdbe8b7db96dd8 (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__.py16
-rw-r--r--fake-service/app/adapter/contacts.py41
-rw-r--r--fake-service/app/adapter/mail.py91
-rw-r--r--fake-service/app/adapter/mail_service.py128
-rw-r--r--fake-service/app/adapter/mailset.py69
-rw-r--r--fake-service/app/adapter/tag.py40
-rw-r--r--fake-service/app/adapter/tagsset.py57
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