Implement config_version in folderlist.xml.
[claws.git] / src / folder.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <stdlib.h>
34 #ifdef WIN32
35 #include <w32lib.h>
36 #endif
37
38 #include "alertpanel.h"
39 #include "folder.h"
40 #include "session.h"
41 #include "inc.h"
42 #include "imap.h"
43 #include "news.h"
44 #include "mh.h"
45 #include "utils.h"
46 #include "xml.h"
47 #include "codeconv.h"
48 #include "prefs_gtk.h"
49 #include "account.h"
50 #include "filtering.h"
51 #include "procheader.h"
52 #include "hooks.h"
53 #include "log.h"
54 #include "folder_item_prefs.h"
55 #include "remotefolder.h"
56 #include "partial_download.h"
57 #include "statusbar.h"
58 #include "gtkutils.h"
59 #include "timing.h"
60 #include "compose.h"
61 #include "main.h"
62 #include "msgcache.h"
63 #include "privacy.h"
64 #include "prefs_common.h"
65 #include "prefs_migration.h"
66
67 /* Dependecies to be removed ?! */
68 #include "prefs_account.h"
69
70 /* Define possible missing constants for Windows. */
71 #ifdef G_OS_WIN32
72 # ifndef S_IRGRP
73 # define S_IRGRP 0
74 # define S_IWGRP 0
75 # endif
76 # ifndef S_IROTH
77 # define S_IROTH 0
78 # define S_IWOTH 0
79 # endif
80 #endif
81
82 static GList *folder_list = NULL;
83 static GSList *class_list = NULL;
84 static GSList *folder_unloaded_list = NULL;
85
86 void folder_init                (Folder         *folder,
87                                  const gchar    *name);
88
89 static gchar *folder_item_get_cache_file        (FolderItem     *item);
90 static gchar *folder_item_get_mark_file (FolderItem     *item);
91 static gchar *folder_item_get_tags_file (FolderItem     *item);
92 static GNode *folder_get_xml_node       (Folder         *folder);
93 static Folder *folder_get_from_xml      (GNode          *node);
94 static void folder_update_op_count_rec  (GNode          *node);
95
96
97 static void folder_get_persist_prefs_recursive
98                                         (GNode *node, GHashTable *pptable);
99 static gboolean persist_prefs_free      (gpointer key, gpointer val, gpointer data);
100 static void folder_item_read_cache              (FolderItem *item);
101 gint folder_item_scan_full              (FolderItem *item, gboolean filtering);
102 static void folder_item_update_with_msg (FolderItem *item, FolderItemUpdateFlags update_flags,
103                                          MsgInfo *msg);
104 static GHashTable *folder_persist_prefs_new     (Folder *folder);
105 static void folder_persist_prefs_free           (GHashTable *pptable);
106 static void folder_item_restore_persist_prefs   (FolderItem *item, GHashTable *pptable);
107
108 void folder_system_init(void)
109 {
110         folder_register_class(mh_get_class());
111         folder_register_class(imap_get_class());
112         folder_register_class(news_get_class());
113 }
114
115 static GSList *folder_get_class_list(void)
116 {
117         return class_list;
118 }
119
120 void folder_register_class(FolderClass *klass)
121 {
122         GSList *xmllist, *cur;
123
124         debug_print("registering folder class %s\n", klass->idstr);
125
126         class_list = g_slist_append(class_list, klass);
127
128         xmllist = g_slist_copy(folder_unloaded_list);
129         for (cur = xmllist; cur != NULL; cur = g_slist_next(cur)) {
130                 GNode *node = (GNode *) cur->data;
131                 XMLNode *xmlnode = (XMLNode *) node->data;
132                 GList *cur = xmlnode->tag->attr;
133
134                 for (; cur != NULL; cur = g_list_next(cur)) {
135                         XMLAttr *attr = (XMLAttr *) cur->data;
136
137                         if (!attr || !attr->name || !attr->value) continue;
138                         if (!strcmp(attr->name, "type") && !strcmp(attr->value, klass->idstr)) {
139                                 Folder *folder;
140
141                                 folder = folder_get_from_xml(node);
142                                 if (folder) {
143                                         folder_add(folder);
144                                         folder_unloaded_list = g_slist_remove(folder_unloaded_list, node);
145                                         xml_free_tree(node);
146                                 }
147                                 cur = NULL;
148                                 continue;
149                         }
150                 }
151         }
152         g_slist_free(xmllist);
153 }
154
155 void folder_unregister_class(FolderClass *klass)
156 {
157         GList *folderlist, *cur;
158
159         debug_print("unregistering folder class %s\n", klass->idstr);
160
161         class_list = g_slist_remove(class_list, klass);
162
163         folderlist = g_list_copy(folder_get_list());
164         for (cur = folderlist; cur != NULL; cur = g_list_next(cur)) {
165                 Folder *folder = (Folder *) cur->data;
166
167                 if (folder->klass == klass) {
168                         GNode *xmlnode = folder_get_xml_node(folder);
169                         folder_unloaded_list = g_slist_append(folder_unloaded_list, xmlnode);
170                         folder_destroy(folder);
171                 }
172         }
173         g_list_free(folderlist);
174
175         if (klass->prefs_pages)
176                 g_slist_free(klass->prefs_pages);
177 }
178
179 Folder *folder_new(FolderClass *klass, const gchar *name, const gchar *path)
180 {
181         Folder *folder = NULL;
182         FolderItem *item;
183
184         cm_return_val_if_fail(klass != NULL, NULL);
185
186         name = name ? name : path;
187         folder = klass->new_folder(name, path);
188
189         /* Create root folder item */
190         item = folder_item_new(folder, name, NULL);
191         if (item == NULL) {
192                 return NULL;
193         }
194         item->folder = folder;
195         folder->node = item->node;
196         folder->data = NULL;
197
198         return folder;
199 }
200
201 void folder_init(Folder *folder, const gchar *name)
202 {
203         cm_return_if_fail(folder != NULL);
204
205         folder_set_name(folder, name);
206
207         /* Init folder data */
208         folder->account = NULL;
209         folder->sort = 0;
210         folder->inbox = NULL;
211         folder->outbox = NULL;
212         folder->draft = NULL;
213         folder->queue = NULL;
214         folder->trash = NULL;
215 }
216
217 static void reset_parent_type(FolderItem *item, gpointer data) {
218         item->parent_stype = -1;
219 }
220
221 void folder_item_change_type(FolderItem *item, SpecialFolderItemType newtype)
222 {
223         Folder *folder = NULL;
224         FolderUpdateData hookdata;
225
226         if (item == NULL)
227                 return;
228
229         folder = item->folder;
230         /* unset previous root of newtype */
231         switch(newtype) {
232         case F_INBOX:
233                 folder_item_change_type(folder->inbox, F_NORMAL);
234                 folder->inbox = item;
235                 break;
236         case F_OUTBOX:
237                 folder_item_change_type(folder->outbox, F_NORMAL);
238                 folder->outbox = item;
239                 break;
240         case F_QUEUE:
241                 folder_item_change_type(folder->queue, F_NORMAL);
242                 folder->queue = item;
243                 break;
244         case F_DRAFT:
245                 folder_item_change_type(folder->draft, F_NORMAL);
246                 folder->draft = item;
247                 break;
248         case F_TRASH:
249                 folder_item_change_type(folder->trash, F_NORMAL);
250                 folder->trash = item;
251                 break;
252         case F_NORMAL:
253         default:
254                 break;
255         }
256         /* set new type for current folder and sons */
257         item->stype = newtype;
258         folder_func_to_all_folders(reset_parent_type, NULL);
259         
260         hookdata.folder = folder;
261         hookdata.update_flags = FOLDER_TREE_CHANGED;
262         hookdata.item = NULL;
263         hookdata.item2 = NULL;
264         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
265 }
266
267 void folder_destroy(Folder *folder)
268 {
269         cm_return_if_fail(folder != NULL);
270         cm_return_if_fail(folder->klass->destroy_folder != NULL);
271
272         folder_remove(folder);
273
274         folder_tree_destroy(folder);
275
276         folder->klass->destroy_folder(folder);
277
278         g_free(folder->name);
279         g_free(folder);
280 }
281
282 void folder_set_xml(Folder *folder, XMLTag *tag)
283 {
284         GList *cur;
285         FolderItem *rootitem = NULL;
286
287         if ((folder->node != NULL) && (folder->node->data != NULL))
288                 rootitem = (FolderItem *) folder->node->data;
289
290         for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
291                 XMLAttr *attr = (XMLAttr *) cur->data;
292
293                 if (!attr || !attr->name || !attr->value) continue;
294                 if (!strcmp(attr->name, "name")) {
295                         g_free(folder->name);
296                         folder->name = g_strdup(attr->value);
297                         if (rootitem != NULL) {
298                                 g_free(rootitem->name);
299                                 rootitem->name = g_strdup(attr->value);
300                         }
301                 } else if (!strcmp(attr->name, "account_id")) {
302                         PrefsAccount *account;
303
304                         account = account_find_from_id(atoi(attr->value));
305                         if (!account)
306                                 g_warning("account_id: %s not found", attr->value);
307                         else {
308                                 folder->account = account;
309                                 account->folder = folder;
310                         }
311                 } else if (!strcmp(attr->name, "collapsed")) {
312                         if (rootitem != NULL)
313                                 rootitem->collapsed = *attr->value == '1' ? TRUE : FALSE;
314                 } else if (!strcmp(attr->name, "sort")) {
315                         folder->sort = atoi(attr->value);
316                 }
317         }
318 }
319
320 XMLTag *folder_get_xml(Folder *folder)
321 {
322         XMLTag *tag;
323
324         tag = xml_tag_new("folder");
325
326         if (folder->name)
327                 xml_tag_add_attr(tag, xml_attr_new("name", folder->name));
328         if (folder->account)
329                 xml_tag_add_attr(tag, xml_attr_new_int("account_id", folder->account->account_id));
330         if (folder->node && folder->node->data) {
331                 FolderItem *rootitem = (FolderItem *) folder->node->data;
332
333                 xml_tag_add_attr(tag, xml_attr_new("collapsed", rootitem->collapsed ? "1" : "0"));
334         }
335         xml_tag_add_attr(tag, xml_attr_new_int("sort", folder->sort));
336
337         return tag;
338 }
339
340 FolderItem *folder_item_new(Folder *folder, const gchar *name, const gchar *path)
341 {
342         FolderItem *item = NULL;
343
344         cm_return_val_if_fail(folder != NULL, NULL);
345
346         if (folder->klass->item_new) {
347                 item = folder->klass->item_new(folder);
348         } else {
349                 item = g_new0(FolderItem, 1);
350         }
351
352         cm_return_val_if_fail(item != NULL, NULL);
353
354         item->stype = F_NORMAL;
355
356         if(!g_utf8_validate(name, -1, NULL)) {
357                 item->name = g_malloc(strlen(name)*2+1);
358                 conv_localetodisp(item->name, strlen(name)*2+1, name);
359         } else {
360                 item->name = g_strdup(name);
361         }
362
363         item->path = g_strdup(path);
364         item->mtime = 0;
365         item->new_msgs = 0;
366         item->unread_msgs = 0;
367         item->unreadmarked_msgs = 0;
368         item->marked_msgs = 0;
369         item->total_msgs = 0;
370         item->replied_msgs = 0;
371         item->forwarded_msgs = 0;
372         item->locked_msgs = 0;
373         item->ignored_msgs = 0;
374         item->watched_msgs = 0;
375         item->order = 0;
376         item->last_num = -1;
377         item->cache = NULL;
378         item->no_sub = FALSE;
379         item->no_select = FALSE;
380         item->collapsed = FALSE;
381         item->thread_collapsed = FALSE;
382         item->threaded  = TRUE;
383         item->ret_rcpt  = FALSE;
384         item->opened    = FALSE;
385         item->node = g_node_new(item);
386         item->folder = NULL;
387         item->account = NULL;
388         item->apply_sub = FALSE;
389         item->mark_queue = NULL;
390         item->data = NULL;
391         item->parent_stype = -1;
392
393         item->sort_key = prefs_common.default_sort_key;
394         item->sort_type = prefs_common.default_sort_type;
395
396         item->prefs = folder_item_prefs_new();
397
398         return item;
399 }
400
401 void folder_item_append(FolderItem *parent, FolderItem *item)
402 {
403         cm_return_if_fail(parent != NULL);
404         cm_return_if_fail(parent->folder != NULL);
405         cm_return_if_fail(parent->node != NULL);
406         cm_return_if_fail(item != NULL);
407
408         item->folder = parent->folder;
409         g_node_append(parent->node, item->node);
410 }
411
412 void folder_item_remove(FolderItem *item)
413 {
414         GNode *node, *start_node;
415         FolderUpdateData hookdata;
416         gchar *tags_file = NULL, *tags_dir = NULL;
417
418         cm_return_if_fail(item != NULL);
419         cm_return_if_fail(item->folder != NULL);
420         cm_return_if_fail(item->folder->node != NULL);
421
422         start_node = item->node;
423         
424         node = item->folder->node;
425         
426         node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
427         node = node->children;
428
429         /* remove my children */
430         while (node != NULL) {
431                 if (node && node->data) {
432                         FolderItem *sub_item = (FolderItem*) node->data;
433                         node = node->next;
434                         folder_item_remove(sub_item);
435                 }
436         }
437
438         /* remove myself */
439         if (item->cache != NULL) {
440                 msgcache_destroy(item->cache);
441                 item->cache = NULL;
442         }
443         tags_file = folder_item_get_tags_file(item);
444         if (tags_file)
445                 claws_unlink(tags_file);
446         tags_dir = g_path_get_dirname(tags_file);
447         if (tags_dir)
448                 rmdir(tags_dir);
449
450         g_free(tags_file);
451         g_free(tags_dir);
452
453         hookdata.folder = item->folder;
454         hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_REMOVE_FOLDERITEM;
455         hookdata.item = item;
456         hookdata.item2 = NULL;
457         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
458
459         node = start_node;
460
461         if (item->folder->node == node)
462                 item->folder->node = NULL;
463
464         folder_item_destroy(item);
465
466         g_node_destroy(node);
467 }
468
469 void folder_item_remove_children(FolderItem *item)
470 {
471         GNode *node, *next;
472
473         cm_return_if_fail(item != NULL);
474         cm_return_if_fail(item->folder != NULL);
475         cm_return_if_fail(item->node != NULL);
476
477         node = item->node->children;
478         while (node != NULL) {
479                 next = node->next;
480                 folder_item_remove(FOLDER_ITEM(node->data));
481                 node = next;
482         }
483 }
484
485 void folder_item_destroy(FolderItem *item)
486 {
487         Folder *folder;
488
489         cm_return_if_fail(item != NULL);
490
491         folder = item->folder;
492         if (folder) {
493                 if (folder->inbox == item)
494                         folder->inbox = NULL;
495                 else if (folder->outbox == item)
496                         folder->outbox = NULL;
497                 else if (folder->draft == item)
498                         folder->draft = NULL;
499                 else if (folder->queue == item)
500                         folder->queue = NULL;
501                 else if (folder->trash == item)
502                         folder->trash = NULL;
503         }
504
505         if (item->cache)
506                 folder_item_free_cache(item, TRUE);
507         if (item->prefs)
508                 folder_item_prefs_free(item->prefs);
509         g_free(item->name);
510         g_free(item->path);
511
512         if (item->folder != NULL) {
513                 if(item->folder->klass->item_destroy) {
514                         item->folder->klass->item_destroy(item->folder, item);
515                 } else {
516                         g_free(item);
517                 }
518         }
519 }
520
521 FolderItem *folder_item_parent(FolderItem *item)
522 {
523         cm_return_val_if_fail(item != NULL, NULL);
524         cm_return_val_if_fail(item->node != NULL, NULL);
525
526         if (item->node->parent == NULL)
527                 return NULL;
528         return (FolderItem *) item->node->parent->data;
529 }
530
531 void folder_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
532 {
533         GList *cur;
534
535         for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
536                 XMLAttr *attr = (XMLAttr *) cur->data;
537
538                 if (!attr || !attr->name || !attr->value) continue;
539                 if (!strcmp(attr->name, "type")) {
540                         if (!g_ascii_strcasecmp(attr->value, "normal"))
541                                 item->stype = F_NORMAL;
542                         else if (!g_ascii_strcasecmp(attr->value, "inbox"))
543                                 item->stype = F_INBOX;
544                         else if (!g_ascii_strcasecmp(attr->value, "outbox"))
545                                 item->stype = F_OUTBOX;
546                         else if (!g_ascii_strcasecmp(attr->value, "draft"))
547                                 item->stype = F_DRAFT;
548                         else if (!g_ascii_strcasecmp(attr->value, "queue"))
549                                 item->stype = F_QUEUE;
550                         else if (!g_ascii_strcasecmp(attr->value, "trash"))
551                                 item->stype = F_TRASH;
552                 } else if (!strcmp(attr->name, "name")) {
553                         g_free(item->name);
554                         item->name = g_strdup(attr->value);
555                 } else if (!strcmp(attr->name, "path")) {
556                         g_free(item->path);
557                         item->path = g_strdup(attr->value);
558                 } else if (!strcmp(attr->name, "mtime"))
559                         item->mtime = strtoul(attr->value, NULL, 10);
560                 else if (!strcmp(attr->name, "new"))
561                         item->new_msgs = atoi(attr->value);
562                 else if (!strcmp(attr->name, "unread"))
563                         item->unread_msgs = atoi(attr->value);
564                 else if (!strcmp(attr->name, "unreadmarked"))
565                         item->unreadmarked_msgs = atoi(attr->value);
566                 else if (!strcmp(attr->name, "marked"))
567                         item->marked_msgs = atoi(attr->value);
568                 else if (!strcmp(attr->name, "replied"))
569                         item->replied_msgs = atoi(attr->value);
570                 else if (!strcmp(attr->name, "forwarded"))
571                         item->forwarded_msgs = atoi(attr->value);
572                 else if (!strcmp(attr->name, "locked"))
573                         item->locked_msgs = atoi(attr->value);
574                 else if (!strcmp(attr->name, "ignored"))
575                         item->ignored_msgs = atoi(attr->value);
576                 else if (!strcmp(attr->name, "watched"))
577                         item->watched_msgs = atoi(attr->value);
578                 else if (!strcmp(attr->name, "order"))
579                         item->order = atoi(attr->value);
580                 else if (!strcmp(attr->name, "total"))
581                         item->total_msgs = atoi(attr->value);
582                 else if (!strcmp(attr->name, "no_sub"))
583                         item->no_sub = *attr->value == '1' ? TRUE : FALSE;
584                 else if (!strcmp(attr->name, "no_select"))
585                         item->no_select = *attr->value == '1' ? TRUE : FALSE;
586                 else if (!strcmp(attr->name, "collapsed"))
587                         item->collapsed = *attr->value == '1' ? TRUE : FALSE;
588                 else if (!strcmp(attr->name, "thread_collapsed"))
589                         item->thread_collapsed =  *attr->value == '1' ? TRUE : FALSE;
590                 else if (!strcmp(attr->name, "threaded"))
591                         item->threaded =  *attr->value == '1' ? TRUE : FALSE;
592                 else if (!strcmp(attr->name, "hidereadmsgs"))
593                         item->hide_read_msgs =  *attr->value == '1' ? TRUE : FALSE;
594                 else if (!strcmp(attr->name, "hidedelmsgs"))
595                         item->hide_del_msgs =  *attr->value == '1' ? TRUE : FALSE;
596                 else if (!strcmp(attr->name, "hidereadthreads"))
597                         item->hide_read_threads =  *attr->value == '1' ? TRUE : FALSE;
598                 else if (!strcmp(attr->name, "reqretrcpt"))
599                         item->ret_rcpt =  *attr->value == '1' ? TRUE : FALSE;
600                 else if (!strcmp(attr->name, "sort_key")) {
601                         if (!strcmp(attr->value, "none"))
602                                 item->sort_key = SORT_BY_NONE;
603                         else if (!strcmp(attr->value, "number"))
604                                 item->sort_key = SORT_BY_NUMBER;
605                         else if (!strcmp(attr->value, "size"))
606                                 item->sort_key = SORT_BY_SIZE;
607                         else if (!strcmp(attr->value, "date"))
608                                 item->sort_key = SORT_BY_DATE;
609                         else if (!strcmp(attr->value, "from"))
610                                 item->sort_key = SORT_BY_FROM;
611                         else if (!strcmp(attr->value, "subject"))
612                                 item->sort_key = SORT_BY_SUBJECT;
613                         else if (!strcmp(attr->value, "score"))
614                                 item->sort_key = SORT_BY_SCORE;
615                         else if (!strcmp(attr->value, "label"))
616                                 item->sort_key = SORT_BY_LABEL;
617                         else if (!strcmp(attr->value, "mark"))
618                                 item->sort_key = SORT_BY_MARK;
619                         else if (!strcmp(attr->value, "unread"))
620                                 item->sort_key = SORT_BY_STATUS;
621                         else if (!strcmp(attr->value, "mime"))
622                                 item->sort_key = SORT_BY_MIME;
623                         else if (!strcmp(attr->value, "to"))
624                                 item->sort_key = SORT_BY_TO;
625                         else if (!strcmp(attr->value, "locked"))
626                                 item->sort_key = SORT_BY_LOCKED;
627                         else if (!strcmp(attr->value, "tags"))
628                                 item->sort_key = SORT_BY_TAGS;
629                         else if (!strcmp(attr->value, "thread_date"))
630                                 item->sort_key = SORT_BY_THREAD_DATE;
631                 } else if (!strcmp(attr->name, "sort_type")) {
632                         if (!strcmp(attr->value, "ascending"))
633                                 item->sort_type = SORT_ASCENDING;
634                         else
635                                 item->sort_type = SORT_DESCENDING;
636                 } else if (!strcmp(attr->name, "account_id")) {
637                         PrefsAccount *account;
638
639                         account = account_find_from_id(atoi(attr->value));
640                         if (!account)
641                                 g_warning("account_id: %s not found", attr->value);
642                         else
643                                 item->account = account;
644                 } else if (!strcmp(attr->name, "apply_sub")) {
645                         item->apply_sub = *attr->value == '1' ? TRUE : FALSE;
646                 } else if (!strcmp(attr->name, "last_seen")) {
647                         if (!claws_crashed())
648                                 item->last_seen = atoi(attr->value);
649                         else
650                                 item->last_seen = 0;
651                 }
652         }
653         /* options without meaning in drafts */
654         if (item->stype == F_DRAFT)
655                 item->hide_read_msgs =
656                         item->hide_del_msgs =
657                                 item->hide_read_threads = FALSE;
658 }
659
660 XMLTag *folder_item_get_xml(Folder *folder, FolderItem *item)
661 {
662         static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox",
663                                                  "draft", "queue", "trash"};
664         static gchar *sort_key_str[] = {"none", "number", "size", "date",
665                                         "from", "subject", "score", "label",
666                                         "mark", "unread", "mime", "to", 
667                                         "locked", "tags", "thread_date" };
668         XMLTag *tag;
669         gchar *value;
670
671         tag = xml_tag_new("folderitem");
672
673         xml_tag_add_attr(tag, xml_attr_new("type", folder_item_stype_str[item->stype]));
674         if (item->name)
675                 xml_tag_add_attr(tag, xml_attr_new("name", item->name));
676         if (item->path)
677                 xml_tag_add_attr(tag, xml_attr_new("path", item->path));
678         if (item->no_sub)
679                 xml_tag_add_attr(tag, xml_attr_new("no_sub", "1"));
680         if (item->no_select)
681                 xml_tag_add_attr(tag, xml_attr_new("no_select", "1"));
682         xml_tag_add_attr(tag, xml_attr_new("collapsed", item->collapsed && item->node->children ? "1" : "0"));
683         xml_tag_add_attr(tag, xml_attr_new("thread_collapsed", item->thread_collapsed ? "1" : "0"));
684         xml_tag_add_attr(tag, xml_attr_new("threaded", item->threaded ? "1" : "0"));
685         xml_tag_add_attr(tag, xml_attr_new("hidereadmsgs", item->hide_read_msgs ? "1" : "0"));
686         xml_tag_add_attr(tag, xml_attr_new("hidedelmsgs", item->hide_del_msgs ? "1" : "0"));
687         xml_tag_add_attr(tag, xml_attr_new("hidereadthreads", item->hide_read_threads ? "1" : "0"));
688         if (item->ret_rcpt)
689                 xml_tag_add_attr(tag, xml_attr_new("reqretrcpt", "1"));
690
691         if (item->sort_key != SORT_BY_NONE) {
692                 xml_tag_add_attr(tag, xml_attr_new("sort_key", sort_key_str[item->sort_key]));
693                 xml_tag_add_attr(tag, xml_attr_new("sort_type", item->sort_type == SORT_ASCENDING ? "ascending" : "descending"));
694         }
695
696         value = g_strdup_printf("%ld", (unsigned long int) item->mtime);
697         xml_tag_add_attr(tag, xml_attr_new("mtime", value));
698         g_free(value);
699         xml_tag_add_attr(tag, xml_attr_new_int("new", item->new_msgs));
700         xml_tag_add_attr(tag, xml_attr_new_int("unread", item->unread_msgs));
701         xml_tag_add_attr(tag, xml_attr_new_int("unreadmarked", item->unreadmarked_msgs));
702         xml_tag_add_attr(tag, xml_attr_new_int("marked", item->marked_msgs));
703         xml_tag_add_attr(tag, xml_attr_new_int("total", item->total_msgs));
704         xml_tag_add_attr(tag, xml_attr_new_int("replied", item->replied_msgs));
705         xml_tag_add_attr(tag, xml_attr_new_int("forwarded", item->forwarded_msgs));
706         xml_tag_add_attr(tag, xml_attr_new_int("locked", item->locked_msgs));
707         xml_tag_add_attr(tag, xml_attr_new_int("ignore", item->ignored_msgs));
708         xml_tag_add_attr(tag, xml_attr_new_int("watched", item->watched_msgs));
709         xml_tag_add_attr(tag, xml_attr_new_int("order", item->order));
710
711         if (item->account)
712                 xml_tag_add_attr(tag, xml_attr_new_int("account_id", item->account->account_id));
713         if (item->apply_sub)
714                 xml_tag_add_attr(tag, xml_attr_new("apply_sub", "1"));
715
716         xml_tag_add_attr(tag, xml_attr_new_int("last_seen", item->last_seen));
717
718         return tag;
719 }
720
721 void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data)
722 {
723         cm_return_if_fail(folder != NULL);
724
725         folder->ui_func = func;
726         folder->ui_func_data = data;
727 }
728
729 void folder_set_name(Folder *folder, const gchar *name)
730 {
731         cm_return_if_fail(folder != NULL);
732
733         g_free(folder->name);
734         folder->name = name ? g_strdup(name) : NULL;
735         if (folder->node && folder->node->data) {
736                 FolderItem *item = (FolderItem *)folder->node->data;
737
738                 g_free(item->name);
739                 item->name = name ? g_strdup(name) : NULL;
740         }
741 }
742
743 void folder_set_sort(Folder *folder, guint sort)
744 {
745         cm_return_if_fail(folder != NULL);
746
747         if (folder->sort != sort) {
748                 folder_remove(folder);
749                 folder->sort = sort;
750                 folder_add(folder);
751         }
752 }
753
754 static gboolean folder_tree_destroy_func(GNode *node, gpointer data) {
755         FolderItem *item = (FolderItem *) node->data;
756
757         folder_item_destroy(item);
758         return FALSE;
759 }
760
761 void folder_tree_destroy(Folder *folder)
762 {
763         GNode *node;
764
765         cm_return_if_fail(folder != NULL);
766
767         node = folder->node;
768         
769         prefs_filtering_clear_folder(folder);
770
771         if (node != NULL) {
772                 g_node_traverse(node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
773                                 folder_tree_destroy_func, NULL);
774                 g_node_destroy(node);
775                 folder->node = NULL;
776         }
777 }
778
779 void folder_add(Folder *folder)
780 {
781         Folder *cur_folder;
782         GList *cur;
783         gint i;
784         FolderUpdateData hookdata;
785
786         cm_return_if_fail(folder != NULL);
787
788         if ((FOLDER_TYPE(folder) == F_IMAP ||
789              FOLDER_TYPE(folder) == F_NEWS) &&
790             folder->account == NULL) {
791                 return;
792         }
793
794         for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) {
795                 cur_folder = FOLDER(cur->data);
796                 if (cur_folder->sort < folder->sort)
797                         break;
798         }
799
800         folder_list = g_list_insert(folder_list, folder, i);
801
802         hookdata.folder = folder;
803         hookdata.update_flags = FOLDER_ADD_FOLDER;
804         hookdata.item = NULL;
805         hookdata.item2 = NULL;
806         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
807 }
808
809 void folder_remove(Folder *folder)
810 {
811         FolderUpdateData hookdata;
812
813         cm_return_if_fail(folder != NULL);
814
815         folder_list = g_list_remove(folder_list, folder);
816
817         hookdata.folder = folder;
818         hookdata.update_flags = FOLDER_REMOVE_FOLDER;
819         hookdata.item = NULL;
820         hookdata.item2 = NULL;
821         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
822 }
823
824 GList *folder_get_list(void)
825 {
826         return folder_list;
827 }
828
829 gint folder_read_list(void)
830 {
831         GNode *node, *cur;
832         XMLNode *xmlnode;
833         gchar *path;
834         GList *list;
835         gint config_version = -1;
836
837         path = folder_get_list_path();
838         if (!is_file_exist(path)) return -1;
839         node = xml_parse_file(path);
840         if (!node) return -1;
841
842         xmlnode = node->data;
843         if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) {
844                 g_warning("wrong folder list");
845                 xml_free_tree(node);
846                 return -1;
847         }
848
849         cur = node->children;
850         while (cur != NULL) {
851                 Folder *folder;
852
853                 folder = folder_get_from_xml(cur);
854                 if (folder != NULL)
855                         folder_add(folder);
856                 else
857                         folder_unloaded_list = g_slist_append(folder_unloaded_list,
858                                 (gpointer) xml_copy_tree(cur));
859                 cur = cur->next;
860         }
861
862         for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
863                 XMLAttr *attr = list->data;
864
865                 if (!attr || !attr->name || !attr->value) continue;
866                 if (!strcmp(attr->name, "config_version")) {
867                         config_version = atoi(attr->value);
868                         debug_print("Found folderlist config_version %d\n", config_version);
869                 }
870         }
871
872         xml_free_tree(node);
873
874         if (prefs_update_config_version_folderlist(config_version) < 0) {
875                 debug_print("Folderlist configuration file version upgrade failed\n");
876                 return -2;
877         }
878
879         if (folder_list || folder_unloaded_list)
880                 return 0;
881         else
882                 return -1;
883 }
884
885 void folder_write_list(void)
886 {
887         GList *list;
888         GSList *slist;
889         Folder *folder;
890         gchar *path;
891         PrefFile *pfile;
892         GNode *rootnode;
893         XMLNode *xmlnode;
894         XMLTag *tag;
895
896         path = folder_get_list_path();
897         if ((pfile = prefs_write_open(path)) == NULL) return;
898
899         if (xml_file_put_xml_decl(pfile->fp) < 0) {
900                 prefs_file_close_revert(pfile);
901                 g_warning("failed to start write folder list.");
902                 return;         
903         }
904         tag = xml_tag_new("folderlist");
905         xml_tag_add_attr(tag, xml_attr_new_int("config_version",
906                                 CLAWS_CONFIG_VERSION));
907
908         xmlnode = xml_node_new(tag, NULL);
909
910         rootnode = g_node_new(xmlnode);
911
912         for (list = folder_list; list != NULL; list = list->next) {
913                 GNode *node;
914
915                 folder = list->data;
916                 node = folder_get_xml_node(folder);
917                 if (node != NULL)
918                         g_node_append(rootnode, node);
919         }
920
921         for (slist = folder_unloaded_list; slist != NULL; slist = g_slist_next(slist)) {
922                 GNode *node = (GNode *) slist->data;
923
924                 g_node_append(rootnode, (gpointer) xml_copy_tree(node));
925         }
926
927         if (xml_write_tree(rootnode, pfile->fp) < 0) {
928                 prefs_file_close_revert(pfile);
929                 g_warning("failed to write folder list.");
930         } else if (prefs_file_close(pfile) < 0) {
931                 g_warning("failed to write folder list.");
932         }
933         xml_free_tree(rootnode);
934 }
935
936 static gboolean folder_scan_tree_func(GNode *node, gpointer data)
937 {
938         GHashTable *pptable = (GHashTable *)data;
939         FolderItem *item = (FolderItem *)node->data;
940
941         folder_item_restore_persist_prefs(item, pptable);
942         folder_item_scan_full(item, FALSE);
943
944         return FALSE;
945 }
946
947 static gboolean folder_restore_prefs_func(GNode *node, gpointer data)
948 {
949         GHashTable *pptable = (GHashTable *)data;
950         FolderItem *item = (FolderItem *)node->data;
951
952         folder_item_restore_persist_prefs(item, pptable);
953
954         return FALSE;
955 }
956
957 void folder_scan_tree(Folder *folder, gboolean rebuild)
958 {
959         GHashTable *pptable;
960         FolderUpdateData hookdata;
961         Folder *old_folder = folder;
962
963         if (!folder->klass->scan_tree)
964                 return;
965         
966         pptable = folder_persist_prefs_new(folder);
967
968         if (rebuild)
969                 folder_remove(folder);
970
971         if (folder->klass->scan_tree(folder) < 0) {
972                 if (rebuild)
973                         folder_add(old_folder);
974                 return;
975         } else if (rebuild)
976                 folder_add(folder);
977
978         hookdata.folder = folder;
979         hookdata.update_flags = FOLDER_TREE_CHANGED;
980         hookdata.item = NULL;
981         hookdata.item2 = NULL;
982         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
983
984         if (rebuild)
985                 g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, folder_scan_tree_func, pptable);
986         else
987                 g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, folder_restore_prefs_func, pptable);
988
989         folder_persist_prefs_free(pptable);
990
991         prefs_matcher_read_config();
992
993         folder_write_list();
994 }
995
996 FolderItem *folder_create_folder(FolderItem *parent, const gchar *name)
997 {
998         FolderItem *new_item;
999         
1000         cm_return_val_if_fail(parent != NULL, NULL);
1001
1002         new_item = parent->folder->klass->create_folder(parent->folder, parent, name);
1003         if (new_item) {
1004                 FolderUpdateData hookdata;
1005
1006                 new_item->cache = msgcache_new();
1007                 new_item->cache_dirty = TRUE;
1008                 new_item->mark_dirty = TRUE;
1009                 new_item->tags_dirty = TRUE;
1010
1011                 hookdata.folder = new_item->folder;
1012                 hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_ADD_FOLDERITEM;
1013                 hookdata.item = new_item;
1014                 hookdata.item2 = NULL;
1015                 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
1016         }
1017
1018         return new_item;
1019 }
1020
1021 gint folder_item_rename(FolderItem *item, gchar *newname)
1022 {
1023         gint retval;
1024
1025         cm_return_val_if_fail(item != NULL, -1);
1026         cm_return_val_if_fail(newname != NULL, -1);
1027
1028         retval = item->folder->klass->rename_folder(item->folder, item, newname);
1029
1030         if (retval >= 0) {
1031                 FolderItemUpdateData hookdata;
1032                 FolderUpdateData hookdata2;
1033
1034                 hookdata.item = item;
1035                 hookdata.update_flags = F_ITEM_UPDATE_NAME;
1036                 hookdata.msg = NULL;
1037                 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &hookdata);
1038
1039                 hookdata2.folder = item->folder;
1040                 hookdata2.item = item;
1041                 hookdata2.item2 = NULL;
1042                 hookdata2.update_flags = FOLDER_RENAME_FOLDERITEM;
1043                 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata2);
1044         }
1045
1046         return retval;
1047 }
1048
1049 struct TotalMsgCount
1050 {
1051         guint new_msgs;
1052         guint unread_msgs;
1053         guint unreadmarked_msgs;
1054         guint marked_msgs;
1055         guint total_msgs;
1056         guint replied_msgs;
1057         guint forwarded_msgs;
1058         guint locked_msgs;
1059         guint ignored_msgs;
1060         guint watched_msgs;
1061 };
1062
1063 struct FuncToAllFoldersData
1064 {
1065         FolderItemFunc  function;
1066         gpointer        data;
1067 };
1068
1069 static gboolean folder_func_to_all_folders_func(GNode *node, gpointer data)
1070 {
1071         FolderItem *item;
1072         struct FuncToAllFoldersData *function_data = (struct FuncToAllFoldersData *) data;
1073
1074         cm_return_val_if_fail(node->data != NULL, FALSE);
1075
1076         item = FOLDER_ITEM(node->data);
1077         cm_return_val_if_fail(item != NULL, FALSE);
1078
1079         function_data->function(item, function_data->data);
1080
1081         return FALSE;
1082 }
1083
1084 void folder_func_to_all_folders(FolderItemFunc function, gpointer data)
1085 {
1086         GList *list;
1087         Folder *folder;
1088         struct FuncToAllFoldersData function_data;
1089         
1090         function_data.function = function;
1091         function_data.data = data;
1092
1093         for (list = folder_list; list != NULL; list = list->next) {
1094                 folder = FOLDER(list->data);
1095                 if (folder->node)
1096                         g_node_traverse(folder->node, G_PRE_ORDER,
1097                                         G_TRAVERSE_ALL, -1,
1098                                         folder_func_to_all_folders_func,
1099                                         &function_data);
1100         }
1101 }
1102
1103 static void folder_count_total_msgs_func(FolderItem *item, gpointer data)
1104 {
1105         struct TotalMsgCount *count = (struct TotalMsgCount *)data;
1106
1107         count->new_msgs += item->new_msgs;
1108         count->unread_msgs += item->unread_msgs;
1109         count->unreadmarked_msgs += item->unreadmarked_msgs;
1110         count->marked_msgs += item->marked_msgs;
1111         count->total_msgs += item->total_msgs;
1112         count->replied_msgs += item->replied_msgs;
1113         count->forwarded_msgs += item->forwarded_msgs;
1114         count->locked_msgs += item->locked_msgs;
1115         count->ignored_msgs += item->ignored_msgs;
1116         count->watched_msgs += item->watched_msgs;
1117 }
1118
1119 struct TotalMsgStatus
1120 {
1121         guint new;
1122         guint unread;
1123         guint total;
1124         GString *str;
1125 };
1126
1127 static gboolean folder_get_status_full_all_func(GNode *node, gpointer data)
1128 {
1129         FolderItem *item;
1130         struct TotalMsgStatus *status = (struct TotalMsgStatus *)data;
1131         gchar *id;
1132  
1133         cm_return_val_if_fail(node->data != NULL, FALSE);
1134  
1135         item = FOLDER_ITEM(node->data);
1136
1137         if (!item->path) return FALSE;
1138
1139         status->new += item->new_msgs;
1140         status->unread += item->unread_msgs;
1141         status->total += item->total_msgs;
1142
1143         if (status->str) {
1144                 id = folder_item_get_identifier(item);
1145                 g_string_append_printf(status->str, "%5d %5d %5d %s\n",
1146                                   item->new_msgs, item->unread_msgs,
1147                                   item->total_msgs, id);
1148                 g_free(id);
1149         }
1150  
1151         return FALSE;
1152  }
1153  
1154 static void folder_get_status_full_all(GString *str, guint *new, guint *unread,
1155                                        guint *total)
1156 {
1157         GList *list;
1158         Folder *folder;
1159         struct TotalMsgStatus status;
1160  
1161         status.new = status.unread = status.total = 0;
1162         status.str = str;
1163  
1164         debug_print("Counting total number of messages...\n");
1165  
1166         for (list = folder_list; list != NULL; list = list->next) {
1167                 folder = FOLDER(list->data);
1168                 if (folder->node)
1169                         g_node_traverse(folder->node, G_PRE_ORDER,
1170                                         G_TRAVERSE_ALL, -1,
1171                                         folder_get_status_full_all_func,
1172                                         &status);
1173         }
1174  
1175         *new = status.new;
1176         *unread = status.unread;
1177         *total = status.total;
1178 }
1179
1180 gchar *folder_get_status(GPtrArray *folders, gboolean full)
1181 {
1182         guint new, unread, total;
1183         GString *str;
1184         gint i;
1185         gchar *ret;
1186
1187         new = unread = total = 0;
1188
1189         str = g_string_new(NULL);
1190
1191         if (folders) {
1192                 for (i = 0; i < folders->len; i++) {
1193                         FolderItem *item;
1194
1195                         item = g_ptr_array_index(folders, i);
1196                         new += item->new_msgs;
1197                         unread += item->unread_msgs;
1198                         total += item->total_msgs;
1199
1200                         if (full) {
1201                                 gchar *id;
1202
1203                                 id = folder_item_get_identifier(item);
1204                                 g_string_append_printf(str, "%5d %5d %5d %s\n",
1205                                                   item->new_msgs, item->unread_msgs,
1206                                                   item->total_msgs, id);
1207                                 g_free(id);
1208                         }
1209                 }
1210         } else {
1211                 folder_get_status_full_all(full ? str : NULL,
1212                                            &new, &unread, &total);
1213         }
1214
1215         if (full)
1216                 g_string_append_printf(str, "%5d %5d %5d\n", new, unread, total);
1217         else
1218                 g_string_append_printf(str, "%d %d %d\n", new, unread, total);
1219
1220         ret = str->str;
1221         g_string_free(str, FALSE);
1222  
1223         return ret;
1224 }
1225
1226 void folder_count_total_msgs(guint *new_msgs, guint *unread_msgs, 
1227                              guint *unreadmarked_msgs, guint *marked_msgs,
1228                              guint *total_msgs, guint *replied_msgs,
1229                              guint *forwarded_msgs, guint *locked_msgs,
1230                              guint *ignored_msgs, guint *watched_msgs)
1231 {
1232         struct TotalMsgCount count;
1233
1234         count.new_msgs = count.unread_msgs = count.unreadmarked_msgs = 0;
1235         count.total_msgs = count.replied_msgs = count.forwarded_msgs = 0;
1236         count.locked_msgs = count.ignored_msgs = count.watched_msgs = 0;
1237         count.marked_msgs = 0;
1238
1239         debug_print("Counting total number of messages...\n");
1240
1241         folder_func_to_all_folders(folder_count_total_msgs_func, &count);
1242
1243         *new_msgs = count.new_msgs;
1244         *unread_msgs = count.unread_msgs;
1245         *unreadmarked_msgs = count.unreadmarked_msgs;
1246         *marked_msgs = count.marked_msgs;
1247         *total_msgs = count.total_msgs;
1248         *replied_msgs = count.replied_msgs;
1249         *forwarded_msgs = count.forwarded_msgs;
1250         *locked_msgs = count.locked_msgs;
1251         *ignored_msgs = count.ignored_msgs;
1252         *watched_msgs = count.watched_msgs;
1253 }
1254
1255 Folder *folder_find_from_path(const gchar *path)
1256 {
1257         GList *list;
1258         Folder *folder;
1259
1260         for (list = folder_list; list != NULL; list = list->next) {
1261                 folder = list->data;
1262                 if ((FOLDER_TYPE(folder) == F_MH || 
1263                      FOLDER_TYPE(folder) == F_MBOX) &&
1264                     !path_cmp(LOCAL_FOLDER(folder)->rootpath, path))
1265                         return folder;
1266         }
1267
1268         return NULL;
1269 }
1270
1271 Folder *folder_find_from_name(const gchar *name, FolderClass *klass)
1272 {
1273         GList *list;
1274         Folder *folder;
1275
1276         for (list = folder_list; list != NULL; list = list->next) {
1277                 folder = list->data;
1278                 if (folder->klass == klass && 
1279                     strcmp2(name, folder->name) == 0)
1280                         return folder;
1281         }
1282
1283         return NULL;
1284 }
1285
1286 static gboolean folder_item_find_func(GNode *node, gpointer data)
1287 {
1288         FolderItem *item = node->data;
1289         gpointer *d = data;
1290         const gchar *path = d[0];
1291
1292         if (path_cmp(path, item->path) != 0)
1293                 return FALSE;
1294
1295         d[1] = item;
1296
1297         return TRUE;
1298 }
1299
1300 FolderItem *folder_find_item_from_path(const gchar *path)
1301 {
1302         Folder *folder;
1303         gpointer d[2];
1304         GList *list = folder_get_list();
1305         
1306         folder = list ? list->data:NULL;
1307         
1308         cm_return_val_if_fail(folder != NULL, NULL);
1309
1310         d[0] = (gpointer)path;
1311         d[1] = NULL;
1312         while (d[1] == NULL && list) {
1313                 folder = FOLDER(list->data);
1314                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1315                         folder_item_find_func, d);
1316                 list = list->next;
1317         }
1318         return d[1];
1319 }
1320
1321 static gboolean folder_item_find_func_real_path(GNode *node, gpointer data)
1322 {
1323         FolderItem *item = node->data;
1324         gpointer *d = data;
1325         const gchar *path = d[0];
1326         gchar *tmp = folder_item_get_path(item);
1327         if (path_cmp(path, tmp) != 0) {
1328                 g_free(tmp);
1329                 return FALSE;
1330         }
1331         g_free(tmp);
1332         d[1] = item;
1333
1334         return TRUE;
1335 }
1336
1337 FolderItem *folder_find_item_from_real_path(const gchar *path)
1338 {
1339         Folder *folder;
1340         gpointer d[2];
1341         GList *list = folder_get_list();
1342         
1343         folder = list ? list->data:NULL;
1344         
1345         cm_return_val_if_fail(folder != NULL, NULL);
1346
1347         d[0] = (gpointer)path;
1348         d[1] = NULL;
1349         while (d[1] == NULL && list) {
1350                 folder = FOLDER(list->data);
1351                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1352                         folder_item_find_func_real_path, d);
1353                 list = list->next;
1354         }
1355         return d[1];
1356 }
1357
1358 FolderItem *folder_find_child_item_by_name(FolderItem *item, const gchar *name)
1359 {
1360         GNode *node;
1361         FolderItem *child;
1362
1363         for (node = item->node->children; node != NULL; node = node->next) {
1364                 child = FOLDER_ITEM(node->data);
1365                 if (strcmp2(child->name, name) == 0) {
1366                         return child;
1367                 }
1368         }
1369
1370         return NULL;
1371 }
1372
1373 FolderClass *folder_get_class_from_string(const gchar *str)
1374 {
1375         GSList *classlist;
1376
1377         classlist = folder_get_class_list();
1378         for (; classlist != NULL; classlist = g_slist_next(classlist)) {
1379                 FolderClass *class = (FolderClass *) classlist->data;
1380                 if (g_ascii_strcasecmp(class->idstr, str) == 0)
1381                         return class;
1382         }
1383
1384         return NULL;
1385 }
1386
1387 gchar *folder_get_identifier(Folder *folder)
1388 {
1389         gchar *type_str;
1390
1391         cm_return_val_if_fail(folder != NULL, NULL);
1392
1393         type_str = folder->klass->idstr;
1394         return g_strconcat("#", type_str, "/", folder->name, NULL);
1395 }
1396
1397 gchar *folder_item_get_identifier(FolderItem *item)
1398 {
1399         gchar *id = NULL;
1400         gchar *folder_id = NULL;
1401
1402         cm_return_val_if_fail(item != NULL, NULL);
1403
1404         if (item->path == NULL)
1405                 return NULL;
1406
1407         folder_id = folder_get_identifier(item->folder);
1408         id = g_strconcat(folder_id, "/", item->path, NULL);
1409         g_free(folder_id);
1410
1411         return id;
1412 }
1413
1414 Folder *folder_find_from_identifier(const gchar *identifier)
1415 {
1416         gchar *str;
1417         gchar *p;
1418         gchar *name;
1419         FolderClass *class;
1420
1421         cm_return_val_if_fail(identifier != NULL, NULL);
1422
1423         if (*identifier != '#')
1424                 return NULL;
1425
1426         Xstrdup_a(str, identifier, return NULL);
1427
1428         p = strchr(str, '/');
1429         if (!p)
1430                 return NULL;
1431         *p = '\0';
1432         p++;
1433         class = folder_get_class_from_string(&str[1]);
1434         if (class == NULL)
1435                 return NULL;
1436
1437         name = p;
1438         p = strchr(p, '/');
1439         if (p)
1440                 return NULL;
1441
1442         return folder_find_from_name(name, class);
1443 }
1444
1445 FolderItem *folder_find_item_from_identifier(const gchar *identifier)
1446 {
1447         Folder *folder;
1448         gpointer d[2];
1449         gchar *str;
1450         gchar *p;
1451         gchar *name;
1452         gchar *path;
1453         FolderClass *class;
1454
1455         cm_return_val_if_fail(identifier != NULL, NULL);
1456
1457         if (*identifier != '#')
1458                 return folder_find_item_from_path(identifier);
1459
1460         Xstrdup_a(str, identifier, return NULL);
1461
1462         p = strchr(str, '/');
1463         if (!p)
1464                 return folder_find_item_from_path(identifier);
1465         *p = '\0';
1466         p++;
1467         class = folder_get_class_from_string(&str[1]);
1468         if (class == NULL)
1469                 return folder_find_item_from_path(identifier);
1470
1471         name = p;
1472         p = strchr(p, '/');
1473         if (!p)
1474                 return folder_find_item_from_path(identifier);
1475         *p = '\0';
1476         p++;
1477
1478         folder = folder_find_from_name(name, class);
1479         if (!folder)
1480                 return folder_find_item_from_path(identifier);
1481
1482         path = p;
1483
1484         d[0] = (gpointer)path;
1485         d[1] = NULL;
1486         g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1487                         folder_item_find_func, d);
1488         return d[1];
1489 }
1490
1491 /** Returns the FolderItem from a given identifier
1492  *
1493  * The FolderItem is created if it doesn't already exist.
1494  * If creation failed, the function returns NULL.
1495  * 
1496  * Identifiers are of the form #type/Mailbox/FolderA/FolderB/FolderC
1497  */
1498 FolderItem *folder_get_item_from_identifier(const gchar *identifier)
1499 {
1500         FolderItem *item, *last_parent;
1501         Folder *folder;
1502         gchar *p1, *p2, *str;
1503         size_t len;
1504         FolderClass *class;
1505         gboolean created_something = FALSE;
1506
1507         item = folder_find_item_from_identifier(identifier);
1508         if(item)
1509                 return item;
1510
1511         /* trivial sanity check: need at least # and two slashes */
1512         len = strlen(identifier);
1513         if(len < 3)
1514                 return NULL;
1515
1516         /* make sure identifier ends with a slash */
1517         if(identifier[len-1] == G_DIR_SEPARATOR) {
1518                 Xstrdup_a(str, identifier, return NULL);
1519         }
1520         else {
1521                 Xstrndup_a(str, identifier, len+1, return NULL);
1522                 str[len] = G_DIR_SEPARATOR;
1523         }
1524
1525         /* find folder class */
1526         p1 = strchr(str, G_DIR_SEPARATOR);
1527         if(!p1)
1528                 return NULL;
1529         *p1 = '\0';
1530         class = folder_get_class_from_string(&str[1]);
1531         if(!class)
1532                 return NULL;
1533         *p1 = G_DIR_SEPARATOR;
1534         ++p1;
1535
1536         /* find folder from class and name */
1537         p2 = strchr(p1, G_DIR_SEPARATOR);
1538         if(!p2)
1539                 return NULL;
1540         *p2 = '\0';
1541         folder = folder_find_from_name(p1, class);
1542         if(!folder)
1543                 return NULL;
1544         *p2 = G_DIR_SEPARATOR;
1545         ++p2;
1546         p1 = p2;
1547
1548         /* Now, move forward and make sure all sections in the path exist */
1549         last_parent = folder->node->data;
1550         while((p1 = strchr(p1, G_DIR_SEPARATOR)) != NULL) {
1551                 *p1 = '\0';
1552                 item = folder_find_item_from_identifier(str);
1553                 if(!item) {
1554                         item = folder_create_folder(last_parent, p2);
1555                         if(!item)
1556                                 return NULL;
1557                         debug_print("Created folder '%s'\n", str);
1558                         created_something = TRUE;
1559                         if(prefs_common.inherit_folder_props && (last_parent != item->folder->node->data)) {
1560                                 folder_item_prefs_copy_prefs(last_parent, item);
1561                         }
1562                 }
1563                 last_parent = item;
1564                 *p1 = G_DIR_SEPARATOR;
1565                 ++p1;
1566                 p2 = p1;
1567         }
1568
1569         if(created_something)
1570                 folder_write_list();
1571
1572         return item;
1573 }
1574
1575
1576 /**
1577  * Get a displayable name for a FolderItem
1578  *
1579  * \param item FolderItem for that a name should be created
1580  * \return Displayable name for item, returned string has to
1581  *         be freed
1582  */
1583 gchar *folder_item_get_name(FolderItem *item)
1584 {
1585         gchar *name = NULL;
1586
1587         cm_return_val_if_fail(item != NULL, g_strdup(""));
1588
1589         switch (item->stype) {
1590         case F_INBOX:
1591                 name = g_strdup(!strcmp2(item->name, INBOX_DIR) ? _("Inbox") :
1592                                 item->name);
1593                 break;
1594         case F_OUTBOX:
1595                 name = g_strdup(!strcmp2(item->name, OUTBOX_DIR) ? _("Sent") :
1596                                 item->name);
1597                 break;
1598         case F_QUEUE:
1599                 name = g_strdup(!strcmp2(item->name, QUEUE_DIR) ? _("Queue") :
1600                                 item->name);
1601                 break;
1602         case F_TRASH:
1603                 name = g_strdup(!strcmp2(item->name, TRASH_DIR) ? _("Trash") :
1604                                 item->name);
1605                 break;
1606         case F_DRAFT:
1607                 name = g_strdup(!strcmp2(item->name, DRAFT_DIR) ? _("Drafts") :
1608                                 item->name);
1609                 break;
1610         default:
1611                 break;
1612         }
1613
1614         if (name == NULL) {
1615                 /*
1616                  * should probably be done by a virtual function,
1617                  * the folder knows the ui string and how to abbrev
1618                 */
1619                 if (folder_item_parent(item) == NULL) {
1620                         name = g_strconcat(item->name, " (", item->folder->klass->uistr, ")", NULL);
1621                 } else {
1622                         if (FOLDER_CLASS(item->folder) == news_get_class() &&
1623                             item->path && !strcmp2(item->name, item->path))
1624                                 name = get_abbrev_newsgroup_name
1625                                         (item->path,
1626                                          prefs_common.ng_abbrev_len);
1627                         else
1628                                 name = g_strdup(item->name);
1629                 }
1630         }
1631
1632         if (name == NULL)
1633                 name = g_strdup("");
1634
1635         return name;
1636 }
1637
1638 gboolean folder_have_mailbox (void)
1639 {
1640         GList *cur;
1641         for (cur = folder_list; cur != NULL; cur = g_list_next(cur)) {
1642                 Folder *folder = FOLDER(cur->data);
1643                 if (folder->inbox && folder->outbox)
1644                         return TRUE;
1645         }
1646         return FALSE;
1647 }
1648
1649 FolderItem *folder_get_default_inbox(void)
1650 {
1651         GList *flist;
1652
1653         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1654                 Folder * folder = FOLDER(flist->data);
1655
1656                 if (folder == NULL)
1657                         continue;
1658                 if (folder->inbox == NULL)
1659                         continue;
1660                 if (folder->klass->type == F_UNKNOWN)
1661                         continue;
1662
1663                 return folder->inbox;
1664         }
1665
1666         return NULL;
1667 }
1668
1669 FolderItem *folder_get_default_inbox_for_class(FolderType type)
1670 {
1671         GList *flist;
1672
1673         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1674                 Folder * folder = FOLDER(flist->data);
1675
1676                 if (folder == NULL)
1677                         continue;
1678                 if (folder->inbox == NULL)
1679                         continue;
1680                 if (folder->klass->type != type)
1681                         continue;
1682
1683                 return folder->inbox;
1684         }
1685
1686         return NULL;
1687 }
1688
1689 FolderItem *folder_get_default_outbox(void)
1690 {
1691         GList *flist;
1692
1693         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1694                 Folder * folder = FOLDER(flist->data);
1695
1696                 if (folder == NULL)
1697                         continue;
1698                 if (folder->outbox == NULL)
1699                         continue;
1700                 if (folder->klass->type == F_UNKNOWN)
1701                         continue;
1702
1703                 return folder->outbox;
1704         }
1705
1706         return NULL;
1707 }
1708
1709 FolderItem *folder_get_default_outbox_for_class(FolderType type)
1710 {
1711         GList *flist;
1712
1713         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1714                 Folder * folder = FOLDER(flist->data);
1715
1716                 if (folder == NULL)
1717                         continue;
1718                 if (folder->outbox == NULL)
1719                         continue;
1720                 if (folder->klass->type != type)
1721                         continue;
1722
1723                 return folder->outbox;
1724         }
1725
1726         return NULL;
1727 }
1728
1729 FolderItem *folder_get_default_draft(void)
1730 {
1731         GList *flist;
1732
1733         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1734                 Folder * folder = FOLDER(flist->data);
1735
1736                 if (folder == NULL)
1737                         continue;
1738                 if (folder->draft == NULL)
1739                         continue;
1740                 if (folder->klass->type == F_UNKNOWN)
1741                         continue;
1742
1743                 return folder->draft;
1744         }
1745
1746         return NULL;
1747 }
1748
1749 FolderItem *folder_get_default_draft_for_class(FolderType type)
1750 {
1751         GList *flist;
1752
1753         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1754                 Folder * folder = FOLDER(flist->data);
1755
1756                 if (folder == NULL)
1757                         continue;
1758                 if (folder->draft == NULL)
1759                         continue;
1760                 if (folder->klass->type != type)
1761                         continue;
1762
1763                 return folder->draft;
1764         }
1765
1766         return NULL;
1767 }
1768
1769 FolderItem *folder_get_default_queue(void)
1770 {
1771         GList *flist;
1772
1773         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1774                 Folder * folder = FOLDER(flist->data);
1775
1776                 if (folder == NULL)
1777                         continue;
1778                 if (folder->queue == NULL)
1779                         continue;
1780                 if (folder->klass->type == F_UNKNOWN)
1781                         continue;
1782
1783                 return folder->queue;
1784         }
1785
1786         return NULL;
1787 }
1788
1789 FolderItem *folder_get_default_queue_for_class(FolderType type)
1790 {
1791         GList *flist;
1792
1793         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1794                 Folder * folder = FOLDER(flist->data);
1795
1796                 if (folder == NULL)
1797                         continue;
1798                 if (folder->queue == NULL)
1799                         continue;
1800                 if (folder->klass->type != type)
1801                         continue;
1802
1803                 return folder->queue;
1804         }
1805
1806         return NULL;
1807 }
1808
1809 FolderItem *folder_get_default_trash(void)
1810 {
1811         GList *flist;
1812
1813         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1814                 Folder * folder = FOLDER(flist->data);
1815
1816                 if (folder == NULL)
1817                         continue;
1818                 if (folder->trash == NULL)
1819                         continue;
1820                 if (folder->klass->type == F_UNKNOWN)
1821                         continue;
1822
1823                 return folder->trash;
1824         }
1825
1826         return NULL;
1827 }
1828
1829 FolderItem *folder_get_default_trash_for_class(FolderType type)
1830 {
1831         GList *flist;
1832
1833         for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1834                 Folder * folder = FOLDER(flist->data);
1835
1836                 if (folder == NULL)
1837                         continue;
1838                 if (folder->trash == NULL)
1839                         continue;
1840                 if (folder->klass->type != type)
1841                         continue;
1842
1843                 return folder->trash;
1844         }
1845
1846         return NULL;
1847 }
1848
1849 #define CREATE_FOLDER_IF_NOT_EXIST(member, dir, type)           \
1850 {                                                               \
1851         if (!folder->member) {                                  \
1852                 item = folder_item_new(folder, dir, dir);       \
1853                 item->stype = type;                             \
1854                 folder_item_append(rootitem, item);             \
1855                 folder->member = item;                          \
1856         }                                                       \
1857 }
1858
1859 void folder_set_missing_folders(void)
1860 {
1861         Folder *folder;
1862         FolderItem *rootitem;
1863         FolderItem *item;
1864         GList *list;
1865
1866         for (list = folder_list; list != NULL; list = list->next) {
1867                 folder = list->data;
1868                 if (FOLDER_TYPE(folder) != F_MH) continue;
1869                 rootitem = FOLDER_ITEM(folder->node->data);
1870                 cm_return_if_fail(rootitem != NULL);
1871
1872                 if (folder->inbox && folder->outbox && folder->draft &&
1873                     folder->queue && folder->trash)
1874                         continue;
1875
1876                 if (folder->klass->create_tree(folder) < 0) {
1877                         g_warning("%s: can't create the folder tree.",
1878                                   LOCAL_FOLDER(folder)->rootpath);
1879                         continue;
1880                 }
1881
1882                 CREATE_FOLDER_IF_NOT_EXIST(inbox,  INBOX_DIR,  F_INBOX);
1883                 CREATE_FOLDER_IF_NOT_EXIST(outbox, OUTBOX_DIR, F_OUTBOX);
1884                 CREATE_FOLDER_IF_NOT_EXIST(draft,  DRAFT_DIR,  F_DRAFT);
1885                 CREATE_FOLDER_IF_NOT_EXIST(queue,  QUEUE_DIR,  F_QUEUE);
1886                 CREATE_FOLDER_IF_NOT_EXIST(trash,  TRASH_DIR,  F_TRASH);
1887         }
1888 }
1889
1890 static gboolean folder_unref_account_func(GNode *node, gpointer data)
1891 {
1892         FolderItem *item = node->data;
1893         PrefsAccount *account = data;
1894
1895         if (item->account == account)
1896                 item->account = NULL;
1897
1898         return FALSE;
1899 }
1900
1901 void folder_unref_account_all(PrefsAccount *account)
1902 {
1903         Folder *folder;
1904         GList *list;
1905
1906         if (!account) return;
1907
1908         for (list = folder_list; list != NULL; list = list->next) {
1909                 folder = list->data;
1910                 if (folder->account == account)
1911                         folder->account = NULL;
1912                 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1913                                 folder_unref_account_func, account);
1914         }
1915 }
1916
1917 #undef CREATE_FOLDER_IF_NOT_EXIST
1918
1919 gchar *folder_item_get_path(FolderItem *item)
1920 {
1921         Folder *folder;
1922
1923         cm_return_val_if_fail(item != NULL, NULL);
1924         folder = item->folder;
1925         cm_return_val_if_fail(folder != NULL, NULL);
1926
1927         return folder->klass->item_get_path(folder, item);
1928 }
1929
1930 static gint folder_sort_cache_list_by_msgnum(gconstpointer a, gconstpointer b)
1931 {
1932         MsgInfo *msginfo_a = (MsgInfo *) a;
1933         MsgInfo *msginfo_b = (MsgInfo *) b;
1934
1935         return (msginfo_a->msgnum - msginfo_b->msgnum);
1936 }
1937
1938 static gint folder_sort_folder_list(gconstpointer a, gconstpointer b)
1939 {
1940         guint gint_a = GPOINTER_TO_INT(a);
1941         guint gint_b = GPOINTER_TO_INT(b);
1942         
1943         return (gint_a - gint_b);
1944 }
1945
1946 static gint syncronize_flags(FolderItem *item, MsgInfoList *msglist)
1947 {
1948         GHashTable *relation;
1949         gint ret = 0;
1950         GSList *cur;
1951
1952         if(msglist == NULL)
1953                 return 0;
1954         if(item->folder->klass->get_flags == NULL)
1955                 return 0;
1956         if (item->no_select)
1957                 return 0;
1958
1959         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
1960         if ((ret = item->folder->klass->get_flags(
1961             item->folder, item, msglist, relation)) == 0) {
1962                 gpointer data, old_key;
1963                 MsgInfo *msginfo;
1964                 MsgPermFlags permflags = 0;
1965
1966                 folder_item_update_freeze();
1967                 folder_item_set_batch(item, TRUE);
1968                 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1969                         msginfo = (MsgInfo *) cur->data;
1970                 
1971                         if (g_hash_table_lookup_extended(relation, msginfo, &old_key, &data)) {
1972                                 permflags = GPOINTER_TO_INT(data);
1973
1974                                 if (msginfo->flags.perm_flags != permflags) {
1975                                         procmsg_msginfo_change_flags(msginfo,
1976                                                 permflags & ~msginfo->flags.perm_flags, 0,
1977                                                 ~permflags & msginfo->flags.perm_flags, 0);
1978                                 }
1979                         }
1980                 }
1981                 folder_item_set_batch(item, FALSE);
1982                 folder_item_update_thaw();
1983         }
1984         g_hash_table_destroy(relation); 
1985
1986         return ret;
1987 }
1988
1989 static gint folder_item_syncronize_flags(FolderItem *item)
1990 {
1991         MsgInfoList *msglist = NULL;
1992         GSList *cur;
1993         gint ret = 0;
1994         
1995         cm_return_val_if_fail(item != NULL, -1);
1996         cm_return_val_if_fail(item->folder != NULL, -1);
1997         cm_return_val_if_fail(item->folder->klass != NULL, -1);
1998         if (item->no_select)
1999                 return -1;
2000
2001         item->scanning = ITEM_SCANNING_WITH_FLAGS;
2002
2003         if (item->cache == NULL)
2004                 folder_item_read_cache(item);
2005         
2006         msglist = msgcache_get_msg_list(item->cache);
2007         
2008         ret = syncronize_flags(item, msglist);
2009
2010         for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
2011                 procmsg_msginfo_free((MsgInfo **)&(cur->data));
2012         }
2013         
2014         g_slist_free(msglist);
2015
2016         item->scanning = ITEM_NOT_SCANNING;
2017
2018         return ret;
2019 }
2020
2021 static void folder_item_process_open (FolderItem *item,
2022                                  void (*before_proc_func)(gpointer data),
2023                                  void (*after_proc_func)(gpointer data),
2024                                  gpointer data)
2025 {
2026         gchar *buf;
2027         if (item == NULL)
2028                 return;
2029         if((item->folder->klass->scan_required != NULL) &&
2030            (item->folder->klass->scan_required(item->folder, item))) {
2031                 folder_item_scan_full(item, TRUE);
2032         } else {
2033                 folder_item_syncronize_flags(item);
2034         }
2035         
2036         /* Processing */
2037         if (item->prefs->enable_processing_when_opening) {
2038                 buf = g_strdup_printf(_("Processing (%s)...\n"), 
2039                               item->path ? item->path : item->name);
2040                 g_free(buf);
2041
2042                 if (before_proc_func)
2043                         before_proc_func(data);
2044
2045                 folder_item_apply_processing(item);
2046
2047                 if (after_proc_func)
2048                         after_proc_func(data);
2049         }
2050         item->processing_pending = FALSE;
2051         return; 
2052 }
2053
2054 gint folder_item_open(FolderItem *item)
2055 {
2056         START_TIMING(""); 
2057         if (item->no_select)
2058                 return -1;
2059
2060         if (item->scanning != ITEM_NOT_SCANNING) {
2061                 debug_print("%s is scanning... \n", item->path ? item->path : item->name);
2062                 return -2;
2063         }
2064
2065         item->processing_pending = TRUE;
2066         folder_item_process_open (item, NULL, NULL, NULL);
2067         
2068         item->opened = TRUE;
2069         END_TIMING();
2070         return 0;
2071 }
2072
2073 gint folder_item_close(FolderItem *item)
2074 {
2075         GSList *mlist, *cur;
2076         Folder *folder;
2077         
2078         cm_return_val_if_fail(item != NULL, -1);
2079
2080         if (item->no_select)
2081                 return -1;
2082
2083         if (item->new_msgs) {
2084                 folder_item_update_freeze();
2085                 mlist = folder_item_get_msg_list(item);
2086                 for (cur = mlist ; cur != NULL ; cur = cur->next) {
2087                         MsgInfo * msginfo;
2088
2089                         msginfo = (MsgInfo *) cur->data;
2090                         if (MSG_IS_NEW(msginfo->flags))
2091                                 procmsg_msginfo_unset_flags(msginfo, MSG_NEW, 0);
2092                         procmsg_msginfo_free(&msginfo);
2093                 }
2094                 g_slist_free(mlist);
2095                 folder_item_update_thaw();
2096         }               
2097
2098         folder_item_write_cache(item);
2099         
2100         folder_item_update(item, F_ITEM_UPDATE_MSGCNT);
2101
2102         item->opened = FALSE;
2103         folder = item->folder;
2104
2105         if (folder->klass->close == NULL)
2106                 return 0;
2107
2108         return folder->klass->close(folder, item);
2109 }
2110
2111 static MsgInfoList *get_msginfos(FolderItem *item, MsgNumberList *numlist)
2112 {
2113         MsgInfoList *msglist = NULL;
2114         Folder *folder = item->folder;
2115         if (item->no_select)
2116                 return NULL;
2117         
2118         if (folder->klass->get_msginfos != NULL)
2119                 msglist = folder->klass->get_msginfos(folder, item, numlist);
2120         else {
2121                 MsgNumberList *elem;
2122
2123                 for (elem = numlist; elem != NULL; elem = g_slist_next(elem)) {
2124                         MsgInfo *msginfo;
2125                         guint num;
2126
2127                         num = GPOINTER_TO_INT(elem->data);
2128                         msginfo = folder->klass->get_msginfo(folder, item, num);
2129                         if (msginfo != NULL)
2130                                 msglist = g_slist_prepend(msglist, msginfo);
2131                 }               
2132         }
2133
2134         return msglist;
2135 }
2136
2137 static MsgInfo *get_msginfo(FolderItem *item, guint num)
2138 {
2139         MsgNumberList numlist;
2140         MsgInfoList *msglist;
2141         MsgInfo *msginfo = NULL;
2142
2143         numlist.data = GINT_TO_POINTER(num);
2144         numlist.next = NULL;
2145         msglist = get_msginfos(item, &numlist);
2146         if (msglist != NULL)
2147                 msginfo = procmsg_msginfo_new_ref(msglist->data);
2148         procmsg_msg_list_free(msglist);
2149
2150         return msginfo;
2151 }
2152
2153 gint folder_item_scan_full(FolderItem *item, gboolean filtering)
2154 {
2155         Folder *folder;
2156         GSList *folder_list = NULL, *cache_list = NULL;
2157         GSList *folder_list_cur, *cache_list_cur, *new_list = NULL;
2158         GSList *exists_list = NULL, *elem;
2159         GSList *newmsg_list = NULL;
2160         guint newcnt = 0, unreadcnt = 0, totalcnt = 0;
2161         guint markedcnt = 0, unreadmarkedcnt = 0;
2162         guint repliedcnt = 0, forwardedcnt = 0;
2163         guint lockedcnt = 0, ignoredcnt = 0, watchedcnt = 0;
2164
2165         guint cache_max_num, folder_max_num, cache_cur_num, folder_cur_num;
2166         gboolean update_flags = 0, old_uids_valid = FALSE;
2167         GHashTable *subject_table = NULL;
2168         
2169         cm_return_val_if_fail(item != NULL, -1);
2170         if (item->path == NULL) return -1;
2171
2172         folder = item->folder;
2173
2174         cm_return_val_if_fail(folder != NULL, -1);
2175         cm_return_val_if_fail(folder->klass->get_num_list != NULL, -1);
2176
2177         item->scanning = ITEM_SCANNING_WITH_FLAGS;
2178
2179         debug_print("Scanning folder %s for cache changes.\n", item->path ? item->path : "(null)");
2180         
2181         /* Get list of messages for folder and cache */
2182         if (folder->klass->get_num_list(item->folder, item, &folder_list, &old_uids_valid) < 0) {
2183                 debug_print("Error fetching list of message numbers\n");
2184                 item->scanning = ITEM_NOT_SCANNING;
2185                 return(-1);
2186         }
2187
2188         if(prefs_common.thread_by_subject) {
2189                 subject_table = g_hash_table_new(g_str_hash, g_str_equal);
2190         }
2191         
2192         if (old_uids_valid) {
2193                 if (!item->cache)
2194                         folder_item_read_cache(item);
2195                 cache_list = msgcache_get_msg_list(item->cache);
2196         } else {
2197                 if (item->cache)
2198                         msgcache_destroy(item->cache);
2199                 item->cache = msgcache_new();
2200                 item->cache_dirty = TRUE;
2201                 item->mark_dirty = TRUE;
2202                 item->tags_dirty = TRUE;
2203                 cache_list = NULL;
2204         }
2205
2206         /* Sort both lists */
2207         cache_list = g_slist_sort(cache_list, folder_sort_cache_list_by_msgnum);
2208         folder_list = g_slist_sort(folder_list, folder_sort_folder_list);
2209
2210         cache_list_cur = cache_list;
2211         folder_list_cur = folder_list;
2212
2213         if (cache_list_cur != NULL) {
2214                 GSList *cache_list_last;
2215         
2216                 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
2217                 cache_list_last = g_slist_last(cache_list);
2218                 cache_max_num = ((MsgInfo *)cache_list_last->data)->msgnum;
2219         } else {
2220                 cache_cur_num = G_MAXUINT;
2221                 cache_max_num = 0;
2222         }
2223
2224         if (folder_list_cur != NULL) {
2225                 GSList *folder_list_last;
2226         
2227                 folder_cur_num = GPOINTER_TO_UINT(folder_list_cur->data);
2228                 folder_list_last = g_slist_last(folder_list);
2229                 folder_max_num = GPOINTER_TO_UINT(folder_list_last->data);
2230         } else {
2231                 folder_cur_num = G_MAXUINT;
2232                 folder_max_num = 0;
2233         }
2234
2235         while ((cache_cur_num != G_MAXUINT) || (folder_cur_num != G_MAXUINT)) {
2236                 /*
2237                  *  Message only exists in the folder
2238                  *  Remember message for fetching
2239                  */
2240                 if (folder_cur_num < cache_cur_num) {
2241                         gboolean add = FALSE;
2242
2243                         switch(FOLDER_TYPE(folder)) {
2244                                 case F_NEWS:
2245                                         if (folder_cur_num < cache_max_num)
2246                                                 break;
2247                                         
2248                                         if (folder->account->max_articles == 0) {
2249                                                 add = TRUE;
2250                                         }
2251
2252                                         if (folder_max_num <= folder->account->max_articles) {
2253                                                 add = TRUE;
2254                                         } else if (folder_cur_num > (folder_max_num - folder->account->max_articles)) {
2255                                                 add = TRUE;
2256                                         }
2257                                         break;
2258                                 default:
2259                                         add = TRUE;
2260                                         break;
2261                         }
2262                         
2263                         if (add) {
2264                                 new_list = g_slist_prepend(new_list, GUINT_TO_POINTER(folder_cur_num));
2265                                 debug_print("Remembered message %u for fetching\n", folder_cur_num);
2266                         }
2267
2268                         /* Move to next folder number */
2269                         if (folder_list_cur)
2270                                 folder_list_cur = folder_list_cur->next;
2271
2272                         if (folder_list_cur != NULL)
2273                                 folder_cur_num = GPOINTER_TO_UINT(folder_list_cur->data);
2274                         else
2275                                 folder_cur_num = G_MAXUINT;
2276
2277                         continue;
2278                 }
2279
2280                 /*
2281                  *  Message only exists in the cache
2282                  *  Remove the message from the cache
2283                  */
2284                 if (cache_cur_num < folder_cur_num) {
2285                         msgcache_remove_msg(item->cache, cache_cur_num);
2286                         debug_print("Removed message %u from cache.\n", cache_cur_num);
2287
2288                         /* Move to next cache number */
2289                         if (cache_list_cur)
2290                                 cache_list_cur = cache_list_cur->next;
2291
2292                         if (cache_list_cur != NULL)
2293                                 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
2294                         else
2295                                 cache_cur_num = G_MAXUINT;
2296
2297                         update_flags |= F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT;
2298
2299                         continue;
2300                 }
2301
2302                 /*
2303                  *  Message number exists in folder and cache!
2304                  *  Check if the message has been modified
2305                  */
2306                 if (cache_cur_num == folder_cur_num) {
2307                         MsgInfo *msginfo;
2308
2309                         msginfo = msgcache_get_msg(item->cache, folder_cur_num);
2310                         if (msginfo && folder->klass->is_msg_changed && folder->klass->is_msg_changed(folder, item, msginfo)) {
2311                                 msgcache_remove_msg(item->cache, msginfo->msgnum);
2312                                 new_list = g_slist_prepend(new_list, GINT_TO_POINTER(msginfo->msgnum));
2313                                 procmsg_msginfo_free(&msginfo);
2314
2315                                 debug_print("Remembering message %u to update...\n", folder_cur_num);
2316                         } else if (msginfo) {
2317                                 exists_list = g_slist_prepend(exists_list, msginfo);
2318
2319                                 if(prefs_common.thread_by_subject &&
2320                                         MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2321                                         !subject_table_lookup(subject_table, msginfo->subject)) {
2322                                         subject_table_insert(subject_table, msginfo->subject, msginfo);
2323                                 }
2324                         }
2325                         
2326                         /* Move to next folder and cache number */
2327                         if (cache_list_cur)
2328                                 cache_list_cur = cache_list_cur->next;
2329                         
2330                         if (folder_list_cur)
2331                                 folder_list_cur = folder_list_cur->next;
2332
2333                         if (cache_list_cur != NULL)
2334                                 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
2335                         else
2336                                 cache_cur_num = G_MAXUINT;
2337
2338                         if (folder_list_cur != NULL)
2339                                 folder_cur_num = GPOINTER_TO_UINT(folder_list_cur->data);
2340                         else
2341                                 folder_cur_num = G_MAXUINT;
2342
2343                         continue;
2344                 }
2345         }
2346         
2347         for(cache_list_cur = cache_list; cache_list_cur != NULL; cache_list_cur = g_slist_next(cache_list_cur))
2348                 procmsg_msginfo_free((MsgInfo **)&(cache_list_cur->data));
2349
2350         g_slist_free(cache_list);
2351         g_slist_free(folder_list);
2352
2353         if (new_list != NULL) {
2354                 GSList *tmp_list = NULL;
2355                 newmsg_list = get_msginfos(item, new_list);
2356                 g_slist_free(new_list);
2357                 tmp_list = g_slist_concat(g_slist_copy(exists_list), g_slist_copy(newmsg_list));
2358                 syncronize_flags(item, tmp_list);
2359                 g_slist_free(tmp_list);
2360         } else {
2361                 syncronize_flags(item, exists_list);
2362         }
2363
2364         folder_item_update_freeze();
2365         
2366         item->scanning = ITEM_SCANNING;
2367
2368         if (newmsg_list != NULL) {
2369                 GSList *elem, *to_filter = NULL;
2370                 gboolean do_filter = (filtering == TRUE) &&
2371                                         (item->stype == F_INBOX) &&
2372                                         (item->folder->account != NULL) && 
2373                                         (item->folder->account->filter_on_recv);
2374                 
2375                 for (elem = newmsg_list; elem != NULL; elem = g_slist_next(elem)) {
2376                         MsgInfo *msginfo = (MsgInfo *) elem->data;
2377
2378                         msgcache_add_msg(item->cache, msginfo);
2379                         if (!do_filter) {
2380                                 exists_list = g_slist_prepend(exists_list, msginfo);
2381
2382                                 if(prefs_common.thread_by_subject &&
2383                                         MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2384                                         !subject_table_lookup(subject_table, msginfo->subject)) {
2385                                         subject_table_insert(subject_table, msginfo->subject, msginfo);
2386                                 }                       
2387                         }
2388                 }
2389
2390                 if (do_filter) {
2391                         GSList *unfiltered;
2392                         
2393                         folder_item_set_batch(item, TRUE);
2394                         procmsg_msglist_filter(newmsg_list, item->folder->account, 
2395                                         &to_filter, &unfiltered, 
2396                                         TRUE);
2397                         folder_item_set_batch(item, FALSE);
2398                         
2399                         filtering_move_and_copy_msgs(newmsg_list);
2400                         if (to_filter != NULL) {
2401                                 for (elem = to_filter; elem; elem = g_slist_next(elem)) {
2402                                         MsgInfo *msginfo = (MsgInfo *)elem->data;
2403                                         procmsg_msginfo_free(&msginfo);
2404                                 }
2405                                 g_slist_free(to_filter);
2406                         }
2407                         if (unfiltered != NULL) {
2408                                 for (elem = unfiltered; elem; elem = g_slist_next(elem)) {
2409                                         MsgInfo *msginfo = (MsgInfo *)elem->data;
2410                                         exists_list = g_slist_prepend(exists_list, msginfo);
2411
2412                                         if(prefs_common.thread_by_subject &&
2413                                                 MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2414                                                 !subject_table_lookup(subject_table, msginfo->subject)) {
2415                                                 subject_table_insert(subject_table, msginfo->subject, msginfo);
2416                                         }
2417                                 }
2418                                 g_slist_free(unfiltered);
2419                         }
2420                         if (prefs_common.real_time_sync)
2421                                 folder_item_synchronise(item);
2422                 } else {
2423                         if (prefs_common.real_time_sync)
2424                                 folder_item_synchronise(item);
2425                 }
2426
2427                 g_slist_free(newmsg_list);
2428
2429                 update_flags |= F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT;
2430         }
2431
2432         folder_item_set_batch(item, TRUE);
2433         for (elem = exists_list; elem != NULL; elem = g_slist_next(elem)) {
2434                 MsgInfo *msginfo, *parent_msginfo;
2435
2436                 msginfo = elem->data;
2437                 if (MSG_IS_IGNORE_THREAD(msginfo->flags) && (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
2438                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
2439                 if (!MSG_IS_IGNORE_THREAD(msginfo->flags) && procmsg_msg_has_flagged_parent(msginfo, MSG_IGNORE_THREAD)) {
2440                         procmsg_msginfo_change_flags(msginfo, MSG_IGNORE_THREAD, 0, MSG_NEW | MSG_UNREAD, 0);
2441                 }
2442                 if (!MSG_IS_WATCH_THREAD(msginfo->flags) && procmsg_msg_has_flagged_parent(msginfo, MSG_WATCH_THREAD)) {
2443                         procmsg_msginfo_set_flags(msginfo, MSG_WATCH_THREAD, 0);
2444                 }
2445                 if(prefs_common.thread_by_subject && !msginfo->inreplyto &&
2446                         !msginfo->references && !MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2447                         (parent_msginfo = subject_table_lookup(subject_table, msginfo->subject)))
2448                 {
2449                         if(MSG_IS_IGNORE_THREAD(parent_msginfo->flags)) {
2450                                 procmsg_msginfo_change_flags(msginfo, MSG_IGNORE_THREAD, 0,
2451                                                 MSG_NEW | MSG_UNREAD, 0);
2452                         }
2453                 }
2454                 if ((folder_has_parent_of_type(item, F_OUTBOX) ||
2455                      folder_has_parent_of_type(item, F_QUEUE)  ||
2456                      folder_has_parent_of_type(item, F_TRASH)) &&
2457                     (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
2458                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
2459                 if (MSG_IS_NEW(msginfo->flags))
2460                         newcnt++;
2461                 if (MSG_IS_UNREAD(msginfo->flags))
2462                         unreadcnt++;
2463                 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
2464                         unreadmarkedcnt++;
2465                 if (MSG_IS_MARKED(msginfo->flags))
2466                         markedcnt++;
2467                 if (MSG_IS_REPLIED(msginfo->flags))
2468                         repliedcnt++;
2469                 if (MSG_IS_FORWARDED(msginfo->flags))
2470                         forwardedcnt++;
2471                 if (MSG_IS_LOCKED(msginfo->flags))
2472                         lockedcnt++;
2473                 if (MSG_IS_IGNORE_THREAD(msginfo->flags))
2474                         ignoredcnt++;
2475                 if (MSG_IS_WATCH_THREAD(msginfo->flags))
2476                         watchedcnt++;
2477
2478                 totalcnt++;
2479
2480                 procmsg_msginfo_free(&msginfo);
2481         }
2482         folder_item_set_batch(item, FALSE);
2483         g_slist_free(exists_list);
2484         
2485         if(prefs_common.thread_by_subject) {
2486                 g_hash_table_destroy(subject_table);
2487         }
2488         
2489         if (item->new_msgs != newcnt || item->unread_msgs != unreadcnt
2490         ||  item->total_msgs != totalcnt || item->marked_msgs != markedcnt
2491         ||  item->unreadmarked_msgs != unreadmarkedcnt
2492         ||  item->replied_msgs != repliedcnt || item->forwarded_msgs != forwardedcnt
2493         ||  item->locked_msgs != lockedcnt || item->ignored_msgs != ignoredcnt
2494         ||  item->watched_msgs != watchedcnt) {
2495                 update_flags |= F_ITEM_UPDATE_CONTENT;
2496         }
2497
2498         item->new_msgs = newcnt;
2499         item->unread_msgs = unreadcnt;
2500         item->total_msgs = totalcnt;
2501         item->unreadmarked_msgs = unreadmarkedcnt;
2502         item->marked_msgs = markedcnt;
2503         item->replied_msgs = repliedcnt;
2504         item->forwarded_msgs = forwardedcnt;
2505         item->locked_msgs = lockedcnt;
2506         item->ignored_msgs = ignoredcnt;
2507         item->watched_msgs = watchedcnt;
2508
2509         update_flags |= F_ITEM_UPDATE_MSGCNT;
2510
2511         folder_item_update(item, update_flags);
2512         folder_item_update_thaw();
2513         
2514         item->scanning = ITEM_NOT_SCANNING;
2515
2516         return 0;
2517 }
2518
2519 gint folder_item_scan(FolderItem *item)
2520 {
2521         return folder_item_scan_full(item, TRUE);
2522 }
2523
2524 static void folder_count_total_cache_memusage(FolderItem *item, gpointer data)
2525 {
2526         gint *memusage = (gint *)data;
2527
2528         if (item->cache == NULL)
2529                 return;
2530         
2531         *memusage += msgcache_get_memory_usage(item->cache);
2532 }
2533
2534 static gint folder_cache_time_compare_func(gconstpointer a, gconstpointer b)
2535 {
2536         FolderItem *fa = (FolderItem *)a;
2537         FolderItem *fb = (FolderItem *)b;
2538         
2539         return (gint) (msgcache_get_last_access_time(fa->cache) - msgcache_get_last_access_time(fb->cache));
2540 }
2541
2542 static void folder_find_expired_caches(FolderItem *item, gpointer data)
2543 {
2544         GSList **folder_item_list = (GSList **)data;
2545         gint difftime, expiretime;
2546         
2547         if (item->cache == NULL)
2548                 return;
2549
2550         if (item->opened > 0)
2551                 return;
2552
2553         difftime = (gint) (time(NULL) - msgcache_get_last_access_time(item->cache));
2554         expiretime = prefs_common.cache_min_keep_time * 60;
2555         debug_print("Cache unused time: %d (Expire time: %d)\n", difftime, expiretime);
2556
2557         if (difftime > expiretime && !item->opened && !item->processing_pending) {
2558                 *folder_item_list = g_slist_insert_sorted(*folder_item_list, item, folder_cache_time_compare_func);
2559         }
2560 }
2561
2562 gboolean folder_item_free_cache(FolderItem *item, gboolean force)
2563 {
2564         cm_return_val_if_fail(item != NULL, TRUE);
2565         
2566         if (item->cache == NULL)
2567                 return TRUE;
2568         
2569         if (item->opened > 0 && !force)
2570                 return FALSE;
2571
2572         folder_item_write_cache(item);
2573         msgcache_destroy(item->cache);
2574         item->cache = NULL;
2575         return TRUE;
2576 }
2577
2578 void folder_clean_cache_memory_force(void)
2579 {
2580         int old_cache_max_mem_usage = prefs_common.cache_max_mem_usage;
2581         int old_cache_min_keep_time = prefs_common.cache_min_keep_time;
2582
2583         prefs_common.cache_max_mem_usage = 0;
2584         prefs_common.cache_min_keep_time = 0;
2585
2586         folder_clean_cache_memory(NULL);
2587
2588         prefs_common.cache_max_mem_usage = old_cache_max_mem_usage;
2589         prefs_common.cache_min_keep_time = old_cache_min_keep_time;
2590 }
2591
2592 void folder_clean_cache_memory(FolderItem *protected_item)
2593 {
2594         gint memusage = 0;
2595
2596         folder_func_to_all_folders(folder_count_total_cache_memusage, &memusage);       
2597         debug_print("Total cache memory usage: %d\n", memusage);
2598         
2599         if (memusage > (prefs_common.cache_max_mem_usage * 1024)) {
2600                 GSList *folder_item_list = NULL, *listitem;
2601                 
2602                 debug_print("Trying to free cache memory\n");
2603
2604                 folder_func_to_all_folders(folder_find_expired_caches, &folder_item_list);      
2605                 listitem = folder_item_list;
2606                 while((listitem != NULL) && (memusage > (prefs_common.cache_max_mem_usage * 1024))) {
2607                         FolderItem *item = (FolderItem *)(listitem->data);
2608                         gint cache_size = 0;
2609                         if (item == protected_item) {
2610                                 listitem = listitem->next;
2611                                 continue;
2612                         }
2613                         debug_print("Freeing cache memory for %s\n", item->path ? item->path : item->name);
2614                         cache_size = msgcache_get_memory_usage(item->cache);
2615                         if (folder_item_free_cache(item, FALSE))
2616                                 memusage -= cache_size;
2617
2618                         listitem = listitem->next;
2619                 }
2620                 g_slist_free(folder_item_list);
2621         }
2622 }
2623
2624 static void folder_item_remove_cached_msg(FolderItem *item, MsgInfo *msginfo)
2625 {
2626         Folder *folder = item->folder;
2627
2628         cm_return_if_fail(folder != NULL);
2629
2630         if (folder->klass->remove_cached_msg == NULL)
2631                 return;
2632         
2633         folder->klass->remove_cached_msg(folder, item, msginfo);
2634 }
2635
2636 static void folder_item_clean_local_files(FolderItem *item, gint days)
2637 {
2638         cm_return_if_fail(item != NULL);
2639         cm_return_if_fail(item->folder != NULL);
2640
2641         if (FOLDER_TYPE(item->folder) == F_IMAP ||
2642             FOLDER_TYPE(item->folder) == F_NEWS) {
2643                 GSList *msglist = folder_item_get_msg_list(item);
2644                 GSList *cur;
2645                 time_t t = time(NULL);
2646                 for (cur = msglist; cur; cur = cur->next) {
2647                         MsgInfo *msginfo = (MsgInfo *)cur->data;
2648                         gint age = (t - msginfo->date_t) / (60*60*24);
2649                         if (age > days)
2650                                 folder_item_remove_cached_msg(item, msginfo);
2651                 }
2652                 procmsg_msg_list_free(msglist);
2653         }
2654 }
2655
2656 static void folder_item_read_cache(FolderItem *item)
2657 {
2658         gchar *cache_file, *mark_file, *tags_file;
2659         START_TIMING("");
2660         cm_return_if_fail(item != NULL);
2661
2662         if (item->path != NULL) {
2663                 cache_file = folder_item_get_cache_file(item);
2664                 mark_file = folder_item_get_mark_file(item);
2665                 tags_file = folder_item_get_tags_file(item);
2666                 item->cache = msgcache_read_cache(item, cache_file);
2667                 item->cache_dirty = FALSE;
2668                 item->mark_dirty = FALSE;
2669                 item->tags_dirty = FALSE;
2670                 if (!item->cache) {
2671                         MsgInfoList *list, *cur;
2672                         guint newcnt = 0, unreadcnt = 0;
2673                         guint markedcnt = 0, unreadmarkedcnt = 0;
2674                         guint repliedcnt = 0, forwardedcnt = 0;
2675                         guint lockedcnt = 0, ignoredcnt = 0;
2676                         guint watchedcnt = 0;
2677                         MsgInfo *msginfo;
2678
2679                         item->cache = msgcache_new();
2680                         item->cache_dirty = TRUE;
2681                         item->mark_dirty = TRUE;
2682                         item->tags_dirty = TRUE;
2683                         folder_item_scan_full(item, TRUE);
2684
2685                         msgcache_read_mark(item->cache, mark_file);
2686
2687                         list = msgcache_get_msg_list(item->cache);
2688                         for (cur = list; cur != NULL; cur = g_slist_next(cur)) {
2689                                 msginfo = cur->data;
2690
2691                                 if (MSG_IS_NEW(msginfo->flags))
2692                                         newcnt++;
2693                                 if (MSG_IS_UNREAD(msginfo->flags))
2694                                         unreadcnt++;
2695                                 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
2696                                         unreadmarkedcnt++;
2697                                 if (MSG_IS_MARKED(msginfo->flags))
2698                                         markedcnt++;
2699                                 if (MSG_IS_REPLIED(msginfo->flags))
2700                                         repliedcnt++;
2701                                 if (MSG_IS_FORWARDED(msginfo->flags))
2702                                         forwardedcnt++;
2703                                 if (MSG_IS_LOCKED(msginfo->flags))
2704                                         lockedcnt++;
2705                                 if (MSG_IS_IGNORE_THREAD(msginfo->flags))
2706                                         ignoredcnt++;
2707                                 if (MSG_IS_WATCH_THREAD(msginfo->flags))
2708                                         watchedcnt++;
2709                                 procmsg_msginfo_unset_flags(msginfo, MSG_FULLY_CACHED, 0);
2710                         }
2711                         item->new_msgs = newcnt;
2712                         item->unread_msgs = unreadcnt;
2713                         item->unreadmarked_msgs = unreadmarkedcnt;
2714                         item->marked_msgs = markedcnt;
2715                         item->replied_msgs = repliedcnt;
2716                         item->forwarded_msgs = forwardedcnt;
2717                         item->locked_msgs = lockedcnt;
2718                         item->ignored_msgs = ignoredcnt;
2719                         item->watched_msgs = watchedcnt;
2720                         procmsg_msg_list_free(list);
2721                 } else
2722                         msgcache_read_mark(item->cache, mark_file);
2723
2724                 msgcache_read_tags(item->cache, tags_file);
2725
2726                 g_free(cache_file);
2727                 g_free(mark_file);
2728                 g_free(tags_file);
2729         } else {
2730                 item->cache = msgcache_new();
2731                 item->cache_dirty = TRUE;
2732                 item->mark_dirty = TRUE;
2733                 item->tags_dirty = TRUE;
2734         }
2735
2736         END_TIMING();
2737         folder_clean_cache_memory(item);
2738 }
2739
2740 void folder_item_write_cache(FolderItem *item)
2741 {
2742         gchar *cache_file = NULL, *mark_file = NULL, *tags_file = NULL;
2743         FolderItemPrefs *prefs;
2744         gint filemode = 0;
2745         gchar *id;
2746         time_t last_mtime = (time_t)0;
2747         gboolean need_scan = FALSE;
2748         
2749         if (!item || !item->path || !item->cache)
2750                 return;
2751
2752         last_mtime = item->mtime;
2753         if (item->folder->klass->set_mtime) {
2754                 if (item->folder->klass->scan_required)
2755                         need_scan = item->folder->klass->scan_required(item->folder, item);
2756                 else
2757                         need_scan = TRUE;
2758         }
2759
2760         id = folder_item_get_identifier(item);
2761         debug_print("Save cache for folder %s\n", id);
2762         g_free(id);
2763
2764         if (item->cache_dirty)
2765                 cache_file = folder_item_get_cache_file(item);
2766         if (item->cache_dirty || item->mark_dirty)
2767                 mark_file = folder_item_get_mark_file(item);
2768         if (item->cache_dirty || item->tags_dirty)
2769                 tags_file = folder_item_get_tags_file(item);
2770         if (msgcache_write(cache_file, mark_file, tags_file, item->cache) < 0) {
2771                 prefs = item->prefs;
2772                 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
2773                         /* for cache file */
2774                         filemode = prefs->folder_chmod;
2775                         if (filemode & S_IRGRP) filemode |= S_IWGRP;
2776                         if (filemode & S_IROTH) filemode |= S_IWOTH;
2777                         if (cache_file != NULL)
2778                                 chmod(cache_file, filemode);
2779                 }
2780         } else {
2781                 item->cache_dirty = FALSE;
2782                 item->mark_dirty = FALSE;
2783                 item->tags_dirty = FALSE;
2784         }
2785
2786         if (!need_scan && item->folder->klass->set_mtime) {
2787                 if (item->mtime == last_mtime) {
2788                         item->folder->klass->set_mtime(item->folder, item);
2789                 }
2790         }
2791
2792         g_free(cache_file);
2793         g_free(mark_file);
2794         g_free(tags_file);
2795 }
2796
2797 MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num)
2798 {
2799         MsgInfo *msginfo = NULL;
2800         
2801         cm_return_val_if_fail(item != NULL, NULL);
2802         if (item->no_select)
2803                 return NULL;
2804         if (!item->cache)
2805                 folder_item_read_cache(item);
2806         
2807         if ((msginfo = msgcache_get_msg(item->cache, num)) != NULL)
2808                 return msginfo;
2809         
2810         msginfo = get_msginfo(item, num);
2811         if (msginfo != NULL) {
2812                 msgcache_add_msg(item->cache, msginfo);
2813                 return msginfo;
2814         }
2815         
2816         return NULL;
2817 }
2818
2819 MsgInfo *folder_item_get_msginfo_by_msgid(FolderItem *item, const gchar *msgid)
2820 {
2821         MsgInfo *msginfo;
2822         
2823         cm_return_val_if_fail(item != NULL, NULL);
2824         cm_return_val_if_fail(msgid != NULL, NULL);
2825         if (item->no_select)
2826                 return NULL;
2827         
2828         if (!item->cache)
2829                 folder_item_read_cache(item);
2830         
2831         if ((msginfo = msgcache_get_msg_by_id(item->cache, msgid)) != NULL)
2832                 return msginfo;
2833
2834         return NULL;
2835 }
2836
2837 GSList *folder_item_get_msg_list(FolderItem *item)
2838 {
2839         cm_return_val_if_fail(item != NULL, NULL);
2840         if (item->no_select)
2841                 return NULL;
2842         
2843         if (item->cache == 0)
2844                 folder_item_read_cache(item);
2845
2846         cm_return_val_if_fail(item->cache != NULL, NULL);
2847         
2848         return msgcache_get_msg_list(item->cache);
2849 }
2850
2851 static void msginfo_set_mime_flags(GNode *node, gpointer data)
2852 {
2853         MsgInfo *msginfo = data;
2854         MimeInfo *mimeinfo = node->data;
2855         
2856         if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT
2857          && (!mimeinfo->subtype || (strcmp(mimeinfo->subtype, "pgp-signature") &&
2858              strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2859              strcmp(mimeinfo->subtype, "pkcs7-signature")))) {
2860                 procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2861         } else if (mimeinfo->disposition == DISPOSITIONTYPE_UNKNOWN && 
2862                  mimeinfo->id == NULL &&
2863                  mimeinfo->type != MIMETYPE_TEXT &&
2864                  mimeinfo->type != MIMETYPE_MULTIPART) {
2865                 if (!mimeinfo->subtype 
2866                 || (strcmp(mimeinfo->subtype, "pgp-signature") && 
2867                     strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2868                     strcmp(mimeinfo->subtype, "pkcs7-signature")))
2869                         procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2870         } else if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE &&
2871                  mimeinfo->id == NULL &&
2872                 (strcmp(mimeinfo->subtype, "pgp-signature") &&
2873                  strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2874                  strcmp(mimeinfo->subtype, "pkcs7-signature")) && 
2875                 (procmime_mimeinfo_get_parameter(mimeinfo, "name") != NULL ||
2876                  procmime_mimeinfo_get_parameter(mimeinfo, "filename") != NULL)) {
2877                 procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2878         } 
2879
2880         /* don't descend below top level message for signed and encrypted info */
2881         if (mimeinfo->type == MIMETYPE_MESSAGE)
2882                 return;
2883
2884         if (privacy_mimeinfo_is_signed(mimeinfo)) {
2885                 procmsg_msginfo_set_flags(msginfo, 0, MSG_SIGNED);
2886         }
2887
2888         if (privacy_mimeinfo_is_encrypted(mimeinfo)) {
2889                 procmsg_msginfo_set_flags(msginfo, 0, MSG_ENCRYPTED);
2890         } else {
2891                 /* searching inside encrypted parts doesn't really make sense */
2892                 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2893         }
2894 }
2895
2896 gchar *folder_item_fetch_msg(FolderItem *item, gint num)
2897 {
2898         Folder *folder;
2899         gchar *msgfile;
2900         MsgInfo *msginfo;
2901
2902         cm_return_val_if_fail(item != NULL, NULL);
2903
2904         folder = item->folder;
2905
2906         cm_return_val_if_fail(folder->klass->fetch_msg != NULL, NULL);
2907         if (item->no_select)
2908                 return NULL;
2909
2910         msgfile = folder->klass->fetch_msg(folder, item, num);
2911
2912         if (msgfile != NULL) {
2913                 msginfo = folder_item_get_msginfo(item, num);
2914                 if ((msginfo != NULL) && !MSG_IS_SCANNED(msginfo->flags)) {
2915                         MimeInfo *mimeinfo;
2916
2917                         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) && 
2918                             !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
2919                                 mimeinfo = procmime_scan_file(msgfile);
2920                         else
2921                                 mimeinfo = procmime_scan_queue_file(msgfile);
2922                         /* check for attachments */
2923                         if (mimeinfo != NULL) { 
2924                                 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2925                                 procmime_mimeinfo_free_all(&mimeinfo);
2926
2927                                 procmsg_msginfo_set_flags(msginfo, 0, MSG_SCANNED);
2928                         }
2929                 }
2930                 procmsg_msginfo_free(&msginfo);
2931         }
2932
2933         return msgfile;
2934 }
2935
2936 gchar *folder_item_fetch_msg_full(FolderItem *item, gint num, gboolean headers,
2937                                   gboolean body)
2938 {
2939         Folder *folder;
2940         gchar *msgfile;
2941         MsgInfo *msginfo;
2942
2943         cm_return_val_if_fail(item != NULL, NULL);
2944         if (item->no_select)
2945                 return NULL;
2946         
2947         folder = item->folder;
2948
2949         if (folder->klass->fetch_msg_full == NULL)
2950                 return folder_item_fetch_msg(item, num);
2951
2952         if (item->prefs->offlinesync && prefs_common.real_time_sync)
2953                 msgfile = folder->klass->fetch_msg_full(folder, item, num, 
2954                                                 TRUE, TRUE);
2955         else
2956                 msgfile = folder->klass->fetch_msg_full(folder, item, num, 
2957                                                 headers, body);
2958
2959         if (msgfile != NULL) {
2960                 msginfo = folder_item_get_msginfo(item, num);
2961                 if ((msginfo != NULL) && !MSG_IS_SCANNED(msginfo->flags)) {
2962                         MimeInfo *mimeinfo;
2963
2964                         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2965                             !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
2966                                 mimeinfo = procmime_scan_file(msgfile);
2967                         else
2968                                 mimeinfo = procmime_scan_queue_file(msgfile);
2969                         /* check for attachments */
2970                         if (mimeinfo != NULL) { 
2971                                 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2972                                 procmime_mimeinfo_free_all(&mimeinfo);
2973
2974                                 procmsg_msginfo_set_flags(msginfo, 0, MSG_SCANNED);
2975                         }
2976                 }
2977                 procmsg_msginfo_free(&msginfo);
2978         }
2979
2980         return msgfile;
2981 }
2982
2983
2984 static gint folder_item_get_msg_num_by_file(FolderItem *dest, const gchar *file)
2985 {
2986         static HeaderEntry hentry[] = {{"Message-ID:",  NULL, TRUE},
2987                                        {NULL,           NULL, FALSE}};
2988         FILE *fp;
2989         MsgInfo *msginfo;
2990         gint msgnum = 0;
2991         gchar buf[BUFFSIZE];
2992
2993         if ((fp = g_fopen(file, "rb")) == NULL)
2994                 return 0;
2995
2996         if ((folder_has_parent_of_type(dest, F_QUEUE)) || 
2997             (folder_has_parent_of_type(dest, F_DRAFT)))
2998                 while (fgets(buf, sizeof(buf), fp) != NULL) {
2999                         /* new way */
3000                         if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
3001                                 strlen("X-Claws-End-Special-Headers:"))) ||
3002                             (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
3003                                 strlen("X-Sylpheed-End-Special-Headers:"))))
3004                                 break;
3005                         /* old way */
3006                         if (buf[0] == '\r' || buf[0] == '\n') break;
3007                         /* from other mailers */
3008                         if (!strncmp(buf, "Date: ", 6)
3009                         ||  !strncmp(buf, "To: ", 4)
3010                         ||  !strncmp(buf, "From: ", 6)
3011                         ||  !strncmp(buf, "Subject: ", 9)) {
3012                                 rewind(fp);
3013                                 break;
3014                         }
3015                 }
3016
3017         procheader_get_header_fields(fp, hentry);
3018         debug_print("looking for %s\n", hentry[0].body);
3019         if (hentry[0].body) {
3020                 extract_parenthesis(hentry[0].body, '<', '>');
3021                 remove_space(hentry[0].body);
3022                 if ((msginfo = msgcache_get_msg_by_id(dest->cache, hentry[0].body)) != NULL) {
3023                         msgnum = msginfo->msgnum;
3024                         procmsg_msginfo_free(&msginfo);
3025
3026                         debug_print("found message as uid %d\n", msgnum);
3027                 }
3028         }
3029         
3030         g_free(hentry[0].body);
3031         hentry[0].body = NULL;
3032         fclose(fp);
3033
3034         return msgnum;
3035 }
3036
3037 static void copy_msginfo_flags(MsgInfo *source, MsgInfo *dest)
3038 {
3039         MsgPermFlags perm_flags = 0;
3040         MsgTmpFlags tmp_flags = 0;
3041
3042         /* create new flags */
3043         if (source != NULL) {
3044                 /* copy original flags */
3045                 perm_flags = source->flags.perm_flags;
3046                 tmp_flags = source->flags.tmp_flags;
3047         } else {
3048                 perm_flags = dest->flags.perm_flags;
3049                 tmp_flags = dest->flags.tmp_flags;
3050         }
3051
3052         /* remove new, unread and deleted in special folders */
3053         if (folder_has_parent_of_type(dest->folder, F_OUTBOX) || 
3054             folder_has_parent_of_type(dest->folder, F_QUEUE) || 
3055             folder_has_parent_of_type(dest->folder, F_DRAFT) || 
3056             folder_has_parent_of_type(dest->folder, F_TRASH))
3057                 perm_flags &= ~(MSG_NEW | MSG_UNREAD | MSG_DELETED);
3058
3059         /* set ignore flag of ignored parent exists */
3060         if (procmsg_msg_has_flagged_parent(dest, MSG_IGNORE_THREAD))
3061                 perm_flags |= MSG_IGNORE_THREAD;
3062
3063         /* unset FULLY_CACHED flags */
3064         perm_flags &= ~MSG_FULLY_CACHED;
3065
3066         if (procmsg_msg_has_flagged_parent(dest, MSG_WATCH_THREAD))
3067                 perm_flags |= MSG_WATCH_THREAD;
3068
3069         /* Unset tmp flags that should not be copied */
3070         tmp_flags &= ~(MSG_MOVE | MSG_COPY | MSG_MOVE_DONE);
3071
3072         /* unset flags that are set but should not */
3073         /* and set new flags */
3074         procmsg_msginfo_change_flags(dest,
3075                                   ~dest->flags.perm_flags & perm_flags,
3076                                   ~dest->flags.tmp_flags  & tmp_flags,
3077                                    dest->flags.perm_flags & ~perm_flags,
3078                                    dest->flags.tmp_flags  & ~tmp_flags);
3079         
3080         if (source && source->tags) {
3081                 g_slist_free(dest->tags);
3082                 dest->tags = g_slist_copy(source->tags);
3083                 folder_item_commit_tags(dest->folder, dest, dest->tags, NULL);
3084         }
3085 }
3086
3087 static void add_msginfo_to_cache(FolderItem *item, MsgInfo *newmsginfo, MsgInfo *flagsource)
3088 {
3089         /* update folder stats */
3090         if (MSG_IS_NEW(newmsginfo->flags))
3091                 item->new_msgs++;
3092         if (MSG_IS_UNREAD(newmsginfo->flags))
3093                 item->unread_msgs++;
3094         if (MSG_IS_UNREAD(newmsginfo->flags) && procmsg_msg_has_marked_parent(newmsginfo))
3095                 item->unreadmarked_msgs++;
3096         if (MSG_IS_MARKED(newmsginfo->flags))
3097                 item->marked_msgs++;
3098         if (MSG_IS_REPLIED(newmsginfo->flags))
3099                 item->replied_msgs++;
3100         if (MSG_IS_FORWARDED(newmsginfo->flags))
3101                 item->forwarded_msgs++;
3102         if (MSG_IS_LOCKED(newmsginfo->flags))
3103                 item->locked_msgs++;
3104         if (MSG_IS_IGNORE_THREAD(newmsginfo->flags))
3105                 item->ignored_msgs++;
3106         if (MSG_IS_WATCH_THREAD(newmsginfo->flags))
3107                 item->watched_msgs++;
3108         item->total_msgs++;
3109
3110         folder_item_update_freeze();
3111
3112         if (!item->cache)
3113                 folder_item_read_cache(item);
3114
3115         msgcache_add_msg(item->cache, newmsginfo);
3116         copy_msginfo_flags(flagsource, newmsginfo);
3117         folder_item_update_with_msg(item,  F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_ADDMSG, newmsginfo);
3118         folder_item_update_thaw();
3119 }
3120
3121 static void remove_msginfo_from_cache(FolderItem *item, MsgInfo *msginfo)
3122 {
3123         MsgInfoUpdate msginfo_update;
3124
3125         if (!item->cache)
3126                 folder_item_read_cache(item);
3127
3128         if (MSG_IS_NEW(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3129                 msginfo->folder->new_msgs--;
3130         if (MSG_IS_UNREAD(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3131                 msginfo->folder->unread_msgs--;
3132         if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
3133                 msginfo->folder->unreadmarked_msgs--;
3134         if (MSG_IS_MARKED(msginfo->flags))
3135                 item->marked_msgs--;
3136         if (MSG_IS_REPLIED(msginfo->flags))
3137                 item->replied_msgs--;
3138         if (MSG_IS_FORWARDED(msginfo->flags))
3139                 item->forwarded_msgs--;
3140         if (MSG_IS_LOCKED(msginfo->flags))
3141                 item->locked_msgs--;
3142         if (MSG_IS_IGNORE_THREAD(msginfo->flags))
3143                 item->ignored_msgs--;
3144         if (MSG_IS_WATCH_THREAD(msginfo->flags))
3145                 item->watched_msgs--;
3146
3147         msginfo->folder->total_msgs--;
3148
3149         msginfo_update.msginfo = msginfo;
3150         msginfo_update.flags = MSGINFO_UPDATE_DELETED;
3151         hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
3152
3153         msgcache_remove_msg(item->cache, msginfo->msgnum);
3154         folder_item_update_with_msg(msginfo->folder, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_REMOVEMSG, msginfo);
3155 }
3156
3157 gint folder_item_add_msg(FolderItem *dest, const gchar *file,
3158                          MsgFlags *flags, gboolean remove_source)
3159 {
3160         GSList file_list;
3161         MsgFileInfo fileinfo;
3162
3163         cm_return_val_if_fail(dest != NULL, -1);
3164         cm_return_val_if_fail(file != NULL, -1);
3165  
3166         fileinfo.msginfo = NULL;
3167         fileinfo.file = (gchar *)file;
3168         fileinfo.flags = flags;
3169         file_list.data = &fileinfo;
3170         file_list.next = NULL;
3171
3172         return folder_item_add_msgs(dest, &file_list, remove_source);
3173 }
3174
3175 gint folder_item_add_msgs(FolderItem *dest, GSList *file_list,
3176                           gboolean remove_source)
3177 {
3178         Folder *folder;
3179         gint ret, num, lastnum = -1;
3180         GSList *file_cur;
3181         GHashTable *relation;
3182         MsgFileInfo *fileinfo = NULL;
3183         gboolean folderscan = FALSE;
3184
3185         cm_return_val_if_fail(dest != NULL, -1);
3186         cm_return_val_if_fail(file_list != NULL, -1);
3187         cm_return_val_if_fail(dest->folder != NULL, -1);
3188         if (dest->no_select)
3189                 return -1;
3190
3191         folder = dest->folder;
3192
3193         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3194
3195         if (folder->klass->add_msgs != NULL) {
3196                 ret = folder->klass->add_msgs(folder, dest, file_list, relation);
3197                 if (ret < 0) {
3198                         g_hash_table_destroy(relation);
3199                         return ret;
3200                 }
3201         } else {
3202                 for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3203                         fileinfo = (MsgFileInfo *) file_cur->data;
3204
3205                         ret = folder->klass->add_msg(folder, dest, fileinfo->file, fileinfo->flags);
3206                         if (ret < 0) {
3207                                 g_hash_table_destroy(relation);
3208                                 return ret;
3209                         }
3210                         g_hash_table_insert(relation, fileinfo, GINT_TO_POINTER(ret));
3211                 }
3212         }
3213
3214         for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3215                 gpointer data, old_key;
3216
3217                 fileinfo = (MsgFileInfo *) file_cur->data;
3218                 if (g_hash_table_lookup_extended(relation, fileinfo, &old_key, &data))
3219                         num = GPOINTER_TO_INT(data);
3220                 else
3221                         num = -1;
3222
3223                 if (num >= 0) {
3224                         MsgInfo *newmsginfo;
3225
3226                         if (num == 0) {
3227                                 if (!folderscan) {
3228                                         folder_item_scan_full(dest, FALSE);
3229                                         folderscan = TRUE;
3230                                 }
3231                                 num = folder_item_get_msg_num_by_file(dest, fileinfo->file);
3232                                 debug_print("got num %d\n", num);
3233                         }
3234
3235                         if (num > lastnum)
3236                                 lastnum = num;
3237
3238                         if (num >= 0 && remove_source) {
3239                                 if (claws_unlink(fileinfo->file) < 0)
3240                                         FILE_OP_ERROR(fileinfo->file, "unlink");
3241                         }
3242
3243                         if (num == 0)
3244                                 continue;
3245
3246                         if (!folderscan && 
3247                             ((newmsginfo = get_msginfo(dest, num)) != NULL)) {
3248                                 add_msginfo_to_cache(dest, newmsginfo, NULL);
3249                                 procmsg_msginfo_free(&newmsginfo);
3250                         } else if ((newmsginfo = msgcache_get_msg(dest->cache, num)) != NULL) {
3251                                 /* TODO: set default flags */
3252                                 procmsg_msginfo_free(&newmsginfo);
3253                         }
3254                 }
3255         }
3256
3257         g_hash_table_destroy(relation);
3258
3259         return lastnum;
3260 }
3261
3262 static FolderItem *folder_item_move_recursive(FolderItem *src, FolderItem *dest, gboolean copy) 
3263 {
3264         GSList *mlist;
3265         FolderItem *new_item;
3266         FolderItem *next_item;
3267         GNode *srcnode;
3268         gchar *old_id, *new_id;
3269         FolderUpdateData hookdata;
3270
3271         /* move messages */
3272         debug_print("%s %s to %s\n", copy?"Copying":"Moving", src->path, dest->path);
3273         new_item = folder_create_folder(dest, src->name);
3274         if (new_item == NULL) {
3275                 g_print("Can't create folder\n");
3276                 return NULL;
3277         }
3278         
3279         if (new_item->folder == NULL)
3280                 new_item->folder = dest->folder;
3281
3282         /* move messages */
3283         log_message(LOG_PROTOCOL, copy ?_("Copying %s to %s...\n"):_("Moving %s to %s...\n"), 
3284                         src->name, new_item->path);
3285
3286         /*copy prefs*/
3287         folder_item_prefs_copy_prefs(src, new_item);
3288         
3289         /* copy internal data */
3290         if (src->folder->klass == new_item->folder->klass &&
3291             src->folder->klass->copy_private_data != NULL)
3292                 src->folder->klass->copy_private_data(src->folder,
3293                                         src, new_item);
3294         new_item->collapsed = src->collapsed;
3295         new_item->thread_collapsed = src->thread_collapsed;
3296         new_item->threaded  = src->threaded;
3297         new_item->ret_rcpt  = src->ret_rcpt;
3298         new_item->hide_read_msgs = src->hide_read_msgs;
3299         new_item->hide_del_msgs = src->hide_del_msgs;
3300         new_item->hide_read_threads = src->hide_read_threads;
3301         new_item->sort_key  = src->sort_key;
3302         new_item->sort_type = src->sort_type;
3303
3304         mlist = folder_item_get_msg_list(src);
3305
3306         if (mlist != NULL) {
3307                 if (copy)
3308                         folder_item_copy_msgs(new_item, mlist);
3309                 else
3310                         folder_item_move_msgs(new_item, mlist);
3311                 procmsg_msg_list_free(mlist);
3312         }
3313
3314         prefs_matcher_write_config();
3315         
3316         /* recurse */
3317         srcnode = src->folder->node;    
3318         srcnode = g_node_find(srcnode, G_PRE_ORDER, G_TRAVERSE_ALL, src);
3319         srcnode = srcnode->children;
3320         while (srcnode != NULL) {
3321                 if (srcnode && srcnode->data) {
3322                         next_item = (FolderItem*) srcnode->data;
3323                         srcnode = srcnode->next;
3324                         if (folder_item_move_recursive(next_item, new_item, copy) == NULL) {
3325                                 return NULL;
3326                         }
3327                 }
3328         }
3329         old_id = folder_item_get_identifier(src);
3330         new_id = folder_item_get_identifier(new_item);
3331
3332         hookdata.folder = src->folder;
3333         hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_MOVE_FOLDERITEM;
3334         hookdata.item = src;
3335         hookdata.item2 = new_item;
3336         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
3337
3338         /* if src supports removing, otherwise only copy folder */
3339         if (src->folder->klass->remove_folder != NULL && !copy) 
3340                 src->folder->klass->remove_folder(src->folder, src);
3341         folder_write_list();
3342
3343         if (!copy) {
3344                 debug_print("updating rules : %s => %s\n", old_id, new_id);
3345                 if (old_id != NULL && new_id != NULL) {
3346                         prefs_filtering_rename_path(old_id, new_id);
3347                         account_rename_path(old_id, new_id);
3348                 }
3349         }
3350         g_free(old_id);
3351         g_free(new_id);
3352         
3353         return new_item;
3354 }
3355
3356 gint folder_item_move_to(FolderItem *src, FolderItem *dest, FolderItem **new_item, gboolean copy)
3357 {
3358         FolderItem *tmp = folder_item_parent(dest);
3359         gchar * src_identifier, * dst_identifier;
3360         gchar * phys_srcpath, * phys_dstpath, *tmppath;
3361
3362         while (tmp) {
3363                 if (tmp == src) {
3364                         return F_MOVE_FAILED_DEST_IS_CHILD;
3365                 }
3366                 tmp = folder_item_parent(tmp);
3367         }
3368
3369         /* both dst and src can be root folders */
3370         src_identifier = folder_item_get_identifier(src);
3371         if (src_identifier == NULL && src->folder && folder_item_parent(src) == NULL) {
3372                 src_identifier = folder_get_identifier(src->folder);
3373         }
3374
3375         dst_identifier = folder_item_get_identifier(dest);
3376         if(dst_identifier == NULL && dest->folder && folder_item_parent(dest) == NULL) {
3377                 dst_identifier = folder_get_identifier(dest->folder);
3378         }
3379
3380         if (src_identifier == NULL || dst_identifier == NULL) {
3381                 debug_print("Can't get identifiers\n");
3382                 return F_MOVE_FAILED;
3383         }
3384
3385         if (src->folder != dest->folder && !copy) {
3386                 return F_MOVE_FAILED_DEST_OUTSIDE_MAILBOX;
3387         }
3388
3389         phys_srcpath = folder_item_get_path(src);
3390         tmppath = folder_item_get_path(dest);
3391         phys_dstpath = g_strconcat(tmppath,
3392                        G_DIR_SEPARATOR_S,
3393                        g_path_get_basename(phys_srcpath),
3394                        NULL);
3395         g_free(tmppath);
3396
3397         if (folder_item_parent(src) == dest || src == dest) {
3398                 g_free(src_identifier);
3399                 g_free(dst_identifier);
3400                 g_free(phys_srcpath);
3401                 g_free(phys_dstpath);
3402                 return F_MOVE_FAILED_DEST_IS_PARENT;
3403         }
3404         debug_print("moving \"%s\" to \"%s\"\n", phys_srcpath, phys_dstpath);
3405         if ((tmp = folder_item_move_recursive(src, dest, copy)) == NULL) {
3406                 return F_MOVE_FAILED;
3407         }
3408         
3409         g_free(src_identifier);
3410         g_free(dst_identifier);
3411         g_free(phys_srcpath);
3412         g_free(phys_dstpath);
3413
3414         *new_item = tmp;
3415
3416         return F_MOVE_OK;
3417 }
3418
3419 struct find_data
3420 {
3421         gboolean found;
3422 };      
3423 static void find_num(gpointer key, gpointer value, gpointer data)
3424 {
3425         struct find_data *fdata = (struct find_data *)data;
3426         if (GPOINTER_TO_INT(value) == 0)
3427                 fdata->found = TRUE;
3428 }
3429
3430 static gboolean some_msgs_have_zero_num(GHashTable *hashtable)
3431 {
3432         struct find_data fdata;
3433         
3434         fdata.found = FALSE;
3435         g_hash_table_foreach(hashtable, find_num, &fdata);
3436         
3437         return fdata.found;
3438 }
3439
3440 /**
3441  * Copy a list of message to a new folder and remove
3442  * source messages if wanted
3443  */
3444 static gint do_copy_msgs(FolderItem *dest, GSList *msglist, gboolean remove_source)
3445 {
3446         Folder *folder;
3447         GSList *l;
3448         gint num, lastnum = -1;
3449         gboolean folderscan = FALSE;
3450         GHashTable *relation;
3451         GSList *not_moved = NULL;
3452         gint total = 0, curmsg = 0;
3453         MsgInfo *msginfo = NULL;
3454
3455         cm_return_val_if_fail(dest != NULL, -1);
3456         cm_return_val_if_fail(msglist != NULL, -1);
3457
3458         folder = dest->folder;
3459
3460         cm_return_val_if_fail(folder->klass->copy_msg != NULL, -1);
3461         if (dest->no_select)
3462                 return -1;
3463
3464         msginfo = (MsgInfo *)msglist->data;
3465         
3466         if (!msginfo)
3467                 return -1;
3468         
3469         if (!MSG_IS_QUEUED(msginfo->flags) && 
3470             MSG_IS_DRAFT(msginfo->flags) && 
3471             folder_has_parent_of_type(dest, F_QUEUE)) {
3472                 GSList *cur = msglist;
3473                 gboolean queue_err = FALSE;
3474                 for (; cur; cur = cur->next) {
3475                         Compose *compose = NULL;
3476                         FolderItem *queue = dest;
3477                         int val = 0;
3478                         
3479                         msginfo = (MsgInfo *)cur->data;
3480                         compose = compose_reedit(msginfo, TRUE);
3481                         if (compose == NULL) {
3482                                 queue_err = TRUE;
3483                                 continue;
3484                         }
3485                         val = compose_queue(compose, NULL, &queue, NULL,
3486                                         FALSE);
3487                         if (val < 0) {
3488                                 queue_err = TRUE;
3489                         } else if (remove_source) {
3490                                 folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
3491                         }
3492                         if (val == 0)
3493                                 compose_close(compose);
3494                 }
3495                 return queue_err ? -1:0;
3496         }
3497
3498         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3499
3500         for (l = msglist ; l != NULL ; l = g_slist_next(l)) {
3501                 MsgInfo * msginfo = (MsgInfo *) l->data;
3502
3503                 if (msginfo->planned_download != 0) {
3504                         int old_planned = msginfo->planned_download;
3505                         partial_unmark(msginfo);
3506                         /* little hack to reenable after */
3507                         msginfo->planned_download = old_planned;
3508                 }
3509         }
3510
3511         /* 
3512          * Copy messages to destination folder and 
3513          * store new message numbers in newmsgnums
3514          */
3515         if (folder->klass->copy_msgs != NULL) {
3516                 if (folder->klass->copy_msgs(folder, dest, msglist, relation) < 0) {
3517                         g_hash_table_destroy(relation);
3518                         return -1;
3519                 }