Add tokyocabinet support
[apps/madmutt.git] / lib-mx / hcache.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2004 Thomas Glanzmann <sithglan@stud.uni-erlangen.de>
4  * Copyright (C) 2004 Tobias Werth <sitowert@stud.uni-erlangen.de>
5  * Copyright (C) 2004 Brian Fundakowski Feldman <green@FreeBSD.org>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 #include <lib-lib/lib-lib.h>
13
14 #ifdef USE_HCACHE
15
16 #if defined(HAVE_TOKYOCABINET)
17 #include <tcutil.h>
18 #include <tchdb.h>
19 #elif defined(HAVE_QDBM)
20 #include <depot.h>
21 #include <cabin.h>
22 #include <villa.h>
23 #elif defined(HAVE_GDBM)
24 #include <gdbm.h>
25 #else
26 #error no supported DB library found ?
27 #endif
28
29 #include "charset.h"
30 #include "mutt.h"
31 #include "hcache.h"
32
33 struct hcache_t {
34 #if defined(HAVE_TOKYOCABINET)
35     TCHDB *db;
36 #elif defined(HAVE_QDBM)
37     VILLA *db;
38 #elif defined(HAVE_GDBM)
39     GDBM_FILE db;
40 #endif
41     char *folder;
42     unsigned int crc;
43 };
44
45 static unsigned int crc32(unsigned int crc, const void *src, ssize_t len)
46 {
47     int i;
48     const unsigned char *p = src;
49
50     while (len--) {
51         crc ^= *p++;
52         for (i = 0; i < 8; i++)
53             crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
54     }
55     return crc;
56 }
57
58 static int generate_crc32(void)
59 {
60     int crc = 0;
61
62     crc = crc32(crc, "madmutt.2007.05.13", m_strlen("madmutt.2007.05.13"));
63 #ifdef HAVE_LANGINFO_H
64     crc = crc32(crc, mod_cset.charset, m_strlen(mod_cset.charset));
65 #endif
66     crc = crc32(crc, "USE_POP",   m_strlen("USE_POP"));
67     crc = crc32(crc, "USE_IMAP",  m_strlen("USE_IMAP"));
68     return crc;
69 }
70
71 static const char *
72 mutt_hcache_per_folder(const char *path, const char *folder)
73 {
74     static char buf[_POSIX_PATH_MAX];
75     struct stat st;
76     int pos;
77
78     if (stat(path, &st) < 0 || !S_ISDIR(st.st_mode)) {
79         return path;
80     }
81
82     pos  = m_strcpy(buf, sizeof(buf), path);
83     pos += m_strputc(buf + pos, sizeof(buf) - pos, '/');
84
85     while (*folder) {
86         if (isalnum((unsigned char)*folder) || *folder == '.') {
87             pos += m_strputc(buf + pos, sizeof(buf) - pos, *folder);
88         } else {
89             pos += m_strputc(buf + pos, sizeof(buf) - pos, '_');
90         }
91         folder++;
92     }
93     pos += m_strcpy(buf + pos, sizeof(buf) - pos, ".hdb");
94     return buf;
95 }
96
97 /* store and restore things {{{ */
98
99 static void dump_int(buffer_t *buf, int i)
100 {
101     buffer_add(buf, &i, sizeof(i));
102 }
103
104 static const void *restore_int(const char *d, int *i)
105 {
106     memcpy(i, d, sizeof(*i));
107     return d + sizeof(*i);
108 }
109
110 static void dump_cstr(buffer_t *buf, const char *s)
111 {
112     int size = 0;
113
114     if (m_strisempty(s)) {
115         dump_int(buf, size);
116         return;
117     }
118
119     size = strlen(s) + 1;
120     dump_int(buf, size);
121     buffer_add(buf, s, size);
122 }
123
124 static const void *restore_cstr(const char *d, char **c)
125 {
126     int size;
127
128     d  = restore_int(d, &size);
129     *c = p_dup(d, size);
130     return d + size;
131 }
132
133 static void dump_address(buffer_t *buf, address_t *a)
134 {
135     int counter = 0, pos = buf->len;
136
137     dump_int(buf, counter);
138
139     for (; a; a = a->next, counter++) {
140         dump_cstr(buf, a->personal);
141         dump_cstr(buf, a->mailbox);
142         dump_int(buf, a->group);
143     }
144
145     memcpy(buf->data + pos, &counter, sizeof(counter));
146 }
147
148 static const void *restore_address(const char *d, address_t **a)
149 {
150     int counter;
151
152     d = restore_int(d, &counter);
153
154     for (; counter > 0; counter--) {
155         *a = address_new();
156         d = restore_cstr(d, &(*a)->personal);
157         d = restore_cstr(d, &(*a)->mailbox);
158         d = restore_int(d, &(*a)->group);
159         a = &(*a)->next;
160     }
161
162     *a = NULL;
163     return d;
164 }
165
166 static void dump_list(buffer_t *buf, string_list_t *l)
167 {
168     int pos = buf->len;
169     int counter = 0;
170
171     dump_int(buf, counter);
172
173     for (; l; l = l->next, counter++) {
174         dump_cstr(buf, l->data);
175     }
176
177     memcpy(buf->data + pos, &counter, sizeof(counter));
178 }
179
180 static const void *restore_list(const char *d, string_list_t **l)
181 {
182     int counter;
183
184     d = restore_int(d, &counter);
185
186     for (; counter > 0; counter--) {
187         *l = string_item_new();
188         d = restore_cstr(d, &(*l)->data);
189         l = &(*l)->next;
190     }
191
192     *l = NULL;
193     return d;
194 }
195
196 static void dump_parameter(buffer_t *buf, parameter_t *p)
197 {
198     int pos = buf->len, counter = 0;
199
200     dump_int(buf, counter);
201
202     for (; p; p = p->next, counter++) {
203         dump_cstr(buf, p->attribute);
204         dump_cstr(buf, p->value);
205     }
206
207     memcpy(buf->data + pos, &counter, sizeof(counter));
208 }
209
210 static const void *restore_parameter(const char *d, parameter_t ** p)
211 {
212     int counter;
213
214     d = restore_int(d, &counter);
215
216     for (; counter > 0; counter--) {
217         *p = parameter_new();
218         d  = restore_cstr(d, &(*p)->attribute);
219         d  = restore_cstr(d, &(*p)->value);
220         p  = &(*p)->next;
221     }
222
223     *p = NULL;
224     return d;
225 }
226
227 static void dump_body(buffer_t *buf, BODY *b)
228 {
229     buffer_add(buf, b, sizeof(*b));
230     dump_cstr(buf, b->xtype);
231     dump_cstr(buf, b->subtype);
232
233     dump_parameter(buf, b->parameter);
234
235     dump_cstr(buf, b->description);
236     dump_cstr(buf, b->form_name);
237     dump_cstr(buf, b->filename);
238     dump_cstr(buf, b->d_filename);
239 }
240
241 static const void *restore_body(const char *d, BODY *c)
242 {
243     memcpy(c, d, sizeof(*c));
244     d += sizeof(*c);
245
246     d = restore_cstr(d, &c->xtype);
247     d = restore_cstr(d, &c->subtype);
248
249     d = restore_parameter(d, &c->parameter);
250
251     d = restore_cstr(d, &c->description);
252     d = restore_cstr(d, &c->form_name);
253     d = restore_cstr(d, &c->filename);
254     d = restore_cstr(d, &c->d_filename);
255     return d;
256 }
257
258 static void dump_envelope(buffer_t *buf, ENVELOPE * e)
259 {
260     int n;
261
262     dump_address(buf, e->return_path);
263     dump_address(buf, e->from);
264     dump_address(buf, e->to);
265     dump_address(buf, e->cc);
266     dump_address(buf, e->bcc);
267     dump_address(buf, e->sender);
268     dump_address(buf, e->reply_to);
269     dump_address(buf, e->mail_followup_to);
270
271     dump_cstr(buf, e->subject);
272     n = e->real_subj ? e->real_subj - e->subject : -1;
273     dump_int(buf, n);
274
275     dump_cstr(buf, e->message_id);
276     dump_cstr(buf, e->supersedes);
277     dump_cstr(buf, e->date);
278     dump_cstr(buf, e->x_label);
279     dump_cstr(buf, e->list_post);
280
281     dump_list(buf, e->references);
282     dump_list(buf, e->in_reply_to);
283     dump_list(buf, e->userhdrs);
284 }
285
286 static const void *restore_envelope(const char *d, ENVELOPE *e)
287 {
288     int real_subj_off;
289
290     d = restore_address(d, &e->return_path);
291     d = restore_address(d, &e->from);
292     d = restore_address(d, &e->to);
293     d = restore_address(d, &e->cc);
294     d = restore_address(d, &e->bcc);
295     d = restore_address(d, &e->sender);
296     d = restore_address(d, &e->reply_to);
297     d = restore_address(d, &e->mail_followup_to);
298
299     d = restore_cstr(d, &e->subject);
300     d = restore_int(d, &real_subj_off);
301     if (real_subj_off >= 0) {
302         e->real_subj = e->subject + real_subj_off;
303     } else {
304         e->real_subj = NULL;
305     }
306     d = restore_cstr(d, &e->message_id);
307     d = restore_cstr(d, &e->supersedes);
308     d = restore_cstr(d, &e->date);
309     d = restore_cstr(d, &e->x_label);
310     d = restore_cstr(d, &e->list_post);
311
312     d = restore_list(d, &e->references);
313     d = restore_list(d, &e->in_reply_to);
314     d = restore_list(d, &e->userhdrs);
315     return d;
316 }
317
318 static buffer_t *mutt_hcache_dump(hcache_t *db, HEADER *h, long uid_validity)
319 {
320     buffer_t *res = buffer_new();
321
322     if (!uid_validity) {
323         uid_validity = time(NULL);
324     }
325     buffer_add(res, &uid_validity, sizeof(uid_validity));
326     dump_int(res, db->crc);
327     buffer_add(res, h, sizeof(*h));
328
329     dump_envelope(res, h->env);
330     dump_body(res, h->content);
331     dump_cstr(res, h->maildir_flags);
332     return res;
333 }
334
335 HEADER *mutt_hcache_restore(const void *_d, HEADER **oh)
336 {
337     const char *d = _d;
338     HEADER *h = header_new();
339
340     /* skip uid_validity + crc */
341     d += sizeof(long) + sizeof(int);
342
343     memcpy(h, d, sizeof(*h));
344     d += sizeof(*h);
345
346     h->env = envelope_new();
347     d = restore_envelope(d, h->env);
348
349     h->content = body_new();
350     d = restore_body(d, h->content);
351
352     d = restore_cstr(d, &h->maildir_flags);
353
354     /* this is needed for maildir style mailboxes */
355     if (oh) {
356         h->old = (*oh)->old;
357         h->path = m_strdup((*oh)->path);
358         header_delete(oh);
359     }
360
361     return h;
362 }
363
364 /* }}} */
365
366 hcache_t *mutt_hcache_open(const char *folder)
367 {
368     const char *path;
369     hcache_t *h;
370
371     if (m_strisempty(mod_core.cachedir)) {
372         return NULL;
373     }
374
375     h = p_new(hcache_t, 1);
376     h->folder = m_strdup(folder);
377     h->crc    = generate_crc32();
378
379     path = mutt_hcache_per_folder(mod_core.cachedir, folder);
380
381     {
382 #if defined(HAVE_TOKYOCABINET)
383         h->db = tchdbnew();
384         if (!tchdbopen(h->db, path, HDBOWRITER | HDBOCREAT)) {
385             tchdbdel(h->db);
386             h->db = NULL;
387         }
388 #elif defined(HAVE_QDBM)
389         h->db = vlopen(path, VL_OWRITER | VL_OCREAT, VL_CMPLEX);
390 #elif defined(HAVE_GDBM)
391         h->db = gdbm_open((char *) path, 16384, GDBM_WRCREAT, 00600, NULL);
392 #endif
393     }
394
395     if (!h->db) {
396         p_delete(&h->folder);
397         p_delete(&h);
398     }
399     return h;
400 }
401
402 void mutt_hcache_close(hcache_t **db)
403 {
404     if (!*db)
405         return;
406
407 #if defined(HAVE_TOKYOCABINET)
408     tchdbdel((*db)->db);
409     (*db)->db = NULL;
410 #elif defined(HAVE_QDBM)
411     vlclose((*db)->db);
412 #elif defined(HAVE_GDBM)
413     gdbm_close((*db)->db);
414 #endif
415
416     p_delete(&(*db)->folder);
417     p_delete(db);
418 }
419
420 void *mutt_hcache_fetch(hcache_t *db, const char *filename,
421                         ssize_t (*keylen)(const char *fn))
422 {
423     char path[_POSIX_PATH_MAX];
424     char *data = NULL;
425
426     if (!db)
427         return NULL;
428
429     snprintf(path, sizeof(path), "%s%s", db->folder, filename);
430
431     {
432         int ksize = keylen(path);
433 #if defined(HAVE_TOKYOCABINET)
434         int unused;
435         data  = tchdbget(db->db, path, ksize, &unused);
436 #elif defined(HAVE_QDBM)
437         data  = vlget(db->db, path, ksize, NULL);
438 #elif defined(HAVE_GDBM)
439         datum k = { .dptr = path, .dsize = ksize };
440
441         data = gdbm_fetch(db->db, k).dtpr;
442 #endif
443     }
444
445     if (data) {
446         unsigned crc = 0;
447
448         restore_int(data + sizeof(long), (int *)&crc);
449         if (crc != db->crc)
450             p_delete(&data);
451     }
452
453     return data;
454 }
455
456 int mutt_hcache_store(hcache_t *db, const char *filename, HEADER *header,
457                       long uid_validity, ssize_t (*keylen)(const char *fn))
458 {
459     char path[_POSIX_PATH_MAX];
460     buffer_t *data;
461     int ret;
462
463     if (!db)
464         return -1;
465
466     snprintf(path, sizeof(path), "%s%s", db->folder, filename);
467     data = mutt_hcache_dump(db, header, uid_validity);
468
469     {
470         int ksize = keylen(path);
471 #if defined(HAVE_TOKYOCABINET)
472         ret = !!tchdbput(db->db, path, ksize, data->data, data->len) - 1;
473 #elif defined(HAVE_QDBM)
474         ret = !!vlput(db->db, path, ksize, data->data, data->len, VL_DOVER) - 1;
475 #elif defined(HAVE_GDBM)
476         datum k = { .dptr = path, .dsize = ksize };
477         datum v = { .dptr = data->data, .dsize = data->len };
478
479         ret = gdbm_store(db->db, k, v, GDBM_REPLACE);
480 #endif
481     }
482
483     buffer_delete(&data);
484     return ret;
485 }
486
487 void mutt_hcache_delete(hcache_t *db, const char *filename,
488                        ssize_t (*keylen)(const char *fn))
489 {
490     char path[_POSIX_PATH_MAX];
491
492     if (!db)
493         return -1;
494
495     snprintf(path, sizeof(path), "%s%s", db->folder, filename);
496
497     {
498         int ksize = keylen(path);
499 #if defined(HAVE_TOKYOCABINET)
500         tchdbout(db->db, path, ksize);
501 #elif defined(HAVE_QDBM)
502         vlout(db->db, path, ksize);
503 #elif defined(HAVE_GDBM)
504         datum k = { .dptr = path, .dsize = ksize };
505         gdbm_delete(db->db, k);
506 #endif
507     }
508 }
509
510 #endif /* USE_HCACHE */