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