diff options
author | Patrick Maia and Victor Shyba <pixelated-team+pmaia+vshyba@thoughtworks.com> | 2014-09-05 22:45:55 +0000 |
---|---|---|
committer | Patrick Maia <pmaia@thoughtworks.com> | 2014-09-05 22:46:17 +0000 |
commit | 3c79a54ab332e15f31a4a57a4a9baabf4b62e26a (patch) | |
tree | ce6b592049227da18b0500f4695b0feb490a944d /service/pixelated/adapter | |
parent | d2cf8b51904420917a5f86986ce7c02e89935998 (diff) |
#51 - persists new tags globally (in a local file) and shows on tag list
Diffstat (limited to 'service/pixelated/adapter')
-rw-r--r-- | service/pixelated/adapter/mail_service.py | 12 | ||||
-rw-r--r-- | service/pixelated/adapter/pixelated_mail.py | 16 | ||||
-rw-r--r-- | service/pixelated/adapter/pixelated_mailbox.py | 37 | ||||
-rw-r--r-- | service/pixelated/adapter/tag.py | 61 | ||||
-rw-r--r-- | service/pixelated/adapter/tag_index.py | 44 |
5 files changed, 105 insertions, 65 deletions
diff --git a/service/pixelated/adapter/mail_service.py b/service/pixelated/adapter/mail_service.py index e3444b9f..2c67ae2d 100644 --- a/service/pixelated/adapter/mail_service.py +++ b/service/pixelated/adapter/mail_service.py @@ -14,8 +14,6 @@ # 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 - class MailService: __slots__ = ['leap_session', 'account', 'mailbox_name'] @@ -41,13 +39,9 @@ class MailService: def update_tags(self, mail_id, new_tags): mail = self.mail(mail_id) - tags = set(Tag(str_tag) for str_tag in new_tags) - current_tags = mail.update_tags(tags) - self._update_mailbox_tags(tags) - return current_tags - - def _update_mailbox_tags(self, tags): - self.mailbox.update_tags(tags) + added, removed = mail.update_tags(new_tags) + self.mailbox.notify_tags_updated(added, removed, self.ident) + return new_tags def mail(self, mail_id): return self.mailbox.mail(mail_id) diff --git a/service/pixelated/adapter/pixelated_mail.py b/service/pixelated/adapter/pixelated_mail.py index cd34fe46..31e8ccc7 100644 --- a/service/pixelated/adapter/pixelated_mail.py +++ b/service/pixelated/adapter/pixelated_mail.py @@ -13,7 +13,6 @@ # # 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.status import Status import dateutil.parser as dateparser from email.MIMEMultipart import MIMEMultipart @@ -68,34 +67,35 @@ class PixelatedMail: return temporary_headers def _extract_tags(self): - return set(Tag(tag_name) for tag_name in self.headers.get('x-tags', [])) + return set(self.headers.get('x-tags', [])) def update_tags(self, tags): + old_tags = self.tags self.tags = tags + removed = old_tags.difference(tags) + added = tags.difference(old_tags) self._persist_mail_tags(tags) - return self.tags + return added, removed def mark_as_read(self): self.status.add("read") def _persist_mail_tags(self, current_tags): - tags_headers = [tag.name for tag in current_tags] hdoc = self.leap_mail.hdoc - hdoc.content['headers']['X-Tags'] = tags_headers + hdoc.content['headers']['X-Tags'] = current_tags self.leap_mail._soledad.put_doc(hdoc) def has_tag(self, tag): - return Tag(tag) in self.tags + return tag in self.tags def as_dict(self): - tags = [tag.name for tag in self.tags] statuses = [status.name for status in self.status] _headers = self.headers.copy() _headers['date'] = self.date return { 'header': _headers, 'ident': self.ident, - 'tags': tags, + 'tags': self.tags, 'status': statuses, 'security_casing': self.security_casing, 'body': self.body diff --git a/service/pixelated/adapter/pixelated_mailbox.py b/service/pixelated/adapter/pixelated_mailbox.py index 06e0cccc..3424ddd7 100644 --- a/service/pixelated/adapter/pixelated_mailbox.py +++ b/service/pixelated/adapter/pixelated_mailbox.py @@ -17,14 +17,19 @@ from pixelated.adapter.pixelated_mail import PixelatedMail from pixelated.adapter.tag import Tag +from pixelated.adapter.tag_index import TagIndex class PixelatedMailbox: - SPECIAL_TAGS = ['inbox', 'sent', 'drafts', 'trash'] + SPECIAL_TAGS = set([Tag('inbox', True), Tag('sent', True), Tag('drafts', True), Tag('trash', True)]) - def __init__(self, leap_mailbox): + def __init__(self, leap_mailbox, index_file_path): self.leap_mailbox = leap_mailbox + self.tag_index = TagIndex(index_file_path) + for tag in self.SPECIAL_TAGS: + if tag not in self.tag_index.values(): + self.tag_index.set(tag) @property def messages(self): @@ -47,22 +52,18 @@ class PixelatedMailbox: return PixelatedMail.from_leap_mail(message) def all_tags(self): - return Tag.from_flags(self._getFlags()) - - def _getFlags(self): - # XXX Temporary workaround while getFlags from leap is disabled - mbox = self.leap_mailbox._get_mbox_doc() - if not mbox: - return self.leap_mailbox.getFlags() - return mbox.content.get(self.leap_mailbox.FLAGS_KEY, []) - - def update_tags(self, tags): - new_flags = set(tag.to_flag() for tag in tags) - current_flags = set(self._getFlags()) - - flags = tuple(current_flags.union(new_flags)) - self.leap_mailbox.setFlags(flags) + 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) + 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'): - return PixelatedMailbox(account.getMailbox(mailbox_name)) + return PixelatedMailbox(account.getMailbox(mailbox_name), '~/.pixelated_index') diff --git a/service/pixelated/adapter/tag.py b/service/pixelated/adapter/tag.py index 386ccafc..45a972ab 100644 --- a/service/pixelated/adapter/tag.py +++ b/service/pixelated/adapter/tag.py @@ -14,41 +14,32 @@ # 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 json -class Tag: - LEAP_FLAGS_TAGS = { - '\\Deleted': 'trash', - '\\Draft': 'drafts', - '\\Recent': 'inbox' - } +class Tag: @classmethod - def from_flags(cls, flags): - return set(filter(None, (cls.from_flag(flag) for flag in flags))) + def from_dict(cls, tag_dict): + tag = Tag(tag_dict['name'], tag_dict['default']) + tag.mails = tag_dict['mails'] + return tag @classmethod - def from_flag(cls, flag): - if flag in cls.LEAP_FLAGS_TAGS.keys(): - return Tag(cls.LEAP_FLAGS_TAGS[flag]) - if flag.startswith('tag_'): - return Tag(cls._remove_prefix(flag)) - return None + def from_json_string(cls, json_string): + tag_dict = json.loads(json_string) + tag_dict['mails'] = set(tag_dict['mails']) + return Tag.from_dict(tag_dict) - def to_flag(self): - for flag, tag in self.LEAP_FLAGS_TAGS.items(): - if tag == str(self.name): - return flag - return 'tag_' + str(self.name) - - @classmethod - def _remove_prefix(cls, flag_name): - return flag_name.replace('tag_', '', 1) + @property + def total(self): + return len(self.mails) def __init__(self, name, default=False): self.name = name - self.default = default self.ident = name.__hash__() + self.default = default + self.mails = set() def __eq__(self, other): return self.name == other.name @@ -56,18 +47,28 @@ class Tag: def __hash__(self): return self.name.__hash__() + def increment(self, mail_ident): + self.mails.add(mail_ident) + + def decrement(self, mail_ident): + self.mails.discard(mail_ident) + def as_dict(self): return { 'name': self.name, 'default': self.default, 'ident': self.ident, - 'counts': { - 'total': 0, - 'read': 0, - 'starred': 0, - 'replied': 0 - } + 'counts': {'total': self.total, + 'read': 0, + 'starred': 0, + 'replied': 0}, + 'mails': self.mails } + def as_json_string(self): + tag_dict = self.as_dict() + tag_dict['mails'] = list(self.mails) + return json.dumps(tag_dict) + def __repr__(self): return self.name diff --git a/service/pixelated/adapter/tag_index.py b/service/pixelated/adapter/tag_index.py new file mode 100644 index 00000000..6cc2a68d --- /dev/null +++ b/service/pixelated/adapter/tag_index.py @@ -0,0 +1,44 @@ +# +# 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 dbm +import atexit +from pixelated.adapter.tag import Tag + + +class TagIndex: + """ + Manages an index for mail's tags using a file storage. + """ + + def __init__(self, filename): + self.db = dbm.open(filename, 'c') + atexit.register(self.close_db) + + def set(self, tag): + self.db[tag.name] = tag.as_json_string() + + def get(self, tag_name): + if tag_name in self.db: + return Tag.from_json_string(self.db.get(tag_name)) + else: + return None + + def values(self): + return set(self.get(key) for key in self.db.keys()) + + def close_db(self): + self.db.close() |