616905c539ab8e86d96c10670e50ab01ed8d8d5a
[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 struct _MsgCache {
32         GHashTable      *msgnum_table;
33         guint            memusage;
34         time_t           last_access;
35 };
36
37 MsgCache *msgcache_new()
38 {
39         MsgCache *cache;
40         
41         cache = g_new0(MsgCache, 1),
42         cache->msgnum_table = g_hash_table_new(NULL, NULL);
43         cache->last_access = time(NULL);
44
45         return cache;
46 }
47
48 static gboolean msgcache_msginfo_free_func(gpointer num, gpointer msginfo, gpointer user_data)
49 {
50         procmsg_msginfo_free((MsgInfo *)msginfo);
51         return TRUE;
52 }                                                                                         
53
54 void msgcache_destroy(MsgCache *cache)
55 {
56         g_return_if_fail(cache != NULL);
57
58         g_hash_table_foreach_remove(cache->msgnum_table, msgcache_msginfo_free_func, NULL);
59         g_hash_table_destroy(cache->msgnum_table);
60         g_free(cache);
61 }
62
63 void msgcache_add_msg(MsgCache *cache, MsgInfo *msginfo) 
64 {
65         MsgInfo *newmsginfo;
66
67         g_return_if_fail(cache != NULL);
68         g_return_if_fail(msginfo != NULL);
69
70         newmsginfo = procmsg_msginfo_new_ref(msginfo);
71         g_hash_table_insert(cache->msgnum_table, GINT_TO_POINTER(msginfo->msgnum), newmsginfo);
72         cache->memusage += procmsg_msginfo_memusage(msginfo);
73         cache->last_access = time(NULL);
74
75         debug_print(_("Cache size: %d messages, %d byte\n"), g_hash_table_size(cache->msgnum_table), cache->memusage);
76 }
77
78 void msgcache_remove_msg(MsgCache *cache, guint msgnum)
79 {
80         MsgInfo *msginfo;
81
82         g_return_if_fail(cache != NULL);
83         g_return_if_fail(msgnum > 0);
84
85         msginfo = (MsgInfo *) g_hash_table_lookup(cache->msgnum_table, GINT_TO_POINTER(msgnum));
86         if(!msginfo)
87                 return;
88
89         cache->memusage -= procmsg_msginfo_memusage(msginfo);
90         procmsg_msginfo_free(msginfo);
91         g_hash_table_remove(cache->msgnum_table, GINT_TO_POINTER(msgnum));
92         cache->last_access = time(NULL);
93
94         debug_print(_("Cache size: %d messages, %d byte\n"), g_hash_table_size(cache->msgnum_table), cache->memusage);
95 }
96
97 void msgcache_update_msg(MsgCache *cache, MsgInfo *msginfo)
98 {
99         MsgInfo *oldmsginfo, *newmsginfo;
100         
101         g_return_if_fail(cache != NULL);
102         g_return_if_fail(msginfo != NULL);
103
104         oldmsginfo = g_hash_table_lookup(cache->msgnum_table, GINT_TO_POINTER(msginfo->msgnum));
105         if(msginfo) {
106                 g_hash_table_remove(cache->msgnum_table, GINT_TO_POINTER(oldmsginfo->msgnum));
107                 procmsg_msginfo_free(oldmsginfo);
108         }
109         cache->memusage -= procmsg_msginfo_memusage(oldmsginfo);
110
111         newmsginfo = procmsg_msginfo_new_ref(msginfo);
112         g_hash_table_insert(cache->msgnum_table, GINT_TO_POINTER(msginfo->msgnum), newmsginfo);
113         cache->memusage += procmsg_msginfo_memusage(newmsginfo);
114         cache->last_access = time(NULL);
115         
116         debug_print(_("Cache size: %d messages, %d byte\n"), g_hash_table_size(cache->msgnum_table), cache->memusage);
117
118         return;
119 }
120
121 static gint msgcache_read_cache_data_str(FILE *fp, gchar **str)
122 {
123         gchar buf[BUFFSIZE];
124         gint ret = 0;
125         size_t len;
126
127         if (fread(&len, sizeof(len), 1, fp) == 1) {
128                 if (len < 0)
129                         ret = -1;
130                 else {
131                         gchar *tmp = NULL;
132
133                         while (len > 0) {
134                                 size_t size = MIN(len, BUFFSIZE - 1);
135
136                                 if (fread(buf, size, 1, fp) != 1) {
137                                         ret = -1;
138                                         if (tmp) g_free(tmp);
139                                         *str = NULL;
140                                         break;
141                                 }
142
143                                 buf[size] = '\0';
144                                 if (tmp) {
145                                         *str = g_strconcat(tmp, buf, NULL);
146                                         g_free(tmp);
147                                         tmp = *str;
148                                 } else
149                                         tmp = *str = g_strdup(buf);
150
151                                 len -= size;
152                         }
153                 }
154         } else
155                 ret = -1;
156
157         if (ret < 0)
158                 g_warning(_("Cache data is corrupted\n"));
159
160         return ret;
161 }
162
163
164 #define READ_CACHE_DATA(data, fp) \
165 { \
166         if (msgcache_read_cache_data_str(fp, &data) < 0) { \
167                 procmsg_msginfo_free(msginfo); \
168                 error = TRUE; \
169                 break; \
170         } \
171 }
172
173 #define READ_CACHE_DATA_INT(n, fp) \
174 { \
175         if (fread(&n, sizeof(n), 1, fp) != 1) { \
176                 g_warning(_("Cache data is corrupted\n")); \
177                 procmsg_msginfo_free(msginfo); \
178                 error = TRUE; \
179                 break; \
180         } \
181 }
182
183 MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
184 {
185         MsgCache *cache;
186         FILE *fp;
187         MsgInfo *msginfo;
188 /*      MsgFlags default_flags; */
189         gchar file_buf[BUFFSIZE];
190         gint ver;
191         guint num;
192         gboolean error = FALSE;
193
194         g_return_val_if_fail(cache_file != NULL, NULL);
195         g_return_val_if_fail(item != NULL, NULL);
196
197         if ((fp = fopen(cache_file, "rb")) == NULL) {
198                 debug_print(_("\tNo cache file\n"));
199                 return NULL;
200         }
201         setvbuf(fp, file_buf, _IOFBF, sizeof(file_buf));
202
203         debug_print(_("\tReading message cache from %s...\n"), cache_file);
204
205         /* compare cache version */
206         if (fread(&ver, sizeof(ver), 1, fp) != 1 ||
207             CACHE_VERSION != ver) {
208                 debug_print(_("Cache version is different. Discarding it.\n"));
209                 fclose(fp);
210                 return NULL;
211         }
212
213         cache = msgcache_new();
214
215         g_hash_table_freeze(cache->msgnum_table);
216
217         while (fread(&num, sizeof(num), 1, fp) == 1) {
218                 msginfo = procmsg_msginfo_new();
219                 msginfo->msgnum = num;
220                 READ_CACHE_DATA_INT(msginfo->size, fp);
221                 READ_CACHE_DATA_INT(msginfo->mtime, fp);
222                 READ_CACHE_DATA_INT(msginfo->date_t, fp);
223                 READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
224
225                 READ_CACHE_DATA(msginfo->fromname, fp);
226
227                 READ_CACHE_DATA(msginfo->date, fp);
228                 READ_CACHE_DATA(msginfo->from, fp);
229                 READ_CACHE_DATA(msginfo->to, fp);
230                 READ_CACHE_DATA(msginfo->cc, fp);
231                 READ_CACHE_DATA(msginfo->newsgroups, fp);
232                 READ_CACHE_DATA(msginfo->subject, fp);
233                 READ_CACHE_DATA(msginfo->msgid, fp);
234                 READ_CACHE_DATA(msginfo->inreplyto, fp);
235                 READ_CACHE_DATA(msginfo->references, fp);
236                 READ_CACHE_DATA(msginfo->xref, fp);
237
238 /*
239                 MSG_SET_PERM_FLAGS(msginfo->flags, default_flags.perm_flags);
240                 MSG_SET_TMP_FLAGS(msginfo->flags, default_flags.tmp_flags);
241 */
242                 msginfo->folder = item;
243
244                 g_hash_table_insert(cache->msgnum_table, GINT_TO_POINTER(msginfo->msgnum), msginfo);
245                 cache->memusage += procmsg_msginfo_memusage(msginfo);
246         }
247         fclose(fp);
248
249         if(error) {
250                 g_hash_table_thaw(cache->msgnum_table);
251                 msgcache_destroy(cache);
252                 return NULL;
253         }
254
255         cache->last_access = time(NULL);
256         g_hash_table_thaw(cache->msgnum_table);
257
258         debug_print(_("done. (%d items read)\n"), g_hash_table_size(cache->msgnum_table));
259         debug_print(_("Cache size: %d messages, %d byte\n"), g_hash_table_size(cache->msgnum_table), cache->memusage);
260
261         return cache;
262 }
263
264 void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
265 {
266         FILE *fp;
267         MsgInfo *msginfo;
268         MsgPermFlags perm_flags;
269         gint ver;
270         guint num;
271
272         if ((fp = fopen(mark_file, "rb")) == NULL) {
273                 debug_print(_("Mark file not found.\n"));
274                 return;
275         } else if (fread(&ver, sizeof(ver), 1, fp) != 1 || MARK_VERSION != ver) {
276                 debug_print(_("Mark version is different (%d != %d). "
277                               "Discarding it.\n"), ver, MARK_VERSION);
278         } else {
279                 debug_print(_("\tReading message marks from %s...\n"), mark_file);
280
281                 while (fread(&num, sizeof(num), 1, fp) == 1) {
282                         if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
283
284                         msginfo = g_hash_table_lookup(cache->msgnum_table, GUINT_TO_POINTER(num));
285                         if(msginfo) {
286                                 msginfo->flags.perm_flags = perm_flags;
287                         }
288                 }
289         }
290         fclose(fp);
291 }
292
293 #define WRITE_CACHE_DATA_INT(n, fp) \
294         fwrite(&n, sizeof(n), 1, fp)
295
296 #define WRITE_CACHE_DATA(data, fp) \
297 { \
298         gint len; \
299  \
300         if (data == NULL || (len = strlen(data)) == 0) { \
301                 len = 0; \
302                 WRITE_CACHE_DATA_INT(len, fp); \
303         } else { \
304                 len = strlen(data); \
305                 WRITE_CACHE_DATA_INT(len, fp); \
306                 fwrite(data, len, 1, fp); \
307         } \
308 }
309
310 void msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
311 {
312         MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
313
314         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
315         WRITE_CACHE_DATA_INT(msginfo->size, fp);
316         WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
317         WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
318         WRITE_CACHE_DATA_INT(flags, fp);
319
320         WRITE_CACHE_DATA(msginfo->fromname, fp);
321
322         WRITE_CACHE_DATA(msginfo->date, fp);
323         WRITE_CACHE_DATA(msginfo->from, fp);
324         WRITE_CACHE_DATA(msginfo->to, fp);
325         WRITE_CACHE_DATA(msginfo->cc, fp);
326         WRITE_CACHE_DATA(msginfo->newsgroups, fp);
327         WRITE_CACHE_DATA(msginfo->subject, fp);
328         WRITE_CACHE_DATA(msginfo->msgid, fp);
329         WRITE_CACHE_DATA(msginfo->inreplyto, fp);
330         WRITE_CACHE_DATA(msginfo->references, fp);
331         WRITE_CACHE_DATA(msginfo->xref, fp);
332 }
333
334 static void msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
335 {
336         MsgPermFlags flags = msginfo->flags.perm_flags;
337
338         WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
339         WRITE_CACHE_DATA_INT(flags, fp);
340 }
341
342 struct write_fps
343 {
344         FILE *cache_fp;
345         FILE *mark_fp;
346 };
347
348 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
349 {
350         MsgInfo *msginfo;
351         struct write_fps *write_fps;
352         
353         msginfo = (MsgInfo *)value;
354         write_fps = user_data;
355
356         msgcache_write_cache(msginfo, write_fps->cache_fp);
357         msgcache_write_flags(msginfo, write_fps->mark_fp);
358 }
359
360 gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
361 {
362         FILE *fp;
363         struct write_fps write_fps;
364         gint ver;
365
366         g_return_val_if_fail(cache_file != NULL, -1);
367         g_return_val_if_fail(mark_file != NULL, -1);
368         g_return_val_if_fail(cache != NULL, -1);
369
370         debug_print(_("\tWriting message cache to %s and %s...\n"), cache_file, mark_file);
371
372         if ((fp = fopen(cache_file, "wb")) == NULL) {
373                 FILE_OP_ERROR(cache_file, "fopen");
374                 return -1;
375         }
376         if (change_file_mode_rw(fp, cache_file) < 0)
377                 FILE_OP_ERROR(cache_file, "chmod");
378
379         ver = CACHE_VERSION;
380         WRITE_CACHE_DATA_INT(ver, fp);  
381         write_fps.cache_fp = fp;
382
383         if ((fp = fopen(mark_file, "wb")) == NULL) {
384                 FILE_OP_ERROR(mark_file, "fopen");
385                 fclose(write_fps.cache_fp);
386                 return -1;
387         }
388
389         ver = MARK_VERSION;
390         WRITE_CACHE_DATA_INT(ver, fp);
391         write_fps.mark_fp = fp;
392
393         g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
394
395         fclose(write_fps.cache_fp);
396         fclose(write_fps.mark_fp);
397
398         cache->last_access = time(NULL);
399
400         debug_print(_("done.\n"));
401         return 0;
402 }
403
404 MsgInfo *msgcache_get_msg(MsgCache *cache, guint num)
405 {
406         MsgInfo *msginfo;
407
408         g_return_val_if_fail(cache != NULL, NULL);
409
410         msginfo = g_hash_table_lookup(cache->msgnum_table, GINT_TO_POINTER(num));
411         if(!msginfo)
412                 return NULL;
413         cache->last_access = time(NULL);
414         
415         return procmsg_msginfo_new_ref(msginfo);
416 }
417
418 static void msgcache_get_msg_list_func(gpointer key, gpointer value, gpointer user_data)
419 {
420         GSList **listptr = user_data;
421         MsgInfo *msginfo = value;
422
423         *listptr = g_slist_prepend(*listptr, procmsg_msginfo_new_ref(msginfo));
424 }
425
426 GSList *msgcache_get_msg_list(MsgCache *cache)
427 {
428         GSList *msg_list = NULL;
429
430         g_return_val_if_fail(cache != NULL, NULL);
431
432         g_hash_table_foreach((GHashTable *)cache->msgnum_table, msgcache_get_msg_list_func, (gpointer)&msg_list);       
433         cache->last_access = time(NULL);
434
435         return msg_list;
436 }
437
438 time_t msgcache_get_last_access_time(MsgCache *cache)
439 {
440         g_return_val_if_fail(cache != NULL, 0);
441         
442         return cache->last_access;
443 }
444
445 gint msgcache_get_memory_usage(MsgCache *cache)
446 {
447         g_return_val_if_fail(cache != NULL, 0);
448
449         return cache->memusage;
450 }