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