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