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