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