Make messages returned by Thread objects owned

This reverses the logic of StandaloneMessage to instead create a
OwnedMessage.  Only the Thread class allows retrieving messages more
then once so it can explicitly create such messages.

The added test fails with SIGABRT without the fix for the message
re-use in threads being present.
This commit is contained in:
Floris Bruynooghe 2020-06-15 22:58:50 +02:00 committed by David Bremner
parent 1317579079
commit 2d895a0119
4 changed files with 51 additions and 29 deletions

View file

@ -400,7 +400,7 @@ class Database(base.NotmuchObject):
capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID] capi.lib.NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID]
if ret not in ok: if ret not in ok:
raise errors.NotmuchError(ret) raise errors.NotmuchError(ret)
msg = message.StandaloneMessage(self, msg_pp[0], db=self) msg = message.Message(self, msg_pp[0], db=self)
if sync_flags: if sync_flags:
msg.tags.from_maildir_flags() msg.tags.from_maildir_flags()
return self.AddedMessage( return self.AddedMessage(
@ -469,7 +469,7 @@ class Database(base.NotmuchObject):
msg_p = msg_pp[0] msg_p = msg_pp[0]
if msg_p == capi.ffi.NULL: if msg_p == capi.ffi.NULL:
raise LookupError raise LookupError
msg = message.StandaloneMessage(self, msg_p, db=self) msg = message.Message(self, msg_p, db=self)
return msg return msg
def get(self, filename): def get(self, filename):
@ -502,7 +502,7 @@ class Database(base.NotmuchObject):
msg_p = msg_pp[0] msg_p = msg_pp[0]
if msg_p == capi.ffi.NULL: if msg_p == capi.ffi.NULL:
raise LookupError raise LookupError
msg = message.StandaloneMessage(self, msg_p, db=self) msg = message.Message(self, msg_p, db=self)
return msg return msg
@property @property

View file

@ -47,9 +47,7 @@ class Message(base.NotmuchObject):
:type db: Database :type db: Database
:param msg_p: The C pointer to the ``notmuch_message_t``. :param msg_p: The C pointer to the ``notmuch_message_t``.
:type msg_p: <cdata> :type msg_p: <cdata>
:param dup: Whether the message was a duplicate on insertion. :param dup: Whether the message was a duplicate on insertion.
:type dup: None or bool :type dup: None or bool
""" """
_msg_p = base.MemoryPointer() _msg_p = base.MemoryPointer()
@ -61,10 +59,22 @@ class Message(base.NotmuchObject):
@property @property
def alive(self): def alive(self):
return self._parent.alive if not self._parent.alive:
return False
try:
self._msg_p
except errors.ObjectDestroyedError:
return False
else:
return True
def __del__(self):
self._destroy()
def _destroy(self): def _destroy(self):
pass if self.alive:
capi.lib.notmuch_message_destroy(self._msg_p)
self._msg_p = None
@property @property
def messageid(self): def messageid(self):
@ -363,30 +373,26 @@ class Message(base.NotmuchObject):
if isinstance(other, self.__class__): if isinstance(other, self.__class__):
return self.messageid == other.messageid return self.messageid == other.messageid
class StandaloneMessage(Message):
"""An email message stored in the notmuch database.
This subclass of Message is used for messages that are retrieved from the class OwnedMessage(Message):
database directly and are not owned by a query. """An email message owned by parent thread object.
This subclass of Message is used for messages that are retrieved
from the notmuch database via a parent :class:`notmuch2.Thread`
object, which "owns" this message. This means that when this
message object is destroyed, by calling :func:`del` or
:meth:`_destroy` directly or indirectly, the message is not freed
in the notmuch API and the parent :class:`notmuch2.Thread` object
can return the same object again when needed.
""" """
@property @property
def alive(self): def alive(self):
if not self._parent.alive: return self._parent.alive
return False
try:
self._msg_p
except errors.ObjectDestroyedError:
return False
else:
return True
def __del__(self):
self._destroy()
def _destroy(self): def _destroy(self):
if self.alive: pass
capi.lib.notmuch_message_destroy(self._msg_p)
self._msg_p = None
class FilenamesIter(base.NotmuchIter): class FilenamesIter(base.NotmuchIter):
"""Iterator for binary filenames objects.""" """Iterator for binary filenames objects."""
@ -690,8 +696,9 @@ collections.abc.ValuesView.register(PropertiesValuesView)
class MessageIter(base.NotmuchIter): class MessageIter(base.NotmuchIter):
def __init__(self, parent, msgs_p, *, db): def __init__(self, parent, msgs_p, *, db, msg_cls=Message):
self._db = db self._db = db
self._msg_cls = msg_cls
super().__init__(parent, msgs_p, super().__init__(parent, msgs_p,
fn_destroy=capi.lib.notmuch_messages_destroy, fn_destroy=capi.lib.notmuch_messages_destroy,
fn_valid=capi.lib.notmuch_messages_valid, fn_valid=capi.lib.notmuch_messages_valid,
@ -700,4 +707,4 @@ class MessageIter(base.NotmuchIter):
def __next__(self): def __next__(self):
msg_p = super().__next__() msg_p = super().__next__()
return Message(self, msg_p, db=self._db) return self._msg_cls(self, msg_p, db=self._db)

View file

@ -62,7 +62,9 @@ class Thread(base.NotmuchObject, collections.abc.Iterable):
:raises ObjectDestroyedError: if used after destroyed. :raises ObjectDestroyedError: if used after destroyed.
""" """
msgs_p = capi.lib.notmuch_thread_get_toplevel_messages(self._thread_p) msgs_p = capi.lib.notmuch_thread_get_toplevel_messages(self._thread_p)
return message.MessageIter(self, msgs_p, db=self._db) return message.MessageIter(self, msgs_p,
db=self._db,
msg_cls=message.OwnedMessage)
def __iter__(self): def __iter__(self):
"""Return an iterator over all the messages in the thread. """Return an iterator over all the messages in the thread.
@ -72,7 +74,9 @@ class Thread(base.NotmuchObject, collections.abc.Iterable):
:raises ObjectDestroyedError: if used after destroyed. :raises ObjectDestroyedError: if used after destroyed.
""" """
msgs_p = capi.lib.notmuch_thread_get_messages(self._thread_p) msgs_p = capi.lib.notmuch_thread_get_messages(self._thread_p)
return message.MessageIter(self, msgs_p, db=self._db) return message.MessageIter(self, msgs_p,
db=self._db,
msg_cls=message.OwnedMessage)
@property @property
def matched(self): def matched(self):

View file

@ -324,3 +324,14 @@ class TestQuery:
threads = db.threads('*') threads = db.threads('*')
thread = next(threads) thread = next(threads)
assert isinstance(thread, notmuch2.Thread) assert isinstance(thread, notmuch2.Thread)
def test_use_threaded_message_twice(self, db):
thread = next(db.threads('*'))
for msg in thread.toplevel():
assert isinstance(msg, notmuch2.Message)
assert msg.alive
del msg
for msg in thread:
assert isinstance(msg, notmuch2.Message)
assert msg.alive
del msg