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