unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
blob 251607bbb06c8579e20b1877375d1c425a9d04e9 9511 bytes (raw)
name: bindings/python-cffi/notdb/_message.py 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
 
import os
import pathlib

import notdb._base as base
import notdb._capi as capi
import notdb._errors as errors
import notdb._tags as tags


class Message(base.NotmuchObject):
    """An email message stored in the notmuch database.

    This should not be directly created, instead it will be returned
    by calling methods on :class:`Database`.  A message keeps a
    reference to the database object since the database object can not
    be released while the message is in use.

    Note that this represents a message in the notmuch database.  For
    full email functionality you may want to use the :mod:`email`
    package from Python's standard library.  You could e.g. create
    this as such::

       notmuch_msg = db.get_message(msgid)  # or from a query
       parser = email.parser.BytesParser(policy=email.policy.default)
       with notmuch_msg.path.open('rb) as fp:
           email_msg = parser.parse(fp)

    Most commonly the functionality provided by notmuch is sufficient
    to read email however.

    :param db: The database instance this message is associated with.
    :type db: Database
    :param msg_p: The C pointer to the ``notmuch_message_t``.
    :type msg_p: <cdata>

    :param dup: Whether the message was a duplicate on insertion.

    :type dup: None or bool
    """
    _msg_p = base.MemoryPointer()

    def __init__(self, db, msg_p):
        self._db = db
        self._msg_p = msg_p

    @property
    def alive(self):
        if not self._db.alive:
            return False
        try:
            self._msg_p
        except errors.ObjectDestroyedError:
            return False
        else:
            return True

    def __del__(self):
        """Destroy the message, freeing the memory.

        Note that when an owning object, like the containing query, is
        destroyed the messages also get destoryed.
        """
        self.destroy()

    def destroy(self):
        """Destroy the object and all children.

        This will destroy the object, freeing all memory for it and
        it's children.  You should not normally need to call this, it
        will be called automatically by Python garbage collection.

        The main reason for it's existence is for parent objects being
        able to destroy their children, this is required when Python's
        garbage collection does not guarantee ordered deletion,
        e.g. at intepreter shutdown.

        :param parent: Used by the parent to indicate it has already
           destroyed itself, and thus all it's children.  In this case
           this object only marks itself as already destroyed to avoid
           double freeing memory.
        """
        if self.alive:
            capi.lib.notmuch_message_destroy(self._msg_p)
        self._msg_p = None

    @property
    def messageid(self):
        """The message ID as a string.

        The message ID is decoded with the ignore error handler.  This
        is fine as long as the message ID is well formed.  If it is
        not valid ASCII then this will be lossy.  So if you need to be
        able to write the exact same message ID back you should use
        :attr:`messageidb`.

        Note that notmuch will decode the message ID value and thus
        strip off the surrounding ``<`` and ``>`` characters.  This is
        different from Python's :mod:`email` package behaviour which
        leaves these characters in place.

        :returns: The message ID.
        :rtype: :class:`BinString`, this is a normal str but calling
           bytes() on it will return the original bytes used to create
           it.

        :raises ObjectDestroyedError: if used after destoryed.
        """
        ret = capi.lib.notmuch_message_get_message_id(self._msg_p)
        return base.BinString(capi.ffi.string(ret))

    @property
    def threadid(self):
        """The thread ID.

        The thread ID is decoded with the surrogateescape error
        handler so that it is possible to reconstruct the original
        thread ID if it is not valid UTF-8.
        """
        raise NotImplementedError

    @property
    def path(self):
        """A pathname of the message as a pathlib.Path instance.

        If multiple files in the database contain the same message ID
        this will be just one of the files, chosen at random.

        :raises ObjectDestroyedError: if used after destoryed.
        """
        ret = capi.lib.notmuch_message_get_filename(self._msg_p)
        return pathlib.Path(os.fsdecode(capi.ffi.string(ret)))

    @property
    def pathb(self):
        """A pathname of the message as a bytes object.

        See :attr:`path` for details, this is the same but does return
        the path as a bytes object which is faster but less convenient.

        :raises ObjectDestroyedError: if used after destoryed.
        """
        ret = capi.lib.notmuch_message_get_filename(self._msg_p)
        return capi.ffi.string(ret)

    def pathnames(self):
        """Return an iterator of all files for this message.

        If multiple files contained the same message ID they will all
        be returned here.  The files are returned as intances of
        :class:`pathlib.Path`.

        :raises ObjectDestroyedError: if used after destoryed.
        """
        raise NotImplementedError

    def pathnamesb(self):
        """Return an iterator of all files for this message.

        This is like :meth:`pathnames` but the files are returned as
        byte objects instead.

        :raises ObjectDestroyedError: if used after destoryed.
        """
        raise NotImplementedError

    @property
    def ghost(self):
        """Indicates whether this message is a ghost message.

        A ghost message if a message which we know exists, but it has
        no files or content associated with it.  This can happen if
        it was referenced by some other message.  Only the
        :attr:`messageid` and :attr:`threadid` attributes are valid
        for it.
        """
        raise NotImplementedError

    @property
    def date(self):
        """The message date.

        XXX Figure out which format to provide this in.
        """
        raise NotImplementedError

    def header(self, name):
        """Return the value of the named header.

        Returns the header from notmuch, some common headers are
        stored in the database, others are read from the file.
        Headers are returned with their newlines stripped and
        collapsed concatenated together if they occur multiple times.
        You may be better off using the standard library email
        package's ``email.message_from_file(msg.path.open())`` if that
        is not sufficient for you.

        :param header: Case-insensitive header name to retrieve.
        :type header: str

        :returns: The header value, an empty string if the header is
           not present.
        :rtype: str

        :raises NoSuchHeaderError: if the header is not present.
        """
        raise NotImplementedError

    @property
    def tags(self):
        """The tags associated with the message.

        This behaves as a set.  But removing and adding items to the
        set removes and adds them to the message in the database.
        """
        # By caching the tagset this creates a circular reference.
        # This is fine on CPython 3.4+.  We could improve this by using
        # weakref.finalizer instead of __del__.
        try:
            return self._cached_tagset
        except AttributeError:
            self._cached_tagset = tags.MutableTagSet(
                self, '_msg_p', capi.lib.notmuch_message_get_tags)
            return self._cached_tagset

    def tags_to_flags(self):
        """Sync the notmuch tags to maildir flags.

        This will rename the pathname of the message so that the
        maildir flags match the current set of notmuch tags.  The
        mappings are:

        flag    tag
        -----   --------------
        ``D``   ``draft``
        ``F``   ``flagged``
        ``P``   ``passed``
        ``R``   ``replied``
        ``S``   not ``unread``

        Any other flags are preserved in the renaming.  If the
        existing flag format is invalid, e.g. flags repeated, not in
        ASCII order file not ending in ``:2,``, the file is not
        renamed.
        """
        raise NotImplementedError

    def flags_to_tags(self):
        """Sync the maildir flags to notmuch tags.

        This synchronizes the opposite way as described in
        :meth:`rags_to_flags`.
        """
        raise NotImplementedError

    def frozen(self):
        """Context manager to freeze the message state.

        This allows you to perform atomic tag updates::

           with msg.frozen():
               msg.tags.clear()
               msg.tags.add('foo')

        Using This would ensure the message never ends up with no tags
        applied at all.

        It is safe to nest calls to this context manager.
        """
        raise NotImplementedError

    @property
    def properties(self):
        """A map of arbitrary key-value pairs associated with the message.

        Be aware that properties may be used by other extensions to
        store state in.  So delete or modify with care.
        """
        raise NotImplementedError


class MessageProperties:
    # XXX This will need to decide what to do with encoding.  Easiest
    #     is to store bytes and leave it to the user to call .encode()
    #     .decode().
    pass

debug log:

solving 251607bb ...
found 251607bb in https://yhetil.org/notmuch/20171128204608.12210-2-flub@devork.be/

applying [1/1] https://yhetil.org/notmuch/20171128204608.12210-2-flub@devork.be/
diff --git a/bindings/python-cffi/notdb/_message.py b/bindings/python-cffi/notdb/_message.py
new file mode 100644
index 00000000..251607bb

Checking patch bindings/python-cffi/notdb/_message.py...
Applied patch bindings/python-cffi/notdb/_message.py cleanly.

index at:
100644 251607bbb06c8579e20b1877375d1c425a9d04e9	bindings/python-cffi/notdb/_message.py

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).