41384604588d16ef70f5eb47f84f62aca129695f
[notmuch] / bindings / python / notmuch / globals.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 import sys
20 from ctypes import CDLL, c_char_p, c_int, Structure, POINTER
21
22 #-----------------------------------------------------------------------------
23 #package-global instance of the notmuch library
24 try:
25     nmlib = CDLL("libnotmuch.so.2")
26 except:
27     raise ImportError("Could not find shared 'notmuch' library.")
28
29
30 if sys.version_info[0] == 2:
31     class Python3StringMixIn(object):
32         def __str__(self):
33             return unicode(self).encode('utf-8')
34
35
36     def _str(value):
37         """Ensure a nicely utf-8 encoded string to pass to libnotmuch
38
39         C++ code expects strings to be well formatted and
40         unicode strings to have no null bytes."""
41         if not isinstance(value, basestring):
42             raise TypeError("Expected str or unicode, got %s" % type(value))
43         if isinstance(value, unicode):
44             return value.encode('UTF-8')
45         return value
46 else:
47     class Python3StringMixIn(object):
48         def __str__(self):
49             return self.__unicode__()
50
51
52     def _str(value):
53         """Ensure a nicely utf-8 encoded string to pass to libnotmuch
54
55         C++ code expects strings to be well formatted and
56         unicode strings to have no null bytes."""
57         if not isinstance(value, str):
58             raise TypeError("Expected str, got %s" % type(value))
59         return value.encode('UTF-8')
60
61
62 class Enum(object):
63     """Provides ENUMS as "code=Enum(['a','b','c'])" where code.a=0 etc..."""
64     def __init__(self, names):
65         for number, name in enumerate(names):
66             setattr(self, name, number)
67
68
69 class Status(Enum):
70     """Enum with a string representation of a notmuch_status_t value."""
71     _status2str = nmlib.notmuch_status_to_string
72     _status2str.restype = c_char_p
73     _status2str.argtypes = [c_int]
74
75     def __init__(self, statuslist):
76         """It is initialized with a list of strings that are available as
77         Status().string1 - Status().stringn attributes.
78         """
79         super(Status, self).__init__(statuslist)
80
81     @classmethod
82     def status2str(self, status):
83         """Get a (unicode) string representation of a notmuch_status_t value."""
84         # define strings for custom error messages
85         if status == STATUS.NOT_INITIALIZED:
86             return "Operation on uninitialized object impossible."
87         return unicode(Status._status2str(status))
88
89 STATUS = Status(['SUCCESS',
90   'OUT_OF_MEMORY',
91   'READ_ONLY_DATABASE',
92   'XAPIAN_EXCEPTION',
93   'FILE_ERROR',
94   'FILE_NOT_EMAIL',
95   'DUPLICATE_MESSAGE_ID',
96   'NULL_POINTER',
97   'TAG_TOO_LONG',
98   'UNBALANCED_FREEZE_THAW',
99   'UNBALANCED_ATOMIC',
100   'NOT_INITIALIZED'])
101 """STATUS is a class, whose attributes provide constants that serve as return
102 indicators for notmuch functions. Currently the following ones are defined. For
103 possible return values and specific meaning for each method, see the method
104 description.
105
106   * SUCCESS
107   * OUT_OF_MEMORY
108   * READ_ONLY_DATABASE
109   * XAPIAN_EXCEPTION
110   * FILE_ERROR
111   * FILE_NOT_EMAIL
112   * DUPLICATE_MESSAGE_ID
113   * NULL_POINTER
114   * TAG_TOO_LONG
115   * UNBALANCED_FREEZE_THAW
116   * UNBALANCED_ATOMIC
117   * NOT_INITIALIZED
118
119 Invoke the class method `notmuch.STATUS.status2str` with a status value as
120 argument to receive a human readable string"""
121 STATUS.__name__ = 'STATUS'
122
123
124 class NotmuchError(Exception, Python3StringMixIn):
125     """Is initiated with a (notmuch.STATUS[, message=None]). It will not
126     return an instance of the class NotmuchError, but a derived instance
127     of a more specific Error Message, e.g. OutOfMemoryError. Each status
128     but SUCCESS has a corresponding subclassed Exception."""
129
130     @classmethod
131     def get_exc_subclass(cls, status):
132         """Returns a fine grained Exception() type,
133         detailing the error status"""
134         subclasses = {
135             STATUS.OUT_OF_MEMORY: OutOfMemoryError,
136             STATUS.READ_ONLY_DATABASE: ReadOnlyDatabaseError,
137             STATUS.XAPIAN_EXCEPTION: XapianError,
138             STATUS.FILE_ERROR: FileError,
139             STATUS.FILE_NOT_EMAIL: FileNotEmailError,
140             STATUS.DUPLICATE_MESSAGE_ID: DuplicateMessageIdError,
141             STATUS.NULL_POINTER: NullPointerError,
142             STATUS.TAG_TOO_LONG: TagTooLongError,
143             STATUS.UNBALANCED_FREEZE_THAW: UnbalancedFreezeThawError,
144             STATUS.UNBALANCED_ATOMIC: UnbalancedAtomicError,
145             STATUS.NOT_INITIALIZED: NotInitializedError,
146         }
147         assert 0 < status <= len(subclasses)
148         return subclasses[status]
149
150     def __new__(cls, *args, **kwargs):
151         """Return a correct subclass of NotmuchError if needed
152
153         We return a NotmuchError instance if status is None (or 0) and a
154         subclass that inherits from NotmuchError depending on the
155         'status' parameter otherwise."""
156         # get 'status'. Passed in as arg or kwarg?
157         status = args[0] if len(args) else kwargs.get('status', None)
158         # no 'status' or cls is subclass already, return 'cls' instance
159         if not status or cls != NotmuchError:
160             return super(NotmuchError, cls).__new__(cls)
161         subclass = cls.get_exc_subclass(status)  # which class to use?
162         return subclass.__new__(subclass, *args, **kwargs)
163
164     def __init__(self, status=None, message=None):
165         self.status = status
166         self.message = message
167
168     def __unicode__(self):
169         if self.message is not None:
170             return self.message
171         elif self.status is not None:
172             return STATUS.status2str(self.status)
173         else:
174             return 'Unknown error'
175
176
177 # List of Subclassed exceptions that correspond to STATUS values and are
178 # subclasses of NotmuchError.
179 class OutOfMemoryError(NotmuchError):
180     status = STATUS.OUT_OF_MEMORY
181
182
183 class ReadOnlyDatabaseError(NotmuchError):
184     status = STATUS.READ_ONLY_DATABASE
185
186
187 class XapianError(NotmuchError):
188     status = STATUS.XAPIAN_EXCEPTION
189
190
191 class FileError(NotmuchError):
192     status = STATUS.FILE_ERROR
193
194
195 class FileNotEmailError(NotmuchError):
196     status = STATUS.FILE_NOT_EMAIL
197
198
199 class DuplicateMessageIdError(NotmuchError):
200     status = STATUS.DUPLICATE_MESSAGE_ID
201
202
203 class NullPointerError(NotmuchError):
204     status = STATUS.NULL_POINTER
205
206
207 class TagTooLongError(NotmuchError):
208     status = STATUS.TAG_TOO_LONG
209
210
211 class UnbalancedFreezeThawError(NotmuchError):
212     status = STATUS.UNBALANCED_FREEZE_THAW
213
214
215 class UnbalancedAtomicError(NotmuchError):
216     status = STATUS.UNBALANCED_ATOMIC
217
218
219 class NotInitializedError(NotmuchError):
220     """Derived from NotmuchError, this occurs if the underlying data
221     structure (e.g. database is not initialized (yet) or an iterator has
222     been exhausted. You can test for NotmuchError with .status =
223     STATUS.NOT_INITIALIZED"""
224     status = STATUS.NOT_INITIALIZED
225
226
227 class NotmuchDatabaseS(Structure):
228     pass
229 NotmuchDatabaseP = POINTER(NotmuchDatabaseS)
230
231
232 class NotmuchQueryS(Structure):
233     pass
234 NotmuchQueryP = POINTER(NotmuchQueryS)
235
236
237 class NotmuchThreadsS(Structure):
238     pass
239 NotmuchThreadsP = POINTER(NotmuchThreadsS)
240
241
242 class NotmuchThreadS(Structure):
243     pass
244 NotmuchThreadP = POINTER(NotmuchThreadS)
245
246
247 class NotmuchMessagesS(Structure):
248     pass
249 NotmuchMessagesP = POINTER(NotmuchMessagesS)
250
251
252 class NotmuchMessageS(Structure):
253     pass
254 NotmuchMessageP = POINTER(NotmuchMessageS)
255
256
257 class NotmuchTagsS(Structure):
258     pass
259 NotmuchTagsP = POINTER(NotmuchTagsS)
260
261
262 class NotmuchDirectoryS(Structure):
263     pass
264 NotmuchDirectoryP = POINTER(NotmuchDirectoryS)
265
266
267 class NotmuchFilenamesS(Structure):
268     pass
269 NotmuchFilenamesP = POINTER(NotmuchFilenamesS)