Fix a logic error introduced in previous commit.
[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