Merge tag '0.34.1'
[notmuch] / bindings / python / notmuch / threads.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 .globals import (
21     nmlib,
22     Python3StringMixIn,
23     NotmuchThreadP,
24     NotmuchThreadsP,
25 )
26 from .errors import (
27     NullPointerError,
28     NotInitializedError,
29 )
30 from .thread import Thread
31
32 class Threads(Python3StringMixIn):
33     """Represents a list of notmuch threads
34
35     This object provides an iterator over a list of notmuch threads
36     (Technically, it provides a wrapper for the underlying
37     *notmuch_threads_t* structure). Do note that the underlying
38     library only provides a one-time iterator (it cannot reset the
39     iterator to the start). Thus iterating over the function will
40     "exhaust" the list of threads, and a subsequent iteration attempt
41     will raise a :exc:`NotInitializedError`. Also
42     note, that any function that uses iteration will also
43     exhaust the messages. So both::
44
45       for thread in threads: print thread
46
47     as well as::
48
49        list_of_threads = list(threads)
50
51     will "exhaust" the threads. If you need to re-iterate over a list of
52     messages you will need to retrieve a new :class:`Threads` object.
53
54     Things are not as bad as it seems though, you can store and reuse
55     the single Thread objects as often as you want as long as you
56     keep the parent Threads object around. (Recall that due to
57     hierarchical memory allocation, all derived Threads objects will
58     be invalid when we delete the parent Threads() object, even if it
59     was already "exhausted".) So this works::
60
61       db   = Database()
62       threads = Query(db,'').search_threads() #get a Threads() object
63       threadlist = []
64       for thread in threads:
65          threadlist.append(thread)
66
67       # threads is "exhausted" now.
68       # However it will be kept around until all retrieved Thread() objects are
69       # also deleted. If you did e.g. an explicit del(threads) here, the
70       # following lines would fail.
71
72       # You can reiterate over *threadlist* however as often as you want.
73       # It is simply a list with Thread objects.
74
75       print (threadlist[0].get_thread_id())
76       print (threadlist[1].get_thread_id())
77       print (threadlist[0].get_total_messages())
78     """
79
80     #notmuch_threads_get
81     _get = nmlib.notmuch_threads_get
82     _get.argtypes = [NotmuchThreadsP]
83     _get.restype = NotmuchThreadP
84
85     def __init__(self, threads_p, parent=None):
86         """
87         :param threads_p:  A pointer to an underlying *notmuch_threads_t*
88              structure. These are not publicly exposed, so a user
89              will almost never instantiate a :class:`Threads` object
90              herself. They are usually handed back as a result,
91              e.g. in :meth:`Query.search_threads`.  *threads_p* must be
92              valid, we will raise an :exc:`NullPointerError` if it is
93              `None`.
94         :type threads_p: :class:`ctypes.c_void_p`
95         :param parent: The parent object
96              (ie :class:`Query`) these tags are derived from. It saves
97              a reference to it, so we can automatically delete the db
98              object once all derived objects are dead.
99         :TODO: Make the iterator work more than once and cache the tags in
100                the Python object.(?)
101         """
102         if not threads_p:
103             raise NullPointerError()
104
105         self._threads = threads_p
106         #store parent, so we keep them alive as long as self  is alive
107         self._parent = parent
108
109     def __iter__(self):
110         """ Make Threads an iterator """
111         return self
112
113     _valid = nmlib.notmuch_threads_valid
114     _valid.argtypes = [NotmuchThreadsP]
115     _valid.restype = bool
116
117     _move_to_next = nmlib.notmuch_threads_move_to_next
118     _move_to_next.argtypes = [NotmuchThreadsP]
119     _move_to_next.restype = None
120
121     def __next__(self):
122         if not self._threads:
123             raise NotInitializedError()
124
125         if not self._valid(self._threads):
126             self._threads = None
127             raise StopIteration
128
129         thread = Thread(Threads._get(self._threads), self)
130         self._move_to_next(self._threads)
131         return thread
132     next = __next__ # python2.x iterator protocol compatibility
133
134     def __nonzero__(self):
135         '''
136         Implement truth value testing. If __nonzero__ is not
137         implemented, the python runtime would fall back to `len(..) >
138         0` thus exhausting the iterator.
139
140         :returns: True if the wrapped iterator has at least one more object
141                   left.
142         '''
143         return self._threads and self._valid(self._threads)
144
145     _destroy = nmlib.notmuch_threads_destroy
146     _destroy.argtypes = [NotmuchThreadsP]
147     _destroy.restype = None
148
149     def __del__(self):
150         """Close and free the notmuch Threads"""
151         if self._threads:
152             self._destroy(self._threads)