diff options
Diffstat (limited to 'alot')
-rw-r--r-- | alot/db.py | 227 | ||||
-rw-r--r-- | alot/message.py | 227 | ||||
-rw-r--r-- | alot/widgets.py | 1 |
3 files changed, 237 insertions, 218 deletions
@@ -18,18 +18,11 @@ Copyright (C) 2011 Patrick Totzke <patricktotzke@gmail.com> """ from notmuch import Database, NotmuchError from datetime import datetime -import email from collections import deque -import os -import logging -import tempfile -from settings import config -from settings import get_mime_handler -import helper -from helper import cmd_output +from message import Message -DB_ENC = 'utf8' +DB_ENC = 'utf-8' class DatabaseError(Exception): @@ -96,7 +89,8 @@ class DBManager: sync_maildir_flags=sync) msg.thaw() - def tag(self, querystring, tags, remove_rest=False, sync_maildir_flags=False): + def tag(self, querystring, tags, remove_rest=False, + sync_maildir_flags=False): """ add tags to all matching messages. Raises :exc:`DatabaseROError` if in read only mode. @@ -112,9 +106,11 @@ class DBManager: if self.ro: raise DatabaseROError() if remove_rest: - self.writequeue.append(('set', querystring, tags, sync_maildir_flags)) + self.writequeue.append(('set', querystring, tags, + sync_maildir_flags)) else: - self.writequeue.append(('tag', querystring, tags, sync_maildir_flags)) + self.writequeue.append(('tag', querystring, tags, + sync_maildir_flags)) def untag(self, querystring, tags, sync_maildir_flags=False): """ @@ -129,7 +125,8 @@ class DBManager: """ if self.ro: raise DatabaseROError() - self.writequeue.append(('untag', querystring, tags, sync_maildir_flags)) + self.writequeue.append(('untag', querystring, tags, + sync_maildir_flags)) def count_messages(self, querystring): """returns number of messages that match querystring""" @@ -181,12 +178,10 @@ class Thread: if not thread: query = self._dbman.query('thread:' + self._id) thread = query.search_threads().next() - logging.debug(thread) self._total_messages = thread.get_total_messages() self._authors = str(thread.get_authors()).decode(DB_ENC) self._subject = str(thread.get_subject()).decode(DB_ENC) ts = thread.get_oldest_date() - logging.debug(ts) self._oldest_date = datetime.fromtimestamp(ts) self._newest_date = datetime.fromtimestamp(thread.get_newest_date()) self._tags = set(thread.get_tags()) @@ -302,205 +297,3 @@ class Thread: def get_total_messages(self): """returns number of contained messages""" return self._total_messages - - -class Message: - def __init__(self, dbman, msg, thread=None): - """ - :param dbman: db manager that is used for further lookups - :type dbman: alot.db.DBManager - :param msg: the wrapped message - :type msg: notmuch.database.Message - :param thread: this messages thread - :type thread: alot.db.thread - """ - self._dbman = dbman - self._id = msg.get_message_id() - self._thread_id = msg.get_thread_id() - self._thread = thread - self._datetime = datetime.fromtimestamp(msg.get_date()) - self._filename = msg.get_filename() - # TODO: change api to return unicode - self._from = msg.get_header('From').decode(DB_ENC) - self._email = None # will be read upon first use - self._attachments = None # will be read upon first use - self._tags = set(msg.get_tags()) - - def __str__(self): - """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._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_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_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._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): - """returns tags attached to this message as list of strings""" - return list(self._tags) - - def get_thread(self): - """returns the thread this msg belongs to as alot.db.Thread object""" - if not self._thread: - self._thread = self._dbman.get_thread(self._thread_id) - return self._thread - - def get_replies(self): - """returns a list of replies to this msg""" - t = self.get_thread() - 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): - """adds tags to message - - :param tags: tags to add - :type tags: list of str - """ - self._dbman.tag('id:' + self._id, tags) - self._tags = self._tags.union(tags) - - def remove_tags(self, tags): - """remove tags from message - - :param tags: tags to remove - :type tags: list of str - """ - self._dbman.untag('id:' + self._id, tags) - self._tags = self._tags.difference(tags) - - def get_attachments(self): - if not self._attachments: - self._attachments = [] - for part in self.get_message_parts(): - if part.get_content_maintype() != 'text': - self._attachments.append(Attachment(part)) - return self._attachments - - def accumulate_body(self): - bodytxt = '' - for part in self.get_email().walk(): - ctype = part.get_content_type() - enc = part.get_content_charset() - raw_payload = part.get_payload(decode=True) - if part.get_content_maintype() == 'text': - if enc: - raw_payload = unicode(raw_payload, enc) - else: - raw_payload = unicode(raw_payload, errors='replace') - if ctype == 'text/plain': - bodytxt += raw_payload - else: - #get mime handler - handler = get_mime_handler(ctype, key='view', - interactive=False) - if handler: - #open tempfile. Not all handlers accept stuff from stdin - tmpfile = tempfile.NamedTemporaryFile(delete=False, - suffix='.html') - #write payload to tmpfile - 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) - #remove tempfile - tmpfile.close() - os.unlink(tmpfile.name) - if rendered_payload: # handler had output - bodytxt += unicode(rendered_payload.strip(), - encoding='utf8', errors='replace') - elif part.get_content_maintype() == 'text': - bodytxt += raw_payload - # else drop - return bodytxt - - -class Attachment: - """represents a single mail attachment""" - - def __init__(self, emailpart): - """ - :param emailpart: a non-multipart email that is the attachment - :type emailpart: email.message.Message - """ - self.part = emailpart - - def __str__(self): - return '%s:%s (%s)' % (self.get_content_type(), - self.get_filename(), - self.get_size()) - - def get_filename(self): - """return the filename, extracted from content-disposition header""" - return self.part.get_filename() - - def get_content_type(self): - """mime type of the attachment""" - return self.part.get_content_type() - - def get_size(self): - """returns attachments size as human-readable string""" - size_in_kbyte = len(self.part.get_payload()) / 1024 - if size_in_kbyte > 1024: - return "%.1fM" % (size_in_kbyte / 1024.0) - else: - return "%dK" % size_in_kbyte - - def save(self, path): - """save the attachment to disk. Uses self.get_filename - in case path is a directory""" - if os.path.isdir(path): - path = os.path.join(path, self.get_filename()) - FILE = open(path, "w") - FILE.write(self.part.get_payload(decode=True)) - FILE.close() diff --git a/alot/message.py b/alot/message.py new file mode 100644 index 00000000..42ba1fb7 --- /dev/null +++ b/alot/message.py @@ -0,0 +1,227 @@ +""" +This file is part of alot. + +Alot is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +Alot 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 General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with notmuch. If not, see <http://www.gnu.org/licenses/>. + +Copyright (C) 2011 Patrick Totzke <patricktotzke@gmail.com> +""" +import os +import email +import tempfile +from datetime import datetime + +import helper +from settings import get_mime_handler + + +class Message: + def __init__(self, dbman, msg, thread=None): + """ + :param dbman: db manager that is used for further lookups + :type dbman: alot.db.DBManager + :param msg: the wrapped message + :type msg: notmuch.database.Message + :param thread: this messages thread + :type thread: alot.db.thread + """ + self._dbman = dbman + self._id = msg.get_message_id() + self._thread_id = msg.get_thread_id() + self._thread = thread + self._datetime = datetime.fromtimestamp(msg.get_date()) + self._filename = msg.get_filename() + # TODO: change api to return unicode + self._from = msg.get_header('From').decode('utf-8') + self._email = None # will be read upon first use + self._attachments = None # will be read upon first use + self._tags = set(msg.get_tags()) + + def __str__(self): + """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._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_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_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._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): + """returns tags attached to this message as list of strings""" + return list(self._tags) + + def get_thread(self): + """returns the thread this msg belongs to as alot.db.Thread object""" + if not self._thread: + self._thread = self._dbman.get_thread(self._thread_id) + return self._thread + + def get_replies(self): + """returns a list of replies to this msg""" + t = self.get_thread() + 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): + """adds tags to message + + :param tags: tags to add + :type tags: list of str + """ + self._dbman.tag('id:' + self._id, tags) + self._tags = self._tags.union(tags) + + def remove_tags(self, tags): + """remove tags from message + + :param tags: tags to remove + :type tags: list of str + """ + self._dbman.untag('id:' + self._id, tags) + self._tags = self._tags.difference(tags) + + def get_attachments(self): + if not self._attachments: + self._attachments = [] + for part in self.get_message_parts(): + if part.get_content_maintype() != 'text': + self._attachments.append(Attachment(part)) + return self._attachments + + def accumulate_body(self): + bodytxt = '' + for part in self.get_email().walk(): + ctype = part.get_content_type() + enc = part.get_content_charset() + raw_payload = part.get_payload(decode=True) + if part.get_content_maintype() == 'text': + if enc: + raw_payload = unicode(raw_payload, enc) + else: + raw_payload = unicode(raw_payload, errors='replace') + if ctype == 'text/plain': + bodytxt += raw_payload + else: + #get mime handler + handler = get_mime_handler(ctype, key='view', + interactive=False) + if handler: + #open tempfile. Not all handlers accept stuff from stdin + tmpfile = tempfile.NamedTemporaryFile(delete=False, + suffix='.html') + #write payload to tmpfile + 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 = helper.cmd_output(cmd) + #remove tempfile + tmpfile.close() + os.unlink(tmpfile.name) + if rendered_payload: # handler had output + bodytxt += unicode(rendered_payload.strip(), + encoding='utf8', errors='replace') + elif part.get_content_maintype() == 'text': + bodytxt += raw_payload + # else drop + return bodytxt + + +class Attachment: + """represents a single mail attachment""" + + def __init__(self, emailpart): + """ + :param emailpart: a non-multipart email that is the attachment + :type emailpart: email.message.Message + """ + self.part = emailpart + + def __str__(self): + return '%s:%s (%s)' % (self.get_content_type(), + self.get_filename(), + self.get_size()) + + def get_filename(self): + """return the filename, extracted from content-disposition header""" + return self.part.get_filename() + + def get_content_type(self): + """mime type of the attachment""" + return self.part.get_content_type() + + def get_size(self): + """returns attachments size as human-readable string""" + size_in_kbyte = len(self.part.get_payload()) / 1024 + if size_in_kbyte > 1024: + return "%.1fM" % (size_in_kbyte / 1024.0) + else: + return "%dK" % size_in_kbyte + + def save(self, path): + """save the attachment to disk. Uses self.get_filename + in case path is a directory""" + if os.path.isdir(path): + path = os.path.join(path, self.get_filename()) + FILE = open(path, "w") + FILE.write(self.part.get_payload(decode=True)) + FILE.close() diff --git a/alot/widgets.py b/alot/widgets.py index da35d6f3..e82585d8 100644 --- a/alot/widgets.py +++ b/alot/widgets.py @@ -23,7 +23,6 @@ from urwid.command_map import command_map from settings import config from helper import shorten from helper import pretty_datetime -from helper import cmd_output class ThreadlineWidget(urwid.AttrMap): |