]> git.notmuchmail.org Git - notmuch/blob - bindings/python-cffi/notmuch2/_thread.py
Make messages returned by Thread objects owned
[notmuch] / bindings / python-cffi / notmuch2 / _thread.py
1 import collections.abc
2 import weakref
3
4 from notmuch2 import _base as base
5 from notmuch2 import _capi as capi
6 from notmuch2 import _errors as errors
7 from notmuch2 import _message as message
8 from notmuch2 import _tags as tags
9
10
11 __all__ = ['Thread']
12
13
14 class Thread(base.NotmuchObject, collections.abc.Iterable):
15     _thread_p = base.MemoryPointer()
16
17     def __init__(self, parent, thread_p, *, db):
18         self._parent = parent
19         self._thread_p = thread_p
20         self._db = db
21
22     @property
23     def alive(self):
24         if not self._parent.alive:
25             return False
26         try:
27             self._thread_p
28         except errors.ObjectDestroyedError:
29             return False
30         else:
31             return True
32
33     def __del__(self):
34         self._destroy()
35
36     def _destroy(self):
37         if self.alive:
38             capi.lib.notmuch_thread_destroy(self._thread_p)
39         self._thread_p = None
40
41     @property
42     def threadid(self):
43         """The thread ID as a :class:`BinString`.
44
45         :raises ObjectDestroyedError: if used after destroyed.
46         """
47         ret = capi.lib.notmuch_thread_get_thread_id(self._thread_p)
48         return base.BinString.from_cffi(ret)
49
50     def __len__(self):
51         """Return the number of messages in the thread.
52
53         :raises ObjectDestroyedError: if used after destroyed.
54         """
55         return capi.lib.notmuch_thread_get_total_messages(self._thread_p)
56
57     def toplevel(self):
58         """Return an iterator of the toplevel messages.
59
60         :returns: An iterator yielding :class:`Message` instances.
61
62         :raises ObjectDestroyedError: if used after destroyed.
63         """
64         msgs_p = capi.lib.notmuch_thread_get_toplevel_messages(self._thread_p)
65         return message.MessageIter(self, msgs_p,
66                                    db=self._db,
67                                    msg_cls=message.OwnedMessage)
68
69     def __iter__(self):
70         """Return an iterator over all the messages in the thread.
71
72         :returns: An iterator yielding :class:`Message` instances.
73
74         :raises ObjectDestroyedError: if used after destroyed.
75         """
76         msgs_p = capi.lib.notmuch_thread_get_messages(self._thread_p)
77         return message.MessageIter(self, msgs_p,
78                                    db=self._db,
79                                    msg_cls=message.OwnedMessage)
80
81     @property
82     def matched(self):
83         """The number of messages in this thread which matched the query.
84
85         Of the messages in the thread this gives the count of messages
86         which did directly match the search query which this thread
87         originates from.
88
89         :raises ObjectDestroyedError: if used after destroyed.
90         """
91         return capi.lib.notmuch_thread_get_matched_messages(self._thread_p)
92
93     @property
94     def authors(self):
95         """A comma-separated string of all authors in the thread.
96
97         Authors of messages which matched the query the thread was
98         retrieved from will be at the head of the string, ordered by
99         date of their messages.  Following this will be the authors of
100         the other messages in the thread, also ordered by date of
101         their messages.  Both groups of authors are separated by the
102         ``|`` character.
103
104         :returns: The stringified list of authors.
105         :rtype: BinString
106
107         :raises ObjectDestroyedError: if used after destroyed.
108         """
109         ret = capi.lib.notmuch_thread_get_authors(self._thread_p)
110         return base.BinString.from_cffi(ret)
111
112     @property
113     def subject(self):
114         """The subject of the thread, taken from the first message.
115
116         The thread's subject is taken to be the subject of the first
117         message according to query sort order.
118
119         :returns: The thread's subject.
120         :rtype: BinString
121
122         :raises ObjectDestroyedError: if used after destroyed.
123         """
124         ret = capi.lib.notmuch_thread_get_subject(self._thread_p)
125         return base.BinString.from_cffi(ret)
126
127     @property
128     def first(self):
129         """Return the date of the oldest message in the thread.
130
131         The time the first message was sent as an integer number of
132         seconds since the *epoch*, 1 Jan 1970.
133
134         :raises ObjectDestroyedError: if used after destroyed.
135         """
136         return capi.lib.notmuch_thread_get_oldest_date(self._thread_p)
137
138     @property
139     def last(self):
140         """Return the date of the newest message in the thread.
141
142         The time the last message was sent as an integer number of
143         seconds since the *epoch*, 1 Jan 1970.
144
145         :raises ObjectDestroyedError: if used after destroyed.
146         """
147         return capi.lib.notmuch_thread_get_newest_date(self._thread_p)
148
149     @property
150     def tags(self):
151         """Return an immutable set with all tags used in this thread.
152
153         This returns an immutable set-like object implementing the
154         collections.abc.Set Abstract Base Class.  Due to the
155         underlying libnotmuch implementation some operations have
156         different performance characteristics then plain set objects.
157         Mainly any lookup operation is O(n) rather then O(1).
158
159         Normal usage treats tags as UTF-8 encoded unicode strings so
160         they are exposed to Python as normal unicode string objects.
161         If you need to handle tags stored in libnotmuch which are not
162         valid unicode do check the :class:`ImmutableTagSet` docs for
163         how to handle this.
164
165         :rtype: ImmutableTagSet
166
167         :raises ObjectDestroyedError: if used after destroyed.
168         """
169         try:
170             ref = self._cached_tagset
171         except AttributeError:
172             tagset = None
173         else:
174             tagset = ref()
175         if tagset is None:
176             tagset = tags.ImmutableTagSet(
177                 self, '_thread_p', capi.lib.notmuch_thread_get_tags)
178             self._cached_tagset = weakref.ref(tagset)
179         return tagset
180
181
182 class ThreadIter(base.NotmuchIter):
183
184     def __init__(self, parent, threads_p, *, db):
185         self._db = db
186         super().__init__(parent, threads_p,
187                          fn_destroy=capi.lib.notmuch_threads_destroy,
188                          fn_valid=capi.lib.notmuch_threads_valid,
189                          fn_get=capi.lib.notmuch_threads_get,
190                          fn_next=capi.lib.notmuch_threads_move_to_next)
191
192     def __next__(self):
193         thread_p = super().__next__()
194         return Thread(self, thread_p, db=self._db)