2011-11-26 [pawel] 3.7.10cvs105
[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         /* update folder stats */
3070         if (MSG_IS_NEW(newmsginfo->flags))
3071                 item->new_msgs++;
3072         if (MSG_IS_UNREAD(newmsginfo->flags))
3073                 item->unread_msgs++;
3074         if (MSG_IS_UNREAD(newmsginfo->flags) && procmsg_msg_has_marked_parent(newmsginfo))
3075                 item->unreadmarked_msgs++;
3076         if (MSG_IS_MARKED(newmsginfo->flags))
3077                 item->marked_msgs++;
3078         if (MSG_IS_REPLIED(newmsginfo->flags))
3079                 item->replied_msgs++;
3080         if (MSG_IS_FORWARDED(newmsginfo->flags))
3081                 item->forwarded_msgs++;
3082         if (MSG_IS_LOCKED(newmsginfo->flags))
3083                 item->locked_msgs++;
3084         if (MSG_IS_IGNORE_THREAD(newmsginfo->flags))
3085                 item->ignored_msgs++;
3086         if (MSG_IS_WATCH_THREAD(newmsginfo->flags))
3087                 item->watched_msgs++;
3088         item->total_msgs++;
3089
3090         folder_item_update_freeze();
3091
3092         if (!item->cache)
3093                 folder_item_read_cache(item);
3094
3095         msgcache_add_msg(item->cache, newmsginfo);
3096         copy_msginfo_flags(flagsource, newmsginfo);
3097         folder_item_update_with_msg(item,  F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_ADDMSG, newmsginfo);
3098         folder_item_update_thaw();
3099 }
3100
3101 static void remove_msginfo_from_cache(FolderItem *item, MsgInfo *msginfo)
3102 {
3103         MsgInfoUpdate msginfo_update;
3104
3105         if (!item->cache)
3106                 folder_item_read_cache(item);
3107
3108         if (MSG_IS_NEW(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3109                 msginfo->folder->new_msgs--;
3110         if (MSG_IS_UNREAD(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3111                 msginfo->folder->unread_msgs--;
3112         if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
3113                 msginfo->folder->unreadmarked_msgs--;
3114         if (MSG_IS_MARKED(msginfo->flags))
3115                 item->marked_msgs--;
3116         if (MSG_IS_REPLIED(msginfo->flags))
3117                 item->replied_msgs--;
3118         if (MSG_IS_FORWARDED(msginfo->flags))
3119                 item->forwarded_msgs--;
3120         if (MSG_IS_LOCKED(msginfo->flags))
3121                 item->locked_msgs--;
3122         if (MSG_IS_IGNORE_THREAD(msginfo->flags))
3123                 item->ignored_msgs--;
3124         if (MSG_IS_WATCH_THREAD(msginfo->flags))
3125                 item->watched_msgs--;
3126
3127         msginfo->folder->total_msgs--;
3128
3129         msginfo_update.msginfo = msginfo;
3130         msginfo_update.flags = MSGINFO_UPDATE_DELETED;
3131         hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
3132
3133         msgcache_remove_msg(item->cache, msginfo->msgnum);
3134         folder_item_update_with_msg(msginfo->folder, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_REMOVEMSG, msginfo);
3135 }
3136
3137 gint folder_item_add_msg(FolderItem *dest, const gchar *file,
3138                          MsgFlags *flags, gboolean remove_source)
3139 {
3140         GSList file_list;
3141         MsgFileInfo fileinfo;
3142
3143         cm_return_val_if_fail(dest != NULL, -1);
3144         cm_return_val_if_fail(file != NULL, -1);
3145  
3146         fileinfo.msginfo = NULL;
3147         fileinfo.file = (gchar *)file;
3148         fileinfo.flags = flags;
3149         file_list.data = &fileinfo;
3150         file_list.next = NULL;
3151
3152         return folder_item_add_msgs(dest, &file_list, remove_source);
3153 }
3154
3155 gint folder_item_add_msgs(FolderItem *dest, GSList *file_list,
3156                           gboolean remove_source)
3157 {
3158         Folder *folder;
3159         gint ret, num, lastnum = -1;
3160         GSList *file_cur;
3161         GHashTable *relation;
3162         MsgFileInfo *fileinfo = NULL;
3163         gboolean folderscan = FALSE;
3164
3165         cm_return_val_if_fail(dest != NULL, -1);
3166         cm_return_val_if_fail(file_list != NULL, -1);
3167         cm_return_val_if_fail(dest->folder != NULL, -1);
3168         if (dest->no_select)
3169                 return -1;
3170
3171         folder = dest->folder;
3172
3173         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3174
3175         if (folder->klass->add_msgs != NULL) {
3176                 ret = folder->klass->add_msgs(folder, dest, file_list, relation);
3177                 if (ret < 0) {
3178                         g_hash_table_destroy(relation);
3179                         return ret;
3180                 }
3181         } else {
3182                 for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3183                         fileinfo = (MsgFileInfo *) file_cur->data;
3184
3185                         ret = folder->klass->add_msg(folder, dest, fileinfo->file, fileinfo->flags);
3186                         if (ret < 0) {
3187                                 g_hash_table_destroy(relation);
3188                                 return ret;
3189                         }
3190                         g_hash_table_insert(relation, fileinfo, GINT_TO_POINTER(ret));
3191                 }
3192         }
3193
3194         for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3195                 gpointer data, old_key;
3196
3197                 fileinfo = (MsgFileInfo *) file_cur->data;
3198                 if (g_hash_table_lookup_extended(relation, fileinfo, &old_key, &data))
3199                         num = GPOINTER_TO_INT(data);
3200                 else
3201                         num = -1;
3202
3203                 if (num >= 0) {
3204                         MsgInfo *newmsginfo;
3205
3206                         if (num == 0) {
3207                                 if (!folderscan) {
3208                                         folder_item_scan_full(dest, FALSE);
3209                                         folderscan = TRUE;
3210                                 }
3211                                 num = folder_item_get_msg_num_by_file(dest, fileinfo->file);
3212                                 debug_print("got num %d\n", num);
3213                         }
3214
3215                         if (num > lastnum)
3216                                 lastnum = num;
3217
3218                         if (num >= 0 && remove_source) {
3219                                 if (claws_unlink(fileinfo->file) < 0)
3220                                         FILE_OP_ERROR(fileinfo->file, "unlink");
3221                         }
3222
3223                         if (num == 0)
3224                                 continue;
3225
3226                         if (!folderscan && 
3227                             ((newmsginfo = get_msginfo(dest, num)) != NULL)) {
3228                                 add_msginfo_to_cache(dest, newmsginfo, NULL);
3229                                 procmsg_msginfo_free(newmsginfo);
3230                         } else if ((newmsginfo = msgcache_get_msg(dest->cache, num)) != NULL) {
3231                                 /* TODO: set default flags */
3232                                 procmsg_msginfo_free(newmsginfo);
3233                         }
3234                 }
3235         }
3236
3237         g_hash_table_destroy(relation);
3238
3239         return lastnum;
3240 }
3241
3242 static FolderItem *folder_item_move_recursive(FolderItem *src, FolderItem *dest, gboolean copy) 
3243 {
3244         GSList *mlist;
3245         FolderItem *new_item;
3246         FolderItem *next_item;
3247         GNode *srcnode;
3248         gchar *old_id, *new_id;
3249
3250         /* move messages */
3251         debug_print("%s %s to %s\n", copy?"Copying":"Moving", src->path, dest->path);
3252         new_item = folder_create_folder(dest, src->name);
3253         if (new_item == NULL) {
3254                 g_print("Can't create folder\n");
3255                 return NULL;
3256         }
3257         
3258         if (new_item->folder == NULL)
3259                 new_item->folder = dest->folder;
3260
3261         /* move messages */
3262         log_message(LOG_PROTOCOL, copy ?_("Copying %s to %s...\n"):_("Moving %s to %s...\n"), 
3263                         src->name, new_item->path);
3264
3265         mlist = folder_item_get_msg_list(src);
3266         
3267         if (mlist != NULL) {
3268                 if (copy)
3269                         folder_item_copy_msgs(new_item, mlist);
3270                 else
3271                         folder_item_move_msgs(new_item, mlist);
3272                 procmsg_msg_list_free(mlist);
3273         }
3274         
3275         /*copy prefs*/
3276         folder_item_prefs_copy_prefs(src, new_item);
3277         
3278         /* copy internal data */
3279         if (src->folder->klass == new_item->folder->klass &&
3280             src->folder->klass->copy_private_data != NULL)
3281                 src->folder->klass->copy_private_data(src->folder,
3282                                         src, new_item);
3283         new_item->collapsed = src->collapsed;
3284         new_item->thread_collapsed = src->thread_collapsed;
3285         new_item->threaded  = src->threaded;
3286         new_item->ret_rcpt  = src->ret_rcpt;
3287         new_item->hide_read_msgs = src->hide_read_msgs;
3288         new_item->hide_del_msgs = src->hide_del_msgs;
3289         new_item->hide_read_threads = src->hide_read_threads;
3290         new_item->sort_key  = src->sort_key;
3291         new_item->sort_type = src->sort_type;
3292
3293         prefs_matcher_write_config();
3294         
3295         /* recurse */
3296         srcnode = src->folder->node;    
3297         srcnode = g_node_find(srcnode, G_PRE_ORDER, G_TRAVERSE_ALL, src);
3298         srcnode = srcnode->children;
3299         while (srcnode != NULL) {
3300                 if (srcnode && srcnode->data) {
3301                         next_item = (FolderItem*) srcnode->data;
3302                         srcnode = srcnode->next;
3303                         if (folder_item_move_recursive(next_item, new_item, copy) == NULL) {
3304                                 return NULL;
3305                         }
3306                 }
3307         }
3308         old_id = folder_item_get_identifier(src);
3309         new_id = folder_item_get_identifier(new_item);
3310
3311         /* if src supports removing, otherwise only copy folder */
3312         if (src->folder->klass->remove_folder != NULL && !copy) 
3313                 src->folder->klass->remove_folder(src->folder, src);
3314         folder_write_list();
3315
3316         if (!copy) {
3317                 debug_print("updating rules : %s => %s\n", old_id, new_id);
3318                 if (old_id != NULL && new_id != NULL) {
3319                         prefs_filtering_rename_path(old_id, new_id);
3320                         account_rename_path(old_id, new_id);
3321                 }
3322         }
3323         g_free(old_id);
3324         g_free(new_id);
3325         
3326         return new_item;
3327 }
3328
3329 gint folder_item_move_to(FolderItem *src, FolderItem *dest, FolderItem **new_item, gboolean copy)
3330 {
3331         FolderItem *tmp = folder_item_parent(dest);
3332         gchar * src_identifier, * dst_identifier;
3333         gchar * phys_srcpath, * phys_dstpath, *tmppath;
3334
3335         while (tmp) {
3336                 if (tmp == src) {
3337                         return F_MOVE_FAILED_DEST_IS_CHILD;
3338                 }
3339                 tmp = folder_item_parent(tmp);
3340         }
3341         
3342         tmp = folder_item_parent(src);
3343         
3344         src_identifier = folder_item_get_identifier(src);
3345         dst_identifier = folder_item_get_identifier(dest);
3346         
3347         if(dst_identifier == NULL && dest->folder && folder_item_parent(dest) == NULL) {
3348                 /* dest can be a root folder */
3349                 dst_identifier = folder_get_identifier(dest->folder);
3350         }
3351         if (src_identifier == NULL || dst_identifier == NULL) {
3352                 debug_print("Can't get identifiers\n");
3353                 return F_MOVE_FAILED;
3354         }
3355
3356         if (src->folder != dest->folder && !copy) {
3357                 return F_MOVE_FAILED_DEST_OUTSIDE_MAILBOX;
3358         }
3359
3360         phys_srcpath = folder_item_get_path(src);
3361         tmppath = folder_item_get_path(dest);
3362         phys_dstpath = g_strconcat(tmppath,
3363                        G_DIR_SEPARATOR_S,
3364                        g_path_get_basename(phys_srcpath),
3365                        NULL);
3366         g_free(tmppath);
3367
3368         if (folder_item_parent(src) == dest || src == dest) {
3369                 g_free(src_identifier);
3370                 g_free(dst_identifier);
3371                 g_free(phys_srcpath);
3372                 g_free(phys_dstpath);
3373                 return F_MOVE_FAILED_DEST_IS_PARENT;
3374         }
3375         debug_print("moving \"%s\" to \"%s\"\n", phys_srcpath, phys_dstpath);
3376         if ((tmp = folder_item_move_recursive(src, dest, copy)) == NULL) {
3377                 return F_MOVE_FAILED;
3378         }
3379         
3380         g_free(src_identifier);
3381         g_free(dst_identifier);
3382         g_free(phys_srcpath);
3383         g_free(phys_dstpath);
3384
3385         *new_item = tmp;
3386
3387         return F_MOVE_OK;
3388 }
3389
3390 struct find_data
3391 {
3392         gboolean found;
3393 };      
3394 static void find_num(gpointer key, gpointer value, gpointer data)
3395 {
3396         struct find_data *fdata = (struct find_data *)data;
3397         if (GPOINTER_TO_INT(value) == 0)
3398                 fdata->found = TRUE;
3399 }
3400
3401 static gboolean some_msgs_have_zero_num(GHashTable *hashtable)
3402 {
3403         struct find_data fdata;
3404         
3405         fdata.found = FALSE;
3406         g_hash_table_foreach(hashtable, find_num, &fdata);
3407         
3408         return fdata.found;
3409 }
3410
3411 /**
3412  * Copy a list of message to a new folder and remove
3413  * source messages if wanted
3414  */
3415 static gint do_copy_msgs(FolderItem *dest, GSList *msglist, gboolean remove_source)
3416 {
3417         Folder *folder;
3418         GSList *l;
3419         gint num, lastnum = -1;
3420         gboolean folderscan = FALSE;
3421         GHashTable *relation;
3422         GSList *not_moved = NULL;
3423         gint total = 0, curmsg = 0;
3424         MsgInfo *msginfo = NULL;
3425
3426         cm_return_val_if_fail(dest != NULL, -1);
3427         cm_return_val_if_fail(msglist != NULL, -1);
3428
3429         folder = dest->folder;
3430
3431         cm_return_val_if_fail(folder->klass->copy_msg != NULL, -1);
3432         if (dest->no_select)
3433                 return -1;
3434
3435         msginfo = (MsgInfo *)msglist->data;
3436         
3437         if (!msginfo)
3438                 return -1;
3439         
3440         if (!MSG_IS_QUEUED(msginfo->flags) && 
3441             MSG_IS_DRAFT(msginfo->flags) && 
3442             folder_has_parent_of_type(dest, F_QUEUE)) {
3443                 GSList *cur = msglist;
3444                 gboolean queue_err = FALSE;
3445                 for (; cur; cur = cur->next) {
3446                         Compose *compose = NULL;
3447                         FolderItem *queue = dest;
3448                         int val = 0;
3449                         
3450                         msginfo = (MsgInfo *)cur->data;
3451                         compose = compose_reedit(msginfo, TRUE);
3452                         if (compose == NULL) {
3453                                 queue_err = TRUE;
3454                                 continue;
3455                         }
3456                         val = compose_queue(compose, NULL, &queue, NULL,
3457                                         FALSE);
3458                         if (val < 0) {
3459                                 queue_err = TRUE;
3460                         } else if (remove_source) {
3461                                 folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
3462                         }
3463                         if (val == 0)
3464                                 compose_close(compose);
3465                 }
3466                 return queue_err ? -1:0;
3467         }
3468
3469         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3470
3471         for (l = msglist ; l != NULL ; l = g_slist_next(l)) {
3472                 MsgInfo * msginfo = (MsgInfo *) l->data;
3473
3474                 if (msginfo->planned_download != 0) {
3475                         int old_planned = msginfo->planned_download;
3476                         partial_unmark(msginfo);
3477                         /* little hack to reenable after */
3478                         msginfo->planned_download = old_planned;
3479                 }
3480         }
3481
3482         /* 
3483          * Copy messages to destination folder and 
3484          * store new message numbers in newmsgnums
3485          */
3486         if (folder->klass->copy_msgs != NULL) {
3487                 if (folder->klass->copy_msgs(folder, dest, msglist, relation) < 0) {
3488                         g_hash_table_destroy(relation);
3489                         return -1;
3490                 }
3491         } else {
3492                 MsgInfo * msginfo;
3493                 l = msglist;
3494
3495                 /* immediately stop if src and dest folders are identical */
3496                 if (l != NULL) {
3497                         msginfo = (MsgInfo *) l->data;
3498                         if (msginfo != NULL && msginfo->folder == dest) {
3499                                 g_hash_table_destroy(relation);
3500                                 return -1;
3501                         }
3502                 }
3503
3504                 for (; l != NULL ; l = g_slist_next(l)) {
3505                         msginfo = (MsgInfo *) l->data;
3506
3507                         num = folder->klass->copy_msg(folder, dest, msginfo);
3508                         if (num > 0)
3509                                 g_hash_table_insert(relation, msginfo, GINT_TO_POINTER(num));
3510                         else
3511                                 not_moved = g_slist_prepend(not_moved, msginfo);
3512                 }
3513         }
3514
3515         if (remove_source) {
3516                 MsgInfo *msginfo = (MsgInfo *) msglist->data;
3517                 FolderItem *item = msginfo->folder;
3518                 /*
3519                  * Remove source messages from their folders if
3520                  * copying was successfull and update folder
3521                  * message counts
3522                  */
3523                 if (not_moved == NULL && item->folder->klass->remove_msgs) {
3524                         item->folder->klass->remove_msgs(item->folder,
3525                                                                 msginfo->folder,
3526                                                                 msglist,
3527                                                                 relation);
3528                 }
3529                 for (l = msglist; l != NULL; l = g_slist_next(l)) {
3530                         gpointer old_key, data;
3531                         msginfo = (MsgInfo *) l->data;
3532                         item = msginfo->folder;
3533
3534                         if (g_hash_table_lookup_extended(relation, msginfo, &old_key, &data))
3535                                 num = GPOINTER_TO_INT(data);
3536                         else
3537                                 num = 0;
3538
3539                         if (g_slist_find(not_moved, msginfo))
3540                                 continue;
3541
3542                         if ((num >= 0) && (item->folder->klass->remove_msg != NULL)) {
3543                                 if (!item->folder->klass->remove_msgs)
3544                                         item->folder->klass->remove_msg(item->folder,
3545                                                                 msginfo->folder,
3546                                                                 msginfo->msgnum);
3547                                 if (!item->folder->account || item->folder->account->imap_use_trash) {
3548                                         remove_msginfo_from_cache(item, msginfo);
3549                                 }
3550                         }
3551                 }
3552         }
3553
3554         /* Read cache for dest folder */
3555         if (!dest->cache) folder_item_read_cache(dest);
3556
3557         /* 
3558          * Fetch new MsgInfos for new messages in dest folder,
3559          * add them to the msgcache and update folder message counts
3560          */
3561         if (some_msgs_have_zero_num(relation)) {
3562                 folder_item_scan_full(dest, FALSE);
3563                 folderscan = TRUE;
3564         }
3565
3566         statusbar_print_all(_("Updating cache for %s..."), dest->path ? dest->path : "(null)");
3567         total = g_slist_length(msglist);
3568         
3569         if (FOLDER_TYPE(dest->folder) == F_IMAP && total > 1) {
3570                 folder_item_scan_full(dest, FALSE);
3571                 folderscan = TRUE;
3572         }
3573         folder_item_set_batch(dest, TRUE);
3574         for (l = msglist; l != NULL; l = g_slist_next(l)) {
3575                 MsgInfo *msginfo = (MsgInfo *) l->data;
3576                 gpointer data, old_key;
3577
3578                 if (!msginfo)
3579                         continue;
3580
3581                 if (g_hash_table_lookup_extended(relation, msginfo, &old_key, &data))
3582                         num = GPOINTER_TO_INT(data);
3583                 else
3584                         num = 0;
3585
3586                 statusbar_progress_all(curmsg++,total, 100);
3587                 if (curmsg % 100 == 0)
3588                         GTK_EVENTS_FLUSH();
3589
3590                 if (num >= 0) {
3591                         MsgInfo *newmsginfo = NULL;
3592
3593                         if (!folderscan && num > 0) {
3594                                 newmsginfo = get_msginfo(dest, num);
3595                                 if (newmsginfo != NULL) {
3596                                         add_msginfo_to_cache(dest, newmsginfo, msginfo);
3597                                 }
3598                         }
3599                         if (newmsginfo == NULL) {
3600                                 if (!folderscan) {
3601                                         folder_item_scan_full(dest, FALSE);
3602                                         folderscan = TRUE;
3603                                 }
3604                                 if (msginfo->msgid != NULL) {
3605                                         newmsginfo = folder_item_get_msginfo_by_msgid(dest, msginfo->msgid);
3606                                         if (newmsginfo != NULL) {
3607                                                 copy_msginfo_flags(msginfo, newmsginfo);
3608                                                 num = newmsginfo->msgnum;
3609                                         }
3610                                 }
3611                         }
3612
3613                         if (msginfo->planned_download 
3614                             == POP3_PARTIAL_DLOAD_DELE) {
3615                                 partial_mark_for_delete(newmsginfo);
3616                         }
3617                         if (msginfo->planned_download 
3618                             == POP3_PARTIAL_DLOAD_DLOAD) {
3619                                 partial_mark_for_download(newmsginfo);
3620                         }
3621                         if (!MSG_IS_POSTFILTERED (msginfo->flags)) {
3622                                 procmsg_msginfo_set_flags (   msginfo, MSG_POSTFILTERED, 0);
3623                                 if (newmsginfo) {
3624                                         procmsg_msginfo_set_flags (newmsginfo, MSG_POSTFILTERED, 0);
3625                                         hooks_invoke (MAIL_POSTFILTERING_HOOKLIST, newmsginfo);
3626                                 }
3627                         }
3628                         procmsg_msginfo_free(newmsginfo);
3629
3630
3631                         if (num > lastnum)
3632                                 lastnum = num;
3633                 }
3634         }
3635         folder_item_set_batch(dest, FALSE);
3636         statusbar_progress_all(0,0,0);
3637         statusbar_pop_all();
3638
3639         g_hash_table_destroy(relation);
3640         if (not_moved != NULL) {
3641                 g_slist_free(not_moved);
3642                 return -1;
3643         } else
3644                 return lastnum;
3645 }
3646
3647 /**
3648  * Move a message to a new folder.
3649  *
3650  * \param dest Destination folder
3651  * \param msginfo The message
3652  */
3653 gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
3654 {
3655         GSList list;
3656
3657         cm_return_val_if_fail(dest != NULL, -1);
3658         cm_return_val_if_fail(msginfo != NULL, -1);
3659
3660         list.data = msginfo;
3661         list.next = NULL;
3662
3663         return do_copy_msgs(dest, &list, TRUE);
3664 }
3665
3666 /**
3667  * Move a list of messages to a new folder.
3668  *
3669  * \param dest Destination folder
3670  * \param msglist List of messages
3671  */
3672 gint folder_item_move_msgs(FolderItem *dest, GSList *msglist)
3673 {
3674         gint result = -1;
3675         cm_return_val_if_fail(dest != NULL, -1);
3676         cm_return_val_if_fail(msglist != NULL, -1);
3677         inc_lock();
3678         result = do_copy_msgs(dest, msglist, TRUE);
3679         inc_unlock();
3680         return result;
3681 }
3682
3683 /**
3684  * Copy a message to a new folder.
3685  *
3686  * \param dest Destination folder
3687  * \param msginfo The message
3688  */
3689 gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo)
3690 {
3691         GSList list;
3692
3693         cm_return_val_if_fail(dest != NULL, -1);
3694         cm_return_val_if_fail(msginfo != NULL, -1);
3695     
3696         list.data = msginfo;
3697         list.next = NULL;
3698
3699         return do_copy_msgs(dest, &list, FALSE);
3700 }
3701
3702 /**
3703  * Copy a list of messages to a new folder.
3704  *
3705  * \param dest Destination folder
3706  * \param msglist List of messages
3707  */
3708 gint folder_item_copy_msgs(FolderItem *dest, GSList *msglist)
3709 {
3710         gint result;
3711         cm_return_val_if_fail(dest != NULL, -1);
3712         cm_return_val_if_fail(msglist != NULL, -1);
3713
3714         inc_lock();
3715         result = do_copy_msgs(dest, msglist, FALSE);
3716         inc_unlock();
3717         
3718         return result;
3719 }
3720
3721 gint folder_item_remove_msg(FolderItem *item, gint num)
3722 {
3723         Folder *folder;
3724         gint ret;
3725         MsgInfo *msginfo;
3726
3727         cm_return_val_if_fail(item != NULL, -1);
3728         folder = item->folder;
3729         cm_return_val_if_fail(folder->klass->remove_msg != NULL, -1);
3730         if (item->no_select)
3731                 return -1;
3732
3733         if (!item->cache) folder_item_read_cache(item);
3734
3735         msginfo = msgcache_get_msg(item->cache, num);
3736         if (msginfo && MSG_IS_LOCKED(msginfo->flags)) {
3737                 procmsg_msginfo_free(msginfo);
3738                 return -1;
3739         }
3740         ret = folder->klass->remove_msg(folder, item, num);
3741
3742         if (!item->folder->account || item->folder->account->imap_use_trash) {
3743                 if (msginfo != NULL) {
3744                         remove_msginfo_from_cache(item, msginfo);
3745                         procmsg_msginfo_free(msginfo);
3746                 }
3747         }
3748
3749         return ret;
3750 }
3751
3752 gint folder_item_remove_msgs(FolderItem *item, GSList *msglist)
3753 {
3754         Folder *folder;
3755         gint ret = 0;
3756         GSList *real_list = NULL, *cur = NULL;
3757
3758         cm_return_val_if_fail(item != NULL, -1);
3759         folder = item->folder;
3760         cm_return_val_if_fail(folder != NULL, -1);
3761         if (item->no_select)
3762                 return -1;
3763         inc_lock();
3764         if (!item->cache) folder_item_read_cache(item);
3765
3766         folder_item_update_freeze();
3767         
3768         /* filter out locked mails */
3769         for (cur = msglist; cur; cur = cur->next) {
3770                 MsgInfo *info = (MsgInfo *)cur->data;
3771                 if (!MSG_IS_LOCKED(info->flags))
3772                         real_list = g_slist_prepend(real_list, info);
3773         }
3774
3775         real_list = g_slist_reverse(real_list);
3776
3777         if (item->folder->klass->remove_msgs) {
3778                 ret = item->folder->klass->remove_msgs(item->folder,
3779                                                         item,
3780                                                         real_list,
3781                                                         NULL);
3782         }
3783         while (ret == 0 && real_list != NULL) {
3784                 MsgInfo *msginfo = (MsgInfo *)real_list->data;
3785                 if (msginfo && MSG_IS_LOCKED(msginfo->flags)) {
3786                         real_list = real_list->next;
3787                         continue;
3788                 }
3789                 if (!item->folder->klass->remove_msgs)
3790                         ret = folder_item_remove_msg(item, msginfo->msgnum);
3791                 if (ret != 0) break;
3792                 msgcache_remove_msg(item->cache, msginfo->msgnum);
3793                 real_list = real_list->next;
3794         }
3795         g_slist_free(real_list);
3796         folder_item_scan_full(item, FALSE);
3797         folder_item_update_thaw();
3798         inc_unlock();
3799         return ret;
3800 }
3801
3802 gint folder_item_expunge(FolderItem *item)
3803 {
3804         Folder *folder = item->folder;
3805         gint result = 0;
3806         if (folder == NULL)
3807                 return -1;
3808         if (folder->klass->expunge) {
3809                 GSList *msglist = folder_item_get_msg_list(item);
3810                 GSList *cur;
3811                 result = folder->klass->expunge(folder, item);
3812                 if (result == 0) {
3813                         for (cur = msglist; cur; cur = cur->next) {
3814                                 MsgInfo *msginfo = (MsgInfo *)cur->data;
3815                                 if (MSG_IS_DELETED(msginfo->flags)) {
3816                                         remove_msginfo_from_cache(item, msginfo);
3817                                 }
3818                         }
3819                 }
3820                 procmsg_msg_list_free(msglist);
3821         }
3822         return result;
3823 }
3824
3825 gint folder_item_remove_all_msg(FolderItem *item)
3826 {
3827         Folder *folder;
3828         gint result;
3829
3830         cm_return_val_if_fail(item != NULL, -1);
3831         if (item->no_select)
3832                 return -1;
3833
3834         folder = item->folder;
3835
3836         inc_lock();
3837         if (folder->klass->remove_all_msg != NULL) {
3838                 result = folder->klass->remove_all_msg(folder, item);
3839
3840                 if (result == 0) {
3841                         folder_item_free_cache(item, TRUE);
3842                         item->cache = msgcache_new();
3843                         item->cache_dirty = TRUE;
3844                         item->mark_dirty = TRUE;
3845                         item->tags_dirty = TRUE;
3846                 }
3847         } else {
3848                 MsgInfoList *msglist;
3849
3850                 msglist = folder_item_get_msg_list(item);
3851                 result = folder_item_remove_msgs(item, msglist);
3852                 procmsg_msg_list_free(msglist);
3853         }
3854
3855         if (result == 0) {
3856                 item->new_msgs = 0;
3857                 item->unread_msgs = 0;
3858                 item->unreadmarked_msgs = 0;
3859                 item->marked_msgs = 0;
3860                 item->total_msgs = 0;
3861                 item->replied_msgs = 0;
3862                 item->forwarded_msgs = 0;
3863                 item->locked_msgs = 0;
3864                 item->ignored_msgs = 0;
3865                 item->watched_msgs = 0;
3866                 folder_item_update(item, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT);
3867         }
3868
3869         inc_unlock();
3870         return result;
3871 }
3872
3873 void folder_item_change_msg_flags(FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3874 {
3875         cm_return_if_fail(item != NULL);
3876         cm_return_if_fail(msginfo != NULL);
3877         
3878         item->mark_dirty = TRUE;
3879
3880         if (item->no_select)
3881                 return;
3882         
3883         if (item->folder->klass->change_flags != NULL && item->scanning != ITEM_SCANNING_WITH_FLAGS) {
3884                 item->folder->klass->change_flags(item->folder, item, msginfo, newflags);
3885         } else {
3886                 msginfo->flags.perm_flags = newflags;
3887         }
3888 }
3889
3890 void folder_item_commit_tags(FolderItem *item, MsgInfo *msginfo, GSList *tags_set, GSList *tags_unset)
3891 {
3892         Folder *folder = NULL;
3893
3894         if (!msginfo)
3895                 return;
3896         if (!item)
3897                 return;
3898         if (!tags_set && !tags_unset)
3899                 return;
3900
3901         folder = item->folder;
3902         if (!folder)
3903                 return;
3904         
3905         item->tags_dirty = TRUE;
3906
3907         if (folder->klass->commit_tags == NULL)
3908                 return;
3909         
3910         folder->klass->commit_tags(item, msginfo, tags_set, tags_unset);
3911 }
3912
3913 gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo)
3914 {
3915         Folder *folder;
3916
3917         cm_return_val_if_fail(item != NULL, FALSE);
3918         if (item->no_select)
3919                 return FALSE;
3920
3921         folder = item->folder;
3922
3923         cm_return_val_if_fail(folder->klass->is_msg_changed != NULL, -1);
3924
3925         return folder->klass->is_msg_changed(folder, item, msginfo);
3926 }
3927
3928 void folder_item_discard_cache(FolderItem *item)
3929 {
3930         gchar *dir;
3931         gchar *cache;
3932
3933         if (!item)
3934                 return;
3935
3936         if (item->cache) {
3937                 msgcache_destroy(item->cache);
3938                 item->cache = NULL;
3939         }
3940         dir = folder_item_get_path(item);
3941         if (is_dir_exist(dir))
3942                 remove_all_numbered_files(dir);
3943         g_free(dir);
3944         
3945         cache = folder_item_get_cache_file(item);
3946         if (is_file_exist(cache))
3947                 claws_unlink(cache);
3948         g_free(cache);
3949         
3950 }
3951
3952 static gchar *folder_item_get_cache_file(FolderItem *item)
3953 {
3954         gchar *path;
3955         gchar *file;
3956         gchar *old_file;
3957
3958         cm_return_val_if_fail(item != NULL, NULL);
3959         cm_return_val_if_fail(item->path != NULL, NULL);
3960
3961         path = folder_item_get_path(item);
3962         cm_return_val_if_fail(path != NULL, NULL);
3963         if (!is_dir_exist(path))
3964                 make_dir_hier(path);
3965         file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL);
3966         old_file = g_strconcat(path, G_DIR_SEPARATOR_S, OLD_CACHE_FILE, NULL);
3967
3968         if (!is_file_exist(file) && is_file_exist(old_file))
3969                 move_file(old_file, file, FALSE);
3970         g_free(old_file);
3971         g_free(path);
3972
3973         return file;
3974 }
3975
3976 static gchar *folder_item_get_mark_file(FolderItem *item)
3977 {
3978         gchar *path;
3979         gchar *file;
3980         gchar *old_file;
3981
3982         cm_return_val_if_fail(item != NULL, NULL);
3983         cm_return_val_if_fail(item->path != NULL, NULL);
3984
3985         path = folder_item_get_path(item);
3986         cm_return_val_if_fail(path != NULL, NULL);
3987         if (!is_dir_exist(path))
3988                 make_dir_hier(path);
3989         file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL);
3990         old_file = g_strconcat(path, G_DIR_SEPARATOR_S, OLD_MARK_FILE, NULL);
3991
3992         if (!is_file_exist(file) && is_file_exist(old_file))
3993                 move_file(old_file, file, FALSE);
3994         g_free(old_file);
3995         g_free(path);
3996
3997         return file;
3998 }
3999
4000 static gchar *folder_item_get_tags_file(FolderItem *item)
4001 {
4002         gchar *path;
4003         gchar *identifier;
4004         gchar *file;
4005
4006         /* we save tags files in rc_dir, because tagsrc is there too,
4007          * and storing tags directly in the mailboxes would give strange
4008          * result when using another Claws mailbox from another install
4009          * with different tags. */
4010
4011         cm_return_val_if_fail(item != NULL, NULL);
4012
4013         identifier = folder_item_get_identifier(item);
4014         cm_return_val_if_fail(identifier != NULL, NULL);
4015
4016 #ifdef G_OS_WIN32
4017         while (strchr(identifier, '/'))
4018                 *strchr(identifier, '/') = '\\';
4019 #endif
4020
4021         path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
4022                            "tagsdb", G_DIR_SEPARATOR_S,
4023                            identifier, NULL);
4024         
4025         g_free(identifier);
4026                            
4027         if (!is_dir_exist(path))
4028                 make_dir_hier(path);
4029
4030         file = g_strconcat(path, G_DIR_SEPARATOR_S, TAGS_FILE, NULL);
4031         
4032         g_free(path);
4033
4034         return file;
4035 }
4036
4037 static gpointer xml_to_folder_item(gpointer nodedata, gpointer data)
4038 {
4039         XMLNode *xmlnode = (XMLNode *) nodedata;
4040         Folder *folder = (Folder *) data;
4041         FolderItem *item;
4042
4043         cm_return_val_if_fail(xmlnode != NULL, NULL);
4044         cm_return_val_if_fail(folder != NULL, NULL);
4045
4046         if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
4047                 g_warning("tag name != \"folderitem\"\n");
4048                 return NULL;
4049         }
4050
4051         item = folder_item_new(folder, "", "");
4052         if (folder->klass->item_set_xml != NULL)
4053                 folder->klass->item_set_xml(folder, item, xmlnode->tag);
4054         else
4055                 folder_item_set_xml(folder, item, xmlnode->tag);
4056
4057         item->folder = folder;
4058
4059         switch (item->stype) {
4060         case F_INBOX:  folder->inbox  = item; break;
4061         case F_OUTBOX: folder->outbox = item; break;
4062         case F_DRAFT:  folder->draft  = item; break;
4063         case F_QUEUE:  folder->queue  = item; break;
4064         case F_TRASH:  folder->trash  = item; break;
4065         default:       break;
4066         }
4067         folder_item_prefs_read_config(item);
4068
4069         return item;
4070 }
4071
4072 static gboolean folder_item_set_node(GNode *node, gpointer data)
4073 {
4074         FolderItem *item = (FolderItem *) node->data;
4075         item->node = node;
4076
4077         return FALSE;
4078 }
4079
4080 static Folder *folder_get_from_xml(GNode *node)
4081 {
4082         Folder *folder;
4083         XMLNode *xmlnode;
4084         GList *list;
4085         FolderClass *klass = NULL;
4086         GNode *cur;
4087
4088         cm_return_val_if_fail(node->data != NULL, NULL);
4089
4090         xmlnode = node->data;
4091         if (strcmp2(xmlnode->tag->tag, "folder") != 0) {
4092                 g_warning("tag name != \"folder\"\n");
4093                 return NULL;
4094         }
4095         list = xmlnode->tag->attr;
4096         for (; list != NULL; list = list->next) {
4097                 XMLAttr *attr = list->data;
4098
4099                 if (!attr || !attr->name || !attr->value) continue;
4100                 if (!strcmp(attr->name, "type"))
4101                         klass = folder_get_class_from_string(attr->value);
4102         }
4103         if (klass == NULL)
4104                 return NULL;
4105
4106         folder = folder_new(klass, "", "");
4107         cm_return_val_if_fail(folder != NULL, NULL);
4108
4109         if (klass->set_xml)
4110                 klass->set_xml(folder, xmlnode->tag);
4111         else
4112                 folder_set_xml(folder, xmlnode->tag);
4113
4114         cur = node->children;
4115         while (cur != NULL) {
4116                 GNode *itemnode;
4117
4118                 itemnode = g_node_map(cur, xml_to_folder_item, (gpointer) folder);
4119                 g_node_append(folder->node, itemnode);
4120                 cur = cur->next;
4121         }
4122         g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, folder_item_set_node, NULL);
4123         
4124         return folder;
4125 }
4126
4127 static gchar *folder_get_list_path(void)
4128 {
4129         static gchar *filename = NULL;
4130
4131         if (!filename)
4132                 filename =  g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
4133                                         FOLDER_LIST, NULL);
4134
4135         return filename;
4136 }
4137
4138 static gpointer folder_item_to_xml(gpointer nodedata, gpointer data)
4139 {
4140         FolderItem *item = (FolderItem *) nodedata;
4141         XMLTag *tag;
4142
4143         cm_return_val_if_fail(item != NULL, NULL);
4144
4145         if (item->folder->klass->item_get_xml != NULL)
4146                 tag = item->folder->klass->item_get_xml(item->folder, item);
4147         else
4148                 tag = folder_item_get_xml(item->folder, item);
4149
4150         return xml_node_new(tag, NULL);
4151 }
4152
4153 static GNode *folder_get_xml_node(Folder *folder)
4154 {
4155         GNode *node;
4156         XMLNode *xmlnode;
4157         XMLTag *tag;
4158
4159         cm_return_val_if_fail(folder != NULL, NULL);
4160
4161         if (folder->klass->get_xml != NULL)
4162                 tag = folder->klass->get_xml(folder);
4163         else
4164                 tag = folder_get_xml(folder);
4165
4166         xml_tag_add_attr(tag, xml_attr_new("type", folder->klass->idstr));
4167
4168         xmlnode = xml_node_new(tag, NULL);
4169
4170         node = g_node_new(xmlnode);
4171         
4172         cm_return_val_if_fail (folder->node != NULL, NULL);
4173         
4174         if (folder->node->children) {
4175                 GNode *cur;
4176
4177                 cur = folder->node->children;
4178                 while (cur) {
4179                         GNode *xmlnode;
4180
4181                         xmlnode = g_node_map(cur, folder_item_to_xml, (gpointer) folder);
4182                         g_node_append(node, xmlnode);
4183                         cur = cur->next;
4184                 }
4185         }
4186
4187         return node;
4188 }
4189
4190 static void folder_update_op_count_rec(GNode *node)
4191 {
4192         FolderItem *fitem = FOLDER_ITEM(node->data);
4193
4194         if (g_node_depth(node) > 0) {
4195                 if (fitem->op_count > 0) {
4196                         fitem->op_count = 0;
4197                         folder_item_update(fitem, F_ITEM_UPDATE_MSGCNT);
4198                 }
4199                 if (node->children) {
4200                         GNode *child;
4201
4202                         child = node->children;
4203                         while (child) {
4204                                 GNode *cur;
4205
4206                                 cur = child;
4207                                 child = cur->next;
4208                                 folder_update_op_count_rec(cur);
4209                         }
4210                 }
4211         }
4212 }
4213
4214 void folder_update_op_count(void) 
4215 {
4216         GList *cur;
4217         Folder *folder;
4218
4219         for (cur = folder_list; cur != NULL; cur = cur->next) {
4220                 folder = cur->data;
4221                 folder_update_op_count_rec(folder->node);
4222         }
4223 }
4224
4225 typedef struct _type_str {
4226         gchar * str;
4227         gint type;
4228 } type_str;
4229
4230
4231 /*
4232 static gchar * folder_item_get_tree_identifier(FolderItem * item)
4233 {
4234         if (item->parent != NULL) {
4235                 gchar * path;
4236                 gchar * id;
4237
4238                 path = folder_item_get_tree_identifier(item->parent);
4239                 if (path == NULL)
4240                         return NULL;
4241
4242                 id = g_strconcat(path, "/", item->name, NULL);
4243                 g_free(path);
4244
4245                 return id;
4246         }
4247         else {
4248                 return g_strconcat("/", item->name, NULL);
4249         }
4250 }
4251 */
4252
4253 /* CLAWS: temporary local folder for filtering */
4254 #define TEMP_FOLDER "TEMP_FOLDER"
4255 #define PROCESSING_FOLDER_ITEM "processing"     
4256
4257 static FolderItem *processing_folder_item;
4258
4259 static void folder_create_processing_folder(void)
4260 {
4261         Folder *processing_folder;
4262         gchar      *tmpname;
4263
4264         if ((processing_folder = folder_find_from_name(TEMP_FOLDER, mh_get_class())) == NULL) {
4265                 gchar *tmppath;
4266
4267                 tmppath =
4268                     g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
4269                                 "tempfolder", NULL);
4270                 processing_folder =
4271                     folder_new(mh_get_class(), TEMP_FOLDER, tmppath);
4272                 g_free(tmppath);
4273         }
4274         g_assert(processing_folder != NULL);
4275
4276         debug_print("tmpparentroot %s\n", LOCAL_FOLDER(processing_folder)->rootpath);
4277         /* FIXME: [W32] The code below does not correctly merge
4278            relative filenames; there should be a function to handle
4279            this.  */
4280         if (!is_relative_filename(LOCAL_FOLDER(processing_folder)->rootpath))
4281                 tmpname = g_strconcat(LOCAL_FOLDER(processing_folder)->rootpath,
4282                                       G_DIR_SEPARATOR_S, PROCESSING_FOLDER_ITEM,
4283                                       NULL);
4284         else
4285                 tmpname = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
4286                                       LOCAL_FOLDER(processing_folder)->rootpath,
4287                                       G_DIR_SEPARATOR_S, PROCESSING_FOLDER_ITEM,
4288                                       NULL);
4289
4290         if (!is_dir_exist(tmpname)) {
4291                 debug_print("*TMP* creating %s\n", tmpname);
4292                 processing_folder_item = processing_folder->klass->create_folder(processing_folder,
4293                                                                                  processing_folder->node->data,
4294                                                                                  PROCESSING_FOLDER_ITEM);
4295         } else {
4296                 debug_print("*TMP* already created\n");
4297                 processing_folder_item = folder_item_new(processing_folder, PROCESSING_FOLDER_ITEM, PROCESSING_FOLDER_ITEM);
4298                 g_assert(processing_folder_item);
4299                 folder_item_append(processing_folder->node->data, processing_folder_item);
4300         }
4301         g_assert(processing_folder_item != NULL);
4302         g_free(tmpname);
4303 }
4304
4305 FolderItem *folder_get_default_processing(void)
4306 {
4307         if (!processing_folder_item) {
4308                 folder_create_processing_folder();
4309         }
4310         return processing_folder_item;
4311 }
4312
4313 /* folder_persist_prefs_new() - return hash table with persistent
4314  * settings (and folder name as key). 
4315  * (note that in claws other options are in the folder_item_prefs_RC
4316  * file, so those don't need to be included in PersistPref yet) 
4317  */
4318 static GHashTable *folder_persist_prefs_new(Folder *folder)
4319 {
4320         GHashTable *pptable;
4321
4322         cm_return_val_if_fail(folder, NULL);
4323         pptable = g_hash_table_new(g_str_hash, g_str_equal);
4324         folder_get_persist_prefs_recursive(folder->node, pptable);
4325         return pptable;
4326 }
4327
4328 static void folder_persist_prefs_free(GHashTable *pptable)
4329 {
4330         cm_return_if_fail(pptable);
4331         g_hash_table_foreach_remove(pptable, persist_prefs_free, NULL);
4332         g_hash_table_destroy(pptable);
4333 }
4334
4335 static const PersistPrefs *folder_get_persist_prefs(GHashTable *pptable, const char *name)
4336 {
4337         if (pptable == NULL || name == NULL) return NULL;
4338         return g_hash_table_lookup(pptable, name);
4339 }
4340
4341 static void folder_item_restore_persist_prefs(FolderItem *item, GHashTable *pptable)
4342 {
4343         const PersistPrefs *pp;
4344         gchar *id = folder_item_get_identifier(item);
4345
4346         pp = folder_get_persist_prefs(pptable, id); 
4347         g_free(id);
4348
4349         if (!pp) return;
4350
4351         /* CLAWS: since not all folder properties have been migrated to 
4352          * folderlist.xml, we need to call the old stuff first before
4353          * setting things that apply both to Main and Claws. */
4354         folder_item_prefs_read_config(item); 
4355
4356         item->collapsed = pp->collapsed;
4357         item->thread_collapsed = pp->thread_collapsed;
4358         item->threaded  = pp->threaded;
4359         item->ret_rcpt  = pp->ret_rcpt;
4360         item->hide_read_msgs = pp->hide_read_msgs;
4361         item->hide_del_msgs = pp->hide_del_msgs;
4362         item->hide_read_threads = pp->hide_read_threads;
4363         item->sort_key  = pp->sort_key;
4364         item->sort_type = pp->sort_type;
4365 }
4366
4367 static void folder_get_persist_prefs_recursive(GNode *node, GHashTable *pptable)
4368 {
4369         FolderItem *item = FOLDER_ITEM(node->data);
4370         PersistPrefs *pp;
4371         GNode *child, *cur;
4372         gchar *id;
4373
4374         cm_return_if_fail(node != NULL);
4375         cm_return_if_fail(item != NULL);
4376
4377         /* NOTE: item->path == NULL means top level folder; not interesting
4378          * to store preferences of that one.  */
4379         if (item->path) {
4380                 id = folder_item_get_identifier(item);
4381                 pp = g_new0(PersistPrefs, 1);
4382                 cm_return_if_fail(pp != NULL);
4383                 pp->collapsed = item->collapsed;
4384                 pp->thread_collapsed = item->thread_collapsed;
4385                 pp->threaded  = item->threaded;
4386                 pp->ret_rcpt  = item->ret_rcpt; 
4387                 pp->hide_read_msgs = item->hide_read_msgs;
4388                 pp->hide_del_msgs = item->hide_del_msgs;
4389                 pp->hide_read_threads = item->hide_read_threads;
4390                 pp->sort_key  = item->sort_key;
4391                 pp->sort_type = item->sort_type;
4392                 g_hash_table_insert(pptable, id, pp);
4393         }
4394
4395         if (node->children) {
4396                 child = node->children;
4397                 while (child) {
4398                         cur = child;
4399                         child = cur->next;
4400                         folder_get_persist_prefs_recursive(cur, pptable);
4401                 }
4402         }       
4403 }
4404
4405 static gboolean persist_prefs_free(gpointer key, gpointer val, gpointer data)
4406 {
4407         g_free(key);
4408         g_free(val);
4409         return TRUE;    
4410 }
4411
4412 void folder_item_apply_processing(FolderItem *item)
4413 {
4414         GSList *processing_list;
4415         GSList *mlist, *cur;
4416         guint total = 0, curmsg = 0;
4417         gint last_apply_per_account;
4418
4419         cm_return_if_fail(item != NULL);
4420
4421         if (item->no_select)
4422                return;
4423
4424         processing_list = item->prefs->processing;
4425
4426         if (!pre_global_processing && !processing_list
4427         &&  !post_global_processing)
4428                 return;
4429
4430         debug_print("processing %s\n", item->name);
4431         folder_item_update_freeze();
4432
4433         inc_lock();
4434
4435         mlist = folder_item_get_msg_list(item);
4436         total = g_slist_length(mlist);
4437         statusbar_print_all(_("Processing messages..."));
4438
4439         last_apply_per_account = prefs_common.apply_per_account_filtering_rules;
4440         prefs_common.apply_per_account_filtering_rules = FILTERING_ACCOUNT_RULES_SKIP;
4441
4442         folder_item_set_batch(item, TRUE);
4443         for (cur = mlist ; cur != NULL ; cur = cur->next) {
4444                 MsgInfo * msginfo;
4445
4446                 msginfo = (MsgInfo *) cur->data;
4447                 
4448                 /* reset parameters that can be modified by processing */
4449                 msginfo->hidden = 0;
4450                 msginfo->score = 0;
4451
4452                 statusbar_progress_all(curmsg++,total, 10);
4453
4454                 /* apply pre global rules */
4455                 filter_message_by_msginfo(pre_global_processing, msginfo, NULL,
4456                                 FILTERING_PRE_PROCESSING, NULL);
4457                 
4458                 /* apply rules of the folder */
4459                 filter_message_by_msginfo(processing_list, msginfo, NULL,
4460                                 FILTERING_FOLDER_PROCESSING, item->name);
4461
4462                 /* apply post global rules */
4463                 filter_message_by_msginfo(post_global_processing, msginfo, NULL,
4464                                 FILTERING_POST_PROCESSING, NULL);
4465                 if (curmsg % 1000 == 0)
4466                         GTK_EVENTS_FLUSH();
4467         }
4468         folder_item_set_batch(item, FALSE);
4469
4470         prefs_common.apply_per_account_filtering_rules = last_apply_per_account;
4471
4472         if (pre_global_processing || processing_list
4473             || post_global_processing)
4474                 filtering_move_and_copy_msgs(mlist);
4475         for (cur = mlist ; cur != NULL ; cur = cur->next) {
4476                 MsgInfo * msginfo = (MsgInfo *)cur->data;
4477                 procmsg_msginfo_free(msginfo);
4478         }
4479         g_slist_free(mlist);
4480         
4481         statusbar_progress_all(0,0,0);
4482         statusbar_pop_all();
4483
4484         inc_unlock();
4485
4486         folder_item_update_thaw();
4487 }
4488
4489 /*
4490  *  functions for handling FolderItem content changes
4491  */
4492 static gint folder_item_update_freeze_cnt = 0;
4493
4494 static void folder_item_update_with_msg(FolderItem *item, FolderItemUpdateFlags update_flags, MsgInfo *msg)
4495 {
4496         if (folder_item_update_freeze_cnt == 0 /* || (msg != NULL && item->opened) */) {
4497                 FolderItemUpdateData source;
4498         
4499                 source.item = item;
4500                 source.update_flags = update_flags;
4501                 source.msg = msg;
4502                 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &source);
4503         } else {
4504                 item->update_flags |= update_flags & ~(F_ITEM_UPDATE_ADDMSG | F_ITEM_UPDATE_REMOVEMSG);
4505         }
4506 }
4507
4508 /**
4509  * Notify the folder system about changes to a folder. If the
4510  * update system is not frozen the FOLDER_ITEM_UPDATE_HOOKLIST will
4511  * be invoked, otherwise the changes will be remebered until
4512  * the folder system is thawed.
4513  *
4514  * \param item The FolderItem that was changed
4515  * \param update_flags Type of changed that was made
4516  */
4517 void folder_item_update(FolderItem *item, FolderItemUpdateFlags update_flags)
4518 {
4519         folder_item_update_with_msg(item, update_flags, NULL);
4520 }
4521
4522 void folder_item_update_recursive(FolderItem *item, FolderItemUpdateFlags update_flags)
4523 {
4524         GNode *node = item->folder->node;       
4525
4526         node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
4527         node = node->children;
4528
4529         folder_item_update(item, update_flags);
4530         while (node != NULL) {
4531                 if (node && node->data) {
4532                         FolderItem *next_item = (FolderItem*) node->data;
4533
4534                         folder_item_update(next_item, update_flags);
4535                 }
4536                 node = node->next;
4537         }
4538 }
4539
4540 void folder_item_update_freeze(void)
4541 {
4542         folder_item_update_freeze_cnt++;
4543 }
4544
4545 static void folder_item_update_func(FolderItem *item, gpointer data)
4546 {
4547         FolderItemUpdateData source;
4548     
4549         if (item->update_flags) {
4550                 source.item = item;
4551                 source.update_flags = item->update_flags;
4552                 source.msg = NULL;
4553                 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &source);                             
4554                 item->update_flags = 0;
4555         }
4556 }
4557
4558 void folder_item_update_thaw(void)
4559 {
4560         if (folder_item_update_freeze_cnt > 0)
4561                 folder_item_update_freeze_cnt--;
4562         if (folder_item_update_freeze_cnt == 0) {
4563                 /* Update all folders */
4564                 folder_func_to_all_folders(folder_item_update_func, NULL);
4565         }
4566 }
4567
4568 void folder_item_synchronise(FolderItem *item)
4569 {
4570         if (!item)
4571                 return;
4572         if (item->prefs->offlinesync && item->folder->klass->synchronise) {
4573                 statuswindow_print_all(_("Synchronising %s for offline use...\n"), item->path ? item->path : "(null)");
4574                 item->folder->klass->synchronise(item, 
4575                         item->prefs->offlinesync_days);
4576                 if (item->prefs->offlinesync_days > 0 &&
4577                     item->prefs->remove_old_bodies)
4578                         folder_item_clean_local_files(item, item->prefs->offlinesync_days);
4579                 statuswindow_pop_all();
4580         }
4581 }
4582
4583 static void folder_item_synchronise_func(FolderItem *item, gpointer data)
4584 {
4585         Folder *folder = (Folder *)data;
4586         if (folder == NULL || item->folder == folder) {
4587                 folder_item_synchronise(item);
4588         }
4589 }
4590
4591 void folder_synchronise(Folder *folder)
4592 {
4593         folder_func_to_all_folders(folder_item_synchronise_func, folder);
4594 }
4595
4596 typedef struct _WantSyncData {
4597         Folder *folder;
4598         gboolean want_sync;
4599 } WantSyncData;
4600
4601 static void folder_item_want_synchronise_func(FolderItem *item, gpointer data)
4602 {
4603         WantSyncData *want_sync_data = (WantSyncData *)data;
4604         
4605         if (want_sync_data->folder == NULL || item->folder == want_sync_data->folder) {
4606                 if (item->prefs->offlinesync && item->folder->klass->synchronise)
4607                         want_sync_data->want_sync |= TRUE;
4608         }
4609 }
4610
4611 gboolean folder_want_synchronise(Folder *folder)
4612 {
4613         WantSyncData *want_sync_data = g_new0(WantSyncData, 1);
4614         gboolean result;
4615         want_sync_data->folder = folder;
4616         want_sync_data->want_sync = FALSE;
4617         
4618         folder_func_to_all_folders(folder_item_want_synchronise_func, want_sync_data);
4619         result = want_sync_data->want_sync;
4620         g_free(want_sync_data);
4621         if (result > 0)
4622                 debug_print("Folder %s wants sync\n", folder->name);
4623         return result;
4624 }
4625
4626 void folder_item_set_batch (FolderItem *item, gboolean batch)
4627 {
4628         if (!item || !item->folder)
4629                 return;
4630         if (item->folder->klass->set_batch) {
4631                 item->folder->klass->set_batch(item->folder, item, batch);
4632         }
4633 }
4634
4635 gboolean folder_has_parent_of_type(FolderItem *item, 
4636                                           SpecialFolderItemType type) 
4637 {
4638         FolderItem *cur = item;
4639
4640         if (!item)
4641                 return FALSE;
4642         /* if we already know it, make it short */
4643         if (item->parent_stype != -1) {
4644                 return (item->parent_stype == type);
4645         }
4646         
4647         /* if we don't, find the type from the first possible parent,
4648          * and set our parent type to be faster next time */
4649         while (cur) {
4650                 if (cur->stype == type || cur->parent_stype == type) {
4651                         item->parent_stype = type;
4652                         return TRUE;
4653                 }
4654                 cur = folder_item_parent(cur);
4655         }
4656         
4657         /* if we didn't match what was asked, we didn't return. If our
4658          * parent type is unknown, we may as well find it now to be faster
4659          * later. */
4660         if (item->parent_stype == -1) {
4661                 cur = item;
4662                 while (cur) {
4663                         /* here's an exception: Inbox subfolders are normal. */
4664                         if (item->parent_stype == -1 && cur->stype == F_INBOX 
4665                         && item != cur) {
4666                                 item->parent_stype = F_NORMAL;
4667                                 break;
4668                         }
4669                         /* ah, we know this parent's parent's type, we may as 
4670                          * well copy it instead of going up the full way */
4671                         if (cur->parent_stype != -1) {
4672                                 item->parent_stype = cur->parent_stype;
4673                                 break;
4674                         }
4675                         /* we found a parent that has a special type. That's 
4676                          * our parent type. */
4677                         if (cur->stype != F_NORMAL) {
4678                                 cur->parent_stype = cur->stype;
4679                                 item->parent_stype = cur->stype;
4680                                 break;
4681                         }
4682                         /* if we didn't find anything, go up once more */
4683                         cur = folder_item_parent(cur);
4684                 }
4685                 /* as we still didn't find anything, our parents must all be 
4686                  * normal. */
4687                 if (item->parent_stype == -1) {
4688                         item->parent_stype = F_NORMAL;
4689                 }
4690         }
4691         return FALSE;
4692 }
4693
4694 gboolean folder_subscribe (const gchar *uri)
4695 {
4696         GList *cur;
4697         for (cur = folder_get_list(); cur != NULL; cur = g_list_next(cur)) {
4698                 Folder *folder = (Folder *) cur->data;
4699
4700                 if (folder->klass->subscribe
4701                 &&  folder->klass->subscribe(folder, uri)) {
4702                         return TRUE;
4703                 }
4704         }
4705         return FALSE;
4706
4707 }
4708
4709 gboolean folder_get_sort_type           (Folder         *folder,
4710                                          FolderSortKey  *sort_key,
4711                                          FolderSortType *sort_type)
4712 {
4713         if (!folder || !sort_key || !sort_type)
4714                 return FALSE;
4715         if (folder->klass->get_sort_type == NULL)
4716                 return FALSE;
4717         folder->klass->get_sort_type(folder, sort_key, sort_type); 
4718         return TRUE;
4719 }