summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDuda Dornelles <ddornell@thoughtworks.com>2014-09-11 14:25:19 -0300
committerDuda Dornelles <ddornell@thoughtworks.com>2014-09-11 14:25:19 -0300
commit332e7d54e0e4c3d71e20a9dc8d9957298e6dcb90 (patch)
tree3be446548f7149e32def8eadf595cb1ce4f4a8ac
parente0cd19256171b18eee571808f7fc12cd042faf19 (diff)
Refactoring tags functionality into TagService
-rw-r--r--service/pixelated/adapter/mail_service.py20
-rw-r--r--service/pixelated/adapter/pixelated_mailbox.py48
-rw-r--r--service/pixelated/adapter/pixelated_mailboxes.py11
-rw-r--r--service/pixelated/adapter/tag.py4
-rw-r--r--service/pixelated/adapter/tag_index.py7
-rw-r--r--service/pixelated/adapter/tag_service.py55
-rw-r--r--service/pixelated/user_agent.py3
-rw-r--r--service/test/adapter/mail_service_test.py9
-rw-r--r--service/test/adapter/pixelated_mailbox_test.py56
-rw-r--r--service/test/adapter/test_tag_service.py59
10 files changed, 154 insertions, 118 deletions
diff --git a/service/pixelated/adapter/mail_service.py b/service/pixelated/adapter/mail_service.py
index 7f0d111b..52c1abc7 100644
--- a/service/pixelated/adapter/mail_service.py
+++ b/service/pixelated/adapter/mail_service.py
@@ -13,25 +13,23 @@
#
# 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.adapter.tag_service import TagService
class MailService:
__slots__ = ['leap_session', 'account', 'mailbox_name']
- def __init__(self, mailboxes, mail_sender):
+ ALL_MAILS_QUERY = {'tags': ['all']}
+
+ def __init__(self, mailboxes, mail_sender, tag_service=TagService.get_instance()):
+ self.tag_service = tag_service
self.mailboxes = mailboxes
self.mail_sender = mail_sender
-
- @property
- def mailbox(self):
- return self.mailboxes.inbox()
+ self.tag_service.load_index(self.mails(MailService.ALL_MAILS_QUERY))
def mails(self, query):
_mails = None
- if not query['tags']:
- return self.mailbox.mails()
-
if query['tags']:
_mails = self.mailboxes.mails_by_tag(query['tags'])
@@ -40,17 +38,17 @@ class MailService:
def update_tags(self, mail_id, new_tags):
mail = self.mail(mail_id)
added, removed = mail.update_tags(set(new_tags))
- self.mailbox.notify_tags_updated(added, removed, mail_id)
+ self.tag_service.notify_tags_updated(added, removed, mail_id)
return new_tags
def mail(self, mail_id):
- return self.mailbox.mail(mail_id)
+ return self.mailboxes.mail(mail_id)
def send(self, mail):
self.mail_sender.sendmail(mail)
def all_tags(self):
- return self.mailbox.all_tags()
+ return self.tag_service.all_tags()
def thread(self, thread_id):
raise NotImplementedError()
diff --git a/service/pixelated/adapter/pixelated_mailbox.py b/service/pixelated/adapter/pixelated_mailbox.py
index 6323d3c3..06f30896 100644
--- a/service/pixelated/adapter/pixelated_mailbox.py
+++ b/service/pixelated/adapter/pixelated_mailbox.py
@@ -14,48 +14,39 @@
# 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
-
from pixelated.adapter.pixelated_mail import PixelatedMail
-from pixelated.adapter.tag import Tag
-from pixelated.adapter.tag_index import TagIndex
+from pixelated.adapter.tag_service import TagService
from pixelated.support.id_gen import gen_pixelated_uid
class PixelatedMailbox:
- SPECIAL_TAGS = set([Tag('inbox', True), Tag('sent', True), Tag('drafts', True), Tag('trash', True)])
-
- def __init__(self, leap_mailbox, index_file_path):
+ def __init__(self, leap_mailbox, tag_service=TagService.get_instance()):
+ self.tag_service = tag_service
self.leap_mailbox = leap_mailbox
- self.tag_index = TagIndex(index_file_path)
- if self.tag_index.empty():
- for mail in self.mails():
- self.notify_tags_updated(mail.tags, [], mail.ident)
- for tag in self.SPECIAL_TAGS:
- self.tag_index.add(tag)
+ self.mailbox_tag = self.leap_mailbox.mbox.lower()
@property
def messages(self):
return self.leap_mailbox.messages
+ def add_mailbox_tag_if_not_there(self, pixelated_mail):
+ if not pixelated_mail.has_tag(self.mailbox_tag):
+ pixelated_mail.update_tags({self.mailbox_tag}.union(pixelated_mail.tags))
+ self.tag_service.notify_tags_updated({self.mailbox_tag}, [], pixelated_mail.ident)
+
def mails(self):
mails = self.leap_mailbox.messages or []
result = []
- mailbox_name = self.leap_mailbox.mbox
for mail in mails:
pixelated_mail = PixelatedMail.from_leap_mail(mail)
- if not pixelated_mail.has_tag(mailbox_name):
- new_tags = set([mailbox_name.lower()])
- pixelated_mail.update_tags(new_tags.union(pixelated_mail.tags))
- self.notify_tags_updated(new_tags, [], pixelated_mail.ident)
+ self.add_mailbox_tag_if_not_there(pixelated_mail)
result.append(pixelated_mail)
return result
def mails_by_tags(self, tags):
if 'all' in tags:
return self.mails()
-
return [mail for mail in self.mails() if len(mail.tags.intersection(tags)) > 0]
def mail(self, mail_id):
@@ -63,23 +54,6 @@ class PixelatedMailbox:
if gen_pixelated_uid(self.leap_mailbox.mbox, message.getUID()) == mail_id:
return PixelatedMail.from_leap_mail(message)
- def all_tags(self):
- return self.tag_index.values().union(self.SPECIAL_TAGS)
-
- def notify_tags_updated(self, added_tags, removed_tags, mail_ident):
- for removed_tag in removed_tags:
- tag = self.tag_index.get(removed_tag)
- tag.decrement(mail_ident)
- if tag.total == 0:
- self.tag_index.remove(tag.name)
- else:
- self.tag_index.set(tag)
- for added_tag in added_tags:
- tag = self.tag_index.get(added_tag) or Tag(added_tag)
- tag.increment(mail_ident)
- self.tag_index.set(tag)
-
@classmethod
def create(cls, account, mailbox_name='INBOX'):
- db_path = os.path.join(os.environ['HOME'], '.pixelated_index')
- return PixelatedMailbox(account.getMailbox(mailbox_name), db_path)
+ return PixelatedMailbox(account.getMailbox(mailbox_name))
diff --git a/service/pixelated/adapter/pixelated_mailboxes.py b/service/pixelated/adapter/pixelated_mailboxes.py
index 652080ca..411e8ac0 100644
--- a/service/pixelated/adapter/pixelated_mailboxes.py
+++ b/service/pixelated/adapter/pixelated_mailboxes.py
@@ -4,10 +4,6 @@ from pixelated.adapter.pixelated_mailbox import PixelatedMailbox
class PixelatedMailBoxes():
def __init__(self, account):
self.account = account
- self.mailbox_name = 'INBOX'
-
- def inbox(self):
- return PixelatedMailbox.create(self.account)
@property
def mailboxes(self):
@@ -21,5 +17,8 @@ class PixelatedMailBoxes():
return mails
- def leap_inbox_mailbox(self):
- return self.account.getMailbox(self.mailbox_name)
+ def mail(self, mail_id):
+ for mailbox in self.mailboxes:
+ mail = mailbox.mail(mail_id)
+ if mail:
+ return mail
diff --git a/service/pixelated/adapter/tag.py b/service/pixelated/adapter/tag.py
index ba545043..2e965fdf 100644
--- a/service/pixelated/adapter/tag.py
+++ b/service/pixelated/adapter/tag.py
@@ -36,8 +36,8 @@ class Tag:
return len(self.mails)
def __init__(self, name, default=False):
- self.name = name
- self.ident = name.__hash__()
+ self.name = name.lower()
+ self.ident = self.name.__hash__()
self.default = default
self.mails = set()
diff --git a/service/pixelated/adapter/tag_index.py b/service/pixelated/adapter/tag_index.py
index 357f7513..2a0955ef 100644
--- a/service/pixelated/adapter/tag_index.py
+++ b/service/pixelated/adapter/tag_index.py
@@ -13,9 +13,10 @@
#
# 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 dbm
import atexit
+import os
from pixelated.adapter.tag import Tag
@@ -23,10 +24,11 @@ class TagIndex:
"""
Manages an index for mail's tags using a file storage.
"""
+ DB_PATH = os.path.join(os.environ['HOME'], '.pixelated_index')
__db_instances = dict()
- def __init__(self, db_path):
+ def __init__(self, db_path=DB_PATH):
self.db_path = db_path
if db_path not in TagIndex.__db_instances:
TagIndex.__db_instances[db_path] = dbm.open(db_path, 'c')
@@ -39,6 +41,7 @@ class TagIndex:
def add(self, tag):
if tag.name not in self.db:
self.set(tag)
+ return tag
def get(self, tag_name):
if tag_name in self.db:
diff --git a/service/pixelated/adapter/tag_service.py b/service/pixelated/adapter/tag_service.py
new file mode 100644
index 00000000..00f28b40
--- /dev/null
+++ b/service/pixelated/adapter/tag_service.py
@@ -0,0 +1,55 @@
+#
+# 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.adapter.tag import Tag
+from pixelated.adapter.tag_index import TagIndex
+
+
+class TagService:
+
+ instance = None
+ SPECIAL_TAGS = {Tag('inbox', True), Tag('sent', True), Tag('drafts', True), Tag('trash', True)}
+
+ @classmethod
+ def get_instance(cls):
+ if not cls.instance:
+ cls.instance = TagService()
+ return cls.instance
+
+ def __init__(self, tag_index=TagIndex()):
+ self.tag_index = tag_index
+
+ def load_index(self, mails):
+ if self.tag_index.empty():
+ for mail in mails:
+ self.notify_tags_updated(mail.tags, [], mail.ident)
+ for tag in self.SPECIAL_TAGS:
+ self.tag_index.add(tag)
+
+ def notify_tags_updated(self, added_tags, removed_tags, mail_ident):
+ for removed_tag in removed_tags:
+ tag = self.tag_index.get(removed_tag)
+ tag.decrement(mail_ident)
+ if tag.total == 0:
+ self.tag_index.remove(tag.name)
+ else:
+ self.tag_index.set(tag)
+ for added_tag in added_tags:
+ tag = self.tag_index.get(added_tag) or self.tag_index.add(Tag(added_tag))
+ tag.increment(mail_ident)
+ self.tag_index.set(tag)
+
+ def all_tags(self):
+ return self.tag_index.values().union(self.SPECIAL_TAGS)
diff --git a/service/pixelated/user_agent.py b/service/pixelated/user_agent.py
index bf48e740..aba06f05 100644
--- a/service/pixelated/user_agent.py
+++ b/service/pixelated/user_agent.py
@@ -22,6 +22,7 @@ from flask import request
from flask import Response
from pixelated.adapter.pixelated_mail_sender import PixelatedMailSender
from pixelated.adapter.pixelated_mailboxes import PixelatedMailBoxes
+from pixelated.adapter.tag_service import TagService
import pixelated.reactor_manager as reactor_manager
import pixelated.search_query as search_query
import pixelated.bitmask_libraries.session as LeapSession
@@ -114,7 +115,7 @@ def mail(mail_id):
@app.route('/mail/<mail_id>/tags', methods=['POST'])
def mail_tags(mail_id):
- new_tags = request.get_json()['newtags']
+ new_tags = map(lambda tag: tag.lower(), request.get_json()['newtags'])
tags = mail_service.update_tags(mail_id, new_tags)
return respond_json(tags)
diff --git a/service/test/adapter/mail_service_test.py b/service/test/adapter/mail_service_test.py
index b6082b45..e96b7e11 100644
--- a/service/test/adapter/mail_service_test.py
+++ b/service/test/adapter/mail_service_test.py
@@ -25,15 +25,6 @@ class TestMailService(unittest.TestCase):
self.mail_sender = mock()
self.mail_service = MailService(self.mailboxes, self.mail_sender)
- def test_search_without_query_returns_unfiltered_mailbox(self):
- mailbox_inbox = mock()
- when(mailbox_inbox).mails().thenReturn(["mail"])
- when(self.mailboxes).inbox().thenReturn(mailbox_inbox)
-
- mails = self.mail_service.mails({'tags': {}})
-
- self.assertEqual(1, len(mails))
-
def test_send_mail(self):
mail = "mail"
diff --git a/service/test/adapter/pixelated_mailbox_test.py b/service/test/adapter/pixelated_mailbox_test.py
index 6b84616f..6574d407 100644
--- a/service/test/adapter/pixelated_mailbox_test.py
+++ b/service/test/adapter/pixelated_mailbox_test.py
@@ -14,64 +14,20 @@
# 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 unittest
-import os
-
+from mockito import *
import test_helper
-from pixelated.adapter.tag import Tag
-from pixelated.adapter.tag_index import TagIndex
from pixelated.adapter.pixelated_mailbox import PixelatedMailbox
class TestPixelatedMailbox(unittest.TestCase):
-
def setUp(self):
- self.db_file_path = '/tmp/test_tags'
-
- def tearDown(self):
- TagIndex(self.db_file_path)._close_db()
- os.remove(self.db_file_path + '.db')
-
- def test_special_tags_always_exists(self):
- mailbox = PixelatedMailbox(test_helper.leap_mailbox(), self.db_file_path)
- self.assertEquals(mailbox.SPECIAL_TAGS, mailbox.all_tags())
-
- def test_retrieve_all_tags_from_mailbox(self):
- tag_index = TagIndex(self.db_file_path)
- tag_index.set(Tag('one_tag'))
- tag_index.set(Tag('two_tag'))
- mailbox = PixelatedMailbox(test_helper.leap_mailbox(), self.db_file_path)
- expected_tags = set([Tag('one_tag'), Tag('two_tag')]).union(mailbox.SPECIAL_TAGS)
- self.assertEquals(expected_tags, mailbox.all_tags())
-
- def test_notify_tags_updated_method_properly_changes_tags_state(self):
- tag_index = TagIndex(self.db_file_path)
- tag = Tag('one_tag')
- tag.increment(12)
- tag_index.set(tag)
- mailbox = PixelatedMailbox(test_helper.leap_mailbox(), self.db_file_path)
- self.assertEquals(0, mailbox.tag_index.get('inbox').total)
- self.assertEquals(1, mailbox.tag_index.get('one_tag').total)
-
- mailbox.notify_tags_updated(set(['inbox']), set(['one_tag']), 12)
-
- self.assertEquals(1, mailbox.tag_index.get('inbox').total)
- self.assertIsNone(mailbox.tag_index.get('one_tag'))
-
- def test_mailbox_tag_is_added_when_new_mail_arrives(self):
mail_one = test_helper.leap_mail(uid=0, mbox='SENT')
-
leap_mailbox = test_helper.leap_mailbox(messages=[mail_one], mailbox_name='SENT')
- mailbox = PixelatedMailbox(leap_mailbox, self.db_file_path)
-
- from pixelated.support.id_gen import gen_pixelated_uid
- mail = mailbox.mail(gen_pixelated_uid('SENT', 0))
- self.assertIn('sent', mail.tags)
- def test_index_is_initialized_with_mail_tags_if_empty(self):
- mail_one = test_helper.leap_mail(uid=0, extra_headers={'X-Tags': ['tag_1']})
- mail_two = test_helper.leap_mail(uid=1, extra_headers={'X-Tags': ['tag_2']})
+ self.tag_service = mock()
+ self.mailbox = PixelatedMailbox(leap_mailbox, self.tag_service)
- leap_mailbox = test_helper.leap_mailbox(messages=[mail_one, mail_two])
+ def test_mailbox_tag_is_added_when_new_mail_arrives(self):
+ mails = self.mailbox.mails()
- mailbox = PixelatedMailbox(leap_mailbox, self.db_file_path)
- self.assertEquals(set([Tag('tag_1'), Tag('tag_2'), Tag('inbox'), Tag('sent'), Tag('drafts'), Tag('trash')]), mailbox.all_tags())
+ self.assertIn('sent', mails[0].tags)
diff --git a/service/test/adapter/test_tag_service.py b/service/test/adapter/test_tag_service.py
new file mode 100644
index 00000000..20888092
--- /dev/null
+++ b/service/test/adapter/test_tag_service.py
@@ -0,0 +1,59 @@
+#
+# 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 unittest
+import tempfile
+import test_helper
+from pixelated.adapter.tag import Tag
+from pixelated.adapter.pixelated_mail import PixelatedMail
+from pixelated.adapter.tag_index import TagIndex
+from pixelated.adapter.tag_service import TagService
+
+
+class TagServiceTest(unittest.TestCase):
+ def setUp(self):
+ self.index_file_handler, self.index_file_path = tempfile.mkstemp()
+ self.tag_index = TagIndex(self.index_file_path)
+ self.tag_service = TagService(tag_index=self.tag_index)
+
+ def test_index_is_initialized_with_mail_tags_if_empty(self):
+ mail_one = PixelatedMail.from_leap_mail(test_helper.leap_mail(uid=0, extra_headers={'X-Tags': ['tag_1']}))
+ mail_two = PixelatedMail.from_leap_mail(test_helper.leap_mail(uid=1, extra_headers={'X-Tags': ['tag_2']}))
+ mails = [mail_one, mail_two]
+
+ self.tag_service.load_index(mails)
+
+ self.assertEqual(self.tag_service.all_tags(), {Tag('sent'), Tag('inbox'), Tag('drafts'), Tag('trash'), Tag('tag_1'), Tag('tag_2')})
+
+ def test_special_tags_always_exists(self):
+ self.tag_service.load_index([])
+
+ self.assertEqual(self.tag_service.all_tags(), {Tag('sent'), Tag('inbox'), Tag('drafts'), Tag('trash')})
+
+ def test_notify_tags_updated_method_properly_changes_tags_state(self):
+ mail_ident = 12
+ tag = Tag('one_tag')
+ tag.increment(mail_ident)
+ self.tag_service.load_index([])
+ self.tag_service.tag_index.set(tag)
+
+ self.assertEquals(0, self.tag_service.tag_index.get('inbox').total)
+ self.assertEquals(1, self.tag_service.tag_index.get('one_tag').total)
+
+ self.tag_service.notify_tags_updated(set(['inbox']), set(['one_tag']), mail_ident)
+
+ self.assertEquals(1, self.tag_service.tag_index.get('inbox').total)
+ self.assertIsNone(self.tag_service.tag_index.get('one_tag'))