2004-09-28 [colin] 0.9.12cvs110
[claws.git] / src / msgcache.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto
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
24 #include <time.h>
25
26 #include "intl.h"
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  \
241         if (fread(&idata, sizeof(idata), 1, fp) != 1) { \
242                 g_warning("Cache data is corrupted\n"); \
243                 procmsg_msginfo_free(msginfo); \
244                 error = TRUE; \
245                 break; \
246         } else \
247                 n = idata;\
248 }
249
250 #define WRITE_CACHE_DATA_INT(n, fp)             \
251 {                                               \
252         guint32 idata;                          \
253                                                 \
254         idata = (guint32)n;                     \
255         fwrite(&idata, sizeof(idata), 1, fp);   \
256 }
257
258 #define WRITE_CACHE_DATA(data, fp) \
259 { \
260         size_t len; \
261         if (data == NULL) \
262                 len = 0; \
263         else \
264                 len = strlen(data); \
265         WRITE_CACHE_DATA_INT(len, fp); \
266         if (len > 0) { \
267                 fwrite(data, len, 1, fp); \
268         } \
269 }
270
271 static FILE *msgcache_open_data_file(const gchar *file, gint version,
272                                      DataOpenMode mode,
273                                      gchar *buf, size_t buf_size)
274 {
275         FILE *fp;
276         gint data_ver;
277
278         g_return_val_if_fail(file != NULL, NULL);
279
280         if (mode == DATA_WRITE) {
281                 if ((fp = fopen(file, "wb")) == NULL) {
282                         FILE_OP_ERROR(file, "fopen");
283                         return NULL;
284                 }
285                 if (change_file_mode_rw(fp, file) < 0)
286                         FILE_OP_ERROR(file, "chmod");
287
288                 WRITE_CACHE_DATA_INT(version, fp);
289                 return fp;
290         }
291
292         /* check version */
293         if ((fp = fopen(file, "rb")) == NULL)
294                 debug_print("Mark/Cache file not found\n");
295         else {
296                 if (buf && buf_size > 0)
297                         setvbuf(fp, buf, _IOFBF, buf_size);
298                 if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
299                          version != data_ver) {
300                         debug_print("Mark/Cache version is different (%d != %d). "
301                                     "Discarding it.\n", data_ver, version);
302                         fclose(fp);
303                         fp = NULL;
304                 }
305         }
306
307         if (mode == DATA_READ)
308                 return fp;
309
310         if (fp) {
311                 /* reopen with append mode */
312                 fclose(fp);
313                 if ((fp = fopen(file, "ab")) == NULL)
314                         FILE_OP_ERROR(file, "fopen");
315         } else {
316                 /* open with overwrite mode if mark file doesn't exist or
317                    version is different */
318                 fp = msgcache_open_data_file(file, version, DATA_WRITE, buf,
319                                             buf_size);
320         }
321
322         return fp;
323 }
324
325 static gint msgcache_read_cache_data_str(FILE *fp, gchar **str, StringConverter *conv)
326 {
327         gchar buf[BUFFSIZE], *tmpstr = NULL;
328         gint ret = 0;
329         guint32 len;
330
331         if (fread(&len, sizeof(len), 1, fp) == 1) {
332                 if (len > G_MAXINT)
333                         ret = -1;
334                 else {
335                         gchar *tmp = NULL;
336
337                         while (len > 0) {
338                                 size_t size = MIN(len, BUFFSIZE - 1);
339
340                                 if (fread(buf, size, 1, fp) != 1) {
341                                         ret = -1;
342                                         if (tmp) g_free(tmp);
343                                         tmpstr = NULL;
344                                         break;
345                                 }
346
347                                 buf[size] = '\0';
348                                 if (tmp) {
349                                         *str = g_strconcat(tmp, buf, NULL);
350                                         g_free(tmp);
351                                         tmp = tmpstr;
352                                 } else
353                                         tmp = tmpstr = g_strdup(buf);
354
355                                 len -= size;
356                         }
357                 }
358         } else
359                 ret = -1;
360
361         if (ret < 0)
362                 g_warning("Cache data is corrupted\n");
363
364         if (tmpstr != NULL && conv != NULL) {
365                 *str = conv->convert(conv, tmpstr);
366                 g_free(tmpstr);
367         } else if (tmpstr != NULL) {
368                 *str = g_strdup(tmpstr);
369                 g_free(tmpstr);
370         } else
371                 *str = NULL;
372
373         return ret;
374 }
375
376 gchar *strconv_strdup_convert(StringConverter *conv, gchar *srcstr)
377 {
378         return g_strdup(srcstr);
379 }
380
381 gchar *strconv_charset_convert(StringConverter *conv, gchar *srcstr)
382 {
383         CharsetConverter *charsetconv = (CharsetConverter *) conv;
384
385         return conv_codeset_strdup(srcstr, charsetconv->srccharset, charsetconv->dstcharset);
386 }
387
388 void strconv_charset_free(StringConverter *conv)
389 {
390         CharsetConverter *charsetconv = (CharsetConverter *) conv;
391
392         g_free(charsetconv->srccharset);
393         g_free(charsetconv->dstcharset);
394 }
395
396 MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
397 {
398         MsgCache *cache;
399         FILE *fp;
400         MsgInfo *msginfo;
401         MsgTmpFlags tmp_flags = 0;
402         gchar file_buf[BUFFSIZE];
403         guint num;
404         gboolean error = FALSE;
405         StringConverter *conv = NULL;
406         gchar *srccharset = NULL;
407         const gchar *dstcharset = NULL;
408
409         g_return_val_if_fail(cache_file != NULL, NULL);
410         g_return_val_if_fail(item != NULL, NULL);
411
412         if ((fp = msgcache_open_data_file
413                 (cache_file, CACHE_VERSION, DATA_READ, file_buf, sizeof(file_buf))) == NULL)
414                 return NULL;
415
416         debug_print("\tReading message cache from %s...\n", cache_file);
417
418         if (item->stype == F_QUEUE) {
419                 tmp_flags |= MSG_QUEUED;
420         } else if (item->stype == F_DRAFT) {
421                 tmp_flags |= MSG_DRAFT;
422         }
423
424         if (msgcache_read_cache_data_str(fp, &srccharset, NULL) < 0)
425                 return NULL;
426         dstcharset = conv_get_current_charset_str();
427         if (srccharset == NULL || dstcharset == NULL) {
428                 conv = NULL;
429         } else if (strcmp(srccharset, dstcharset) == 0) {
430                 StrdupConverter *strdupconv;
431
432                 debug_print("using StrdupConverter\n");
433
434                 strdupconv = g_new0(StrdupConverter, 1);
435                 strdupconv->converter.convert = strconv_strdup_convert;
436                 strdupconv->converter.free = NULL;
437
438                 conv = (StringConverter *) strdupconv;
439         } else {
440                 CharsetConverter *charsetconv;
441
442                 debug_print("using CharsetConverter\n");
443
444                 charsetconv = g_new0(CharsetConverter, 1);
445                 charsetconv->converter.convert = strconv_charset_convert;
446                 charsetconv->converter.free = strconv_charset_free;
447                 charsetconv->srccharset = g_strdup(srccharset);
448                 charsetconv->dstcharset = g_strdup(dstcharset);
449
450                 conv = (StringConverter *) charsetconv;
451         }
452         g_free(srccharset);
453
454         cache = msgcache_new();
455         g_hash_table_freeze(cache->msgnum_table);
456
457         while (fread(&num, sizeof(num), 1, fp) == 1) {
458                 msginfo = procmsg_msginfo_new();
459                 msginfo->msgnum = num;
460                 READ_CACHE_DATA_INT(msginfo->size, fp);
461                 READ_CACHE_DATA_INT(msginfo->mtime, fp);
462                 READ_CACHE_DATA_INT(msginfo->date_t, fp);
463                 READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
464
465                 READ_CACHE_DATA(msginfo->fromname, fp);
466
467                 READ_CACHE_DATA(msginfo->date, fp);
468                 READ_CACHE_DATA(msginfo->from, fp);
469                 READ_CACHE_DATA(msginfo->to, fp);
470                 READ_CACHE_DATA(msginfo->cc, fp);
471                 READ_CACHE_DATA(msginfo->newsgroups, fp);
472                 READ_CACHE_DATA(msginfo->subject, fp);
473                 READ_CACHE_DATA(msginfo->msgid, fp);
474                 READ_CACHE_DATA(msginfo->inreplyto, fp);
475                 READ_CACHE_DATA(msginfo->references, fp);
476                 READ_CACHE_DATA(msginfo->xref, fp);
477                 READ_CACHE_DATA_INT(msginfo->planned_download, fp);
478
479                 msginfo->folder = item;
480                 msginfo->flags.tmp_flags |= tmp_flags;
481
482                 g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
483                 if(msginfo->msgid)
484                         g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
485                 cache->memusage += procmsg_msginfo_memusage(msginfo);
486         }
487         fclose(fp);
488         g_hash_table_thaw(cache->msgnum_table);
489
490         if(error) {
491                 msgcache_destroy(cache);
492                 return NULL;
493         }
494
495         if (conv != NULL) {
496                 if (conv->free != NULL)
497                         conv->free(conv);
498                 g_free(conv);
499         }
500
501         cache->last_access = time(NULL);
502
503         debug_print("done. (%d items read)\n", g_hash_table_size(cache->msgnum_table));
504         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
505
506         return cache;
507 }
508
509 void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
510 {
511         FILE *fp;
512         MsgInfo *msginfo;
513         MsgPermFlags perm_flags;
514         guint num;
515
516         if ((fp = msgcache_open_data_file(mark_file, MARK_VERSION, DATA_READ, NULL, 0)) == NULL)
517                 return;
518
519         debug_print("\tReading message marks from %s...\n", mark_file);
520
521         while (fread(&num, sizeof(num), 1, fp) == 1) {
522                 if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
523
524                 msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
525                 if(msginfo) {
526                         msginfo->flags.perm_flags = perm_flags;
527                 }
528         }
529         fclose(fp);
530 }
531
532 void msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
533 {
534         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
535
536         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
537         WRITE_CACHE_DATA_INT(msginfo->size, fp);
538         WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
539         WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
540         WRITE_CACHE_DATA_INT(flags, fp);
541
542         WRITE_CACHE_DATA(msginfo->fromname, fp);
543
544         WRITE_CACHE_DATA(msginfo->date, fp);
545         WRITE_CACHE_DATA(msginfo->from, fp);
546         WRITE_CACHE_DATA(msginfo->to, fp);
547         WRITE_CACHE_DATA(msginfo->cc, fp);
548         WRITE_CACHE_DATA(msginfo->newsgroups, fp);
549         WRITE_CACHE_DATA(msginfo->subject, fp);
550         WRITE_CACHE_DATA(msginfo->msgid, fp);
551         WRITE_CACHE_DATA(msginfo->inreplyto, fp);
552         WRITE_CACHE_DATA(msginfo->references, fp);
553         WRITE_CACHE_DATA(msginfo->xref, fp);
554         WRITE_CACHE_DATA_INT(msginfo->planned_download, fp);
555 }
556
557 static void msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
558 {
559         MsgPermFlags flags = msginfo->flags.perm_flags;
560
561         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
562         WRITE_CACHE_DATA_INT(flags, fp);
563 }
564
565 struct write_fps
566 {
567         FILE *cache_fp;
568         FILE *mark_fp;
569 };
570
571 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
572 {
573         MsgInfo *msginfo;
574         struct write_fps *write_fps;
575
576         msginfo = (MsgInfo *)value;
577         write_fps = user_data;
578
579         msgcache_write_cache(msginfo, write_fps->cache_fp);
580         msgcache_write_flags(msginfo, write_fps->mark_fp);
581 }
582
583 gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
584 {
585         struct write_fps write_fps;
586
587         g_return_val_if_fail(cache_file != NULL, -1);
588         g_return_val_if_fail(mark_file != NULL, -1);
589         g_return_val_if_fail(cache != NULL, -1);
590
591         write_fps.cache_fp = msgcache_open_data_file(cache_file, CACHE_VERSION,
592                 DATA_WRITE, NULL, 0);
593         if (write_fps.cache_fp == NULL)
594                 return -1;
595
596         WRITE_CACHE_DATA(conv_get_current_charset_str(), write_fps.cache_fp);
597
598         write_fps.mark_fp = msgcache_open_data_file(mark_file, MARK_VERSION,
599                 DATA_WRITE, NULL, 0);
600         if (write_fps.mark_fp == NULL) {
601                 fclose(write_fps.cache_fp);
602                 return -1;
603         }
604
605         debug_print("\tWriting message cache to %s and %s...\n", cache_file, mark_file);
606
607         if (change_file_mode_rw(write_fps.cache_fp, cache_file) < 0)
608                 FILE_OP_ERROR(cache_file, "chmod");
609
610         g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
611
612         fclose(write_fps.cache_fp);
613         fclose(write_fps.mark_fp);
614
615         cache->last_access = time(NULL);
616
617         debug_print("done.\n");
618         return 0;
619 }
620