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