python: move the exception classes into error.py
[notmuch] / bindings / python / notmuch / filenames.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 <http://www.gnu.org/licenses/>.
16
17 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
18 """
19 from ctypes import c_char_p
20 from notmuch.globals import (
21     nmlib,
22     NotmuchMessageP,
23     NotmuchFilenamesP,
24     Python3StringMixIn,
25 )
26 from .errors import (
27     NullPointerError,
28     NotInitializedError,
29 )
30
31
32 class Filenames(Python3StringMixIn):
33     """Represents a list of filenames as returned by notmuch
34
35     This object contains the Filenames iterator. The main function is
36     as_generator() which will return a generator so we can do a Filenamesth an
37     iterator over a list of notmuch filenames. Do note that the underlying
38     library only provides a one-time iterator (it cannot reset the iterator to
39     the start). Thus iterating over the function will "exhaust" the list of
40     tags, and a subsequent iteration attempt will raise a
41     :exc:`NotInitializedError`. Also note, that any function that uses
42     iteration (nearly all) will also exhaust the tags. So both::
43
44       for name in filenames: print name
45
46     as well as::
47
48        number_of_names = len(names)
49
50     and even a simple::
51
52        #str() iterates over all tags to construct a space separated list
53        print(str(filenames))
54
55     will "exhaust" the Filenames. However, you can use
56     :meth:`Message.get_filenames` repeatedly to get fresh Filenames
57     objects to perform various actions on filenames.
58     """
59
60     #notmuch_filenames_get
61     _get = nmlib.notmuch_filenames_get
62     _get.argtypes = [NotmuchFilenamesP]
63     _get.restype = c_char_p
64
65     def __init__(self, files_p, parent):
66         """
67         :param files_p: A pointer to an underlying *notmuch_tags_t*
68              structure. These are not publically exposed, so a user
69              will almost never instantiate a :class:`Tags` object
70              herself. They are usually handed back as a result,
71              e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
72              valid, we will raise an :exc:`NullPointerError`
73              if it is `None`.
74         :type files_p: :class:`ctypes.c_void_p`
75         :param parent: The parent object (ie :class:`Message` these
76              filenames are derived from, and saves a
77              reference to it, so we can automatically delete the db object
78              once all derived objects are dead.
79         """
80         if not files_p:
81             raise NullPointerError()
82
83         self._files_p = files_p
84         #save reference to parent object so we keep it alive
85         self._parent = parent
86
87     def __iter__(self):
88         """ Make Filenames an iterator """
89         return self
90
91     _valid = nmlib.notmuch_filenames_valid
92     _valid.argtypes = [NotmuchFilenamesP]
93     _valid.restype = bool
94
95     _move_to_next = nmlib.notmuch_filenames_move_to_next
96     _move_to_next.argtypes = [NotmuchFilenamesP]
97     _move_to_next.restype = None
98
99     def __next__(self):
100         if not self._files_p:
101             raise NotInitializedError()
102
103         if not self._valid(self._files_p):
104             self._files_p = None
105             raise StopIteration
106
107         file_ = Filenames._get(self._files_p)
108         self._move_to_next(self._files_p)
109         return file_.decode('utf-8', 'ignore')
110     next = __next__ # python2.x iterator protocol compatibility
111
112     def as_generator(self):
113         """Return generator of Filenames
114
115         This is the main function that will usually be used by the
116         user.
117
118         .. deprecated:: 0.12
119                         :class:`Filenames` objects implement the
120                         iterator protocol.
121         """
122         return self
123
124     def __unicode__(self):
125         """Represent Filenames() as newline-separated list of full paths
126
127         .. note:: As this iterates over the filenames, we will not be
128                able to iterate over them again (as in retrieve them)! If
129                the tags have been exhausted already, this will raise a
130                :exc:`NotInitializedError` on subsequent
131                attempts. However, you can use
132                :meth:`Message.get_filenames` repeatedly to perform
133                various actions on filenames.
134         """
135         return "\n".join(self)
136
137     _destroy = nmlib.notmuch_filenames_destroy
138     _destroy.argtypes = [NotmuchMessageP]
139     _destroy.restype = None
140
141     def __del__(self):
142         """Close and free the notmuch filenames"""
143         if self._files_p is not None:
144             self._destroy(self._files_p)
145
146     def __len__(self):
147         """len(:class:`Filenames`) returns the number of contained files
148
149         .. note::
150
151             As this iterates over the files, we will not be able to
152             iterate over them again! So this will fail::
153
154                  #THIS FAILS
155                  files = Database().get_directory('').get_child_files()
156                  if len(files) > 0:  # this 'exhausts' msgs
157                      # next line raises
158                      # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
159                      for file in files: print file
160         """
161         if not self._files_p:
162             raise NotInitializedError()
163
164         i = 0
165         while self._valid(self._files_p):
166             self._move_to_next(self._files_p)
167             i += 1
168         self._files_p = None
169         return i