Fix orthography
[notmuch] / bindings / python / notmuch / thread.py
1 """
2 This file is part of notmuch.
3
4 Notmuch is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License as published by the
6 Free Software Foundation, either version 3 of the License, or (at your
7 option) any later version.
8
9 Notmuch is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with notmuch.  If not, see <https://www.gnu.org/licenses/>.
16
17 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
18 """
19
20 from ctypes import c_char_p, c_long, c_int
21 from .globals import (
22     nmlib,
23     NotmuchThreadP,
24     NotmuchMessagesP,
25     NotmuchTagsP,
26 )
27 from .errors import (
28     NullPointerError,
29     NotInitializedError,
30 )
31 from .messages import Messages
32 from .tag import Tags
33 from datetime import date
34
35 class Thread(object):
36     """Represents a single message thread."""
37
38     """notmuch_thread_get_thread_id"""
39     _get_thread_id = nmlib.notmuch_thread_get_thread_id
40     _get_thread_id.argtypes = [NotmuchThreadP]
41     _get_thread_id.restype = c_char_p
42
43     """notmuch_thread_get_authors"""
44     _get_authors = nmlib.notmuch_thread_get_authors
45     _get_authors.argtypes = [NotmuchThreadP]
46     _get_authors.restype = c_char_p
47
48     """notmuch_thread_get_subject"""
49     _get_subject = nmlib.notmuch_thread_get_subject
50     _get_subject.argtypes = [NotmuchThreadP]
51     _get_subject.restype = c_char_p
52
53     """notmuch_thread_get_toplevel_messages"""
54     _get_toplevel_messages = nmlib.notmuch_thread_get_toplevel_messages
55     _get_toplevel_messages.argtypes = [NotmuchThreadP]
56     _get_toplevel_messages.restype = NotmuchMessagesP
57
58     _get_newest_date = nmlib.notmuch_thread_get_newest_date
59     _get_newest_date.argtypes = [NotmuchThreadP]
60     _get_newest_date.restype = c_long
61
62     _get_oldest_date = nmlib.notmuch_thread_get_oldest_date
63     _get_oldest_date.argtypes = [NotmuchThreadP]
64     _get_oldest_date.restype = c_long
65
66     """notmuch_thread_get_tags"""
67     _get_tags = nmlib.notmuch_thread_get_tags
68     _get_tags.argtypes = [NotmuchThreadP]
69     _get_tags.restype = NotmuchTagsP
70
71     def __init__(self, thread_p, parent=None):
72         """
73         :param thread_p: A pointer to an internal notmuch_thread_t
74             Structure.  These are not publically exposed, so a user
75             will almost never instantiate a :class:`Thread` object
76             herself. They are usually handed back as a result,
77             e.g. when iterating through :class:`Threads`. *thread_p*
78             must be valid, we will raise an :exc:`NullPointerError`
79             if it is `None`.
80
81         :param parent: A 'parent' object is passed which this message is
82               derived from. We save a reference to it, so we can
83               automatically delete the parent object once all derived
84               objects are dead.
85         """
86         if not thread_p:
87             raise NullPointerError()
88         self._thread = thread_p
89         #keep reference to parent, so we keep it alive
90         self._parent = parent
91
92     def get_thread_id(self):
93         """Get the thread ID of 'thread'
94
95         The returned string belongs to 'thread' and will only be valid
96         for as long as the thread is valid.
97
98         :returns: String with a message ID
99         :raises: :exc:`NotInitializedError` if the thread
100                     is not initialized.
101         """
102         if not self._thread:
103             raise NotInitializedError()
104         return Thread._get_thread_id(self._thread).decode('utf-8', 'ignore')
105
106     _get_total_messages = nmlib.notmuch_thread_get_total_messages
107     _get_total_messages.argtypes = [NotmuchThreadP]
108     _get_total_messages.restype = c_int
109
110     def get_total_messages(self):
111         """Get the total number of messages in 'thread'
112
113         :returns: The number of all messages in the database
114                   belonging to this thread. Contrast with
115                   :meth:`get_matched_messages`.
116         :raises: :exc:`NotInitializedError` if the thread
117                     is not initialized.
118         """
119         if not self._thread:
120             raise NotInitializedError()
121         return self._get_total_messages(self._thread)
122
123     def get_toplevel_messages(self):
124         """Returns a :class:`Messages` iterator for the top-level messages in
125            'thread'
126
127            This iterator will not necessarily iterate over all of the messages
128            in the thread. It will only iterate over the messages in the thread
129            which are not replies to other messages in the thread.
130
131         :returns: :class:`Messages`
132         :raises: :exc:`NotInitializedError` if query is not initialized
133         :raises: :exc:`NullPointerError` if search_messages failed
134         """
135         if not self._thread:
136             raise NotInitializedError()
137
138         msgs_p = Thread._get_toplevel_messages(self._thread)
139
140         if not msgs_p:
141             raise NullPointerError()
142
143         return Messages(msgs_p, self)
144
145     """notmuch_thread_get_messages"""
146     _get_messages = nmlib.notmuch_thread_get_messages
147     _get_messages.argtypes = [NotmuchThreadP]
148     _get_messages.restype = NotmuchMessagesP
149
150     def get_messages(self):
151         """Returns a :class:`Messages` iterator for all messages in 'thread'
152
153         :returns: :class:`Messages`
154         :raises: :exc:`NotInitializedError` if query is not initialized
155         :raises: :exc:`NullPointerError` if get_messages failed
156         """
157         if not self._thread:
158             raise NotInitializedError()
159
160         msgs_p = Thread._get_messages(self._thread)
161
162         if not msgs_p:
163             raise NullPointerError()
164
165         return Messages(msgs_p, self)
166
167     _get_matched_messages = nmlib.notmuch_thread_get_matched_messages
168     _get_matched_messages.argtypes = [NotmuchThreadP]
169     _get_matched_messages.restype = c_int
170
171     def get_matched_messages(self):
172         """Returns the number of messages in 'thread' that matched the query
173
174         :returns: The number of all messages belonging to this thread that
175                   matched the :class:`Query`from which this thread was created.
176                   Contrast with :meth:`get_total_messages`.
177         :raises: :exc:`NotInitializedError` if the thread
178                     is not initialized.
179         """
180         if not self._thread:
181             raise NotInitializedError()
182         return self._get_matched_messages(self._thread)
183
184     def get_authors(self):
185         """Returns the authors of 'thread'
186
187         The returned string is a comma-separated list of the names of the
188         authors of mail messages in the query results that belong to this
189         thread.
190
191         The returned string belongs to 'thread' and will only be valid for
192         as long as this Thread() is not deleted.
193         """
194         if not self._thread:
195             raise NotInitializedError()
196         authors = Thread._get_authors(self._thread)
197         if not authors:
198             return None
199         return authors.decode('UTF-8', 'ignore')
200
201     def get_subject(self):
202         """Returns the Subject of 'thread'
203
204         The returned string belongs to 'thread' and will only be valid for
205         as long as this Thread() is not deleted.
206         """
207         if not self._thread:
208             raise NotInitializedError()
209         subject = Thread._get_subject(self._thread)
210         if not subject:
211             return None
212         return subject.decode('UTF-8', 'ignore')
213
214     def get_newest_date(self):
215         """Returns time_t of the newest message date
216
217         :returns: A time_t timestamp.
218         :rtype: c_unit64
219         :raises: :exc:`NotInitializedError` if the message
220                     is not initialized.
221         """
222         if not self._thread:
223             raise NotInitializedError()
224         return Thread._get_newest_date(self._thread)
225
226     def get_oldest_date(self):
227         """Returns time_t of the oldest message date
228
229         :returns: A time_t timestamp.
230         :rtype: c_unit64
231         :raises: :exc:`NotInitializedError` if the message
232                     is not initialized.
233         """
234         if not self._thread:
235             raise NotInitializedError()
236         return Thread._get_oldest_date(self._thread)
237
238     def get_tags(self):
239         """ Returns the message tags
240
241         In the Notmuch database, tags are stored on individual
242         messages, not on threads. So the tags returned here will be all
243         tags of the messages which matched the search and which belong to
244         this thread.
245
246         The :class:`Tags` object is owned by the thread and as such, will only
247         be valid for as long as this :class:`Thread` is valid (e.g. until the
248         query from which it derived is explicitly deleted).
249
250         :returns: A :class:`Tags` iterator.
251         :raises: :exc:`NotInitializedError` if query is not initialized
252         :raises: :exc:`NullPointerError` if search_messages failed
253         """
254         if not self._thread:
255             raise NotInitializedError()
256
257         tags_p = Thread._get_tags(self._thread)
258         if not tags_p:
259             raise NullPointerError()
260         return Tags(tags_p, self)
261
262     def __unicode__(self):
263         frm = "thread:%s %12s [%d/%d] %s; %s (%s)"
264
265         return frm % (self.get_thread_id(),
266                       date.fromtimestamp(self.get_newest_date()),
267                       self.get_matched_messages(),
268                       self.get_total_messages(),
269                       self.get_authors(),
270                       self.get_subject(),
271                       self.get_tags(),
272                      )
273
274     _destroy = nmlib.notmuch_thread_destroy
275     _destroy.argtypes = [NotmuchThreadP]
276     _destroy.restype = None
277
278     def __del__(self):
279         """Close and free the notmuch Thread"""
280         if self._thread:
281             self._destroy(self._thread)