2005-03-30 [colin] 1.9.6cvs11
[claws.git] / src / msgcache.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 Hiroyuki Yamamoto & The Sylpheed Claws Team
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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "defs.h"
21
22 #include <glib.h>
23 #include <glib/gi18n.h>
24
25 #include <time.h>
26
27 #include "msgcache.h"
28 #include "utils.h"
29 #include "procmsg.h"
30 #include "codeconv.h"
31
32 typedef enum
33 {
34         DATA_READ,
35         DATA_WRITE,
36         DATA_APPEND
37 } DataOpenMode;
38
39 struct _MsgCache {
40         GHashTable      *msgnum_table;
41         GHashTable      *msgid_table;
42         guint            memusage;
43         time_t           last_access;
44 };
45
46 typedef struct _StringConverter StringConverter;
47 struct _StringConverter {
48         gchar *(*convert) (StringConverter *converter, gchar *srcstr);
49         void   (*free)    (StringConverter *converter);
50 };
51
52 typedef struct _StrdupConverter StrdupConverter;
53 struct _StrdupConverter {
54         StringConverter converter;
55 };
56
57 typedef struct _CharsetConverter CharsetConverter;
58 struct _CharsetConverter {
59         StringConverter converter;
60
61         gchar *srccharset;
62         gchar *dstcharset;
63 };
64
65 MsgCache *msgcache_new(void)
66 {
67         MsgCache *cache;
68         
69         cache = g_new0(MsgCache, 1),
70         cache->msgnum_table = g_hash_table_new(g_int_hash, g_int_equal);
71         cache->msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
72         cache->last_access = time(NULL);
73
74         return cache;
75 }
76
77 static gboolean msgcache_msginfo_free_func(gpointer num, gpointer msginfo, gpointer user_data)
78 {
79         procmsg_msginfo_free((MsgInfo *)msginfo);
80         return TRUE;
81 }                                                                                         
82
83 void msgcache_destroy(MsgCache *cache)
84 {
85         g_return_if_fail(cache != NULL);
86
87         g_hash_table_foreach_remove(cache->msgnum_table, msgcache_msginfo_free_func, NULL);
88         g_hash_table_destroy(cache->msgid_table);
89         g_hash_table_destroy(cache->msgnum_table);
90         g_free(cache);
91 }
92
93 void msgcache_add_msg(MsgCache *cache, MsgInfo *msginfo) 
94 {
95         MsgInfo *newmsginfo;
96
97         g_return_if_fail(cache != NULL);
98         g_return_if_fail(msginfo != NULL);
99
100         newmsginfo = procmsg_msginfo_new_ref(msginfo);
101         g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
102         if(newmsginfo->msgid != NULL)
103                 g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
104         cache->memusage += procmsg_msginfo_memusage(msginfo);
105         cache->last_access = time(NULL);
106
107         debug_print("Cache size: %d messages, %d bytes\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
108 }
109
110 void msgcache_remove_msg(MsgCache *cache, guint msgnum)
111 {
112         MsgInfo *msginfo;
113
114         g_return_if_fail(cache != NULL);
115         g_return_if_fail(msgnum > 0);
116
117         msginfo = (MsgInfo *) g_hash_table_lookup(cache->msgnum_table, &msgnum);
118         if(!msginfo)
119                 return;
120
121         cache->memusage -= procmsg_msginfo_memusage(msginfo);
122         if(msginfo->msgid)
123                 g_hash_table_remove(cache->msgid_table, msginfo->msgid);
124         g_hash_table_remove(cache->msgnum_table, &msginfo->msgnum);
125         procmsg_msginfo_free(msginfo);
126         cache->last_access = time(NULL);
127
128         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
129 }
130
131 void msgcache_update_msg(MsgCache *cache, MsgInfo *msginfo)
132 {
133         MsgInfo *oldmsginfo, *newmsginfo;
134         
135         g_return_if_fail(cache != NULL);
136         g_return_if_fail(msginfo != NULL);
137
138         oldmsginfo = g_hash_table_lookup(cache->msgnum_table, &msginfo->msgnum);
139         if(oldmsginfo && oldmsginfo->msgid) 
140                 g_hash_table_remove(cache->msgid_table, oldmsginfo->msgid);
141         if (oldmsginfo) {
142                 g_hash_table_remove(cache->msgnum_table, &oldmsginfo->msgnum);
143                 cache->memusage -= procmsg_msginfo_memusage(oldmsginfo);
144                 procmsg_msginfo_free(oldmsginfo);
145         }
146
147         newmsginfo = procmsg_msginfo_new_ref(msginfo);
148         g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
149         if(newmsginfo->msgid)
150                 g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
151         cache->memusage += procmsg_msginfo_memusage(newmsginfo);
152         cache->last_access = time(NULL);
153         
154         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
155
156         return;
157 }
158
159 MsgInfo *msgcache_get_msg(MsgCache *cache, guint num)
160 {
161         MsgInfo *msginfo;
162
163         g_return_val_if_fail(cache != NULL, NULL);
164
165         msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
166         if(!msginfo)
167                 return NULL;
168         cache->last_access = time(NULL);
169         
170         return procmsg_msginfo_new_ref(msginfo);
171 }
172
173 MsgInfo *msgcache_get_msg_by_id(MsgCache *cache, const gchar *msgid)
174 {
175         MsgInfo *msginfo;
176         
177         g_return_val_if_fail(cache != NULL, NULL);
178         g_return_val_if_fail(msgid != NULL, NULL);
179
180         msginfo = g_hash_table_lookup(cache->msgid_table, msgid);
181         if(!msginfo)
182                 return NULL;
183         cache->last_access = time(NULL);
184         
185         return procmsg_msginfo_new_ref(msginfo);        
186 }
187
188 static void msgcache_get_msg_list_func(gpointer key, gpointer value, gpointer user_data)
189 {
190         MsgInfoList **listptr = user_data;
191         MsgInfo *msginfo = value;
192
193         *listptr = g_slist_prepend(*listptr, procmsg_msginfo_new_ref(msginfo));
194 }
195
196 MsgInfoList *msgcache_get_msg_list(MsgCache *cache)
197 {
198         MsgInfoList *msg_list = NULL;
199
200         g_return_val_if_fail(cache != NULL, NULL);
201
202         g_hash_table_foreach((GHashTable *)cache->msgnum_table, msgcache_get_msg_list_func, (gpointer)&msg_list);       
203         cache->last_access = time(NULL);
204         
205         msg_list = g_slist_reverse(msg_list);
206
207         return msg_list;
208 }
209
210 time_t msgcache_get_last_access_time(MsgCache *cache)
211 {
212         g_return_val_if_fail(cache != NULL, 0);
213         
214         return cache->last_access;
215 }
216
217 gint msgcache_get_memory_usage(MsgCache *cache)
218 {
219         g_return_val_if_fail(cache != NULL, 0);
220
221         return cache->memusage;
222 }
223
224 /*
225  *  Cache saving functions
226  */
227
228 #define READ_CACHE_DATA(data, fp) \
229 { \
230         if (msgcache_read_cache_data_str(fp, &data, conv) < 0) { \
231                 procmsg_msginfo_free(msginfo); \
232                 error = TRUE; \
233                 break; \
234         } \
235 }
236
237 #define READ_CACHE_DATA_INT(n, fp) \
238 { \
239         guint32 idata; \
240         size_t ni; \
241  \
242         if ((ni = fread(&idata, 1, sizeof(idata), fp)) != sizeof(idata)) { \
243                 g_warning("read_int: Cache data corrupted, read %d of %d at " \
244                           "offset %d\n", ni, sizeof(idata), ftell(fp)); \
245                 procmsg_msginfo_free(msginfo); \
246                 error = TRUE; \
247                 break; \
248         } else \
249                 n = idata;\
250 }
251
252 #define WRITE_CACHE_DATA_INT(n, fp)             \
253 {                                               \
254         guint32 idata;                          \
255                                                 \
256         idata = (guint32)n;                     \
257         fwrite(&idata, sizeof(idata), 1, fp);   \
258 }
259
260 #define WRITE_CACHE_DATA(data, fp) \
261 { \
262         size_t len; \
263         if (data == NULL) \
264                 len = 0; \
265         else \
266                 len = strlen(data); \
267         WRITE_CACHE_DATA_INT(len, fp); \
268         if (len > 0) { \
269                 fwrite(data, len, 1, fp); \
270         } \
271 }
272
273 static FILE *msgcache_open_data_file(const gchar *file, gint version,
274                                      DataOpenMode mode,
275                                      gchar *buf, size_t buf_size)
276 {
277         FILE *fp;
278         gint32 data_ver;
279
280         g_return_val_if_fail(file != NULL, NULL);
281
282         if (mode == DATA_WRITE) {
283                 if ((fp = fopen(file, "wb")) == NULL) {
284                         FILE_OP_ERROR(file, "fopen");
285                         return NULL;
286                 }
287                 if (change_file_mode_rw(fp, file) < 0)
288                         FILE_OP_ERROR(file, "chmod");
289
290                 WRITE_CACHE_DATA_INT(version, fp);
291                 return fp;
292         }
293
294         /* check version */
295         if ((fp = fopen(file, "rb")) == NULL)
296                 debug_print("Mark/Cache file not found\n");
297         else {
298                 if (buf && buf_size > 0)
299                         setvbuf(fp, buf, _IOFBF, buf_size);
300                 if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
301                          version != data_ver) {
302                         debug_print("Mark/Cache version is different (%d != %d). "
303                                     "Discarding it.\n", data_ver, version);
304                         fclose(fp);
305                         fp = NULL;
306                 }
307         }
308
309         if (mode == DATA_READ)
310                 return fp;
311
312         if (fp) {
313                 /* reopen with append mode */
314                 fclose(fp);
315                 if ((fp = fopen(file, "ab")) == NULL)
316                         FILE_OP_ERROR(file, "fopen");
317         } else {
318                 /* open with overwrite mode if mark file doesn't exist or
319                    version is different */
320                 fp = msgcache_open_data_file(file, version, DATA_WRITE, buf,
321                                             buf_size);
322         }
323
324         return fp;
325 }
326
327 static gint msgcache_read_cache_data_str(FILE *fp, gchar **str, 
328                                          StringConverter *conv)
329 {
330         gchar *tmpstr = NULL;
331         size_t ni;
332         guint32 len;
333
334         *str = NULL;
335         if ((ni = fread(&len, 1, sizeof(len), fp) != sizeof(len)) ||
336             len > G_MAXINT) {
337                 g_warning("read_data_str: Cache data (len) corrupted, read %d "
338                           "of %d bytes at offset %d\n", ni, sizeof(len), 
339                           ftell(fp));
340                 return -1;
341         }
342
343         if (len == 0)
344                 return 0;
345
346         tmpstr = g_malloc(len + 1);
347
348         if ((ni = fread(tmpstr, 1, len, fp)) != len) {
349                 g_warning("read_data_str: Cache data corrupted, read %d of %d "
350                           "bytes at offset %d\n", 
351                           ni, len, ftell(fp));
352                 g_free(tmpstr);
353                 return -1;
354         }
355         tmpstr[len] = 0;
356
357         if (conv != NULL) {
358                 *str = conv->convert(conv, tmpstr);
359                 g_free(tmpstr);
360         } else 
361                 *str = tmpstr;
362
363         return 0;
364 }
365
366 gchar *strconv_strdup_convert(StringConverter *conv, gchar *srcstr)
367 {
368         return g_strdup(srcstr);
369 }
370
371 gchar *strconv_charset_convert(StringConverter *conv, gchar *srcstr)
372 {
373         CharsetConverter *charsetconv = (CharsetConverter *) conv;
374
375         return conv_codeset_strdup(srcstr, charsetconv->srccharset, charsetconv->dstcharset);
376 }
377
378 void strconv_charset_free(StringConverter *conv)
379 {
380         CharsetConverter *charsetconv = (CharsetConverter *) conv;
381
382         g_free(charsetconv->srccharset);
383         g_free(charsetconv->dstcharset);
384 }
385
386 MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
387 {
388         MsgCache *cache;
389         FILE *fp;
390         MsgInfo *msginfo;
391         MsgTmpFlags tmp_flags = 0;
392         gchar file_buf[BUFFSIZE];
393         guint32 num;
394         gboolean error = FALSE;
395         StringConverter *conv = NULL;
396         gchar *srccharset = NULL;
397         const gchar *dstcharset = NULL;
398
399         g_return_val_if_fail(cache_file != NULL, NULL);
400         g_return_val_if_fail(item != NULL, NULL);
401
402         if ((fp = msgcache_open_data_file
403                 (cache_file, CACHE_VERSION, DATA_READ, file_buf, sizeof(file_buf))) == NULL)
404                 return NULL;
405
406         debug_print("\tReading message cache from %s...\n", cache_file);
407
408         if (item->stype == F_QUEUE) {
409                 tmp_flags |= MSG_QUEUED;
410         } else if (item->stype == F_DRAFT) {
411                 tmp_flags |= MSG_DRAFT;
412         }
413
414         if (msgcache_read_cache_data_str(fp, &srccharset, NULL) < 0)
415                 return NULL;
416         dstcharset = CS_UTF_8;
417         if (srccharset == NULL || dstcharset == NULL) {
418                 conv = NULL;
419         } else if (strcmp(srccharset, dstcharset) == 0) {
420                 StrdupConverter *strdupconv;
421
422                 debug_print("using StrdupConverter\n");
423
424                 strdupconv = g_new0(StrdupConverter, 1);
425                 strdupconv->converter.convert = strconv_strdup_convert;
426                 strdupconv->converter.free = NULL;
427
428                 conv = (StringConverter *) strdupconv;
429         } else {
430                 CharsetConverter *charsetconv;
431
432                 debug_print("using CharsetConverter\n");
433
434                 charsetconv = g_new0(CharsetConverter, 1);
435                 charsetconv->converter.convert = strconv_charset_convert;
436                 charsetconv->converter.free = strconv_charset_free;
437                 charsetconv->srccharset = g_strdup(srccharset);
438                 charsetconv->dstcharset = g_strdup(dstcharset);
439
440                 conv = (StringConverter *) charsetconv;
441         }
442         g_free(srccharset);
443
444         cache = msgcache_new();
445         g_hash_table_freeze(cache->msgnum_table);
446
447         while (fread(&num, sizeof(num), 1, fp) == 1) {
448                 msginfo = procmsg_msginfo_new();
449                 msginfo->msgnum = num;
450                 READ_CACHE_DATA_INT(msginfo->size, fp);
451                 READ_CACHE_DATA_INT(msginfo->mtime, fp);
452                 READ_CACHE_DATA_INT(msginfo->date_t, fp);
453                 READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
454
455                 READ_CACHE_DATA(msginfo->fromname, fp);
456
457                 READ_CACHE_DATA(msginfo->date, fp);
458                 READ_CACHE_DATA(msginfo->from, fp);
459                 READ_CACHE_DATA(msginfo->to, fp);
460                 READ_CACHE_DATA(msginfo->cc, fp);
461                 READ_CACHE_DATA(msginfo->newsgroups, fp);
462                 READ_CACHE_DATA(msginfo->subject, fp);
463                 READ_CACHE_DATA(msginfo->msgid, fp);
464                 READ_CACHE_DATA(msginfo->inreplyto, fp);
465                 READ_CACHE_DATA(msginfo->references, fp);
466                 READ_CACHE_DATA(msginfo->xref, fp);
467                 READ_CACHE_DATA_INT(msginfo->planned_download, fp);
468                 READ_CACHE_DATA_INT(msginfo->total_size, fp);
469
470                 msginfo->folder = item;
471                 msginfo->flags.tmp_flags |= tmp_flags;
472
473                 g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
474                 if(msginfo->msgid)
475                         g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
476                 cache->memusage += procmsg_msginfo_memusage(msginfo);
477         }
478         fclose(fp);
479         g_hash_table_thaw(cache->msgnum_table);
480
481         if (conv != NULL) {
482                 if (conv->free != NULL)
483                         conv->free(conv);
484                 g_free(conv);
485         }
486
487         if(error) {
488                 msgcache_destroy(cache);
489                 return NULL;
490         }
491
492         cache->last_access = time(NULL);
493
494         debug_print("done. (%d items read)\n", g_hash_table_size(cache->msgnum_table));
495         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
496
497         return cache;
498 }
499
500 void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
501 {
502         FILE *fp;
503         MsgInfo *msginfo;
504         MsgPermFlags perm_flags;
505         guint32 num;
506
507         if ((fp = msgcache_open_data_file(mark_file, MARK_VERSION, DATA_READ, NULL, 0)) == NULL)
508                 return;
509
510         debug_print("\tReading message marks from %s...\n", mark_file);
511
512         while (fread(&num, sizeof(num), 1, fp) == 1) {
513                 if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
514
515                 msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
516                 if(msginfo) {
517                         msginfo->flags.perm_flags = perm_flags;
518                 }
519         }
520         fclose(fp);
521 }
522
523 void msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
524 {
525         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
526
527         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
528         WRITE_CACHE_DATA_INT(msginfo->size, fp);
529         WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
530         WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
531         WRITE_CACHE_DATA_INT(flags, fp);
532
533         WRITE_CACHE_DATA(msginfo->fromname, fp);
534
535         WRITE_CACHE_DATA(msginfo->date, fp);
536         WRITE_CACHE_DATA(msginfo->from, fp);
537         WRITE_CACHE_DATA(msginfo->to, fp);
538         WRITE_CACHE_DATA(msginfo->cc, fp);
539         WRITE_CACHE_DATA(msginfo->newsgroups, fp);
540         WRITE_CACHE_DATA(msginfo->subject, fp);
541         WRITE_CACHE_DATA(msginfo->msgid, fp);
542         WRITE_CACHE_DATA(msginfo->inreplyto, fp);
543         WRITE_CACHE_DATA(msginfo->references, fp);
544         WRITE_CACHE_DATA(msginfo->xref, fp);
545         WRITE_CACHE_DATA_INT(msginfo->planned_download, fp);
546         WRITE_CACHE_DATA_INT(msginfo->total_size, fp);
547 }
548
549 static void msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
550 {
551         MsgPermFlags flags = msginfo->flags.perm_flags;
552
553         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
554         WRITE_CACHE_DATA_INT(flags, fp);
555 }
556
557 struct write_fps
558 {
559         FILE *cache_fp;
560         FILE *mark_fp;
561 };
562
563 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
564 {
565         MsgInfo *msginfo;
566         struct write_fps *write_fps;
567
568         msginfo = (MsgInfo *)value;
569         write_fps = user_data;
570
571         msgcache_write_cache(msginfo, write_fps->cache_fp);
572         msgcache_write_flags(msginfo, write_fps->mark_fp);
573 }
574
575 gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
576 {
577         struct write_fps write_fps;
578
579         g_return_val_if_fail(cache_file != NULL, -1);
580         g_return_val_if_fail(mark_file != NULL, -1);
581         g_return_val_if_fail(cache != NULL, -1);
582
583         write_fps.cache_fp = msgcache_open_data_file(cache_file, CACHE_VERSION,
584                 DATA_WRITE, NULL, 0);
585         if (write_fps.cache_fp == NULL)
586                 return -1;
587
588         WRITE_CACHE_DATA(CS_UTF_8, write_fps.cache_fp);
589
590         write_fps.mark_fp = msgcache_open_data_file(mark_file, MARK_VERSION,
591                 DATA_WRITE, NULL, 0);
592         if (write_fps.mark_fp == NULL) {
593                 fclose(write_fps.cache_fp);
594                 return -1;
595         }
596
597         debug_print("\tWriting message cache to %s and %s...\n", cache_file, mark_file);
598
599         if (change_file_mode_rw(write_fps.cache_fp, cache_file) < 0)
600                 FILE_OP_ERROR(cache_file, "chmod");
601
602         g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
603
604         fclose(write_fps.cache_fp);
605         fclose(write_fps.mark_fp);
606
607         cache->last_access = time(NULL);
608
609         debug_print("done.\n");
610         return 0;
611 }
612