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