90c220a14b93a0c5bafaf5fd807645fd782bb2ec
[notmuch] / bindings / ruby / database.c
1 /* The Ruby interface to the notmuch mail library
2  *
3  * Copyright © 2010, 2011 Ali Polatel
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: Ali Polatel <alip@exherbo.org>
19  */
20
21 #include "defs.h"
22
23 VALUE
24 notmuch_rb_database_alloc(VALUE klass)
25 {
26     return Data_Wrap_Struct(klass, NULL, NULL, NULL);
27 }
28
29 /*
30  * call-seq: Notmuch::Database.new(path [, {:create => false, :mode => Notmuch::MODE_READ_ONLY}]) => DB
31  *
32  * Create or open a notmuch database using the given path.
33  *
34  * If :create is +true+, create the database instead of opening.
35  *
36  * The argument :mode specifies the open mode of the database.
37  */
38 VALUE
39 notmuch_rb_database_initialize(int argc, VALUE *argv, VALUE self)
40 {
41     const char *path;
42     int create, mode;
43     VALUE pathv, hashv;
44     VALUE modev;
45
46 #if !defined(RSTRING_PTR)
47 #define RSTRING_PTR(v) (RSTRING((v))->ptr)
48 #endif /* !defined(RSTRING_PTR) */
49
50     /* Check arguments */
51     rb_scan_args(argc, argv, "11", &pathv, &hashv);
52
53     SafeStringValue(pathv);
54     path = RSTRING_PTR(pathv);
55
56     if (!NIL_P(hashv)) {
57         Check_Type(hashv, T_HASH);
58         create = RTEST(rb_hash_aref(hashv, ID2SYM(ID_db_create)));
59         modev = rb_hash_aref(hashv, ID2SYM(ID_db_mode));
60         if (NIL_P(modev))
61             mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
62         else if (!FIXNUM_P(modev))
63             rb_raise(rb_eTypeError, ":mode isn't a Fixnum");
64         else {
65             mode = FIX2INT(modev);
66             switch (mode) {
67             case NOTMUCH_DATABASE_MODE_READ_ONLY:
68             case NOTMUCH_DATABASE_MODE_READ_WRITE:
69                 break;
70             default:
71                 rb_raise(rb_eTypeError, "Invalid mode");
72             }
73         }
74     }
75     else {
76         create = 0;
77         mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
78     }
79
80     Check_Type(self, T_DATA);
81     DATA_PTR(self) = create ? notmuch_database_create(path) : notmuch_database_open(path, mode);
82     if (!DATA_PTR(self))
83         rb_raise(notmuch_rb_eDatabaseError, "Failed to open database");
84
85     return self;
86 }
87
88 /*
89  * call-seq: Notmuch::Database.open(path [, ahash]) {|db| ...}
90  *
91  * Identical to new, except that when it is called with a block, it yields with
92  * the new instance and closes it, and returns the result which is returned from
93  * the block.
94  */
95 VALUE
96 notmuch_rb_database_open(int argc, VALUE *argv, VALUE klass)
97 {
98     VALUE obj;
99
100     obj = rb_class_new_instance(argc, argv, klass);
101     if (!rb_block_given_p())
102         return obj;
103
104     return rb_ensure(rb_yield, obj, notmuch_rb_database_close, obj);
105 }
106
107 /*
108  * call-seq: DB.close => nil
109  *
110  * Close the notmuch database.
111  */
112 VALUE
113 notmuch_rb_database_close(VALUE self)
114 {
115     notmuch_database_t *db;
116
117     Data_Get_Notmuch_Database(self, db);
118     notmuch_database_close(db);
119     DATA_PTR(self) = NULL;
120
121     return Qnil;
122 }
123
124 /*
125  * call-seq: DB.path => String
126  *
127  * Return the path of the database
128  */
129 VALUE
130 notmuch_rb_database_path(VALUE self)
131 {
132     notmuch_database_t *db;
133
134     Data_Get_Notmuch_Database(self, db);
135
136     return rb_str_new2(notmuch_database_get_path(db));
137 }
138
139 /*
140  * call-seq: DB.version => Fixnum
141  *
142  * Return the version of the database
143  */
144 VALUE
145 notmuch_rb_database_version(VALUE self)
146 {
147     notmuch_database_t *db;
148
149     Data_Get_Notmuch_Database(self, db);
150
151     return INT2FIX(notmuch_database_get_version(db));
152 }
153
154 /*
155  * call-seq: DB.needs_upgrade? => true or false
156  *
157  * Return the +true+ if the database needs upgrading, +false+ otherwise
158  */
159 VALUE
160 notmuch_rb_database_needs_upgrade(VALUE self)
161 {
162     notmuch_database_t *db;
163
164     Data_Get_Notmuch_Database(self, db);
165
166     return notmuch_database_needs_upgrade(db) ? Qtrue : Qfalse;
167 }
168
169 static void
170 notmuch_rb_upgrade_notify(void *closure, double progress)
171 {
172     VALUE *block = (VALUE *)closure;
173     rb_funcall(*block, ID_call, 1, rb_float_new(progress));
174 }
175
176 /*
177  * call-seq: DB.upgrade! [{|progress| block }] => nil
178  *
179  * Upgrade the database.
180  *
181  * If a block is given the block is called with a progress indicator as a
182  * floating point value in the range of [0.0..1.0].
183  */
184 VALUE
185 notmuch_rb_database_upgrade(VALUE self)
186 {
187     notmuch_status_t ret;
188     void (*pnotify) (void *closure, double progress);
189     notmuch_database_t *db;
190     VALUE block;
191
192     Data_Get_Notmuch_Database(self, db);
193
194     if (rb_block_given_p()) {
195         pnotify = notmuch_rb_upgrade_notify;
196         block = rb_block_proc();
197     }
198     else
199         pnotify = NULL;
200
201     ret = notmuch_database_upgrade(db, pnotify, pnotify ? &block : NULL);
202     notmuch_rb_status_raise(ret);
203
204     return Qtrue;
205 }
206
207 /*
208  * call-seq: DB.begin_atomic => nil
209  *
210  * Begin an atomic database operation.
211  */
212 VALUE
213 notmuch_rb_database_begin_atomic(VALUE self)
214 {
215     notmuch_status_t ret;
216     notmuch_database_t *db;
217
218     Data_Get_Notmuch_Database(self, db);
219
220     ret = notmuch_database_begin_atomic(db);
221     notmuch_rb_status_raise(ret);
222
223     return Qtrue;
224 }
225
226 /*
227  * call-seq: DB.end_atomic => nil
228  *
229  * Indicate the end of an atomic database operation.
230  */
231 VALUE
232 notmuch_rb_database_end_atomic(VALUE self)
233 {
234     notmuch_status_t ret;
235     notmuch_database_t *db;
236
237     Data_Get_Notmuch_Database(self, db);
238
239     ret = notmuch_database_end_atomic(db);
240     notmuch_rb_status_raise(ret);
241
242     return Qtrue;
243 }
244
245 /*
246  * call-seq: DB.get_directory(path) => DIR
247  *
248  * Retrieve a directory object from the database for 'path'
249  */
250 VALUE
251 notmuch_rb_database_get_directory(VALUE self, VALUE pathv)
252 {
253     const char *path;
254     notmuch_directory_t *dir;
255     notmuch_database_t *db;
256
257     Data_Get_Notmuch_Database(self, db);
258
259 #if !defined(RSTRING_PTR)
260 #define RSTRING_PTR(v) (RSTRING((v))->ptr)
261 #endif /* !defined(RSTRING_PTR) */
262
263     SafeStringValue(pathv);
264     path = RSTRING_PTR(pathv);
265
266     dir = notmuch_database_get_directory(db, path);
267     if (!dir)
268         rb_raise(notmuch_rb_eXapianError, "Xapian exception");
269
270     return Data_Wrap_Struct(notmuch_rb_cDirectory, NULL, NULL, dir);
271 }
272
273 /*
274  * call-seq: DB.add_message(path) => MESSAGE, isdup
275  *
276  * Add a message to the database and return it.
277  *
278  * +isdup+ is a boolean that specifies whether the added message was a
279  * duplicate.
280  */
281 VALUE
282 notmuch_rb_database_add_message(VALUE self, VALUE pathv)
283 {
284     const char *path;
285     notmuch_status_t ret;
286     notmuch_message_t *message;
287     notmuch_database_t *db;
288
289     Data_Get_Notmuch_Database(self, db);
290
291 #if !defined(RSTRING_PTR)
292 #define RSTRING_PTR(v) (RSTRING((v))->ptr)
293 #endif /* !defined(RSTRING_PTR) */
294
295     SafeStringValue(pathv);
296     path = RSTRING_PTR(pathv);
297
298     ret = notmuch_database_add_message(db, path, &message);
299     notmuch_rb_status_raise(ret);
300     return rb_assoc_new(Data_Wrap_Struct(notmuch_rb_cMessage, NULL, NULL, message),
301         (ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) ? Qtrue : Qfalse);
302 }
303
304 /*
305  * call-seq: DB.remove_message(path) => isdup
306  *
307  * Remove a message from the database.
308  *
309  * +isdup+ is a boolean that specifies whether the removed message was a
310  * duplicate.
311  */
312 VALUE
313 notmuch_rb_database_remove_message(VALUE self, VALUE pathv)
314 {
315     const char *path;
316     notmuch_status_t ret;
317     notmuch_database_t *db;
318
319     Data_Get_Notmuch_Database(self, db);
320
321 #if !defined(RSTRING_PTR)
322 #define RSTRING_PTR(v) (RSTRING((v))->ptr)
323 #endif /* !defined(RSTRING_PTR) */
324
325     SafeStringValue(pathv);
326     path = RSTRING_PTR(pathv);
327
328     ret = notmuch_database_remove_message(db, path);
329     notmuch_rb_status_raise(ret);
330     return (ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) ? Qtrue : Qfalse;
331 }
332
333 /*
334  * call-seq: DB.find_message(id) => MESSAGE or nil
335  *
336  * Find a message by message id.
337  */
338 VALUE
339 notmuch_rb_database_find_message(VALUE self, VALUE idv)
340 {
341     const char *id;
342     notmuch_status_t ret;
343     notmuch_database_t *db;
344     notmuch_message_t *message;
345
346     Data_Get_Notmuch_Database(self, db);
347
348 #if !defined(RSTRING_PTR)
349 #define RSTRING_PTR(v) (RSTRING((v))->ptr)
350 #endif /* !defined(RSTRING_PTR) */
351
352     SafeStringValue(idv);
353     id = RSTRING_PTR(idv);
354
355     ret = notmuch_database_find_message(db, id, &message);
356     notmuch_rb_status_raise(ret);
357
358     if (message)
359         return Data_Wrap_Struct(notmuch_rb_cMessage, NULL, NULL, message);
360     return Qnil;
361 }
362
363 /*
364  * call-seq: DB.find_message_by_filename(path) => MESSAGE or nil
365  *
366  * Find a message by filename.
367  */
368 VALUE
369 notmuch_rb_database_find_message_by_filename(VALUE self, VALUE pathv)
370 {
371     const char *path;
372     notmuch_status_t ret;
373     notmuch_database_t *db;
374     notmuch_message_t *message;
375
376     Data_Get_Notmuch_Database(self, db);
377
378 #if !defined(RSTRING_PTR)
379 #define RSTRING_PTR(v) (RSTRING((v))->ptr)
380 #endif /* !defined(RSTRING_PTR) */
381
382     SafeStringValue(pathv);
383     path = RSTRING_PTR(pathv);
384
385     ret = notmuch_database_find_message_by_filename(db, path, &message);
386     notmuch_rb_status_raise(ret);
387
388     if (message)
389         return Data_Wrap_Struct(notmuch_rb_cMessage, NULL, NULL, message);
390     return Qnil;
391 }
392
393 /*
394  * call-seq: DB.query(query) => QUERY
395  *
396  * Retrieve a query object for the query string 'query'
397  */
398 VALUE
399 notmuch_rb_database_query_create(VALUE self, VALUE qstrv)
400 {
401     const char *qstr;
402     notmuch_query_t *query;
403     notmuch_database_t *db;
404
405     Data_Get_Notmuch_Database(self, db);
406
407 #if !defined(RSTRING_PTR)
408 #define RSTRING_PTR(v) (RSTRING((v))->ptr)
409 #endif /* !defined(RSTRING_PTR) */
410
411     SafeStringValue(qstrv);
412     qstr = RSTRING_PTR(qstrv);
413
414     query = notmuch_query_create(db, qstr);
415     if (!query)
416         rb_raise(notmuch_rb_eMemoryError, "Out of memory");
417
418     return Data_Wrap_Struct(notmuch_rb_cQuery, NULL, NULL, query);
419 }