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