0.9.10claws55
[claws.git] / src / folder.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 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 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <stdlib.h>
33
34 #include "intl.h"
35 #include "folder.h"
36 #include "session.h"
37 #include "imap.h"
38 #include "news.h"
39 #include "mh.h"
40 #include "utils.h"
41 #include "xml.h"
42 #include "codeconv.h"
43 #include "prefs_gtk.h"
44 #include "account.h"
45 #include "filtering.h"
46 #include "procheader.h"
47 #include "hooks.h"
48 #include "log.h"
49 #include "folder_item_prefs.h"
50 #include "remotefolder.h"
51
52 /* Dependecies to be removed ?! */
53 #include "prefs_common.h"
54 #include "prefs_account.h"
55
56 static GList *folder_list = NULL;
57 static GSList *class_list = NULL;
58 static GSList *folder_unloaded_list = NULL;
59
60 void folder_init                (Folder         *folder,
61                                  const gchar    *name);
62
63 static gchar *folder_get_list_path      (void);
64 static GNode *folder_get_xml_node       (Folder         *folder);
65 static Folder *folder_get_from_xml      (GNode          *node);
66 static void folder_update_op_count_rec  (GNode          *node);
67
68
69 static void folder_get_persist_prefs_recursive
70                                         (GNode *node, GHashTable *pptable);
71 static gboolean persist_prefs_free      (gpointer key, gpointer val, gpointer data);
72 void folder_item_read_cache             (FolderItem *item);
73 void folder_item_free_cache             (FolderItem *item);
74 gint folder_item_scan_full              (FolderItem *item, gboolean filtering);
75
76 void folder_system_init(void)
77 {
78         folder_register_class(mh_get_class());
79         folder_register_class(imap_get_class());
80         folder_register_class(news_get_class());
81 }
82
83 GSList *folder_get_class_list(void)
84 {
85         return class_list;
86 }
87
88 void folder_register_class(FolderClass *klass)
89 {
90         GSList *xmllist, *cur;
91
92         debug_print("registering folder class %s\n", klass->idstr);
93
94         class_list = g_slist_append(class_list, klass);
95
96         xmllist = g_slist_copy(folder_unloaded_list);
97         for (cur = xmllist; cur != NULL; cur = g_slist_next(cur)) {
98                 GNode *node = (GNode *) cur->data;
99                 XMLNode *xmlnode = (XMLNode *) node->data;
100                 GList *cur = xmlnode->tag->attr;
101
102                 for (; cur != NULL; cur = g_list_next(cur)) {
103                         XMLAttr *attr = (XMLAttr *) cur->data;
104
105                         if (!attr || !attr->name || !attr->value) continue;
106                         if (!strcmp(attr->name, "type") && !strcmp(attr->value, klass->idstr)) {
107                                 Folder *folder;
108
109                                 folder = folder_get_from_xml(node);
110                                 folder_add(folder);
111                                 folder_unloaded_list = g_slist_remove(folder_unloaded_list, node);
112
113                                 cur = NULL;
114                                 continue;
115                         }
116                 }
117         }
118         g_slist_free(xmllist);
119 }
120
121 void folder_unregister_class(FolderClass *klass)
122 {
123         GList *folderlist, *cur;
124
125         debug_print("unregistering folder class %s\n", klass->idstr);
126
127         class_list = g_slist_remove(class_list, klass);
128
129         folderlist = g_list_copy(folder_get_list());
130         for (cur = folderlist; cur != NULL; cur = g_list_next(cur)) {
131                 Folder *folder = (Folder *) cur->data;
132
133                 if (folder->klass == klass) {
134                         GNode *xmlnode = folder_get_xml_node(folder);
135                         folder_unloaded_list = g_slist_append(folder_unloaded_list, xmlnode);
136                         folder_destroy(folder);
137                 }
138         }
139         g_list_free(folderlist);
140 }
141
142 Folder *folder_new(FolderClass *klass, const gchar *name, const gchar *path)
143 {
144         Folder *folder = NULL;
145         FolderItem *item;
146
147         g_return_val_if_fail(klass != NULL, NULL);
148
149         name = name ? name : path;
150         folder = klass->new_folder(name, path);
151
152         /* Create root folder item */
153         item = folder_item_new(folder, name, NULL);
154         item->folder = folder;
155         folder->node = item->node = g_node_new(item);
156         folder->data = NULL;
157
158         return folder;
159 }
160
161 void folder_init(Folder *folder, const gchar *name)
162 {
163         g_return_if_fail(folder != NULL);
164
165         folder_set_name(folder, name);
166
167         /* Init folder data */
168         folder->account = NULL;
169         folder->sort = 0;
170         folder->inbox = NULL;
171         folder->outbox = NULL;
172         folder->draft = NULL;
173         folder->queue = NULL;
174         folder->trash = NULL;
175 }
176
177 void folder_destroy(Folder *folder)
178 {
179         g_return_if_fail(folder != NULL);
180         g_return_if_fail(folder->klass->destroy_folder != NULL);
181
182         folder_remove(folder);
183
184         folder_tree_destroy(folder);
185
186         folder->klass->destroy_folder(folder);
187
188         g_free(folder->name);
189         g_free(folder);
190 }
191
192 void folder_set_xml(Folder *folder, XMLTag *tag)
193 {
194         GList *cur;
195         FolderItem *rootitem = NULL;
196
197         if ((folder->node != NULL) && (folder->node->data != NULL))
198                 rootitem = (FolderItem *) folder->node->data;
199
200         for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
201                 XMLAttr *attr = (XMLAttr *) cur->data;
202
203                 if (!attr || !attr->name || !attr->value) continue;
204                 if (!strcmp(attr->name, "name")) {
205                         if (folder->name != NULL)
206                                 g_free(folder->name);
207                         folder->name = g_strdup(attr->value);
208                         if (rootitem != NULL) {
209                                 if (rootitem->name != NULL)
210                                         g_free(rootitem->name);
211                                 rootitem->name = g_strdup(attr->value);
212                         }
213                 } else if (!strcmp(attr->name, "account_id")) {
214                         PrefsAccount *account;
215
216                         account = account_find_from_id(atoi(attr->value));
217                         if (!account)
218                                 g_warning("account_id: %s not found\n", attr->value);
219                         else {
220                                 folder->account = account;
221                                 account->folder = folder;
222                         }
223                 } else if (!strcmp(attr->name, "collapsed")) {
224                         if (rootitem != NULL)
225                                 rootitem->collapsed = *attr->value == '1' ? TRUE : FALSE;
226                 } else if (!strcmp(attr->name, "sort")) {
227                         folder->sort = atoi(attr->value);
228                 }
229         }
230 }
231
232 XMLTag *folder_get_xml(Folder *folder)
233 {
234         XMLTag *tag;
235
236         tag = xml_new_tag("folder");
237
238         if (folder->name)
239                 xml_tag_add_attr(tag, "name", g_strdup(folder->name));
240         if (folder->account)
241                 xml_tag_add_attr(tag, "account_id", g_strdup_printf("%d", folder->account->account_id));
242         if (folder->node && folder->node->data) {
243                 FolderItem *rootitem = (FolderItem *) folder->node->data;
244
245                 xml_tag_add_attr(tag, "collapsed", g_strdup(rootitem->collapsed ? "1" : "0"));
246         }
247         xml_tag_add_attr(tag, "sort", g_strdup_printf("%d", folder->sort));
248
249         return tag;
250 }
251
252 FolderItem *folder_item_new(Folder *folder, const gchar *name, const gchar *path)
253 {
254         FolderItem *item = NULL;
255
256         if (folder->klass->item_new) {
257                 item = folder->klass->item_new(folder);
258         } else {
259                 item = g_new0(FolderItem, 1);
260         }
261
262         g_return_val_if_fail(item != NULL, NULL);
263
264         item->stype = F_NORMAL;
265         item->name = g_strdup(name);
266         item->path = g_strdup(path);
267         item->mtime = 0;
268         item->new_msgs = 0;
269         item->unread_msgs = 0;
270         item->unreadmarked_msgs = 0;
271         item->total_msgs = 0;
272         item->last_num = -1;
273         item->cache = NULL;
274         item->no_sub = FALSE;
275         item->no_select = FALSE;
276         item->collapsed = FALSE;
277         item->thread_collapsed = FALSE;
278         item->threaded  = TRUE;
279         item->ret_rcpt  = FALSE;
280         item->opened    = FALSE;
281         item->node = g_node_new(item);
282         item->folder = NULL;
283         item->account = NULL;
284         item->apply_sub = FALSE;
285         item->mark_queue = NULL;
286         item->data = NULL;
287
288         item->prefs = folder_item_prefs_new();
289
290         return item;
291 }
292
293 void folder_item_append(FolderItem *parent, FolderItem *item)
294 {
295         g_return_if_fail(parent != NULL);
296         g_return_if_fail(parent->folder != NULL);
297         g_return_if_fail(parent->node != NULL);
298         g_return_if_fail(item != NULL);
299
300         item->folder = parent->folder;
301         g_node_append(parent->node, item->node);
302 }
303
304 static gboolean folder_item_remove_func(GNode *node, gpointer data)
305 {
306         FolderItem *item = FOLDER_ITEM(node->data);
307         FolderUpdateData hookdata;
308
309         if (item->cache != NULL) {
310                 msgcache_destroy(item->cache);
311                 item->cache = NULL;
312         }
313
314         hookdata.folder = item->folder;
315         hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_REMOVE_FOLDERITEM;
316         hookdata.item = item;
317         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
318
319         folder_item_destroy(item);
320
321         return FALSE;
322 }
323
324 void folder_item_remove(FolderItem *item)
325 {
326         GNode *node;
327
328         g_return_if_fail(item != NULL);
329         g_return_if_fail(item->folder != NULL);
330         g_return_if_fail(item->node != NULL);
331
332         node = item->node;
333
334         if (item->folder->node == node)
335                 item->folder->node = NULL;
336
337         g_node_traverse(node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
338                         folder_item_remove_func, NULL);
339         g_node_destroy(node);
340 }
341
342 void folder_item_remove_children(FolderItem *item)
343 {
344         GNode *node, *next;
345
346         g_return_if_fail(item != NULL);
347         g_return_if_fail(item->folder != NULL);
348         g_return_if_fail(item->node != NULL);
349
350         node = item->node->children;
351         while (node != NULL) {
352                 next = node->next;
353                 folder_item_remove(FOLDER_ITEM(node->data));
354                 node = next;
355         }
356 }
357
358 void folder_item_destroy(FolderItem *item)
359 {
360         Folder *folder;
361
362         g_return_if_fail(item != NULL);
363
364         folder = item->folder;
365         if (folder) {
366                 if (folder->inbox == item)
367                         folder->inbox = NULL;
368                 else if (folder->outbox == item)
369                         folder->outbox = NULL;
370                 else if (folder->draft == item)
371                         folder->draft = NULL;
372                 else if (folder->queue == item)
373                         folder->queue = NULL;
374                 else if (folder->trash == item)
375                         folder->trash = NULL;
376         }
377
378         if (item->cache)
379                 folder_item_free_cache(item);
380         if (item->prefs)
381                 folder_item_prefs_free(item->prefs);
382         g_free(item->name);
383         g_free(item->path);
384
385         if (item->folder != NULL) {
386                 if(item->folder->klass->item_destroy) {
387                         item->folder->klass->item_destroy(item->folder, item);
388                 } else {
389                         g_free(item);
390                 }
391         }
392 }
393
394 FolderItem *folder_item_parent(FolderItem *item)
395 {
396         g_return_val_if_fail(item != NULL, NULL);
397         g_return_val_if_fail(item->node != NULL, NULL);
398
399         if (item->node->parent == NULL)
400                 return NULL;
401         return (FolderItem *) item->node->parent->data;
402 }
403
404 void folder_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
405 {
406         GList *cur;
407
408         for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
409                 XMLAttr *attr = (XMLAttr *) cur->data;
410
411                 if (!attr || !attr->name || !attr->value) continue;
412                 if (!strcmp(attr->name, "type")) {
413                         if (!strcasecmp(attr->value, "normal"))
414                                 item->stype = F_NORMAL;
415                         else if (!strcasecmp(attr->value, "inbox"))
416                                 item->stype = F_INBOX;
417                         else if (!strcasecmp(attr->value, "outbox"))
418                                 item->stype = F_OUTBOX;
419                         else if (!strcasecmp(attr->value, "draft"))
420                                 item->stype = F_DRAFT;
421                         else if (!strcasecmp(attr->value, "queue"))
422                                 item->stype = F_QUEUE;
423                         else if (!strcasecmp(attr->value, "trash"))
424                                 item->stype = F_TRASH;
425                 } else if (!strcmp(attr->name, "name")) {
426                         if (item->name != NULL)
427                                 g_free(item->name);
428                         item->name = g_strdup(attr->value);
429                 } else if (!strcmp(attr->name, "path")) {
430                         if (item->path != NULL)
431                                 g_free(item->path);
432                         item->path = g_strdup(attr->value);
433                 } else if (!strcmp(attr->name, "mtime"))
434                         item->mtime = strtoul(attr->value, NULL, 10);
435                 else if (!strcmp(attr->name, "new"))
436                         item->new_msgs = atoi(attr->value);
437                 else if (!strcmp(attr->name, "unread"))
438                         item->unread_msgs = atoi(attr->value);
439                 else if (!strcmp(attr->name, "unreadmarked"))
440                         item->unreadmarked_msgs = atoi(attr->value);
441                 else if (!strcmp(attr->name, "total"))
442                         item->total_msgs = atoi(attr->value);
443                 else if (!strcmp(attr->name, "no_sub"))
444                         item->no_sub = *attr->value == '1' ? TRUE : FALSE;
445                 else if (!strcmp(attr->name, "no_select"))
446                         item->no_select = *attr->value == '1' ? TRUE : FALSE;
447                 else if (!strcmp(attr->name, "collapsed"))
448                         item->collapsed = *attr->value == '1' ? TRUE : FALSE;
449                 else if (!strcmp(attr->name, "thread_collapsed"))
450                         item->thread_collapsed =  *attr->value == '1' ? TRUE : FALSE;
451                 else if (!strcmp(attr->name, "threaded"))
452                         item->threaded =  *attr->value == '1' ? TRUE : FALSE;
453                 else if (!strcmp(attr->name, "hidereadmsgs"))
454                         item->hide_read_msgs =  *attr->value == '1' ? TRUE : FALSE;
455                 else if (!strcmp(attr->name, "reqretrcpt"))
456                         item->ret_rcpt =  *attr->value == '1' ? TRUE : FALSE;
457                 else if (!strcmp(attr->name, "sort_key")) {
458                         if (!strcmp(attr->value, "none"))
459                                 item->sort_key = SORT_BY_NONE;
460                         else if (!strcmp(attr->value, "number"))
461                                 item->sort_key = SORT_BY_NUMBER;
462                         else if (!strcmp(attr->value, "size"))
463                                 item->sort_key = SORT_BY_SIZE;
464                         else if (!strcmp(attr->value, "date"))
465                                 item->sort_key = SORT_BY_DATE;
466                         else if (!strcmp(attr->value, "from"))
467                                 item->sort_key = SORT_BY_FROM;
468                         else if (!strcmp(attr->value, "subject"))
469                                 item->sort_key = SORT_BY_SUBJECT;
470                         else if (!strcmp(attr->value, "score"))
471                                 item->sort_key = SORT_BY_SCORE;
472                         else if (!strcmp(attr->value, "label"))
473                                 item->sort_key = SORT_BY_LABEL;
474                         else if (!strcmp(attr->value, "mark"))
475                                 item->sort_key = SORT_BY_MARK;
476                         else if (!strcmp(attr->value, "unread"))
477                                 item->sort_key = SORT_BY_STATUS;
478                         else if (!strcmp(attr->value, "mime"))
479                                 item->sort_key = SORT_BY_MIME;
480                         else if (!strcmp(attr->value, "to"))
481                                 item->sort_key = SORT_BY_TO;
482                         else if (!strcmp(attr->value, "locked"))
483                                 item->sort_key = SORT_BY_LOCKED;
484                 } else if (!strcmp(attr->name, "sort_type")) {
485                         if (!strcmp(attr->value, "ascending"))
486                                 item->sort_type = SORT_ASCENDING;
487                         else
488                                 item->sort_type = SORT_DESCENDING;
489                 } else if (!strcmp(attr->name, "account_id")) {
490                         PrefsAccount *account;
491
492                         account = account_find_from_id(atoi(attr->value));
493                         if (!account)
494                                 g_warning("account_id: %s not found\n", attr->value);
495                         else
496                                 item->account = account;
497                 } else if (!strcmp(attr->name, "apply_sub"))
498                         item->apply_sub = *attr->value == '1' ? TRUE : FALSE;
499         }
500 }
501
502 XMLTag *folder_item_get_xml(Folder *folder, FolderItem *item)
503 {
504         static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox",
505                                                  "draft", "queue", "trash"};
506         static gchar *sort_key_str[] = {"none", "number", "size", "date",
507                                         "from", "subject", "score", "label",
508                                         "mark", "unread", "mime", "to", 
509                                         "locked"};
510         XMLTag *tag;
511
512         tag = xml_new_tag("folderitem");
513
514         xml_tag_add_attr(tag, "type", g_strdup(folder_item_stype_str[item->stype]));
515         if (item->name)
516                 xml_tag_add_attr(tag, "name", g_strdup(item->name));
517         if (item->path)
518                 xml_tag_add_attr(tag, "path", g_strdup(item->path));
519         if (item->no_sub)
520                 xml_tag_add_attr(tag, "no_sub", g_strdup("1"));
521         if (item->no_select)
522                 xml_tag_add_attr(tag, "no_select", g_strdup("1"));
523         xml_tag_add_attr(tag, "collapsed", g_strdup(item->collapsed && item->node->children ? "1" : "0"));
524         xml_tag_add_attr(tag, "thread_collapsed", g_strdup(item->thread_collapsed ? "1" : "0"));
525         xml_tag_add_attr(tag, "threaded", g_strdup(item->threaded ? "1" : "0"));
526         xml_tag_add_attr(tag, "hidereadmsgs", g_strdup(item->hide_read_msgs ? "1" : "0"));
527         if (item->ret_rcpt)
528                 xml_tag_add_attr(tag, "reqretrcpt", g_strdup("1"));
529
530         if (item->sort_key != SORT_BY_NONE) {
531                 xml_tag_add_attr(tag, "sort_key", g_strdup(sort_key_str[item->sort_key]));
532                 xml_tag_add_attr(tag, "sort_type", g_strdup(item->sort_type == SORT_ASCENDING ? "ascending" : "descending"));
533         }
534
535         xml_tag_add_attr(tag, "mtime", g_strdup_printf("%ld", (unsigned long int) item->mtime));
536         xml_tag_add_attr(tag, "new", g_strdup_printf("%d", item->new_msgs));
537         xml_tag_add_attr(tag, "unread", g_strdup_printf("%d", item->unread_msgs));
538         xml_tag_add_attr(tag, "unreadmarked", g_strdup_printf("%d", item->unreadmarked_msgs));
539         xml_tag_add_attr(tag, "total", g_strdup_printf("%d", item->total_msgs));
540
541         if (item->account)
542                 xml_tag_add_attr(tag, "account_id", g_strdup_printf("%d", item->account->account_id));
543         if (item->apply_sub)
544                 xml_tag_add_attr(tag, "apply_sub", g_strdup("1"));
545
546         return tag;
547 }
548
549 void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data)
550 {
551         g_return_if_fail(folder != NULL);
552
553         folder->ui_func = func;
554         folder->ui_func_data = data;
555 }
556
557 void folder_set_name(Folder *folder, const gchar *name)
558 {
559         g_return_if_fail(folder != NULL);
560
561         g_free(folder->name);
562         folder->name = name ? g_strdup(name) : NULL;
563         if (folder->node && folder->node->data) {
564                 FolderItem *item = (FolderItem *)folder->node->data;
565
566                 g_free(item->name);
567                 item->name = name ? g_strdup(name) : NULL;
568         }
569 }
570
571 void folder_set_sort(Folder *folder, guint sort)
572 {
573         g_return_if_fail(folder != NULL);
574
575         if (folder->sort != sort) {
576                 folder_remove(folder);
577                 folder->sort = sort;
578                 folder_add(folder);
579         }
580 }
581
582 gboolean folder_tree_destroy_func(GNode *node, gpointer data) {
583         FolderItem *item = (FolderItem *) node->data;
584
585         folder_item_destroy(item);
586         return FALSE;
587 }
588
589 void folder_tree_destroy(Folder *folder)
590 {
591         GNode *node;
592
593         g_return_if_fail(folder != NULL);
594
595         node = folder->node;
596         
597         prefs_filtering_clear_folder(folder);
598
599         if (node != NULL) {
600                 g_node_traverse(node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
601                                 folder_tree_destroy_func, NULL);
602                 g_node_destroy(node);
603                 folder->node = NULL;
604         }
605 }
606
607 void folder_add(Folder *folder)
608 {
609         Folder *cur_folder;
610         GList *cur;
611         gint i;
612         FolderUpdateData hookdata;
613
614         g_return_if_fail(folder != NULL);
615
616         for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) {
617                 cur_folder = FOLDER(cur->data);
618                 if (cur_folder->sort < folder->sort)
619                         break;
620         }
621
622         folder_list = g_list_insert(folder_list, folder, i);
623
624         hookdata.folder = folder;
625         hookdata.update_flags = FOLDER_ADD_FOLDER;
626         hookdata.item = NULL;
627         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
628 }
629
630 void folder_remove(Folder *folder)
631 {
632         FolderUpdateData hookdata;
633
634         g_return_if_fail(folder != NULL);
635
636         folder_list = g_list_remove(folder_list, folder);
637
638         hookdata.folder = folder;
639         hookdata.update_flags = FOLDER_REMOVE_FOLDER;
640         hookdata.item = NULL;
641         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
642 }
643
644 GList *folder_get_list(void)
645 {
646         return folder_list;
647 }
648
649 gint folder_read_list(void)
650 {
651         GNode *node, *cur;
652         XMLNode *xmlnode;
653         gchar *path;
654
655         path = folder_get_list_path();
656         if (!is_file_exist(path)) return -1;
657         node = xml_parse_file(path);
658         if (!node) return -1;
659
660         xmlnode = node->data;
661         if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) {
662                 g_warning("wrong folder list\n");
663                 xml_free_tree(node);
664                 return -1;
665         }
666
667         cur = node->children;
668         while (cur != NULL) {
669                 Folder *folder;
670
671                 folder = folder_get_from_xml(cur);
672                 if (folder != NULL)
673                         folder_add(folder);
674                 else
675                         folder_unloaded_list = g_slist_append(folder_unloaded_list,
676                                 (gpointer) xml_copy_tree(cur));
677                 cur = cur->next;
678         }
679
680         xml_free_tree(node);
681         if (folder_list)
682                 return 0;
683         else
684                 return -1;
685 }
686
687 void folder_write_list(void)
688 {
689         GList *list;
690         GSList *slist;
691         Folder *folder;
692         gchar *path;
693         PrefFile *pfile;
694         GNode *rootnode;
695         XMLNode *xmlnode;
696         XMLTag *tag;
697
698         path = folder_get_list_path();
699         if ((pfile = prefs_write_open(path)) == NULL) return;
700
701         fprintf(pfile->fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
702                 conv_get_current_charset_str());
703         tag = xml_new_tag("folderlist");
704
705         xmlnode = g_new0(XMLNode, 1);
706         xmlnode->tag = tag;
707         xmlnode->element = NULL;
708
709         rootnode = g_node_new(xmlnode);
710
711         for (list = folder_list; list != NULL; list = list->next) {
712                 GNode *node;
713
714                 folder = list->data;
715                 node = folder_get_xml_node(folder);
716                 if (node != NULL)
717                         g_node_append(rootnode, node);
718         }
719
720         for (slist = folder_unloaded_list; slist != NULL; slist = g_slist_next(slist)) {
721                 GNode *node = (GNode *) slist->data;
722
723                 g_node_append(rootnode, (gpointer) xml_copy_tree(node));
724         }
725
726         xml_write_tree(rootnode, pfile->fp);
727
728         if (prefs_file_close(pfile) < 0)
729                 g_warning("failed to write folder list.\n");
730
731         xml_free_tree(rootnode);
732 }
733
734 gboolean folder_scan_tree_func(GNode *node, gpointer data)
735 {
736         GHashTable *pptable = (GHashTable *)data;
737         FolderItem *item = (FolderItem *)node->data;
738         
739         folder_item_restore_persist_prefs(item, pptable);
740         folder_item_scan_full(item, FALSE);
741
742         return FALSE;
743 }
744
745 void folder_scan_tree(Folder *folder)
746 {
747         GHashTable *pptable;
748         FolderUpdateData hookdata;
749         
750         if (!folder->klass->scan_tree)
751                 return;
752         
753         pptable = folder_persist_prefs_new(folder);
754
755         /*
756          * should be changed and tree update should be done without 
757          * destroying the tree first
758          */
759         folder_tree_destroy(folder);
760         folder->klass->scan_tree(folder);
761
762         hookdata.folder = folder;
763         hookdata.update_flags = FOLDER_TREE_CHANGED;
764         hookdata.item = NULL;
765         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
766
767         g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, folder_scan_tree_func, pptable);
768         folder_persist_prefs_free(pptable);
769
770         prefs_matcher_read_config();
771
772         folder_write_list();
773 }
774
775 FolderItem *folder_create_folder(FolderItem *parent, const gchar *name)
776 {
777         FolderItem *new_item;
778
779         new_item = parent->folder->klass->create_folder(parent->folder, parent, name);
780         if (new_item) {
781                 FolderUpdateData hookdata;
782
783                 new_item->cache = msgcache_new();
784
785                 hookdata.folder = new_item->folder;
786                 hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_ADD_FOLDERITEM;
787                 hookdata.item = new_item;
788                 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
789         }
790
791         return new_item;
792 }
793
794 gint folder_item_rename(FolderItem *item, gchar *newname)
795 {
796         gint retval;
797
798         g_return_val_if_fail(item != NULL, -1);
799         g_return_val_if_fail(newname != NULL, -1);
800
801         retval = item->folder->klass->rename_folder(item->folder, item, newname);
802
803         if (retval >= 0) {
804                 FolderItemUpdateData hookdata;
805
806                 hookdata.item = item;
807                 hookdata.update_flags = FOLDER_TREE_CHANGED;
808                 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &hookdata);
809         }
810
811         return retval;
812 }
813
814 struct TotalMsgCount
815 {
816         guint new_msgs;
817         guint unread_msgs;
818         guint unreadmarked_msgs;
819         guint total_msgs;
820 };
821
822 struct FuncToAllFoldersData
823 {
824         FolderItemFunc  function;
825         gpointer        data;
826 };
827
828 static gboolean folder_func_to_all_folders_func(GNode *node, gpointer data)
829 {
830         FolderItem *item;
831         struct FuncToAllFoldersData *function_data = (struct FuncToAllFoldersData *) data;
832
833         g_return_val_if_fail(node->data != NULL, FALSE);
834
835         item = FOLDER_ITEM(node->data);
836         g_return_val_if_fail(item != NULL, FALSE);
837
838         function_data->function(item, function_data->data);
839
840         return FALSE;
841 }
842
843 void folder_func_to_all_folders(FolderItemFunc function, gpointer data)
844 {
845         GList *list;
846         Folder *folder;
847         struct FuncToAllFoldersData function_data;
848         
849         function_data.function = function;
850         function_data.data = data;
851
852         for (list = folder_list; list != NULL; list = list->next) {
853                 folder = FOLDER(list->data);
854                 if (folder->node)
855                         g_node_traverse(folder->node, G_PRE_ORDER,
856                                         G_TRAVERSE_ALL, -1,
857                                         folder_func_to_all_folders_func,
858                                         &function_data);
859         }
860 }
861
862 static void folder_count_total_msgs_func(FolderItem *item, gpointer data)
863 {
864         struct TotalMsgCount *count = (struct TotalMsgCount *)data;
865
866         count->new_msgs += item->new_msgs;
867         count->unread_msgs += item->unread_msgs;
868         count->unreadmarked_msgs += item->unreadmarked_msgs;
869         count->total_msgs += item->total_msgs;
870 }
871
872 struct TotalMsgStatus
873 {
874         guint new;
875         guint unread;
876         guint total;
877         GString *str;
878 };
879
880 static gboolean folder_get_status_full_all_func(GNode *node, gpointer data)
881 {
882         FolderItem *item;
883         struct TotalMsgStatus *status = (struct TotalMsgStatus *)data;
884         gchar *id;
885  
886         g_return_val_if_fail(node->data != NULL, FALSE);
887  
888         item = FOLDER_ITEM(node->data);
889
890         if (!item->path) return FALSE;
891
892         status->new += item->new_msgs;
893         status->unread += item->unread_msgs;
894         status->total += item->total_msgs;
895
896         if (status->str) {
897                 id = folder_item_get_identifier(item);
898                 g_string_sprintfa(status->str, "%5d %5d %5d %s\n",
899                                   item->new_msgs, item->unread_msgs,
900                                   item->total_msgs, id);
901                 g_free(id);
902         }
903  
904         return FALSE;
905  }
906  
907 static void folder_get_status_full_all(GString *str, guint *new, guint *unread,
908                                        guint *total)
909 {
910         GList *list;
911         Folder *folder;
912         struct TotalMsgStatus status;
913  
914         status.new = status.unread = status.total = 0;
915         status.str = str;
916  
917         debug_print("Counting total number of messages...\n");
918  
919         for (list = folder_list; list != NULL; list = list->next) {
920                 folder = FOLDER(list->data);
921                 if (folder->node)
922                         g_node_traverse(folder->node, G_PRE_ORDER,
923                                         G_TRAVERSE_ALL, -1,
924                                         folder_get_status_full_all_func,
925                                         &status);
926         }
927  
928         *new = status.new;
929         *unread = status.unread;
930         *total = status.total;
931 }
932
933 gchar *folder_get_status(GPtrArray *folders, gboolean full)
934 {
935         guint new, unread, total;
936         GString *str;
937         gint i;
938         gchar *ret;
939
940         new = unread = total = 0;
941
942         str = g_string_new(NULL);
943
944         if (folders) {
945                 for (i = 0; i < folders->len; i++) {
946                         FolderItem *item;
947
948                         item = g_ptr_array_index(folders, i);
949                         new += item->new_msgs;
950                         unread += item->unread_msgs;
951                         total += item->total_msgs;
952
953                         if (full) {
954                                 gchar *id;
955
956                                 id = folder_item_get_identifier(item);
957                                 g_string_sprintfa(str, "%5d %5d %5d %s\n",
958                                                   item->new_msgs, item->unread_msgs,
959                                                   item->total_msgs, id);
960                                 g_free(id);
961                         }
962                 }
963         } else {
964                 folder_get_status_full_all(full ? str : NULL,
965                                            &new, &unread, &total);
966         }
967
968         if (full)
969                 g_string_sprintfa(str, "%5d %5d %5d\n", new, unread, total);
970         else
971                 g_string_sprintfa(str, "%d %d %d\n", new, unread, total);
972
973         ret = str->str;
974         g_string_free(str, FALSE);
975  
976         return ret;
977 }
978
979 void folder_count_total_msgs(guint *new_msgs, guint *unread_msgs, guint *unreadmarked_msgs, guint *total_msgs)
980 {
981         struct TotalMsgCount count;
982
983         count.new_msgs = count.unread_msgs = count.unreadmarked_msgs = count.total_msgs = 0;
984
985         debug_print("Counting total number of messages...\n");
986
987         folder_func_to_all_folders(folder_count_total_msgs_func, &count);
988
989         *new_msgs = count.new_msgs;
990         *unread_msgs = count.unread_msgs;
991         *unreadmarked_msgs = count.unreadmarked_msgs;
992         *total_msgs = count.total_msgs;
993 }
994
995 Folder *folder_find_from_path(const gchar *path)
996 {
997         GList *list;
998         Folder *folder;
999
1000         for (list = folder_list; list != NULL; list = list->next) {
1001                 folder = list->data;
1002                 if ((FOLDER_TYPE(folder) == F_MH || 
1003                      FOLDER_TYPE(folder) == F_MBOX) &&
1004                     !path_cmp(LOCAL_FOLDER(folder)->rootpath, path))
1005                         return folder;
1006         }
1007
1008         return NULL;
1009 }
1010
1011 Folder *folder_find_from_name(const gchar *name, FolderClass *klass)
1012 {
1013         GList *list;
1014         Folder *folder;
1015
1016         for (list = folder_list; list != NULL; list = list->next) {
1017                 folder = list->data;
1018                 if (folder->klass == klass && 
1019                     strcmp2(name, folder->name) == 0)
1020                         return folder;
1021         }
1022
1023         return NULL;
1024 }
1025
1026 static gboolean folder_item_find_func(GNode *node, gpointer data)
1027 {
1028         FolderItem *item = node->data;
1029         gpointer *d = data;
1030         const gchar *path = d[0];
1031
1032         if (path_cmp(path, item->path) != 0)
1033                 return FALSE;
1034
1035         d[1] = item;
1036
1037         return TRUE;
1038 }
1039
1040 FolderItem *folder_find_item_from_path(const gchar *path)
1041 {
1042         Folder *folder;
1043         gpointer d[2];
1044
1045         folder = folder_get_default_folder();
1046         g_return_val_if_fail(folder != NULL, NULL);
1047
1048         d[0] = (gpointer)path;
1049         d[1] = NULL;
1050         g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1051                         folder_item_find_func, d);
1052         return d[1];
1053 }
1054
1055 FolderItem *folder_find_child_item_by_name(FolderItem *item, const gchar *name)
1056 {
1057         GNode *node;
1058         FolderItem *child;
1059
1060         for (node = item->node->children; node != NULL; node = node->next) {
1061                 child = FOLDER_ITEM(node->data);
1062                 if (strcmp2(g_basename(child->path), name) == 0)
1063                         return child;
1064         }
1065
1066         return NULL;
1067 }
1068
1069 FolderClass *folder_get_class_from_string(const gchar *str)
1070 {
1071         GSList *classlist;
1072
1073         classlist = folder_get_class_list();
1074         for (; classlist != NULL; classlist = g_slist_next(classlist)) {
1075                 FolderClass *class = (FolderClass *) classlist->data;
1076                 if (g_strcasecmp(class->idstr, str) == 0)
1077                         return class;
1078         }
1079
1080         return NULL;
1081 }
1082
1083 gchar *folder_get_identifier(Folder *folder)
1084 {
1085         gchar *type_str;
1086
1087         g_return_val_if_fail(folder != NULL, NULL);
1088
1089         type_str = folder->klass->idstr;
1090         return g_strconcat("#", type_str, "/", folder->name, NULL);
1091 }
1092
1093 gchar *folder_item_get_identifier(FolderItem *item)
1094 {
1095         gchar *id;
1096         gchar *folder_id;
1097
1098         g_return_val_if_fail(item != NULL, NULL);
1099         g_return_val_if_fail(item->path != NULL, NULL);
1100
1101         folder_id = folder_get_identifier(item->folder);
1102         id = g_strconcat(folder_id, "/", item->path, NULL);
1103         g_free(folder_id);
1104
1105         return id;
1106 }
1107
1108 FolderItem *folder_find_item_from_identifier(const gchar *identifier)
1109 {
1110         Folder *folder;
1111         gpointer d[2];
1112         gchar *str;
1113         gchar *p;
1114         gchar *name;
1115         gchar *path;
1116         FolderClass *class;
1117
1118         g_return_val_if_fail(identifier != NULL, NULL);
1119
1120         if (*identifier != '#')
1121                 return folder_find_item_from_path(identifier);
1122
1123         Xstrdup_a(str, identifier, return NULL);
1124
1125         p = strchr(str, '/');
1126         if (!p)
1127                 return folder_find_item_from_path(identifier);
1128         *p = '\0';
1129         p++;
1130         class = folder_get_class_from_string(&str[1]);
1131         if (class == NULL)
1132                 return folder_find_item_from_path(identifier);
1133
1134         name = p;
1135         p = strchr(p, '/');
1136         if (!p)
1137                 return folder_find_item_from_path(identifier);
1138         *p = '\0';
1139         p++;
1140
1141         folder = folder_find_from_name(name, class);
1142         if (!folder)
1143                 return folder_find_item_from_path(identifier);
1144
1145         path = p;
1146
1147         d[0] = (gpointer)path;
1148         d[1] = NULL;
1149         g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1150                         folder_item_find_func, d);
1151         return d[1];
1152 }
1153
1154 /**
1155  * Get a displayable name for a FolderItem
1156  *
1157  * \param item FolderItem for that a name should be created
1158  * \return Displayable name for item, returned string has to
1159  *         be freed
1160  */
1161 gchar *folder_item_get_name(FolderItem *item)
1162 {
1163         gchar *name = NULL;
1164
1165         g_return_val_if_fail(item != NULL, g_strdup(""));
1166
1167         switch (item->stype) {
1168         case F_INBOX:
1169                 name = g_strdup(!g_strcasecmp(item->name, INBOX_DIR) ? _("Inbox") :
1170                                 item->name);
1171                 break;
1172         case F_OUTBOX:
1173                 name = g_strdup(!g_strcasecmp(item->name, OUTBOX_DIR) ? _("Sent") :
1174                                 item->name);
1175                 break;
1176         case F_QUEUE:
1177                 name = g_strdup(!g_strcasecmp(item->name, QUEUE_DIR) ? _("Queue") :
1178                                 item->name);
1179                 break;
1180         case F_TRASH:
1181                 name = g_strdup(!g_strcasecmp(item->name, TRASH_DIR) ? _("Trash") :
1182                                 item->name);
1183                 break;
1184         case F_DRAFT:
1185                 name = g_strdup(!g_strcasecmp(item->name, DRAFT_DIR) ? _("Drafts") :
1186                                 item->name);
1187                 break;
1188         default:
1189                 break;
1190         }
1191
1192         if (name == NULL) {
1193                 /*
1194                  * should probably be done by a virtual function,
1195                  * the folder knows the ui string and how to abbrev
1196                 */
1197                 if (folder_item_parent(item) == NULL) {
1198                         name = g_strconcat(item->name, " (", item->folder->klass->uistr, ")", NULL);
1199                 } else {
1200                         if (FOLDER_CLASS(item->folder) == news_get_class() &&
1201                             item->path && !strcmp2(item->name, item->path))
1202                                 name = get_abbrev_newsgroup_name
1203                                         (item->path,
1204                                          prefs_common.ng_abbrev_len);
1205                         else
1206                                 name = g_strdup(item->name);
1207                 }
1208         }
1209
1210         if (name == NULL)
1211                 name = g_strdup("");
1212
1213         return name;
1214 }
1215
1216 Folder *folder_get_default_folder(void)
1217 {
1218         return folder_list ? FOLDER(folder_list->data) : NULL;
1219 }
1220
1221 FolderItem *folder_get_default_inbox(void)
1222 {
1223         Folder *folder;
1224
1225         if (!folder_list) return NULL;
1226         folder = FOLDER(folder_list->data);
1227         g_return_val_if_fail(folder != NULL, NULL);
1228         return folder->inbox;
1229 }
1230
1231 FolderItem *folder_get_default_outbox(void)
1232 {
1233         Folder *folder;
1234
1235         if (!folder_list) return NULL;
1236         folder = FOLDER(folder_list->data);
1237         g_return_val_if_fail(folder != NULL, NULL);
1238         return folder->outbox;
1239 }
1240
1241 FolderItem *folder_get_default_draft(void)
1242 {
1243         Folder *folder;
1244
1245         if (!folder_list) return NULL;
1246         folder = FOLDER(folder_list->data);
1247         g_return_val_if_fail(folder != NULL, NULL);
1248         return folder->draft;
1249 }
1250
1251 FolderItem *folder_get_default_queue(void)
1252 {
1253         Folder *folder;
1254
1255         if (!folder_list) return NULL;
1256         folder = FOLDER(folder_list->data);
1257         g_return_val_if_fail(folder != NULL, NULL);
1258         return folder->queue;
1259 }
1260
1261 FolderItem *folder_get_default_trash(void)
1262 {
1263         Folder *folder;
1264
1265         if (!folder_list) return NULL;
1266         folder = FOLDER(folder_list->data);
1267         g_return_val_if_fail(folder != NULL, NULL);
1268         return folder->trash;
1269 }
1270
1271 #define CREATE_FOLDER_IF_NOT_EXIST(member, dir, type)           \
1272 {                                                               \
1273         if (!folder->member) {                                  \
1274                 item = folder_item_new(folder, dir, dir);       \
1275                 item->stype = type;                             \
1276                 folder_item_append(rootitem, item);             \
1277                 folder->member = item;                          \
1278         }                                                       \
1279 }
1280
1281 void folder_set_missing_folders(void)
1282 {
1283         Folder *folder;
1284         FolderItem *rootitem;
1285         FolderItem *item;
1286         GList *list;
1287
1288         for (list = folder_list; list != NULL; list = list->next) {
1289                 folder = list->data;
1290                 if (FOLDER_TYPE(folder) != F_MH) continue;
1291                 rootitem = FOLDER_ITEM(folder->node->data);
1292                 g_return_if_fail(rootitem != NULL);
1293
1294                 if (folder->inbox && folder->outbox && folder->draft &&
1295                     folder->queue && folder->trash)
1296                         continue;
1297
1298                 if (folder->klass->create_tree(folder) < 0) {
1299                         g_warning("%s: can't create the folder tree.\n",
1300                                   LOCAL_FOLDER(folder)->rootpath);
1301                         continue;
1302                 }
1303
1304                 CREATE_FOLDER_IF_NOT_EXIST(inbox,  INBOX_DIR,  F_INBOX);
1305                 CREATE_FOLDER_IF_NOT_EXIST(outbox, OUTBOX_DIR, F_OUTBOX);
1306                 CREATE_FOLDER_IF_NOT_EXIST(draft,  DRAFT_DIR,  F_DRAFT);
1307                 CREATE_FOLDER_IF_NOT_EXIST(queue,  QUEUE_DIR,  F_QUEUE);
1308                 CREATE_FOLDER_IF_NOT_EXIST(trash,  TRASH_DIR,  F_TRASH);
1309         }
1310 }
1311
1312 static gboolean folder_unref_account_func(GNode *node, gpointer data)
1313 {
1314         FolderItem *item = node->data;
1315         PrefsAccount *account = data;
1316
1317         if (item->account == account)
1318                 item->account = NULL;
1319
1320         return FALSE;
1321 }
1322
1323 void folder_unref_account_all(PrefsAccount *account)
1324 {
1325         Folder *folder;
1326         GList *list;
1327
1328         if (!account) return;
1329
1330         for (list = folder_list; list != NULL; list = list->next) {
1331                 folder = list->data;
1332                 if (folder->account == account)
1333                         folder->account = NULL;
1334                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1335                                 folder_unref_account_func, account);
1336         }
1337 }
1338
1339 #undef CREATE_FOLDER_IF_NOT_EXIST
1340
1341 gchar *folder_item_get_path(FolderItem *item)
1342 {
1343         Folder *folder;
1344
1345         g_return_val_if_fail(item != NULL, NULL);
1346         folder = item->folder;
1347         g_return_val_if_fail(folder != NULL, NULL);
1348
1349         return folder->klass->item_get_path(folder, item);
1350 }
1351
1352 void folder_item_set_default_flags(FolderItem *dest, MsgFlags *flags)
1353 {
1354         if (!(dest->stype == F_OUTBOX ||
1355               dest->stype == F_QUEUE  ||
1356               dest->stype == F_DRAFT  ||
1357               dest->stype == F_TRASH)) {
1358                 flags->perm_flags = MSG_NEW|MSG_UNREAD;
1359         } else {
1360                 flags->perm_flags = 0;
1361         }
1362         flags->tmp_flags = MSG_CACHED;
1363         if (FOLDER_TYPE(dest->folder) == F_MH) {
1364                 if (dest->stype == F_QUEUE) {
1365                         MSG_SET_TMP_FLAGS(*flags, MSG_QUEUED);
1366                 } else if (dest->stype == F_DRAFT) {
1367                         MSG_SET_TMP_FLAGS(*flags, MSG_DRAFT);
1368                 }
1369         }
1370 }
1371
1372 static gint folder_sort_cache_list_by_msgnum(gconstpointer a, gconstpointer b)
1373 {
1374         MsgInfo *msginfo_a = (MsgInfo *) a;
1375         MsgInfo *msginfo_b = (MsgInfo *) b;
1376
1377         return (msginfo_a->msgnum - msginfo_b->msgnum);
1378 }
1379
1380 static gint folder_sort_folder_list(gconstpointer a, gconstpointer b)
1381 {
1382         guint gint_a = GPOINTER_TO_INT(a);
1383         guint gint_b = GPOINTER_TO_INT(b);
1384         
1385         return (gint_a - gint_b);
1386 }
1387
1388 gint folder_item_open(FolderItem *item)
1389 {
1390         gchar *buf;
1391         if((item->folder->klass->scan_required != NULL) && (item->folder->klass->scan_required(item->folder, item))) {
1392                 folder_item_scan_full(item, TRUE);
1393         }
1394         folder_item_syncronize_flags(item);
1395         
1396         /* Processing */
1397         buf = g_strdup_printf(_("Processing (%s)...\n"), 
1398                               item->path ? item->path : item->name);
1399         debug_print("%s\n", buf);
1400         g_free(buf);
1401         
1402         folder_item_apply_processing(item);
1403
1404         debug_print("done.\n");
1405
1406         return 0;
1407 }
1408
1409 gint folder_item_close(FolderItem *item)
1410 {
1411         GSList *mlist, *cur;
1412         Folder *folder;
1413         
1414         g_return_val_if_fail(item != NULL, -1);
1415
1416         if (item->new_msgs) {
1417                 folder_item_update_freeze();
1418                 mlist = folder_item_get_msg_list(item);
1419                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
1420                         MsgInfo * msginfo;
1421
1422                         msginfo = (MsgInfo *) cur->data;
1423                         if (MSG_IS_NEW(msginfo->flags))
1424                                 procmsg_msginfo_unset_flags(msginfo, MSG_NEW, 0);
1425                         procmsg_msginfo_free(msginfo);
1426                 }
1427                 g_slist_free(mlist);
1428                 folder_item_update_thaw();
1429         }               
1430
1431         folder_item_write_cache(item);
1432         
1433         folder_item_update(item, F_ITEM_UPDATE_MSGCNT);
1434
1435         item->opened = FALSE;
1436         folder = item->folder;
1437
1438         if (folder->klass->close == NULL)
1439                 return 0;
1440
1441         return folder->klass->close(folder, item);
1442 }
1443
1444 gint folder_item_scan_full(FolderItem *item, gboolean filtering)
1445 {
1446         Folder *folder;
1447         GSList *folder_list = NULL, *cache_list = NULL;
1448         GSList *folder_list_cur, *cache_list_cur, *new_list = NULL;
1449         GSList *exists_list = NULL, *elem;
1450         GSList *newmsg_list = NULL;
1451         guint newcnt = 0, unreadcnt = 0, totalcnt = 0, unreadmarkedcnt = 0;
1452         guint cache_max_num, folder_max_num, cache_cur_num, folder_cur_num;
1453         gboolean update_flags = 0, old_uids_valid = FALSE;
1454     
1455         g_return_val_if_fail(item != NULL, -1);
1456         if (item->path == NULL) return -1;
1457
1458         folder = item->folder;
1459
1460         g_return_val_if_fail(folder != NULL, -1);
1461         g_return_val_if_fail(folder->klass->get_num_list != NULL, -1);
1462
1463         debug_print("Scanning folder %s for cache changes.\n", item->path);
1464
1465         /* Get list of messages for folder and cache */
1466         if (folder->klass->get_num_list(item->folder, item, &folder_list, &old_uids_valid) < 0) {
1467                 debug_print("Error fetching list of message numbers\n");
1468                 return(-1);
1469         }
1470
1471         if (old_uids_valid) {
1472                 if (!item->cache)
1473                         folder_item_read_cache(item);
1474                 cache_list = msgcache_get_msg_list(item->cache);
1475         } else {
1476                 if (item->cache)
1477                         msgcache_destroy(item->cache);
1478                 item->cache = msgcache_new();
1479                 cache_list = NULL;
1480         }
1481
1482         /* Sort both lists */
1483         cache_list = g_slist_sort(cache_list, folder_sort_cache_list_by_msgnum);
1484         folder_list = g_slist_sort(folder_list, folder_sort_folder_list);
1485
1486         cache_list_cur = cache_list;
1487         folder_list_cur = folder_list;
1488
1489         if (cache_list_cur != NULL) {
1490                 GSList *cache_list_last;
1491         
1492                 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
1493                 cache_list_last = g_slist_last(cache_list);
1494                 cache_max_num = ((MsgInfo *)cache_list_last->data)->msgnum;
1495         } else {
1496                 cache_cur_num = G_MAXINT;
1497                 cache_max_num = 0;
1498         }
1499
1500         if (folder_list_cur != NULL) {
1501                 GSList *folder_list_last;
1502         
1503                 folder_cur_num = GPOINTER_TO_INT(folder_list_cur->data);
1504                 folder_list_last = g_slist_last(folder_list);
1505                 folder_max_num = GPOINTER_TO_INT(folder_list_last->data);
1506         } else {
1507                 folder_cur_num = G_MAXINT;
1508                 folder_max_num = 0;
1509         }
1510
1511         while ((cache_cur_num != G_MAXINT) || (folder_cur_num != G_MAXINT)) {
1512                 /*
1513                  *  Message only exists in the folder
1514                  *  Remember message for fetching
1515                  */
1516                 if (folder_cur_num < cache_cur_num) {
1517                         gboolean add = FALSE;
1518
1519                         switch(FOLDER_TYPE(folder)) {
1520                                 case F_NEWS:
1521                                         if (folder_cur_num < cache_max_num)
1522                                                 break;
1523                                         
1524                                         if (folder->account->max_articles == 0) {
1525                                                 add = TRUE;
1526                                         }
1527
1528                                         if (folder_max_num <= folder->account->max_articles) {
1529                                                 add = TRUE;
1530                                         } else if (folder_cur_num > (folder_max_num - folder->account->max_articles)) {
1531                                                 add = TRUE;
1532                                         }
1533                                         break;
1534                                 default:
1535                                         add = TRUE;
1536                                         break;
1537                         }
1538                         
1539                         if (add) {
1540                                 new_list = g_slist_prepend(new_list, GINT_TO_POINTER(folder_cur_num));
1541                                 debug_print("Remembered message %d for fetching\n", folder_cur_num);
1542                         }
1543
1544                         /* Move to next folder number */
1545                         folder_list_cur = folder_list_cur->next;
1546
1547                         if (folder_list_cur != NULL)
1548                                 folder_cur_num = GPOINTER_TO_INT(folder_list_cur->data);
1549                         else
1550                                 folder_cur_num = G_MAXINT;
1551
1552                         continue;
1553                 }
1554
1555                 /*
1556                  *  Message only exists in the cache
1557                  *  Remove the message from the cache
1558                  */
1559                 if (cache_cur_num < folder_cur_num) {
1560                         msgcache_remove_msg(item->cache, cache_cur_num);
1561                         debug_print("Removed message %d from cache.\n", cache_cur_num);
1562
1563                         /* Move to next cache number */
1564                         cache_list_cur = cache_list_cur->next;
1565
1566                         if (cache_list_cur != NULL)
1567                                 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
1568                         else
1569                                 cache_cur_num = G_MAXINT;
1570
1571                         update_flags |= F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT;
1572
1573                         continue;
1574                 }
1575
1576                 /*
1577                  *  Message number exists in folder and cache!
1578                  *  Check if the message has been modified
1579                  */
1580                 if (cache_cur_num == folder_cur_num) {
1581                         MsgInfo *msginfo;
1582
1583                         msginfo = msgcache_get_msg(item->cache, folder_cur_num);
1584                         if (folder->klass->is_msg_changed && folder->klass->is_msg_changed(folder, item, msginfo)) {
1585                                 msgcache_remove_msg(item->cache, msginfo->msgnum);
1586                                 new_list = g_slist_prepend(new_list, GINT_TO_POINTER(msginfo->msgnum));
1587                                 procmsg_msginfo_free(msginfo);
1588
1589                                 debug_print("Remembering message %d to update...\n", folder_cur_num);
1590                         } else
1591                                 exists_list = g_slist_prepend(exists_list, msginfo);
1592
1593                         /* Move to next folder and cache number */
1594                         cache_list_cur = cache_list_cur->next;
1595                         folder_list_cur = folder_list_cur->next;
1596
1597                         if (cache_list_cur != NULL)
1598                                 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
1599                         else
1600                                 cache_cur_num = G_MAXINT;
1601
1602                         if (folder_list_cur != NULL)
1603                                 folder_cur_num = GPOINTER_TO_INT(folder_list_cur->data);
1604                         else
1605                                 folder_cur_num = G_MAXINT;
1606
1607                         continue;
1608                 }
1609         }
1610         
1611         for(cache_list_cur = cache_list; cache_list_cur != NULL; cache_list_cur = g_slist_next(cache_list_cur))
1612                 procmsg_msginfo_free((MsgInfo *) cache_list_cur->data);
1613
1614         g_slist_free(cache_list);
1615         g_slist_free(folder_list);
1616
1617         if (new_list != NULL) {
1618                 if (folder->klass->get_msginfos) {
1619                         newmsg_list = folder->klass->get_msginfos(folder, item, new_list);
1620                 } else if (folder->klass->get_msginfo) {
1621                         GSList *elem;
1622         
1623                         for (elem = new_list; elem != NULL; elem = g_slist_next(elem)) {
1624                                 MsgInfo *msginfo;
1625                                 guint num;
1626
1627                                 num = GPOINTER_TO_INT(elem->data);
1628                                 msginfo = folder->klass->get_msginfo(folder, item, num);
1629                                 if (msginfo != NULL) {
1630                                         newmsg_list = g_slist_prepend(newmsg_list, msginfo);
1631                                         debug_print("Added newly found message %d to cache.\n", num);
1632                                 }
1633                         }
1634                 }
1635                 g_slist_free(new_list);
1636         }
1637
1638         folder_item_update_freeze();
1639         if (newmsg_list != NULL) {
1640                 GSList *elem;
1641
1642                 for (elem = newmsg_list; elem != NULL; elem = g_slist_next(elem)) {
1643                         MsgInfo *msginfo = (MsgInfo *) elem->data;
1644
1645                         msgcache_add_msg(item->cache, msginfo);
1646                         if ((filtering == TRUE) &&
1647                             (item->stype == F_INBOX) &&
1648                             (item->folder->account != NULL) && 
1649                             (item->folder->account->filter_on_recv) &&
1650                             procmsg_msginfo_filter(msginfo))
1651                                 procmsg_msginfo_free(msginfo);
1652                         else
1653                                 exists_list = g_slist_prepend(exists_list, msginfo);
1654                 }
1655                 g_slist_free(newmsg_list);
1656
1657                 update_flags |= F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT;
1658         }
1659
1660         for (elem = exists_list; elem != NULL; elem = g_slist_next(elem)) {
1661                 MsgInfo *msginfo;
1662
1663                 msginfo = elem->data;
1664                 if (MSG_IS_IGNORE_THREAD(msginfo->flags) && (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
1665                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
1666                 if (!MSG_IS_IGNORE_THREAD(msginfo->flags) && procmsg_msg_has_flagged_parent(msginfo, MSG_IGNORE_THREAD)) {
1667                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
1668                         procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
1669                 }
1670                 if ((item->stype == F_OUTBOX ||
1671                      item->stype == F_QUEUE  ||
1672                      item->stype == F_DRAFT  ||
1673                      item->stype == F_TRASH) &&
1674                     (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
1675                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
1676                 if (MSG_IS_NEW(msginfo->flags))
1677                         newcnt++;
1678                 if (MSG_IS_UNREAD(msginfo->flags))
1679                         unreadcnt++;
1680                 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
1681                         unreadmarkedcnt++;
1682                 totalcnt++;
1683
1684                 procmsg_msginfo_free(msginfo);
1685         }
1686         g_slist_free(exists_list);
1687
1688         item->new_msgs = newcnt;
1689         item->unread_msgs = unreadcnt;
1690         item->total_msgs = totalcnt;
1691         item->unreadmarked_msgs = unreadmarkedcnt;
1692
1693         update_flags |= F_ITEM_UPDATE_MSGCNT;
1694
1695         folder_item_update(item, update_flags);
1696         folder_item_update_thaw();
1697
1698         return 0;
1699 }
1700
1701 gint folder_item_syncronize_flags(FolderItem *item)
1702 {
1703         MsgInfoList *msglist = NULL;
1704         GSList *cur;
1705         GRelation *relation;
1706         gint ret = 0;
1707         
1708         g_return_val_if_fail(item != NULL, -1);
1709         g_return_val_if_fail(item->folder != NULL, -1);
1710         g_return_val_if_fail(item->folder->klass != NULL, -1);
1711         if(item->folder->klass->get_flags == NULL)
1712                 return 0;
1713         
1714         if (item->cache == NULL)
1715                 folder_item_read_cache(item);
1716         
1717         msglist = msgcache_get_msg_list(item->cache);
1718         
1719         relation = g_relation_new(2);
1720         g_relation_index(relation, 0, g_direct_hash, g_direct_equal);
1721         if ((ret = item->folder->klass->get_flags(
1722             item->folder, item, msglist, relation)) == 0) {
1723                 GTuples *tuples;
1724                 MsgInfo *msginfo;
1725                 MsgPermFlags permflags;
1726                 gboolean skip;
1727
1728                 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1729                         msginfo = (MsgInfo *) cur->data;
1730                 
1731                         tuples = g_relation_select(relation, msginfo, 0);
1732                         skip = tuples->len < 1;
1733                         if (!skip)
1734                                 permflags = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1735                         g_tuples_destroy(tuples);
1736                         if (skip)
1737                                 continue;
1738                         
1739                         if (msginfo->flags.perm_flags != permflags) {
1740                                 procmsg_msginfo_set_flags(msginfo,
1741                                         permflags & ~msginfo->flags.perm_flags, 0);
1742                                 procmsg_msginfo_unset_flags(msginfo,
1743                                         ~permflags & msginfo->flags.perm_flags, 0);
1744                         }
1745                 }
1746         }
1747         g_relation_destroy(relation);
1748         
1749         for (cur = msglist; cur != NULL; cur = g_slist_next(cur))
1750                 procmsg_msginfo_free((MsgInfo *) cur->data);
1751         
1752         return ret;
1753 }
1754
1755 gint folder_item_scan(FolderItem *item)
1756 {
1757         return folder_item_scan_full(item, TRUE);
1758 }
1759
1760 static gboolean folder_scan_all_items_func(GNode *node, gpointer data)
1761 {
1762         FolderItem *item = node->data;
1763
1764         folder_item_scan(item);
1765
1766         return FALSE;
1767 }
1768
1769 void folder_scan_all_items(Folder * folder)
1770 {
1771         g_node_traverse(folder->node, G_PRE_ORDER,
1772                         G_TRAVERSE_ALL, -1, folder_scan_all_items_func, NULL);
1773 }
1774
1775 static void folder_item_scan_foreach_func(gpointer key, gpointer val,
1776                                           gpointer data)
1777 {
1778         folder_item_scan(FOLDER_ITEM(key));
1779 }
1780
1781 void folder_item_scan_foreach(GHashTable *table)
1782 {
1783         g_hash_table_foreach(table, folder_item_scan_foreach_func, NULL);
1784 }
1785
1786 void folder_count_total_cache_memusage(FolderItem *item, gpointer data)
1787 {
1788         gint *memusage = (gint *)data;
1789
1790         if (item->cache == NULL)
1791                 return;
1792         
1793         *memusage += msgcache_get_memory_usage(item->cache);
1794 }
1795
1796 gint folder_cache_time_compare_func(gconstpointer a, gconstpointer b)
1797 {
1798         FolderItem *fa = (FolderItem *)a;
1799         FolderItem *fb = (FolderItem *)b;
1800         
1801         return (gint) (msgcache_get_last_access_time(fa->cache) - msgcache_get_last_access_time(fb->cache));
1802 }
1803
1804 void folder_find_expired_caches(FolderItem *item, gpointer data)
1805 {
1806         GSList **folder_item_list = (GSList **)data;
1807         gint difftime, expiretime;
1808         
1809         if (item->cache == NULL)
1810                 return;
1811
1812         if (item->opened > 0)
1813                 return;
1814
1815         difftime = (gint) (time(NULL) - msgcache_get_last_access_time(item->cache));
1816         expiretime = prefs_common.cache_min_keep_time * 60;
1817         debug_print("Cache unused time: %d (Expire time: %d)\n", difftime, expiretime);
1818         if (difftime > expiretime) {
1819                 *folder_item_list = g_slist_insert_sorted(*folder_item_list, item, folder_cache_time_compare_func);
1820         }
1821 }
1822
1823 void folder_item_free_cache(FolderItem *item)
1824 {
1825         g_return_if_fail(item != NULL);
1826         
1827         if (item->cache == NULL)
1828                 return;
1829         
1830         if (item->opened > 0)
1831                 return;
1832
1833         folder_item_write_cache(item);
1834         msgcache_destroy(item->cache);
1835         item->cache = NULL;
1836 }
1837
1838 void folder_clean_cache_memory(void)
1839 {
1840         gint memusage = 0;
1841
1842         folder_func_to_all_folders(folder_count_total_cache_memusage, &memusage);       
1843         debug_print("Total cache memory usage: %d\n", memusage);
1844         
1845         if (memusage > (prefs_common.cache_max_mem_usage * 1024)) {
1846                 GSList *folder_item_list = NULL, *listitem;
1847                 
1848                 debug_print("Trying to free cache memory\n");
1849
1850                 folder_func_to_all_folders(folder_find_expired_caches, &folder_item_list);      
1851                 listitem = folder_item_list;
1852                 while((listitem != NULL) && (memusage > (prefs_common.cache_max_mem_usage * 1024))) {
1853                         FolderItem *item = (FolderItem *)(listitem->data);
1854
1855                         debug_print("Freeing cache memory for %s\n", item->path);
1856                         memusage -= msgcache_get_memory_usage(item->cache);
1857                         folder_item_free_cache(item);
1858                         listitem = listitem->next;
1859                 }
1860                 g_slist_free(folder_item_list);
1861         }
1862 }
1863
1864 void folder_item_read_cache(FolderItem *item)
1865 {
1866         gchar *cache_file, *mark_file;
1867         
1868         g_return_if_fail(item != NULL);
1869
1870         if (item->path != NULL) {
1871                 cache_file = folder_item_get_cache_file(item);
1872                 mark_file = folder_item_get_mark_file(item);
1873                 item->cache = msgcache_read_cache(item, cache_file);
1874                 if (!item->cache) {
1875                         item->cache = msgcache_new();
1876                         folder_item_scan_full(item, TRUE);
1877                 }
1878                 msgcache_read_mark(item->cache, mark_file);
1879                 g_free(cache_file);
1880                 g_free(mark_file);
1881         } else {
1882                 item->cache = msgcache_new();
1883         }
1884
1885         folder_clean_cache_memory();
1886 }
1887
1888 void folder_item_write_cache(FolderItem *item)
1889 {
1890         gchar *cache_file, *mark_file;
1891         FolderItemPrefs *prefs;
1892         gint filemode = 0;
1893         gchar *id;
1894         
1895         if (!item || !item->path || !item->cache)
1896                 return;
1897
1898         id = folder_item_get_identifier(item);
1899         debug_print("Save cache for folder %s\n", id);
1900         g_free(id);
1901
1902         cache_file = folder_item_get_cache_file(item);
1903         mark_file = folder_item_get_mark_file(item);
1904         if (msgcache_write(cache_file, mark_file, item->cache) < 0) {
1905                 prefs = item->prefs;
1906                 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
1907                         /* for cache file */
1908                         filemode = prefs->folder_chmod;
1909                         if (filemode & S_IRGRP) filemode |= S_IWGRP;
1910                         if (filemode & S_IROTH) filemode |= S_IWOTH;
1911                         chmod(cache_file, filemode);
1912                 }
1913         }
1914
1915         g_free(cache_file);
1916         g_free(mark_file);
1917 }
1918
1919 MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num)
1920 {
1921         Folder *folder;
1922         MsgInfo *msginfo;
1923         
1924         g_return_val_if_fail(item != NULL, NULL);
1925         
1926         folder = item->folder;
1927         if (!item->cache)
1928                 folder_item_read_cache(item);
1929         
1930         if ((msginfo = msgcache_get_msg(item->cache, num)) != NULL)
1931                 return msginfo;
1932         
1933         g_return_val_if_fail(folder->klass->get_msginfo, NULL);
1934         if ((msginfo = folder->klass->get_msginfo(folder, item, num)) != NULL) {
1935                 msgcache_add_msg(item->cache, msginfo);
1936                 return msginfo;
1937         }
1938         
1939         return NULL;
1940 }
1941
1942 MsgInfo *folder_item_get_msginfo_by_msgid(FolderItem *item, const gchar *msgid)
1943 {
1944         Folder *folder;
1945         MsgInfo *msginfo;
1946         
1947         g_return_val_if_fail(item != NULL, NULL);
1948         g_return_val_if_fail(msgid != NULL, NULL);
1949         
1950         folder = item->folder;
1951         if (!item->cache)
1952                 folder_item_read_cache(item);
1953         
1954         if ((msginfo = msgcache_get_msg_by_id(item->cache, msgid)) != NULL)
1955                 return msginfo;
1956
1957         return NULL;
1958 }
1959
1960 GSList *folder_item_get_msg_list(FolderItem *item)
1961 {
1962         g_return_val_if_fail(item != NULL, NULL);
1963         
1964         if (item->cache == 0)
1965                 folder_item_read_cache(item);
1966
1967         g_return_val_if_fail(item->cache != NULL, NULL);
1968         
1969         return msgcache_get_msg_list(item->cache);
1970 }
1971
1972 gchar *folder_item_fetch_msg(FolderItem *item, gint num)
1973 {
1974         Folder *folder;
1975
1976         g_return_val_if_fail(item != NULL, NULL);
1977
1978         folder = item->folder;
1979
1980         g_return_val_if_fail(folder->klass->fetch_msg != NULL, NULL);
1981
1982         return folder->klass->fetch_msg(folder, item, num);
1983 }
1984
1985 gint folder_item_fetch_all_msg(FolderItem *item)
1986 {
1987         Folder *folder;
1988         GSList *mlist;
1989         GSList *cur;
1990         gint num = 0;
1991         gint ret = 0;
1992
1993         g_return_val_if_fail(item != NULL, -1);
1994
1995         debug_print("fetching all messages in %s ...\n", item->path);
1996
1997         folder = item->folder;
1998
1999         if (folder->ui_func)
2000                 folder->ui_func(folder, item, folder->ui_func_data ?
2001                                 folder->ui_func_data : GINT_TO_POINTER(num));
2002
2003         mlist = folder_item_get_msg_list(item);
2004
2005         for (cur = mlist; cur != NULL; cur = cur->next) {
2006                 MsgInfo *msginfo = (MsgInfo *)cur->data;
2007                 gchar *msg;
2008
2009                 num++;
2010                 if (folder->ui_func)
2011                         folder->ui_func(folder, item,
2012                                         folder->ui_func_data ?
2013                                         folder->ui_func_data :
2014                                         GINT_TO_POINTER(num));
2015
2016                 msg = folder_item_fetch_msg(item, msginfo->msgnum);
2017                 if (!msg) {
2018                         g_warning("Can't fetch message %d. Aborting.\n",
2019                                   msginfo->msgnum);
2020                         ret = -1;
2021                         break;
2022                 }
2023                 g_free(msg);
2024         }
2025
2026         procmsg_msg_list_free(mlist);
2027
2028         return ret;
2029 }
2030
2031 static gint folder_item_get_msg_num_by_file(FolderItem *dest, const gchar *file)
2032 {
2033         static HeaderEntry hentry[] = {{"Message-ID:",  NULL, TRUE},
2034                                        {NULL,           NULL, FALSE}};
2035         FILE *fp;
2036         MsgInfo *msginfo;
2037         gint msgnum = 0;
2038         gchar buf[BUFFSIZE];
2039
2040         if ((fp = fopen(file, "rb")) == NULL)
2041                 return 0;
2042
2043         if ((dest->stype == F_QUEUE) || (dest->stype == F_DRAFT))
2044                 while (fgets(buf, sizeof(buf), fp) != NULL)
2045                         if (buf[0] == '\r' || buf[0] == '\n') break;
2046
2047         procheader_get_header_fields(fp, hentry);
2048         if (hentry[0].body) {
2049                 extract_parenthesis(hentry[0].body, '<', '>');
2050                 remove_space(hentry[0].body);
2051                 if ((msginfo = msgcache_get_msg_by_id(dest->cache, hentry[0].body)) != NULL) {
2052                         msgnum = msginfo->msgnum;
2053                         procmsg_msginfo_free(msginfo);
2054
2055                         debug_print("found message as uid %d\n", msgnum);
2056                 }
2057         }
2058         
2059         g_free(hentry[0].body);
2060         hentry[0].body = NULL;
2061         fclose(fp);
2062
2063         return msgnum;
2064 }
2065
2066 static void copy_msginfo_flags(MsgInfo *source, MsgInfo *dest)
2067 {
2068         MsgPermFlags perm_flags = 0;
2069         MsgTmpFlags tmp_flags = 0;
2070
2071         /* create new flags */
2072         if (source != NULL) {
2073                 /* copy original flags */
2074                 perm_flags = source->flags.perm_flags;
2075                 tmp_flags = source->flags.tmp_flags;
2076         } else {
2077                 perm_flags = dest->flags.perm_flags;
2078                 tmp_flags = dest->flags.tmp_flags;
2079         }
2080
2081         /* remove new, unread and deleted in special folders */
2082         if (dest->folder->stype == F_OUTBOX ||
2083             dest->folder->stype == F_QUEUE  ||
2084             dest->folder->stype == F_DRAFT  ||
2085             dest->folder->stype == F_TRASH)
2086                 perm_flags &= ~(MSG_NEW | MSG_UNREAD | MSG_DELETED);
2087
2088         /* set ignore flag of ignored parent exists */
2089         if (procmsg_msg_has_flagged_parent(dest, MSG_IGNORE_THREAD))
2090                 perm_flags |= MSG_IGNORE_THREAD;
2091
2092         /* Unset tmp flags that should not be copied */
2093         tmp_flags &= ~(MSG_MOVE | MSG_COPY);
2094
2095         /* unset flags that are set but should not */
2096         procmsg_msginfo_unset_flags(dest,
2097                                     dest->flags.perm_flags & ~perm_flags,
2098                                     dest->flags.tmp_flags  & ~tmp_flags);
2099         /* set new flags */
2100         procmsg_msginfo_set_flags(dest,
2101                                   ~dest->flags.perm_flags & perm_flags,
2102                                   ~dest->flags.tmp_flags  & tmp_flags);
2103 }
2104
2105 static void add_msginfo_to_cache(FolderItem *item, MsgInfo *newmsginfo, MsgInfo *flagsource)
2106 {
2107         /* update folder stats */
2108         if (MSG_IS_NEW(newmsginfo->flags))
2109                 item->new_msgs++;
2110         if (MSG_IS_UNREAD(newmsginfo->flags))
2111                 item->unread_msgs++;
2112         if (MSG_IS_UNREAD(newmsginfo->flags) && procmsg_msg_has_marked_parent(newmsginfo))
2113                 item->unreadmarked_msgs++;
2114         item->total_msgs++;
2115
2116         folder_item_update_freeze();
2117         msgcache_add_msg(item->cache, newmsginfo);
2118         copy_msginfo_flags(flagsource, newmsginfo);
2119         folder_item_update(item,  F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT);
2120         folder_item_update_thaw();
2121 }
2122
2123 static void remove_msginfo_from_cache(FolderItem *item, MsgInfo *msginfo)
2124 {
2125         MsgInfoUpdate msginfo_update;
2126
2127         if (!item->cache)
2128                 folder_item_read_cache(item);
2129
2130         if (MSG_IS_NEW(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
2131                 msginfo->folder->new_msgs--;
2132         if (MSG_IS_UNREAD(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
2133                 msginfo->folder->unread_msgs--;
2134         if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
2135                 msginfo->folder->unreadmarked_msgs--;
2136         msginfo->folder->total_msgs--;
2137
2138         msginfo_update.msginfo = msginfo;
2139         msginfo_update.flags = MSGINFO_UPDATE_DELETED;
2140         hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
2141
2142         msgcache_remove_msg(item->cache, msginfo->msgnum);
2143         folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT);
2144 }
2145
2146 gint folder_item_add_msg(FolderItem *dest, const gchar *file,
2147                          MsgFlags *flags, gboolean remove_source)
2148 {
2149         GSList file_list;
2150         MsgFileInfo fileinfo;
2151
2152         g_return_val_if_fail(dest != NULL, -1);
2153         g_return_val_if_fail(file != NULL, -1);
2154  
2155         fileinfo.msginfo = NULL;
2156         fileinfo.file = (gchar *)file;
2157         fileinfo.flags = flags;
2158         file_list.data = &fileinfo;
2159         file_list.next = NULL;
2160
2161         return folder_item_add_msgs(dest, &file_list, remove_source);
2162 }
2163
2164 gint folder_item_add_msgs(FolderItem *dest, GSList *file_list,
2165                           gboolean remove_source)
2166 {
2167         Folder *folder;
2168         gint ret, num, lastnum = -1;
2169         GSList *file_cur;
2170         GRelation *relation;
2171         MsgFileInfo *fileinfo = NULL;
2172         gboolean folderscan = FALSE;
2173
2174         g_return_val_if_fail(dest != NULL, -1);
2175         g_return_val_if_fail(file_list != NULL, -1);
2176         g_return_val_if_fail(dest->folder != NULL, -1);
2177
2178         folder = dest->folder;
2179
2180         relation = g_relation_new(2);
2181         g_relation_index(relation, 0, g_direct_hash, g_direct_equal);
2182
2183         if (folder->klass->add_msgs != NULL) {
2184                 ret = folder->klass->add_msgs(folder, dest, file_list, relation);
2185                 if (ret < 0) {
2186                         g_relation_destroy(relation);
2187                         return ret;
2188                 }
2189         } else {
2190                 for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
2191                         fileinfo = (MsgFileInfo *) file_cur->data;
2192
2193                         ret = folder->klass->add_msg(folder, dest, fileinfo->file, fileinfo->flags);
2194                         if (ret < 0) {
2195                                 g_relation_destroy(relation);
2196                                 return ret;
2197                         }
2198                         g_relation_insert(relation, fileinfo, GINT_TO_POINTER(ret));
2199                 }
2200         }
2201
2202         for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
2203                 GTuples *tuples;
2204
2205                 fileinfo = (MsgFileInfo *) file_cur->data;
2206                 tuples = g_relation_select(relation, fileinfo, 0);
2207                 num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
2208                 g_tuples_destroy(tuples);
2209
2210                 if (num >= 0) {
2211                         MsgInfo *newmsginfo;
2212
2213                         if (num == 0) {
2214                                 if (!folderscan) {
2215                                         folder_item_scan_full(dest, FALSE);
2216                                         folderscan = TRUE;
2217                                 }
2218                                 num = folder_item_get_msg_num_by_file(dest, fileinfo->file);
2219                         }
2220
2221                         if (num > lastnum)
2222                                 lastnum = num;
2223
2224                         if (num >= 0 && remove_source) {
2225                                 if (unlink(fileinfo->file) < 0)
2226                                         FILE_OP_ERROR(fileinfo->file, "unlink");
2227                         }
2228
2229                         if (num == 0)
2230                                 continue;
2231
2232                         if (!folderscan && 
2233                             ((newmsginfo = folder->klass->get_msginfo(folder, dest, num)) != NULL)) {
2234                                 add_msginfo_to_cache(dest, newmsginfo, NULL);
2235                                 procmsg_msginfo_free(newmsginfo);
2236                         } else if ((newmsginfo = msgcache_get_msg(dest->cache, num)) != NULL) {
2237                                 /* TODO: set default flags */
2238                                 procmsg_msginfo_free(newmsginfo);
2239                         }
2240                 }
2241         }
2242
2243         g_relation_destroy(relation);
2244
2245         return lastnum;
2246 }
2247
2248 /*
2249 gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
2250 {
2251         Folder *folder;
2252         gint num;
2253
2254         g_return_val_if_fail(dest != NULL, -1);
2255         g_return_val_if_fail(msginfo != NULL, -1);
2256
2257         folder = dest->folder;
2258         if (dest->last_num < 0) folder->scan(folder, dest);
2259
2260         num = folder->move_msg(folder, dest, msginfo);
2261         if (num > 0) dest->last_num = num;
2262
2263         return num;
2264 }
2265 */
2266                 
2267 FolderItem *folder_item_move_recursive(FolderItem *src, FolderItem *dest) 
2268 {
2269         GSList *mlist;
2270         FolderItem *new_item;
2271         FolderItem *next_item;
2272         GNode *srcnode;
2273         gchar *old_id, *new_id;
2274
2275         mlist = folder_item_get_msg_list(src);
2276
2277         /* move messages */
2278         debug_print("Moving %s to %s\n", src->path, dest->path);
2279         new_item = folder_create_folder(dest, g_basename(src->path));
2280         if (new_item == NULL) {
2281                 printf("Can't create folder\n");
2282                 return NULL;
2283         }
2284         
2285         if (new_item->folder == NULL)
2286                 new_item->folder = dest->folder;
2287
2288         /* move messages */
2289         log_message(_("Moving %s to %s...\n"), 
2290                         src->name, new_item->path);
2291         folder_item_move_msgs(new_item, mlist);
2292         
2293         /*copy prefs*/
2294         folder_item_prefs_copy_prefs(src, new_item);
2295         new_item->collapsed = src->collapsed;
2296         new_item->thread_collapsed = src->thread_collapsed;
2297         new_item->threaded  = src->threaded;
2298         new_item->ret_rcpt  = src->ret_rcpt;
2299         new_item->hide_read_msgs = src->hide_read_msgs;
2300         new_item->sort_key  = src->sort_key;
2301         new_item->sort_type = src->sort_type;
2302
2303         prefs_matcher_write_config();
2304         
2305         /* recurse */
2306         srcnode = src->folder->node;    
2307         srcnode = g_node_find(srcnode, G_PRE_ORDER, G_TRAVERSE_ALL, src);
2308         srcnode = srcnode->children;
2309         while (srcnode != NULL) {
2310                 if (srcnode && srcnode->data) {
2311                         next_item = (FolderItem*) srcnode->data;
2312                         srcnode = srcnode->next;
2313                         if (folder_item_move_recursive(next_item, new_item) == NULL)
2314                                 return NULL;
2315                 }
2316         }
2317         old_id = folder_item_get_identifier(src);
2318         new_id = folder_item_get_identifier(new_item);
2319         debug_print("updating rules : %s => %s\n", old_id, new_id);
2320         
2321         src->folder->klass->remove_folder(src->folder, src);
2322         folder_write_list();
2323
2324         if (old_id != NULL && new_id != NULL)
2325                 prefs_filtering_rename_path(old_id, new_id);
2326         g_free(old_id);
2327         g_free(new_id);
2328
2329         return new_item;
2330 }
2331
2332 gint folder_item_move_to(FolderItem *src, FolderItem *dest, FolderItem **new_item)
2333 {
2334         FolderItem *tmp = folder_item_parent(dest);
2335         gchar * src_identifier, * dst_identifier;
2336         gchar * phys_srcpath, * phys_dstpath;
2337         
2338         while (tmp) {
2339                 if (tmp == src) {
2340                         return F_MOVE_FAILED_DEST_IS_CHILD;
2341                 }
2342                 tmp = folder_item_parent(tmp);;
2343         }
2344         
2345         tmp = folder_item_parent(src);
2346         
2347         src_identifier = folder_item_get_identifier(src);
2348         dst_identifier = folder_item_get_identifier(dest);
2349         
2350         if(dst_identifier == NULL && dest->folder && folder_item_parent(dest) == NULL) {
2351                 /* dest can be a root folder */
2352                 dst_identifier = folder_get_identifier(dest->folder);
2353         }
2354         if (src_identifier == NULL || dst_identifier == NULL) {
2355                 debug_print("Can't get identifiers\n");
2356                 return F_MOVE_FAILED;
2357         }
2358
2359         if (src->folder != dest->folder) {
2360                 return F_MOVE_FAILED_DEST_OUTSIDE_MAILBOX;
2361         }
2362
2363         phys_srcpath = folder_item_get_path(src);
2364         phys_dstpath = g_strconcat(folder_item_get_path(dest),G_DIR_SEPARATOR_S,g_basename(phys_srcpath),NULL);
2365
2366         if (folder_item_parent(src) == dest || src == dest) {
2367                 g_free(src_identifier);
2368                 g_free(dst_identifier);
2369                 g_free(phys_srcpath);
2370                 g_free(phys_dstpath);
2371                 return F_MOVE_FAILED_DEST_IS_PARENT;
2372         }
2373         debug_print("moving \"%s\" to \"%s\"\n", phys_srcpath, phys_dstpath);
2374         if ((tmp = folder_item_move_recursive(src, dest)) == NULL) {
2375                 return F_MOVE_FAILED;
2376         }
2377         
2378         g_free(src_identifier);
2379         g_free(dst_identifier);
2380         g_free(phys_srcpath);
2381         g_free(phys_dstpath);
2382
2383         *new_item = tmp;
2384
2385         return F_MOVE_OK;
2386 }
2387
2388 /**
2389  * Copy a list of message to a new folder and remove
2390  * source messages if wanted
2391  */
2392 static gint do_copy_msgs(FolderItem *dest, GSList *msglist, gboolean remove_source)
2393 {
2394         Folder *folder;
2395         GSList *l;
2396         gint num, lastnum = -1;
2397         gboolean folderscan = FALSE;
2398         GRelation *relation;
2399
2400         g_return_val_if_fail(dest != NULL, -1);
2401         g_return_val_if_fail(msglist != NULL, -1);
2402
2403         folder = dest->folder;
2404
2405         g_return_val_if_fail(folder->klass->copy_msg != NULL, -1);
2406
2407         relation = g_relation_new(2);
2408         g_relation_index(relation, 0, g_direct_hash, g_direct_equal);
2409         g_relation_index(relation, 1, g_direct_hash, g_direct_equal);
2410
2411         /* 
2412          * Copy messages to destination folder and 
2413          * store new message numbers in newmsgnums
2414          */
2415         if (folder->klass->copy_msgs != NULL) {
2416                 if (folder->klass->copy_msgs(folder, dest, msglist, relation) < 0) {
2417                         g_relation_destroy(relation);
2418                         return -1;
2419                 }
2420         } else {
2421                 for (l = msglist ; l != NULL ; l = g_slist_next(l)) {
2422                         MsgInfo * msginfo = (MsgInfo *) l->data;
2423
2424                         num = folder->klass->copy_msg(folder, dest, msginfo);
2425                         g_relation_insert(relation, msginfo, GINT_TO_POINTER(num));
2426                 }
2427         }
2428
2429         /* Read cache for dest folder */
2430         if (!dest->cache) folder_item_read_cache(dest);
2431
2432         /* 
2433          * Fetch new MsgInfos for new messages in dest folder,
2434          * add them to the msgcache and update folder message counts
2435          */
2436         if (g_relation_count(relation, GINT_TO_POINTER(0), 1) > 0) {
2437                 folder_item_scan_full(dest, FALSE);
2438                 folderscan = TRUE;
2439         }
2440
2441         for (l = msglist; l != NULL; l = g_slist_next(l)) {
2442                 MsgInfo *msginfo = (MsgInfo *) l->data;
2443                 GTuples *tuples;
2444
2445                 tuples = g_relation_select(relation, msginfo, 0);
2446                 num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
2447                 g_tuples_destroy(tuples);
2448
2449                 if (num >= 0) {
2450                         MsgInfo *newmsginfo;
2451
2452                         if (folderscan) {
2453                                 if (msginfo->msgid != NULL) {
2454                                         newmsginfo = folder_item_get_msginfo_by_msgid(dest, msginfo->msgid);
2455                                         if (newmsginfo != NULL) {
2456                                                 copy_msginfo_flags(msginfo, newmsginfo);
2457                                                 num = newmsginfo->msgnum;
2458                                                 procmsg_msginfo_free(newmsginfo);
2459                                         }
2460                                 }
2461                         } else {
2462                                 newmsginfo = folder->klass->get_msginfo(folder, dest, num);
2463                                 if (newmsginfo != NULL) {
2464                                         add_msginfo_to_cache(dest, newmsginfo, msginfo);
2465                                         procmsg_msginfo_free(newmsginfo);
2466                                 }
2467                         }
2468
2469                         if (num > lastnum)
2470                                 lastnum = num;
2471                 }
2472         }
2473
2474         if (remove_source) {
2475                 /*
2476                  * Remove source messages from their folders if
2477                  * copying was successfull and update folder
2478                  * message counts
2479                  */
2480                 for (l = msglist; l != NULL; l = g_slist_next(l)) {
2481                         MsgInfo *msginfo = (MsgInfo *) l->data;
2482                         FolderItem *item = msginfo->folder;
2483                         GTuples *tuples;
2484
2485                         tuples = g_relation_select(relation, msginfo, 0);
2486                         num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
2487                         g_tuples_destroy(tuples);
2488
2489                         if ((num >= 0) && (item->folder->klass->remove_msg != NULL)) {
2490                                 item->folder->klass->remove_msg(item->folder,
2491                                                                 msginfo->folder,
2492                                                                 msginfo->msgnum);
2493                                 remove_msginfo_from_cache(item, msginfo);
2494                         }
2495                 }
2496         }
2497
2498         g_relation_destroy(relation);
2499         return lastnum;
2500 }
2501
2502 /**
2503  * Move a message to a new folder.
2504  *
2505  * \param dest Destination folder
2506  * \param msginfo The message
2507  */
2508 gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
2509 {
2510         GSList list;
2511
2512         g_return_val_if_fail(dest != NULL, -1);
2513         g_return_val_if_fail(msginfo != NULL, -1);
2514
2515         list.data = msginfo;
2516         list.next = NULL;
2517
2518         return do_copy_msgs(dest, &list, TRUE);
2519 }
2520
2521 /**
2522  * Move a list of messages to a new folder.
2523  *
2524  * \param dest Destination folder
2525  * \param msglist List of messages
2526  */
2527 gint folder_item_move_msgs(FolderItem *dest, GSList *msglist)
2528 {
2529         g_return_val_if_fail(dest != NULL, -1);
2530         g_return_val_if_fail(msglist != NULL, -1);
2531
2532         return do_copy_msgs(dest, msglist, TRUE);
2533 }
2534
2535 /**
2536  * Copy a message to a new folder.
2537  *
2538  * \param dest Destination folder
2539  * \param msginfo The message
2540  */
2541 gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo)
2542 {
2543         GSList list;
2544
2545         g_return_val_if_fail(dest != NULL, -1);
2546         g_return_val_if_fail(msginfo != NULL, -1);
2547     
2548         list.data = msginfo;
2549         list.next = NULL;
2550         
2551         return do_copy_msgs(dest, &list, FALSE);
2552 }
2553
2554 /**
2555  * Copy a list of messages to a new folder.
2556  *
2557  * \param dest Destination folder
2558  * \param msglist List of messages
2559  */
2560 gint folder_item_copy_msgs(FolderItem *dest, GSList *msglist)
2561 {
2562         g_return_val_if_fail(dest != NULL, -1);
2563         g_return_val_if_fail(msglist != NULL, -1);
2564
2565         return do_copy_msgs(dest, msglist, FALSE);
2566 }
2567
2568 gint folder_item_remove_msg(FolderItem *item, gint num)
2569 {
2570         Folder *folder;
2571         gint ret;
2572         MsgInfo *msginfo;
2573
2574         g_return_val_if_fail(item != NULL, -1);
2575         folder = item->folder;
2576         g_return_val_if_fail(folder->klass->remove_msg != NULL, -1);
2577
2578         if (!item->cache) folder_item_read_cache(item);
2579
2580         ret = folder->klass->remove_msg(folder, item, num);
2581
2582         msginfo = msgcache_get_msg(item->cache, num);
2583         if (msginfo != NULL) {
2584                 remove_msginfo_from_cache(item, msginfo);
2585                 procmsg_msginfo_free(msginfo);
2586         }
2587         folder_item_update(item, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT);
2588
2589         return ret;
2590 }
2591
2592 gint folder_item_remove_msgs(FolderItem *item, GSList *msglist)
2593 {
2594         Folder *folder;
2595         gint ret = 0;
2596
2597         g_return_val_if_fail(item != NULL, -1);
2598         folder = item->folder;
2599         g_return_val_if_fail(folder != NULL, -1);
2600
2601         if (!item->cache) folder_item_read_cache(item);
2602
2603         folder_item_update_freeze();
2604         while (msglist != NULL) {
2605                 MsgInfo *msginfo = (MsgInfo *)msglist->data;
2606
2607                 ret = folder_item_remove_msg(item, msginfo->msgnum);
2608                 if (ret != 0) break;
2609                 msgcache_remove_msg(item->cache, msginfo->msgnum);
2610                 msglist = msglist->next;
2611         }
2612         folder_item_update_thaw();
2613
2614         return ret;
2615 }
2616
2617 gint folder_item_remove_all_msg(FolderItem *item)
2618 {
2619         Folder *folder;
2620         gint result;
2621
2622         g_return_val_if_fail(item != NULL, -1);
2623
2624         folder = item->folder;
2625
2626         g_return_val_if_fail(folder->klass->remove_all_msg != NULL, -1);
2627
2628         result = folder->klass->remove_all_msg(folder, item);
2629
2630         if (result == 0) {
2631                 folder_item_free_cache(item);
2632                 item->cache = msgcache_new();
2633
2634                 item->new_msgs = 0;
2635                 item->unread_msgs = 0;
2636                 item->unreadmarked_msgs = 0;
2637                 item->total_msgs = 0;
2638                 folder_item_update(item, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT);
2639         }
2640
2641         return result;
2642 }
2643
2644 void folder_item_change_msg_flags(FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
2645 {
2646         g_return_if_fail(item != NULL);
2647         g_return_if_fail(msginfo != NULL);
2648         
2649         if (item->folder->klass->change_flags != NULL) {
2650                 item->folder->klass->change_flags(item->folder, item, msginfo, newflags);
2651         } else {
2652                 msginfo->flags.perm_flags = newflags;
2653         }
2654 }
2655
2656 gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo)
2657 {
2658         Folder *folder;
2659
2660         g_return_val_if_fail(item != NULL, FALSE);
2661
2662         folder = item->folder;
2663
2664         g_return_val_if_fail(folder->klass->is_msg_changed != NULL, -1);
2665
2666         return folder->klass->is_msg_changed(folder, item, msginfo);
2667 }
2668
2669 gchar *folder_item_get_cache_file(FolderItem *item)
2670 {
2671         gchar *path;
2672         gchar *file;
2673
2674         g_return_val_if_fail(item != NULL, NULL);
2675         g_return_val_if_fail(item->path != NULL, NULL);
2676
2677         path = folder_item_get_path(item);
2678         g_return_val_if_fail(path != NULL, NULL);
2679         if (!is_dir_exist(path))
2680                 make_dir_hier(path);
2681         file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL);
2682         g_free(path);
2683
2684         return file;
2685 }
2686
2687 gchar *folder_item_get_mark_file(FolderItem *item)
2688 {
2689         gchar *path;
2690         gchar *file;
2691
2692         g_return_val_if_fail(item != NULL, NULL);
2693         g_return_val_if_fail(item->path != NULL, NULL);
2694
2695         path = folder_item_get_path(item);
2696         g_return_val_if_fail(path != NULL, NULL);
2697         if (!is_dir_exist(path))
2698                 make_dir_hier(path);
2699         file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL);
2700         g_free(path);
2701
2702         return file;
2703 }
2704
2705 static gpointer xml_to_folder_item(gpointer nodedata, gpointer data)
2706 {
2707         XMLNode *xmlnode = (XMLNode *) nodedata;
2708         Folder *folder = (Folder *) data;
2709         FolderItem *item;
2710
2711         g_return_val_if_fail(xmlnode != NULL, NULL);
2712         g_return_val_if_fail(folder != NULL, NULL);
2713
2714         if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
2715                 g_warning("tag name != \"folderitem\"\n");
2716                 return NULL;
2717         }
2718
2719         item = folder_item_new(folder, "", "");
2720         if (folder->klass->item_set_xml != NULL)
2721                 folder->klass->item_set_xml(folder, item, xmlnode->tag);
2722         else
2723                 folder_item_set_xml(folder, item, xmlnode->tag);
2724
2725         item->folder = folder;
2726
2727         switch (item->stype) {
2728         case F_INBOX:  folder->inbox  = item; break;
2729         case F_OUTBOX: folder->outbox = item; break;
2730         case F_DRAFT:  folder->draft  = item; break;
2731         case F_QUEUE:  folder->queue  = item; break;
2732         case F_TRASH:  folder->trash  = item; break;
2733         default:       break;
2734         }
2735         folder_item_prefs_read_config(item);
2736
2737         return item;
2738 }
2739
2740 static gboolean folder_item_set_node(GNode *node, gpointer data)
2741 {
2742         FolderItem *item = (FolderItem *) node->data;
2743         item->node = node;
2744
2745         return FALSE;
2746 }
2747
2748 static Folder *folder_get_from_xml(GNode *node)
2749 {
2750         Folder *folder;
2751         XMLNode *xmlnode;
2752         GList *list;
2753         FolderClass *klass = NULL;
2754         GNode *cur;
2755
2756         g_return_val_if_fail(node->data != NULL, NULL);
2757
2758         xmlnode = node->data;
2759         if (strcmp2(xmlnode->tag->tag, "folder") != 0) {
2760                 g_warning("tag name != \"folder\"\n");
2761                 return NULL;
2762         }
2763         list = xmlnode->tag->attr;
2764         for (; list != NULL; list = list->next) {
2765                 XMLAttr *attr = list->data;
2766
2767                 if (!attr || !attr->name || !attr->value) continue;
2768                 if (!strcmp(attr->name, "type"))
2769                         klass = folder_get_class_from_string(attr->value);
2770         }
2771         if (klass == NULL)
2772                 return NULL;
2773
2774         folder = folder_new(klass, "", "");
2775         g_return_val_if_fail(folder != NULL, NULL);
2776
2777         if (klass->set_xml)
2778                 klass->set_xml(folder, xmlnode->tag);
2779         else
2780                 folder_set_xml(folder, xmlnode->tag);
2781
2782         cur = node->children;
2783         while (cur != NULL) {
2784                 GNode *itemnode;
2785
2786                 itemnode = g_node_map(cur, xml_to_folder_item, (gpointer) folder);
2787                 g_node_append(folder->node, itemnode);
2788                 cur = cur->next;
2789         }
2790         g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, folder_item_set_node, NULL);
2791         
2792         return folder;
2793 }
2794
2795 static gchar *folder_get_list_path(void)
2796 {
2797         static gchar *filename = NULL;
2798
2799         if (!filename)
2800                 filename =  g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2801                                         FOLDER_LIST, NULL);
2802
2803         return filename;
2804 }
2805
2806 #define PUT_ESCAPE_STR(fp, attr, str)                   \
2807 {                                                       \
2808         fputs(" " attr "=\"", fp);                      \
2809         xml_file_put_escape_str(fp, str);               \
2810         fputs("\"", fp);                                \
2811 }
2812
2813 static gpointer folder_item_to_xml(gpointer nodedata, gpointer data)
2814 {
2815         FolderItem *item = (FolderItem *) nodedata;
2816         XMLNode *xmlnode;
2817         XMLTag *tag;
2818
2819         g_return_val_if_fail(item != NULL, NULL);
2820
2821         if (item->folder->klass->item_get_xml != NULL)
2822                 tag = item->folder->klass->item_get_xml(item->folder, item);
2823         else
2824                 tag = folder_item_get_xml(item->folder, item);
2825
2826         xmlnode = g_new0(XMLNode, 1);
2827         xmlnode->tag = tag;
2828         xmlnode->element = NULL;
2829
2830         return xmlnode;
2831 }
2832
2833 static GNode *folder_get_xml_node(Folder *folder)
2834 {
2835         GNode *node;
2836         XMLNode *xmlnode;
2837         XMLTag *tag;
2838
2839         g_return_val_if_fail(folder != NULL, NULL);
2840
2841         if (folder->klass->get_xml != NULL)
2842                 tag = folder->klass->get_xml(folder);
2843         else
2844                 tag = folder_get_xml(folder);
2845
2846         xml_tag_add_attr(tag, "type", g_strdup(folder->klass->idstr));
2847
2848         xmlnode = g_new0(XMLNode, 1);
2849         xmlnode->tag = tag;
2850         xmlnode->element = NULL;
2851
2852         node = g_node_new(xmlnode);
2853         if (folder->node->children) {
2854                 GNode *cur;
2855
2856                 cur = folder->node->children;
2857                 while (cur) {
2858                         GNode *xmlnode;
2859
2860                         xmlnode = g_node_map(cur, folder_item_to_xml, (gpointer) folder);
2861                         g_node_append(node, xmlnode);
2862                         cur = cur->next;
2863                 }
2864         }
2865
2866         return node;
2867 }
2868
2869 static void folder_update_op_count_rec(GNode *node)
2870 {
2871         FolderItem *fitem = FOLDER_ITEM(node->data);
2872
2873         if (g_node_depth(node) > 0) {
2874                 if (fitem->op_count > 0) {
2875                         fitem->op_count = 0;
2876                         folder_item_update(fitem, F_ITEM_UPDATE_MSGCNT);
2877                 }
2878                 if (node->children) {
2879                         GNode *child;
2880
2881                         child = node->children;
2882                         while (child) {
2883                                 GNode *cur;
2884
2885                                 cur = child;
2886                                 child = cur->next;
2887                                 folder_update_op_count_rec(cur);
2888                         }
2889                 }
2890         }
2891 }
2892
2893 void folder_update_op_count(void) 
2894 {
2895         GList *cur;
2896         Folder *folder;
2897
2898         for (cur = folder_list; cur != NULL; cur = cur->next) {
2899                 folder = cur->data;
2900                 folder_update_op_count_rec(folder->node);
2901         }
2902 }
2903
2904 typedef struct _type_str {
2905         gchar * str;
2906         gint type;
2907 } type_str;
2908
2909
2910 /*
2911 static gchar * folder_item_get_tree_identifier(FolderItem * item)
2912 {
2913         if (item->parent != NULL) {
2914                 gchar * path;
2915                 gchar * id;
2916
2917                 path = folder_item_get_tree_identifier(item->parent);
2918                 if (path == NULL)
2919                         return NULL;
2920
2921                 id = g_strconcat(path, "/", item->name, NULL);
2922                 g_free(path);
2923
2924                 return id;
2925         }
2926         else {
2927                 return g_strconcat("/", item->name, NULL);
2928         }
2929 }
2930 */
2931
2932 /* CLAWS: temporary local folder for filtering */
2933 #define TEMP_FOLDER "TEMP_FOLDER"
2934 #define PROCESSING_FOLDER_ITEM "processing"     
2935
2936 static FolderItem *processing_folder_item;
2937
2938 static void folder_create_processing_folder(void)
2939 {
2940         Folder *processing_folder;
2941         gchar      *tmpname;
2942
2943         if ((processing_folder = folder_find_from_name(TEMP_FOLDER, mh_get_class())) == NULL) {
2944                 gchar *tmppath;
2945
2946                 tmppath =
2947                     g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2948                                 "tempfolder", NULL);
2949                 processing_folder =
2950                     folder_new(mh_get_class(), TEMP_FOLDER, tmppath);
2951                 g_free(tmppath);
2952         }
2953         g_assert(processing_folder != NULL);
2954
2955         debug_print("tmpparentroot %s\n", LOCAL_FOLDER(processing_folder)->rootpath);
2956         if (LOCAL_FOLDER(processing_folder)->rootpath[0] == '/')
2957                 tmpname = g_strconcat(LOCAL_FOLDER(processing_folder)->rootpath,
2958                                       G_DIR_SEPARATOR_S, PROCESSING_FOLDER_ITEM,
2959                                       NULL);
2960         else
2961                 tmpname = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
2962                                       LOCAL_FOLDER(processing_folder)->rootpath,
2963                                       G_DIR_SEPARATOR_S, PROCESSING_FOLDER_ITEM,
2964                                       NULL);
2965
2966         if (!is_dir_exist(tmpname)) {
2967                 debug_print("*TMP* creating %s\n", tmpname);
2968                 processing_folder_item = processing_folder->klass->create_folder(processing_folder,
2969                                                                                  processing_folder->node->data,
2970                                                                                  PROCESSING_FOLDER_ITEM);
2971         } else {
2972                 debug_print("*TMP* already created\n");
2973                 processing_folder_item = folder_item_new(processing_folder, PROCESSING_FOLDER_ITEM, PROCESSING_FOLDER_ITEM);
2974                 g_assert(processing_folder_item);
2975                 folder_item_append(processing_folder->node->data, processing_folder_item);
2976         }
2977         g_assert(processing_folder_item != NULL);
2978         g_free(tmpname);
2979 }
2980
2981 FolderItem *folder_get_default_processing(void)
2982 {
2983         if (!processing_folder_item) {
2984                 folder_create_processing_folder();
2985         }
2986         return processing_folder_item;
2987 }
2988
2989 /* folder_persist_prefs_new() - return hash table with persistent
2990  * settings (and folder name as key). 
2991  * (note that in claws other options are in the folder_item_prefs_RC
2992  * file, so those don't need to be included in PersistPref yet) 
2993  */
2994 GHashTable *folder_persist_prefs_new(Folder *folder)
2995 {
2996         GHashTable *pptable;
2997
2998         g_return_val_if_fail(folder, NULL);
2999         pptable = g_hash_table_new(g_str_hash, g_str_equal);
3000         folder_get_persist_prefs_recursive(folder->node, pptable);
3001         return pptable;
3002 }
3003
3004 void folder_persist_prefs_free(GHashTable *pptable)
3005 {
3006         g_return_if_fail(pptable);
3007         g_hash_table_foreach_remove(pptable, persist_prefs_free, NULL);
3008         g_hash_table_destroy(pptable);
3009 }
3010
3011 const PersistPrefs *folder_get_persist_prefs(GHashTable *pptable, const char *name)
3012 {
3013         if (pptable == NULL || name == NULL) return NULL;
3014         return g_hash_table_lookup(pptable, name);
3015 }
3016
3017 void folder_item_restore_persist_prefs(FolderItem *item, GHashTable *pptable)
3018 {
3019         const PersistPrefs *pp;
3020         gchar *id = folder_item_get_identifier(item);
3021
3022         pp = folder_get_persist_prefs(pptable, id); 
3023         g_free(id);
3024
3025         if (!pp) return;
3026
3027         /* CLAWS: since not all folder properties have been migrated to 
3028          * folderlist.xml, we need to call the old stuff first before
3029          * setting things that apply both to Main and Claws. */
3030         folder_item_prefs_read_config(item); 
3031
3032         item->collapsed = pp->collapsed;
3033         item->thread_collapsed = pp->thread_collapsed;
3034         item->threaded  = pp->threaded;
3035         item->ret_rcpt  = pp->ret_rcpt;
3036         item->hide_read_msgs = pp->hide_read_msgs;
3037         item->sort_key  = pp->sort_key;
3038         item->sort_type = pp->sort_type;
3039 }
3040
3041 static void folder_get_persist_prefs_recursive(GNode *node, GHashTable *pptable)
3042 {
3043         FolderItem *item = FOLDER_ITEM(node->data);
3044         PersistPrefs *pp;
3045         GNode *child, *cur;
3046         gchar *id;
3047
3048         g_return_if_fail(node != NULL);
3049         g_return_if_fail(item != NULL);
3050
3051         /* NOTE: item->path == NULL means top level folder; not interesting
3052          * to store preferences of that one.  */
3053         if (item->path) {
3054                 id = folder_item_get_identifier(item);
3055                 pp = g_new0(PersistPrefs, 1);
3056                 g_return_if_fail(pp != NULL);
3057                 pp->collapsed = item->collapsed;
3058                 pp->thread_collapsed = item->thread_collapsed;
3059                 pp->threaded  = item->threaded;
3060                 pp->ret_rcpt  = item->ret_rcpt; 
3061                 pp->hide_read_msgs = item->hide_read_msgs;
3062                 pp->sort_key  = item->sort_key;
3063                 pp->sort_type = item->sort_type;
3064                 g_hash_table_insert(pptable, id, pp);
3065         }
3066
3067         if (node->children) {
3068                 child = node->children;
3069                 while (child) {
3070                         cur = child;
3071                         child = cur->next;
3072                         folder_get_persist_prefs_recursive(cur, pptable);
3073                 }
3074         }       
3075 }
3076
3077 static gboolean persist_prefs_free(gpointer key, gpointer val, gpointer data)
3078 {
3079         if (key) 
3080                 g_free(key);
3081         if (val) 
3082                 g_free(val);
3083         return TRUE;    
3084 }
3085
3086 void folder_item_apply_processing(FolderItem *item)
3087 {
3088         GSList *processing_list;
3089         GSList *mlist, *cur;
3090         
3091         g_return_if_fail(item != NULL);
3092         
3093         processing_list = item->prefs->processing;
3094
3095         folder_item_update_freeze();
3096
3097         mlist = folder_item_get_msg_list(item);
3098         for (cur = mlist ; cur != NULL ; cur = cur->next) {
3099                 MsgInfo * msginfo;
3100
3101                 msginfo = (MsgInfo *) cur->data;
3102                 
3103                 /* reset parameters that can be modified by processing */
3104                 msginfo->hidden = 0;
3105                 msginfo->score = 0;
3106                 
3107                 /* apply pre global rules */
3108                 filter_message_by_msginfo(pre_global_processing, msginfo);
3109                 
3110                 /* apply rules of the folder */
3111                 filter_message_by_msginfo(processing_list, msginfo);
3112
3113                 /* apply post global rules */
3114                 filter_message_by_msginfo(post_global_processing, msginfo);
3115                 
3116                 procmsg_msginfo_free(msginfo);
3117         }
3118         g_slist_free(mlist);
3119
3120         folder_item_update_thaw();
3121 }
3122
3123 /*
3124  *  functions for handling FolderItem content changes
3125  */
3126 static gint folder_item_update_freeze_cnt = 0;
3127
3128 /**
3129  * Notify the folder system about changes to a folder. If the
3130  * update system is not frozen the FOLDER_ITEM_UPDATE_HOOKLIST will
3131  * be invoked, otherwise the changes will be remebered until
3132  * the folder system is thawed.
3133  *
3134  * \param item The FolderItem that was changed
3135  * \param update_flags Type of changed that was made
3136  */
3137 void folder_item_update(FolderItem *item, FolderItemUpdateFlags update_flags)
3138 {
3139         if (folder_item_update_freeze_cnt == 0) {
3140                 FolderItemUpdateData source;
3141         
3142                 source.item = item;
3143                 source.update_flags = update_flags;
3144                 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &source);
3145         } else {
3146                 item->update_flags |= update_flags;
3147         }
3148 }
3149
3150 void folder_item_update_recursive(FolderItem *item, FolderItemUpdateFlags update_flags)
3151 {
3152         GNode *node = item->folder->node;       
3153
3154         node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
3155         node = node->children;
3156
3157         folder_item_update(item, update_flags);
3158         while (node != NULL) {
3159                 if (node && node->data) {
3160                         FolderItem *next_item = (FolderItem*) node->data;
3161
3162                         folder_item_update(next_item, update_flags);
3163                 }
3164                 node = node->next;
3165         }
3166 }
3167
3168 void folder_item_update_freeze(void)
3169 {
3170         folder_item_update_freeze_cnt++;
3171 }
3172
3173 static void folder_item_update_func(FolderItem *item, gpointer data)
3174 {
3175         FolderItemUpdateData source;
3176     
3177         if (item->update_flags) {
3178                 source.item = item;
3179                 source.update_flags = item->update_flags;
3180                 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &source);                             
3181                 item->update_flags = 0;
3182         }
3183 }
3184
3185 void folder_item_update_thaw(void)
3186 {
3187         if (folder_item_update_freeze_cnt > 0)
3188                 folder_item_update_freeze_cnt--;
3189         if (folder_item_update_freeze_cnt == 0) {
3190                 /* Update all folders */
3191                 folder_func_to_all_folders(folder_item_update_func, NULL);
3192         }
3193 }
3194
3195 #undef PUT_ESCAPE_STR