b17d2c4093d0d980437d47e035d9492a56cbd2c6
[claws.git] / src / msgcache.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #include "defs.h"
21
22 #include <glib.h>
23 #include <glib/gi18n.h>
24 #include <sys/mman.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27
28 #include <time.h>
29
30 #include "msgcache.h"
31 #include "utils.h"
32 #include "procmsg.h"
33 #include "codeconv.h"
34 #include "timing.h"
35
36 #if G_BYTE_ORDER == G_BIG_ENDIAN
37 #define bswap_32(x) \
38      ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) | \
39       (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))
40      
41 #define MMAP_TO_GUINT32(x)      \
42         (((x[3]&0xff)) |        \
43          ((x[2]&0xff) << 8) |   \
44          ((x[1]&0xff) << 16) |  \
45          ((x[0]&0xff) << 24))
46
47 #define MMAP_TO_GUINT32_SWAPPED(x)      \
48         (((x[0]&0xff)) |                \
49          ((x[1]&0xff) << 8) |           \
50          ((x[2]&0xff) << 16) |          \
51          ((x[3]&0xff) << 24))
52
53 static gboolean msgcache_use_mmap = FALSE;
54
55 #else
56 #define bswap_32(x) (x)
57
58 #define MMAP_TO_GUINT32(x)      \
59         (((x[0]&0xff)) |        \
60          ((x[1]&0xff) << 8) |   \
61          ((x[2]&0xff) << 16) |  \
62          ((x[3]&0xff) << 24))
63
64 #define MMAP_TO_GUINT32_SWAPPED(x)      \
65         (((x[0]&0xff)) |                \
66          ((x[1]&0xff) << 8) |           \
67          ((x[2]&0xff) << 16) |          \
68          ((x[3]&0xff) << 24))
69
70 static gboolean msgcache_use_mmap = FALSE;
71 #endif
72
73 static gboolean swapping = TRUE;
74
75 typedef enum
76 {
77         DATA_READ,
78         DATA_WRITE,
79         DATA_APPEND
80 } DataOpenMode;
81
82 struct _MsgCache {
83         GHashTable      *msgnum_table;
84         GHashTable      *msgid_table;
85         guint            memusage;
86         time_t           last_access;
87 };
88
89 typedef struct _StringConverter StringConverter;
90 struct _StringConverter {
91         gchar *(*convert) (StringConverter *converter, gchar *srcstr);
92         void   (*free)    (StringConverter *converter);
93 };
94
95 typedef struct _StrdupConverter StrdupConverter;
96 struct _StrdupConverter {
97         StringConverter converter;
98 };
99
100 typedef struct _CharsetConverter CharsetConverter;
101 struct _CharsetConverter {
102         StringConverter converter;
103
104         gchar *srccharset;
105         gchar *dstcharset;
106 };
107
108 MsgCache *msgcache_new(void)
109 {
110         MsgCache *cache;
111         
112         cache = g_new0(MsgCache, 1),
113         cache->msgnum_table = g_hash_table_new(g_int_hash, g_int_equal);
114         cache->msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
115         cache->last_access = time(NULL);
116
117         return cache;
118 }
119
120 static gboolean msgcache_msginfo_free_func(gpointer num, gpointer msginfo, gpointer user_data)
121 {
122         procmsg_msginfo_free((MsgInfo *)msginfo);
123         return TRUE;
124 }                                                                                         
125
126 void msgcache_destroy(MsgCache *cache)
127 {
128         g_return_if_fail(cache != NULL);
129
130         g_hash_table_foreach_remove(cache->msgnum_table, msgcache_msginfo_free_func, NULL);
131         g_hash_table_destroy(cache->msgid_table);
132         g_hash_table_destroy(cache->msgnum_table);
133         g_free(cache);
134 }
135
136 void msgcache_add_msg(MsgCache *cache, MsgInfo *msginfo) 
137 {
138         MsgInfo *newmsginfo;
139
140         g_return_if_fail(cache != NULL);
141         g_return_if_fail(msginfo != NULL);
142
143         newmsginfo = procmsg_msginfo_new_ref(msginfo);
144         g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
145         if(newmsginfo->msgid != NULL)
146                 g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
147         cache->memusage += procmsg_msginfo_memusage(msginfo);
148         cache->last_access = time(NULL);
149
150         debug_print("Cache size: %d messages, %d bytes\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
151 }
152
153 void msgcache_remove_msg(MsgCache *cache, guint msgnum)
154 {
155         MsgInfo *msginfo;
156
157         g_return_if_fail(cache != NULL);
158         g_return_if_fail(msgnum > 0);
159
160         msginfo = (MsgInfo *) g_hash_table_lookup(cache->msgnum_table, &msgnum);
161         if(!msginfo)
162                 return;
163
164         cache->memusage -= procmsg_msginfo_memusage(msginfo);
165         if(msginfo->msgid)
166                 g_hash_table_remove(cache->msgid_table, msginfo->msgid);
167         g_hash_table_remove(cache->msgnum_table, &msginfo->msgnum);
168         procmsg_msginfo_free(msginfo);
169         cache->last_access = time(NULL);
170
171         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
172 }
173
174 void msgcache_update_msg(MsgCache *cache, MsgInfo *msginfo)
175 {
176         MsgInfo *oldmsginfo, *newmsginfo;
177         
178         g_return_if_fail(cache != NULL);
179         g_return_if_fail(msginfo != NULL);
180
181         oldmsginfo = g_hash_table_lookup(cache->msgnum_table, &msginfo->msgnum);
182         if(oldmsginfo && oldmsginfo->msgid) 
183                 g_hash_table_remove(cache->msgid_table, oldmsginfo->msgid);
184         if (oldmsginfo) {
185                 g_hash_table_remove(cache->msgnum_table, &oldmsginfo->msgnum);
186                 cache->memusage -= procmsg_msginfo_memusage(oldmsginfo);
187                 procmsg_msginfo_free(oldmsginfo);
188         }
189
190         newmsginfo = procmsg_msginfo_new_ref(msginfo);
191         g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
192         if(newmsginfo->msgid)
193                 g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
194         cache->memusage += procmsg_msginfo_memusage(newmsginfo);
195         cache->last_access = time(NULL);
196         
197         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
198
199         return;
200 }
201
202 MsgInfo *msgcache_get_msg(MsgCache *cache, guint num)
203 {
204         MsgInfo *msginfo;
205
206         g_return_val_if_fail(cache != NULL, NULL);
207
208         msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
209         if(!msginfo)
210                 return NULL;
211         cache->last_access = time(NULL);
212         
213         return procmsg_msginfo_new_ref(msginfo);
214 }
215
216 MsgInfo *msgcache_get_msg_by_id(MsgCache *cache, const gchar *msgid)
217 {
218         MsgInfo *msginfo;
219         
220         g_return_val_if_fail(cache != NULL, NULL);
221         g_return_val_if_fail(msgid != NULL, NULL);
222
223         msginfo = g_hash_table_lookup(cache->msgid_table, msgid);
224         if(!msginfo)
225                 return NULL;
226         cache->last_access = time(NULL);
227         
228         return procmsg_msginfo_new_ref(msginfo);        
229 }
230
231 static void msgcache_get_msg_list_func(gpointer key, gpointer value, gpointer user_data)
232 {
233         MsgInfoList **listptr = user_data;
234         MsgInfo *msginfo = value;
235
236         *listptr = g_slist_prepend(*listptr, procmsg_msginfo_new_ref(msginfo));
237 }
238
239 MsgInfoList *msgcache_get_msg_list(MsgCache *cache)
240 {
241         MsgInfoList *msg_list = NULL;
242         START_TIMING("msgcache_get_msg_list");
243         g_return_val_if_fail(cache != NULL, NULL);
244
245         g_hash_table_foreach((GHashTable *)cache->msgnum_table, msgcache_get_msg_list_func, (gpointer)&msg_list);       
246         cache->last_access = time(NULL);
247         
248         msg_list = g_slist_reverse(msg_list);
249         END_TIMING();
250         return msg_list;
251 }
252
253 time_t msgcache_get_last_access_time(MsgCache *cache)
254 {
255         g_return_val_if_fail(cache != NULL, 0);
256         
257         return cache->last_access;
258 }
259
260 gint msgcache_get_memory_usage(MsgCache *cache)
261 {
262         g_return_val_if_fail(cache != NULL, 0);
263
264         return cache->memusage;
265 }
266
267 /*
268  *  Cache saving functions
269  */
270
271 #define READ_CACHE_DATA(data, fp, total_len) \
272 { \
273         if ((tmp_len = msgcache_read_cache_data_str(fp, &data, conv)) < 0) { \
274                 procmsg_msginfo_free(msginfo); \
275                 error = TRUE; \
276                 goto bail_err; \
277         } \
278         total_len += tmp_len; \
279 }
280
281 #define READ_CACHE_DATA_INT(n, fp) \
282 { \
283         guint32 idata; \
284         size_t ni; \
285  \
286         if ((ni = fread(&idata, sizeof(idata), 1, fp)) != 1) { \
287                 g_warning("read_int: Cache data corrupted, read %d of %d at " \
288                           "offset %ld\n", ni, sizeof(idata), ftell(fp)); \
289                 procmsg_msginfo_free(msginfo); \
290                 error = TRUE; \
291                 goto bail_err; \
292         } else \
293                 n = swapping ? bswap_32(idata) : (idata);\
294 }
295
296 #define GET_CACHE_DATA_INT(n) \
297 { \
298         n = (swapping ? (MMAP_TO_GUINT32_SWAPPED(walk_data)):(MMAP_TO_GUINT32(walk_data))); \
299         walk_data += 4; rem_len -= 4;                   \
300 }
301
302 #define GET_CACHE_DATA(data, total_len) \
303 { \
304         GET_CACHE_DATA_INT(tmp_len);    \
305         if ((tmp_len = msgcache_get_cache_data_str(walk_data, &data, tmp_len, conv)) < 0) { \
306                 printf("error at rem_len:%d\n", rem_len);\
307                 procmsg_msginfo_free(msginfo); \
308                 error = TRUE; \
309                 goto bail_err; \
310         } \
311         total_len += tmp_len; \
312         walk_data += tmp_len; rem_len -= tmp_len; \
313 }
314
315
316 #define WRITE_CACHE_DATA_INT(n, fp)                     \
317 {                                                       \
318         guint32 idata;                                  \
319                                                         \
320         idata = (guint32)bswap_32(n);                   \
321         if (fwrite(&idata, sizeof(idata), 1, fp) != 1)  \
322                 w_err = 1;                              \
323         wrote += 4;                                     \
324 }
325
326 #define PUT_CACHE_DATA_INT(n)                           \
327 {                                                       \
328         walk_data[0]=(((guint32)n)&0x000000ff);                 \
329         walk_data[1]=(((guint32)n)&0x0000ff00)>>8;              \
330         walk_data[2]=(((guint32)n)&0x00ff0000)>>16;             \
331         walk_data[3]=(((guint32)n)&0xff000000)>>24;             \
332         walk_data += 4;                                 \
333         wrote += 4;                                     \
334 }
335
336 #define WRITE_CACHE_DATA(data, fp) \
337 { \
338         size_t len;                                     \
339         if (data == NULL)                               \
340                 len = 0;                                \
341         else                                            \
342                 len = strlen(data);                     \
343         WRITE_CACHE_DATA_INT(len, fp);                  \
344         if (w_err == 0 && len > 0) {                    \
345                 if (fwrite(data, 1, len, fp) != len)    \
346                         w_err = 1;                      \
347                 wrote += len;                           \
348         } \
349 }
350
351 #define PUT_CACHE_DATA(data)                            \
352 {                                                       \
353         size_t len;                                     \
354         if (data == NULL)                               \
355                 len = 0;                                \
356         else                                            \
357                 len = strlen(data);                     \
358         PUT_CACHE_DATA_INT(len);                        \
359         if (len > 0) {                                  \
360                 memcpy(walk_data, data, len);           \
361                 walk_data += len;                       \
362                 wrote += len;                           \
363         }                                               \
364 }
365
366 static FILE *msgcache_open_data_file(const gchar *file, guint version,
367                                      DataOpenMode mode,
368                                      gchar *buf, size_t buf_size)
369 {
370         FILE *fp;
371         gint32 data_ver;
372
373         g_return_val_if_fail(file != NULL, NULL);
374
375         if (mode == DATA_WRITE) {
376                 int w_err = 0, wrote = 0;
377                 if ((fp = g_fopen(file, "w+")) == NULL) {
378                         FILE_OP_ERROR(file, "fopen");
379                         return NULL;
380                 }
381                 if (change_file_mode_rw(fp, file) < 0)
382                         FILE_OP_ERROR(file, "chmod");
383
384                 WRITE_CACHE_DATA_INT(version, fp);
385                 if (w_err != 0) {
386                         g_warning("failed to write int\n");
387                         fclose(fp);
388                         return NULL;
389                 }
390                 return fp;
391         }
392
393         /* check version */
394         if ((fp = g_fopen(file, "rb")) == NULL)
395                 debug_print("Mark/Cache file '%s' not found\n", file);
396         else {
397                 if (buf && buf_size > 0)
398                         setvbuf(fp, buf, _IOFBF, buf_size);
399                 if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
400                          version != bswap_32(data_ver)) {
401                         g_message("%s: Mark/Cache version is different (%u != %u).\n",
402                                   file, bswap_32(data_ver), version);
403                         fclose(fp);
404                         fp = NULL;
405                 }
406                 data_ver = bswap_32(data_ver);
407         }
408         
409         if (mode == DATA_READ)
410                 return fp;
411
412         if (fp) {
413                 /* reopen with append mode */
414                 fclose(fp);
415                 if ((fp = g_fopen(file, "ab")) == NULL)
416                         FILE_OP_ERROR(file, "fopen");
417         } else {
418                 /* open with overwrite mode if mark file doesn't exist or
419                    version is different */
420                 fp = msgcache_open_data_file(file, version, DATA_WRITE, buf,
421                                             buf_size);
422         }
423
424         return fp;
425 }
426
427 static gint msgcache_read_cache_data_str(FILE *fp, gchar **str, 
428                                          StringConverter *conv)
429 {
430         gchar *tmpstr = NULL;
431         size_t ni;
432         guint32 len;
433
434         *str = NULL;
435         if (!swapping) {
436                 if ((ni = fread(&len, sizeof(len), 1, fp) != 1) ||
437                     len > G_MAXINT) {
438                         g_warning("read_data_str: Cache data (len) corrupted, read %d "
439                                   "of %d bytes at offset %ld\n", ni, sizeof(len), 
440                                   ftell(fp));
441                         return -1;
442                 }
443         } else {
444                 if ((ni = fread(&len, sizeof(len), 1, fp) != 1) ||
445                     bswap_32(len) > G_MAXINT) {
446                         g_warning("read_data_str: Cache data (len) corrupted, read %d "
447                                   "of %d bytes at offset %ld\n", ni, sizeof(len), 
448                                   ftell(fp));
449                         return -1;
450                 }
451                 len = bswap_32(len);
452         }
453
454         if (len == 0)
455                 return 0;
456
457         tmpstr = g_try_malloc(len + 1);
458
459         if(tmpstr == NULL) {
460                 g_warning("read_data_str: can't g_malloc %d bytes - cache data probably corrupted.\n", len);
461                 return -1;
462         }
463
464         if ((ni = fread(tmpstr, 1, len, fp)) != len) {
465                 g_warning("read_data_str: Cache data corrupted, read %d of %d "
466                           "bytes at offset %ld\n", 
467                           ni, len, ftell(fp));
468                 g_free(tmpstr);
469                 return -1;
470         }
471         tmpstr[len] = 0;
472
473         if (conv != NULL) {
474                 *str = conv->convert(conv, tmpstr);
475                 g_free(tmpstr);
476         } else 
477                 *str = tmpstr;
478
479         return len;
480 }
481
482 static gint msgcache_get_cache_data_str(gchar *src, gchar **str, gint len,
483                                          StringConverter *conv)
484 {
485         gchar *tmpstr = NULL;
486
487         *str = NULL;
488
489         if (len == 0)
490                 return 0;
491
492         tmpstr = g_try_malloc(len + 1);
493
494         if(tmpstr == NULL) {
495                 g_warning("read_data_str: can't g_malloc %d bytes - cache data probably corrupted.\n", len);
496                 return -1;
497         }
498
499         strncpy(tmpstr, src, len);
500
501         tmpstr[len] = 0;
502
503         if (conv != NULL) {
504                 *str = conv->convert(conv, tmpstr);
505                 g_free(tmpstr);
506         } else 
507                 *str = tmpstr;
508
509         return len;
510 }
511
512 gchar *strconv_strdup_convert(StringConverter *conv, gchar *srcstr)
513 {
514         return g_strdup(srcstr);
515 }
516
517 gchar *strconv_charset_convert(StringConverter *conv, gchar *srcstr)
518 {
519         CharsetConverter *charsetconv = (CharsetConverter *) conv;
520
521         return conv_codeset_strdup(srcstr, charsetconv->srccharset, charsetconv->dstcharset);
522 }
523
524 void strconv_charset_free(StringConverter *conv)
525 {
526         CharsetConverter *charsetconv = (CharsetConverter *) conv;
527
528         g_free(charsetconv->srccharset);
529         g_free(charsetconv->dstcharset);
530 }
531
532 MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
533 {
534         MsgCache *cache;
535         FILE *fp;
536         MsgInfo *msginfo;
537         MsgTmpFlags tmp_flags = 0;
538         gchar file_buf[BUFFSIZE];
539         guint32 num;
540         guint refnum;
541         gboolean error = FALSE;
542         StringConverter *conv = NULL;
543         gchar *srccharset = NULL;
544         const gchar *dstcharset = NULL;
545         gchar *ref = NULL;
546         guint memusage = 0;
547         gint tmp_len = 0, map_len = -1;
548         char *cache_data = NULL;
549         struct stat st;
550
551         g_return_val_if_fail(cache_file != NULL, NULL);
552         g_return_val_if_fail(item != NULL, NULL);
553
554         swapping = TRUE;
555
556         /* In case we can't open the mark file with MARK_VERSION, check if we can open it with the
557          * swapped MARK_VERSION. As msgcache_open_data_file swaps it too, if this succeeds, 
558          * it means it's the old version (not little-endian) on a big-endian machine. The code has
559          * no effect on x86 as their file doesn't change. */
560
561         if ((fp = msgcache_open_data_file
562                 (cache_file, CACHE_VERSION, DATA_READ, file_buf, sizeof(file_buf))) == NULL) {
563                 if ((fp = msgcache_open_data_file
564                 (cache_file, bswap_32(CACHE_VERSION), DATA_READ, file_buf, sizeof(file_buf))) == NULL)
565                         return NULL;
566                 else
567                         swapping = FALSE;
568         }
569
570         debug_print("\tReading %sswapped message cache from %s...\n", swapping?"":"un", cache_file);
571
572         if (folder_has_parent_of_type(item, F_QUEUE)) {
573                 tmp_flags |= MSG_QUEUED;
574         } else if (folder_has_parent_of_type(item, F_DRAFT)) {
575                 tmp_flags |= MSG_DRAFT;
576         }
577
578         if (msgcache_read_cache_data_str(fp, &srccharset, NULL) < 0)
579                 return NULL;
580         dstcharset = CS_UTF_8;
581         if (srccharset == NULL || dstcharset == NULL) {
582                 conv = NULL;
583         } else if (strcmp(srccharset, dstcharset) == 0) {
584                 debug_print("using Noop Converter\n");
585
586                 conv = NULL;
587         } else {
588                 CharsetConverter *charsetconv;
589
590                 debug_print("using CharsetConverter\n");
591
592                 charsetconv = g_new0(CharsetConverter, 1);
593                 charsetconv->converter.convert = strconv_charset_convert;
594                 charsetconv->converter.free = strconv_charset_free;
595                 charsetconv->srccharset = g_strdup(srccharset);
596                 charsetconv->dstcharset = g_strdup(dstcharset);
597
598                 conv = (StringConverter *) charsetconv;
599         }
600         g_free(srccharset);
601
602         cache = msgcache_new();
603
604         if (msgcache_use_mmap == TRUE) {
605                 if (fstat(fileno(fp), &st) >= 0)
606                         map_len = st.st_size;
607                 else
608                         map_len = -1;
609                 if (map_len > 0)
610                         cache_data = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
611         } else {
612                 cache_data = NULL;
613         }
614         if (cache_data != NULL && cache_data != MAP_FAILED) {
615                 int rem_len = map_len-ftell(fp);
616                 char *walk_data = cache_data+ftell(fp);
617
618                 while(rem_len > 0) {
619                         GET_CACHE_DATA_INT(num);
620                         
621                         msginfo = procmsg_msginfo_new();
622                         msginfo->msgnum = num;
623                         memusage += sizeof(MsgInfo);
624
625                         GET_CACHE_DATA_INT(msginfo->size);
626                         GET_CACHE_DATA_INT(msginfo->mtime);
627                         GET_CACHE_DATA_INT(msginfo->date_t);
628                         GET_CACHE_DATA_INT(msginfo->flags.tmp_flags);
629
630                         GET_CACHE_DATA(msginfo->fromname, memusage);
631
632                         GET_CACHE_DATA(msginfo->date, memusage);
633                         GET_CACHE_DATA(msginfo->from, memusage);
634                         GET_CACHE_DATA(msginfo->to, memusage);
635                         GET_CACHE_DATA(msginfo->cc, memusage);
636                         GET_CACHE_DATA(msginfo->newsgroups, memusage);
637                         GET_CACHE_DATA(msginfo->subject, memusage);
638                         GET_CACHE_DATA(msginfo->msgid, memusage);
639                         GET_CACHE_DATA(msginfo->inreplyto, memusage);
640                         GET_CACHE_DATA(msginfo->xref, memusage);
641
642                         GET_CACHE_DATA_INT(msginfo->planned_download);
643                         GET_CACHE_DATA_INT(msginfo->total_size);
644                         GET_CACHE_DATA_INT(refnum);
645
646                         for (; refnum != 0; refnum--) {
647                                 ref = NULL;
648
649                                 GET_CACHE_DATA(ref, memusage);
650
651                                 if (ref && *ref)
652                                         msginfo->references =
653                                                 g_slist_prepend(msginfo->references, ref);
654                         }
655                         if (msginfo->references)
656                                 msginfo->references =
657                                         g_slist_reverse(msginfo->references);
658
659                         msginfo->folder = item;
660                         msginfo->flags.tmp_flags |= tmp_flags;
661
662                         g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
663                         if(msginfo->msgid)
664                                 g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
665                 }
666                 
667                 munmap(cache_data, map_len);
668         } else {
669                 while (fread(&num, sizeof(num), 1, fp) == 1) {
670                         if (swapping)
671                                 num = bswap_32(num);
672
673                         msginfo = procmsg_msginfo_new();
674                         msginfo->msgnum = num;
675                         memusage += sizeof(MsgInfo);
676
677                         READ_CACHE_DATA_INT(msginfo->size, fp);
678                         READ_CACHE_DATA_INT(msginfo->mtime, fp);
679                         READ_CACHE_DATA_INT(msginfo->date_t, fp);
680                         READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
681
682                         READ_CACHE_DATA(msginfo->fromname, fp, memusage);
683
684                         READ_CACHE_DATA(msginfo->date, fp, memusage);
685                         READ_CACHE_DATA(msginfo->from, fp, memusage);
686                         READ_CACHE_DATA(msginfo->to, fp, memusage);
687                         READ_CACHE_DATA(msginfo->cc, fp, memusage);
688                         READ_CACHE_DATA(msginfo->newsgroups, fp, memusage);
689                         READ_CACHE_DATA(msginfo->subject, fp, memusage);
690                         READ_CACHE_DATA(msginfo->msgid, fp, memusage);
691                         READ_CACHE_DATA(msginfo->inreplyto, fp, memusage);
692                         READ_CACHE_DATA(msginfo->xref, fp, memusage);
693
694                         READ_CACHE_DATA_INT(msginfo->planned_download, fp);
695                         READ_CACHE_DATA_INT(msginfo->total_size, fp);
696                         READ_CACHE_DATA_INT(refnum, fp);
697
698                         for (; refnum != 0; refnum--) {
699                                 ref = NULL;
700
701                                 READ_CACHE_DATA(ref, fp, memusage);
702
703                                 if (ref && *ref)
704                                         msginfo->references =
705                                                 g_slist_prepend(msginfo->references, ref);
706                         }
707                         if (msginfo->references)
708                                 msginfo->references =
709                                         g_slist_reverse(msginfo->references);
710
711                         msginfo->folder = item;
712                         msginfo->flags.tmp_flags |= tmp_flags;
713
714                         g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
715                         if(msginfo->msgid)
716                                 g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
717                 }
718         }
719 bail_err:
720         fclose(fp);
721
722         if (conv != NULL) {
723                 if (conv->free != NULL)
724                         conv->free(conv);
725                 g_free(conv);
726         }
727
728         if(error) {
729                 msgcache_destroy(cache);
730                 return NULL;
731         }
732
733         cache->last_access = time(NULL);
734         cache->memusage = memusage;
735
736         debug_print("done. (%d items read)\n", g_hash_table_size(cache->msgnum_table));
737         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
738         return cache;
739 }
740
741 void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
742 {
743         FILE *fp;
744         MsgInfo *msginfo;
745         MsgPermFlags perm_flags;
746         guint32 num;
747         gint map_len = -1;
748         char *cache_data = NULL;
749         struct stat st;
750         
751         swapping = TRUE;
752
753         /* In case we can't open the mark file with MARK_VERSION, check if we can open it with the
754          * swapped MARK_VERSION. As msgcache_open_data_file swaps it too, if this succeeds, 
755          * it means it's the old version (not little-endian) on a big-endian machine. The code has
756          * no effect on x86 as their file doesn't change. */
757
758         if ((fp = msgcache_open_data_file(mark_file, MARK_VERSION, DATA_READ, NULL, 0)) == NULL) {
759                 /* see if it isn't swapped ? */
760                 if ((fp = msgcache_open_data_file(mark_file, bswap_32(MARK_VERSION), DATA_READ, NULL, 0)) == NULL)
761                         return;
762                 else
763                         swapping = FALSE; /* yay */
764         }
765         debug_print("reading %sswapped mark file.\n", swapping?"":"un");
766         
767         if (msgcache_use_mmap) {
768                 if (fstat(fileno(fp), &st) >= 0)
769                         map_len = st.st_size;
770                 else
771                         map_len = -1;
772                 if (map_len > 0)
773                         cache_data = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
774         } else {
775                 cache_data = NULL;
776         }
777         if (cache_data != NULL && cache_data != MAP_FAILED) {
778                 int rem_len = map_len-ftell(fp);
779                 char *walk_data = cache_data+ftell(fp);
780
781                 while(rem_len > 0) {
782                         GET_CACHE_DATA_INT(num);
783                         GET_CACHE_DATA_INT(perm_flags);
784                         msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
785                         if(msginfo) {
786                                 msginfo->flags.perm_flags = perm_flags;
787                         }
788                 }
789                 munmap(cache_data, map_len);
790         } else {
791                 while (fread(&num, sizeof(num), 1, fp) == 1) {
792                         if (swapping)
793                                 num = bswap_32(num);
794                         if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
795                         if (swapping)
796                                 perm_flags = bswap_32(perm_flags);
797                         msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
798                         if(msginfo) {
799                                 msginfo->flags.perm_flags = perm_flags;
800                         }
801                 }       
802         }
803         fclose(fp);
804 }
805
806 static int msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
807 {
808         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
809         GSList *cur;
810         int w_err = 0, wrote = 0;
811
812         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
813         WRITE_CACHE_DATA_INT(msginfo->size, fp);
814         WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
815         WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
816         WRITE_CACHE_DATA_INT(flags, fp);
817
818         WRITE_CACHE_DATA(msginfo->fromname, fp);
819
820         WRITE_CACHE_DATA(msginfo->date, fp);
821         WRITE_CACHE_DATA(msginfo->from, fp);
822         WRITE_CACHE_DATA(msginfo->to, fp);
823         WRITE_CACHE_DATA(msginfo->cc, fp);
824         WRITE_CACHE_DATA(msginfo->newsgroups, fp);
825         WRITE_CACHE_DATA(msginfo->subject, fp);
826         WRITE_CACHE_DATA(msginfo->msgid, fp);
827         WRITE_CACHE_DATA(msginfo->inreplyto, fp);
828         WRITE_CACHE_DATA(msginfo->xref, fp);
829         WRITE_CACHE_DATA_INT(msginfo->planned_download, fp);
830         WRITE_CACHE_DATA_INT(msginfo->total_size, fp);
831         
832         WRITE_CACHE_DATA_INT(g_slist_length(msginfo->references), fp);
833
834         for (cur = msginfo->references; cur != NULL; cur = cur->next) {
835                 WRITE_CACHE_DATA((gchar *)cur->data, fp);
836         }
837         return w_err ? -1 : wrote;
838 }
839
840 static int msgcache_write_mmap_cache(MsgInfo *msginfo, char *walk_data)
841 {
842         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
843         GSList *cur;
844         int wrote = 0;
845
846         PUT_CACHE_DATA_INT(msginfo->msgnum);
847         PUT_CACHE_DATA_INT(msginfo->size);
848         PUT_CACHE_DATA_INT(msginfo->mtime);
849         PUT_CACHE_DATA_INT(msginfo->date_t);
850         PUT_CACHE_DATA_INT(flags);
851         PUT_CACHE_DATA(msginfo->fromname);
852
853         PUT_CACHE_DATA(msginfo->date);
854         PUT_CACHE_DATA(msginfo->from);
855         PUT_CACHE_DATA(msginfo->to);
856         PUT_CACHE_DATA(msginfo->cc);
857         PUT_CACHE_DATA(msginfo->newsgroups);
858         PUT_CACHE_DATA(msginfo->subject);
859         PUT_CACHE_DATA(msginfo->msgid);
860         PUT_CACHE_DATA(msginfo->inreplyto);
861         PUT_CACHE_DATA(msginfo->xref);
862         PUT_CACHE_DATA_INT(msginfo->planned_download);
863         PUT_CACHE_DATA_INT(msginfo->total_size);
864         
865         PUT_CACHE_DATA_INT(g_slist_length(msginfo->references));
866
867         for (cur = msginfo->references; cur != NULL; cur = cur->next) {
868                 PUT_CACHE_DATA((gchar *)cur->data);
869         }
870         return wrote;
871 }
872
873 static int msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
874 {
875         MsgPermFlags flags = msginfo->flags.perm_flags;
876         int w_err = 0, wrote = 0;
877         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
878         WRITE_CACHE_DATA_INT(flags, fp);
879         return w_err ? -1 : wrote;
880 }
881
882 static int msgcache_write_mmap_flags(MsgInfo *msginfo, char *walk_data)
883 {
884         MsgPermFlags flags = msginfo->flags.perm_flags;
885         int wrote = 0;
886
887         PUT_CACHE_DATA_INT(msginfo->msgnum);
888         PUT_CACHE_DATA_INT(flags);
889         return wrote;
890 }
891
892 struct write_fps
893 {
894         FILE *cache_fp;
895         FILE *mark_fp;
896         char *cache_data;
897         char *mark_data;
898         int error;
899         guint cache_size;
900         guint mark_size;
901 };
902
903 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
904 {
905         MsgInfo *msginfo;
906         struct write_fps *write_fps;
907         int tmp;
908
909         msginfo = (MsgInfo *)value;
910         write_fps = user_data;
911
912         tmp = msgcache_write_cache(msginfo, write_fps->cache_fp);
913         if (tmp < 0)
914                 write_fps->error = 1;
915         else
916                 write_fps->cache_size += tmp;
917         tmp= msgcache_write_flags(msginfo, write_fps->mark_fp);
918         if (tmp < 0)
919                 write_fps->error = 1;
920         else
921                 write_fps->mark_size += tmp;
922 }
923
924 static void msgcache_write_mmap_func(gpointer key, gpointer value, gpointer user_data)
925 {
926         MsgInfo *msginfo;
927         struct write_fps *write_fps;
928         int tmp;
929
930         msginfo = (MsgInfo *)value;
931         write_fps = user_data;
932
933         tmp = msgcache_write_mmap_cache(msginfo, write_fps->cache_data);
934         write_fps->cache_size += tmp;
935         write_fps->cache_data += tmp;
936         tmp = msgcache_write_mmap_flags(msginfo, write_fps->mark_data);
937         write_fps->mark_size += tmp;
938         write_fps->mark_data += tmp;
939 }
940
941 gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
942 {
943         struct write_fps write_fps;
944         gchar *new_cache, *new_mark;
945         int w_err = 0, wrote = 0;
946         gint map_len = -1;
947         char *cache_data = NULL;
948         char *mark_data = NULL;
949         START_TIMING("*** writing caches");
950         g_return_val_if_fail(cache_file != NULL, -1);
951         g_return_val_if_fail(mark_file != NULL, -1);
952         g_return_val_if_fail(cache != NULL, -1);
953
954         new_cache = g_strconcat(cache_file, ".new", NULL);
955         new_mark  = g_strconcat(mark_file, ".new", NULL);
956
957         write_fps.error = 0;
958         write_fps.cache_size = 0;
959         write_fps.mark_size = 0;
960         write_fps.cache_fp = msgcache_open_data_file(new_cache, CACHE_VERSION,
961                 DATA_WRITE, NULL, 0);
962         if (write_fps.cache_fp == NULL) {
963                 g_free(new_cache);
964                 g_free(new_mark);
965                 return -1;
966         }
967
968         WRITE_CACHE_DATA(CS_UTF_8, write_fps.cache_fp);
969
970         if (w_err != 0) {
971                 g_warning("failed to write charset\n");
972                 fclose(write_fps.cache_fp);
973                 g_unlink(new_cache);
974                 g_free(new_cache);
975                 g_free(new_mark);
976                 return -1;
977         }
978
979         write_fps.mark_fp = msgcache_open_data_file(new_mark, MARK_VERSION,
980                 DATA_WRITE, NULL, 0);
981         if (write_fps.mark_fp == NULL) {
982                 fclose(write_fps.cache_fp);
983                 g_unlink(new_cache);
984                 g_free(new_cache);
985                 g_free(new_mark);
986                 return -1;
987         }
988
989         debug_print("\tWriting message cache to %s and %s...\n", new_cache, new_mark);
990
991         if (change_file_mode_rw(write_fps.cache_fp, new_cache) < 0)
992                 FILE_OP_ERROR(new_cache, "chmod");
993
994         write_fps.cache_size = ftell(write_fps.cache_fp);
995         write_fps.mark_size = ftell(write_fps.mark_fp);
996         if (msgcache_use_mmap && cache->memusage > 0) {
997                 map_len = cache->memusage;
998                 if (ftruncate(fileno(write_fps.cache_fp), (off_t)map_len) == 0) {
999                         cache_data = mmap(NULL, map_len, PROT_WRITE, MAP_SHARED, 
1000                                 fileno(write_fps.cache_fp), 0);
1001                 }
1002                 if (cache_data != NULL && cache_data != MAP_FAILED) {
1003                         if (ftruncate(fileno(write_fps.mark_fp), (off_t)map_len) == 0) {
1004                                 mark_data = mmap(NULL, map_len, PROT_WRITE, MAP_SHARED, 
1005                                         fileno(write_fps.mark_fp), 0);
1006                         } 
1007                         if (mark_data == NULL || mark_data == MAP_FAILED) {
1008                                 munmap(cache_data, map_len);
1009                                 cache_data = NULL;
1010                         }
1011                 }
1012         }
1013
1014         if (cache_data != NULL && cache_data != MAP_FAILED) {
1015                 write_fps.cache_data = cache_data + ftell(write_fps.cache_fp);
1016                 write_fps.mark_data = mark_data + ftell(write_fps.mark_fp);
1017                 g_hash_table_foreach(cache->msgnum_table, msgcache_write_mmap_func, (gpointer)&write_fps);
1018                 munmap(cache_data, map_len);
1019                 munmap(mark_data, map_len);
1020         } else {
1021                 g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
1022         }
1023         ftruncate(fileno(write_fps.cache_fp), write_fps.cache_size);
1024         ftruncate(fileno(write_fps.mark_fp), write_fps.mark_size);
1025         fclose(write_fps.cache_fp);
1026         fclose(write_fps.mark_fp);
1027
1028
1029         if (write_fps.error != 0) {
1030                 g_unlink(new_cache);
1031                 g_unlink(new_mark);
1032                 g_free(new_cache);
1033                 g_free(new_mark);
1034                 return -1;
1035         } else {
1036                 move_file(new_cache, cache_file, TRUE);
1037                 move_file(new_mark, mark_file, TRUE);
1038                 cache->last_access = time(NULL);
1039         }
1040
1041         g_free(new_cache);
1042         g_free(new_mark);
1043         debug_print("done.\n");
1044         END_TIMING();
1045         return 0;
1046 }
1047