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/>.
+along with notmuch. If not, see <https://www.gnu.org/licenses/>.
Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
Jesse Rosenthal <jrosenthal@jhu.edu>
"""
-from ctypes import c_char_p, c_long, c_uint, c_int
+from ctypes import c_char_p, c_long, c_uint, c_int, POINTER, byref
from datetime import date
from .globals import (
nmlib,
NotmuchTagsP,
NotmuchMessageP,
NotmuchMessagesP,
+ NotmuchMessagePropertiesP,
NotmuchFilenamesP,
)
from .errors import (
from .filenames import Filenames
import email
-try:
- import simplejson as json
-except ImportError:
- import json
+import sys
class Message(Python3StringMixIn):
_maildir_flags_to_tags.argtypes = [NotmuchMessageP]
_maildir_flags_to_tags.restype = c_int
+ """notmuch_message_get_property"""
+ _get_property = nmlib.notmuch_message_get_property
+ _get_property.argtypes = [NotmuchMessageP, c_char_p, POINTER(c_char_p)]
+ _get_property.restype = c_int
+
+ """notmuch_message_get_properties"""
+ _get_properties = nmlib.notmuch_message_get_properties
+ _get_properties.argtypes = [NotmuchMessageP, c_char_p, c_int]
+ _get_properties.restype = NotmuchMessagePropertiesP
+
+ """notmuch_message_properties_valid"""
+ _properties_valid = nmlib.notmuch_message_properties_valid
+ _properties_valid.argtypes = [NotmuchMessagePropertiesP]
+ _properties_valid.restype = bool
+
+ """notmuch_message_properties_value"""
+ _properties_value = nmlib.notmuch_message_properties_value
+ _properties_value.argtypes = [NotmuchMessagePropertiesP]
+ _properties_value.restype = c_char_p
+
+ """notmuch_message_properties_key"""
+ _properties_key = nmlib.notmuch_message_properties_key
+ _properties_key.argtypes = [NotmuchMessagePropertiesP]
+ _properties_key.restype = c_char_p
+
+ """notmuch_message_properties_move_to_next"""
+ _properties_move_to_next = nmlib.notmuch_message_properties_move_to_next
+ _properties_move_to_next.argtypes = [NotmuchMessagePropertiesP]
+ _properties_move_to_next.restype = None
+
#Constants: Flags that can be set/get with set_flag
FLAG = Enum(['MATCH'])
:returns: The header value as string
:raises: :exc:`NotInitializedError` if the message is not
initialized
- :raises: :exc:`NullPointerError` if any error occured
+ :raises: :exc:`NullPointerError` if any error occurred
"""
if not self._msg:
raise NotInitializedError()
:returns: A :class:`Tags` iterator.
:raises: :exc:`NotInitializedError` if the message is not
initialized
- :raises: :exc:`NullPointerError` if any error occured
+ :raises: :exc:`NullPointerError` if any error occurred
"""
if not self._msg:
raise NotInitializedError()
tags_p = Message._get_tags(self._msg)
- if tags_p == None:
+ if not tags_p:
raise NullPointerError()
return Tags(tags_p, self)
_freeze.argtypes = [NotmuchMessageP]
_freeze.restype = c_uint
+ def get_property(self, prop):
+ """ Retrieve the value for a single property key
+
+ :param prop: The name of the property to get.
+ :returns: String with the property value or None if there is no such
+ key. In the case of multiple values for the given key, the
+ first one is retrieved.
+ :raises: :exc:`NotInitializedError` if message has not been
+ initialized
+ """
+ if not self._msg:
+ raise NotInitializedError()
+
+ value = c_char_p()
+ status = Message._get_property(self._msg, _str(prop), byref(value))
+ if status != 0:
+ raise NotmuchError(status)
+
+ return value.value.decode('utf-8') if value is not None else None
+
+ def get_properties(self, prop="", exact=False):
+ """ Get the properties of the message, returning a generator of
+ name, value pairs.
+
+ The generator will yield once per value. There might be more than one
+ value on each name, so the generator might yield the same name several
+ times.
+
+ :param prop: The name of the property to get. Otherwise it will return
+ the full list of properties of the message.
+ :param exact: if True, require exact match with key. Otherwise
+ treat as prefix.
+ :yields: Each property values as a pair of `name, value`
+ :ytype: pairs of str
+ :raises: :exc:`NotInitializedError` if message has not been
+ initialized
+ """
+ if not self._msg:
+ raise NotInitializedError()
+
+ properties = Message._get_properties(self._msg, _str(prop), exact)
+ while Message._properties_valid(properties):
+ key = Message._properties_key(properties)
+ value = Message._properties_value(properties)
+ yield key.decode("utf-8"), value.decode("utf-8")
+ Message._properties_move_to_next(properties)
+
def freeze(self):
"""Freezes the current state of 'message' within the database
logical OR operator.)
As a convenience, you can set the sync_maildir_flags parameter in
- :meth:`Database.add_message` to implicitly call this.
+ :meth:`Database.index_file` to implicitly call this.
:returns: a :class:`STATUS`. In short, you want to see
notmuch.STATUS.SUCCESS here. See there for details."""
if not self._msg:
raise NotInitializedError()
- return Message._tags_to_maildir_flags(self._msg)
+ return Message._maildir_flags_to_tags(self._msg)
def __repr__(self):
"""Represent a Message() object by str()"""
def get_message_parts(self):
"""Output like notmuch show"""
- fp = open(self.get_filename())
- email_msg = email.message_from_file(fp)
+ fp = open(self.get_filename(), 'rb')
+ if sys.version_info[0] < 3:
+ email_msg = email.message_from_file(fp)
+ else:
+ email_msg = email.message_from_binary_file(fp)
fp.close()
out = []
out_part = parts[(num - 1)]
return out_part.get_payload(decode=True)
- def format_message_internal(self):
- """Create an internal representation of the message parts,
- which can easily be output to json, text, or another output
- format. The argument match tells whether this matched a
- query."""
- output = {}
- output["id"] = self.get_message_id()
- output["match"] = self.is_match()
- output["filename"] = self.get_filename()
- output["tags"] = list(self.get_tags())
-
- headers = {}
- for h in ["Subject", "From", "To", "Cc", "Bcc", "Date"]:
- headers[h] = self.get_header(h)
- output["headers"] = headers
-
- body = []
- parts = self.get_message_parts()
- for i in xrange(len(parts)):
- msg = parts[i]
- part_dict = {}
- part_dict["id"] = i + 1
- # We'll be using this is a lot, so let's just get it once.
- cont_type = msg.get_content_type()
- part_dict["content-type"] = cont_type
- # NOTE:
- # Now we emulate the current behaviour, where it ignores
- # the html if there's a text representation.
- #
- # This is being worked on, but it will be easier to fix
- # here in the future than to end up with another
- # incompatible solution.
- disposition = msg["Content-Disposition"]
- if disposition and disposition.lower().startswith("attachment"):
- part_dict["filename"] = msg.get_filename()
- else:
- if cont_type.lower() == "text/plain":
- part_dict["content"] = msg.get_payload()
- elif (cont_type.lower() == "text/html" and
- i == 0):
- part_dict["content"] = msg.get_payload()
- body.append(part_dict)
-
- output["body"] = body
-
- return output
-
- def format_message_as_json(self, indent=0):
- """Outputs the message as json. This is essentially the same
- as python's dict format, but we run it through, just so we
- don't have to worry about the details."""
- return json.dumps(self.format_message_internal())
-
- def format_message_as_text(self, indent=0):
- """Outputs it in the old-fashioned notmuch text form. Will be
- easy to change to a new format when the format changes."""
-
- format = self.format_message_internal()
- output = "\fmessage{ id:%s depth:%d match:%d filename:%s" \
- % (format['id'], indent, format['match'], format['filename'])
- output += "\n\fheader{"
-
- #Todo: this date is supposed to be prettified, as in the index.
- output += "\n%s (%s) (" % (format["headers"]["From"],
- format["headers"]["Date"])
- output += ", ".join(format["tags"])
- output += ")"
-
- output += "\nSubject: %s" % format["headers"]["Subject"]
- output += "\nFrom: %s" % format["headers"]["From"]
- output += "\nTo: %s" % format["headers"]["To"]
- if format["headers"]["Cc"]:
- output += "\nCc: %s" % format["headers"]["Cc"]
- if format["headers"]["Bcc"]:
- output += "\nBcc: %s" % format["headers"]["Bcc"]
- output += "\nDate: %s" % format["headers"]["Date"]
- output += "\n\fheader}"
-
- output += "\n\fbody{"
-
- parts = format["body"]
- parts.sort(key=lambda x: x['id'])
- for p in parts:
- if not "filename" in p:
- output += "\n\fpart{ "
- output += "ID: %d, Content-type: %s\n" % (p["id"],
- p["content-type"])
- if "content" in p:
- output += "\n%s\n" % p["content"]
- else:
- output += "Non-text part: %s\n" % p["content-type"]
- output += "\n\fpart}"
- else:
- output += "\n\fattachment{ "
- output += "ID: %d, Content-type:%s\n" % (p["id"],
- p["content-type"])
- output += "Attachment: %s\n" % p["filename"]
- output += "\n\fattachment}\n"
-
- output += "\n\fbody}\n"
- output += "\n\fmessage}"
-
- return output
-
def __hash__(self):
"""Implement hash(), so we can use Message() sets"""
file = self.get_filename()