]> git.notmuchmail.org Git - notmuch/blobdiff - bindings/python/notmuch/message.py
Merge tag '0.29.2'
[notmuch] / bindings / python / notmuch / message.py
index 9eb4feef4d1d9f61c0316edbb5e0b9e624318b00..6e32b5f7ada2d825d4cd6c23403f6b6b1271691f 100644 (file)
@@ -12,14 +12,14 @@ 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/>.
+along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
 
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
+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,
@@ -29,6 +29,7 @@ from .globals import (
     NotmuchTagsP,
     NotmuchMessageP,
     NotmuchMessagesP,
+    NotmuchMessagePropertiesP,
     NotmuchFilenamesP,
 )
 from .errors import (
@@ -41,10 +42,7 @@ from .tag import Tags
 from .filenames import Filenames
 
 import email
-try:
-    import simplejson as json
-except ImportError:
-    import json
+import sys
 
 
 class Message(Python3StringMixIn):
@@ -116,6 +114,36 @@ 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'])
 
@@ -226,7 +254,7 @@ class Message(Python3StringMixIn):
         :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()
@@ -259,7 +287,7 @@ class Message(Python3StringMixIn):
 
         files_p = Message._get_filenames(self._msg)
 
-        return Filenames(files_p, self).as_generator()
+        return Filenames(files_p, self)
 
     def get_flag(self, flag):
         """Checks whether a specific flag is set for this message
@@ -298,13 +326,13 @@ class Message(Python3StringMixIn):
         :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)
 
@@ -436,6 +464,55 @@ class Message(Python3StringMixIn):
     _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)
+
+        if value is None or value.value is None:
+            return None
+        return value.value.decode('utf-8')
+
+    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
 
@@ -570,13 +647,13 @@ class Message(Python3StringMixIn):
         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()"""
@@ -591,8 +668,11 @@ class Message(Python3StringMixIn):
 
     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 = []
@@ -610,110 +690,6 @@ class Message(Python3StringMixIn):
             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()
@@ -741,5 +717,5 @@ class Message(Python3StringMixIn):
 
     def __del__(self):
         """Close and free the notmuch Message"""
-        if self._msg is not None:
+        if self._msg:
             self._destroy(self._msg)