summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpazz <patricktotzke@gmail.com>2011-07-09 11:52:41 +0100
committerpazz <patricktotzke@gmail.com>2011-07-09 11:52:41 +0100
commit3c51d28567ce43e98bc74e13d21b067c5c21fbca (patch)
tree1900be4da614f3aa03b025f954e14740662a53be
parenta32bae95a49ccddb72e7ceeec0b2880aad83ef31 (diff)
cleanup of db.Message
-rw-r--r--TODO13
-rw-r--r--alot/db.py196
-rw-r--r--alot/widgets.py54
3 files changed, 184 insertions, 79 deletions
diff --git a/TODO b/TODO
index d5886162..acbbc790 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,15 @@
+rewrite widgets:
+ * write example howto use treewidgets
+ * attachments as TW
+ * rewrite messagewidget
+
+remove cmdline interface in favour of simple commands -> interpret args -> instanciate alot.command.Command
+
Commands
* how should we do mappings? via settings, 'set' in the commandline interpreter or both?
+ use global commandmap to map urwid actions: 'activate/cursor down etc'. only those are interpreted by widgets!
+ all other will be read from buffer.bindings: in b.keypress, look for local, then global mappings, then return
+ widget.keypress (interprets urwid actions)
* help strings in commands and help buffer
* undo for commands
@@ -34,6 +44,7 @@ db:
* db.Message.get_replies() needs fixing. see
http://notmuch.198994.n3.nabble.com/python-get-all-messages-of-a-thread-tt2996080.html
* relay matched-flag to msg objects (to open only matches messages in thread buffer)
+ * all interpretation of the mail shoud happen in db.Message
ui.commandline:
possible cmds:
@@ -47,7 +58,7 @@ ui:
the benefit is that switching to notmuch's mimepart api later will be painless
general
- * gg, g5j.. combos like in vim
+ * gg, g5j.. combos like in vim: input->stack; rm timeoutalarm; try to interpret stack; if this fails, wait for more; on alarm remove stack
* pydoc/sphinx
* update dependencies in setup.py. Do we have to include all these?
cmd
diff --git a/alot/db.py b/alot/db.py
index bee7aee0..2c92d960 100644
--- a/alot/db.py
+++ b/alot/db.py
@@ -110,25 +110,36 @@ class DBManager:
threads = self.query(querystring).search_threads()
return [thread.get_thread_id() for thread in threads]
- def find_message(self, mid, writeable=False):
- db = Database(path=self.path)
- if writeable:
- query = self.query('id:' + mid, writeable=writeable)
- #TODO raise exceptions here in 0<case msgcount>1
- msg = query.search_messages().next()
- else:
- msg = db.find_message(mid)
- return msg
-
- def get_message(self, mid):
- """returns the message with the given id and wrapps it in a Message
-
- :param mid: the message id of the message to look up
- :type mid: str.
- :returns: Message -- the message.
-
- """
- return Message(self, self.find_message(mid))
+ #def find_message(self, mid):
+ # db = Database(path=self.path)
+ # query = self.query('id:' + mid)
+ # try:
+ # thread = query.search_threads().next()
+ # def search_msg_in_replies(mid, msg):
+ # if msg.get_message_id() == mid:
+ # return msg
+ # else:
+ # replies = msg.get_replies()
+ # if replies is not None:
+ # for r in replies:
+ # return search_msg_in_replies(mid, r)
+
+ # for m in thread.get_toplevel_messages():
+ # return searchformsg(mid, msg)
+ # except:
+ # return None
+ # #TODO raise exceptions here in 0<case msgcount>1
+ # msg = query.search_messages().next()
+
+ #def get_message(self, mid):
+ # """returns the message with the given id and wrapps it in a Message
+
+ # :param mid: the message id of the message to look up
+ # :type mid: str.
+ # :returns: Message -- the message.
+
+ # """
+ # return Message(self, self.find_message(mid))
def get_thread(self, tid):
query = self.query('thread:' + tid)
@@ -165,6 +176,7 @@ class Thread:
self.oldest = datetime.fromtimestamp(thread.get_oldest_date())
self.newest = datetime.fromtimestamp(thread.get_newest_date())
self.tags = set([str(tag).decode(DB_ENC) for tag in thread.get_tags()])
+ self._messages = None # will be read on demand
def get_thread_id(self):
return self.tid
@@ -195,7 +207,7 @@ class Thread:
return self.subject
def _build_messages(self, acc, msg):
- M = Message(self.dbman, msg)
+ M = Message(self.dbman, msg, thread=self)
acc[M] = {}
r = msg.get_replies()
if r is not None:
@@ -203,13 +215,14 @@ class Thread:
self._build_messages(acc[M], m)
def get_messages(self):
- query = self.dbman.query('thread:' + self.tid)
- thread = query.search_threads().next()
+ if not self._messages:
+ query = self.dbman.query('thread:' + self.tid)
+ thread = query.search_threads().next()
- messages = {}
- for m in thread.get_toplevel_messages():
- self._build_messages(messages, m)
- return messages
+ self._messages = {}
+ for m in thread.get_toplevel_messages():
+ self._build_messages(self._messages, m)
+ return self._messages
def get_newest_date(self):
return self.newest
@@ -220,53 +233,112 @@ class Thread:
def get_total_messages(self):
return self.total_messages
+ def get_replies_to(self, msg):
+ msgs = self.get_messages()
+ if msg in msgs:
+ return msgs[msg]
+ else:
+ return None
+
class Message:
- def __init__(self, dbman, msg):
- self.dbman = dbman
- self.mid = msg.get_message_id()
- self.datetime = datetime.fromtimestamp(msg.get_date())
- self.sender = str(msg.get_header('From')).decode(DB_ENC)
- self.strrep = str(msg).decode(DB_ENC)
- self.email = None # will be read upon first use
- self.tags = set([str(tag).decode(DB_ENC) for tag in msg.get_tags()])
+ def __init__(self, dbman, msg, thread=None):
+ self._dbman = dbman
+ self._message_id = msg.get_message_id()
+ self._thread_id = msg.get_thread_id()
+ self._thread = thread
+ self._datetime = datetime.fromtimestamp(msg.get_date())
+ self._filename = str(msg.get_filename()).decode(DB_ENC)
+ self._from = str(msg.get_header('From')).decode(DB_ENC)
+ self._email = None # will be read upon first use
+ self._tags = set([str(tag).decode(DB_ENC) for tag in msg.get_tags()])
def __str__(self):
- return self.strrep
+ """prettyprint the message"""
+ aname, aaddress = self.get_author()
+ if not aname:
+ aname = aaddress
+ #tags = ','.join(self.get_tags())
+ return "%s (%s)" % (aname, self.get_datestring())
+
+ def __hash__(self):
+ """Implement hash(), so we can use Message() sets"""
+ return hash(self._message_id)
+
+ def __cmp__(self, other):
+ """Implement cmp(), so we can compare Message()s"""
+ res = cmp(self.get_message_id(), other.get_message_id())
+ return res
- def get_replies(self):
- #this doesn't work. see Note in doc -> more work here.
- return [self.dbman.find_message(mid) for mid in self.replies]
+ def get_email(self):
+ """returns email.Message representing this message"""
+ if not self._email:
+ f_mail = open(self.get_filename())
+ self._email = email.message_from_file(f_mail)
+ f_mail.close()
+ return self._email
- def get_author(self):
- return helper.parse_addr(self.sender)
+ def get_date(self):
+ """returns date as datetime obj"""
+ return self._datetime
+
+ def get_filename(self):
+ """returns absolute path of messages location"""
+ return self._filename
+
+ def get_message_id(self):
+ """returns messages id (a string)"""
+ return self._message_id
+
+ def get_thread_id(self):
+ """returns id of messages thread (a string)"""
+ return self._thread_id
+
+ def get_message_parts(self):
+ """returns a list of all body parts of this message"""
+ out = []
+ for msg in self._get_email().walk():
+ if not msg.is_multipart():
+ out.append(msg)
+ return out
def get_tags(self):
- return list(self.tags)
+ """returns tags attached to this message as list of stings"""
+ return list(self._tags)
- def get_email(self):
- if not self.email:
- self.email = self.read_mail(self.get_filename())
- return self.email
-
- def read_mail(self, filename):
- try:
- f_mail = open(filename)
- except EnvironmentError:
- eml = email.message_from_string('Unable to open the file')
- else:
- eml = email.message_from_file(f_mail)
- f_mail.close()
- return eml
+ def get_thread(self):
+ """returns the thread this msg belongs to as alot.db.Thread object"""
+ if not self._thread:
+ self._thread = seld._dbman.get_thread(self._thread_id)
+ return self._thread
+
+ def get_replies(self):
+ """returns a list of replies to this msg"""
+ t = self.get_tread()
+ return t.get_replies_to(self)
+
+ def get_datestring(self, pretty=True):
+ """returns formated datestring in sup-style, eg: 'Yest.3pm'"""
+ return helper.pretty_datetime(self._datetime)
+
+ def get_author(self):
+ """returns realname and address pair of this messages author"""
+ return email.Utils.parseaddr(self._from)
def add_tags(self, tags):
- self.dbman.tag('id:' + self.mid, tags)
- self.tags = self.tags.union(tags)
+ """adds tags from message
+
+ :param tags: tags to add
+ :type tags: list of str
+ """
+ self._dbman.tag('id:' + self._message_id, tags)
+ self._tags = self._tags.union(tags)
def remove_tags(self, tags):
- self.dbman.untag('id:' + self.mid, tags)
- self.tags = self.tags.difference(tags)
+ """remove tags from message
- def get_filename(self):
- m = self.dbman.find_message(self.mid)
- return m.get_filename()
+ :param tags: tags to remove
+ :type tags: list of str
+ """
+ self._dbman.untag('id:' + self._message_id, tags)
+ self._tags = self._tags.difference(tags)
diff --git a/alot/widgets.py b/alot/widgets.py
index 059eda30..9915b93f 100644
--- a/alot/widgets.py
+++ b/alot/widgets.py
@@ -293,6 +293,11 @@ class MessageWidget(urwid.WidgetWrap):
self.displayed_list.insert(1, hw)
self.rebuild()
+ def toggle_full_header(self):
+ """toggles if message headers are shown"""
+ hw = self._get_header_widget().widget_list[-1]
+ hw.toggle_all()
+
def toggle_body(self):
"""toggles if message body is shown"""
bw = self._get_body_widget()
@@ -306,12 +311,15 @@ class MessageWidget(urwid.WidgetWrap):
def selectable(self):
return True
+ #TODO: this needs to go in favour of a binding in the buffer!
def keypress(self, size, key):
if key == 'h':
self.toggle_header()
elif key == 'enter':
self.toggle_header()
self.toggle_body()
+ elif key == 'H':
+ self.toggle_full_header()
else:
return self.pile.keypress(size, key)
@@ -343,11 +351,7 @@ class MessageSummaryWidget(urwid.WidgetWrap):
prefix = "- "
if self.folded:
prefix = '+ '
- aname, aaddress = self.message.get_author()
- if not aname:
- aname = aaddress
- return "%s%s (%s)" % (prefix, aname,
- pretty_datetime(self.message.datetime))
+ return "%s%s" % (prefix, str(self.message))
def toggle_folded(self):
self.folded = not self.folded
@@ -374,18 +378,33 @@ class MessageHeaderWidget(urwid.AttrMap):
:type state: list(str)
"""
self.eml = eml
- headerlines = []
+ self.display_all = False
+ self.displayed_headers = displayed_headers
+ headerlines = self._build_lines(displayed_headers)
+ urwid.AttrMap.__init__(self, urwid.Pile(headerlines), 'message_header')
+
+ def toggle_all(self):
+ if self.display_all:
+ self.display_all = False
+ headerlines = self._build_lines(self.displayed_headers)
+ else:
+ self.display_all = True
+ headerlines = self._build_lines(None)
+ self._w = urwid.Pile(headerlines)
+
+ def _build_lines(self, displayed):
max_key_len = 1
- if not displayed_headers:
- displayed_headers = eml.keys()
- for key in displayed_headers:
- if key in eml:
+ headerlines = []
+ if not displayed:
+ displayed = self.eml.keys()
+ for key in displayed:
+ if key in self.eml:
if len(key) > max_key_len:
max_key_len = len(key)
- for key in displayed_headers:
+ for key in displayed:
#todo: parse from,cc,bcc seperately into name-addr-widgets
- if key in eml:
- valuelist = email.header.decode_header(eml[key])
+ if key in self.eml:
+ valuelist = email.header.decode_header(self.eml[key])
value = ''
for v, enc in valuelist:
if enc:
@@ -400,7 +419,7 @@ class MessageHeaderWidget(urwid.AttrMap):
valuew = urwid.Text(('message_header_value', value))
line = urwid.Columns([keyw, valuew])
headerlines.append(line)
- urwid.AttrMap.__init__(self, urwid.Pile(headerlines), 'message_header')
+ return headerlines
def selectable(self):
return True
@@ -439,7 +458,10 @@ class MessageBodyWidget(urwid.AttrMap):
tmpfile = tempfile.NamedTemporaryFile(delete=False,
suffix='.html')
#write payload to tmpfile
- tmpfile.write(raw_payload.encode('utf8'))
+ if part.get_content_maintype() == 'text':
+ tmpfile.write(raw_payload.encode('utf8'))
+ else:
+ tmpfile.write(raw_payload)
#create and call external command
cmd = handler % tmpfile.name
rendered_payload = cmd_output(cmd)
@@ -448,7 +470,7 @@ class MessageBodyWidget(urwid.AttrMap):
os.unlink(tmpfile.name)
if rendered_payload: # handler had output
bodytxt += rendered_payload.decode('utf8').strip()
- elif part.get_content_maintype() == 'text': # revert to plaintext
+ elif part.get_content_maintype() == 'text':
bodytxt += raw_payload
# else drop
urwid.AttrMap.__init__(self, urwid.Text(bodytxt), 'message_body')