2 from ctypes import c_int, c_char_p, c_void_p, c_uint64
3 from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
5 from datetime import date
7 class Database(object):
8 """ Wrapper around a notmuch_database_t
10 Do note that as soon as we tear down this object, all derived queries,
11 threads, and messages will be freed as well.
13 MODE = Enum(['READ_ONLY','READ_WRITE'])
14 """Constants: Mode in which to open the database"""
17 """Class attribute of users default database"""
19 """notmuch_database_get_path (notmuch_database_t *database)"""
20 _get_path = nmlib.notmuch_database_get_path
21 _get_path.restype = c_char_p
23 """notmuch_database_open (const char *path, notmuch_database_mode_t mode)"""
24 _open = nmlib.notmuch_database_open
25 _open.restype = c_void_p
27 """ notmuch_database_find_message """
28 _find_message = nmlib.notmuch_database_find_message
29 _find_message.restype = c_void_p
31 """notmuch_database_get_all_tags (notmuch_database_t *database)"""
32 _get_all_tags = nmlib.notmuch_database_get_all_tags
33 _get_all_tags.restype = c_void_p
35 """ notmuch_database_create(const char *path):"""
36 _create = nmlib.notmuch_database_create
37 _create.restype = c_void_p
39 def __init__(self, path=None, create=False, status= MODE_READ_ONLY):
40 """ Open or create a notmuch database
42 If path is None, we will try to read a users notmuch configuration and
43 use his default database.
44 Throws a NotmuchError in case of failure.
48 # no path specified. use a user's default database
49 if Database._std_db_path is None:
50 #the following line throws a NotmuchError if it fails
51 Database._std_db_path = self._get_user_default_db()
52 path = Database._std_db_path
55 self.open(path, status)
57 self.create(path, status)
59 def create(self, path, status=MODE_READ_ONLY):
60 """ notmuch_database_create(const char *path)
62 :returns: Raises :exc:`notmuch.NotmuchError` in case
63 of any failure (after printing an error message on stderr).
65 res = Database._create(path, status)
69 message="Could not create the specified database")
72 def open(self, path, status= MODE_READ_ONLY):
73 """calls notmuch_database_open
75 :returns: Raises :exc:`notmuch.NotmuchError` in case
76 of any failure (after printing an error message on stderr).
78 res = Database._open(path, status)
82 message="Could not open the specified database")
86 """notmuch_database_get_path (notmuch_database_t *database); """
87 return Database._get_path(self._db)
89 def find_message(self, msgid):
90 """notmuch_database_find_message
91 :param msgid: The message id
94 :returns: Message() or None if no message is found or if an
95 out-of-memory situation occurs.
98 raise NotmuchError(STATUS.NOT_INITIALIZED)
99 msg_p = Database._find_message(self._db, msgid)
102 return Message(msg_p, self)
104 def get_all_tags(self):
105 """Return a Tags() object (list of all tags found in the database)
107 :returns: Tags() object or raises :exc:`NotmuchError` with
108 STATUS.NULL_POINTER on error
111 raise NotmuchError(STATUS.NOT_INITIALIZED)
113 tags_p = Database._get_all_tags (self._db)
115 raise NotmuchError(STATUS.NULL_POINTER)
116 return Tags(tags_p, self)
119 return "'Notmuch DB " + self.get_path() + "'"
122 """Close and free the notmuch database if needed"""
123 if self._db is not None:
124 logging.debug("Freeing the database now")
125 nmlib.notmuch_database_close(self._db)
127 def _get_user_default_db(self):
128 """ Reads a user's notmuch config and returns his db location
130 Throws a NotmuchError if it cannot find it"""
131 from ConfigParser import SafeConfigParser
133 config = SafeConfigParser()
134 config.read(os.path.expanduser('~/.notmuch-config'))
135 if not config.has_option('database','path'):
136 raise NotmuchError(message=
137 "No DB path specified and no user default found")
138 return config.get('database','path')
142 """Returns a pointer to the current notmuch_database_t or None"""
145 #------------------------------------------------------------------------------
147 """ Wrapper around a notmuch_query_t
149 Do note that as soon as we tear down this object, all derived
150 threads, and messages will be freed as well.
153 SORT_OLDEST_FIRST = 0
154 SORT_NEWEST_FIRST = 1
157 """notmuch_query_create"""
158 _create = nmlib.notmuch_query_create
159 _create.restype = c_void_p
161 """notmuch_query_search_messages"""
162 _search_messages = nmlib.notmuch_query_search_messages
163 _search_messages.restype = c_void_p
165 def __init__(self, db, querystr):
169 self.create(db, querystr)
171 def create(self, db, querystr):
172 """db is a Database() and querystr a string
174 raises NotmuchError STATUS.NOT_INITIALIZED if db is not inited and
175 STATUS.NULL_POINTER if the query creation failed (too little memory)
178 raise NotmuchError(STATUS.NOT_INITIALIZED)
179 # create reference to parent db to keep it alive
182 # create query, return None if too little mem available
183 query_p = Query._create(db.db_p, querystr)
185 NotmuchError(STATUS.NULL_POINTER)
186 self._query = query_p
188 def set_sort(self, sort):
189 """notmuch_query_set_sort
191 :param sort: one of Query.SORT_OLDEST_FIRST|SORT_NEWEST_FIRST|SORT_MESSAGE_ID
192 :returns: Nothing, but raises NotmuchError if query is not inited
194 if self._query is None:
195 raise NotmuchError(STATUS.NOT_INITIALIZED)
197 nmlib.notmuch_query_set_sort(self._query, sort)
199 def search_messages(self):
200 """notmuch_query_search_messages
201 Returns Messages() or a raises a NotmuchError()
203 if self._query is None:
204 raise NotmuchError(STATUS.NOT_INITIALIZED)
206 msgs_p = Query._search_messages(self._query)
209 NotmuchError(STATUS.NULL_POINTER)
211 return Messages(msgs_p,self)
215 """Close and free the Query"""
216 if self._query is not None:
217 logging.debug("Freeing the Query now")
218 nmlib.notmuch_query_destroy (self._query)
220 #------------------------------------------------------------------------------
222 """Wrapper around notmuch_tags_t"""
225 _get = nmlib.notmuch_tags_get
226 _get.restype = c_char_p
228 def __init__(self, tags_p, parent=None):
230 msg_p is a pointer to an notmuch_message_t Structure. If it is None,
231 we will raise an NotmuchError(STATUS.NULL_POINTER).
233 Is passed the parent these tags are derived from, and saves a
234 reference to it, so we can automatically delete the db object
235 once all derived objects are dead.
237 Tags() provides an iterator over all contained tags. However, you will
238 only be able to iterate over the Tags once, because the underlying C
239 function only allows iterating once.
240 #TODO: make the iterator work more than once and cache the tags in
244 NotmuchError(STATUS.NULL_POINTER)
247 #save reference to parent object so we keep it alive
248 self._parent = parent
249 logging.debug("Inited Tags derived from %s" %(repr(parent)))
252 """ Make Tags an iterator """
256 if self._tags is None:
257 raise NotmuchError(STATUS.NOT_INITIALIZED)
259 if not nmlib.notmuch_tags_valid(self._tags):
263 tag = Tags._get (self._tags)
264 nmlib.notmuch_tags_move_to_next(self._tags)
268 """str() of Tags() is a space separated list of tags
270 This iterates over the list of Tags and will therefore 'exhaust' Tags()
272 return " ".join(self)
275 """Close and free the notmuch tags"""
276 if self._tags is not None:
277 logging.debug("Freeing the Tags now")
278 nmlib.notmuch_tags_destroy (self._tags)
281 #------------------------------------------------------------------------------
282 class Messages(object):
283 """Wrapper around notmuch_messages_t"""
286 _get = nmlib.notmuch_messages_get
287 _get.restype = c_void_p
289 _collect_tags = nmlib.notmuch_messages_collect_tags
290 _collect_tags.restype = c_void_p
292 def __init__(self, msgs_p, parent=None):
294 msg_p is a pointer to an notmuch_messages_t Structure. If it is None,
295 we will raise an NotmuchError(STATUS.NULL_POINTER).
297 If passed the parent query this Messages() is derived from, it saves a
298 reference to it, so we can automatically delete the parent query object
299 once all derived objects are dead.
301 Messages() provides an iterator over all contained Message()s.
302 However, you will only be able to iterate over it once,
303 because the underlying C function only allows iterating once.
304 #TODO: make the iterator work more than once and cache the tags in
308 NotmuchError(STATUS.NULL_POINTER)
311 #store parent, so we keep them alive as long as self is alive
312 self._parent = parent
313 logging.debug("Inited Messages derived from %s" %(str(parent)))
315 def collect_tags(self):
316 """ return the Tags() belonging to the messages
318 Do note that collect_tags will iterate over the messages and
319 therefore will not allow further iterationsl
320 Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
322 if self._msgs is None:
323 raise NotmuchError(STATUS.NOT_INITIALIZED)
325 # collect all tags (returns NULL on error)
326 tags_p = Messages._collect_tags (self._msgs)
327 #reset _msgs as we iterated over it and can do so only once
331 raise NotmuchError(STATUS.NULL_POINTER)
332 return Tags(tags_p, self)
335 """ Make Messages an iterator """
339 if self._msgs is None:
340 raise NotmuchError(STATUS.NOT_INITIALIZED)
342 if not nmlib.notmuch_messages_valid(self._msgs):
346 msg = Message(Messages._get (self._msgs), self)
347 nmlib.notmuch_messages_move_to_next(self._msgs)
351 """ Returns the number of contained messages
353 :note: As this iterates over the messages, we will not be able to
354 iterate over them again (as in retrieve them)!
356 if self._msgs is None:
357 raise NotmuchError(STATUS.NOT_INITIALIZED)
360 while nmlib.notmuch_messages_valid(self._msgs):
361 nmlib.notmuch_messages_move_to_next(self._msgs)
369 """Close and free the notmuch Messages"""
370 if self._msgs is not None:
371 logging.debug("Freeing the Messages now")
372 nmlib.notmuch_messages_destroy (self._msgs)
375 #------------------------------------------------------------------------------
376 class Message(object):
377 """Wrapper around notmuch_message_t"""
379 """notmuch_message_get_filename (notmuch_message_t *message)"""
380 _get_filename = nmlib.notmuch_message_get_filename
381 _get_filename.restype = c_char_p
382 """notmuch_message_get_message_id (notmuch_message_t *message)"""
383 _get_message_id = nmlib.notmuch_message_get_message_id
384 _get_message_id.restype = c_char_p
386 """notmuch_message_get_tags (notmuch_message_t *message)"""
387 _get_tags = nmlib.notmuch_message_get_tags
388 _get_tags.restype = c_void_p
390 _get_date = nmlib.notmuch_message_get_date
391 _get_date.restype = c_uint64
393 _get_header = nmlib.notmuch_message_get_header
394 _get_header.restype = c_char_p
396 def __init__(self, msg_p, parent=None):
398 msg_p is a pointer to an notmuch_message_t Structure. If it is None,
399 we will raise an NotmuchError(STATUS.NULL_POINTER).
401 Is a 'parent' object is passed which this message is derived from,
402 we save a reference to it, so we can automatically delete the parent
403 object once all derived objects are dead.
406 NotmuchError(STATUS.NULL_POINTER)
408 #keep reference to parent, so we keep it alive
409 self._parent = parent
410 logging.debug("Inited Message derived from %s" %(str(parent)))
413 def get_message_id(self):
414 """ return the msg id
416 Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
418 if self._msg is None:
419 raise NotmuchError(STATUS.NOT_INITIALIZED)
420 return Message._get_message_id(self._msg)
423 """returns time_t of the message date
425 For the original textual representation of the Date header from the
426 message call notmuch_message_get_header() with a header value of
428 Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
430 if self._msg is None:
431 raise NotmuchError(STATUS.NOT_INITIALIZED)
432 return Message._get_date(self._msg)
434 def get_header(self, header):
435 """ TODO document me"""
436 if self._msg is None:
437 raise NotmuchError(STATUS.NOT_INITIALIZED)
439 #Returns NULL if any error occurs.
440 header = Message._get_header (self._msg, header)
442 raise NotmuchError(STATUS.NULL_POINTER)
445 def get_filename(self):
446 """ return the msg filename
448 Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
450 if self._msg is None:
451 raise NotmuchError(STATUS.NOT_INITIALIZED)
452 return Message._get_filename(self._msg)
455 """ return the msg tags
457 Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
458 Raises NotmuchError(STATUS.NULL_POINTER) on error.
460 if self._msg is None:
461 raise NotmuchError(STATUS.NOT_INITIALIZED)
463 tags_p = Message._get_tags(self._msg)
465 raise NotmuchError(STATUS.NULL_POINTER)
466 return Tags(tags_p, self)
469 """A message() is represented by a 1-line summary"""
471 msg['from'] = self.get_header('from')
472 msg['tags'] = str(self.get_tags())
473 msg['date'] = date.fromtimestamp(self.get_date())
474 return "%(from)s (%(date)s) (%(tags)s)" % (msg)
476 def format_as_text(self):
477 """ Output like notmuch show """
481 """Close and free the notmuch Message"""
482 if self._msg is not None:
483 logging.debug("Freeing the Message now")
484 nmlib.notmuch_message_destroy (self._msg)