Replace GtkCMCList in LDIF import dialog with GtkTreeView.
[claws.git] / src / folder.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
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 3 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, see <http://www.gnu.org/licenses/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <stdlib.h>
34 #ifdef WIN32
35 #include <w32lib.h>
36 #endif
37
38 #include "alertpanel.h"
39 #include "folder.h"
40 #include "session.h"
41 #include "inc.h"
42 #include "imap.h"
43 #include "news.h"
44 #include "mh.h"
45 #include "utils.h"
46 #include "xml.h"
47 #include "codeconv.h"
48 #include "prefs_gtk.h"
49 #include "account.h"
50 #include "filtering.h"
51 #include "procheader.h"
52 #include "hooks.h"
53 #include "log.h"
54 #include "folder_item_prefs.h"
55 #include "remotefolder.h"
56 #include "partial_download.h"
57 #include "statusbar.h"
58 #include "gtkutils.h"
59 #include "timing.h"
60 #include "compose.h"
61 #include "main.h"
62 #include "msgcache.h"
63 #include "privacy.h"
64 #include "prefs_common.h"
65 #include "prefs_migration.h"
66
67 /* Dependecies to be removed ?! */
68 #include "prefs_account.h"
69
70 /* Define possible missing constants for Windows. */
71 #ifdef G_OS_WIN32
72 # ifndef S_IRGRP
73 # define S_IRGRP 0
74 # define S_IWGRP 0
75 # endif
76 # ifndef S_IROTH
77 # define S_IROTH 0
78 # define S_IWOTH 0
79 # endif
80 #endif
81
82 static GList *folder_list = NULL;
83 static GSList *class_list = NULL;
84 static GSList *folder_unloaded_list = NULL;
85
86 void folder_init                (Folder         *folder,
87                                  const gchar    *name);
88
89 static gchar *folder_item_get_cache_file        (FolderItem     *item);
90 static gchar *folder_item_get_mark_file (FolderItem     *item);
91 static gchar *folder_item_get_tags_file (FolderItem     *item);
92 static GNode *folder_get_xml_node       (Folder         *folder);
93 static Folder *folder_get_from_xml      (GNode          *node);
94 static void folder_update_op_count_rec  (GNode          *node);
95
96
97 static void folder_get_persist_prefs_recursive
98                                         (GNode *node, GHashTable *pptable);
99 static gboolean persist_prefs_free      (gpointer key, gpointer val, gpointer data);
100 static void folder_item_read_cache              (FolderItem *item);
101 gint folder_item_scan_full              (FolderItem *item, gboolean filtering);
102 static void folder_item_update_with_msg (FolderItem *item, FolderItemUpdateFlags update_flags,
103                                          MsgInfo *msg);
104 static GHashTable *folder_persist_prefs_new     (Folder *folder);
105 static void folder_persist_prefs_free           (GHashTable *pptable);
106 static void folder_item_restore_persist_prefs   (FolderItem *item, GHashTable *pptable);
107
108 void folder_system_init(void)
109 {
110         folder_register_class(mh_get_class());
111         folder_register_class(imap_get_class());
112         folder_register_class(news_get_class());
113 }
114
115 static GSList *folder_get_class_list(void)
116 {
117         return class_list;
118 }
119
120 void folder_register_class(FolderClass *klass)
121 {
122         GSList *xmllist, *cur;
123
124         debug_print("registering folder class %s\n", klass->idstr);
125
126         class_list = g_slist_append(class_list, klass);
127
128         xmllist = g_slist_copy(folder_unloaded_list);
129         for (cur = xmllist; cur != NULL; cur = g_slist_next(cur)) {
130                 GNode *node = (GNode *) cur->data;
131                 XMLNode *xmlnode = (XMLNode *) node->data;
132                 GList *cur = xmlnode->tag->attr;
133
134                 for (; cur != NULL; cur = g_list_next(cur)) {
135                         XMLAttr *attr = (XMLAttr *) cur->data;
136
137                         if (!attr || !attr->name || !attr->value) continue;
138                         if (!strcmp(attr->name, "type") && !strcmp(attr->value, klass->idstr)) {
139                                 Folder *folder;
140
141                                 folder = folder_get_from_xml(node);
142                                 if (folder) {
143                                         folder_add(folder);
144                                         folder_unloaded_list = g_slist_remove(folder_unloaded_list, node);
145                                         xml_free_tree(node);
146                                 }
147                                 cur = NULL;
148                                 continue;
149                         }
150                 }
151         }
152         g_slist_free(xmllist);
153 }
154
155 void folder_unregister_class(FolderClass *klass)
156 {
157         GList *folderlist, *cur;
158
159         debug_print("unregistering folder class %s\n", klass->idstr);
160
161         class_list = g_slist_remove(class_list, klass);
162
163         folderlist = g_list_copy(folder_get_list());
164         for (cur = folderlist; cur != NULL; cur = g_list_next(cur)) {
165                 Folder *folder = (Folder *) cur->data;
166
167                 if (folder->klass == klass) {
168                         GNode *xmlnode = folder_get_xml_node(folder);
169                         folder_unloaded_list = g_slist_append(folder_unloaded_list, xmlnode);
170                         folder_destroy(folder);
171                 }
172         }
173         g_list_free(folderlist);
174
175         if (klass->prefs_pages)
176                 g_slist_free(klass->prefs_pages);
177 }
178
179 Folder *folder_new(FolderClass *klass, const gchar *name, const gchar *path)
180 {
181         Folder *folder = NULL;
182         FolderItem *item;
183
184         cm_return_val_if_fail(klass != NULL, NULL);
185
186         name = name ? name : path;
187         folder = klass->new_folder(name, path);
188
189         /* Create root folder item */
190         item = folder_item_new(folder, name, NULL);
191         if (item == NULL) {
192                 return NULL;
193         }
194         item->folder = folder;
195         folder->node = item->node;
196         folder->data = NULL;
197
198         return folder;
199 }
200
201 void folder_init(Folder *folder, const gchar *name)
202 {
203         cm_return_if_fail(folder != NULL);
204
205         folder_set_name(folder, name);
206
207         /* Init folder data */
208         folder->account = NULL;
209         folder->sort = 0;
210         folder->inbox = NULL;
211         folder->outbox = NULL;
212         folder->draft = NULL;
213         folder->queue = NULL;
214         folder->trash = NULL;
215 }
216
217 static void reset_parent_type(FolderItem *item, gpointer data) {
218         item->parent_stype = -1;
219 }
220
221 void folder_item_change_type(FolderItem *item, SpecialFolderItemType newtype)
222 {
223         Folder *folder = NULL;
224         FolderUpdateData hookdata;
225
226         if (item == NULL)
227                 return;
228
229         folder = item->folder;
230         /* unset previous root of newtype */
231         switch(newtype) {
232         case F_INBOX:
233                 folder_item_change_type(folder->inbox, F_NORMAL);
234                 folder->inbox = item;
235                 break;
236         case F_OUTBOX:
237                 folder_item_change_type(folder->outbox, F_NORMAL);
238                 folder->outbox = item;
239                 break;
240         case F_QUEUE:
241                 folder_item_change_type(folder->queue, F_NORMAL);
242                 folder->queue = item;
243                 break;
244         case F_DRAFT:
245                 folder_item_change_type(folder->draft, F_NORMAL);
246                 folder->draft = item;
247                 break;
248         case F_TRASH:
249                 folder_item_change_type(folder->trash, F_NORMAL);
250                 folder->trash = item;
251                 break;
252         case F_NORMAL:
253         default:
254                 break;
255         }
256         /* set new type for current folder and sons */
257         item->stype = newtype;
258         folder_func_to_all_folders(reset_parent_type, NULL);
259         
260         hookdata.folder = folder;
261         hookdata.update_flags = FOLDER_TREE_CHANGED;
262         hookdata.item = NULL;
263         hookdata.item2 = NULL;
264         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
265 }
266
267 void folder_destroy(Folder *folder)
268 {
269         cm_return_if_fail(folder != NULL);
270         cm_return_if_fail(folder->klass->destroy_folder != NULL);
271
272         folder_remove(folder);
273
274         folder_tree_destroy(folder);
275
276         folder->klass->destroy_folder(folder);
277
278         g_free(folder->name);
279         g_free(folder);
280 }
281
282 void folder_set_xml(Folder *folder, XMLTag *tag)
283 {
284         GList *cur;
285         FolderItem *rootitem = NULL;
286
287         if ((folder->node != NULL) && (folder->node->data != NULL))
288                 rootitem = (FolderItem *) folder->node->data;
289
290         for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
291                 XMLAttr *attr = (XMLAttr *) cur->data;
292
293                 if (!attr || !attr->name || !attr->value) continue;
294                 if (!strcmp(attr->name, "name")) {
295                         g_free(folder->name);
296                         folder->name = g_strdup(attr->value);
297                         if (rootitem != NULL) {
298                                 g_free(rootitem->name);
299                                 rootitem->name = g_strdup(attr->value);
300                         }
301                 } else if (!strcmp(attr->name, "account_id")) {
302                         PrefsAccount *account;
303
304                         account = account_find_from_id(atoi(attr->value));
305                         if (!account)
306                                 g_warning("account_id: %s not found", attr->value);
307                         else {
308                                 folder->account = account;
309                                 account->folder = folder;
310                         }
311                 } else if (!strcmp(attr->name, "collapsed")) {
312                         if (rootitem != NULL)
313                                 rootitem->collapsed = *attr->value == '1' ? TRUE : FALSE;
314                 } else if (!strcmp(attr->name, "sort")) {
315                         folder->sort = atoi(attr->value);
316                 }
317         }
318 }
319
320 XMLTag *folder_get_xml(Folder *folder)
321 {
322         XMLTag *tag;
323
324         tag = xml_tag_new("folder");
325
326         if (folder->name)
327                 xml_tag_add_attr(tag, xml_attr_new("name", folder->name));
328         if (folder->account)
329                 xml_tag_add_attr(tag, xml_attr_new_int("account_id", folder->account->account_id));
330         if (folder->node && folder->node->data) {
331                 FolderItem *rootitem = (FolderItem *) folder->node->data;
332
333                 xml_tag_add_attr(tag, xml_attr_new("collapsed", rootitem->collapsed ? "1" : "0"));
334         }
335         xml_tag_add_attr(tag, xml_attr_new_int("sort", folder->sort));
336
337         return tag;
338 }
339
340 FolderItem *folder_item_new(Folder *folder, const gchar *name, const gchar *path)
341 {
342         FolderItem *item = NULL;
343
344         cm_return_val_if_fail(folder != NULL, NULL);
345
346         if (folder->klass->item_new) {
347                 item = folder->klass->item_new(folder);
348         } else {
349                 item = g_new0(FolderItem, 1);
350         }
351
352         cm_return_val_if_fail(item != NULL, NULL);
353
354         item->stype = F_NORMAL;
355
356         if(!g_utf8_validate(name, -1, NULL)) {
357                 item->name = g_malloc(strlen(name)*2+1);
358                 conv_localetodisp(item->name, strlen(name)*2+1, name);
359         } else {
360                 item->name = g_strdup(name);
361         }
362
363         item->path = g_strdup(path);
364         item->mtime = 0;
365         item->new_msgs = 0;
366         item->unread_msgs = 0;
367         item->unreadmarked_msgs = 0;
368         item->marked_msgs = 0;
369         item->total_msgs = 0;
370         item->replied_msgs = 0;
371         item->forwarded_msgs = 0;
372         item->locked_msgs = 0;
373         item->ignored_msgs = 0;
374         item->watched_msgs = 0;
375         item->order = 0;
376         item->last_num = -1;
377         item->cache = NULL;
378         item->no_sub = FALSE;
379         item->no_select = FALSE;
380         item->collapsed = FALSE;
381         item->thread_collapsed = FALSE;
382         item->threaded  = TRUE;
383         item->ret_rcpt  = FALSE;
384         item->opened    = FALSE;
385         item->node = g_node_new(item);
386         item->folder = NULL;
387         item->account = NULL;
388         item->apply_sub = FALSE;
389         item->mark_queue = NULL;
390         item->data = NULL;
391         item->parent_stype = -1;
392
393         item->sort_key = prefs_common.default_sort_key;
394         item->sort_type = prefs_common.default_sort_type;
395
396         item->prefs = folder_item_prefs_new();
397
398         return item;
399 }
400
401 void folder_item_append(FolderItem *parent, FolderItem *item)
402 {
403         cm_return_if_fail(parent != NULL);
404         cm_return_if_fail(parent->folder != NULL);
405         cm_return_if_fail(parent->node != NULL);
406         cm_return_if_fail(item != NULL);
407
408         item->folder = parent->folder;
409         g_node_append(parent->node, item->node);
410 }
411
412 void folder_item_remove(FolderItem *item)
413 {
414         GNode *node, *start_node;
415         FolderUpdateData hookdata;
416         gchar *tags_file = NULL, *tags_dir = NULL;
417
418         cm_return_if_fail(item != NULL);
419         cm_return_if_fail(item->folder != NULL);
420         cm_return_if_fail(item->folder->node != NULL);
421
422         start_node = item->node;
423         
424         node = item->folder->node;
425         
426         node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
427         node = node->children;
428
429         /* remove my children */
430         while (node != NULL) {
431                 if (node && node->data) {
432                         FolderItem *sub_item = (FolderItem*) node->data;
433                         node = node->next;
434                         folder_item_remove(sub_item);
435                 }
436         }
437
438         /* remove myself */
439         if (item->cache != NULL) {
440                 msgcache_destroy(item->cache);
441                 item->cache = NULL;
442         }
443         tags_file = folder_item_get_tags_file(item);
444         if (tags_file)
445                 claws_unlink(tags_file);
446         tags_dir = g_path_get_dirname(tags_file);
447         if (tags_dir)
448                 rmdir(tags_dir);
449
450         g_free(tags_file);
451         g_free(tags_dir);
452
453         hookdata.folder = item->folder;
454         hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_REMOVE_FOLDERITEM;
455         hookdata.item = item;
456         hookdata.item2 = NULL;
457         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
458
459         node = start_node;
460
461         if (item->folder->node == node)
462                 item->folder->node = NULL;
463
464         folder_item_destroy(item);
465
466         g_node_destroy(node);
467 }
468
469 void folder_item_remove_children(FolderItem *item)
470 {
471         GNode *node, *next;
472
473         cm_return_if_fail(item != NULL);
474         cm_return_if_fail(item->folder != NULL);
475         cm_return_if_fail(item->node != NULL);
476
477         node = item->node->children;
478         while (node != NULL) {
479                 next = node->next;
480                 folder_item_remove(FOLDER_ITEM(node->data));
481                 node = next;
482         }
483 }
484
485 void folder_item_destroy(FolderItem *item)
486 {
487         Folder *folder;
488
489         cm_return_if_fail(item != NULL);
490
491         folder = item->folder;
492         if (folder) {
493                 if (folder->inbox == item)
494                         folder->inbox = NULL;
495                 else if (folder->outbox == item)
496                         folder->outbox = NULL;
497                 else if (folder->draft == item)
498                         folder->draft = NULL;
499                 else if (folder->queue == item)
500                         folder->queue = NULL;
501                 else if (folder->trash == item)
502                         folder->trash = NULL;
503         }
504
505         if (item->cache)
506                 folder_item_free_cache(item, TRUE);
507         if (item->prefs)
508                 folder_item_prefs_free(item->prefs);
509         g_free(item->name);
510         g_free(item->path);
511
512         if (item->folder != NULL) {
513                 if(item->folder->klass->item_destroy) {
514                         item->folder->klass->item_destroy(item->folder, item);
515                 } else {
516                         g_free(item);
517                 }
518         }
519 }
520
521 FolderItem *folder_item_parent(FolderItem *item)
522 {
523         cm_return_val_if_fail(item != NULL, NULL);
524         cm_return_val_if_fail(item->node != NULL, NULL);
525
526         if (item->node->parent == NULL)
527                 return NULL;
528         return (FolderItem *) item->node->parent->data;
529 }
530
531 void folder_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
532 {
533         GList *cur;
534
535         for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
536                 XMLAttr *attr = (XMLAttr *) cur->data;
537
538                 if (!attr || !attr->name || !attr->value) continue;
539                 if (!strcmp(attr->name, "type")) {
540                         if (!g_ascii_strcasecmp(attr->value, "normal"))
541                                 item->stype = F_NORMAL;
542                         else if (!g_ascii_strcasecmp(attr->value, "inbox"))
543                                 item->stype = F_INBOX;
544                         else if (!g_ascii_strcasecmp(attr->value, "outbox"))
545                                 item->stype = F_OUTBOX;
546                         else if (!g_ascii_strcasecmp(attr->value, "draft"))
547                                 item->stype = F_DRAFT;
548                         else if (!g_ascii_strcasecmp(attr->value, "queue"))
549                                 item->stype = F_QUEUE;
550                         else if (!g_ascii_strcasecmp(attr->value, "trash"))
551                                 item->stype = F_TRASH;
552                 } else if (!strcmp(attr->name, "name")) {
553                         g_free(item->name);
554                         item->name = g_strdup(attr->value);
555                 } else if (!strcmp(attr->name, "path")) {
556                         g_free(item->path);
557                         item->path = g_strdup(attr->value);
558                 } else if (!strcmp(attr->name, "mtime"))
559                         item->mtime = strtoul(attr->value, NULL, 10);
560                 else if (!strcmp(attr->name, "new"))
561                         item->new_msgs = atoi(attr->value);
562                 else if (!strcmp(attr->name, "unread"))
563                         item->unread_msgs = atoi(attr->value);
564                 else if (!strcmp(attr->name, "unreadmarked"))
565                         item->unreadmarked_msgs = atoi(attr->value);
566                 else if (!strcmp(attr->name, "marked"))
567                         item->marked_msgs = atoi(attr->value);
568                 else if (!strcmp(attr->name, "replied"))
569                         item->replied_msgs = atoi(attr->value);
570                 else if (!strcmp(attr->name, "forwarded"))
571                         item->forwarded_msgs = atoi(attr->value);
572                 else if (!strcmp(attr->name, "locked"))
573                         item->locked_msgs = atoi(attr->value);
574                 else if (!strcmp(attr->name, "ignored"))
575                         item->ignored_msgs = atoi(attr->value);
576                 else if (!strcmp(attr->name, "watched"))
577                         item->watched_msgs = atoi(attr->value);
578                 else if (!strcmp(attr->name, "order"))
579                         item->order = atoi(attr->value);
580                 else if (!strcmp(attr->name, "total"))
581                         item->total_msgs = atoi(attr->value);
582                 else if (!strcmp(attr->name, "no_sub"))
583                         item->no_sub = *attr->value == '1' ? TRUE : FALSE;
584                 else if (!strcmp(attr->name, "no_select"))
585                         item->no_select = *attr->value == '1' ? TRUE : FALSE;
586                 else if (!strcmp(attr->name, "collapsed"))
587                         item->collapsed = *attr->value == '1' ? TRUE : FALSE;
588                 else if (!strcmp(attr->name, "thread_collapsed"))
589                         item->thread_collapsed =  *attr->value == '1' ? TRUE : FALSE;
590                 else if (!strcmp(attr->name, "threaded"))
591                         item->threaded =  *attr->value == '1' ? TRUE : FALSE;
592                 else if (!strcmp(attr->name, "hidereadmsgs"))
593                         item->hide_read_msgs =  *attr->value == '1' ? TRUE : FALSE;
594                 else if (!strcmp(attr->name, "hidedelmsgs"))
595                         item->hide_del_msgs =  *attr->value == '1' ? TRUE : FALSE;
596                 else if (!strcmp(attr->name, "hidereadthreads"))
597                         item->hide_read_threads =  *attr->value == '1' ? TRUE : FALSE;
598                 else if (!strcmp(attr->name, "reqretrcpt"))
599                         item->ret_rcpt =  *attr->value == '1' ? TRUE : FALSE;
600                 else if (!strcmp(attr->name, "sort_key")) {
601                         if (!strcmp(attr->value, "none"))
602                                 item->sort_key = SORT_BY_NONE;
603                         else if (!strcmp(attr->value, "number"))
604                                 item->sort_key = SORT_BY_NUMBER;
605                         else if (!strcmp(attr->value, "size"))
606                                 item->sort_key = SORT_BY_SIZE;
607                         else if (!strcmp(attr->value, "date"))
608                                 item->sort_key = SORT_BY_DATE;
609                         else if (!strcmp(attr->value, "from"))
610                                 item->sort_key = SORT_BY_FROM;
611                         else if (!strcmp(attr->value, "subject"))
612                                 item->sort_key = SORT_BY_SUBJECT;
613                         else if (!strcmp(attr->value, "score"))
614                                 item->sort_key = SORT_BY_SCORE;
615                         else if (!strcmp(attr->value, "label"))
616                                 item->sort_key = SORT_BY_LABEL;
617                         else if (!strcmp(attr->value, "mark"))
618                                 item->sort_key = SORT_BY_MARK;
619                         else if (!strcmp(attr->value, "unread"))
620                                 item->sort_key = SORT_BY_STATUS;
621                         else if (!strcmp(attr->value, "mime"))
622                                 item->sort_key = SORT_BY_MIME;
623                         else if (!strcmp(attr->value, "to"))
624                                 item->sort_key = SORT_BY_TO;
625                         else if (!strcmp(attr->value, "locked"))
626                                 item->sort_key = SORT_BY_LOCKED;
627                         else if (!strcmp(attr->value, "tags"))
628                                 item->sort_key = SORT_BY_TAGS;
629                         else if (!strcmp(attr->value, "thread_date"))
630                                 item->sort_key = SORT_BY_THREAD_DATE;
631                 } else if (!strcmp(attr->name, "sort_type")) {
632                         if (!strcmp(attr->value, "ascending"))
633                                 item->sort_type = SORT_ASCENDING;
634                         else
635                                 item->sort_type = SORT_DESCENDING;
636                 } else if (!strcmp(attr->name, "account_id")) {
637                         PrefsAccount *account;
638
639                         account = account_find_from_id(atoi(attr->value));
640                         if (!account)
641                                 g_warning("account_id: %s not found", attr->value);
642                         else
643                                 item->account = account;
644                 } else if (!strcmp(attr->name, "apply_sub")) {
645                         item->apply_sub = *attr->value == '1' ? TRUE : FALSE;
646                 } else if (!strcmp(attr->name, "last_seen")) {
647                         if (!claws_crashed())
648                                 item->last_seen = atoi(attr->value);
649                         else
650                                 item->last_seen = 0;
651                 }
652         }
653         /* options without meaning in drafts */
654         if (item->stype == F_DRAFT)
655                 item->hide_read_msgs =
656                         item->hide_del_msgs =
657                                 item->hide_read_threads = FALSE;
658 }
659
660 XMLTag *folder_item_get_xml(Folder *folder, FolderItem *item)
661 {
662         static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox",
663                                                  "draft", "queue", "trash"};
664         static gchar *sort_key_str[] = {"none", "number", "size", "date",
665                                         "from", "subject", "score", "label",
666                                         "mark", "unread", "mime", "to", 
667                                         "locked", "tags", "thread_date" };
668         XMLTag *tag;
669         gchar *value;
670
671         tag = xml_tag_new("folderitem");
672
673         xml_tag_add_attr(tag, xml_attr_new("type", folder_item_stype_str[item->stype]));
674         if (item->name)
675                 xml_tag_add_attr(tag, xml_attr_new("name", item->name));
676         if (item->path)
677                 xml_tag_add_attr(tag, xml_attr_new("path", item->path));
678         if (item->no_sub)
679                 xml_tag_add_attr(tag, xml_attr_new("no_sub", "1"));
680         if (item->no_select)
681                 xml_tag_add_attr(tag, xml_attr_new("no_select", "1"));
682         xml_tag_add_attr(tag, xml_attr_new("collapsed", item->collapsed && item->node->children ? "1" : "0"));
683         xml_tag_add_attr(tag, xml_attr_new("thread_collapsed", item->thread_collapsed ? "1" : "0"));
684         xml_tag_add_attr(tag, xml_attr_new("threaded", item->threaded ? "1" : "0"));
685         xml_tag_add_attr(tag, xml_attr_new("hidereadmsgs", item->hide_read_msgs ? "1" : "0"));
686         xml_tag_add_attr(tag, xml_attr_new("hidedelmsgs", item->hide_del_msgs ? "1" : "0"));
687         xml_tag_add_attr(tag, xml_attr_new("hidereadthreads", item->hide_read_threads ? "1" : "0"));
688         if (item->ret_rcpt)
689                 xml_tag_add_attr(tag, xml_attr_new("reqretrcpt", "1"));
690
691         if (item->sort_key != SORT_BY_NONE) {
692                 xml_tag_add_attr(tag, xml_attr_new("sort_key", sort_key_str[item->sort_key]));
693                 xml_tag_add_attr(tag, xml_attr_new("sort_type", item->sort_type == SORT_ASCENDING ? "ascending" : "descending"));
694         }
695
696         value = g_strdup_printf("%ld", (unsigned long int) item->mtime);
697         xml_tag_add_attr(tag, xml_attr_new("mtime", value));
698         g_free(value);
699         xml_tag_add_attr(tag, xml_attr_new_int("new", item->new_msgs));
700         xml_tag_add_attr(tag, xml_attr_new_int("unread", item->unread_msgs));
701         xml_tag_add_attr(tag, xml_attr_new_int("unreadmarked", item->unreadmarked_msgs));
702         xml_tag_add_attr(tag, xml_attr_new_int("marked", item->marked_msgs));
703         xml_tag_add_attr(tag, xml_attr_new_int("total", item->total_msgs));
704         xml_tag_add_attr(tag, xml_attr_new_int("replied", item->replied_msgs));
705         xml_tag_add_attr(tag, xml_attr_new_int("forwarded", item->forwarded_msgs));
706         xml_tag_add_attr(tag, xml_attr_new_int("locked", item->locked_msgs));
707         xml_tag_add_attr(tag, xml_attr_new_int("ignore", item->ignored_msgs));
708         xml_tag_add_attr(tag, xml_attr_new_int("watched", item->watched_msgs));
709         xml_tag_add_attr(tag, xml_attr_new_int("order", item->order));
710
711         if (item->account)
712                 xml_tag_add_attr(tag, xml_attr_new_int("account_id", item->account->account_id));
713         if (item->apply_sub)
714                 xml_tag_add_attr(tag, xml_attr_new("apply_sub", "1"));
715
716         xml_tag_add_attr(tag, xml_attr_new_int("last_seen", item->last_seen));
717
718         return tag;
719 }
720
721 void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data)
722 {
723         cm_return_if_fail(folder != NULL);
724
725         folder->ui_func = func;
726         folder->ui_func_data = data;
727 }
728
729 void folder_set_name(Folder *folder, const gchar *name)
730 {
731         cm_return_if_fail(folder != NULL);
732
733         g_free(folder->name);
734         folder->name = name ? g_strdup(name) : NULL;
735         if (folder->node && folder->node->data) {
736                 FolderItem *item = (FolderItem *)folder->node->data;
737
738                 g_free(item->name);
739                 item->name = name ? g_strdup(name) : NULL;
740         }
741 }
742
743 void folder_set_sort(Folder *folder, guint sort)
744 {
745         cm_return_if_fail(folder != NULL);
746
747         if (folder->sort != sort) {
748                 folder_remove(folder);
749                 folder->sort = sort;
750                 folder_add(folder);
751         }
752 }
753
754 static gboolean folder_tree_destroy_func(GNode *node, gpointer data) {
755         FolderItem *item = (FolderItem *) node->data;
756
757         folder_item_destroy(item);
758         return FALSE;
759 }
760
761 void folder_tree_destroy(Folder *folder)
762 {
763         GNode *node;
764
765         cm_return_if_fail(folder != NULL);
766
767         node = folder->node;
768         
769         prefs_filtering_clear_folder(folder);
770
771         if (node != NULL) {
772                 g_node_traverse(node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
773                                 folder_tree_destroy_func, NULL);
774                 g_node_destroy(node);
775                 folder->node = NULL;
776         }
777 }
778
779 void folder_add(Folder *folder)
780 {
781         Folder *cur_folder;
782         GList *cur;
783         gint i;
784         FolderUpdateData hookdata;
785
786         cm_return_if_fail(folder != NULL);
787
788         if ((FOLDER_TYPE(folder) == F_IMAP ||
789              FOLDER_TYPE(folder) == F_NEWS) &&
790             folder->account == NULL) {
791                 return;
792         }
793
794         for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) {
795                 cur_folder = FOLDER(cur->data);
796                 if (cur_folder->sort < folder->sort)
797                         break;
798         }
799
800         folder_list = g_list_insert(folder_list, folder, i);
801
802         hookdata.folder = folder;
803         hookdata.update_flags = FOLDER_ADD_FOLDER;
804         hookdata.item = NULL;
805         hookdata.item2 = NULL;
806         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
807 }
808
809 void folder_remove(Folder *folder)
810 {
811         FolderUpdateData hookdata;
812
813         cm_return_if_fail(folder != NULL);
814
815         folder_list = g_list_remove(folder_list, folder);
816
817         hookdata.folder = folder;
818         hookdata.update_flags = FOLDER_REMOVE_FOLDER;
819         hookdata.item = NULL;
820         hookdata.item2 = NULL;
821         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
822 }
823
824 GList *folder_get_list(void)
825 {
826         return folder_list;
827 }
828
829 gint folder_read_list(void)
830 {
831         GNode *node, *cur;
832         XMLNode *xmlnode;
833         gchar *path;
834         GList *list;
835         gint config_version = -1;
836
837         path = folder_get_list_path();
838         if (!is_file_exist(path)) return -1;
839         node = xml_parse_file(path);
840         if (!node) return -1;
841
842         xmlnode = node->data;
843         if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) {
844                 g_warning("wrong folder list");
845                 xml_free_tree(node);
846                 return -1;
847         }
848
849         cur = node->children;
850         while (cur != NULL) {
851                 Folder *folder;
852
853                 folder = folder_get_from_xml(cur);
854                 if (folder != NULL)
855                         folder_add(folder);
856                 else
857                         folder_unloaded_list = g_slist_append(folder_unloaded_list,
858                                 (gpointer) xml_copy_tree(cur));
859                 cur = cur->next;
860         }
861
862         for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
863                 XMLAttr *attr = list->data;
864
865                 if (!attr || !attr->name || !attr->value) continue;
866                 if (!strcmp(attr->name, "config_version")) {
867                         config_version = atoi(attr->value);
868                         debug_print("Found folderlist config_version %d\n", config_version);
869                 }
870         }
871
872         xml_free_tree(node);
873
874         if (prefs_update_config_version_folderlist(config_version) < 0) {
875                 debug_print("Folderlist configuration file version upgrade failed\n");
876                 return -2;
877         }
878
879         if (folder_list || folder_unloaded_list)
880                 return 0;
881         else
882                 return -1;
883 }
884
885 void folder_write_list(void)
886 {
887         GList *list;
888         GSList *slist;
889         Folder *folder;
890         gchar *path;
891         PrefFile *pfile;
892         GNode *rootnode;
893         XMLNode *xmlnode;
894         XMLTag *tag;
895
896         path = folder_get_list_path();
897         if ((pfile = prefs_write_open(path)) == NULL) return;
898
899         if (xml_file_put_xml_decl(pfile->fp) < 0) {
900                 prefs_file_close_revert(pfile);
901                 g_warning("failed to start write folder list.");
902                 return;         
903         }
904         tag = xml_tag_new("folderlist");
905         xml_tag_add_attr(tag, xml_attr_new_int("config_version",
906                                 CLAWS_CONFIG_VERSION));
907
908         xmlnode = xml_node_new(tag, NULL);
909
910         rootnode = g_node_new(xmlnode);
911
912         for (list = folder_list; list != NULL; list = list->next) {
913                 GNode *node;
914
915                 folder = list->data;
916                 node = folder_get_xml_node(folder);
917                 if (node != NULL)
918                         g_node_append(rootnode, node);
919         }
920
921         for (slist = folder_unloaded_list; slist != NULL; slist = g_slist_next(slist)) {
922                 GNode *node = (GNode *) slist->data;
923
924                 g_node_append(rootnode, (gpointer) xml_copy_tree(node));
925         }
926
927         if (xml_write_tree(rootnode, pfile->fp) < 0) {
928                 prefs_file_close_revert(pfile);
929                 g_warning("failed to write folder list.");
930         } else if (prefs_file_close(pfile) < 0) {
931                 g_warning("failed to write folder list.");
932         }
933         xml_free_tree(rootnode);
934 }
935
936 static gboolean folder_scan_tree_func(GNode *node, gpointer data)
937 {
938         GHashTable *pptable = (GHashTable *)data;
939         FolderItem *item = (FolderItem *)node->data;
940
941         folder_item_restore_persist_prefs(item, pptable);
942         folder_item_scan_full(item, FALSE);
943
944         return FALSE;
945 }
946
947 static gboolean folder_restore_prefs_func(GNode *node, gpointer data)
948 {
949         GHashTable *pptable = (GHashTable *)data;
950         FolderItem *item = (FolderItem *)node->data;
951
952         folder_item_restore_persist_prefs(item, pptable);
953
954         return FALSE;
955 }
956
957 void folder_scan_tree(Folder *folder, gboolean rebuild)
958 {
959         GHashTable *pptable;
960         FolderUpdateData hookdata;
961         Folder *old_folder = folder;
962
963         if (!folder->klass->scan_tree)
964                 return;
965         
966         pptable = folder_persist_prefs_new(folder);
967
968         if (rebuild)
969                 folder_remove(folder);
970
971         if (folder->klass->scan_tree(folder) < 0) {
972                 if (rebuild)
973                         folder_add(old_folder);
974                 return;
975         } else if (rebuild)
976                 folder_add(folder);
977
978         hookdata.folder = folder;
979         hookdata.update_flags = FOLDER_TREE_CHANGED;
980         hookdata.item = NULL;
981         hookdata.item2 = NULL;
982         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
983
984         if (rebuild)
985                 g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, folder_scan_tree_func, pptable);
986         else
987                 g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, folder_restore_prefs_func, pptable);
988
989         folder_persist_prefs_free(pptable);
990
991         prefs_matcher_read_config();
992
993         folder_write_list();
994 }
995
996 FolderItem *folder_create_folder(FolderItem *parent, const gchar *name)
997 {
998         FolderItem *new_item;
999         
1000         cm_return_val_if_fail(parent != NULL, NULL);
1001
1002         new_item = parent->folder->klass->create_folder(parent->folder, parent, name);
1003         if (new_item) {
1004                 FolderUpdateData hookdata;
1005
1006                 new_item->cache = msgcache_new();
1007                 new_item->cache_dirty = TRUE;
1008                 new_item->mark_dirty = TRUE;
1009                 new_item->tags_dirty = TRUE;
1010
1011                 hookdata.folder = new_item->folder;
1012                 hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_ADD_FOLDERITEM;
1013                 hookdata.item = new_item;
1014                 hookdata.item2 = NULL;
1015                 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
1016         }
1017
1018         return new_item;
1019 }
1020
1021 gint folder_item_rename(FolderItem *item, gchar *newname)
1022 {
1023         gint retval;
1024
1025         cm_return_val_if_fail(item != NULL, -1);
1026         cm_return_val_if_fail(newname != NULL, -1);
1027
1028         retval = item->folder->klass->rename_folder(item->folder, item, newname);
1029
1030         if (retval >= 0) {
1031                 FolderItemUpdateData hookdata;
1032                 FolderUpdateData hookdata2;
1033
1034                 hookdata.item = item;
1035                 hookdata.update_flags = F_ITEM_UPDATE_NAME;
1036                 hookdata.msg = NULL;
1037                 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &hookdata);
1038
1039                 hookdata2.folder = item->folder;
1040                 hookdata2.item = item;
1041                 hookdata2.item2 = NULL;
1042                 hookdata2.update_flags = FOLDER_RENAME_FOLDERITEM;
1043                 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata2);
1044         }
1045
1046         return retval;
1047 }
1048
1049 struct TotalMsgCount
1050 {
1051         guint new_msgs;
1052         guint unread_msgs;
1053         guint unreadmarked_msgs;
1054         guint marked_msgs;
1055         guint total_msgs;
1056         guint replied_msgs;
1057         guint forwarded_msgs;
1058         guint locked_msgs;
1059         guint ignored_msgs;
1060         guint watched_msgs;
1061 };
1062
1063 struct FuncToAllFoldersData
1064 {
1065         FolderItemFunc  function;
1066         gpointer        data;
1067 };
1068
1069 static gboolean folder_func_to_all_folders_func(GNode *node, gpointer data)
1070 {
1071         FolderItem *item;
1072         struct FuncToAllFoldersData *function_data = (struct FuncToAllFoldersData *) data;
1073
1074         cm_return_val_if_fail(node->data != NULL, FALSE);
1075
1076         item = FOLDER_ITEM(node->data);
1077         cm_return_val_if_fail(item != NULL, FALSE);
1078
1079         function_data->function(item, function_data->data);
1080
1081         return FALSE;
1082 }
1083
1084 void folder_func_to_all_folders(FolderItemFunc function, gpointer data)
1085 {
1086         GList *list;
1087         Folder *folder;
1088         struct FuncToAllFoldersData function_data;
1089         
1090         function_data.function = function;
1091         function_data.data = data;
1092
1093         for (list = folder_list; list != NULL; list = list->next) {
1094                 folder = FOLDER(list->data);
1095                 if (folder->node)
1096                         g_node_traverse(folder->node, G_PRE_ORDER,
1097                                         G_TRAVERSE_ALL, -1,
1098                                         folder_func_to_all_folders_func,
1099                                         &function_data);
1100         }
1101 }
1102
1103 static void folder_count_total_msgs_func(FolderItem *item, gpointer data)
1104 {
1105         struct TotalMsgCount *count = (struct TotalMsgCount *)data;
1106
1107         count->new_msgs += item->new_msgs;
1108         count->unread_msgs += item->unread_msgs;
1109         count->unreadmarked_msgs += item->unreadmarked_msgs;
1110         count->marked_msgs += item->marked_msgs;
1111         count->total_msgs += item->total_msgs;
1112         count->replied_msgs += item->replied_msgs;
1113         count->forwarded_msgs += item->forwarded_msgs;
1114         count->locked_msgs += item->locked_msgs;
1115         count->ignored_msgs += item->ignored_msgs;
1116         count->watched_msgs += item->watched_msgs;
1117 }
1118
1119 struct TotalMsgStatus
1120 {
1121         guint new;
1122         guint unread;
1123         guint total;
1124         GString *str;
1125 };
1126
1127 static gboolean folder_get_status_full_all_func(GNode *node, gpointer data)
1128 {
1129         FolderItem *item;
1130         struct TotalMsgStatus *status = (struct TotalMsgStatus *)data;
1131         gchar *id;
1132  
1133         cm_return_val_if_fail(node->data != NULL, FALSE);
1134  
1135         item = FOLDER_ITEM(node->data);
1136
1137         if (!item->path) return FALSE;
1138
1139         status->new += item->new_msgs;
1140         status->unread += item->unread_msgs;
1141         status->total += item->total_msgs;
1142
1143         if (status->str) {
1144                 id = folder_item_get_identifier(item);
1145                 g_string_append_printf(status->str, "%5d %5d %5d %s\n",
1146                                   item->new_msgs, item->unread_msgs,
1147                                   item->total_msgs, id);
1148                 g_free(id);
1149         }
1150  
1151         return FALSE;
1152  }
1153  
1154 static void folder_get_status_full_all(GString *str, guint *new, guint *unread,
1155                                        guint *total)
1156 {
1157         GList *list;
1158         Folder *folder;
1159         struct TotalMsgStatus status;
1160  
1161         status.new = status.unread = status.total = 0;
1162         status.str = str;
1163  
1164         debug_print("Counting total number of messages...\n");
1165  
1166         for (list = folder_list; list != NULL; list = list->next) {
1167                 folder = FOLDER(list->data);
1168                 if (folder->node)
1169                         g_node_traverse(folder->node, G_PRE_ORDER,
1170                                         G_TRAVERSE_ALL, -1,
1171                                         folder_get_status_full_all_func,
1172                                         &status);
1173         }
1174  
1175         *new = status.new;
1176         *unread = status.unread;
1177         *total = status.total;
1178 }
1179
1180 gchar *folder_get_status(GPtrArray *folders, gboolean full)
1181 {
1182         guint new, unread, total;
1183         GString *str;
1184         gint i;
1185         gchar *ret;
1186
1187         new = unread = total = 0;
1188
1189         str = g_string_new(NULL);
1190
1191         if (folders) {
1192                 for (i = 0; i < folders->len; i++) {
1193                         FolderItem *item;
1194
1195                         item = g_ptr_array_index(folders, i);
1196                         new += item->new_msgs;
1197                         unread += item->unread_msgs;
1198                         total += item->total_msgs;
1199
1200                         if (full) {
1201                                 gchar *id;
1202
1203                                 id = folder_item_get_identifier(item);
1204                                 g_string_append_printf(str, "%5d %5d %5d %s\n",
1205                                                   item->new_msgs, item->unread_msgs,
1206                                                   item->total_msgs, id);
1207                                 g_free(id);
1208                         }
1209                 }
1210         } else {
1211                 folder_get_status_full_all(full ? str : NULL,
1212                                            &new, &unread, &total);
1213         }
1214
1215         if (full)
1216                 g_string_append_printf(str, "%5d %5d %5d\n", new, unread, total);
1217         else
1218                 g_string_append_printf(str, "%d %d %d\n", new, unread, total);
1219
1220         ret = str->str;
1221         g_string_free(str, FALSE);
1222  
1223         return ret;
1224 }
1225
1226 void folder_count_total_msgs(guint *new_msgs, guint *unread_msgs, 
1227                              guint *unreadmarked_msgs, guint *marked_msgs,
1228                              guint *total_msgs, guint *replied_msgs,
1229                              guint *forwarded_msgs, guint *locked_msgs,
1230                              guint *ignored_msgs, guint *watched_msgs)
1231 {
1232         struct TotalMsgCount count;
1233
1234         count.new_msgs = count.unread_msgs = count.unreadmarked_msgs = 0;
1235         count.total_msgs = count.replied_msgs = count.forwarded_msgs = 0;
1236         count.locked_msgs = count.ignored_msgs = count.watched_msgs = 0;
1237         count.marked_msgs = 0;
1238
1239         debug_print("Counting total number of messages...\n");
1240
1241         folder_func_to_all_folders(folder_count_total_msgs_func, &count);
1242
1243         *new_msgs = count.new_msgs;
1244         *unread_msgs = count.unread_msgs;
1245         *unreadmarked_msgs = count.unreadmarked_msgs;
1246         *marked_msgs = count.marked_msgs;
1247         *total_msgs = count.total_msgs;
1248         *replied_msgs = count.replied_msgs;
1249         *forwarded_msgs = count.forwarded_msgs;
1250         *locked_msgs = count.locked_msgs;
1251         *ignored_msgs = count.ignored_msgs;
1252         *watched_msgs = count.watched_msgs;
1253 }
1254
1255 Folder *folder_find_from_path(const gchar *path)
1256 {
1257         GList *list;
1258         Folder *folder;
1259
1260         for (list = folder_list; list != NULL; list = list->next) {
1261                 folder = list->data;
1262                 if ((FOLDER_TYPE(folder) == F_MH || 
1263                      FOLDER_TYPE(folder) == F_MBOX) &&
1264                     !path_cmp(LOCAL_FOLDER(folder)->rootpath, path))
1265                         return folder;
1266         }
1267
1268         return NULL;
1269 }
1270
1271 Folder *folder_find_from_name(const gchar *name, FolderClass *klass)
1272 {
1273         GList *list;
1274         Folder *folder;
1275
1276         for (list = folder_list; list != NULL; list = list->next) {
1277                 folder = list->data;
1278                 if (folder->klass == klass && 
1279                     strcmp2(name, folder->name) == 0)
1280                         return folder;
1281         }
1282
1283         return NULL;
1284 }
1285
1286 static gboolean folder_item_find_func(GNode *node, gpointer data)
1287 {
1288         FolderItem *item = node->data;
1289         gpointer *d = data;
1290         const gchar *path = d[0];
1291
1292         if (path_cmp(path, item->path) != 0)
1293                 return FALSE;
1294
1295         d[1] = item;
1296
1297         return TRUE;
1298 }
1299
1300 FolderItem *folder_find_item_from_path(const gchar *path)
1301 {
1302         Folder *folder;
1303         gpointer d[2];
1304         GList *list = folder_get_list();
1305         
1306         folder = list ? list->data:NULL;
1307         
1308         cm_return_val_if_fail(folder != NULL, NULL);
1309
1310         d[0] = (gpointer)path;
1311         d[1] = NULL;
1312         while (d[1] == NULL && list) {
1313                 folder = FOLDER(list->data);
1314                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1315                         folder_item_find_func, d);
1316                 list = list->next;
1317         }
1318         return d[1];
1319 }
1320
1321 static gboolean folder_item_find_func_real_path(GNode *node, gpointer data)
1322 {
1323         FolderItem *item = node->data;
1324         gpointer *d = data;
1325         const gchar *path = d[0];
1326         gchar *tmp = folder_item_get_path(item);
1327         if (path_cmp(path, tmp) != 0) {
1328                 g_free(tmp);
1329                 return FALSE;
1330         }
1331         g_free(tmp);
1332         d[1] = item;
1333
1334         return TRUE;
1335 }
1336
1337 FolderItem *folder_find_item_from_real_path(const gchar *path)
1338 {
1339         Folder *folder;
1340         gpointer d[2];
1341         GList *list = folder_get_list();
1342         
1343         folder = list ? list->data:NULL;
1344         
1345         cm_return_val_if_fail(folder != NULL, NULL);
1346
1347         d[0] = (gpointer)path;
1348         d[1] = NULL;
1349         while (d[1] == NULL && list) {
1350                 folder = FOLDER(list->data);
1351                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1352                         folder_item_find_func_real_path, d);
1353                 list = list->next;
1354         }
1355         return d[1];
1356 }
1357
1358 FolderItem *folder_find_child_item_by_name(FolderItem *item, const gchar *name)
1359 {
1360         GNode *node;
1361         FolderItem *child;
1362
1363         for (node = item->node->children; node != NULL; node = node->next) {
1364                 child = FOLDER_ITEM(node->data);
1365                 if (strcmp2(child->name, name) == 0) {
1366                         return child;
1367                 }
1368         }
1369
1370         return NULL;
1371 }
1372
1373 FolderClass *folder_get_class_from_string(const gchar *str)
1374 {
1375         GSList *classlist;
1376
1377         classlist = folder_get_class_list();
1378         for (; classlist != NULL; classlist = g_slist_next(classlist)) {
1379                 FolderClass *class = (FolderClass *) classlist->data;
1380                 if (g_ascii_strcasecmp(class->idstr, str) == 0)
1381                         return class;
1382         }
1383
1384         return NULL;
1385 }
1386
1387 gchar *folder_get_identifier(Folder *folder)
1388 {
1389         gchar *type_str;
1390
1391         cm_return_val_if_fail(folder != NULL, NULL);
1392
1393         type_str = folder->klass->idstr;
1394         return g_strconcat("#", type_str, "/", folder->name, NULL);
1395 }
1396
1397 gchar *folder_item_get_identifier(FolderItem *item)
1398 {
1399         gchar *id = NULL;
1400         gchar *folder_id = NULL;
1401
1402         cm_return_val_if_fail(item != NULL, NULL);
1403
1404         if (item->path == NULL)
1405                 return NULL;
1406
1407         folder_id = folder_get_identifier(item->folder);
1408         id = g_strconcat(folder_id, "/", item->path, NULL);
1409         g_free(folder_id);
1410
1411         return id;
1412 }
1413
1414 Folder *folder_find_from_identifier(const gchar *identifier)
1415 {
1416         gchar *str;
1417         gchar *p;
1418         gchar *name;
1419         FolderClass *class;
1420
1421         cm_return_val_if_fail(identifier != NULL, NULL);
1422
1423         if (*identifier != '#')
1424                 return NULL;
1425
1426         Xstrdup_a(str, identifier, return NULL);
1427
1428         p = strchr(str, '/');
1429         if (!p)
1430                 return NULL;
1431         *p = '\0';
1432         p++;
1433         class = folder_get_class_from_string(&str[1]);
1434         if (class == NULL)
1435                 return NULL;
1436
1437         name = p;
1438         p = strchr(p, '/');
1439         if (p)
1440                 return NULL;
1441
1442         return folder_find_from_name(name, class);
1443 }
1444
1445 FolderItem *folder_find_item_from_identifier(const gchar *identifier)
1446 {
1447         Folder *folder;
1448         gpointer d[2];
1449         gchar *str;
1450         gchar *p;
1451         gchar *name;
1452         gchar *path;
1453         FolderClass *class;
1454
1455         cm_return_val_if_fail(identifier != NULL, NULL);
1456
1457         if (*identifier != '#')
1458                 return folder_find_item_from_path(identifier);
1459
1460         Xstrdup_a(str, identifier, return NULL);
1461
1462         p = strchr(str, '/');
1463         if (!p)
1464                 return folder_find_item_from_path(identifier);
1465         *p = '\0';
1466         p++;
1467         class = folder_get_class_from_string(&str[1]);
1468         if (class == NULL)
1469                 return folder_find_item_from_path(identifier);
1470
1471         name = p;
1472         p = strchr(p, '/');
1473         if (!p)
1474                 return folder_find_item_from_path(identifier);
1475         *p = '\0';
1476         p++;
1477
1478         folder = folder_find_from_name(name, class);
1479         if (!folder)
1480                 return folder_find_item_from_path(identifier);
1481
1482         path = p;
1483
1484         d[0] = (gpointer)path;
1485         d[1] = NULL;
1486         g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1487                         folder_item_find_func, d);
1488         return d[1];
1489 }
1490
1491 /** Returns the FolderItem from a given identifier
1492  *
1493  * The FolderItem is created if it doesn't already exist.
1494  * If creation failed, the function returns NULL.
1495  * 
1496  * Identifiers are of the form #type/Mailbox/FolderA/FolderB/FolderC
1497  */
1498 FolderItem *folder_get_item_from_identifier(const gchar *identifier)
1499 {
1500         FolderItem *item, *last_parent;
1501         Folder *folder;
1502         gchar *p1, *p2, *str;
1503         size_t len;
1504         FolderClass *class;
1505         gboolean created_something = FALSE;
1506
1507         item = folder_find_item_from_identifier(identifier);
1508         if(item)
1509                 return item;
1510
1511         /* trivial sanity check: need at least # and two slashes */
1512         len = strlen(identifier);
1513         if(len < 3)
1514                 return NULL;
1515
1516         /* make sure identifier ends with a slash */
1517         if(identifier[len-1] == G_DIR_SEPARATOR) {
1518                 Xstrdup_a(str, identifier, return NULL);
1519         }
1520         else {
1521                 Xstrndup_a(str, identifier, len+1, return NULL);
1522                 str[len] = G_DIR_SEPARATOR;
1523         }
1524
1525         /* find folder class */
1526         p1 = strchr(str, G_DIR_SEPARATOR);
1527         if(!p1)
1528                 return NULL;
1529         *p1 = '\0';
1530         class = folder_get_class_from_string(&str[1]);
1531         if(!class)
1532                 return NULL;
1533         *p1 = G_DIR_SEPARATOR;
1534         ++p1;
1535
1536         /* find folder from class and name */
1537         p2 = strchr(p1, G_DIR_SEPARATOR);
1538         if(!p2)
1539                 return NULL;
1540         *p2 = '\0';
1541         folder = folder_find_from_name(p1, class);
1542         if(!folder)
1543                 return NULL;
1544         *p2 = G_DIR_SEPARATOR;
1545         ++p2;
1546         p1 = p2;
1547
1548         /* Now, move forward and make sure all sections in the path exist */
1549         last_parent = folder->node->data;
1550         while((p1 = strchr(p1, G_DIR_SEPARATOR)) != NULL) {
1551                 *p1 = '\0';
1552                 item = folder_find_item_from_identifier(str);
1553                 if(!item) {
1554                         item = folder_create_folder(last_parent, p2);
1555                         if(!item)
1556                                 return NULL;
1557                         debug_print("Created folder '%s'\n", str);
1558                         created_something = TRUE;
1559                         if(prefs_common.inherit_folder_props && (last_parent != item->folder->node->data)) {
1560                                 folder_item_prefs_copy_prefs(last_parent, item);
1561                         }
1562                 }
1563                 last_parent = item;
1564                 *p1 = G_DIR_SEPARATOR;
1565                 ++p1;
1566                 p2 = p1;
1567         }
1568
1569         if(created_something)
1570                 folder_write_list();
1571
1572         return item;
1573 }
1574
1575
1576 /**
1577  * Get a displayable name for a FolderItem
1578  *
1579  * \param item FolderItem for that a name should be created
1580  * \return Displayable name for item, returned string has to
1581  *         be freed
1582  */
1583 gchar *folder_item_get_name(FolderItem *item)
1584 {
1585         gchar *name = NULL;
1586
1587         cm_return_val_if_fail(item != NULL, g_strdup(""));
1588
1589         switch (item->stype) {
1590         case F_INBOX:
1591                 name = g_strdup(!strcmp2(item->name, INBOX_DIR) ? _("Inbox") :
1592                                 item->name);
1593                 break;
1594         case F_OUTBOX:
1595                 name = g_strdup(!strcmp2(item->name, OUTBOX_DIR) ? _("Sent") :
1596                                 item->name);
1597                 break;
1598         case F_QUEUE:
1599                 name = g_strdup(!strcmp2(item->name, QUEUE_DIR) ? _("Queue") :
1600                                 item->name);
1601                 break;
1602         case F_TRASH:
1603                 name = g_strdup(!strcmp2(item->name, TRASH_DIR) ? _("Trash") :
1604                                 item->name);
1605                 break;
1606         case F_DRAFT:
1607                 name = g_strdup(!strcmp2(item->name, DRAFT_DIR) ? _("Drafts") :
1608                                 item->name);
1609                 break;
1610         default:
1611                 break;
1612         }
1613
1614         if (name == NULL) {
1615                 /*
1616                  * should probably be done by a virtual function,
1617                  * the folder knows the ui string and how to abbrev
1618                 */
1619                 if (folder_item_parent(item) == NULL) {
1620                         name = g_strconcat(item->name, " (", item->folder->klass->uistr, ")", NULL);
1621                 } else {
1622                         if (FOLDER_CLASS(item->folder) == news_get_class() &&
1623                             item->path && !strcmp2(item->name, item->path))
1624                                 name = get_abbrev_newsgroup_name
1625                                         (item->path,
1626                                          prefs_common.ng_abbrev_len);
1627                         else
1628                                 name = g_strdup(item->name);
1629                 }
1630         }
1631
1632         if (name == NULL)
1633                 name = g_strdup("");
1634
1635         return name;
1636 }
1637
1638 gboolean folder_have_mailbox (void)
1639 {
1640         GList *cur;
1641         for (cur = folder_list; cur != NULL; cur = g_list_next(cur)) {
1642                 Folder *folder = FOLDER(cur->data);
1643                 if (folder->inbox && folder->outbox)
1644                         return TRUE;
1645         }
1646         return FALSE;
1647 }
1648
1649 FolderItem *folder_get_default_inbox(void)
1650 {
1651         GList *flist;
1652
1653         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1654                 Folder * folder = FOLDER(flist->data);
1655
1656                 if (folder == NULL)
1657                         continue;
1658                 if (folder->inbox == NULL)
1659                         continue;
1660                 if (folder->klass->type == F_UNKNOWN)
1661                         continue;
1662
1663                 return folder->inbox;
1664         }
1665
1666         return NULL;
1667 }
1668
1669 FolderItem *folder_get_default_inbox_for_class(FolderType type)
1670 {
1671         GList *flist;
1672
1673         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1674                 Folder * folder = FOLDER(flist->data);
1675
1676                 if (folder == NULL)
1677                         continue;
1678                 if (folder->inbox == NULL)
1679                         continue;
1680                 if (folder->klass->type != type)
1681                         continue;
1682
1683                 return folder->inbox;
1684         }
1685
1686         return NULL;
1687 }
1688
1689 FolderItem *folder_get_default_outbox(void)
1690 {
1691         GList *flist;
1692
1693         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1694                 Folder * folder = FOLDER(flist->data);
1695
1696                 if (folder == NULL)
1697                         continue;
1698                 if (folder->outbox == NULL)
1699                         continue;
1700                 if (folder->klass->type == F_UNKNOWN)
1701                         continue;
1702
1703                 return folder->outbox;
1704         }
1705
1706         return NULL;
1707 }
1708
1709 FolderItem *folder_get_default_outbox_for_class(FolderType type)
1710 {
1711         GList *flist;
1712
1713         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1714                 Folder * folder = FOLDER(flist->data);
1715
1716                 if (folder == NULL)
1717                         continue;
1718                 if (folder->outbox == NULL)
1719                         continue;
1720                 if (folder->klass->type != type)
1721                         continue;
1722
1723                 return folder->outbox;
1724         }
1725
1726         return NULL;
1727 }
1728
1729 FolderItem *folder_get_default_draft(void)
1730 {
1731         GList *flist;
1732
1733         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1734                 Folder * folder = FOLDER(flist->data);
1735
1736                 if (folder == NULL)
1737                         continue;
1738                 if (folder->draft == NULL)
1739                         continue;
1740                 if (folder->klass->type == F_UNKNOWN)
1741                         continue;
1742
1743                 return folder->draft;
1744         }
1745
1746         return NULL;
1747 }
1748
1749 FolderItem *folder_get_default_draft_for_class(FolderType type)
1750 {
1751         GList *flist;
1752
1753         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1754                 Folder * folder = FOLDER(flist->data);
1755
1756                 if (folder == NULL)
1757                         continue;
1758                 if (folder->draft == NULL)
1759                         continue;
1760                 if (folder->klass->type != type)
1761                         continue;
1762
1763                 return folder->draft;
1764         }
1765
1766         return NULL;
1767 }
1768
1769 FolderItem *folder_get_default_queue(void)
1770 {
1771         GList *flist;
1772
1773         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1774                 Folder * folder = FOLDER(flist->data);
1775
1776                 if (folder == NULL)
1777                         continue;
1778                 if (folder->queue == NULL)
1779                         continue;
1780                 if (folder->klass->type == F_UNKNOWN)
1781                         continue;
1782
1783                 return folder->queue;
1784         }
1785
1786         return NULL;
1787 }
1788
1789 FolderItem *folder_get_default_queue_for_class(FolderType type)
1790 {
1791         GList *flist;
1792
1793         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1794                 Folder * folder = FOLDER(flist->data);
1795
1796                 if (folder == NULL)
1797                         continue;
1798                 if (folder->queue == NULL)
1799                         continue;
1800                 if (folder->klass->type != type)
1801                         continue;
1802
1803                 return folder->queue;
1804         }
1805
1806         return NULL;
1807 }
1808
1809 FolderItem *folder_get_default_trash(void)
1810 {
1811         GList *flist;
1812
1813         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1814                 Folder * folder = FOLDER(flist->data);
1815
1816                 if (folder == NULL)
1817                         continue;
1818                 if (folder->trash == NULL)
1819                         continue;
1820                 if (folder->klass->type == F_UNKNOWN)
1821                         continue;
1822
1823                 return folder->trash;
1824         }
1825
1826         return NULL;
1827 }
1828
1829 FolderItem *folder_get_default_trash_for_class(FolderType type)
1830 {
1831         GList *flist;
1832
1833         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1834                 Folder * folder = FOLDER(flist->data);
1835
1836                 if (folder == NULL)
1837                         continue;
1838                 if (folder->trash == NULL)
1839                         continue;
1840                 if (folder->klass->type != type)
1841                         continue;
1842
1843                 return folder->trash;
1844         }
1845
1846         return NULL;
1847 }
1848
1849 #define CREATE_FOLDER_IF_NOT_EXIST(member, dir, type)           \
1850 {                                                               \
1851         if (!folder->member) {                                  \
1852                 item = folder_item_new(folder, dir, dir);       \
1853                 item->stype = type;                             \
1854                 folder_item_append(rootitem, item);             \
1855                 folder->member = item;                          \
1856         }                                                       \
1857 }
1858
1859 void folder_set_missing_folders(void)
1860 {
1861         Folder *folder;
1862         FolderItem *rootitem;
1863         FolderItem *item;
1864         GList *list;
1865
1866         for (list = folder_list; list != NULL; list = list->next) {
1867                 folder = list->data;
1868                 if (FOLDER_TYPE(folder) != F_MH) continue;
1869                 rootitem = FOLDER_ITEM(folder->node->data);
1870                 cm_return_if_fail(rootitem != NULL);
1871
1872                 if (folder->inbox && folder->outbox && folder->draft &&
1873                     folder->queue && folder->trash)
1874                         continue;
1875
1876                 if (folder->klass->create_tree(folder) < 0) {
1877                         g_warning("%s: can't create the folder tree.",
1878                                   LOCAL_FOLDER(folder)->rootpath);
1879                         continue;
1880                 }
1881
1882                 CREATE_FOLDER_IF_NOT_EXIST(inbox,  INBOX_DIR,  F_INBOX);
1883                 CREATE_FOLDER_IF_NOT_EXIST(outbox, OUTBOX_DIR, F_OUTBOX);
1884                 CREATE_FOLDER_IF_NOT_EXIST(draft,  DRAFT_DIR,  F_DRAFT);
1885                 CREATE_FOLDER_IF_NOT_EXIST(queue,  QUEUE_DIR,  F_QUEUE);
1886                 CREATE_FOLDER_IF_NOT_EXIST(trash,  TRASH_DIR,  F_TRASH);
1887         }
1888 }
1889
1890 static gboolean folder_unref_account_func(GNode *node, gpointer data)
1891 {
1892         FolderItem *item = node->data;
1893         PrefsAccount *account = data;
1894
1895         if (item->account == account)
1896                 item->account = NULL;
1897
1898         return FALSE;
1899 }
1900
1901 void folder_unref_account_all(PrefsAccount *account)
1902 {
1903         Folder *folder;
1904         GList *list;
1905
1906         if (!account) return;
1907
1908         for (list = folder_list; list != NULL; list = list->next) {
1909                 folder = list->data;
1910                 if (folder->account == account)
1911                         folder->account = NULL;
1912                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1913                                 folder_unref_account_func, account);
1914         }
1915 }
1916
1917 #undef CREATE_FOLDER_IF_NOT_EXIST
1918
1919 gchar *folder_item_get_path(FolderItem *item)
1920 {
1921         Folder *folder;
1922
1923         cm_return_val_if_fail(item != NULL, NULL);
1924         folder = item->folder;
1925         cm_return_val_if_fail(folder != NULL, NULL);
1926
1927         return folder->klass->item_get_path(folder, item);
1928 }
1929
1930 static gint folder_sort_cache_list_by_msgnum(gconstpointer a, gconstpointer b)
1931 {
1932         MsgInfo *msginfo_a = (MsgInfo *) a;
1933         MsgInfo *msginfo_b = (MsgInfo *) b;
1934
1935         return (msginfo_a->msgnum - msginfo_b->msgnum);
1936 }
1937
1938 static gint folder_sort_folder_list(gconstpointer a, gconstpointer b)
1939 {
1940         guint gint_a = GPOINTER_TO_INT(a);
1941         guint gint_b = GPOINTER_TO_INT(b);
1942         
1943         return (gint_a - gint_b);
1944 }
1945
1946 static gint syncronize_flags(FolderItem *item, MsgInfoList *msglist)
1947 {
1948         GHashTable *relation;
1949         gint ret = 0;
1950         GSList *cur;
1951
1952         if(msglist == NULL)
1953                 return 0;
1954         if(item->folder->klass->get_flags == NULL)
1955                 return 0;
1956         if (item->no_select)
1957                 return 0;
1958
1959         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
1960         if ((ret = item->folder->klass->get_flags(
1961             item->folder, item, msglist, relation)) == 0) {
1962                 gpointer data, old_key;
1963                 MsgInfo *msginfo;
1964                 MsgPermFlags permflags = 0;
1965
1966                 folder_item_update_freeze();
1967                 folder_item_set_batch(item, TRUE);
1968                 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1969                         msginfo = (MsgInfo *) cur->data;
1970                 
1971                         if (g_hash_table_lookup_extended(relation, msginfo, &old_key, &data)) {
1972                                 permflags = GPOINTER_TO_INT(data);
1973
1974                                 if (msginfo->flags.perm_flags != permflags) {
1975                                         procmsg_msginfo_change_flags(msginfo,
1976                                                 permflags & ~msginfo->flags.perm_flags, 0,
1977                                                 ~permflags & msginfo->flags.perm_flags, 0);
1978                                 }
1979                         }
1980                 }
1981                 folder_item_set_batch(item, FALSE);
1982                 folder_item_update_thaw();
1983         }
1984         g_hash_table_destroy(relation); 
1985
1986         return ret;
1987 }
1988
1989 static gint folder_item_syncronize_flags(FolderItem *item)
1990 {
1991         MsgInfoList *msglist = NULL;
1992         GSList *cur;
1993         gint ret = 0;
1994         
1995         cm_return_val_if_fail(item != NULL, -1);
1996         cm_return_val_if_fail(item->folder != NULL, -1);
1997         cm_return_val_if_fail(item->folder->klass != NULL, -1);
1998         if (item->no_select)
1999                 return -1;
2000
2001         item->scanning = ITEM_SCANNING_WITH_FLAGS;
2002
2003         if (item->cache == NULL)
2004                 folder_item_read_cache(item);
2005         
2006         msglist = msgcache_get_msg_list(item->cache);
2007         
2008         ret = syncronize_flags(item, msglist);
2009
2010         for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
2011                 procmsg_msginfo_free((MsgInfo **)&(cur->data));
2012         }
2013         
2014         g_slist_free(msglist);
2015
2016         item->scanning = ITEM_NOT_SCANNING;
2017
2018         return ret;
2019 }
2020
2021 static void folder_item_process_open (FolderItem *item,
2022                                  void (*before_proc_func)(gpointer data),
2023                                  void (*after_proc_func)(gpointer data),
2024                                  gpointer data)
2025 {
2026         gchar *buf;
2027         if (item == NULL)
2028                 return;
2029         if((item->folder->klass->scan_required != NULL) &&
2030            (item->folder->klass->scan_required(item->folder, item))) {
2031                 folder_item_scan_full(item, TRUE);
2032         } else {
2033                 folder_item_syncronize_flags(item);
2034         }
2035         
2036         /* Processing */
2037         if (item->prefs->enable_processing_when_opening) {
2038                 buf = g_strdup_printf(_("Processing (%s)...\n"), 
2039                               item->path ? item->path : item->name);
2040                 g_free(buf);
2041
2042                 if (before_proc_func)
2043                         before_proc_func(data);
2044
2045                 folder_item_apply_processing(item);
2046
2047                 if (after_proc_func)
2048                         after_proc_func(data);
2049         }
2050         item->processing_pending = FALSE;
2051         return; 
2052 }
2053
2054 gint folder_item_open(FolderItem *item)
2055 {
2056         START_TIMING(""); 
2057         if (item->no_select)
2058                 return -1;
2059
2060         if (item->scanning != ITEM_NOT_SCANNING) {
2061                 debug_print("%s is scanning... \n", item->path ? item->path : item->name);
2062                 return -2;
2063         }
2064
2065         item->processing_pending = TRUE;
2066         folder_item_process_open (item, NULL, NULL, NULL);
2067         
2068         item->opened = TRUE;
2069         END_TIMING();
2070         return 0;
2071 }
2072
2073 gint folder_item_close(FolderItem *item)
2074 {
2075         GSList *mlist, *cur;
2076         Folder *folder;
2077         
2078         cm_return_val_if_fail(item != NULL, -1);
2079
2080         if (item->no_select)
2081                 return -1;
2082
2083         if (item->new_msgs) {
2084                 folder_item_update_freeze();
2085                 mlist = folder_item_get_msg_list(item);
2086                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
2087                         MsgInfo * msginfo;
2088
2089                         msginfo = (MsgInfo *) cur->data;
2090                         if (MSG_IS_NEW(msginfo->flags))
2091                                 procmsg_msginfo_unset_flags(msginfo, MSG_NEW, 0);
2092                         procmsg_msginfo_free(&msginfo);
2093                 }
2094                 g_slist_free(mlist);
2095                 folder_item_update_thaw();
2096         }               
2097
2098         folder_item_write_cache(item);
2099         
2100         folder_item_update(item, F_ITEM_UPDATE_MSGCNT);
2101
2102         item->opened = FALSE;
2103         folder = item->folder;
2104
2105         if (folder->klass->close == NULL)
2106                 return 0;
2107
2108         return folder->klass->close(folder, item);
2109 }
2110
2111 static MsgInfoList *get_msginfos(FolderItem *item, MsgNumberList *numlist)
2112 {
2113         MsgInfoList *msglist = NULL;
2114         Folder *folder = item->folder;
2115         if (item->no_select)
2116                 return NULL;
2117         
2118         if (folder->klass->get_msginfos != NULL)
2119                 msglist = folder->klass->get_msginfos(folder, item, numlist);
2120         else {
2121                 MsgNumberList *elem;
2122
2123                 for (elem = numlist; elem != NULL; elem = g_slist_next(elem)) {
2124                         MsgInfo *msginfo;
2125                         guint num;
2126
2127                         num = GPOINTER_TO_INT(elem->data);
2128                         msginfo = folder->klass->get_msginfo(folder, item, num);
2129                         if (msginfo != NULL)
2130                                 msglist = g_slist_prepend(msglist, msginfo);
2131                 }               
2132         }
2133
2134         return msglist;
2135 }
2136
2137 static MsgInfo *get_msginfo(FolderItem *item, guint num)
2138 {
2139         MsgNumberList numlist;
2140         MsgInfoList *msglist;
2141         MsgInfo *msginfo = NULL;
2142
2143         numlist.data = GINT_TO_POINTER(num);
2144         numlist.next = NULL;
2145         msglist = get_msginfos(item, &numlist);
2146         if (msglist != NULL)
2147                 msginfo = procmsg_msginfo_new_ref(msglist->data);
2148         procmsg_msg_list_free(msglist);
2149
2150         return msginfo;
2151 }
2152
2153 gint folder_item_scan_full(FolderItem *item, gboolean filtering)
2154 {
2155         Folder *folder;
2156         GSList *folder_list = NULL, *cache_list = NULL;
2157         GSList *folder_list_cur, *cache_list_cur, *new_list = NULL;
2158         GSList *exists_list = NULL, *elem;
2159         GSList *newmsg_list = NULL;
2160         guint newcnt = 0, unreadcnt = 0, totalcnt = 0;
2161         guint markedcnt = 0, unreadmarkedcnt = 0;
2162         guint repliedcnt = 0, forwardedcnt = 0;
2163         guint lockedcnt = 0, ignoredcnt = 0, watchedcnt = 0;
2164
2165         guint cache_max_num, folder_max_num, cache_cur_num, folder_cur_num;
2166         gboolean update_flags = 0, old_uids_valid = FALSE;
2167         GHashTable *subject_table = NULL;
2168         
2169         cm_return_val_if_fail(item != NULL, -1);
2170         if (item->path == NULL) return -1;
2171
2172         folder = item->folder;
2173
2174         cm_return_val_if_fail(folder != NULL, -1);
2175         cm_return_val_if_fail(folder->klass->get_num_list != NULL, -1);
2176
2177         item->scanning = ITEM_SCANNING_WITH_FLAGS;
2178
2179         debug_print("Scanning folder %s for cache changes.\n", item->path ? item->path : "(null)");
2180         
2181         /* Get list of messages for folder and cache */
2182         if (folder->klass->get_num_list(item->folder, item, &folder_list, &old_uids_valid) < 0) {
2183                 debug_print("Error fetching list of message numbers\n");
2184                 item->scanning = ITEM_NOT_SCANNING;
2185                 return(-1);
2186         }
2187
2188         if(prefs_common.thread_by_subject) {
2189                 subject_table = g_hash_table_new(g_str_hash, g_str_equal);
2190         }
2191         
2192         if (old_uids_valid) {
2193                 if (!item->cache)
2194                         folder_item_read_cache(item);
2195                 cache_list = msgcache_get_msg_list(item->cache);
2196         } else {
2197                 if (item->cache)
2198                         msgcache_destroy(item->cache);
2199                 item->cache = msgcache_new();
2200                 item->cache_dirty = TRUE;
2201                 item->mark_dirty = TRUE;
2202                 item->tags_dirty = TRUE;
2203                 cache_list = NULL;
2204         }
2205
2206         /* Sort both lists */
2207         cache_list = g_slist_sort(cache_list, folder_sort_cache_list_by_msgnum);
2208         folder_list = g_slist_sort(folder_list, folder_sort_folder_list);
2209
2210         cache_list_cur = cache_list;
2211         folder_list_cur = folder_list;
2212
2213         if (cache_list_cur != NULL) {
2214                 GSList *cache_list_last;
2215         
2216                 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
2217                 cache_list_last = g_slist_last(cache_list);
2218                 cache_max_num = ((MsgInfo *)cache_list_last->data)->msgnum;
2219         } else {
2220                 cache_cur_num = G_MAXUINT;
2221                 cache_max_num = 0;
2222         }
2223
2224         if (folder_list_cur != NULL) {
2225                 GSList *folder_list_last;
2226         
2227                 folder_cur_num = GPOINTER_TO_UINT(folder_list_cur->data);
2228                 folder_list_last = g_slist_last(folder_list);
2229                 folder_max_num = GPOINTER_TO_UINT(folder_list_last->data);
2230         } else {
2231                 folder_cur_num = G_MAXUINT;
2232                 folder_max_num = 0;
2233         }
2234
2235         while ((cache_cur_num != G_MAXUINT) || (folder_cur_num != G_MAXUINT)) {
2236                 /*
2237                  *  Message only exists in the folder
2238                  *  Remember message for fetching
2239                  */
2240                 if (folder_cur_num < cache_cur_num) {
2241                         gboolean add = FALSE;
2242
2243                         switch(FOLDER_TYPE(folder)) {
2244                                 case F_NEWS:
2245                                         if (folder_cur_num < cache_max_num)
2246                                                 break;
2247                                         
2248                                         if (folder->account->max_articles == 0) {
2249                                                 add = TRUE;
2250                                         }
2251
2252                                         if (folder_max_num <= folder->account->max_articles) {
2253                                                 add = TRUE;
2254                                         } else if (folder_cur_num > (folder_max_num - folder->account->max_articles)) {
2255                                                 add = TRUE;
2256                                         }
2257                                         break;
2258                                 default:
2259                                         add = TRUE;
2260                                         break;
2261                         }
2262                         
2263                         if (add) {
2264                                 new_list = g_slist_prepend(new_list, GUINT_TO_POINTER(folder_cur_num));
2265                                 debug_print("Remembered message %u for fetching\n", folder_cur_num);
2266                         }
2267
2268                         /* Move to next folder number */
2269                         if (folder_list_cur)
2270                                 folder_list_cur = folder_list_cur->next;
2271
2272                         if (folder_list_cur != NULL)
2273                                 folder_cur_num = GPOINTER_TO_UINT(folder_list_cur->data);
2274                         else
2275                                 folder_cur_num = G_MAXUINT;
2276
2277                         continue;
2278                 }
2279
2280                 /*
2281                  *  Message only exists in the cache
2282                  *  Remove the message from the cache
2283                  */
2284                 if (cache_cur_num < folder_cur_num) {
2285                         msgcache_remove_msg(item->cache, cache_cur_num);
2286                         debug_print("Removed message %u from cache.\n", cache_cur_num);
2287
2288                         /* Move to next cache number */
2289                         if (cache_list_cur)
2290                                 cache_list_cur = cache_list_cur->next;
2291
2292                         if (cache_list_cur != NULL)
2293                                 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
2294                         else
2295                                 cache_cur_num = G_MAXUINT;
2296
2297                         update_flags |= F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT;
2298
2299                         continue;
2300                 }
2301
2302                 /*
2303                  *  Message number exists in folder and cache!
2304                  *  Check if the message has been modified
2305                  */
2306                 if (cache_cur_num == folder_cur_num) {
2307                         MsgInfo *msginfo;
2308
2309                         msginfo = msgcache_get_msg(item->cache, folder_cur_num);
2310                         if (msginfo && folder->klass->is_msg_changed && folder->klass->is_msg_changed(folder, item, msginfo)) {
2311                                 msgcache_remove_msg(item->cache, msginfo->msgnum);
2312                                 new_list = g_slist_prepend(new_list, GINT_TO_POINTER(msginfo->msgnum));
2313                                 procmsg_msginfo_free(&msginfo);
2314
2315                                 debug_print("Remembering message %u to update...\n", folder_cur_num);
2316                         } else if (msginfo) {
2317                                 exists_list = g_slist_prepend(exists_list, msginfo);
2318
2319                                 if(prefs_common.thread_by_subject &&
2320                                         MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2321                                         !subject_table_lookup(subject_table, msginfo->subject)) {
2322                                         subject_table_insert(subject_table, msginfo->subject, msginfo);
2323                                 }
2324                         }
2325                         
2326                         /* Move to next folder and cache number */
2327                         if (cache_list_cur)
2328                                 cache_list_cur = cache_list_cur->next;
2329                         
2330                         if (folder_list_cur)
2331                                 folder_list_cur = folder_list_cur->next;
2332
2333                         if (cache_list_cur != NULL)
2334                                 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
2335                         else
2336                                 cache_cur_num = G_MAXUINT;
2337
2338                         if (folder_list_cur != NULL)
2339                                 folder_cur_num = GPOINTER_TO_UINT(folder_list_cur->data);
2340                         else
2341                                 folder_cur_num = G_MAXUINT;
2342
2343                         continue;
2344                 }
2345         }
2346         
2347         for(cache_list_cur = cache_list; cache_list_cur != NULL; cache_list_cur = g_slist_next(cache_list_cur))
2348                 procmsg_msginfo_free((MsgInfo **)&(cache_list_cur->data));
2349
2350         g_slist_free(cache_list);
2351         g_slist_free(folder_list);
2352
2353         if (new_list != NULL) {
2354                 GSList *tmp_list = NULL;
2355                 newmsg_list = get_msginfos(item, new_list);
2356                 g_slist_free(new_list);
2357                 tmp_list = g_slist_concat(g_slist_copy(exists_list), g_slist_copy(newmsg_list));
2358                 syncronize_flags(item, tmp_list);
2359                 g_slist_free(tmp_list);
2360         } else {
2361                 syncronize_flags(item, exists_list);
2362         }
2363
2364         folder_item_update_freeze();
2365         
2366         item->scanning = ITEM_SCANNING;
2367
2368         if (newmsg_list != NULL) {
2369                 GSList *elem, *to_filter = NULL;
2370                 gboolean do_filter = (filtering == TRUE) &&
2371                                         (item->stype == F_INBOX) &&
2372                                         (item->folder->account != NULL) && 
2373                                         (item->folder->account->filter_on_recv);
2374                 
2375                 for (elem = newmsg_list; elem != NULL; elem = g_slist_next(elem)) {
2376                         MsgInfo *msginfo = (MsgInfo *) elem->data;
2377
2378                         msgcache_add_msg(item->cache, msginfo);
2379                         if (!do_filter) {
2380                                 exists_list = g_slist_prepend(exists_list, msginfo);
2381
2382                                 if(prefs_common.thread_by_subject &&
2383                                         MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2384                                         !subject_table_lookup(subject_table, msginfo->subject)) {
2385                                         subject_table_insert(subject_table, msginfo->subject, msginfo);
2386                                 }                       
2387                         }
2388                 }
2389
2390                 if (do_filter) {
2391                         GSList *unfiltered;
2392                         
2393                         folder_item_set_batch(item, TRUE);
2394                         procmsg_msglist_filter(newmsg_list, item->folder->account, 
2395                                         &to_filter, &unfiltered, 
2396                                         TRUE);
2397                         folder_item_set_batch(item, FALSE);
2398                         
2399                         filtering_move_and_copy_msgs(newmsg_list);
2400                         if (to_filter != NULL) {
2401                                 for (elem = to_filter; elem; elem = g_slist_next(elem)) {
2402                                         MsgInfo *msginfo = (MsgInfo *)elem->data;
2403                                         procmsg_msginfo_free(&msginfo);
2404                                 }
2405                                 g_slist_free(to_filter);
2406                         }
2407                         if (unfiltered != NULL) {
2408                                 for (elem = unfiltered; elem; elem = g_slist_next(elem)) {
2409                                         MsgInfo *msginfo = (MsgInfo *)elem->data;
2410                                         exists_list = g_slist_prepend(exists_list, msginfo);
2411
2412                                         if(prefs_common.thread_by_subject &&
2413                                                 MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2414                                                 !subject_table_lookup(subject_table, msginfo->subject)) {
2415                                                 subject_table_insert(subject_table, msginfo->subject, msginfo);
2416                                         }
2417                                 }
2418                                 g_slist_free(unfiltered);
2419                         }
2420                         if (prefs_common.real_time_sync)
2421                                 folder_item_synchronise(item);
2422                 } else {
2423                         if (prefs_common.real_time_sync)
2424                                 folder_item_synchronise(item);
2425                 }
2426
2427                 g_slist_free(newmsg_list);
2428
2429                 update_flags |= F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT;
2430         }
2431
2432         folder_item_set_batch(item, TRUE);
2433         for (elem = exists_list; elem != NULL; elem = g_slist_next(elem)) {
2434                 MsgInfo *msginfo, *parent_msginfo;
2435
2436                 msginfo = elem->data;
2437                 if (MSG_IS_IGNORE_THREAD(msginfo->flags) && (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
2438                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
2439                 if (!MSG_IS_IGNORE_THREAD(msginfo->flags) && procmsg_msg_has_flagged_parent(msginfo, MSG_IGNORE_THREAD)) {
2440                         procmsg_msginfo_change_flags(msginfo, MSG_IGNORE_THREAD, 0, MSG_NEW | MSG_UNREAD, 0);
2441                 }
2442                 if (!MSG_IS_WATCH_THREAD(msginfo->flags) && procmsg_msg_has_flagged_parent(msginfo, MSG_WATCH_THREAD)) {
2443                         procmsg_msginfo_set_flags(msginfo, MSG_WATCH_THREAD, 0);
2444                 }
2445                 if(prefs_common.thread_by_subject && !msginfo->inreplyto &&
2446                         !msginfo->references && !MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2447                         (parent_msginfo = subject_table_lookup(subject_table, msginfo->subject)))
2448                 {
2449                         if(MSG_IS_IGNORE_THREAD(parent_msginfo->flags)) {
2450                                 procmsg_msginfo_change_flags(msginfo, MSG_IGNORE_THREAD, 0,
2451                                                 MSG_NEW | MSG_UNREAD, 0);
2452                         }
2453                 }
2454                 if ((folder_has_parent_of_type(item, F_OUTBOX) ||
2455                      folder_has_parent_of_type(item, F_QUEUE)  ||
2456                      folder_has_parent_of_type(item, F_TRASH)) &&
2457                     (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
2458                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
2459                 if (MSG_IS_NEW(msginfo->flags))
2460                         newcnt++;
2461                 if (MSG_IS_UNREAD(msginfo->flags))
2462                         unreadcnt++;
2463                 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
2464                         unreadmarkedcnt++;
2465                 if (MSG_IS_MARKED(msginfo->flags))
2466                         markedcnt++;
2467                 if (MSG_IS_REPLIED(msginfo->flags))
2468                         repliedcnt++;
2469                 if (MSG_IS_FORWARDED(msginfo->flags))
2470                         forwardedcnt++;
2471                 if (MSG_IS_LOCKED(msginfo->flags))
2472                         lockedcnt++;
2473                 if (MSG_IS_IGNORE_THREAD(msginfo->flags))
2474                         ignoredcnt++;
2475                 if (MSG_IS_WATCH_THREAD(msginfo->flags))
2476                         watchedcnt++;
2477
2478                 totalcnt++;
2479
2480                 procmsg_msginfo_free(&msginfo);
2481         }
2482         folder_item_set_batch(item, FALSE);
2483         g_slist_free(exists_list);
2484         
2485         if(prefs_common.thread_by_subject) {
2486                 g_hash_table_destroy(subject_table);
2487         }
2488         
2489         if (item->new_msgs != newcnt || item->unread_msgs != unreadcnt
2490         ||  item->total_msgs != totalcnt || item->marked_msgs != markedcnt
2491         ||  item->unreadmarked_msgs != unreadmarkedcnt
2492         ||  item->replied_msgs != repliedcnt || item->forwarded_msgs != forwardedcnt
2493         ||  item->locked_msgs != lockedcnt || item->ignored_msgs != ignoredcnt
2494         ||  item->watched_msgs != watchedcnt) {
2495                 update_flags |= F_ITEM_UPDATE_CONTENT;
2496         }
2497
2498         item->new_msgs = newcnt;
2499         item->unread_msgs = unreadcnt;
2500         item->total_msgs = totalcnt;
2501         item->unreadmarked_msgs = unreadmarkedcnt;
2502         item->marked_msgs = markedcnt;
2503         item->replied_msgs = repliedcnt;
2504         item->forwarded_msgs = forwardedcnt;
2505         item->locked_msgs = lockedcnt;
2506         item->ignored_msgs = ignoredcnt;
2507         item->watched_msgs = watchedcnt;
2508
2509         update_flags |= F_ITEM_UPDATE_MSGCNT;
2510
2511         folder_item_update(item, update_flags);
2512         folder_item_update_thaw();
2513         
2514         item->scanning = ITEM_NOT_SCANNING;
2515
2516         return 0;
2517 }
2518
2519 gint folder_item_scan(FolderItem *item)
2520 {
2521         return folder_item_scan_full(item, TRUE);
2522 }
2523
2524 static void folder_count_total_cache_memusage(FolderItem *item, gpointer data)
2525 {
2526         gint *memusage = (gint *)data;
2527
2528         if (item->cache == NULL)
2529                 return;
2530         
2531         *memusage += msgcache_get_memory_usage(item->cache);
2532 }
2533
2534 static gint folder_cache_time_compare_func(gconstpointer a, gconstpointer b)
2535 {
2536         FolderItem *fa = (FolderItem *)a;
2537         FolderItem *fb = (FolderItem *)b;
2538         
2539         return (gint) (msgcache_get_last_access_time(fa->cache) - msgcache_get_last_access_time(fb->cache));
2540 }
2541
2542 static void folder_find_expired_caches(FolderItem *item, gpointer data)
2543 {
2544         GSList **folder_item_list = (GSList **)data;
2545         gint difftime, expiretime;
2546         
2547         if (item->cache == NULL)
2548                 return;
2549
2550         if (item->opened > 0)
2551                 return;
2552
2553         difftime = (gint) (time(NULL) - msgcache_get_last_access_time(item->cache));
2554         expiretime = prefs_common.cache_min_keep_time * 60;
2555         debug_print("Cache unused time: %d (Expire time: %d)\n", difftime, expiretime);
2556
2557         if (difftime > expiretime && !item->opened && !item->processing_pending) {
2558                 *folder_item_list = g_slist_insert_sorted(*folder_item_list, item, folder_cache_time_compare_func);
2559         }
2560 }
2561
2562 gboolean folder_item_free_cache(FolderItem *item, gboolean force)
2563 {
2564         cm_return_val_if_fail(item != NULL, TRUE);
2565         
2566         if (item->cache == NULL)
2567                 return TRUE;
2568         
2569         if (item->opened > 0 && !force)
2570                 return FALSE;
2571
2572         folder_item_write_cache(item);
2573         msgcache_destroy(item->cache);
2574         item->cache = NULL;
2575         return TRUE;
2576 }
2577
2578 void folder_clean_cache_memory_force(void)
2579 {
2580         int old_cache_max_mem_usage = prefs_common.cache_max_mem_usage;
2581         int old_cache_min_keep_time = prefs_common.cache_min_keep_time;
2582
2583         prefs_common.cache_max_mem_usage = 0;
2584         prefs_common.cache_min_keep_time = 0;
2585
2586         folder_clean_cache_memory(NULL);
2587
2588         prefs_common.cache_max_mem_usage = old_cache_max_mem_usage;
2589         prefs_common.cache_min_keep_time = old_cache_min_keep_time;
2590 }
2591
2592 void folder_clean_cache_memory(FolderItem *protected_item)
2593 {
2594         gint memusage = 0;
2595
2596         folder_func_to_all_folders(folder_count_total_cache_memusage, &memusage);       
2597         debug_print("Total cache memory usage: %d\n", memusage);
2598         
2599         if (memusage > (prefs_common.cache_max_mem_usage * 1024)) {
2600                 GSList *folder_item_list = NULL, *listitem;
2601                 
2602                 debug_print("Trying to free cache memory\n");
2603
2604                 folder_func_to_all_folders(folder_find_expired_caches, &folder_item_list);      
2605                 listitem = folder_item_list;
2606                 while((listitem != NULL) && (memusage > (prefs_common.cache_max_mem_usage * 1024))) {
2607                         FolderItem *item = (FolderItem *)(listitem->data);
2608                         gint cache_size = 0;
2609                         if (item == protected_item) {
2610                                 listitem = listitem->next;
2611                                 continue;
2612                         }
2613                         debug_print("Freeing cache memory for %s\n", item->path ? item->path : item->name);
2614                         cache_size = msgcache_get_memory_usage(item->cache);
2615                         if (folder_item_free_cache(item, FALSE))
2616                                 memusage -= cache_size;
2617
2618                         listitem = listitem->next;
2619                 }
2620                 g_slist_free(folder_item_list);
2621         }
2622 }
2623
2624 static void folder_item_remove_cached_msg(FolderItem *item, MsgInfo *msginfo)
2625 {
2626         Folder *folder = item->folder;
2627
2628         cm_return_if_fail(folder != NULL);
2629
2630         if (folder->klass->remove_cached_msg == NULL)
2631                 return;
2632         
2633         folder->klass->remove_cached_msg(folder, item, msginfo);
2634 }
2635
2636 static void folder_item_clean_local_files(FolderItem *item, gint days)
2637 {
2638         cm_return_if_fail(item != NULL);
2639         cm_return_if_fail(item->folder != NULL);
2640
2641         if (FOLDER_TYPE(item->folder) == F_IMAP ||
2642             FOLDER_TYPE(item->folder) == F_NEWS) {
2643                 GSList *msglist = folder_item_get_msg_list(item);
2644                 GSList *cur;
2645                 time_t t = time(NULL);
2646                 for (cur = msglist; cur; cur = cur->next) {
2647                         MsgInfo *msginfo = (MsgInfo *)cur->data;
2648                         gint age = (t - msginfo->date_t) / (60*60*24);
2649                         if (age > days)
2650                                 folder_item_remove_cached_msg(item, msginfo);
2651                 }
2652                 procmsg_msg_list_free(msglist);
2653         }
2654 }
2655
2656 static void folder_item_read_cache(FolderItem *item)
2657 {
2658         gchar *cache_file, *mark_file, *tags_file;
2659         START_TIMING("");
2660         cm_return_if_fail(item != NULL);
2661
2662         if (item->path != NULL) {
2663                 cache_file = folder_item_get_cache_file(item);
2664                 mark_file = folder_item_get_mark_file(item);
2665                 tags_file = folder_item_get_tags_file(item);
2666                 item->cache = msgcache_read_cache(item, cache_file);
2667                 item->cache_dirty = FALSE;
2668                 item->mark_dirty = FALSE;
2669                 item->tags_dirty = FALSE;
2670                 if (!item->cache) {
2671                         MsgInfoList *list, *cur;
2672                         guint newcnt = 0, unreadcnt = 0;
2673                         guint markedcnt = 0, unreadmarkedcnt = 0;
2674                         guint repliedcnt = 0, forwardedcnt = 0;
2675                         guint lockedcnt = 0, ignoredcnt = 0;
2676                         guint watchedcnt = 0;
2677                         MsgInfo *msginfo;
2678
2679                         item->cache = msgcache_new();
2680                         item->cache_dirty = TRUE;
2681                         item->mark_dirty = TRUE;
2682                         item->tags_dirty = TRUE;
2683                         folder_item_scan_full(item, TRUE);
2684
2685                         msgcache_read_mark(item->cache, mark_file);
2686
2687                         list = msgcache_get_msg_list(item->cache);
2688                         for (cur = list; cur != NULL; cur = g_slist_next(cur)) {
2689                                 msginfo = cur->data;
2690
2691                                 if (MSG_IS_NEW(msginfo->flags))
2692                                         newcnt++;
2693                                 if (MSG_IS_UNREAD(msginfo->flags))
2694                                         unreadcnt++;
2695                                 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
2696                                         unreadmarkedcnt++;
2697                                 if (MSG_IS_MARKED(msginfo->flags))
2698                                         markedcnt++;
2699                                 if (MSG_IS_REPLIED(msginfo->flags))
2700                                         repliedcnt++;
2701                                 if (MSG_IS_FORWARDED(msginfo->flags))
2702                                         forwardedcnt++;
2703                                 if (MSG_IS_LOCKED(msginfo->flags))
2704                                         lockedcnt++;
2705                                 if (MSG_IS_IGNORE_THREAD(msginfo->flags))
2706                                         ignoredcnt++;
2707                                 if (MSG_IS_WATCH_THREAD(msginfo->flags))
2708                                         watchedcnt++;
2709                                 procmsg_msginfo_unset_flags(msginfo, MSG_FULLY_CACHED, 0);
2710                         }
2711                         item->new_msgs = newcnt;
2712                         item->unread_msgs = unreadcnt;
2713                         item->unreadmarked_msgs = unreadmarkedcnt;
2714                         item->marked_msgs = markedcnt;
2715                         item->replied_msgs = repliedcnt;
2716                         item->forwarded_msgs = forwardedcnt;
2717                         item->locked_msgs = lockedcnt;
2718                         item->ignored_msgs = ignoredcnt;
2719                         item->watched_msgs = watchedcnt;
2720                         procmsg_msg_list_free(list);
2721                 } else
2722                         msgcache_read_mark(item->cache, mark_file);
2723
2724                 msgcache_read_tags(item->cache, tags_file);
2725
2726                 g_free(cache_file);
2727                 g_free(mark_file);
2728                 g_free(tags_file);
2729         } else {
2730                 item->cache = msgcache_new();
2731                 item->cache_dirty = TRUE;
2732                 item->mark_dirty = TRUE;
2733                 item->tags_dirty = TRUE;
2734         }
2735
2736         END_TIMING();
2737         folder_clean_cache_memory(item);
2738 }
2739
2740 void folder_item_write_cache(FolderItem *item)
2741 {
2742         gchar *cache_file = NULL, *mark_file = NULL, *tags_file = NULL;
2743         FolderItemPrefs *prefs;
2744         gint filemode = 0;
2745         gchar *id;
2746         time_t last_mtime = (time_t)0;
2747         gboolean need_scan = FALSE;
2748         
2749         if (!item || !item->path || !item->cache)
2750                 return;
2751
2752         last_mtime = item->mtime;
2753         if (item->folder->klass->set_mtime) {
2754                 if (item->folder->klass->scan_required)
2755                         need_scan = item->folder->klass->scan_required(item->folder, item);
2756                 else
2757                         need_scan = TRUE;
2758         }
2759
2760         id = folder_item_get_identifier(item);
2761         debug_print("Save cache for folder %s\n", id);
2762         g_free(id);
2763
2764         if (item->cache_dirty)
2765                 cache_file = folder_item_get_cache_file(item);
2766         if (item->cache_dirty || item->mark_dirty)
2767                 mark_file = folder_item_get_mark_file(item);
2768         if (item->cache_dirty || item->tags_dirty)
2769                 tags_file = folder_item_get_tags_file(item);
2770         if (msgcache_write(cache_file, mark_file, tags_file, item->cache) < 0) {
2771                 prefs = item->prefs;
2772                 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
2773                         /* for cache file */
2774                         filemode = prefs->folder_chmod;
2775                         if (filemode & S_IRGRP) filemode |= S_IWGRP;
2776                         if (filemode & S_IROTH) filemode |= S_IWOTH;
2777                         if (cache_file != NULL)
2778                                 chmod(cache_file, filemode);
2779                 }
2780         } else {
2781                 item->cache_dirty = FALSE;
2782                 item->mark_dirty = FALSE;
2783                 item->tags_dirty = FALSE;
2784         }
2785
2786         if (!need_scan && item->folder->klass->set_mtime) {
2787                 if (item->mtime == last_mtime) {
2788                         item->folder->klass->set_mtime(item->folder, item);
2789                 }
2790         }
2791
2792         g_free(cache_file);
2793         g_free(mark_file);
2794         g_free(tags_file);
2795 }
2796
2797 MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num)
2798 {
2799         MsgInfo *msginfo = NULL;
2800         
2801         cm_return_val_if_fail(item != NULL, NULL);
2802         if (item->no_select)
2803                 return NULL;
2804         if (!item->cache)
2805                 folder_item_read_cache(item);
2806         
2807         if ((msginfo = msgcache_get_msg(item->cache, num)) != NULL)
2808                 return msginfo;
2809         
2810         msginfo = get_msginfo(item, num);
2811         if (msginfo != NULL) {
2812                 msgcache_add_msg(item->cache, msginfo);
2813                 return msginfo;
2814         }
2815         
2816         return NULL;
2817 }
2818
2819 MsgInfo *folder_item_get_msginfo_by_msgid(FolderItem *item, const gchar *msgid)
2820 {
2821         MsgInfo *msginfo;
2822         
2823         cm_return_val_if_fail(item != NULL, NULL);
2824         cm_return_val_if_fail(msgid != NULL, NULL);
2825         if (item->no_select)
2826                 return NULL;
2827         
2828         if (!item->cache)
2829                 folder_item_read_cache(item);
2830         
2831         if ((msginfo = msgcache_get_msg_by_id(item->cache, msgid)) != NULL)
2832                 return msginfo;
2833
2834         return NULL;
2835 }
2836
2837 GSList *folder_item_get_msg_list(FolderItem *item)
2838 {
2839         cm_return_val_if_fail(item != NULL, NULL);
2840         if (item->no_select)
2841                 return NULL;
2842         
2843         if (item->cache == 0)
2844                 folder_item_read_cache(item);
2845
2846         cm_return_val_if_fail(item->cache != NULL, NULL);
2847         
2848         return msgcache_get_msg_list(item->cache);
2849 }
2850
2851 static void msginfo_set_mime_flags(GNode *node, gpointer data)
2852 {
2853         MsgInfo *msginfo = data;
2854         MimeInfo *mimeinfo = node->data;
2855         
2856         if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT
2857          && (!mimeinfo->subtype || (strcmp(mimeinfo->subtype, "pgp-signature") &&
2858              strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2859              strcmp(mimeinfo->subtype, "pkcs7-signature")))) {
2860                 procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2861         } else if (mimeinfo->disposition == DISPOSITIONTYPE_UNKNOWN && 
2862                  mimeinfo->id == NULL &&
2863                  mimeinfo->type != MIMETYPE_TEXT &&
2864                  mimeinfo->type != MIMETYPE_MULTIPART) {
2865                 if (!mimeinfo->subtype 
2866                 || (strcmp(mimeinfo->subtype, "pgp-signature") && 
2867                     strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2868                     strcmp(mimeinfo->subtype, "pkcs7-signature")))
2869                         procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2870         } else if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE &&
2871                  mimeinfo->id == NULL &&
2872                 (strcmp(mimeinfo->subtype, "pgp-signature") &&
2873                  strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2874                  strcmp(mimeinfo->subtype, "pkcs7-signature")) && 
2875                 (procmime_mimeinfo_get_parameter(mimeinfo, "name") != NULL ||
2876                  procmime_mimeinfo_get_parameter(mimeinfo, "filename") != NULL)) {
2877                 procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2878         } 
2879
2880         /* don't descend below top level message for signed and encrypted info */
2881         if (mimeinfo->type == MIMETYPE_MESSAGE)
2882                 return;
2883
2884         if (privacy_mimeinfo_is_signed(mimeinfo)) {
2885                 procmsg_msginfo_set_flags(msginfo, 0, MSG_SIGNED);
2886         }
2887
2888         if (privacy_mimeinfo_is_encrypted(mimeinfo)) {
2889                 procmsg_msginfo_set_flags(msginfo, 0, MSG_ENCRYPTED);
2890         } else {
2891                 /* searching inside encrypted parts doesn't really make sense */
2892                 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2893         }
2894 }
2895
2896 gchar *folder_item_fetch_msg(FolderItem *item, gint num)
2897 {
2898         Folder *folder;
2899         gchar *msgfile;
2900         MsgInfo *msginfo;
2901
2902         cm_return_val_if_fail(item != NULL, NULL);
2903
2904         folder = item->folder;
2905
2906         cm_return_val_if_fail(folder->klass->fetch_msg != NULL, NULL);
2907         if (item->no_select)
2908                 return NULL;
2909
2910         msgfile = folder->klass->fetch_msg(folder, item, num);
2911
2912         if (msgfile != NULL) {
2913                 msginfo = folder_item_get_msginfo(item, num);
2914                 if ((msginfo != NULL) && !MSG_IS_SCANNED(msginfo->flags)) {
2915                         MimeInfo *mimeinfo;
2916
2917                         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) && 
2918                             !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
2919                                 mimeinfo = procmime_scan_file(msgfile);
2920                         else
2921                                 mimeinfo = procmime_scan_queue_file(msgfile);
2922                         /* check for attachments */
2923                         if (mimeinfo != NULL) { 
2924                                 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2925                                 procmime_mimeinfo_free_all(&mimeinfo);
2926
2927                                 procmsg_msginfo_set_flags(msginfo, 0, MSG_SCANNED);
2928                         }
2929                 }
2930                 procmsg_msginfo_free(&msginfo);
2931         }
2932
2933         return msgfile;
2934 }
2935
2936 gchar *folder_item_fetch_msg_full(FolderItem *item, gint num, gboolean headers,
2937                                   gboolean body)
2938 {
2939         Folder *folder;
2940         gchar *msgfile;
2941         MsgInfo *msginfo;
2942
2943         cm_return_val_if_fail(item != NULL, NULL);
2944         if (item->no_select)
2945                 return NULL;
2946         
2947         folder = item->folder;
2948
2949         if (folder->klass->fetch_msg_full == NULL)
2950                 return folder_item_fetch_msg(item, num);
2951
2952         if (item->prefs->offlinesync && prefs_common.real_time_sync)
2953                 msgfile = folder->klass->fetch_msg_full(folder, item, num, 
2954                                                 TRUE, TRUE);
2955         else
2956                 msgfile = folder->klass->fetch_msg_full(folder, item, num, 
2957                                                 headers, body);
2958
2959         if (msgfile != NULL) {
2960                 msginfo = folder_item_get_msginfo(item, num);
2961                 if ((msginfo != NULL) && !MSG_IS_SCANNED(msginfo->flags)) {
2962                         MimeInfo *mimeinfo;
2963
2964                         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2965                             !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
2966                                 mimeinfo = procmime_scan_file(msgfile);
2967                         else
2968                                 mimeinfo = procmime_scan_queue_file(msgfile);
2969                         /* check for attachments */
2970                         if (mimeinfo != NULL) { 
2971                                 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2972                                 procmime_mimeinfo_free_all(&mimeinfo);
2973
2974                                 procmsg_msginfo_set_flags(msginfo, 0, MSG_SCANNED);
2975                         }
2976                 }
2977                 procmsg_msginfo_free(&msginfo);
2978         }
2979
2980         return msgfile;
2981 }
2982
2983
2984 static gint folder_item_get_msg_num_by_file(FolderItem *dest, const gchar *file)
2985 {
2986         static HeaderEntry hentry[] = {{"Message-ID:",  NULL, TRUE},
2987                                        {NULL,           NULL, FALSE}};
2988         FILE *fp;
2989         MsgInfo *msginfo;
2990         gint msgnum = 0;
2991         gchar buf[BUFFSIZE];
2992
2993         if ((fp = g_fopen(file, "rb")) == NULL)
2994                 return 0;
2995
2996         if ((folder_has_parent_of_type(dest, F_QUEUE)) || 
2997             (folder_has_parent_of_type(dest, F_DRAFT)))
2998                 while (fgets(buf, sizeof(buf), fp) != NULL) {
2999                         /* new way */
3000                         if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
3001                                 strlen("X-Claws-End-Special-Headers:"))) ||
3002                             (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
3003                                 strlen("X-Sylpheed-End-Special-Headers:"))))
3004                                 break;
3005                         /* old way */
3006                         if (buf[0] == '\r' || buf[0] == '\n') break;
3007                         /* from other mailers */
3008                         if (!strncmp(buf, "Date: ", 6)
3009                         ||  !strncmp(buf, "To: ", 4)
3010                         ||  !strncmp(buf, "From: ", 6)
3011                         ||  !strncmp(buf, "Subject: ", 9)) {
3012                                 rewind(fp);
3013                                 break;
3014                         }
3015                 }
3016
3017         procheader_get_header_fields(fp, hentry);
3018         debug_print("looking for %s\n", hentry[0].body);
3019         if (hentry[0].body) {
3020                 extract_parenthesis(hentry[0].body, '<', '>');
3021                 remove_space(hentry[0].body);
3022                 if ((msginfo = msgcache_get_msg_by_id(dest->cache, hentry[0].body)) != NULL) {
3023                         msgnum = msginfo->msgnum;
3024                         procmsg_msginfo_free(&msginfo);
3025
3026                         debug_print("found message as uid %d\n", msgnum);
3027                 }
3028         }
3029         
3030         g_free(hentry[0].body);
3031         hentry[0].body = NULL;
3032         fclose(fp);
3033
3034         return msgnum;
3035 }
3036
3037 static void copy_msginfo_flags(MsgInfo *source, MsgInfo *dest)
3038 {
3039         MsgPermFlags perm_flags = 0;
3040         MsgTmpFlags tmp_flags = 0;
3041
3042         /* create new flags */
3043         if (source != NULL) {
3044                 /* copy original flags */
3045                 perm_flags = source->flags.perm_flags;
3046                 tmp_flags = source->flags.tmp_flags;
3047         } else {
3048                 perm_flags = dest->flags.perm_flags;
3049                 tmp_flags = dest->flags.tmp_flags;
3050         }
3051
3052         /* remove new, unread and deleted in special folders */
3053         if (folder_has_parent_of_type(dest->folder, F_OUTBOX) || 
3054             folder_has_parent_of_type(dest->folder, F_QUEUE) || 
3055             folder_has_parent_of_type(dest->folder, F_DRAFT) || 
3056             folder_has_parent_of_type(dest->folder, F_TRASH))
3057                 perm_flags &= ~(MSG_NEW | MSG_UNREAD | MSG_DELETED);
3058
3059         /* set ignore flag of ignored parent exists */
3060         if (procmsg_msg_has_flagged_parent(dest, MSG_IGNORE_THREAD))
3061                 perm_flags |= MSG_IGNORE_THREAD;
3062
3063         /* unset FULLY_CACHED flags */
3064         perm_flags &= ~MSG_FULLY_CACHED;
3065
3066         if (procmsg_msg_has_flagged_parent(dest, MSG_WATCH_THREAD))
3067                 perm_flags |= MSG_WATCH_THREAD;
3068
3069         /* Unset tmp flags that should not be copied */
3070         tmp_flags &= ~(MSG_MOVE | MSG_COPY | MSG_MOVE_DONE);
3071
3072         /* unset flags that are set but should not */
3073         /* and set new flags */
3074         procmsg_msginfo_change_flags(dest,
3075                                   ~dest->flags.perm_flags & perm_flags,
3076                                   ~dest->flags.tmp_flags  & tmp_flags,
3077                                    dest->flags.perm_flags & ~perm_flags,
3078                                    dest->flags.tmp_flags  & ~tmp_flags);
3079         
3080         if (source && source->tags) {
3081                 g_slist_free(dest->tags);
3082                 dest->tags = g_slist_copy(source->tags);
3083                 folder_item_commit_tags(dest->folder, dest, dest->tags, NULL);
3084         }
3085 }
3086
3087 static void add_msginfo_to_cache(FolderItem *item, MsgInfo *newmsginfo, MsgInfo *flagsource)
3088 {
3089         /* update folder stats */
3090         if (MSG_IS_NEW(newmsginfo->flags))
3091                 item->new_msgs++;
3092         if (MSG_IS_UNREAD(newmsginfo->flags))
3093                 item->unread_msgs++;
3094         if (MSG_IS_UNREAD(newmsginfo->flags) && procmsg_msg_has_marked_parent(newmsginfo))
3095                 item->unreadmarked_msgs++;
3096         if (MSG_IS_MARKED(newmsginfo->flags))
3097                 item->marked_msgs++;
3098         if (MSG_IS_REPLIED(newmsginfo->flags))
3099                 item->replied_msgs++;
3100         if (MSG_IS_FORWARDED(newmsginfo->flags))
3101                 item->forwarded_msgs++;
3102         if (MSG_IS_LOCKED(newmsginfo->flags))
3103                 item->locked_msgs++;
3104         if (MSG_IS_IGNORE_THREAD(newmsginfo->flags))
3105                 item->ignored_msgs++;
3106         if (MSG_IS_WATCH_THREAD(newmsginfo->flags))
3107                 item->watched_msgs++;
3108         item->total_msgs++;
3109
3110         folder_item_update_freeze();
3111
3112         if (!item->cache)
3113                 folder_item_read_cache(item);
3114
3115         msgcache_add_msg(item->cache, newmsginfo);
3116         copy_msginfo_flags(flagsource, newmsginfo);
3117         folder_item_update_with_msg(item,  F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_ADDMSG, newmsginfo);
3118         folder_item_update_thaw();
3119 }
3120
3121 static void remove_msginfo_from_cache(FolderItem *item, MsgInfo *msginfo)
3122 {
3123         MsgInfoUpdate msginfo_update;
3124
3125         if (!item->cache)
3126                 folder_item_read_cache(item);
3127
3128         if (MSG_IS_NEW(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3129                 msginfo->folder->new_msgs--;
3130         if (MSG_IS_UNREAD(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3131                 msginfo->folder->unread_msgs--;
3132         if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
3133                 msginfo->folder->unreadmarked_msgs--;
3134         if (MSG_IS_MARKED(msginfo->flags))
3135                 item->marked_msgs--;
3136         if (MSG_IS_REPLIED(msginfo->flags))
3137                 item->replied_msgs--;
3138         if (MSG_IS_FORWARDED(msginfo->flags))
3139                 item->forwarded_msgs--;
3140         if (MSG_IS_LOCKED(msginfo->flags))
3141                 item->locked_msgs--;
3142         if (MSG_IS_IGNORE_THREAD(msginfo->flags))
3143                 item->ignored_msgs--;
3144         if (MSG_IS_WATCH_THREAD(msginfo->flags))
3145                 item->watched_msgs--;
3146
3147         msginfo->folder->total_msgs--;
3148
3149         msginfo_update.msginfo = msginfo;
3150         msginfo_update.flags = MSGINFO_UPDATE_DELETED;
3151         hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
3152
3153         msgcache_remove_msg(item->cache, msginfo->msgnum);
3154         folder_item_update_with_msg(msginfo->folder, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_REMOVEMSG, msginfo);
3155 }
3156
3157 gint folder_item_add_msg(FolderItem *dest, const gchar *file,
3158                          MsgFlags *flags, gboolean remove_source)
3159 {
3160         GSList file_list;
3161         MsgFileInfo fileinfo;
3162
3163         cm_return_val_if_fail(dest != NULL, -1);
3164         cm_return_val_if_fail(file != NULL, -1);
3165  
3166         fileinfo.msginfo = NULL;
3167         fileinfo.file = (gchar *)file;
3168         fileinfo.flags = flags;
3169         file_list.data = &fileinfo;
3170         file_list.next = NULL;
3171
3172         return folder_item_add_msgs(dest, &file_list, remove_source);
3173 }
3174
3175 gint folder_item_add_msgs(FolderItem *dest, GSList *file_list,
3176                           gboolean remove_source)
3177 {
3178         Folder *folder;
3179         gint ret, num, lastnum = -1;
3180         GSList *file_cur;
3181         GHashTable *relation;
3182         MsgFileInfo *fileinfo = NULL;
3183         gboolean folderscan = FALSE;
3184
3185         cm_return_val_if_fail(dest != NULL, -1);
3186         cm_return_val_if_fail(file_list != NULL, -1);
3187         cm_return_val_if_fail(dest->folder != NULL, -1);
3188         if (dest->no_select)
3189                 return -1;
3190
3191         folder = dest->folder;
3192
3193         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3194
3195         if (folder->klass->add_msgs != NULL) {
3196                 ret = folder->klass->add_msgs(folder, dest, file_list, relation);
3197                 if (ret < 0) {
3198                         g_hash_table_destroy(relation);
3199                         return ret;
3200                 }
3201         } else {
3202                 for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3203                         fileinfo = (MsgFileInfo *) file_cur->data;
3204
3205                         ret = folder->klass->add_msg(folder, dest, fileinfo->file, fileinfo->flags);
3206                         if (ret < 0) {
3207                                 g_hash_table_destroy(relation);
3208                                 return ret;
3209                         }
3210                         g_hash_table_insert(relation, fileinfo, GINT_TO_POINTER(ret));
3211                 }
3212         }
3213
3214         for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3215                 gpointer data, old_key;
3216
3217                 fileinfo = (MsgFileInfo *) file_cur->data;
3218                 if (g_hash_table_lookup_extended(relation, fileinfo, &old_key, &data))
3219                         num = GPOINTER_TO_INT(data);
3220                 else
3221                         num = -1;
3222
3223                 if (num >= 0) {
3224                         MsgInfo *newmsginfo;
3225
3226                         if (num == 0) {
3227                                 if (!folderscan) {
3228                                         folder_item_scan_full(dest, FALSE);
3229                                         folderscan = TRUE;
3230                                 }
3231                                 num = folder_item_get_msg_num_by_file(dest, fileinfo->file);
3232                                 debug_print("got num %d\n", num);
3233                         }
3234
3235                         if (num > lastnum)
3236                                 lastnum = num;
3237
3238                         if (num >= 0 && remove_source) {
3239                                 if (claws_unlink(fileinfo->file) < 0)
3240                                         FILE_OP_ERROR(fileinfo->file, "unlink");
3241                         }
3242
3243                         if (num == 0)
3244                                 continue;
3245
3246                         if (!folderscan && 
3247                             ((newmsginfo = get_msginfo(dest, num)) != NULL)) {
3248                                 add_msginfo_to_cache(dest, newmsginfo, NULL);
3249                                 procmsg_msginfo_free(&newmsginfo);
3250                         } else if ((newmsginfo = msgcache_get_msg(dest->cache, num)) != NULL) {
3251                                 /* TODO: set default flags */
3252                                 procmsg_msginfo_free(&newmsginfo);
3253                         }
3254                 }
3255         }
3256
3257         g_hash_table_destroy(relation);
3258
3259         return lastnum;
3260 }
3261
3262 static FolderItem *folder_item_move_recursive(FolderItem *src, FolderItem *dest, gboolean copy) 
3263 {
3264         GSList *mlist;
3265         FolderItem *new_item;
3266         FolderItem *next_item;
3267         GNode *srcnode;
3268         gchar *old_id, *new_id;
3269         FolderUpdateData hookdata;
3270
3271         /* move messages */
3272         debug_print("%s %s to %s\n", copy?"Copying":"Moving", src->path, dest->path);
3273         new_item = folder_create_folder(dest, src->name);
3274         if (new_item == NULL) {
3275                 g_print("Can't create folder\n");
3276                 return NULL;
3277         }
3278         
3279         if (new_item->folder == NULL)
3280                 new_item->folder = dest->folder;
3281
3282         /* move messages */
3283         log_message(LOG_PROTOCOL, copy ?_("Copying %s to %s...\n"):_("Moving %s to %s...\n"), 
3284                         src->name, new_item->path);
3285
3286         /*copy prefs*/
3287         folder_item_prefs_copy_prefs(src, new_item);
3288         
3289         /* copy internal data */
3290         if (src->folder->klass == new_item->folder->klass &&
3291             src->folder->klass->copy_private_data != NULL)
3292                 src->folder->klass->copy_private_data(src->folder,
3293                                         src, new_item);
3294         new_item->collapsed = src->collapsed;
3295         new_item->thread_collapsed = src->thread_collapsed;
3296         new_item->threaded  = src->threaded;
3297         new_item->ret_rcpt  = src->ret_rcpt;
3298         new_item->hide_read_msgs = src->hide_read_msgs;
3299         new_item->hide_del_msgs = src->hide_del_msgs;
3300         new_item->hide_read_threads = src->hide_read_threads;
3301         new_item->sort_key  = src->sort_key;
3302         new_item->sort_type = src->sort_type;
3303
3304         mlist = folder_item_get_msg_list(src);
3305
3306         if (mlist != NULL) {
3307                 if (copy)
3308                         folder_item_copy_msgs(new_item, mlist);
3309                 else
3310                         folder_item_move_msgs(new_item, mlist);
3311                 procmsg_msg_list_free(mlist);
3312         }
3313
3314         prefs_matcher_write_config();
3315         
3316         /* recurse */
3317         srcnode = src->folder->node;    
3318         srcnode = g_node_find(srcnode, G_PRE_ORDER, G_TRAVERSE_ALL, src);
3319         srcnode = srcnode->children;
3320         while (srcnode != NULL) {
3321                 if (srcnode && srcnode->data) {
3322                         next_item = (FolderItem*) srcnode->data;
3323                         srcnode = srcnode->next;
3324                         if (folder_item_move_recursive(next_item, new_item, copy) == NULL) {
3325                                 return NULL;
3326                         }
3327                 }
3328         }
3329         old_id = folder_item_get_identifier(src);
3330         new_id = folder_item_get_identifier(new_item);
3331
3332         hookdata.folder = src->folder;
3333         hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_MOVE_FOLDERITEM;
3334         hookdata.item = src;
3335         hookdata.item2 = new_item;
3336         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
3337
3338         /* if src supports removing, otherwise only copy folder */
3339         if (src->folder->klass->remove_folder != NULL && !copy) 
3340                 src->folder->klass->remove_folder(src->folder, src);
3341         folder_write_list();
3342
3343         if (!copy) {
3344                 debug_print("updating rules : %s => %s\n", old_id, new_id);
3345                 if (old_id != NULL && new_id != NULL) {
3346                         prefs_filtering_rename_path(old_id, new_id);
3347                         account_rename_path(old_id, new_id);
3348                 }
3349         }
3350         g_free(old_id);
3351         g_free(new_id);
3352         
3353         return new_item;
3354 }
3355
3356 gint folder_item_move_to(FolderItem *src, FolderItem *dest, FolderItem **new_item, gboolean copy)
3357 {
3358         FolderItem *tmp = folder_item_parent(dest);
3359         gchar * src_identifier, * dst_identifier;
3360         gchar * phys_srcpath, * phys_dstpath, *tmppath;
3361
3362         while (tmp) {
3363                 if (tmp == src) {
3364                         return F_MOVE_FAILED_DEST_IS_CHILD;
3365                 }
3366                 tmp = folder_item_parent(tmp);
3367         }
3368
3369         /* both dst and src can be root folders */
3370         src_identifier = folder_item_get_identifier(src);
3371         if (src_identifier == NULL && src->folder && folder_item_parent(src) == NULL) {
3372                 src_identifier = folder_get_identifier(src->folder);
3373         }
3374
3375         dst_identifier = folder_item_get_identifier(dest);
3376         if(dst_identifier == NULL && dest->folder && folder_item_parent(dest) == NULL) {
3377                 dst_identifier = folder_get_identifier(dest->folder);
3378         }
3379
3380         if (src_identifier == NULL || dst_identifier == NULL) {
3381                 debug_print("Can't get identifiers\n");
3382                 return F_MOVE_FAILED;
3383         }
3384
3385         if (src->folder != dest->folder && !copy) {
3386                 return F_MOVE_FAILED_DEST_OUTSIDE_MAILBOX;
3387         }
3388
3389         phys_srcpath = folder_item_get_path(src);
3390         tmppath = folder_item_get_path(dest);
3391         phys_dstpath = g_strconcat(tmppath,
3392                        G_DIR_SEPARATOR_S,
3393                        g_path_get_basename(phys_srcpath),
3394                        NULL);
3395         g_free(tmppath);
3396
3397         if (folder_item_parent(src) == dest || src == dest) {
3398                 g_free(src_identifier);
3399                 g_free(dst_identifier);
3400                 g_free(phys_srcpath);
3401                 g_free(phys_dstpath);
3402                 return F_MOVE_FAILED_DEST_IS_PARENT;
3403         }
3404         debug_print("moving \"%s\" to \"%s\"\n", phys_srcpath, phys_dstpath);
3405         if ((tmp = folder_item_move_recursive(src, dest, copy)) == NULL) {
3406                 return F_MOVE_FAILED;
3407         }
3408         
3409         g_free(src_identifier);
3410         g_free(dst_identifier);
3411         g_free(phys_srcpath);
3412         g_free(phys_dstpath);
3413
3414         *new_item = tmp;
3415
3416         return F_MOVE_OK;
3417 }
3418
3419 struct find_data
3420 {
3421         gboolean found;
3422 };      
3423 static void find_num(gpointer key, gpointer value, gpointer data)
3424 {
3425         struct find_data *fdata = (struct find_data *)data;
3426         if (GPOINTER_TO_INT(value) == 0)
3427                 fdata->found = TRUE;
3428 }
3429
3430 static gboolean some_msgs_have_zero_num(GHashTable *hashtable)
3431 {
3432         struct find_data fdata;
3433         
3434         fdata.found = FALSE;
3435         g_hash_table_foreach(hashtable, find_num, &fdata);
3436         
3437         return fdata.found;
3438 }
3439
3440 /**
3441  * Copy a list of message to a new folder and remove
3442  * source messages if wanted
3443  */
3444 static gint do_copy_msgs(FolderItem *dest, GSList *msglist, gboolean remove_source)
3445 {
3446         Folder *folder;
3447         GSList *l;
3448         gint num, lastnum = -1;
3449         gboolean folderscan = FALSE;
3450         GHashTable *relation;
3451         GSList *not_moved = NULL;
3452         gint total = 0, curmsg = 0;
3453         MsgInfo *msginfo = NULL;
3454
3455         cm_return_val_if_fail(dest != NULL, -1);
3456         cm_return_val_if_fail(msglist != NULL, -1);
3457
3458         folder = dest->folder;
3459
3460         cm_return_val_if_fail(folder->klass->copy_msg != NULL, -1);
3461         if (dest->no_select)
3462                 return -1;
3463
3464         msginfo = (MsgInfo *)msglist->data;
3465         
3466         if (!msginfo)
3467                 return -1;
3468         
3469         if (!MSG_IS_QUEUED(msginfo->flags) && 
3470             MSG_IS_DRAFT(msginfo->flags) && 
3471             folder_has_parent_of_type(dest, F_QUEUE)) {
3472                 GSList *cur = msglist;
3473                 gboolean queue_err = FALSE;
3474                 for (; cur; cur = cur->next) {
3475                         Compose *compose = NULL;
3476                         FolderItem *queue = dest;
3477                         int val = 0;
3478                         
3479                         msginfo = (MsgInfo *)cur->data;
3480                         compose = compose_reedit(msginfo, TRUE);
3481                         if (compose == NULL) {
3482                                 queue_err = TRUE;
3483                                 continue;
3484                         }
3485                         val = compose_queue(compose, NULL, &queue, NULL,
3486                                         FALSE);
3487                         if (val < 0) {
3488                                 queue_err = TRUE;
3489                         } else if (remove_source) {
3490                                 folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
3491                         }
3492                         if (val == 0)
3493                                 compose_close(compose);
3494                 }
3495                 return queue_err ? -1:0;
3496         }
3497
3498         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3499
3500         for (l = msglist ; l != NULL ; l = g_slist_next(l)) {
3501                 MsgInfo * msginfo = (MsgInfo *) l->data;
3502
3503                 if (msginfo->planned_download != 0) {
3504                         int old_planned = msginfo->planned_download;
3505                         partial_unmark(msginfo);
3506                         /* little hack to reenable after */
3507                         msginfo->planned_download = old_planned;
3508                 }
3509         }
3510
3511         /* 
3512          * Copy messages to destination folder and 
3513          * store new message numbers in newmsgnums
3514          */
3515         if (folder->klass->copy_msgs != NULL) {
3516                 if (folder->klass->copy_msgs(folder, dest, msglist, relation) < 0) {
3517                         g_hash_table_destroy(relation);
3518                         return -1;
3519                 }
3520         } else {
3521                 MsgInfo * msginfo;
3522                 l = msglist;
3523
3524                 /* immediately stop if src and dest folders are identical */
3525                 if (l != NULL) {
3526                         msginfo = (MsgInfo *) l->data;
3527                         if (msginfo != NULL && msginfo->folder == dest) {
3528                                 g_hash_table_destroy(relation);
3529                                 return -1;
3530                         }
3531                 }
3532
3533                 for (; l != NULL ; l = g_slist_next(l)) {
3534                         msginfo = (MsgInfo *) l->data;
3535
3536                         num = folder->klass->copy_msg(folder, dest, msginfo);
3537                         if (num > 0)
3538                                 g_hash_table_insert(relation, msginfo, GINT_TO_POINTER(num));
3539                         else
3540                                 not_moved = g_slist_prepend(not_moved, msginfo);
3541                 }
3542         }
3543
3544         if (remove_source) {
3545                 MsgInfo *msginfo = (MsgInfo *) msglist->data;
3546                 FolderItem *item = msginfo->folder;
3547                 /*
3548                  * Remove source messages from their folders if
3549                  * copying was successfull and update folder
3550                  * message counts
3551                  */
3552                 if (not_moved == NULL && item->folder->klass->remove_msgs) {
3553                         item->folder->klass->remove_msgs(item->folder,
3554                                                                 msginfo->folder,
3555                                                                 msglist,
3556                                                                 relation);
3557                 }
3558                 for (l = msglist; l != NULL; l = g_slist_next(l)) {
3559                         gpointer old_key, data;
3560                         msginfo = (MsgInfo *) l->data;
3561                         item = msginfo->folder;
3562
3563                         if (g_hash_table_lookup_extended(relation, msginfo, &old_key, &data))
3564                                 num = GPOINTER_TO_INT(data);
3565                         else
3566                                 num = 0;
3567
3568                         if (g_slist_find(not_moved, msginfo))
3569                                 continue;
3570
3571                         if ((num >= 0) && (item->folder->klass->remove_msg != NULL)) {
3572                                 if (!item->folder->klass->remove_msgs)
3573                                         item->folder->klass->remove_msg(item->folder,
3574                                                                 msginfo->folder,
3575                                                                 msginfo->msgnum);
3576                                 remove_msginfo_from_cache(item, msginfo);
3577                         }
3578                 }
3579         }
3580
3581         /* Read cache for dest folder */
3582         if (!dest->cache) folder_item_read_cache(dest);
3583
3584         /* 
3585          * Fetch new MsgInfos for new messages in dest folder,
3586          * add them to the msgcache and update folder message counts
3587          */
3588         if (some_msgs_have_zero_num(relation)) {
3589                 folder_item_scan_full(dest, FALSE);
3590                 folderscan = TRUE;
3591         }
3592
3593         statusbar_print_all(_("Updating cache for %s..."), dest->path ? dest->path : "(null)");
3594         total = g_slist_length(msglist);
3595         
3596         if (FOLDER_TYPE(dest->folder) == F_IMAP && total > 1) {
3597                 folder_item_scan_full(dest, FALSE);
3598                 folderscan = TRUE;
3599         }
3600         folder_item_set_batch(dest, TRUE);
3601         for (l = msglist; l != NULL; l = g_slist_next(l)) {
3602                 MsgInfo *msginfo = (MsgInfo *) l->data;
3603                 gpointer data, old_key;
3604
3605                 if (!msginfo)
3606                         continue;
3607
3608                 if (g_hash_table_lookup_extended(relation, msginfo, &old_key, &data))
3609                         num = GPOINTER_TO_INT(data);
3610                 else
3611                         num = 0;
3612
3613                 statusbar_progress_all(curmsg++,total, 100);
3614                 if (curmsg % 100 == 0)
3615                         GTK_EVENTS_FLUSH();
3616
3617                 if (num >= 0) {
3618                         MsgInfo *newmsginfo = NULL;
3619
3620                         if (!folderscan && num > 0) {
3621                                 newmsginfo = get_msginfo(dest, num);
3622                                 if (newmsginfo != NULL) {
3623                                         add_msginfo_to_cache(dest, newmsginfo, msginfo);
3624                                 }
3625                         }
3626                         if (newmsginfo == NULL) {
3627                                 if (!folderscan) {
3628                                         folder_item_scan_full(dest, FALSE);
3629                                         folderscan = TRUE;
3630                                 }
3631                                 if (msginfo->msgid != NULL) {
3632                                         newmsginfo = folder_item_get_msginfo_by_msgid(dest, msginfo->msgid);
3633                                         if (newmsginfo != NULL) {
3634                                                 copy_msginfo_flags(msginfo, newmsginfo);
3635                                                 num = newmsginfo->msgnum;
3636                                         }
3637                                 }
3638                         }
3639                         if (newmsginfo != NULL 
3640                          && msginfo->planned_download == POP3_PARTIAL_DLOAD_DELE) {
3641                                 partial_mark_for_delete(newmsginfo);
3642                         }
3643                         if (newmsginfo != NULL 
3644                          && msginfo->planned_download == POP3_PARTIAL_DLOAD_DLOAD) {
3645                                 partial_mark_for_download(newmsginfo);
3646                         }
3647                         if (!MSG_IS_POSTFILTERED (msginfo->flags)) {
3648                                 procmsg_msginfo_set_flags (   msginfo, MSG_POSTFILTERED, 0);
3649                                 if (newmsginfo) {
3650                                         procmsg_msginfo_set_flags (newmsginfo, MSG_POSTFILTERED, 0);
3651                                         hooks_invoke (MAIL_POSTFILTERING_HOOKLIST, newmsginfo);
3652                                 }
3653                         }
3654                         procmsg_msginfo_free(&newmsginfo);
3655
3656
3657                         if (num > lastnum)
3658                                 lastnum = num;
3659                 }
3660         }
3661         folder_item_set_batch(dest, FALSE);
3662         statusbar_progress_all(0,0,0);
3663         statusbar_pop_all();
3664
3665         g_hash_table_destroy(relation);
3666         if (not_moved != NULL) {
3667                 g_slist_free(not_moved);
3668                 return -1;
3669         } else
3670                 return lastnum;
3671 }
3672
3673 /**
3674  * Move a message to a new folder.
3675  *
3676  * \param dest Destination folder
3677  * \param msginfo The message
3678  */
3679 gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
3680 {
3681         GSList list;
3682
3683         cm_return_val_if_fail(dest != NULL, -1);
3684         cm_return_val_if_fail(msginfo != NULL, -1);
3685
3686         list.data = msginfo;
3687         list.next = NULL;
3688
3689         return do_copy_msgs(dest, &list, TRUE);
3690 }
3691
3692 /**
3693  * Move a list of messages to a new folder.
3694  *
3695  * \param dest Destination folder
3696  * \param msglist List of messages
3697  */
3698 gint folder_item_move_msgs(FolderItem *dest, GSList *msglist)
3699 {
3700         gint result = -1;
3701         cm_return_val_if_fail(dest != NULL, -1);
3702         cm_return_val_if_fail(msglist != NULL, -1);
3703         inc_lock();
3704         result = do_copy_msgs(dest, msglist, TRUE);
3705         inc_unlock();
3706         return result;
3707 }
3708
3709 /**
3710  * Copy a message to a new folder.
3711  *
3712  * \param dest Destination folder
3713  * \param msginfo The message
3714  */
3715 gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo)
3716 {
3717         GSList list;
3718
3719         cm_return_val_if_fail(dest != NULL, -1);
3720         cm_return_val_if_fail(msginfo != NULL, -1);
3721     
3722         list.data = msginfo;
3723         list.next = NULL;
3724
3725         return do_copy_msgs(dest, &list, FALSE);
3726 }
3727
3728 /**
3729  * Copy a list of messages to a new folder.
3730  *
3731  * \param dest Destination folder
3732  * \param msglist List of messages
3733  */
3734 gint folder_item_copy_msgs(FolderItem *dest, GSList *msglist)
3735 {
3736         gint result;
3737         cm_return_val_if_fail(dest != NULL, -1);
3738         cm_return_val_if_fail(msglist != NULL, -1);
3739
3740         inc_lock();
3741         result = do_copy_msgs(dest, msglist, FALSE);
3742         inc_unlock();
3743         
3744         return result;
3745 }
3746
3747 gint folder_item_remove_msg(FolderItem *item, gint num)
3748 {
3749         Folder *folder;
3750         gint ret;
3751         MsgInfo *msginfo;
3752
3753         cm_return_val_if_fail(item != NULL, -1);
3754         folder = item->folder;
3755         cm_return_val_if_fail(folder->klass->remove_msg != NULL, -1);
3756         if (item->no_select)
3757                 return -1;
3758
3759         if (!item->cache) folder_item_read_cache(item);
3760
3761         msginfo = msgcache_get_msg(item->cache, num);
3762         if (msginfo && MSG_IS_LOCKED(msginfo->flags)) {
3763                 procmsg_msginfo_free(&msginfo);
3764                 return -1;
3765         }
3766         ret = folder->klass->remove_msg(folder, item, num);
3767
3768         if (msginfo != NULL) {
3769                 if (ret == 0)
3770                         remove_msginfo_from_cache(item, msginfo);
3771                 procmsg_msginfo_free(&msginfo);
3772         }
3773
3774         return ret;
3775 }
3776
3777 gint folder_item_remove_msgs(FolderItem *item, GSList *msglist)
3778 {
3779         Folder *folder;
3780         gint ret = 0;
3781         GSList *real_list = NULL, *cur = NULL;
3782
3783         cm_return_val_if_fail(item != NULL, -1);
3784         folder = item->folder;
3785         cm_return_val_if_fail(folder != NULL, -1);
3786         if (item->no_select)
3787                 return -1;
3788         inc_lock();
3789         if (!item->cache) folder_item_read_cache(item);
3790
3791         folder_item_update_freeze();
3792         
3793         /* filter out locked mails */
3794         for (cur = msglist; cur; cur = cur->next) {
3795                 MsgInfo *info = (MsgInfo *)cur->data;
3796                 if (!MSG_IS_LOCKED(info->flags))
3797                         real_list = g_slist_prepend(real_list, info);
3798         }
3799
3800         real_list = g_slist_reverse(real_list);
3801
3802         if (item->folder->klass->remove_msgs) {
3803                 ret = item->folder->klass->remove_msgs(item->folder,
3804                                                         item,
3805                                                         real_list,
3806                                                         NULL);
3807         }
3808         cur = real_list;
3809         while (ret == 0 && cur != NULL) {
3810                 MsgInfo *msginfo = (MsgInfo *)cur->data;
3811                 if (msginfo && MSG_IS_LOCKED(msginfo->flags)) {
3812                         cur = cur->next;
3813                         continue;
3814                 }
3815                 if (!item->folder->klass->remove_msgs)
3816                         ret = folder_item_remove_msg(item, msginfo->msgnum);
3817                 if (ret != 0) break;
3818                 msgcache_remove_msg(item->cache, msginfo->msgnum);
3819                 cur = cur->next;
3820         }
3821         g_slist_free(real_list);
3822         folder_item_scan_full(item, FALSE);
3823         folder_item_update_thaw();
3824         inc_unlock();
3825         return ret;
3826 }
3827
3828 gint folder_item_expunge(FolderItem *item)
3829 {
3830         Folder *folder = item->folder;
3831         gint result = 0;
3832         if (folder == NULL)
3833                 return -1;
3834         if (folder->klass->expunge) {
3835                 GSList *msglist = folder_item_get_msg_list(item);
3836                 GSList *cur;
3837                 result = folder->klass->expunge(folder, item);
3838                 if (result == 0) {
3839                         for (cur = msglist; cur; cur = cur->next) {
3840                                 MsgInfo *msginfo = (MsgInfo *)cur->data;
3841                                 if (MSG_IS_DELETED(msginfo->flags)) {
3842                                         remove_msginfo_from_cache(item, msginfo);
3843                                 }
3844                         }
3845                 }
3846                 procmsg_msg_list_free(msglist);
3847         }
3848         return result;
3849 }
3850
3851 gint folder_item_remove_all_msg(FolderItem *item)
3852 {
3853         Folder *folder;
3854         gint result;
3855
3856         cm_return_val_if_fail(item != NULL, -1);
3857         if (item->no_select)
3858                 return -1;
3859
3860         folder = item->folder;
3861
3862         inc_lock();
3863         if (folder->klass->remove_all_msg != NULL) {
3864                 result = folder->klass->remove_all_msg(folder, item);
3865
3866                 if (result == 0) {
3867                         folder_item_free_cache(item, TRUE);
3868                         item->cache = msgcache_new();
3869                         item->cache_dirty = TRUE;
3870                         item->mark_dirty = TRUE;
3871                         item->tags_dirty = TRUE;
3872                 }
3873         } else {
3874                 MsgInfoList *msglist;
3875
3876                 msglist = folder_item_get_msg_list(item);
3877                 result = folder_item_remove_msgs(item, msglist);
3878                 procmsg_msg_list_free(msglist);
3879         }
3880
3881         if (result == 0) {
3882                 item->new_msgs = 0;
3883                 item->unread_msgs = 0;
3884                 item->unreadmarked_msgs = 0;
3885                 item->marked_msgs = 0;
3886                 item->total_msgs = 0;
3887                 item->replied_msgs = 0;
3888                 item->forwarded_msgs = 0;
3889                 item->locked_msgs = 0;
3890                 item->ignored_msgs = 0;
3891                 item->watched_msgs = 0;
3892                 folder_item_update(item, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT);
3893         }
3894
3895         inc_unlock();
3896         return result;
3897 }
3898
3899 void folder_item_change_msg_flags(FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3900 {
3901         cm_return_if_fail(item != NULL);
3902         cm_return_if_fail(msginfo != NULL);
3903         
3904         item->mark_dirty = TRUE;
3905
3906         if (item->no_select)
3907                 return;
3908         
3909         if (item->folder->klass->change_flags != NULL && item->scanning != ITEM_SCANNING_WITH_FLAGS) {
3910                 item->folder->klass->change_flags(item->folder, item, msginfo, newflags);
3911         } else {
3912                 msginfo->flags.perm_flags = newflags;
3913         }
3914 }
3915
3916 void folder_item_commit_tags(FolderItem *item, MsgInfo *msginfo, GSList *tags_set, GSList *tags_unset)
3917 {
3918         Folder *folder = NULL;
3919
3920         if (!msginfo)
3921                 return;
3922         if (!item)
3923                 return;
3924         if (!tags_set && !tags_unset)
3925                 return;
3926
3927         folder = item->folder;
3928         if (!folder)
3929                 return;
3930         
3931         item->tags_dirty = TRUE;
3932
3933         if (folder->klass->commit_tags == NULL)
3934                 return;
3935         
3936         folder->klass->commit_tags(item, msginfo, tags_set, tags_unset);
3937 }
3938
3939 gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo)
3940 {
3941         Folder *folder;
3942
3943         cm_return_val_if_fail(item != NULL, FALSE);
3944         if (item->no_select)
3945                 return FALSE;
3946
3947         folder = item->folder;
3948
3949         cm_return_val_if_fail(folder->klass->is_msg_changed != NULL, -1);
3950
3951         return folder->klass->is_msg_changed(folder, item, msginfo);
3952 }
3953
3954 void folder_item_discard_cache(FolderItem *item)
3955 {
3956         gchar *dir;
3957         gchar *cache;
3958
3959         if (!item)
3960                 return;
3961
3962         if (item->cache) {
3963                 msgcache_destroy(item->cache);
3964                 item->cache = NULL;
3965         }
3966         dir = folder_item_get_path(item);
3967         if (is_dir_exist(dir))
3968                 remove_all_numbered_files(dir);
3969         g_free(dir);
3970         
3971         cache = folder_item_get_cache_file(item);
3972         if (is_file_exist(cache))
3973                 claws_unlink(cache);
3974         g_free(cache);
3975         
3976 }
3977
3978 static gchar *folder_item_get_cache_file(FolderItem *item)
3979 {
3980         gchar *path;
3981         gchar *file;
3982         gchar *old_file;
3983
3984         cm_return_val_if_fail(item != NULL, NULL);
3985         cm_return_val_if_fail(item->path != NULL, NULL);
3986
3987         path = folder_item_get_path(item);
3988         cm_return_val_if_fail(path != NULL, NULL);
3989         if (!is_dir_exist(path))
3990                 make_dir_hier(path);
3991         file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL);
3992         old_file = g_strconcat(path, G_DIR_SEPARATOR_S, OLD_CACHE_FILE, NULL);
3993
3994         if (!is_file_exist(file) && is_file_exist(old_file))
3995                 move_file(old_file, file, FALSE);
3996         g_free(old_file);
3997         g_free(path);
3998
3999         return file;
4000 }
4001
4002 static gchar *folder_item_get_mark_file(FolderItem *item)
4003 {
4004         gchar *path;
4005         gchar *file;
4006         gchar *old_file;
4007
4008         cm_return_val_if_fail(item != NULL, NULL);
4009         cm_return_val_if_fail(item->path != NULL, NULL);
4010
4011         path = folder_item_get_path(item);
4012         cm_return_val_if_fail(path != NULL, NULL);
4013         if (!is_dir_exist(path))
4014                 make_dir_hier(path);
4015         file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL);
4016         old_file = g_strconcat(path, G_DIR_SEPARATOR_S, OLD_MARK_FILE, NULL);
4017
4018         if (!is_file_exist(file) && is_file_exist(old_file))
4019                 move_file(old_file, file, FALSE);
4020         g_free(old_file);
4021         g_free(path);
4022
4023         return file;
4024 }
4025
4026 static gchar *folder_item_get_tags_file(FolderItem *item)
4027 {
4028         gchar *path;
4029         gchar *identifier;
4030         gchar *file;
4031
4032         /* we save tags files in rc_dir, because tagsrc is there too,
4033          * and storing tags directly in the mailboxes would give strange
4034          * result when using another Claws mailbox from another install
4035          * with different tags. */
4036
4037         cm_return_val_if_fail(item != NULL, NULL);
4038
4039         identifier = folder_item_get_identifier(item);
4040         cm_return_val_if_fail(identifier != NULL, NULL);
4041
4042 #ifdef G_OS_WIN32
4043         while (strchr(identifier, '/'))
4044                 *strchr(identifier, '/') = '\\';
4045 #endif
4046
4047         path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
4048                            TAGS_DIR, G_DIR_SEPARATOR_S,
4049                            identifier, NULL);
4050         
4051         g_free(identifier);
4052                            
4053         if (!is_dir_exist(path))
4054                 make_dir_hier(path);
4055
4056         file = g_strconcat(path, G_DIR_SEPARATOR_S, TAGS_FILE, NULL);
4057         
4058         g_free(path);
4059
4060         return file;
4061 }
4062
4063 static gpointer xml_to_folder_item(gpointer nodedata, gpointer data)
4064 {
4065         XMLNode *xmlnode = (XMLNode *) nodedata;
4066         Folder *folder = (Folder *) data;
4067         FolderItem *item;
4068
4069         cm_return_val_if_fail(xmlnode != NULL, NULL);
4070         cm_return_val_if_fail(folder != NULL, NULL);
4071
4072         if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
4073                 g_warning("tag name != \"folderitem\"");
4074                 return NULL;
4075         }
4076
4077         item = folder_item_new(folder, "", "");
4078         g_node_destroy(item->node);
4079         if (folder->klass->item_set_xml != NULL)
4080                 folder->klass->item_set_xml(folder, item, xmlnode->tag);
4081         else
4082                 folder_item_set_xml(folder, item, xmlnode->tag);
4083
4084         item->folder = folder;
4085
4086         switch (item->stype) {
4087         case F_INBOX:  folder->inbox  = item; break;
4088         case F_OUTBOX: folder->outbox = item; break;
4089         case F_DRAFT:  folder->draft  = item; break;
4090         case F_QUEUE:  folder->queue  = item; break;
4091         case F_TRASH:  folder->trash  = item; break;
4092         default:       break;
4093         }
4094         folder_item_prefs_read_config(item);
4095
4096         return item;
4097 }
4098
4099 static gboolean folder_item_set_node(GNode *node, gpointer data)
4100 {
4101         cm_return_val_if_fail(node->data != NULL, -1);
4102
4103         FolderItem *item = (FolderItem *) node->data;
4104         item->node = node;
4105
4106         return FALSE;
4107 }
4108
4109 static Folder *folder_get_from_xml(GNode *node)
4110 {
4111         Folder *folder;
4112         XMLNode *xmlnode;
4113         GList *list;
4114         FolderClass *klass = NULL;
4115         GNode *cur;
4116
4117         cm_return_val_if_fail(node->data != NULL, NULL);
4118
4119         xmlnode = node->data;
4120         if (strcmp2(xmlnode->tag->tag, "folder") != 0) {
4121                 g_warning("tag name != \"folder\"");
4122                 return NULL;
4123         }
4124         list = xmlnode->tag->attr;
4125         for (; list != NULL; list = list->next) {
4126                 XMLAttr *attr = list->data;
4127
4128                 if (!attr || !attr->name || !attr->value) continue;
4129                 if (!strcmp(attr->name, "type"))
4130                         klass = folder_get_class_from_string(attr->value);
4131         }
4132         if (klass == NULL)
4133                 return NULL;
4134
4135         folder = folder_new(klass, "", "");
4136         cm_return_val_if_fail(folder != NULL, NULL);
4137
4138         if (klass->set_xml)
4139                 klass->set_xml(folder, xmlnode->tag);
4140         else
4141                 folder_set_xml(folder, xmlnode->tag);
4142
4143         cur = node->children;
4144         while (cur != NULL) {
4145                 GNode *itemnode;
4146
4147                 itemnode = g_node_map(cur, xml_to_folder_item, (gpointer) folder);
4148                 g_node_append(folder->node, itemnode);
4149                 cur = cur->next;
4150         }
4151         g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, folder_item_set_node, NULL);
4152         
4153         return folder;
4154 }
4155
4156 gchar *folder_get_list_path(void)
4157 {
4158         static gchar *filename = NULL;
4159
4160         if (!filename)
4161                 filename =  g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
4162                                         FOLDER_LIST, NULL);
4163
4164         return filename;
4165 }
4166
4167 static gpointer folder_item_to_xml(gpointer nodedata, gpointer data)
4168 {
4169         FolderItem *item = (FolderItem *) nodedata;
4170         XMLTag *tag;
4171
4172         cm_return_val_if_fail(item != NULL, NULL);
4173
4174         if (item->folder->klass->item_get_xml != NULL)
4175                 tag = item->folder->klass->item_get_xml(item->folder, item);
4176         else
4177                 tag = folder_item_get_xml(item->folder, item);
4178
4179         return xml_node_new(tag, NULL);
4180 }
4181
4182 static GNode *folder_get_xml_node(Folder *folder)
4183 {
4184         GNode *node;
4185         XMLNode *xmlnode;
4186         XMLTag *tag;
4187
4188         cm_return_val_if_fail(folder != NULL, NULL);
4189
4190         if (folder->klass->get_xml != NULL)
4191                 tag = folder->klass->get_xml(folder);
4192         else
4193                 tag = folder_get_xml(folder);
4194
4195         xml_tag_add_attr(tag, xml_attr_new("type", folder->klass->idstr));
4196
4197         xmlnode = xml_node_new(tag, NULL);
4198
4199         node = g_node_new(xmlnode);
4200         
4201         cm_return_val_if_fail (folder->node != NULL, NULL);
4202         
4203         if (folder->node->children) {
4204                 GNode *cur;
4205
4206                 cur = folder->node->children;
4207                 while (cur) {
4208                         GNode *xmlnode;
4209
4210                         xmlnode = g_node_map(cur, folder_item_to_xml, (gpointer) folder);
4211                         g_node_append(node, xmlnode);
4212                         cur = cur->next;
4213                 }
4214         }
4215
4216         return node;
4217 }
4218
4219 static void folder_update_op_count_rec(GNode *node)
4220 {
4221         FolderItem *fitem = FOLDER_ITEM(node->data);
4222
4223         if (g_node_depth(node) > 0) {
4224                 if (fitem->op_count > 0) {
4225                         fitem->op_count = 0;
4226                         folder_item_update(fitem, F_ITEM_UPDATE_MSGCNT);
4227                 }
4228                 if (node->children) {
4229                         GNode *child;
4230
4231                         child = node->children;
4232                         while (child) {
4233                                 GNode *cur;
4234
4235                                 cur = child;
4236                                 child = cur->next;
4237                                 folder_update_op_count_rec(cur);
4238                         }
4239                 }
4240         }
4241 }
4242
4243 void folder_update_op_count(void) 
4244 {
4245         GList *cur;
4246         Folder *folder;
4247
4248         for (cur = folder_list; cur != NULL; cur = cur->next) {
4249                 folder = cur->data;
4250                 folder_update_op_count_rec(folder->node);
4251         }
4252 }
4253
4254 typedef struct _type_str {
4255         gchar * str;
4256         gint type;
4257 } type_str;
4258
4259
4260 /*
4261 static gchar * folder_item_get_tree_identifier(FolderItem * item)
4262 {
4263         if (item->parent != NULL) {
4264                 gchar * path;
4265                 gchar * id;
4266
4267                 path = folder_item_get_tree_identifier(item->parent);
4268                 if (path == NULL)
4269                         return NULL;
4270
4271                 id = g_strconcat(path, "/", item->name, NULL);
4272                 g_free(path);
4273
4274                 return id;
4275         }
4276         else {
4277                 return g_strconcat("/", item->name, NULL);
4278         }
4279 }
4280 */
4281
4282 /* CLAWS: temporary local folder for filtering */
4283 #define TEMP_FOLDER "TEMP_FOLDER"
4284 #define PROCESSING_FOLDER_ITEM "processing"     
4285
4286 static FolderItem *folder_create_processing_folder(int account_id)
4287 {
4288         static Folder *processing_folder = NULL;
4289         FolderItem *processing_folder_item;
4290
4291         gchar *processing_folder_item_name = NULL;
4292
4293         processing_folder_item_name = g_strdup_printf("%s-%d", PROCESSING_FOLDER_ITEM, account_id);
4294
4295         if (processing_folder == NULL) {
4296                 gchar *tmppath =
4297                     g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
4298                                 "tempfolder", NULL);
4299                 processing_folder =
4300                     folder_new(mh_get_class(), TEMP_FOLDER, tmppath);
4301                 g_free(tmppath);
4302                 tmppath = NULL;
4303                 
4304                 g_assert(processing_folder != NULL);
4305                 processing_folder->klass->scan_tree(processing_folder);
4306         }
4307         g_assert(processing_folder != NULL);
4308
4309         processing_folder_item = folder_find_child_item_by_name(FOLDER_ITEM(processing_folder->node->data),
4310                                         processing_folder_item_name);
4311         if (processing_folder_item) {
4312                 debug_print("*TMP* already created %s\n", folder_item_get_path(processing_folder_item));
4313         } else {
4314                 processing_folder_item = processing_folder->klass->create_folder(processing_folder,
4315                                                                                  processing_folder->node->data,
4316                                                                                  processing_folder_item_name);
4317                 folder_item_append(FOLDER_ITEM(processing_folder->node->data), processing_folder_item);
4318                 debug_print("*TMP* creating %s\n", folder_item_get_path(processing_folder_item));
4319         } 
4320         g_free(processing_folder_item_name);
4321         g_assert(processing_folder_item != NULL);
4322
4323         return(processing_folder_item);
4324 }
4325
4326 FolderItem *folder_get_default_processing(int account_id)
4327 {
4328         return folder_create_processing_folder(account_id);
4329 }
4330
4331 /* folder_persist_prefs_new() - return hash table with persistent
4332  * settings (and folder name as key). 
4333  * (note that in claws other options are in the folder_item_prefs_RC
4334  * file, so those don't need to be included in PersistPref yet) 
4335  */
4336 static GHashTable *folder_persist_prefs_new(Folder *folder)
4337 {
4338         GHashTable *pptable;
4339
4340         cm_return_val_if_fail(folder, NULL);
4341         pptable = g_hash_table_new(g_str_hash, g_str_equal);
4342         folder_get_persist_prefs_recursive(folder->node, pptable);
4343         return pptable;
4344 }
4345
4346 static void folder_persist_prefs_free(GHashTable *pptable)
4347 {
4348         cm_return_if_fail(pptable);
4349         g_hash_table_foreach_remove(pptable, persist_prefs_free, NULL);
4350         g_hash_table_destroy(pptable);
4351 }
4352
4353 static const PersistPrefs *folder_get_persist_prefs(GHashTable *pptable, const char *name)
4354 {
4355         if (pptable == NULL || name == NULL) return NULL;
4356         return g_hash_table_lookup(pptable, name);
4357 }
4358
4359 static void folder_item_restore_persist_prefs(FolderItem *item, GHashTable *pptable)
4360 {
4361         const PersistPrefs *pp;
4362         gchar *id = folder_item_get_identifier(item);
4363
4364         pp = folder_get_persist_prefs(pptable, id); 
4365         g_free(id);
4366
4367         if (!pp) return;
4368
4369         /* CLAWS: since not all folder properties have been migrated to 
4370          * folderlist.xml, we need to call the old stuff first before
4371          * setting things that apply both to Main and Claws. */
4372         folder_item_prefs_read_config(item); 
4373
4374         item->collapsed = pp->collapsed;
4375         item->thread_collapsed = pp->thread_collapsed;
4376         item->threaded  = pp->threaded;
4377         item->ret_rcpt  = pp->ret_rcpt;
4378         item->hide_read_msgs = pp->hide_read_msgs;
4379         item->hide_del_msgs = pp->hide_del_msgs;
4380         item->hide_read_threads = pp->hide_read_threads;
4381         item->sort_key  = pp->sort_key;
4382         item->sort_type = pp->sort_type;
4383 }
4384
4385 static void folder_get_persist_prefs_recursive(GNode *node, GHashTable *pptable)
4386 {
4387         FolderItem *item = FOLDER_ITEM(node->data);
4388         PersistPrefs *pp;
4389         GNode *child, *cur;
4390         gchar *id;
4391
4392         cm_return_if_fail(node != NULL);
4393         cm_return_if_fail(item != NULL);
4394
4395         /* NOTE: item->path == NULL means top level folder; not interesting
4396          * to store preferences of that one.  */
4397         if (item->path) {
4398                 id = folder_item_get_identifier(item);
4399                 pp = g_new0(PersistPrefs, 1);
4400                 cm_return_if_fail(pp != NULL);
4401                 pp->collapsed = item->collapsed;
4402                 pp->thread_collapsed = item->thread_collapsed;
4403                 pp->threaded  = item->threaded;
4404                 pp->ret_rcpt  = item->ret_rcpt; 
4405                 pp->hide_read_msgs = item->hide_read_msgs;
4406                 pp->hide_del_msgs = item->hide_del_msgs;
4407                 pp->hide_read_threads = item->hide_read_threads;
4408                 pp->sort_key  = item->sort_key;
4409                 pp->sort_type = item->sort_type;
4410                 g_hash_table_insert(pptable, id, pp);
4411         }
4412
4413         if (node->children) {
4414                 child = node->children;
4415                 while (child) {
4416                         cur = child;
4417                         child = cur->next;
4418                         folder_get_persist_prefs_recursive(cur, pptable);
4419                 }
4420         }       
4421 }
4422
4423 static gboolean persist_prefs_free(gpointer key, gpointer val, gpointer data)
4424 {
4425         g_free(key);
4426         g_free(val);
4427         return TRUE;    
4428 }
4429
4430 void folder_item_apply_processing(FolderItem *item)
4431 {
4432         GSList *processing_list;
4433         GSList *mlist, *cur;
4434         guint total = 0, curmsg = 0;
4435         gint last_apply_per_account;
4436
4437         cm_return_if_fail(item != NULL);
4438
4439         if (item->no_select)
4440                return;
4441
4442         processing_list = item->prefs->processing;
4443
4444         if (!pre_global_processing && !processing_list
4445         &&  !post_global_processing)
4446                 return;
4447
4448         debug_print("processing %s\n", item->name);
4449         folder_item_update_freeze();
4450
4451         inc_lock();
4452
4453         mlist = folder_item_get_msg_list(item);
4454         total = g_slist_length(mlist);
4455         statusbar_print_all(_("Processing messages..."));
4456
4457         last_apply_per_account = prefs_common.apply_per_account_filtering_rules;
4458         prefs_common.apply_per_account_filtering_rules = FILTERING_ACCOUNT_RULES_SKIP;
4459
4460         folder_item_set_batch(item, TRUE);
4461         for (cur = mlist ; cur != NULL ; cur = cur->next) {
4462                 MsgInfo * msginfo;
4463
4464                 msginfo = (MsgInfo *) cur->data;
4465                 
4466                 /* reset parameters that can be modified by processing */
4467                 msginfo->hidden = 0;
4468                 msginfo->score = 0;
4469
4470                 statusbar_progress_all(curmsg++,total, 10);
4471
4472                 /* apply pre global rules */
4473                 filter_message_by_msginfo(pre_global_processing, msginfo, NULL,
4474                                 FILTERING_PRE_PROCESSING, NULL);
4475                 
4476                 /* apply rules of the folder */
4477                 filter_message_by_msginfo(processing_list, msginfo, NULL,
4478                                 FILTERING_FOLDER_PROCESSING, item->name);
4479
4480                 /* apply post global rules */
4481                 filter_message_by_msginfo(post_global_processing, msginfo, NULL,
4482                                 FILTERING_POST_PROCESSING, NULL);
4483                 if (curmsg % 1000 == 0)
4484                         GTK_EVENTS_FLUSH();
4485         }
4486         folder_item_set_batch(item, FALSE);
4487
4488         prefs_common.apply_per_account_filtering_rules = last_apply_per_account;
4489
4490         if (pre_global_processing || processing_list
4491             || post_global_processing)
4492                 filtering_move_and_copy_msgs(mlist);
4493         for (cur = mlist ; cur != NULL ; cur = cur->next) {
4494                 procmsg_msginfo_free((MsgInfo **)&(cur->data));
4495         }
4496         g_slist_free(mlist);
4497         
4498         statusbar_progress_all(0,0,0);
4499         statusbar_pop_all();
4500
4501         inc_unlock();
4502
4503         folder_item_update_thaw();
4504 }
4505
4506 /*
4507  *  functions for handling FolderItem content changes
4508  */
4509 static gint folder_item_update_freeze_cnt = 0;
4510
4511 static void folder_item_update_with_msg(FolderItem *item, FolderItemUpdateFlags update_flags, MsgInfo *msg)
4512 {
4513         if (folder_item_update_freeze_cnt == 0 /* || (msg != NULL && item->opened) */) {
4514                 FolderItemUpdateData source;
4515         
4516                 source.item = item;
4517                 source.update_flags = update_flags;
4518                 source.msg = msg;
4519                 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &source);
4520         } else {
4521                 item->update_flags |= update_flags & ~(F_ITEM_UPDATE_ADDMSG | F_ITEM_UPDATE_REMOVEMSG);
4522         }
4523 }
4524
4525 /**
4526  * Notify the folder system about changes to a folder. If the
4527  * update system is not frozen the FOLDER_ITEM_UPDATE_HOOKLIST will
4528  * be invoked, otherwise the changes will be remebered until
4529  * the folder system is thawed.
4530  *
4531  * \param item The FolderItem that was changed
4532  * \param update_flags Type of changed that was made
4533  */
4534 void folder_item_update(FolderItem *item, FolderItemUpdateFlags update_flags)
4535 {
4536         folder_item_update_with_msg(item, update_flags, NULL);
4537 }
4538
4539 void folder_item_update_recursive(FolderItem *item, FolderItemUpdateFlags update_flags)
4540 {
4541         GNode *node = item->folder->node;       
4542
4543         node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
4544         node = node->children;
4545
4546         folder_item_update(item, update_flags);
4547         while (node != NULL) {
4548                 if (node && node->data) {
4549                         FolderItem *next_item = (FolderItem*) node->data;
4550
4551                         folder_item_update(next_item, update_flags);
4552                 }
4553                 node = node->next;
4554         }
4555 }
4556
4557 void folder_item_update_freeze(void)
4558 {
4559         folder_item_update_freeze_cnt++;
4560 }
4561
4562 static void folder_item_update_func(FolderItem *item, gpointer data)
4563 {
4564         FolderItemUpdateData source;
4565     
4566         if (item->update_flags) {
4567                 source.item = item;
4568                 source.update_flags = item->update_flags;
4569                 source.msg = NULL;
4570                 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &source);                             
4571                 item->update_flags = 0;
4572         }
4573 }
4574
4575 void folder_item_update_thaw(void)
4576 {
4577         if (folder_item_update_freeze_cnt > 0)
4578                 folder_item_update_freeze_cnt--;
4579         if (folder_item_update_freeze_cnt == 0) {
4580                 /* Update all folders */
4581                 folder_func_to_all_folders(folder_item_update_func, NULL);
4582         }
4583 }
4584
4585 void folder_item_synchronise(FolderItem *item)
4586 {
4587         if (!item)
4588                 return;
4589         if (item->prefs->offlinesync && item->folder->klass->synchronise) {
4590                 statusbar_print_all(_("Synchronising %s for offline use...\n"), item->path ? item->path : "(null)");
4591                 item->folder->klass->synchronise(item, 
4592                         item->prefs->offlinesync_days);
4593                 if (item->prefs->offlinesync_days > 0 &&
4594                     item->prefs->remove_old_bodies)
4595                         folder_item_clean_local_files(item, item->prefs->offlinesync_days);
4596                 statusbar_pop_all();
4597         }
4598 }
4599
4600 static void folder_item_synchronise_func(FolderItem *item, gpointer data)
4601 {
4602         Folder *folder = (Folder *)data;
4603         if (folder == NULL || item->folder == folder) {
4604                 folder_item_synchronise(item);
4605         }
4606 }
4607
4608 void folder_synchronise(Folder *folder)
4609 {
4610         folder_func_to_all_folders(folder_item_synchronise_func, folder);
4611 }
4612
4613 typedef struct _WantSyncData {
4614         Folder *folder;
4615         gboolean want_sync;
4616 } WantSyncData;
4617
4618 static void folder_item_want_synchronise_func(FolderItem *item, gpointer data)
4619 {
4620         WantSyncData *want_sync_data = (WantSyncData *)data;
4621         
4622         if (want_sync_data->folder == NULL || item->folder == want_sync_data->folder) {
4623                 if (item->prefs->offlinesync && item->folder->klass->synchronise)
4624                         want_sync_data->want_sync |= TRUE;
4625         }
4626 }
4627
4628 gboolean folder_want_synchronise(Folder *folder)
4629 {
4630         WantSyncData *want_sync_data = g_new0(WantSyncData, 1);
4631         gboolean result;
4632         want_sync_data->folder = folder;
4633         want_sync_data->want_sync = FALSE;
4634         
4635         folder_func_to_all_folders(folder_item_want_synchronise_func, want_sync_data);
4636         result = want_sync_data->want_sync;
4637         g_free(want_sync_data);
4638         if (result > 0)
4639                 debug_print("Folder %s wants sync\n", folder->name);
4640         return result;
4641 }
4642
4643 void folder_item_set_batch (FolderItem *item, gboolean batch)
4644 {
4645         if (!item || !item->folder)
4646                 return;
4647         if (item->folder->klass->set_batch) {
4648                 item->folder->klass->set_batch(item->folder, item, batch);
4649         }
4650 }
4651
4652 gboolean folder_has_parent_of_type(FolderItem *item, 
4653                                           SpecialFolderItemType type) 
4654 {
4655         FolderItem *cur = item;
4656
4657         if (!item)
4658                 return FALSE;
4659         /* if we already know it, make it short */
4660         if (item->parent_stype != -1) {
4661                 return (item->parent_stype == type);
4662         }
4663         
4664         /* if we don't, find the type from the first possible parent,
4665          * and set our parent type to be faster next time */
4666         while (cur) {
4667                 if (cur->stype == type || cur->parent_stype == type) {
4668                         item->parent_stype = type;
4669                         return TRUE;
4670                 }
4671                 cur = folder_item_parent(cur);
4672         }
4673         
4674         /* if we didn't match what was asked, we didn't return. If our
4675          * parent type is unknown, we may as well find it now to be faster
4676          * later. */
4677         if (item->parent_stype == -1) {
4678                 cur = item;
4679                 while (cur) {
4680                         /* here's an exception: Inbox subfolders are normal. */
4681                         if (item->parent_stype == -1 && cur->stype == F_INBOX 
4682                         && item != cur) {
4683                                 item->parent_stype = F_NORMAL;
4684                                 break;
4685                         }
4686                         /* ah, we know this parent's parent's type, we may as 
4687                          * well copy it instead of going up the full way */
4688                         if (cur->parent_stype != -1) {
4689                                 item->parent_stype = cur->parent_stype;
4690                                 break;
4691                         }
4692                         /* we found a parent that has a special type. That's 
4693                          * our parent type. */
4694                         if (cur->stype != F_NORMAL) {
4695                                 cur->parent_stype = cur->stype;
4696                                 item->parent_stype = cur->stype;
4697                                 break;
4698                         }
4699                         /* if we didn't find anything, go up once more */
4700                         cur = folder_item_parent(cur);
4701                 }
4702                 /* as we still didn't find anything, our parents must all be 
4703                  * normal. */
4704                 if (item->parent_stype == -1) {
4705                         item->parent_stype = F_NORMAL;
4706                 }
4707         }
4708         return FALSE;
4709 }
4710
4711 gboolean folder_is_child_of(FolderItem *item, FolderItem *parent)
4712 {
4713         if (item == NULL || parent == NULL)
4714                 return FALSE;
4715
4716         while (item != NULL) {
4717                 if (parent == item)
4718                         return TRUE;
4719
4720                 item = folder_item_parent(item);
4721         }
4722
4723         return FALSE;
4724 }
4725
4726
4727 gboolean folder_subscribe (const gchar *uri)
4728 {
4729         GList *cur;
4730         for (cur = folder_get_list(); cur != NULL; cur = g_list_next(cur)) {
4731                 Folder *folder = (Folder *) cur->data;
4732
4733                 if (folder->klass->subscribe
4734                 &&  folder->klass->subscribe(folder, uri)) {
4735                         return TRUE;
4736                 }
4737         }
4738         return FALSE;
4739
4740 }
4741
4742 gboolean folder_get_sort_type           (Folder         *folder,
4743                                          FolderSortKey  *sort_key,
4744                                          FolderSortType *sort_type)
4745 {
4746         if (!folder || !sort_key || !sort_type)
4747                 return FALSE;
4748         if (folder->klass->get_sort_type == NULL)
4749                 return FALSE;
4750         folder->klass->get_sort_type(folder, sort_key, sort_type); 
4751         return TRUE;
4752 }
4753
4754 gint folder_item_search_msgs    (Folder                 *folder,
4755                                  FolderItem             *container,
4756                                  MsgNumberList          **msgs,
4757                                  gboolean               *on_server,
4758                                  MatcherList            *predicate,
4759                                  SearchProgressNotify   progress_cb,
4760                                  gpointer               progress_data)
4761 {
4762         gint result = -1;
4763         
4764         folder_item_update_freeze();
4765
4766         if (folder->klass->search_msgs)
4767                 result = folder->klass->search_msgs(folder, container,
4768                                 msgs, on_server, predicate, progress_cb, progress_data);
4769         if (result < 0)
4770                 result = folder_item_search_msgs_local(folder, container,
4771                                 msgs, on_server, predicate, progress_cb, progress_data);
4772         
4773         folder_item_update_thaw();
4774
4775         return result;
4776 }
4777
4778 MsgNumberList *folder_item_get_number_list(FolderItem *item)
4779 {
4780         GSList *nums = NULL;
4781         GSList *msglist = folder_item_get_msg_list(item);
4782
4783         nums = procmsg_get_number_list_for_msgs(msglist);
4784         procmsg_msg_list_free(msglist);
4785         
4786         return nums;
4787 }
4788
4789 gint folder_item_search_msgs_local      (Folder                 *folder,
4790                                          FolderItem             *container,
4791                                          MsgNumberList          **msgs,
4792                                          gboolean               *on_server,
4793                                          MatcherList            *predicate,
4794                                          SearchProgressNotify   progress_cb,
4795                                          gpointer               progress_data)
4796 {
4797         GSList *result = NULL;
4798         GSList *cur = NULL;
4799         gint matched_count = 0;
4800         guint processed_count = 0;
4801         gint msgcount;
4802         GSList *nums = NULL;
4803
4804         if (*msgs == NULL) {
4805                 nums = folder_item_get_number_list(container);
4806         } else {
4807                 nums = *msgs;
4808         }
4809
4810         msgcount = g_slist_length(nums);
4811
4812         if (msgcount < 0)
4813                 return -1;
4814
4815         for (cur = nums; cur != NULL; cur = cur->next) {
4816                 guint msgnum = GPOINTER_TO_UINT(cur->data);
4817                 MsgInfo *msg = folder_item_get_msginfo(container, msgnum);
4818
4819                 if (msg == NULL) {
4820                         g_slist_free(result);
4821                         return -1;
4822                 }
4823
4824                 if (matcherlist_match(predicate, msg)) {
4825                         result = g_slist_prepend(result, GUINT_TO_POINTER(msg->msgnum));
4826                         matched_count++;
4827                 }
4828                 processed_count++;
4829
4830                 procmsg_msginfo_free(&msg);
4831
4832                 if (progress_cb != NULL
4833                     && !progress_cb(progress_data, FALSE, processed_count,
4834                             matched_count, msgcount))
4835                         break;
4836         }
4837
4838         g_slist_free(nums);
4839         *msgs = g_slist_reverse(result);
4840
4841         return matched_count;
4842 }
4843
4844 /* Tests if a local (on disk) folder name is acceptable. */
4845 gboolean folder_local_name_ok(const gchar *name)
4846 {
4847 #ifdef G_OS_WIN32
4848         if (name[0] == '.' || name[strlen(name) - 1] == '.') {
4849                 alertpanel_error(_("A folder name cannot begin or end with a dot."));
4850                 return FALSE;
4851         }
4852         if (name[strlen(name) - 1] == ' ') {
4853                 alertpanel_error(_("A folder name can not end with a space."));
4854                 return FALSE;
4855         }
4856 #endif
4857
4858         return TRUE;
4859 }