cfc1d095b16a664c94077551789776c2e2b3d116
[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         guint32 idata; \
219  \
220         if (fread(&idata, sizeof(idata), 1, fp) != 1) { \
221                 g_warning("Cache data is corrupted\n"); \
222                 procmsg_msginfo_free(msginfo); \
223                 error = TRUE; \
224                 break; \
225         } else \
226                 n = idata;\
227 }
228
229 #define WRITE_CACHE_DATA_INT(n, fp)             \
230 {                                               \
231         guint32 idata;                          \
232                                                 \
233         idata = (guint32)n;                     \
234         fwrite(&idata, sizeof(idata), 1, fp);   \
235 }
236
237 #define WRITE_CACHE_DATA(data, fp) \
238 { \
239         size_t len; \
240         if (data == NULL) \
241                 len = 0; \
242         else \
243                 len = strlen(data); \
244         WRITE_CACHE_DATA_INT(len, fp); \
245         if (len > 0) { \
246                 fwrite(data, len, 1, fp); \
247         } \
248 }
249
250 static FILE *msgcache_open_data_file(const gchar *file, gint version,
251                                      DataOpenMode mode,
252                                      gchar *buf, size_t buf_size)
253 {
254         FILE *fp;
255         gint data_ver;
256
257         g_return_val_if_fail(file != NULL, NULL);
258
259         if (mode == DATA_WRITE) {
260                 if ((fp = fopen(file, "wb")) == NULL) {
261                         FILE_OP_ERROR(file, "fopen");
262                         return NULL;
263                 }
264                 if (change_file_mode_rw(fp, file) < 0)
265                         FILE_OP_ERROR(file, "chmod");
266
267                 WRITE_CACHE_DATA_INT(version, fp);
268                 return fp;
269         }
270
271         /* check version */
272         if ((fp = fopen(file, "rb")) == NULL)
273                 debug_print("Mark/Cache file not found\n");
274         else {
275                 if (buf && buf_size > 0)
276                         setvbuf(fp, buf, _IOFBF, buf_size);
277                 if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
278                          version != data_ver) {
279                         debug_print("Mark/Cache version is different (%d != %d). "
280                                     "Discarding it.\n", data_ver, version);
281                         fclose(fp);
282                         fp = NULL;
283                 }
284         }
285
286         if (mode == DATA_READ)
287                 return fp;
288
289         if (fp) {
290                 /* reopen with append mode */
291                 fclose(fp);
292                 if ((fp = fopen(file, "ab")) == NULL)
293                         FILE_OP_ERROR(file, "fopen");
294         } else {
295                 /* open with overwrite mode if mark file doesn't exist or
296                    version is different */
297                 fp = msgcache_open_data_file(file, version, DATA_WRITE, buf,
298                                             buf_size);
299         }
300
301         return fp;
302 }
303
304 static gint msgcache_read_cache_data_str(FILE *fp, gchar **str)
305 {
306         gchar buf[BUFFSIZE];
307         gint ret = 0;
308         guint32 len;
309
310         if (fread(&len, sizeof(len), 1, fp) == 1) {
311                 if (len > G_MAXINT)
312                         ret = -1;
313                 else {
314                         gchar *tmp = NULL;
315
316                         while (len > 0) {
317                                 size_t size = MIN(len, BUFFSIZE - 1);
318
319                                 if (fread(buf, size, 1, fp) != 1) {
320                                         ret = -1;
321                                         if (tmp) g_free(tmp);
322                                         *str = NULL;
323                                         break;
324                                 }
325
326                                 buf[size] = '\0';
327                                 if (tmp) {
328                                         *str = g_strconcat(tmp, buf, NULL);
329                                         g_free(tmp);
330                                         tmp = *str;
331                                 } else
332                                         tmp = *str = g_strdup(buf);
333
334                                 len -= size;
335                         }
336                 }
337         } else
338                 ret = -1;
339
340         if (ret < 0)
341                 g_warning("Cache data is corrupted\n");
342
343         return ret;
344 }
345
346 MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
347 {
348         MsgCache *cache;
349         FILE *fp;
350         MsgInfo *msginfo;
351         MsgTmpFlags tmp_flags = 0;
352         gchar file_buf[BUFFSIZE];
353         guint num;
354         gboolean error = FALSE;
355
356         g_return_val_if_fail(cache_file != NULL, NULL);
357         g_return_val_if_fail(item != NULL, NULL);
358
359         if ((fp = msgcache_open_data_file
360                 (cache_file, CACHE_VERSION, DATA_READ, file_buf, sizeof(file_buf))) == NULL)
361                 return NULL;
362
363         debug_print("\tReading message cache from %s...\n", cache_file);
364
365         if (item->stype == F_QUEUE) {
366                 tmp_flags |= MSG_QUEUED;
367         } else if (item->stype == F_DRAFT) {
368                 tmp_flags |= MSG_DRAFT;
369         }
370
371         cache = msgcache_new();
372
373         g_hash_table_freeze(cache->msgnum_table);
374
375         while (fread(&num, sizeof(num), 1, fp) == 1) {
376                 msginfo = procmsg_msginfo_new();
377                 msginfo->msgnum = num;
378                 READ_CACHE_DATA_INT(msginfo->size, fp);
379                 READ_CACHE_DATA_INT(msginfo->mtime, fp);
380                 READ_CACHE_DATA_INT(msginfo->date_t, fp);
381                 READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
382
383                 READ_CACHE_DATA(msginfo->fromname, fp);
384
385                 READ_CACHE_DATA(msginfo->date, fp);
386                 READ_CACHE_DATA(msginfo->from, fp);
387                 READ_CACHE_DATA(msginfo->to, fp);
388                 READ_CACHE_DATA(msginfo->cc, fp);
389                 READ_CACHE_DATA(msginfo->newsgroups, fp);
390                 READ_CACHE_DATA(msginfo->subject, fp);
391                 READ_CACHE_DATA(msginfo->msgid, fp);
392                 READ_CACHE_DATA(msginfo->inreplyto, fp);
393                 READ_CACHE_DATA(msginfo->references, fp);
394                 READ_CACHE_DATA(msginfo->xref, fp);
395                 READ_CACHE_DATA_INT(msginfo->planned_download, fp);
396
397                 msginfo->folder = item;
398                 msginfo->flags.tmp_flags |= tmp_flags;
399
400                 g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
401                 if(msginfo->msgid)
402                         g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
403                 cache->memusage += procmsg_msginfo_memusage(msginfo);
404         }
405         fclose(fp);
406         g_hash_table_thaw(cache->msgnum_table);
407
408         if(error) {
409                 msgcache_destroy(cache);
410                 return NULL;
411         }
412
413         cache->last_access = time(NULL);
414
415         debug_print("done. (%d items read)\n", g_hash_table_size(cache->msgnum_table));
416         debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
417
418         return cache;
419 }
420
421 void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
422 {
423         FILE *fp;
424         MsgInfo *msginfo;
425         MsgPermFlags perm_flags;
426         guint num;
427
428         if ((fp = msgcache_open_data_file(mark_file, MARK_VERSION, DATA_READ, NULL, 0)) == NULL)
429                 return;
430
431         debug_print("\tReading message marks from %s...\n", mark_file);
432
433         while (fread(&num, sizeof(num), 1, fp) == 1) {
434                 if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
435
436                 msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
437                 if(msginfo) {
438                         msginfo->flags.perm_flags = perm_flags;
439                 }
440         }
441         fclose(fp);
442 }
443
444 void msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
445 {
446         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
447
448         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
449         WRITE_CACHE_DATA_INT(msginfo->size, fp);
450         WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
451         WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
452         WRITE_CACHE_DATA_INT(flags, fp);
453
454         WRITE_CACHE_DATA(msginfo->fromname, fp);
455
456         WRITE_CACHE_DATA(msginfo->date, fp);
457         WRITE_CACHE_DATA(msginfo->from, fp);
458         WRITE_CACHE_DATA(msginfo->to, fp);
459         WRITE_CACHE_DATA(msginfo->cc, fp);
460         WRITE_CACHE_DATA(msginfo->newsgroups, fp);
461         WRITE_CACHE_DATA(msginfo->subject, fp);
462         WRITE_CACHE_DATA(msginfo->msgid, fp);
463         WRITE_CACHE_DATA(msginfo->inreplyto, fp);
464         WRITE_CACHE_DATA(msginfo->references, fp);
465         WRITE_CACHE_DATA(msginfo->xref, fp);
466         WRITE_CACHE_DATA_INT(msginfo->planned_download, fp);
467 }
468
469 static void msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
470 {
471         MsgPermFlags flags = msginfo->flags.perm_flags;
472
473         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
474         WRITE_CACHE_DATA_INT(flags, fp);
475 }
476
477 struct write_fps
478 {
479         FILE *cache_fp;
480         FILE *mark_fp;
481 };
482
483 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
484 {
485         MsgInfo *msginfo;
486         struct write_fps *write_fps;
487
488         msginfo = (MsgInfo *)value;
489         write_fps = user_data;
490
491         msgcache_write_cache(msginfo, write_fps->cache_fp);
492         msgcache_write_flags(msginfo, write_fps->mark_fp);
493 }
494
495 gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
496 {
497         struct write_fps write_fps;
498
499         g_return_val_if_fail(cache_file != NULL, -1);
500         g_return_val_if_fail(mark_file != NULL, -1);
501         g_return_val_if_fail(cache != NULL, -1);
502
503         write_fps.cache_fp = msgcache_open_data_file(cache_file, CACHE_VERSION,
504                 DATA_WRITE, NULL, 0);
505         if (write_fps.cache_fp == NULL)
506                 return -1;
507
508         write_fps.mark_fp = msgcache_open_data_file(mark_file, MARK_VERSION,
509                 DATA_WRITE, NULL, 0);
510         if (write_fps.mark_fp == NULL) {
511                 fclose(write_fps.cache_fp);
512                 return -1;
513         }
514
515         debug_print("\tWriting message cache to %s and %s...\n", cache_file, mark_file);
516
517         if (change_file_mode_rw(write_fps.cache_fp, cache_file) < 0)
518                 FILE_OP_ERROR(cache_file, "chmod");
519
520         g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
521
522         fclose(write_fps.cache_fp);
523         fclose(write_fps.mark_fp);
524
525         cache->last_access = time(NULL);
526
527         debug_print("done.\n");
528         return 0;
529 }
530