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