50c182bd5f4be69821f85f5e1fa85d1dd015b05e
[notmuch] / cnotmuch / database.py
1 import ctypes
2 from ctypes import c_int, c_char_p, c_void_p
3 from cnotmuch.globals import nmlib, STATUS, NotmuchError
4
5
6 class Database(object):
7     """ Wrapper around a notmuch_database_t
8
9     Do note that as soon as we tear down this object, all derived queries,
10     threads, and messages will be freed as well.
11     """
12     #constants
13     MODE_READ_ONLY = 0
14     MODE_READ_WRITE = 1
15
16     _std_db_path = None
17     """Class attribute of users default database"""
18
19     """notmuch_database_get_path (notmuch_database_t *database)"""
20     _get_path = nmlib.notmuch_database_get_path
21     _get_path.restype = c_char_p
22
23     """notmuch_database_open (const char *path, notmuch_database_mode_t mode)"""
24     _open = nmlib.notmuch_database_open 
25     _open.restype = c_void_p
26
27     """ notmuch_database_find_message """
28     _find_message = nmlib.notmuch_database_find_message
29     _find_message.restype = c_void_p
30
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
34
35     """ notmuch_database_create(const char *path):"""
36     _create = nmlib.notmuch_database_create
37     _create.restype = c_void_p
38
39     def __init__(self, path=None, create=False, status= MODE_READ_ONLY):
40         """ Open or create a notmuch database
41
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.
45         """
46         self._db = None
47         if path is None:
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
53
54         if create == False:
55             self.open(path, status)
56         else:
57             self.create(path, status)
58
59     def create(self, path, status=MODE_READ_ONLY):
60         """ notmuch_database_create(const char *path)
61
62         :returns: Raises :exc:`notmuch.NotmuchError` in case
63                   of any failure (after printing an error message on stderr).
64         """
65         res = Database._create(path, status)
66
67         if res is None:
68             raise NotmuchError(
69                 message="Could not create the specified database")
70         self._db = res
71
72     def open(self, path, status= MODE_READ_ONLY): 
73         """calls notmuch_database_open
74
75         :returns: Raises :exc:`notmuch.NotmuchError` in case
76                   of any failure (after printing an error message on stderr).
77         """
78         res = Database._open(path, status)
79
80         if res is None:
81             raise NotmuchError(
82                 message="Could not open the specified database")
83         self._db = res
84
85     def get_path(self):
86         """notmuch_database_get_path (notmuch_database_t *database);  """
87         return Database._get_path(self._db)
88
89     def find_message(self, msgid):
90         """notmuch_database_find_message
91         :param msgid: The message id
92         :ptype msgid: string
93
94         :returns: Message() or None if no message is found or if an
95                   out-of-memory situation occurs.
96         """
97         if self._db is None:
98             raise NotmuchError(STATUS.NOT_INITIALIZED)
99         msg_p = Database._find_message(self._db, msgid)
100         if msg_p is None:
101             return None
102         return Message(msg_p, self)
103
104     def get_all_tags(self):
105         """Return a Tags() object (list of all tags found in the database)
106
107         :returns: Tags() object or raises :exc:`NotmuchError` with 
108                   STATUS.NULL_POINTER on error
109         """
110         if self._db is None:
111             raise NotmuchError(STATUS.NOT_INITIALIZED)
112
113         tags_p = Database._get_all_tags (self._db)
114         if tags_p == None:
115             raise NotmuchError(STATUS.NULL_POINTER)
116         return Tags(tags_p, self)
117
118     def __repr__(self):
119         return "'Notmuch DB " + self.get_path() + "'"
120
121     def __del__(self):
122         """Close and free the notmuch database if needed"""
123         if self._db is not None:
124             print("Freeing the database now")
125             nmlib.notmuch_database_close(self._db)
126
127     def _get_user_default_db(self):
128         """ Reads a user's notmuch config and returns his db location
129
130         Throws a NotmuchError if it cannot find it"""
131         from ConfigParser import SafeConfigParser
132         import os.path
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')
139
140     @property
141     def db_p(self):
142         """Returns a pointer to the current notmuch_database_t or None"""
143         return self._db
144
145 #------------------------------------------------------------------------------
146 class Query(object):
147     """ Wrapper around a notmuch_query_t
148
149     Do note that as soon as we tear down this object, all derived
150     threads, and messages will be freed as well.
151     """
152     def __init__(self, db, querystr):
153         pass
154
155 #------------------------------------------------------------------------------
156 class Tags(object):
157     """Wrapper around notmuch_tags_t"""
158
159     #notmuch_tags_get
160     _get = nmlib.notmuch_tags_get
161     _get.restype = c_char_p
162
163     def __init__(self, tags_p, db=None):
164         """
165         msg_p is a pointer to an notmuch_message_t Structure. If it is None,
166         we will raise an NotmuchError(STATUS.NULL_POINTER).
167
168         Is passed the db these tags are derived from, and saves a
169         reference to it, so we can automatically delete the db object
170         once all derived objects are dead.
171
172         Tags() provides an iterator over all contained tags. However, you will
173         only be able to iterate over the Tags once, because the underlying C
174         function only allows iterating once.
175         #TODO: make the iterator work more than once and cache the tags in 
176                the Python object.
177         """
178         if tags_p is None:
179             NotmuchError(STATUS.NULL_POINTER)
180
181         self._tags = tags_p
182         self._db = db
183         print "Inited Tags derived from %s" %(str(db))
184     
185     def __iter__(self):
186         """ Make Tags an iterator """
187         return self
188
189     def next(self):
190         if self._tags is None:
191             raise StopIteration
192         nmlib.notmuch_tags_move_to_next(self._tags)
193         if not nmlib.notmuch_tags_valid(self._tags):
194             print("Freeing the Tags now")
195             nmlib.notmuch_tags_destroy (self._tags)
196             raise StopIteration
197         return Tags._get (self._tags)
198
199     def __del__(self):
200         """Close and free the notmuch tags"""
201         if self._tags is not None:
202             print("Freeing the Tags now")
203             nmlib.notmuch_tags_destroy (self._tags)
204
205 #------------------------------------------------------------------------------
206 class Message(object):
207     """Wrapper around notmuch_message_t"""
208
209     """notmuch_message_get_filename (notmuch_message_t *message)"""
210     _get_filename = nmlib.notmuch_message_get_filename
211     _get_filename.restype = c_char_p 
212     """notmuch_message_get_message_id (notmuch_message_t *message)"""
213     _get_message_id = nmlib.notmuch_message_get_message_id
214     _get_message_id.restype = c_char_p 
215
216     """notmuch_message_get_tags (notmuch_message_t *message)"""
217     _get_tags = nmlib.notmuch_message_get_tags
218     _get_tags.restype = c_void_p
219
220     def __init__(self, msg_p, parent=None):
221         """
222         msg_p is a pointer to an notmuch_message_t Structure. If it is None,
223         we will raise an NotmuchError(STATUS.NULL_POINTER).
224
225         Is a 'parent' object is passed which this message is derived from,
226         we save a reference to it, so we can automatically delete the parent
227         object once all derived objects are dead.
228         """
229         if msg_p is None:
230             NotmuchError(STATUS.NULL_POINTER)
231         self._msg = msg_p
232         self._parent = parent
233         print "Inited Message derived from %s" %(str(parent))
234
235
236     def get_message_id(self):
237         """ return the msg id
238         
239         Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
240         """
241         if self._msg is None:
242             raise NotmuchError(STATUS.NOT_INITIALIZED)
243         return Message._get_message_id(self._msg)
244
245
246     def get_filename(self):
247         """ return the msg filename
248         
249         Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
250         """
251         if self._msg is None:
252             raise NotmuchError(STATUS.NOT_INITIALIZED)
253         return Message._get_filename(self._msg)
254
255     def get_tags(self):
256         """ return the msg tags
257         
258         Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
259         Raises NotmuchError(STATUS.NULL_POINTER) on error.
260         """
261         if self._msg is None:
262             raise NotmuchError(STATUS.NOT_INITIALIZED)
263
264         tags_p = Message._get_tags(self._msg)
265         if tags_p == None:
266             raise NotmuchError(STATUS.NULL_POINTER)
267         return Tags(tags_p, self)
268
269     def __del__(self):
270         """Close and free the notmuch Message"""
271         if self._msg is not None:
272             print("Freeing the Message now")
273             nmlib.notmuch_message_destroy (self._msg)