2005-07-01 [colin] 1.9.12cvs8
[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, total_len) \
229 { \
230         if ((tmp_len = msgcache_read_cache_data_str(fp, &data, conv)) < 0) { \
231                 procmsg_msginfo_free(msginfo); \
232                 error = TRUE; \
233                 break; \
234         } \
235         total_len += tmp_len; \
236 }
237
238 #define READ_CACHE_DATA_INT(n, fp) \
239 { \
240         guint32 idata; \
241         size_t ni; \
242  \
243         if ((ni = fread(&idata, 1, sizeof(idata), fp)) != sizeof(idata)) { \
244                 g_warning("read_int: Cache data corrupted, read %d of %d at " \
245                           "offset %ld\n", ni, sizeof(idata), ftell(fp)); \
246                 procmsg_msginfo_free(msginfo); \
247                 error = TRUE; \
248                 break; \
249         } else \
250                 n = idata;\
251 }
252
253 #define WRITE_CACHE_DATA_INT(n, fp)             \
254 {                                               \
255         guint32 idata;                          \
256                                                 \
257         idata = (guint32)n;                     \
258         fwrite(&idata, sizeof(idata), 1, fp);   \
259 }
260
261 #define WRITE_CACHE_DATA(data, fp) \
262 { \
263         size_t len; \
264         if (data == NULL) \
265                 len = 0; \
266         else \
267                 len = strlen(data); \
268         WRITE_CACHE_DATA_INT(len, fp); \
269         if (len > 0) { \
270                 fwrite(data, len, 1, fp); \
271         } \
272 }
273
274 static FILE *msgcache_open_data_file(const gchar *file, guint version,
275                                      DataOpenMode mode,
276                                      gchar *buf, size_t buf_size)
277 {
278         FILE *fp;
279         gint32 data_ver;
280
281         g_return_val_if_fail(file != NULL, NULL);
282
283         if (mode == DATA_WRITE) {
284                 if ((fp = fopen(file, "wb")) == NULL) {
285                         FILE_OP_ERROR(file, "fopen");
286                         return NULL;
287                 }
288                 if (change_file_mode_rw(fp, file) < 0)
289                         FILE_OP_ERROR(file, "chmod");
290
291                 WRITE_CACHE_DATA_INT(version, fp);
292                 return fp;
293         }
294
295         /* check version */
296         if ((fp = fopen(file, "rb")) == NULL)
297                 debug_print("Mark/Cache file '%s' not found\n", file);
298         else {
299                 if (buf && buf_size > 0)
300                         setvbuf(fp, buf, _IOFBF, buf_size);
301                 if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
302                          version != data_ver) {
303                         g_message("%s: Mark/Cache version is different (%u != %u). Discarding it.\n",
304                                   file, data_ver, version);
305                         fclose(fp);
306                         fp = NULL;
307                 }
308         }
309         
310         if (mode == DATA_READ)
311                 return fp;
312
313         if (fp) {
314                 /* reopen with append mode */
315                 fclose(fp);
316                 if ((fp = fopen(file, "ab")) == NULL)
317                         FILE_OP_ERROR(file, "fopen");
318         } else {
319                 /* open with overwrite mode if mark file doesn't exist or
320                    version is different */
321                 fp = msgcache_open_data_file(file, version, DATA_WRITE, buf,
322                                             buf_size);
323         }
324
325         return fp;
326 }
327
328 static gint msgcache_read_cache_data_str(FILE *fp, gchar **str, 
329                                          StringConverter *conv)
330 {
331         gchar *tmpstr = NULL;
332         size_t ni;
333         guint32 len;
334
335         *str = NULL;
336         if ((ni = fread(&len, 1, sizeof(len), fp) != sizeof(len)) ||
337             len > G_MAXINT) {
338                 g_warning("read_data_str: Cache data (len) corrupted, read %d "
339                           "of %d bytes at offset %ld\n", ni, sizeof(len), 
340                           ftell(fp));
341                 return -1;
342         }
343
344         if (len == 0)
345                 return 0;
346
347         tmpstr = g_malloc(len + 1);
348
349         if ((ni = fread(tmpstr, 1, len, fp)) != len) {
350                 g_warning("read_data_str: Cache data corrupted, read %d of %d "
351                           "bytes at offset %ld\n", 
352                           ni, len, ftell(fp));
353                 g_free(tmpstr);
354                 return -1;
355         }
356         tmpstr[len] = 0;
357
358         if (conv != NULL) {
359                 *str = conv->convert(conv, tmpstr);
360                 g_free(tmpstr);
361         } else 
362                 *str = tmpstr;
363
364         return len;
365 }
366
367 gchar *strconv_strdup_convert(StringConverter *conv, gchar *srcstr)
368 {
369         return g_strdup(srcstr);
370 }
371
372 gchar *strconv_charset_convert(StringConverter *conv, gchar *srcstr)
373 {
374         CharsetConverter *charsetconv = (CharsetConverter *) conv;
375
376         return conv_codeset_strdup(srcstr, charsetconv->srccharset, charsetconv->dstcharset);
377 }
378
379 void strconv_charset_free(StringConverter *conv)
380 {
381         CharsetConverter *charsetconv = (CharsetConverter *) conv;
382
383         g_free(charsetconv->srccharset);
384         g_free(charsetconv->dstcharset);
385 }
386
387 MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
388 {
389         MsgCache *cache;
390         FILE *fp;
391         MsgInfo *msginfo;
392         MsgTmpFlags tmp_flags = 0;
393         gchar file_buf[BUFFSIZE];
394         guint32 num;
395         guint refnum;
396         gboolean error = FALSE;
397         StringConverter *conv = NULL;
398         gchar *srccharset = NULL;
399         const gchar *dstcharset = NULL;
400         gchar *ref = NULL;
401         guint memusage = 0;
402         guint tmp_len = 0;
403 #if 0
404         struct timeval start;
405         struct timeval end;
406         struct timeval diff;
407         gettimeofday(&start, NULL);
408 #endif
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 = CS_UTF_8;
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         g_hash_table_freeze(cache->msgid_table);
457
458         while (fread(&num, sizeof(num), 1, fp) == 1) {
459                 msginfo = procmsg_msginfo_new();
460                 msginfo->msgnum = num;
461                 memusage += sizeof(MsgInfo);
462                 
463                 READ_CACHE_DATA_INT(msginfo->size, fp);
464                 READ_CACHE_DATA_INT(msginfo->mtime, fp);
465                 READ_CACHE_DATA_INT(msginfo->date_t, fp);
466                 READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
467                                 
468                 READ_CACHE_DATA(msginfo->fromname, fp, memusage);
469
470                 READ_CACHE_DATA(msginfo->date, fp, memusage);
471                 READ_CACHE_DATA(msginfo->from, fp, memusage);
472                 READ_CACHE_DATA(msginfo->to, fp, memusage);
473                 READ_CACHE_DATA(msginfo->cc, fp, memusage);
474                 READ_CACHE_DATA(msginfo->newsgroups, fp, memusage);
475                 READ_CACHE_DATA(msginfo->subject, fp, memusage);
476                 READ_CACHE_DATA(msginfo->msgid, fp, memusage);
477                 READ_CACHE_DATA(msginfo->inreplyto, fp, memusage);
478                 READ_CACHE_DATA(msginfo->xref, fp, memusage);
479                 
480                 READ_CACHE_DATA_INT(msginfo->planned_download, fp);
481                 READ_CACHE_DATA_INT(msginfo->total_size, fp);
482                 READ_CACHE_DATA_INT(refnum, fp);
483                 
484                 for (; refnum != 0; refnum--) {
485                         ref = NULL;
486
487                         READ_CACHE_DATA(ref, fp, memusage);
488
489                         if (ref && strlen(ref))
490                                 msginfo->references =
491                                         g_slist_prepend(msginfo->references, ref);
492                 }
493                 if (msginfo->references)
494                         msginfo->references =
495                                 g_slist_reverse(msginfo->references);
496
497                 msginfo->folder = item;
498                 msginfo->flags.tmp_flags |= tmp_flags;
499
500                 g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
501                 if(msginfo->msgid)
502                         g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
503         }
504         fclose(fp);
505         g_hash_table_thaw(cache->msgnum_table);
506         g_hash_table_thaw(cache->msgid_table);
507
508         if (conv != NULL) {
509                 if (conv->free != NULL)
510                         conv->free(conv);
511                 g_free(conv);
512         }
513
514         if(error) {
515                 msgcache_destroy(cache);
516                 return NULL;
517         }
518
519         cache->last_access = time(NULL);
520         cache->memusage = memusage;
521
522         debug_print("done. (%d items read)\n", g_hash_table_size(cache->msgnum_table));
523         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
524 #if 0
525         gettimeofday(&end, NULL);
526         timersub(&end, &start, &diff);
527         printf("spent %d seconds %d usages %d;%d\n", diff.tv_sec, diff.tv_usec, cache->memusage, memusage);
528 #endif
529         return cache;
530 }
531
532 void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
533 {
534         FILE *fp;
535         MsgInfo *msginfo;
536         MsgPermFlags perm_flags;
537         guint32 num;
538
539         if ((fp = msgcache_open_data_file(mark_file, MARK_VERSION, DATA_READ, NULL, 0)) == NULL)
540                 return;
541
542         debug_print("\tReading message marks from %s...\n", mark_file);
543
544         while (fread(&num, sizeof(num), 1, fp) == 1) {
545                 if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
546
547                 msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
548                 if(msginfo) {
549                         msginfo->flags.perm_flags = perm_flags;
550                 }
551         }
552         fclose(fp);
553 }
554
555 void msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
556 {
557         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
558         GSList *cur;
559
560         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
561         WRITE_CACHE_DATA_INT(msginfo->size, fp);
562         WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
563         WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
564         WRITE_CACHE_DATA_INT(flags, fp);
565
566         WRITE_CACHE_DATA(msginfo->fromname, fp);
567
568         WRITE_CACHE_DATA(msginfo->date, fp);
569         WRITE_CACHE_DATA(msginfo->from, fp);
570         WRITE_CACHE_DATA(msginfo->to, fp);
571         WRITE_CACHE_DATA(msginfo->cc, fp);
572         WRITE_CACHE_DATA(msginfo->newsgroups, fp);
573         WRITE_CACHE_DATA(msginfo->subject, fp);
574         WRITE_CACHE_DATA(msginfo->msgid, fp);
575         WRITE_CACHE_DATA(msginfo->inreplyto, fp);
576         WRITE_CACHE_DATA(msginfo->xref, fp);
577         WRITE_CACHE_DATA_INT(msginfo->planned_download, fp);
578         WRITE_CACHE_DATA_INT(msginfo->total_size, fp);
579         
580         WRITE_CACHE_DATA_INT(g_slist_length(msginfo->references), fp);
581
582         for (cur = msginfo->references; cur != NULL; cur = cur->next) {
583                 WRITE_CACHE_DATA((gchar *)cur->data, fp);
584         }
585 }
586
587 static void msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
588 {
589         MsgPermFlags flags = msginfo->flags.perm_flags;
590
591         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
592         WRITE_CACHE_DATA_INT(flags, fp);
593 }
594
595 struct write_fps
596 {
597         FILE *cache_fp;
598         FILE *mark_fp;
599 };
600
601 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
602 {
603         MsgInfo *msginfo;
604         struct write_fps *write_fps;
605
606         msginfo = (MsgInfo *)value;
607         write_fps = user_data;
608
609         msgcache_write_cache(msginfo, write_fps->cache_fp);
610         msgcache_write_flags(msginfo, write_fps->mark_fp);
611 }
612
613 gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
614 {
615         struct write_fps write_fps;
616
617         g_return_val_if_fail(cache_file != NULL, -1);
618         g_return_val_if_fail(mark_file != NULL, -1);
619         g_return_val_if_fail(cache != NULL, -1);
620
621         write_fps.cache_fp = msgcache_open_data_file(cache_file, CACHE_VERSION,
622                 DATA_WRITE, NULL, 0);
623         if (write_fps.cache_fp == NULL)
624                 return -1;
625
626         WRITE_CACHE_DATA(CS_UTF_8, write_fps.cache_fp);
627
628         write_fps.mark_fp = msgcache_open_data_file(mark_file, MARK_VERSION,
629                 DATA_WRITE, NULL, 0);
630         if (write_fps.mark_fp == NULL) {
631                 fclose(write_fps.cache_fp);
632                 return -1;
633         }
634
635         debug_print("\tWriting message cache to %s and %s...\n", cache_file, mark_file);
636
637         if (change_file_mode_rw(write_fps.cache_fp, cache_file) < 0)
638                 FILE_OP_ERROR(cache_file, "chmod");
639
640         g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
641
642         fclose(write_fps.cache_fp);
643         fclose(write_fps.mark_fp);
644
645         cache->last_access = time(NULL);
646
647         debug_print("done.\n");
648         return 0;
649 }
650