43cf899b7bc39cc63b6ab09764483449ee78ae48
[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(msginfo) {
120                 g_hash_table_remove(cache->msgid_table, oldmsginfo->msgid);
121                 g_hash_table_remove(cache->msgnum_table, &oldmsginfo->msgnum);
122                 procmsg_msginfo_free(oldmsginfo);
123         }
124         cache->memusage -= procmsg_msginfo_memusage(oldmsginfo);
125
126         newmsginfo = procmsg_msginfo_new_ref(msginfo);
127         g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
128         if(newmsginfo->msgid)
129                 g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
130         cache->memusage += procmsg_msginfo_memusage(newmsginfo);
131         cache->last_access = time(NULL);
132         
133         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
134
135         return;
136 }
137
138 MsgInfo *msgcache_get_msg(MsgCache *cache, guint num)
139 {
140         MsgInfo *msginfo;
141
142         g_return_val_if_fail(cache != NULL, NULL);
143
144         msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
145         if(!msginfo)
146                 return NULL;
147         cache->last_access = time(NULL);
148         
149         return procmsg_msginfo_new_ref(msginfo);
150 }
151
152 MsgInfo *msgcache_get_msg_by_id(MsgCache *cache, const gchar *msgid)
153 {
154         MsgInfo *msginfo;
155         
156         g_return_val_if_fail(cache != NULL, NULL);
157         g_return_val_if_fail(msgid != NULL, NULL);
158
159         msginfo = g_hash_table_lookup(cache->msgid_table, msgid);
160         if(!msginfo)
161                 return NULL;
162         cache->last_access = time(NULL);
163         
164         return procmsg_msginfo_new_ref(msginfo);        
165 }
166
167 static void msgcache_get_msg_list_func(gpointer key, gpointer value, gpointer user_data)
168 {
169         MsgInfoList **listptr = user_data;
170         MsgInfo *msginfo = value;
171
172         *listptr = g_slist_prepend(*listptr, procmsg_msginfo_new_ref(msginfo));
173 }
174
175 MsgInfoList *msgcache_get_msg_list(MsgCache *cache)
176 {
177         MsgInfoList *msg_list = NULL;
178
179         g_return_val_if_fail(cache != NULL, NULL);
180
181         g_hash_table_foreach((GHashTable *)cache->msgnum_table, msgcache_get_msg_list_func, (gpointer)&msg_list);       
182         cache->last_access = time(NULL);
183         
184         msg_list = g_slist_reverse(msg_list);
185
186         return msg_list;
187 }
188
189 time_t msgcache_get_last_access_time(MsgCache *cache)
190 {
191         g_return_val_if_fail(cache != NULL, 0);
192         
193         return cache->last_access;
194 }
195
196 gint msgcache_get_memory_usage(MsgCache *cache)
197 {
198         g_return_val_if_fail(cache != NULL, 0);
199
200         return cache->memusage;
201 }
202
203 /*
204  *  Cache saving functions
205  */
206
207 #define READ_CACHE_DATA(data, fp) \
208 { \
209         if (msgcache_read_cache_data_str(fp, &data) < 0) { \
210                 procmsg_msginfo_free(msginfo); \
211                 error = TRUE; \
212                 break; \
213         } \
214 }
215
216 #define READ_CACHE_DATA_INT(n, fp) \
217 { \
218         if (fread(&n, sizeof(n), 1, fp) != 1) { \
219                 g_warning("Cache data is corrupted\n"); \
220                 procmsg_msginfo_free(msginfo); \
221                 error = TRUE; \
222                 break; \
223         } \
224 }
225
226 #define WRITE_CACHE_DATA_INT(n, fp) \
227         fwrite(&n, sizeof(n), 1, fp)
228
229 #define WRITE_CACHE_DATA(data, fp) \
230 { \
231         gint len; \
232         if (data == NULL) \
233                 len = 0; \
234         else \
235                 len = strlen(data); \
236         WRITE_CACHE_DATA_INT(len, fp); \
237         if (len > 0) { \
238                 fwrite(data, len, 1, fp); \
239         } \
240 }
241
242 static FILE *msgcache_open_data_file(const gchar *file, gint version,
243                                      DataOpenMode mode,
244                                      gchar *buf, size_t buf_size)
245 {
246         FILE *fp;
247         gint data_ver;
248
249         g_return_val_if_fail(file != NULL, NULL);
250
251         if (mode == DATA_WRITE) {
252                 if ((fp = fopen(file, "wb")) == NULL) {
253                         FILE_OP_ERROR(file, "fopen");
254                         return NULL;
255                 }
256                 if (change_file_mode_rw(fp, file) < 0)
257                         FILE_OP_ERROR(file, "chmod");
258
259                 WRITE_CACHE_DATA_INT(version, fp);
260                 return fp;
261         }
262
263         /* check version */
264         if ((fp = fopen(file, "rb")) == NULL)
265                 debug_print("Mark/Cache file not found\n");
266         else {
267                 if (buf && buf_size > 0)
268                         setvbuf(fp, buf, _IOFBF, buf_size);
269                 if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
270                          version != data_ver) {
271                         debug_print("Mark/Cache version is different (%d != %d). "
272                                     "Discarding it.\n", data_ver, version);
273                         fclose(fp);
274                         fp = NULL;
275                 }
276         }
277
278         if (mode == DATA_READ)
279                 return fp;
280
281         if (fp) {
282                 /* reopen with append mode */
283                 fclose(fp);
284                 if ((fp = fopen(file, "ab")) == NULL)
285                         FILE_OP_ERROR(file, "fopen");
286         } else {
287                 /* open with overwrite mode if mark file doesn't exist or
288                    version is different */
289                 fp = msgcache_open_data_file(file, version, DATA_WRITE, buf,
290                                             buf_size);
291         }
292
293         return fp;
294 }
295
296 static gint msgcache_read_cache_data_str(FILE *fp, gchar **str)
297 {
298         gchar buf[BUFFSIZE];
299         gint ret = 0;
300         size_t len;
301
302         if (fread(&len, sizeof(len), 1, fp) == 1) {
303                 if (len < 0)
304                         ret = -1;
305                 else {
306                         gchar *tmp = NULL;
307
308                         while (len > 0) {
309                                 size_t size = MIN(len, BUFFSIZE - 1);
310
311                                 if (fread(buf, size, 1, fp) != 1) {
312                                         ret = -1;
313                                         if (tmp) g_free(tmp);
314                                         *str = NULL;
315                                         break;
316                                 }
317
318                                 buf[size] = '\0';
319                                 if (tmp) {
320                                         *str = g_strconcat(tmp, buf, NULL);
321                                         g_free(tmp);
322                                         tmp = *str;
323                                 } else
324                                         tmp = *str = g_strdup(buf);
325
326                                 len -= size;
327                         }
328                 }
329         } else
330                 ret = -1;
331
332         if (ret < 0)
333                 g_warning("Cache data is corrupted\n");
334
335         return ret;
336 }
337
338 MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
339 {
340         MsgCache *cache;
341         FILE *fp;
342         MsgInfo *msginfo;
343         MsgTmpFlags tmp_flags = 0;
344         gchar file_buf[BUFFSIZE];
345         guint num;
346         gboolean error = FALSE;
347
348         g_return_val_if_fail(cache_file != NULL, NULL);
349         g_return_val_if_fail(item != NULL, NULL);
350
351         if ((fp = msgcache_open_data_file
352                 (cache_file, CACHE_VERSION, DATA_READ, file_buf, sizeof(file_buf))) == NULL)
353                 return NULL;
354
355         debug_print("\tReading message cache from %s...\n", cache_file);
356
357         if (item->stype == F_QUEUE) {
358                 tmp_flags |= MSG_QUEUED;
359         } else if (item->stype == F_DRAFT) {
360                 tmp_flags |= MSG_DRAFT;
361         }
362
363         cache = msgcache_new();
364
365         g_hash_table_freeze(cache->msgnum_table);
366
367         while (fread(&num, sizeof(num), 1, fp) == 1) {
368                 msginfo = procmsg_msginfo_new();
369                 msginfo->msgnum = num;
370                 READ_CACHE_DATA_INT(msginfo->size, fp);
371                 READ_CACHE_DATA_INT(msginfo->mtime, fp);
372                 READ_CACHE_DATA_INT(msginfo->date_t, fp);
373                 READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
374
375                 READ_CACHE_DATA(msginfo->fromname, fp);
376
377                 READ_CACHE_DATA(msginfo->date, fp);
378                 READ_CACHE_DATA(msginfo->from, fp);
379                 READ_CACHE_DATA(msginfo->to, fp);
380                 READ_CACHE_DATA(msginfo->cc, fp);
381                 READ_CACHE_DATA(msginfo->newsgroups, fp);
382                 READ_CACHE_DATA(msginfo->subject, fp);
383                 READ_CACHE_DATA(msginfo->msgid, fp);
384                 READ_CACHE_DATA(msginfo->inreplyto, fp);
385                 READ_CACHE_DATA(msginfo->references, fp);
386                 READ_CACHE_DATA(msginfo->xref, fp);
387
388                 msginfo->folder = item;
389                 msginfo->flags.tmp_flags |= tmp_flags;
390
391                 g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
392                 if(msginfo->msgid)
393                         g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
394                 cache->memusage += procmsg_msginfo_memusage(msginfo);
395         }
396         fclose(fp);
397
398         if(error) {
399                 g_hash_table_thaw(cache->msgnum_table);
400                 msgcache_destroy(cache);
401                 return NULL;
402         }
403
404         cache->last_access = time(NULL);
405         g_hash_table_thaw(cache->msgnum_table);
406
407         debug_print("done. (%d items read)\n", g_hash_table_size(cache->msgnum_table));
408         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
409
410         return cache;
411 }
412
413 void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
414 {
415         FILE *fp;
416         MsgInfo *msginfo;
417         MsgPermFlags perm_flags;
418         guint num;
419
420         if ((fp = msgcache_open_data_file(mark_file, MARK_VERSION, DATA_READ, NULL, 0)) == NULL)
421                 return;
422
423         debug_print("\tReading message marks from %s...\n", mark_file);
424
425         while (fread(&num, sizeof(num), 1, fp) == 1) {
426                 if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
427
428                 msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
429                 if(msginfo) {
430                         msginfo->flags.perm_flags = perm_flags;
431                 }
432         }
433         fclose(fp);
434 }
435
436 void msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
437 {
438         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
439
440         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
441         WRITE_CACHE_DATA_INT(msginfo->size, fp);
442         WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
443         WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
444         WRITE_CACHE_DATA_INT(flags, fp);
445
446         WRITE_CACHE_DATA(msginfo->fromname, fp);
447
448         WRITE_CACHE_DATA(msginfo->date, fp);
449         WRITE_CACHE_DATA(msginfo->from, fp);
450         WRITE_CACHE_DATA(msginfo->to, fp);
451         WRITE_CACHE_DATA(msginfo->cc, fp);
452         WRITE_CACHE_DATA(msginfo->newsgroups, fp);
453         WRITE_CACHE_DATA(msginfo->subject, fp);
454         WRITE_CACHE_DATA(msginfo->msgid, fp);
455         WRITE_CACHE_DATA(msginfo->inreplyto, fp);
456         WRITE_CACHE_DATA(msginfo->references, fp);
457         WRITE_CACHE_DATA(msginfo->xref, fp);
458 }
459
460 static void msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
461 {
462         MsgPermFlags flags = msginfo->flags.perm_flags;
463
464         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
465         WRITE_CACHE_DATA_INT(flags, fp);
466 }
467
468 struct write_fps
469 {
470         FILE *cache_fp;
471         FILE *mark_fp;
472 };
473
474 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
475 {
476         MsgInfo *msginfo;
477         struct write_fps *write_fps;
478
479         msginfo = (MsgInfo *)value;
480         write_fps = user_data;
481
482         msgcache_write_cache(msginfo, write_fps->cache_fp);
483         msgcache_write_flags(msginfo, write_fps->mark_fp);
484 }
485
486 gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
487 {
488         struct write_fps write_fps;
489
490         g_return_val_if_fail(cache_file != NULL, -1);
491         g_return_val_if_fail(mark_file != NULL, -1);
492         g_return_val_if_fail(cache != NULL, -1);
493
494         write_fps.cache_fp = msgcache_open_data_file(cache_file, CACHE_VERSION,
495                 DATA_WRITE, NULL, 0);
496         if (write_fps.cache_fp == NULL)
497                 return -1;
498
499         write_fps.mark_fp = msgcache_open_data_file(mark_file, MARK_VERSION,
500                 DATA_WRITE, NULL, 0);
501         if (write_fps.mark_fp == NULL) {
502                 fclose(write_fps.cache_fp);
503                 return -1;
504         }
505
506         debug_print("\tWriting message cache to %s and %s...\n", cache_file, mark_file);
507
508         if (change_file_mode_rw(write_fps.cache_fp, cache_file) < 0)
509                 FILE_OP_ERROR(cache_file, "chmod");
510
511         g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
512
513         fclose(write_fps.cache_fp);
514         fclose(write_fps.mark_fp);
515
516         cache->last_access = time(NULL);
517
518         debug_print("done.\n");
519         return 0;
520 }
521