diff options
author | Patrick Totzke <patricktotzke@gmail.com> | 2012-03-11 16:06:20 +0000 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2012-03-11 16:08:08 +0000 |
commit | 657b7525f297c885877281861dba90da5301db25 (patch) | |
tree | a0e1f70ab1ad2361adb287d299a238114dacb1a3 | |
parent | 07e9fec4272f2bffabee541f161f88e7fcd082cc (diff) |
refactor db: move into submodule
this moves messages into the new submodule alot.db
which from now on also contains Threads in a separate file
-rw-r--r-- | alot/commands/globals.py | 2 | ||||
-rw-r--r-- | alot/commands/thread.py | 12 | ||||
-rw-r--r-- | alot/db/__init__.py (renamed from alot/db.py) | 263 | ||||
-rw-r--r-- | alot/db/message.py (renamed from alot/message.py) | 8 | ||||
-rw-r--r-- | alot/db/thread.py | 265 | ||||
-rw-r--r-- | alot/widgets.py | 10 | ||||
-rwxr-xr-x | setup.py | 2 |
7 files changed, 284 insertions, 278 deletions
diff --git a/alot/commands/globals.py b/alot/commands/globals.py index 05809c83..6fb1ba49 100644 --- a/alot/commands/globals.py +++ b/alot/commands/globals.py @@ -20,7 +20,7 @@ from alot import helper from alot.db import DatabaseLockedError from alot.completion import ContactsCompleter from alot.completion import AccountCompleter -from alot.message import Envelope +from alot.db.message import Envelope from alot import commands from alot.settings import settings diff --git a/alot/commands/thread.py b/alot/commands/thread.py index c9388f7b..2247151f 100644 --- a/alot/commands/thread.py +++ b/alot/commands/thread.py @@ -14,12 +14,12 @@ from alot.commands.globals import ComposeCommand from alot.commands.globals import RefreshCommand from alot import widgets from alot import completion -from alot.message import decode_header -from alot.message import encode_header -from alot.message import extract_headers -from alot.message import extract_body -from alot.message import Envelope -from alot.message import Attachment +from alot.db.message import decode_header +from alot.db.message import encode_header +from alot.db.message import extract_headers +from alot.db.message import extract_body +from alot.db.message import Envelope +from alot.db.message import Attachment from alot.db import DatabaseROError from alot.settings import settings diff --git a/alot/db.py b/alot/db/__init__.py index c0d7b682..b2796324 100644 --- a/alot/db.py +++ b/alot/db/__init__.py @@ -3,11 +3,11 @@ import notmuch import multiprocessing import logging -from datetime import datetime from collections import deque from message import Message -from settings import settings +from alot.settings import settings +from thread import Thread DB_ENC = 'utf-8' @@ -346,262 +346,3 @@ class DBManager(object): raise DatabaseROError() path = message.get_filename() self.writequeue.append(('remove', afterwards, path)) - - -class Thread(object): - """ - A wrapper around a notmuch mailthread (:class:`notmuch.database.Thread`) - that ensures persistence of the thread: It can be safely read multiple - times, its manipulation is done via a :class:`DBManager` and it - can directly provide contained messages as :class:`~alot.message.Message`. - """ - - def __init__(self, dbman, thread): - """ - :param dbman: db manager that is used for further lookups - :type dbman: :class:`DBManager` - :param thread: the wrapped thread - :type thread: :class:`notmuch.database.Thread` - """ - self._dbman = dbman - self._id = thread.get_thread_id() - self.refresh(thread) - - def refresh(self, thread=None): - """refresh thread metadata from the index""" - if not thread: - thread = self._dbman._get_notmuch_thread(self._id) - - self._total_messages = thread.get_total_messages() - self._notmuch_authors_string = thread.get_authors() - self._subject = thread.get_subject() - self._authors = None - ts = thread.get_oldest_date() - - try: - self._oldest_date = datetime.fromtimestamp(ts) - except ValueError: # year is out of range - self._oldest_date = None - try: - timestamp = thread.get_newest_date() - self._newest_date = datetime.fromtimestamp(timestamp) - except ValueError: # year is out of range - self._newest_date = None - - self._tags = set([t for t in thread.get_tags()]) - self._messages = {} # this maps messages to its children - self._toplevel_messages = [] - - def __str__(self): - return "thread:%s: %s" % (self._id, self.get_subject()) - - def get_thread_id(self): - """returns id of this thread""" - return self._id - - def get_tags(self, intersection=False): - """ - returns tagsstrings attached to this thread - - :param intersection: return tags present in all contained messages - instead of in at least one (union) - :type intersection: bool - :rtype: set of str - """ - tags = set(list(self._tags)) - if intersection: - for m in self.get_messages().keys(): - tags = tags.intersection(set(m.get_tags())) - return tags - - def add_tags(self, tags, afterwards=None, remove_rest=False): - """ - add `tags` to all messages in this thread - - .. note:: - - This only adds the requested operation to this objects - :class:`DBManager's <DBManager>` write queue. - You need to call :meth:`DBManager.flush` to actually write out. - - :param tags: a list of tags to be added - :type tags: list of str - :param afterwards: callback that gets called after successful - application of this tagging operation - :type afterwards: callable - :param remove_rest: remove all other tags - :type remove_rest: bool - """ - def myafterwards(): - if remove_rest: - self._tags = set(tags) - else: - self._tags = self._tags.union(tags) - if callable(afterwards): - afterwards() - - self._dbman.tag('thread:' + self._id, tags, afterwards=myafterwards, - remove_rest=remove_rest) - - def remove_tags(self, tags, afterwards=None): - """ - remove `tags` (list of str) from all messages in this thread - - .. note:: - - This only adds the requested operation to this objects - :class:`DBManager's <DBManager>` write queue. - You need to call :meth:`DBManager.flush` to actually write out. - - :param tags: a list of tags to be added - :type tags: list of str - :param afterwards: callback that gets called after successful - application of this tagging operation - :type afterwards: callable - """ - rmtags = set(tags).intersection(self._tags) - if rmtags: - - def myafterwards(): - self._tags = self._tags.difference(tags) - if callable(afterwards): - afterwards() - self._dbman.untag('thread:' + self._id, tags, myafterwards) - self._tags = self._tags.difference(rmtags) - - def get_authors(self): - """ - returns a list of authors (name, addr) of the messages. - The authors are ordered by msg date and unique (by addr). - - :rtype: list of (str, str) - """ - if self._authors == None: - self._authors = [] - seen = {} - msgs = self.get_messages().keys() - msgs.sort(lambda a, b: cmp(a, b), lambda m: m.get_date()) - for m in msgs: - pair = m.get_author() - if not pair[1] in seen: - seen[pair[1]] = True - self._authors.append(pair) - return self._authors - - def get_authors_string(self, own_addrs=None, replace_own=None): - """ - returns a string of comma-separated authors - Depending on settings, it will substitute "me" for author name if - address is user's own. - - :param own_addrs: list of own email addresses to replace - :type own_addrs: list of str - :param replace_own: whether or not to actually do replacement - :type replace_own: bool - :rtype: str - """ - if replace_own == None: - replace_own = settings.get('thread_authors_replace_me') - if replace_own: - if own_addrs == None: - own_addrs = settings.get_addresses() - authorslist = [] - for aname, aaddress in self.get_authors(): - if aaddress in own_addrs: - aname = settings.get('thread_authors_me') - if not aname: - aname = aaddress - if not aname in authorslist: - authorslist.append(aname) - return ', '.join(authorslist) - else: - return self._notmuch_authors_string - - def get_subject(self): - """returns subject string""" - return self._subject - - def get_toplevel_messages(self): - """ - returns all toplevel messages contained in this thread. - This are all the messages without a parent message - (identified by 'in-reply-to' or 'references' header. - - :rtype: list of :class:`~alot.message.Message` - """ - if not self._messages: - self.get_messages() - return self._toplevel_messages - - def get_messages(self): - """ - returns all messages in this thread as dict mapping all contained - messages to their direct responses. - - :rtype: dict mapping :class:`~alot.message.Message` to a list of - :class:`~alot.message.Message`. - """ - if not self._messages: # if not already cached - query = self._dbman.query('thread:' + self._id) - thread = query.search_threads().next() - - def accumulate(acc, msg): - M = Message(self._dbman, msg, thread=self) - acc[M] = [] - r = msg.get_replies() - if r is not None: - for m in r: - acc[M].append(accumulate(acc, m)) - return M - - self._messages = {} - for m in thread.get_toplevel_messages(): - self._toplevel_messages.append(accumulate(self._messages, m)) - return self._messages - - def get_replies_to(self, msg): - """ - returns all replies to the given message contained in this thread. - - :param msg: parent message to look up - :type msg: :class:`~alot.message.Message` - :returns: list of :class:`~alot.message.Message` or `None` - """ - mid = msg.get_message_id() - msg_hash = self.get_messages() - for m in msg_hash.keys(): - if m.get_message_id() == mid: - return msg_hash[m] - return None - - def get_newest_date(self): - """ - returns date header of newest message in this thread as - :class:`~datetime.datetime` - """ - return self._newest_date - - def get_oldest_date(self): - """ - returns date header of oldest message in this thread as - :class:`~datetime.datetime` - """ - return self._oldest_date - - def get_total_messages(self): - """returns number of contained messages""" - return self._total_messages - - def matches(self, query): - """ - Check if this thread matches the given notmuch query. - - :param query: The query to check against - :type query: string - :returns: True if this thread matches the given query, False otherwise - :rtype: bool - """ - thread_query = 'thread:{tid} AND {subquery}'.format(tid=self._id, - subquery=query) - num_matches = self._dbman.count_messages(thread_query) - return num_matches > 0 diff --git a/alot/message.py b/alot/db/message.py index c91209f7..aa9b8a1e 100644 --- a/alot/message.py +++ b/alot/db/message.py @@ -14,10 +14,10 @@ from notmuch import NullPointerError from alot import __version__ import logging -import helper -from settings import settings -from helper import string_sanitize -from helper import string_decode +import alot.helper as helper +from alot.settings import settings +from alot.helper import string_sanitize +from alot.helper import string_decode class Message(object): diff --git a/alot/db/thread.py b/alot/db/thread.py new file mode 100644 index 00000000..a13eac0e --- /dev/null +++ b/alot/db/thread.py @@ -0,0 +1,265 @@ +from datetime import datetime + +from message import Message +from alot.settings import settings + + +class Thread(object): + """ + A wrapper around a notmuch mailthread (:class:`notmuch.database.Thread`) + that ensures persistence of the thread: It can be safely read multiple + times, its manipulation is done via a :class:`alot.db.DBManager` and it + can directly provide contained messages as :class:`~alot.message.Message`. + """ + + def __init__(self, dbman, thread): + """ + :param dbman: db manager that is used for further lookups + :type dbman: :class:`~alot.db.DBManager` + :param thread: the wrapped thread + :type thread: :class:`notmuch.database.Thread` + """ + self._dbman = dbman + self._id = thread.get_thread_id() + self.refresh(thread) + + def refresh(self, thread=None): + """refresh thread metadata from the index""" + if not thread: + thread = self._dbman._get_notmuch_thread(self._id) + + self._total_messages = thread.get_total_messages() + self._notmuch_authors_string = thread.get_authors() + self._subject = thread.get_subject() + self._authors = None + ts = thread.get_oldest_date() + + try: + self._oldest_date = datetime.fromtimestamp(ts) + except ValueError: # year is out of range + self._oldest_date = None + try: + timestamp = thread.get_newest_date() + self._newest_date = datetime.fromtimestamp(timestamp) + except ValueError: # year is out of range + self._newest_date = None + + self._tags = set([t for t in thread.get_tags()]) + self._messages = {} # this maps messages to its children + self._toplevel_messages = [] + + def __str__(self): + return "thread:%s: %s" % (self._id, self.get_subject()) + + def get_thread_id(self): + """returns id of this thread""" + return self._id + + def get_tags(self, intersection=False): + """ + returns tagsstrings attached to this thread + + :param intersection: return tags present in all contained messages + instead of in at least one (union) + :type intersection: bool + :rtype: set of str + """ + tags = set(list(self._tags)) + if intersection: + for m in self.get_messages().keys(): + tags = tags.intersection(set(m.get_tags())) + return tags + + def add_tags(self, tags, afterwards=None, remove_rest=False): + """ + add `tags` to all messages in this thread + + .. note:: + + This only adds the requested operation to this objects + :class:`DBManager's <~alot.db.DBManager>` write queue. + You need to call :meth:`DBManager.flush <~alot.db.DBManager.flush>` + to actually write out. + + :param tags: a list of tags to be added + :type tags: list of str + :param afterwards: callback that gets called after successful + application of this tagging operation + :type afterwards: callable + :param remove_rest: remove all other tags + :type remove_rest: bool + """ + def myafterwards(): + if remove_rest: + self._tags = set(tags) + else: + self._tags = self._tags.union(tags) + if callable(afterwards): + afterwards() + + self._dbman.tag('thread:' + self._id, tags, afterwards=myafterwards, + remove_rest=remove_rest) + + def remove_tags(self, tags, afterwards=None): + """ + remove `tags` (list of str) from all messages in this thread + + .. note:: + + This only adds the requested operation to this objects + :class:`DBManager's <alot.db.DBManager>` write queue. + You need to call :meth:`DBManager.flush <alot.db.DBManager.flush>` + to actually write out. + + :param tags: a list of tags to be added + :type tags: list of str + :param afterwards: callback that gets called after successful + application of this tagging operation + :type afterwards: callable + """ + rmtags = set(tags).intersection(self._tags) + if rmtags: + + def myafterwards(): + self._tags = self._tags.difference(tags) + if callable(afterwards): + afterwards() + self._dbman.untag('thread:' + self._id, tags, myafterwards) + self._tags = self._tags.difference(rmtags) + + def get_authors(self): + """ + returns a list of authors (name, addr) of the messages. + The authors are ordered by msg date and unique (by addr). + + :rtype: list of (str, str) + """ + if self._authors == None: + self._authors = [] + seen = {} + msgs = self.get_messages().keys() + msgs.sort(lambda a, b: cmp(a, b), lambda m: m.get_date()) + for m in msgs: + pair = m.get_author() + if not pair[1] in seen: + seen[pair[1]] = True + self._authors.append(pair) + return self._authors + + def get_authors_string(self, own_addrs=None, replace_own=None): + """ + returns a string of comma-separated authors + Depending on settings, it will substitute "me" for author name if + address is user's own. + + :param own_addrs: list of own email addresses to replace + :type own_addrs: list of str + :param replace_own: whether or not to actually do replacement + :type replace_own: bool + :rtype: str + """ + if replace_own == None: + replace_own = settings.get('thread_authors_replace_me') + if replace_own: + if own_addrs == None: + own_addrs = settings.get_addresses() + authorslist = [] + for aname, aaddress in self.get_authors(): + if aaddress in own_addrs: + aname = settings.get('thread_authors_me') + if not aname: + aname = aaddress + if not aname in authorslist: + authorslist.append(aname) + return ', '.join(authorslist) + else: + return self._notmuch_authors_string + + def get_subject(self): + """returns subject string""" + return self._subject + + def get_toplevel_messages(self): + """ + returns all toplevel messages contained in this thread. + This are all the messages without a parent message + (identified by 'in-reply-to' or 'references' header. + + :rtype: list of :class:`~alot.message.Message` + """ + if not self._messages: + self.get_messages() + return self._toplevel_messages + + def get_messages(self): + """ + returns all messages in this thread as dict mapping all contained + messages to their direct responses. + + :rtype: dict mapping :class:`~alot.message.Message` to a list of + :class:`~alot.message.Message`. + """ + if not self._messages: # if not already cached + query = self._dbman.query('thread:' + self._id) + thread = query.search_threads().next() + + def accumulate(acc, msg): + M = Message(self._dbman, msg, thread=self) + acc[M] = [] + r = msg.get_replies() + if r is not None: + for m in r: + acc[M].append(accumulate(acc, m)) + return M + + self._messages = {} + for m in thread.get_toplevel_messages(): + self._toplevel_messages.append(accumulate(self._messages, m)) + return self._messages + + def get_replies_to(self, msg): + """ + returns all replies to the given message contained in this thread. + + :param msg: parent message to look up + :type msg: :class:`~alot.message.Message` + :returns: list of :class:`~alot.message.Message` or `None` + """ + mid = msg.get_message_id() + msg_hash = self.get_messages() + for m in msg_hash.keys(): + if m.get_message_id() == mid: + return msg_hash[m] + return None + + def get_newest_date(self): + """ + returns date header of newest message in this thread as + :class:`~datetime.datetime` + """ + return self._newest_date + + def get_oldest_date(self): + """ + returns date header of oldest message in this thread as + :class:`~datetime.datetime` + """ + return self._oldest_date + + def get_total_messages(self): + """returns number of contained messages""" + return self._total_messages + + def matches(self, query): + """ + Check if this thread matches the given notmuch query. + + :param query: The query to check against + :type query: string + :returns: True if this thread matches the given query, False otherwise + :rtype: bool + """ + thread_query = 'thread:{tid} AND {subquery}'.format(tid=self._id, + subquery=query) + num_matches = self._dbman.count_messages(thread_query) + return num_matches > 0 diff --git a/alot/widgets.py b/alot/widgets.py index 817a89cd..eaf4c8e1 100644 --- a/alot/widgets.py +++ b/alot/widgets.py @@ -2,11 +2,11 @@ import urwid import logging from settings import settings -from helper import shorten_author_string -from helper import pretty_datetime -from helper import tag_cmp -from helper import string_decode -import message +from alot.helper import shorten_author_string +from alot.helper import pretty_datetime +from alot.helper import tag_cmp +from alot.helper import string_decode +import alot.db.message as message import time @@ -11,7 +11,7 @@ setup(name='alot', author_email=alot.__author_email__, url=alot.__url__, license=alot.__copyright__, - packages=['alot', 'alot.commands', 'alot.settings'], + packages=['alot', 'alot.commands', 'alot.settings', 'alot.db'], package_data={'alot': [ 'defaults/alot.rc.spec', 'defaults/notmuch.rc.spec', |