fix wrong test
[claws.git] / src / msgcache.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "defs.h"
21
22 #include <glib.h>
23
24 #include <time.h>
25
26 #include "intl.h"
27 #include "msgcache.h"
28 #include "utils.h"
29 #include "procmsg.h"
30
31 typedef enum
32 {
33         DATA_READ,
34         DATA_WRITE,
35         DATA_APPEND
36 } DataOpenMode;
37
38 struct _MsgCache {
39         GHashTable      *msgnum_table;
40         GHashTable      *msgid_table;
41         guint            memusage;
42         time_t           last_access;
43 };
44
45 MsgCache *msgcache_new(void)
46 {
47         MsgCache *cache;
48         
49         cache = g_new0(MsgCache, 1),
50         cache->msgnum_table = g_hash_table_new(g_int_hash, g_int_equal);
51         cache->msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
52         cache->last_access = time(NULL);
53
54         return cache;
55 }
56
57 static gboolean msgcache_msginfo_free_func(gpointer num, gpointer msginfo, gpointer user_data)
58 {
59         procmsg_msginfo_free((MsgInfo *)msginfo);
60         return TRUE;
61 }                                                                                         
62
63 void msgcache_destroy(MsgCache *cache)
64 {
65         g_return_if_fail(cache != NULL);
66
67         g_hash_table_foreach_remove(cache->msgnum_table, msgcache_msginfo_free_func, NULL);
68         g_hash_table_destroy(cache->msgid_table);
69         g_hash_table_destroy(cache->msgnum_table);
70         g_free(cache);
71 }
72
73 void msgcache_add_msg(MsgCache *cache, MsgInfo *msginfo) 
74 {
75         MsgInfo *newmsginfo;
76
77         g_return_if_fail(cache != NULL);
78         g_return_if_fail(msginfo != NULL);
79
80         newmsginfo = procmsg_msginfo_new_ref(msginfo);
81         g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
82         if(newmsginfo->msgid != NULL)
83                 g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
84         cache->memusage += procmsg_msginfo_memusage(msginfo);
85         cache->last_access = time(NULL);
86
87         debug_print("Cache size: %d messages, %d bytes\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
88 }
89
90 void msgcache_remove_msg(MsgCache *cache, guint msgnum)
91 {
92         MsgInfo *msginfo;
93
94         g_return_if_fail(cache != NULL);
95         g_return_if_fail(msgnum > 0);
96
97         msginfo = (MsgInfo *) g_hash_table_lookup(cache->msgnum_table, &msgnum);
98         if(!msginfo)
99                 return;
100
101         cache->memusage -= procmsg_msginfo_memusage(msginfo);
102         if(msginfo->msgid)
103                 g_hash_table_remove(cache->msgid_table, msginfo->msgid);
104         g_hash_table_remove(cache->msgnum_table, &msginfo->msgnum);
105         procmsg_msginfo_free(msginfo);
106         cache->last_access = time(NULL);
107
108         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
109 }
110
111 void msgcache_update_msg(MsgCache *cache, MsgInfo *msginfo)
112 {
113         MsgInfo *oldmsginfo, *newmsginfo;
114         
115         g_return_if_fail(cache != NULL);
116         g_return_if_fail(msginfo != NULL);
117
118         oldmsginfo = g_hash_table_lookup(cache->msgnum_table, &msginfo->msgnum);
119         if(oldmsginfo && oldmsginfo->msgid) {
120                 g_hash_table_remove(cache->msgid_table, oldmsginfo->msgid);
121                 g_hash_table_remove(cache->msgnum_table, &oldmsginfo->msgnum);
122         } 
123         if (oldmsginfo)
124                 procmsg_msginfo_free(oldmsginfo);
125
126         cache->memusage -= procmsg_msginfo_memusage(oldmsginfo);
127
128         newmsginfo = procmsg_msginfo_new_ref(msginfo);
129         g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
130         if(newmsginfo->msgid)
131                 g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
132         cache->memusage += procmsg_msginfo_memusage(newmsginfo);
133         cache->last_access = time(NULL);
134         
135         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
136
137         return;
138 }
139
140 MsgInfo *msgcache_get_msg(MsgCache *cache, guint num)
141 {
142         MsgInfo *msginfo;
143
144         g_return_val_if_fail(cache != NULL, NULL);
145
146         msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
147         if(!msginfo)
148                 return NULL;
149         cache->last_access = time(NULL);
150         
151         return procmsg_msginfo_new_ref(msginfo);
152 }
153
154 MsgInfo *msgcache_get_msg_by_id(MsgCache *cache, const gchar *msgid)
155 {
156         MsgInfo *msginfo;
157         
158         g_return_val_if_fail(cache != NULL, NULL);
159         g_return_val_if_fail(msgid != NULL, NULL);
160
161         msginfo = g_hash_table_lookup(cache->msgid_table, msgid);
162         if(!msginfo)
163                 return NULL;
164         cache->last_access = time(NULL);
165         
166         return procmsg_msginfo_new_ref(msginfo);        
167 }
168
169 static void msgcache_get_msg_list_func(gpointer key, gpointer value, gpointer user_data)
170 {
171         MsgInfoList **listptr = user_data;
172         MsgInfo *msginfo = value;
173
174         *listptr = g_slist_prepend(*listptr, procmsg_msginfo_new_ref(msginfo));
175 }
176
177 MsgInfoList *msgcache_get_msg_list(MsgCache *cache)
178 {
179         MsgInfoList *msg_list = NULL;
180
181         g_return_val_if_fail(cache != NULL, NULL);
182
183         g_hash_table_foreach((GHashTable *)cache->msgnum_table, msgcache_get_msg_list_func, (gpointer)&msg_list);       
184         cache->last_access = time(NULL);
185         
186         msg_list = g_slist_reverse(msg_list);
187
188         return msg_list;
189 }
190
191 time_t msgcache_get_last_access_time(MsgCache *cache)
192 {
193         g_return_val_if_fail(cache != NULL, 0);
194         
195         return cache->last_access;
196 }
197
198 gint msgcache_get_memory_usage(MsgCache *cache)
199 {
200         g_return_val_if_fail(cache != NULL, 0);
201
202         return cache->memusage;
203 }
204
205 /*
206  *  Cache saving functions
207  */
208
209 #define READ_CACHE_DATA(data, fp) \
210 { \
211         if (msgcache_read_cache_data_str(fp, &data) < 0) { \
212                 procmsg_msginfo_free(msginfo); \
213                 error = TRUE; \
214                 break; \
215         } \
216 }
217
218 #define READ_CACHE_DATA_INT(n, fp) \
219 { \
220         guint32 idata; \
221  \
222         if (fread(&idata, sizeof(idata), 1, fp) != 1) { \
223                 g_warning("Cache data is corrupted\n"); \
224                 procmsg_msginfo_free(msginfo); \
225                 error = TRUE; \
226                 break; \
227         } else \
228                 n = idata;\
229 }
230
231 #define WRITE_CACHE_DATA_INT(n, fp)             \
232 {                                               \
233         guint32 idata;                          \
234                                                 \
235         idata = (guint32)n;                     \
236         fwrite(&idata, sizeof(idata), 1, fp);   \
237 }
238
239 #define WRITE_CACHE_DATA(data, fp) \
240 { \
241         size_t len; \
242         if (data == NULL) \
243                 len = 0; \
244         else \
245                 len = strlen(data); \
246         WRITE_CACHE_DATA_INT(len, fp); \
247         if (len > 0) { \
248                 fwrite(data, len, 1, fp); \
249         } \
250 }
251
252 static FILE *msgcache_open_data_file(const gchar *file, gint version,
253                                      DataOpenMode mode,
254                                      gchar *buf, size_t buf_size)
255 {
256         FILE *fp;
257         gint data_ver;
258
259         g_return_val_if_fail(file != NULL, NULL);
260
261         if (mode == DATA_WRITE) {
262                 if ((fp = fopen(file, "wb")) == NULL) {
263                         FILE_OP_ERROR(file, "fopen");
264                         return NULL;
265                 }
266                 if (change_file_mode_rw(fp, file) < 0)
267                         FILE_OP_ERROR(file, "chmod");
268
269                 WRITE_CACHE_DATA_INT(version, fp);
270                 return fp;
271         }
272
273         /* check version */
274         if ((fp = fopen(file, "rb")) == NULL)
275                 debug_print("Mark/Cache file not found\n");
276         else {
277                 if (buf && buf_size > 0)
278                         setvbuf(fp, buf, _IOFBF, buf_size);
279                 if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
280                          version != data_ver) {
281                         debug_print("Mark/Cache version is different (%d != %d). "
282                                     "Discarding it.\n", data_ver, version);
283                         fclose(fp);
284                         fp = NULL;
285                 }
286         }
287
288         if (mode == DATA_READ)
289                 return fp;
290
291         if (fp) {
292                 /* reopen with append mode */
293                 fclose(fp);
294                 if ((fp = fopen(file, "ab")) == NULL)
295                         FILE_OP_ERROR(file, "fopen");
296         } else {
297                 /* open with overwrite mode if mark file doesn't exist or
298                    version is different */
299                 fp = msgcache_open_data_file(file, version, DATA_WRITE, buf,
300                                             buf_size);
301         }
302
303         return fp;
304 }
305
306 static gint msgcache_read_cache_data_str(FILE *fp, gchar **str)
307 {
308         gchar buf[BUFFSIZE];
309         gint ret = 0;
310         guint32 len;
311
312         if (fread(&len, sizeof(len), 1, fp) == 1) {
313                 if (len > G_MAXINT)
314                         ret = -1;
315                 else {
316                         gchar *tmp = NULL;
317
318                         while (len > 0) {
319                                 size_t size = MIN(len, BUFFSIZE - 1);
320
321                                 if (fread(buf, size, 1, fp) != 1) {
322                                         ret = -1;
323                                         if (tmp) g_free(tmp);
324                                         *str = NULL;
325                                         break;
326                                 }
327
328                                 buf[size] = '\0';
329                                 if (tmp) {
330                                         *str = g_strconcat(tmp, buf, NULL);
331                                         g_free(tmp);
332                                         tmp = *str;
333                                 } else
334                                         tmp = *str = g_strdup(buf);
335
336                                 len -= size;
337                         }
338                 }
339         } else
340                 ret = -1;
341
342         if (ret < 0)
343                 g_warning("Cache data is corrupted\n");
344
345         return ret;
346 }
347
348 MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
349 {
350         MsgCache *cache;
351         FILE *fp;
352         MsgInfo *msginfo;
353         MsgTmpFlags tmp_flags = 0;
354         gchar file_buf[BUFFSIZE];
355         guint num;
356         gboolean error = FALSE;
357
358         g_return_val_if_fail(cache_file != NULL, NULL);
359         g_return_val_if_fail(item != NULL, NULL);
360
361         if ((fp = msgcache_open_data_file
362                 (cache_file, CACHE_VERSION, DATA_READ, file_buf, sizeof(file_buf))) == NULL)
363                 return NULL;
364
365         debug_print("\tReading message cache from %s...\n", cache_file);
366
367         if (item->stype == F_QUEUE) {
368                 tmp_flags |= MSG_QUEUED;
369         } else if (item->stype == F_DRAFT) {
370                 tmp_flags |= MSG_DRAFT;
371         }
372
373         cache = msgcache_new();
374
375         g_hash_table_freeze(cache->msgnum_table);
376
377         while (fread(&num, sizeof(num), 1, fp) == 1) {
378                 msginfo = procmsg_msginfo_new();
379                 msginfo->msgnum = num;
380                 READ_CACHE_DATA_INT(msginfo->size, fp);
381                 READ_CACHE_DATA_INT(msginfo->mtime, fp);
382                 READ_CACHE_DATA_INT(msginfo->date_t, fp);
383                 READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
384
385                 READ_CACHE_DATA(msginfo->fromname, fp);
386
387                 READ_CACHE_DATA(msginfo->date, fp);
388                 READ_CACHE_DATA(msginfo->from, fp);
389                 READ_CACHE_DATA(msginfo->to, fp);
390                 READ_CACHE_DATA(msginfo->cc, fp);
391                 READ_CACHE_DATA(msginfo->newsgroups, fp);
392                 READ_CACHE_DATA(msginfo->subject, fp);
393                 READ_CACHE_DATA(msginfo->msgid, fp);
394                 READ_CACHE_DATA(msginfo->inreplyto, fp);
395                 READ_CACHE_DATA(msginfo->references, fp);
396                 READ_CACHE_DATA(msginfo->xref, fp);
397                 READ_CACHE_DATA_INT(msginfo->planned_download, fp);
398
399                 msginfo->folder = item;
400                 msginfo->flags.tmp_flags |= tmp_flags;
401
402                 g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
403                 if(msginfo->msgid)
404                         g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
405                 cache->memusage += procmsg_msginfo_memusage(msginfo);
406         }
407         fclose(fp);
408         g_hash_table_thaw(cache->msgnum_table);
409
410         if(error) {
411                 msgcache_destroy(cache);
412                 return NULL;
413         }
414
415         cache->last_access = time(NULL);
416
417         debug_print("done. (%d items read)\n", g_hash_table_size(cache->msgnum_table));
418         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
419
420         return cache;
421 }
422
423 void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
424 {
425         FILE *fp;
426         MsgInfo *msginfo;
427         MsgPermFlags perm_flags;
428         guint num;
429
430         if ((fp = msgcache_open_data_file(mark_file, MARK_VERSION, DATA_READ, NULL, 0)) == NULL)
431                 return;
432
433         debug_print("\tReading message marks from %s...\n", mark_file);
434
435         while (fread(&num, sizeof(num), 1, fp) == 1) {
436                 if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
437
438                 msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
439                 if(msginfo) {
440                         msginfo->flags.perm_flags = perm_flags;
441                 }
442         }
443         fclose(fp);
444 }
445
446 void msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
447 {
448         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
449
450         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
451         WRITE_CACHE_DATA_INT(msginfo->size, fp);
452         WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
453         WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
454         WRITE_CACHE_DATA_INT(flags, fp);
455
456         WRITE_CACHE_DATA(msginfo->fromname, fp);
457
458         WRITE_CACHE_DATA(msginfo->date, fp);
459         WRITE_CACHE_DATA(msginfo->from, fp);
460         WRITE_CACHE_DATA(msginfo->to, fp);
461         WRITE_CACHE_DATA(msginfo->cc, fp);
462         WRITE_CACHE_DATA(msginfo->newsgroups, fp);
463         WRITE_CACHE_DATA(msginfo->subject, fp);
464         WRITE_CACHE_DATA(msginfo->msgid, fp);
465         WRITE_CACHE_DATA(msginfo->inreplyto, fp);
466         WRITE_CACHE_DATA(msginfo->references, fp);
467         WRITE_CACHE_DATA(msginfo->xref, fp);
468         WRITE_CACHE_DATA_INT(msginfo->planned_download, fp);
469 }
470
471 static void msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
472 {
473         MsgPermFlags flags = msginfo->flags.perm_flags;
474
475         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
476         WRITE_CACHE_DATA_INT(flags, fp);
477 }
478
479 struct write_fps
480 {
481         FILE *cache_fp;
482         FILE *mark_fp;
483 };
484
485 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
486 {
487         MsgInfo *msginfo;
488         struct write_fps *write_fps;
489
490         msginfo = (MsgInfo *)value;
491         write_fps = user_data;
492
493         msgcache_write_cache(msginfo, write_fps->cache_fp);
494         msgcache_write_flags(msginfo, write_fps->mark_fp);
495 }
496
497 gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
498 {
499         struct write_fps write_fps;
500
501         g_return_val_if_fail(cache_file != NULL, -1);
502         g_return_val_if_fail(mark_file != NULL, -1);
503         g_return_val_if_fail(cache != NULL, -1);
504
505         write_fps.cache_fp = msgcache_open_data_file(cache_file, CACHE_VERSION,
506                 DATA_WRITE, NULL, 0);
507         if (write_fps.cache_fp == NULL)
508                 return -1;
509
510         write_fps.mark_fp = msgcache_open_data_file(mark_file, MARK_VERSION,
511                 DATA_WRITE, NULL, 0);
512         if (write_fps.mark_fp == NULL) {
513                 fclose(write_fps.cache_fp);
514                 return -1;
515         }
516
517         debug_print("\tWriting message cache to %s and %s...\n", cache_file, mark_file);
518
519         if (change_file_mode_rw(write_fps.cache_fp, cache_file) < 0)
520                 FILE_OP_ERROR(cache_file, "chmod");
521
522         g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
523
524         fclose(write_fps.cache_fp);
525         fclose(write_fps.mark_fp);
526
527         cache->last_access = time(NULL);
528
529         debug_print("done.\n");
530         return 0;
531 }
532