[PATCH 3/3] lib: add built_with handling for XAPIAN_DB_RETRY_LOCK
[notmuch-archives.git] / 44 / 93c255737b39b2606b46681b2c144ff3435b50
1 Return-Path: <aperez@igalia.com>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id D052E431FBC\r
6         for <notmuch@notmuchmail.org>; Wed, 30 Dec 2009 02:51:18 -0800 (PST)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 Received: from olra.theworths.org ([127.0.0.1])\r
9         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
10         with ESMTP id qAbyH4mrVSfT for <notmuch@notmuchmail.org>;\r
11         Wed, 30 Dec 2009 02:51:16 -0800 (PST)\r
12 Received: from alice.connectical.com (alice.connectical.com [208.89.208.235])\r
13         by olra.theworths.org (Postfix) with ESMTP id A2D76431FAE\r
14         for <notmuch@notmuchmail.org>; Wed, 30 Dec 2009 02:51:16 -0800 (PST)\r
15 Received: (qmail 20357 invoked from network); 30 Dec 2009 10:51:07 -0000\r
16 Received: from 97.126.60.213.dynamic.mundo-r.com (HELO hikari.localdomain)\r
17         (aperez@213.60.126.97)\r
18         by alice.connectical.com with ESMTPA; 30 Dec 2009 10:51:07 -0000\r
19 Received: from hikari (localhost [127.0.0.1])\r
20         by hikari.localdomain (Postfix) with ESMTP id 8360330A5D47\r
21         for <notmuch@notmuchmail.org>; Wed, 30 Dec 2009 11:52:28 +0100 (CET)\r
22 Date: Wed, 30 Dec 2009 11:52:23 +0100\r
23 From: Adrian Perez de Castro <aperez@igalia.com>\r
24 To: notmuch@notmuchmail.org\r
25 Message-ID: <20091230115223.1b3472a1@hikari>\r
26 In-Reply-To: <1262078148-sup-7891@ben-laptop>\r
27 References: <1262078148-sup-7891@ben-laptop>\r
28 Organization: Igalia\r
29 X-Mailer: Claws Mail 3.7.3 (GTK+ 2.19.2; x86_64-redhat-linux-gnu)\r
30 Face: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAAXNSR0IArs4c6QAAADBQTFRFBwcHFhYWKCgoNzc3SEhIV1dXaGhod3d3iIiIlpaWqKiouLi4x8fH2NjY5+fn/v7+rSjDkgAAAjVJREFUOE9l07tvE0EQwOHfrkV9O+eko7g701BBfECJsIigT2IpooIqaSiRUEB0REj00FBQgYSCkhry+gecUPJybJeIxLumTbilsH2PMNXufDOa3ZVW+1JkpbUmD/8+vXR3c7or4Gz93mH309Kz8/C9/RQge7VfhW/LW+PF8IkrQ7Z6OKmQr1tl+LU/yWP9mxJka9O88fZHPwf/7u0kLyCnX3I4fQhgjAgIfi+HHw5A1Y2ggIMcFKAEnRoL0M3BosI4TI2IATjuT8DvSNJoNNJgkIhxlr9TUHeSpDnfohlIrMBlU+BGmsZqfr69FMfGMw4NoG835+J62riWyjQ/uXlTQjNUIoYegMsBM0pCD8oDas7n4HQsBghXFxJTW42KDs+4XLfjsN0wOYgABqARjMKIHIaAQnmHjsI5Cvi9Cf6k03OoWBkpIP3Q7354+dEimFBKHbMP9oKjwfd9gbrxR5KDToczK4uPF8UgNomKU2GaENRi77zyDKICxKBS4xXYbONPMQMdYZTBwMiMWiUg9g6UJ3OBogzjV8E7sBVwyvfAOYdQhsABzuOxI1MGZbs98Q6Md5UOfbbR2R0eWOesrnRw5ajT6f60LrNhWIHZpBnUWv2s14ukArWWTqTes3YQxRXgFkcMu70TPYqqUBs0YwmO967OVIdTG4bY4a7WLaqgLm5vbHdH5np0Dri//fmg7y8scB4u3+zsuNlH0X+g19bby69b+TYH6isvns8VdQWgxj9tHP8AR5/hSdYqkwsAAAAASUVORK5CYII=\r
31 Mime-Version: 1.0\r
32 Content-Type: multipart/signed; micalg=PGP-SHA1;\r
33         boundary="Sig_/NJQn5hRW+avHgHHAlU7=Xa1";\r
34         protocol="application/pgp-signature"\r
35 Subject: Re: [notmuch] SWIG (and particularly Python) bindings\r
36 X-BeenThere: notmuch@notmuchmail.org\r
37 X-Mailman-Version: 2.1.12\r
38 Precedence: list\r
39 List-Id: "Use and development of the notmuch mail system."\r
40         <notmuch.notmuchmail.org>\r
41 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
42         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
43 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
44 List-Post: <mailto:notmuch@notmuchmail.org>\r
45 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
46 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
47         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
48 X-List-Received-Date: Wed, 30 Dec 2009 10:51:19 -0000\r
49 \r
50 --Sig_/NJQn5hRW+avHgHHAlU7=Xa1\r
51 Content-Type: multipart/mixed; boundary="MP_/Qt51GuWb3Bo1f8da6MLGH4N"\r
52 \r
53 --MP_/Qt51GuWb3Bo1f8da6MLGH4N\r
54 Content-Type: text/plain; charset=US-ASCII\r
55 Content-Transfer-Encoding: quoted-printable\r
56 Content-Disposition: inline\r
57 \r
58 On Tue, 29 Dec 2009 04:16:43 -0500, Ben wrote:\r
59 \r
60 > Regardless, I thought it might be nice to have access to the notmuch\r
61 > backend from a language other than C (preferably my high-level language\r
62 > of choice, python) [...]\r
63 \r
64 Funny, I was just doing the same: a Python binding. Haha, so now we have\r
65 two just-backed Python bindings. What should we do?\r
66 \r
67 > [...] To this end, I took a few hours today acquainting\r
68 > myself with SWIG and produced these bindings for Python. Unfortunately,\r
69 > it doesn't appear that SWIG has particularly good support for\r
70 > object-oriented C [...]\r
71 \r
72 I already used SWIG sometimes in the past (and did not like it a lot), so\r
73 my binding is using Cython [*] (which is exactly like Pyrex plus some extra\r
74 features), so the binding is partly manual.\r
75 \r
76 > While the bindings are currently in the form of a patch to notmuch\r
77 > (creating a top-level swig directory in the source tree), they could\r
78 > certainly be moved out-of-tree if the powers that be don't feel it\r
79 > appropriate to include them. [...]\r
80 \r
81 Same here, see attached patch. It is currently unfinished, and I was just\r
82 about to add support for iterating notmuch_threads_t and other similar\r
83 structures. I can also publish a Git repo with the entire branch, just\r
84 drop me a line if you want me to do that.\r
85 \r
86 > [...] Unfortunately, the build system is currently almost entirely\r
87 > independent from the notmuch build system. If  these are to be\r
88 > included in-tree, I would be curious to hear people have\r
89 > to say about how we might integrate it into the sup build system.\r
90                                                   ^^^\r
91 (Mmmh, I suppose you mean "notmuch build system" there :P)\r
92 \r
93 Mine is a little more cooked, as I have added a distutils "setup.py"\r
94 script. The bad news is that Python modules need to be compiled as\r
95 relocatable object files (-fPIC to teh rescue!), and the linker will\r
96 refuse to link the generated code with "notmuch.a" -- so I am instructing\r
97 distutils to compile *all* sources again. Not nice.\r
98 \r
99 BTW, I think that if more bindings start to appear, Notmuch might be built\r
100 as a shared library, to avoid duplicating it everywhere. One option may be\r
101 using *just* libtool but not the rest of auto-foo tools (for the record:\r
102 I agree with Carl that they are slow and wicked).\r
103 \r
104 Regards,\r
105 \r
106 \r
107 [*] http://www.cython.org/\r
108 --=20\r
109 Adrian Perez de Castro <aperez@igalia.com>\r
110 Igalia - Free Software Engineering\r
111 \r
112 --MP_/Qt51GuWb3Bo1f8da6MLGH4N\r
113 Content-Type: text/x-patch\r
114 Content-Transfer-Encoding: quoted-printable\r
115 Content-Disposition: attachment; filename=notmuch-python-wip.patch\r
116 \r
117  Makefile              |    1 +\r
118  python/.gitignore     |    2 +\r
119  python/Makefile       |    6 +\r
120  python/Makefile.local |   15 ++\r
121  python/notmuch.pyx    |  397 +++++++++++++++++++++++++++++++++++++++++++++=\r
122 ++++\r
123  python/pyutil.h       |   16 ++\r
124  python/setup.py       |   89 +++++++++++\r
125  7 files changed, 526 insertions(+), 0 deletions(-)\r
126 \r
127 diff --git a/Makefile b/Makefile\r
128 index 021fdb8..081d670 100644\r
129 --- a/Makefile\r
130 +++ b/Makefile\r
131 @@ -37,6 +37,7 @@ include Makefile.config\r
132 =20\r
133  include lib/Makefile.local\r
134  include compat/Makefile.local\r
135 +include python/Makefile.local\r
136  include Makefile.local\r
137 =20\r
138  # The user has not set any verbosity, default to quiet mode and inform the\r
139 diff --git a/python/.gitignore b/python/.gitignore\r
140 new file mode 100644\r
141 index 0000000..7f0efa8\r
142 --- /dev/null\r
143 +++ b/python/.gitignore\r
144 @@ -0,0 +1,2 @@\r
145 +notmuch.c\r
146 +build/\r
147 diff --git a/python/Makefile b/python/Makefile\r
148 new file mode 100644\r
149 index 0000000..e1e5c43\r
150 --- /dev/null\r
151 +++ b/python/Makefile\r
152 @@ -0,0 +1,6 @@\r
153 +\r
154 +all: python\r
155 +\r
156 +%:\r
157 +       make -C .. $@\r
158 +\r
159 diff --git a/python/Makefile.local b/python/Makefile.local\r
160 new file mode 100644\r
161 index 0000000..140a701\r
162 --- /dev/null\r
163 +++ b/python/Makefile.local\r
164 @@ -0,0 +1,15 @@\r
165 +dir=3Dpython\r
166 +\r
167 +python: $(dir)/build/.stamp\r
168 +       (cd $(dir) && python setup.py build)\r
169 +       touch $@\r
170 +\r
171 +$(dir)/build/.stamp: lib/notmuch.a\r
172 +\r
173 +clean: clean-python\r
174 +\r
175 +clean-python:\r
176 +       $(RM) -r $(dir)/build\r
177 +\r
178 +.PHONY: clean-python python\r
179 +\r
180 diff --git a/python/notmuch.pyx b/python/notmuch.pyx\r
181 new file mode 100644\r
182 index 0000000..f38b719\r
183 --- /dev/null\r
184 +++ b/python/notmuch.pyx\r
185 @@ -0,0 +1,397 @@\r
186 +#! /usr/bin/env python\r
187 +# -*- coding: utf-8 -*-\r
188 +# vim: fenc=3Dutf-8 ft=3Dpyrex\r
189 +#\r
190 +# Copyright =C2=A9 2009 Adrian Perez <aperez@igalia.com>\r
191 +#\r
192 +# Distributed under terms of the GPLv3 license.\r
193 +#\r
194 +\r
195 +cdef extern from "talloc.h":\r
196 +       void* talloc_init(char *fmt, ...)\r
197 +       int   talloc_free(void *ctx)\r
198 +\r
199 +\r
200 +cdef extern from "pyutil.h":\r
201 +       #\r
202 +       # Utility macros\r
203 +       #\r
204 +       char** pyutil_alloc_strv(void *ctx, unsigned nitems)\r
205 +\r
206 +\r
207 +cdef extern from "notmuch.h":\r
208 +       #\r
209 +       # Return status handling\r
210 +       #\r
211 +       ctypedef enum notmuch_status_t:\r
212 +               NOTMUCH_STATUS_SUCCESS\r
213 +               NOTMUCH_STATUS_OUT_OF_MEMORY\r
214 +               NOTMUCH_STATUS_READONLY_DATABASE\r
215 +               NOTMUCH_STATUS_XAPIAN_EXCEPTION\r
216 +               NOTMUCH_STATUS_FILE_ERROR\r
217 +               NOTMUCH_STATUS_FILE_NOT_EMAIL\r
218 +               NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID\r
219 +               NOTMUCH_STATUS_NULL_POINTER\r
220 +               NOTMUCH_STATUS_TAG_TOO_LONG\r
221 +               NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW\r
222 +\r
223 +       char* notmuch_status_to_string(notmuch_status_t status)\r
224 +\r
225 +       #\r
226 +       # notmuch_database_* -> notmuch.Database\r
227 +       #\r
228 +       ctypedef enum notmuch_database_mode_t:\r
229 +               NOTMUCH_DATABASE_MODE_READ_ONLY\r
230 +               NOTMUCH_DATABASE_MODE_READ_WRITE\r
231 +\r
232 +       ctypedef enum notmuch_sort_t:\r
233 +               NOTMUCH_SORT_OLDEST_FIRST\r
234 +               NOTMUCH_SORT_NEWEST_FIRST\r
235 +               NOTMUCH_SORT_MESSAGE_ID\r
236 +\r
237 +       ctypedef struct notmuch_database_t\r
238 +       ctypedef struct notmuch_messages_t\r
239 +       ctypedef struct notmuch_message_t\r
240 +       ctypedef struct notmuch_threads_t\r
241 +       ctypedef struct notmuch_thread_t\r
242 +       ctypedef struct notmuch_query_t\r
243 +       ctypedef struct notmuch_tags_t\r
244 +       ctypedef int time_t\r
245 +\r
246 +       int notmuch_threads_has_more(notmuch_threads_t *threads)\r
247 +       void notmuch_threads_advance(notmuch_threads_t *threads)\r
248 +       void notmuch_threads_destroy(notmuch_threads_t *threads)\r
249 +       notmuch_thread_t* notmuch_threads_get(notmuch_threads_t *threads)\r
250 +\r
251 +       void   notmuch_thread_destroy(notmuch_thread_t *thread)\r
252 +       char*  notmuch_thread_get_authors(notmuch_thread_t *thread)\r
253 +       char*  notmuch_thread_get_subject(notmuch_thread_t *thread)\r
254 +       char*  notmuch_thread_get_thread_id(notmuch_thread_t *thread)\r
255 +       time_t notmuch_thread_get_oldest_date(notmuch_thread_t *thread)\r
256 +       time_t notmuch_thread_get_newest_date(notmuch_thread_t *thread)\r
257 +       int    notmuch_thread_get_total_messages(notmuch_thread_t *thread)\r
258 +       int    notmuch_thread_get_matched_messages(notmuch_thread_t *thread)\r
259 +       notmuch_tags_t*     notmuch_thread_get_tags(notmuch_thread_t *thread)\r
260 +       notmuch_messages_t* notmuch_thread_get_toplevel_messages(notmuch_thread_t=\r
261  *thread)\r
262 +\r
263 +       notmuch_database_t* notmuch_database_create(char *path)\r
264 +       notmuch_database_t* notmuch_database_open(char *path, notmuch_database_mo=\r
265 de_t mode)\r
266 +       char* notmuch_database_get_path(notmuch_database_t *db)\r
267 +       void  notmuch_database_close(notmuch_database_t *db)\r
268 +\r
269 +       time_t notmuch_database_get_timestamp(notmuch_database_t *db, char *key)\r
270 +       notmuch_status_t notmuch_database_set_timestamp(\r
271 +                       notmuch_database_t *db, char *key, time_t timestamp)\r
272 +\r
273 +       notmuch_status_t notmuch_database_add_message(\r
274 +                       notmuch_database_t *db, char *filename, notmuch_message_t **mesage)\r
275 +\r
276 +       notmuch_message_t* notmuch_database_find_message(\r
277 +                       notmuch_database_t *db, char *message_id)\r
278 +\r
279 +       notmuch_tags_t* notmuch_database_get_all_tags(notmuch_database_t *db)\r
280 +\r
281 +       notmuch_query_t* notmuch_query_create(notmuch_database_t *db, char *qstri=\r
282 ng)\r
283 +\r
284 +       void notmuch_query_destroy(notmuch_query_t *query)\r
285 +       void notmuch_query_set_sort(notmuch_query_t *query, notmuch_sort_t sort)\r
286 +       unsigned notmuch_query_count_messages(notmuch_query_t *query)\r
287 +       notmuch_threads_t*  notmuch_query_search_threads(notmuch_query_t *query)\r
288 +       notmuch_messages_t* notmuch_query_search_messages(notmuch_query_t *query)\r
289 +\r
290 +       char* notmuch_message_get_message_id(notmuch_message_t *msg)\r
291 +       char* notmuch_message_get_thread_id(notmuch_message_t *msg)\r
292 +       char* notmuch_message_get_filename(notmuch_message_t *msg)\r
293 +       char* notmuch_message_get_header(notmuch_message_t *msg, char *name)\r
294 +       notmuch_status_t notmuch_message_add_tag(notmuch_message_t *msg, char *ta=\r
295 g)\r
296 +       notmuch_status_t notmuch_message_remove_tag(notmuch_message_t *msg, char =\r
297 *tag)\r
298 +       void notmuch_message_remove_all_tags(notmuch_message_t *msg)\r
299 +       void notmuch_message_destroy(notmuch_message_t *msg)\r
300 +       void notmuch_message_freeze(notmuch_message_t *msg)\r
301 +       void notmuch_message_thaw(notmuch_message_t *msg)\r
302 +\r
303 +\r
304 +cdef extern from "notmuch-client.h":\r
305 +       #\r
306 +       # notmuch_config_* -> notmuch.Config\r
307 +       #\r
308 +       ctypedef struct notmuch_config_t\r
309 +\r
310 +       notmuch_config_t* notmuch_config_open(void *ctx, char *filename, int *is_=\r
311 new_ret)\r
312 +       void   notmuch_config_close(notmuch_config_t *cfg)\r
313 +       int    notmuch_config_save(notmuch_config_t *cfg)\r
314 +       char*  notmuch_config_get_database_path(notmuch_config_t *cfg)\r
315 +       void   notmuch_config_set_database_path(notmuch_config_t *cfg, char *path)\r
316 +       char*  notmuch_config_get_user_name(notmuch_config_t *cfg)\r
317 +       void   notmuch_config_set_user_name(notmuch_config_t *cfg, char *name)\r
318 +       char*  notmuch_config_get_user_primary_email(notmuch_config_t *cfg)\r
319 +       void   notmuch_config_set_user_primary_email(notmuch_config_t *cfg, char =\r
320 *email)\r
321 +       char** notmuch_config_get_user_other_email(notmuch_config_t *cfg, size_t =\r
322 *length)\r
323 +       void   notmuch_config_set_user_other_email(notmuch_config_t *cfg, char **=\r
324 other_email, size_t length)\r
325 +\r
326 +       #\r
327 +       # Miscellaneous\r
328 +       #\r
329 +       int debugger_is_active()\r
330 +\r
331 +\r
332 +#\r
333 +# Import needed Python built-in modules\r
334 +#\r
335 +from datetime import datetime, date\r
336 +\r
337 +#\r
338 +# Miscellaneous functions and information\r
339 +#\r
340 +debugger_active =3D bool(debugger_is_active())\r
341 +\r
342 +\r
343 +#\r
344 +# notmuch_database_* -> notmuch.Database\r
345 +#\r
346 +\r
347 +\r
348 +#\r
349 +# notmuch_config_* -> notmuch.Config\r
350 +#\r
351 +cdef class Config:\r
352 +       """Handles the Notmuch configuration file.\r
353 +       """\r
354 +       cdef notmuch_config_t * _cfg\r
355 +       cdef void * _ctx\r
356 +       cdef object _filename\r
357 +\r
358 +       def __init__(self, filename=3D"~/.notmuch-config"):\r
359 +               """Open a Notmuch configuration file.\r
360 +               """\r
361 +               cdef int newret\r
362 +               self._ctx =3D talloc_init("notmuch.Config")\r
363 +               self._cfg =3D notmuch_config_open(self._ctx, filename, &newret)\r
364 +               self._filename =3D filename\r
365 +\r
366 +       def __dealloc__(self):\r
367 +               notmuch_config_close(self._cfg)\r
368 +               talloc_free(self._ctx)\r
369 +\r
370 +       property filename:\r
371 +               """File name containing the configuration (string)"""\r
372 +               def __get__(self):\r
373 +                       return self._filename\r
374 +\r
375 +       property database_path:\r
376 +               """Path to the Notmuch database (string)"""\r
377 +               def __get__(self):\r
378 +                       return notmuch_config_get_database_path(self._cfg)\r
379 +               def __set__(self, path):\r
380 +                       notmuch_config_set_database_path(self._cfg, path)\r
381 +\r
382 +       property user_name:\r
383 +               """User name (string)"""\r
384 +               def __get__(self):\r
385 +                       return notmuch_config_get_user_name(self._cfg)\r
386 +               def __set__(self, name):\r
387 +                       notmuch_config_set_user_name(self._cfg, name)\r
388 +\r
389 +       property user_primary_email:\r
390 +               """Primary e-mail address of the user (string)"""\r
391 +               def __get__(self):\r
392 +                       return notmuch_config_get_user_primary_email(self._cfg)\r
393 +               def __set__(self, email):\r
394 +                       notmuch_config_set_user_primary_email(self._cfg, email)\r
395 +\r
396 +       property other_email:\r
397 +               """List of other e-mail addresses of the user (tuple of strings)"""\r
398 +               def __get__(self):\r
399 +                       cdef size_t length\r
400 +                       cdef size_t i\r
401 +                       cdef char **emails\r
402 +                       emails =3D notmuch_config_get_user_other_email(self._cfg, &length)\r
403 +\r
404 +                       result =3D []\r
405 +                       for i from 0 <=3D i < length:\r
406 +                               result.append(emails[i])\r
407 +\r
408 +                       # XXX We do not want the result to be modifiable, because the property\r
409 +                       #     must be assigned as a whole, and not just modified only in the\r
410 +                       #     Python side of the world.\r
411 +                       return tuple(result)\r
412 +\r
413 +               def __set__(self, emaillist):\r
414 +                       cdef size_t length =3D len(emaillist)\r
415 +                       cdef char **emails =3D pyutil_alloc_strv(self._ctx, length)\r
416 +                       cdef size_t i\r
417 +\r
418 +                       for i from 0 <=3D i < len(emaillist):\r
419 +                               emails[i] =3D emaillist[i]\r
420 +                       notmuch_config_set_user_other_email(self._cfg, emails, len(emaillist))\r
421 +\r
422 +\r
423 +       def save(self):\r
424 +               """Save the Notmuch configuration"""\r
425 +               notmuch_config_save(self._cfg)\r
426 +\r
427 +\r
428 +cdef class Database\r
429 +\r
430 +\r
431 +cdef class Message:\r
432 +       cdef notmuch_message_t *_msg\r
433 +\r
434 +       def __cinit__(self, object messageptr):\r
435 +               # XXX Counterpart of bogus cast\r
436 +               self._msg =3D <notmuch_message_t*> messageptr\r
437 +\r
438 +       def __dealloc__(self):\r
439 +               notmuch_message_destroy(self._msg)\r
440 +\r
441 +       property message_id:\r
442 +               def __get__(self):\r
443 +                       return notmuch_message_get_message_id(self._msg)\r
444 +\r
445 +       property thread_id:\r
446 +               def __get__(self):\r
447 +                       return notmuch_message_get_thread_id(self._msg)\r
448 +\r
449 +       property filename:\r
450 +               def __get__(self):\r
451 +                       return notmuch_message_get_filename(self._msg)\r
452 +\r
453 +       def get_header(self, name):\r
454 +               return notmuch_message_get_header(self._msg, name)\r
455 +\r
456 +       def add_tag(self, tag):\r
457 +               cdef notmuch_status_t ret =3D notmuch_message_add_tag(self._msg, tag)\r
458 +               if ret !=3D NOTMUCH_STATUS_SUCCESS:\r
459 +                       raise ValueError(notmuch_status_to_string(ret))\r
460 +\r
461 +       def remove_tag(self, tag):\r
462 +               cdef notmuch_status_t ret =3D notmuch_message_remove_tag(self._msg, tag)\r
463 +               if ret !=3D NOTMUCH_STATUS_SUCCESS:\r
464 +                       raise ValueError(notmuch_status_to_string(ret))\r
465 +\r
466 +       def remove_all_tags(self):\r
467 +               notmuch_message_remove_all_tags(self._msg)\r
468 +\r
469 +       def freeze(self):\r
470 +               notmuch_message_freeze(self._msg)\r
471 +\r
472 +       def thaw(self):\r
473 +               notmuch_message_thaw(self._msg)\r
474 +\r
475 +\r
476 +cdef class Thread:\r
477 +       cdef notmuch_thread_t *_thread\r
478 +\r
479 +       def __cinit__(self, object threadptr):\r
480 +               self._thread =3D <notmuch_thread_t*> threadptr\r
481 +\r
482 +       def __dealloc__(self):\r
483 +               notmuch_thread_destroy(self._thread)\r
484 +\r
485 +       property authors:\r
486 +               def __get__(self):\r
487 +                       return notmuch_thread_get_authors(self._thread)\r
488 +\r
489 +       property subject:\r
490 +               def __get__(self):\r
491 +                       return notmuch_thread_get_subject(self._thread)\r
492 +\r
493 +       property thread_id:\r
494 +               def __get__(self):\r
495 +                       return notmuch_thread_get_thread_id(self._thread)\r
496 +\r
497 +       property oldest_date:\r
498 +               def __get__(self):\r
499 +                       return datetime.fromtimestamp(notmuch_thread_get_oldest_date(self._thre=\r
500 ad))\r
501 +\r
502 +       property newest_date:\r
503 +               def __get__(self):\r
504 +                       return datetime.fromtimestamp(notmuch_thread_get_newest_date(self._thre=\r
505 ad))\r
506 +\r
507 +       property total_messages:\r
508 +               def __get__(self):\r
509 +                       return notmuch_thread_get_total_messages(self._thread)\r
510 +\r
511 +       property matched_messages:\r
512 +               def __get__(self):\r
513 +                       return notmuch_thread_get_matched_messages(self._thread)\r
514 +\r
515 +       def __len__(self):\r
516 +               return self.matched_messages\r
517 +\r
518 +\r
519 +cdef class Query:\r
520 +       cdef notmuch_query_t *_query\r
521 +\r
522 +       def __cinit__(self, Database db not None, qs):\r
523 +               self._query =3D notmuch_query_create(db._db, qs)\r
524 +\r
525 +       def __dealloc__(self):\r
526 +               notmuch_query_destroy(self._query)\r
527 +\r
528 +       def __len__(self):\r
529 +               return notmuch_query_count_messages(self._query)\r
530 +\r
531 +       def sort(self, notmuch_sort_t ordering):\r
532 +               notmuch_query_set_sort(self._query, ordering)\r
533 +               return self\r
534 +\r
535 +\r
536 +\r
537 +cdef class Database:\r
538 +       cdef notmuch_database_t *_db\r
539 +\r
540 +       def __init__(self, path=3DNone, readonly=3DTrue):\r
541 +               cdef notmuch_database_mode_t mode =3D NOTMUCH_DATABASE_MODE_READ_WRITE\r
542 +               if path is None:\r
543 +                       path =3D Config().database_path\r
544 +               if readonly:\r
545 +                       mode =3D NOTMUCH_DATABASE_MODE_READ_ONLY\r
546 +               self._db =3D notmuch_database_open(path, mode)\r
547 +\r
548 +       def __dealloc__(self):\r
549 +               notmuch_database_close(self._db)\r
550 +\r
551 +       def get_timestamp(self, key):\r
552 +               cdef time_t ts =3D notmuch_database_get_timestamp(self._db, key)\r
553 +               return datetime.fromtimestamp(ts)\r
554 +\r
555 +       def set_timestamp(self, key, timestamp):\r
556 +               cdef time_t ts\r
557 +               if isinstance(timestamp, date):\r
558 +                       ts =3D int(timestamp.strftime("%s"))\r
559 +               elif isinstance(timestamp, float):\r
560 +                       ts =3D <time_t> timestamp\r
561 +               elif isinstance(timestamp, int):\r
562 +                       ts =3D timestamp\r
563 +               else:\r
564 +                       raise ValueError("Numeric timestamp or datetime.date object expected")\r
565 +\r
566 +       def add_message(self, filename):\r
567 +               cdef notmuch_message_t *message =3D NULL\r
568 +               cdef notmuch_status_t   status\r
569 +               status =3D notmuch_database_add_message(self._db, filename, &message)\r
570 +               if status =3D=3D NOTMUCH_STATUS_SUCCESS:\r
571 +                       # XXX This cast seems bogus, it may work, though\r
572 +                       return Message(<object> message)\r
573 +               else:\r
574 +                       if message !=3D NULL:\r
575 +                               notmuch_message_destroy(message)\r
576 +                       raise ValueError(notmuch_status_to_string(status))\r
577 +\r
578 +       def find_message(self, message_id):\r
579 +               cdef notmuch_message_t *message\r
580 +               message =3D notmuch_database_find_message(self._db, message_id)\r
581 +               if message =3D=3D NULL:\r
582 +                       raise KeyError(message_id)\r
583 +               return Message(<object> message)\r
584 +\r
585 +       property path:\r
586 +               """Database path"""\r
587 +               def __get__(self):\r
588 +                       return notmuch_database_get_path(self._db)\r
589 +\r
590 +       def query(self, qstring):\r
591 +               return Query(self, qstring)\r
592 +\r
593 +\r
594 diff --git a/python/pyutil.h b/python/pyutil.h\r
595 new file mode 100644\r
596 index 0000000..64d93bf\r
597 --- /dev/null\r
598 +++ b/python/pyutil.h\r
599 @@ -0,0 +1,16 @@\r
600 +/*\r
601 + * pyutil.h\r
602 + * Copyright (C) 2009 Adrian Perez <aperez@igalia.com>\r
603 + *\r
604 + * Distributed under terms of the GPLv3 license.\r
605 + */\r
606 +\r
607 +#ifndef __pyutil_h__\r
608 +#define __pyutil_h__\r
609 +\r
610 +#include <talloc.h>\r
611 +\r
612 +#define pyutil_alloc_strv(_ctx, _n)  talloc_array ((_ctx), char*, (_n))\r
613 +\r
614 +#endif /* !__pyutil_h__ */\r
615 +\r
616 diff --git a/python/setup.py b/python/setup.py\r
617 new file mode 100644\r
618 index 0000000..ffd43b2\r
619 --- /dev/null\r
620 +++ b/python/setup.py\r
621 @@ -0,0 +1,89 @@\r
622 +#! /usr/bin/env python\r
623 +# -*- coding: utf-8 -*-\r
624 +# vim:fenc=3Dutf-8\r
625 +#\r
626 +# Copyright =C2=A9 2009 Adrian Perez <aperez@igalia.com>\r
627 +#\r
628 +# Distributed under terms of the GPLv3 license.\r
629 +\r
630 +from distutils.core import setup\r
631 +from distutils.extension import Extension\r
632 +from Cython.Distutils import build_ext\r
633 +import commands\r
634 +\r
635 +\r
636 +class FlagOMatic(dict):\r
637 +    _KEYS =3D ("extra_link_args", "include_dirs", "library_dirs", "librari=\r
638 es")\r
639 +\r
640 +    def __init__(self, *arg, **kw):\r
641 +        super(FlagOMatic, self).__init__(*arg, **kw)\r
642 +        for key in self._KEYS:\r
643 +            self[key] =3D set(self.get(key, []))\r
644 +\r
645 +    extra_link_args =3D property(lambda self: self["extra_link_args"])\r
646 +    include_dirs    =3D property(lambda self: self["include_dirs"])\r
647 +    library_dirs    =3D property(lambda self: self["library_dirs"])\r
648 +    libraries       =3D property(lambda self: self["libraries"])\r
649 +\r
650 +    _FLAG_MAP =3D {\r
651 +            "-I": "include_dirs",\r
652 +            "-L": "library_dirs",\r
653 +            "-l": "libraries",\r
654 +        }\r
655 +\r
656 +    def add_compiler_flags(self, text):\r
657 +        for token in text.split():\r
658 +            key =3D self._FLAG_MAP.get(token[:2], "extra_link_args")\r
659 +            if key =3D=3D "extra_link_args":\r
660 +                self.extra_link_args.add(token)\r
661 +            else:\r
662 +                self[key].add(token[2:])\r
663 +\r
664 +    def add_pkgconfig_flags(self, *modules):\r
665 +        self.add_command_output_flags(\r
666 +                "pkg-config --libs --cflags %s" % " ".join(modules))\r
667 +\r
668 +    def add_command_output_flags(self, command):\r
669 +        self.add_compiler_flags(commands.getoutput(command))\r
670 +\r
671 +    @property\r
672 +    def kwargs(self):\r
673 +        return dict((k, list(v)) for k, v in self.iteritems())\r
674 +\r
675 +\r
676 +# Gather compiler flags\r
677 +#\r
678 +flags =3D FlagOMatic(\r
679 +       #extra_link_args =3D ("../lib/notmuch.a",),\r
680 +        include_dirs    =3D ("..", "../lib", "../compat"))\r
681 +\r
682 +flags.add_pkgconfig_flags("gmime-2.4", "talloc")\r
683 +flags.add_command_output_flags("xapian-config --cxxflags --libs")\r
684 +\r
685 +# We are building a extension module\r
686 +#\r
687 +import os\r
688 +\r
689 +srcs =3D ["notmuch.pyx"]\r
690 +srcs.extend(\r
691 +    map(lambda s: "../"+s,\r
692 +        filter(lambda s: s.endswith(".c"),\r
693 +            os.listdir(".."))))\r
694 +srcs.extend(\r
695 +    map(lambda s: "../lib/"+s,\r
696 +        filter(lambda s: s.endswith(".cc") or s.endswith(".c"),\r
697 +            os.listdir("../lib"))))\r
698 +\r
699 +ext_modules =3D [Extension("notmuch", srcs, **flags.kwargs)]\r
700 +\r
701 +\r
702 +# And now for the easy part :-)\r
703 +#\r
704 +setup(\r
705 +    name         =3D "notmuch",\r
706 +    author       =3D "Adrian Perez",\r
707 +    author_email =3D "aperez@igalia.com",\r
708 +    cmdclass     =3D {"build_ext": build_ext},\r
709 +    ext_modules  =3D ext_modules,\r
710 +)\r
711 +\r
712 \r
713 --MP_/Qt51GuWb3Bo1f8da6MLGH4N--\r
714 \r
715 --Sig_/NJQn5hRW+avHgHHAlU7=Xa1\r
716 Content-Type: application/pgp-signature; name=signature.asc\r
717 Content-Disposition: attachment; filename=signature.asc\r
718 \r
719 -----BEGIN PGP SIGNATURE-----\r
720 Version: GnuPG v2.0.13 (GNU/Linux)\r
721 \r
722 iEYEARECAAYFAks7MOsACgkQkcVZ2+TJEju5RACfQAjq3L+//wNVYgB+05f2OmI7\r
723 9P4AmgKqBUP79jj4EMuuNit64u94aj3q\r
724 =RI1/\r
725 -----END PGP SIGNATURE-----\r
726 \r
727 --Sig_/NJQn5hRW+avHgHHAlU7=Xa1--\r