summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Totzke <patricktotzke@gmail.com>2012-03-11 16:06:20 +0000
committerPatrick Totzke <patricktotzke@gmail.com>2012-03-11 16:08:08 +0000
commit657b7525f297c885877281861dba90da5301db25 (patch)
treea0e1f70ab1ad2361adb287d299a238114dacb1a3
parent07e9fec4272f2bffabee541f161f88e7fcd082cc (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.py2
-rw-r--r--alot/commands/thread.py12
-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.py265
-rw-r--r--alot/widgets.py10
-rwxr-xr-xsetup.py2
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
diff --git a/setup.py b/setup.py
index ec499efe..b4163a49 100755
--- a/setup.py
+++ b/setup.py
@@ -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',