diff options
Diffstat (limited to 'alot/message.py')
-rw-r--r-- | alot/message.py | 227 |
1 files changed, 227 insertions, 0 deletions
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() |