rework Display/Summaries
[claws.git] / src / folder.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <stdlib.h>
34 #ifdef WIN32
35 #include <w32lib.h>
36 #endif
37
38 #include "alertpanel.h"
39 #include "folder.h"
40 #include "session.h"
41 #include "inc.h"
42 #include "imap.h"
43 #include "news.h"
44 #include "mh.h"
45 #include "utils.h"
46 #include "xml.h"
47 #include "codeconv.h"
48 #include "prefs_gtk.h"
49 #include "account.h"
50 #include "filtering.h"
51 #include "procheader.h"
52 #include "hooks.h"
53 #include "log.h"
54 #include "folder_item_prefs.h"
55 #include "remotefolder.h"
56 #include "partial_download.h"
57 #include "statusbar.h"
58 #include "gtkutils.h"
59 #include "timing.h"
60 #include "compose.h"
61 #include "main.h"
62 #include "msgcache.h"
63 #include "privacy.h"
64 #include "prefs_common.h"
65 #include "prefs_migration.h"
66 #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 (strcmp2(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                     strcmp2(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 (strcmp2(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(!strcmp2(item->name, INBOX_DIR) ? _("Inbox") :
1596                                 item->name);
1597                 break;
1598         case F_OUTBOX:
1599                 name = g_strdup(!strcmp2(item->name, OUTBOX_DIR) ? _("Sent") :
1600                                 item->name);
1601                 break;
1602         case F_QUEUE:
1603                 name = g_strdup(!strcmp2(item->name, QUEUE_DIR) ? _("Queue") :
1604                                 item->name);
1605                 break;
1606         case F_TRASH:
1607                 name = g_strdup(!strcmp2(item->name, TRASH_DIR) ? _("Trash") :
1608                                 item->name);
1609                 break;
1610         case F_DRAFT:
1611                 name = g_strdup(!strcmp2(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 && !strcmp2(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->stype == F_INBOX) &&
2376                                         (item->folder->account != NULL) && 
2377                                         (item->folder->account->filter_on_recv);
2378                 
2379                 for (elem = newmsg_list; elem != NULL; elem = g_slist_next(elem)) {
2380                         MsgInfo *msginfo = (MsgInfo *) elem->data;
2381
2382                         msgcache_add_msg(item->cache, msginfo);
2383                         if (!do_filter) {
2384                                 exists_list = g_slist_prepend(exists_list, msginfo);
2385
2386                                 if(prefs_common.thread_by_subject &&
2387                                         MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2388                                         !subject_table_lookup(subject_table, msginfo->subject)) {
2389                                         subject_table_insert(subject_table, msginfo->subject, msginfo);
2390                                 }                       
2391                         }
2392                 }
2393
2394                 if (do_filter) {
2395                         GSList *unfiltered;
2396                         
2397                         folder_item_set_batch(item, TRUE);
2398                         procmsg_msglist_filter(newmsg_list, item->folder->account, 
2399                                         &to_filter, &unfiltered, 
2400                                         TRUE);
2401                         folder_item_set_batch(item, FALSE);
2402                         
2403                         filtering_move_and_copy_msgs(newmsg_list);
2404                         if (to_filter != NULL) {
2405                                 for (elem = to_filter; elem; elem = g_slist_next(elem)) {
2406                                         MsgInfo *msginfo = (MsgInfo *)elem->data;
2407                                         procmsg_msginfo_free(&msginfo);
2408                                 }
2409                                 g_slist_free(to_filter);
2410                         }
2411                         if (unfiltered != NULL) {
2412                                 for (elem = unfiltered; elem; elem = g_slist_next(elem)) {
2413                                         MsgInfo *msginfo = (MsgInfo *)elem->data;
2414                                         exists_list = g_slist_prepend(exists_list, msginfo);
2415
2416                                         if(prefs_common.thread_by_subject &&
2417                                                 MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2418                                                 !subject_table_lookup(subject_table, msginfo->subject)) {
2419                                                 subject_table_insert(subject_table, msginfo->subject, msginfo);
2420                                         }
2421                                 }
2422                                 g_slist_free(unfiltered);
2423                         }
2424                         if (prefs_common.real_time_sync)
2425                                 folder_item_synchronise(item);
2426                 } else {
2427                         if (prefs_common.real_time_sync)
2428                                 folder_item_synchronise(item);
2429                 }
2430
2431                 g_slist_free(newmsg_list);
2432
2433                 update_flags |= F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT;
2434         }
2435
2436         folder_item_set_batch(item, TRUE);
2437         for (elem = exists_list; elem != NULL; elem = g_slist_next(elem)) {
2438                 MsgInfo *msginfo, *parent_msginfo;
2439
2440                 msginfo = elem->data;
2441                 if (MSG_IS_IGNORE_THREAD(msginfo->flags) && (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
2442                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
2443                 if (!MSG_IS_IGNORE_THREAD(msginfo->flags) && procmsg_msg_has_flagged_parent(msginfo, MSG_IGNORE_THREAD)) {
2444                         procmsg_msginfo_change_flags(msginfo, MSG_IGNORE_THREAD, 0, MSG_NEW | MSG_UNREAD, 0);
2445                 }
2446                 if (!MSG_IS_WATCH_THREAD(msginfo->flags) && procmsg_msg_has_flagged_parent(msginfo, MSG_WATCH_THREAD)) {
2447                         procmsg_msginfo_set_flags(msginfo, MSG_WATCH_THREAD, 0);
2448                 }
2449                 if(prefs_common.thread_by_subject && !msginfo->inreplyto &&
2450                         !msginfo->references && !MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2451                         (parent_msginfo = subject_table_lookup(subject_table, msginfo->subject)))
2452                 {
2453                         if(MSG_IS_IGNORE_THREAD(parent_msginfo->flags)) {
2454                                 procmsg_msginfo_change_flags(msginfo, MSG_IGNORE_THREAD, 0,
2455                                                 MSG_NEW | MSG_UNREAD, 0);
2456                         }
2457                 }
2458                 if ((folder_has_parent_of_type(item, F_OUTBOX) ||
2459                      folder_has_parent_of_type(item, F_QUEUE)  ||
2460                      folder_has_parent_of_type(item, F_TRASH)) &&
2461                     (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
2462                         procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
2463                 if (MSG_IS_NEW(msginfo->flags))
2464                         newcnt++;
2465                 if (MSG_IS_UNREAD(msginfo->flags))
2466                         unreadcnt++;
2467                 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
2468                         unreadmarkedcnt++;
2469                 if (MSG_IS_MARKED(msginfo->flags))
2470                         markedcnt++;
2471                 if (MSG_IS_REPLIED(msginfo->flags))
2472                         repliedcnt++;
2473                 if (MSG_IS_FORWARDED(msginfo->flags))
2474                         forwardedcnt++;
2475                 if (MSG_IS_LOCKED(msginfo->flags))
2476                         lockedcnt++;
2477                 if (MSG_IS_IGNORE_THREAD(msginfo->flags))
2478                         ignoredcnt++;
2479                 if (MSG_IS_WATCH_THREAD(msginfo->flags))
2480                         watchedcnt++;
2481
2482                 totalcnt++;
2483
2484                 procmsg_msginfo_free(&msginfo);
2485         }
2486         folder_item_set_batch(item, FALSE);
2487         g_slist_free(exists_list);
2488         
2489         if(prefs_common.thread_by_subject) {
2490                 g_hash_table_destroy(subject_table);
2491         }
2492         
2493         if (item->new_msgs != newcnt || item->unread_msgs != unreadcnt
2494         ||  item->total_msgs != totalcnt || item->marked_msgs != markedcnt
2495         ||  item->unreadmarked_msgs != unreadmarkedcnt
2496         ||  item->replied_msgs != repliedcnt || item->forwarded_msgs != forwardedcnt
2497         ||  item->locked_msgs != lockedcnt || item->ignored_msgs != ignoredcnt
2498         ||  item->watched_msgs != watchedcnt) {
2499                 update_flags |= F_ITEM_UPDATE_CONTENT;
2500         }
2501
2502         item->new_msgs = newcnt;
2503         item->unread_msgs = unreadcnt;
2504         item->total_msgs = totalcnt;
2505         item->unreadmarked_msgs = unreadmarkedcnt;
2506         item->marked_msgs = markedcnt;
2507         item->replied_msgs = repliedcnt;
2508         item->forwarded_msgs = forwardedcnt;
2509         item->locked_msgs = lockedcnt;
2510         item->ignored_msgs = ignoredcnt;
2511         item->watched_msgs = watchedcnt;
2512
2513         update_flags |= F_ITEM_UPDATE_MSGCNT;
2514
2515         folder_item_update(item, update_flags);
2516         folder_item_update_thaw();
2517         
2518         item->scanning = ITEM_NOT_SCANNING;
2519
2520         return 0;
2521 }
2522
2523 gint folder_item_scan(FolderItem *item)
2524 {
2525         return folder_item_scan_full(item, TRUE);
2526 }
2527
2528 static void folder_count_total_cache_memusage(FolderItem *item, gpointer data)
2529 {
2530         gint *memusage = (gint *)data;
2531
2532         if (item->cache == NULL)
2533                 return;
2534         
2535         *memusage += msgcache_get_memory_usage(item->cache);
2536 }
2537
2538 static gint folder_cache_time_compare_func(gconstpointer a, gconstpointer b)
2539 {
2540         FolderItem *fa = (FolderItem *)a;
2541         FolderItem *fb = (FolderItem *)b;
2542         
2543         return (gint) (msgcache_get_last_access_time(fa->cache) - msgcache_get_last_access_time(fb->cache));
2544 }
2545
2546 static void folder_find_expired_caches(FolderItem *item, gpointer data)
2547 {
2548         GSList **folder_item_list = (GSList **)data;
2549         gint difftime, expiretime;
2550         
2551         if (item->cache == NULL)
2552                 return;
2553
2554         if (item->opened > 0)
2555                 return;
2556
2557         difftime = (gint) (time(NULL) - msgcache_get_last_access_time(item->cache));
2558         expiretime = prefs_common.cache_min_keep_time * 60;
2559         debug_print("Cache unused time: %d (Expire time: %d)\n", difftime, expiretime);
2560
2561         if (difftime > expiretime && !item->opened && !item->processing_pending) {
2562                 *folder_item_list = g_slist_insert_sorted(*folder_item_list, item, folder_cache_time_compare_func);
2563         }
2564 }
2565
2566 gboolean folder_item_free_cache(FolderItem *item, gboolean force)
2567 {
2568         cm_return_val_if_fail(item != NULL, TRUE);
2569         
2570         if (item->cache == NULL)
2571                 return TRUE;
2572         
2573         if (item->opened > 0 && !force)
2574                 return FALSE;
2575
2576         folder_item_write_cache(item);
2577         msgcache_destroy(item->cache);
2578         item->cache = NULL;
2579         return TRUE;
2580 }
2581
2582 void folder_clean_cache_memory_force(void)
2583 {
2584         int old_cache_max_mem_usage = prefs_common.cache_max_mem_usage;
2585         int old_cache_min_keep_time = prefs_common.cache_min_keep_time;
2586
2587         prefs_common.cache_max_mem_usage = 0;
2588         prefs_common.cache_min_keep_time = 0;
2589
2590         folder_clean_cache_memory(NULL);
2591
2592         prefs_common.cache_max_mem_usage = old_cache_max_mem_usage;
2593         prefs_common.cache_min_keep_time = old_cache_min_keep_time;
2594 }
2595
2596 void folder_clean_cache_memory(FolderItem *protected_item)
2597 {
2598         gint memusage = 0;
2599
2600         folder_func_to_all_folders(folder_count_total_cache_memusage, &memusage);       
2601         debug_print("Total cache memory usage: %d\n", memusage);
2602         
2603         if (memusage > (prefs_common.cache_max_mem_usage * 1024)) {
2604                 GSList *folder_item_list = NULL, *listitem;
2605                 
2606                 debug_print("Trying to free cache memory\n");
2607
2608                 folder_func_to_all_folders(folder_find_expired_caches, &folder_item_list);      
2609                 listitem = folder_item_list;
2610                 while((listitem != NULL) && (memusage > (prefs_common.cache_max_mem_usage * 1024))) {
2611                         FolderItem *item = (FolderItem *)(listitem->data);
2612                         gint cache_size = 0;
2613                         if (item == protected_item) {
2614                                 listitem = listitem->next;
2615                                 continue;
2616                         }
2617                         debug_print("Freeing cache memory for %s\n", item->path ? item->path : item->name);
2618                         cache_size = msgcache_get_memory_usage(item->cache);
2619                         if (folder_item_free_cache(item, FALSE))
2620                                 memusage -= cache_size;
2621
2622                         listitem = listitem->next;
2623                 }
2624                 g_slist_free(folder_item_list);
2625         }
2626 }
2627
2628 static void folder_item_remove_cached_msg(FolderItem *item, MsgInfo *msginfo)
2629 {
2630         Folder *folder = item->folder;
2631
2632         cm_return_if_fail(folder != NULL);
2633
2634         if (folder->klass->remove_cached_msg == NULL)
2635                 return;
2636         
2637         folder->klass->remove_cached_msg(folder, item, msginfo);
2638 }
2639
2640 static void folder_item_clean_local_files(FolderItem *item, gint days)
2641 {
2642         cm_return_if_fail(item != NULL);
2643         cm_return_if_fail(item->folder != NULL);
2644
2645         if (FOLDER_TYPE(item->folder) == F_IMAP ||
2646             FOLDER_TYPE(item->folder) == F_NEWS) {
2647                 GSList *msglist = folder_item_get_msg_list(item);
2648                 GSList *cur;
2649                 time_t t = time(NULL);
2650                 for (cur = msglist; cur; cur = cur->next) {
2651                         MsgInfo *msginfo = (MsgInfo *)cur->data;
2652                         gint age = (t - msginfo->date_t) / (60*60*24);
2653                         if (age > days)
2654                                 folder_item_remove_cached_msg(item, msginfo);
2655                 }
2656                 procmsg_msg_list_free(msglist);
2657         }
2658 }
2659
2660 static void folder_item_read_cache(FolderItem *item)
2661 {
2662         gchar *cache_file, *mark_file, *tags_file;
2663         START_TIMING("");
2664         cm_return_if_fail(item != NULL);
2665
2666         if (item->path != NULL) {
2667                 cache_file = folder_item_get_cache_file(item);
2668                 mark_file = folder_item_get_mark_file(item);
2669                 tags_file = folder_item_get_tags_file(item);
2670                 item->cache = msgcache_read_cache(item, cache_file);
2671                 item->cache_dirty = FALSE;
2672                 item->mark_dirty = FALSE;
2673                 item->tags_dirty = FALSE;
2674                 if (!item->cache) {
2675                         MsgInfoList *list, *cur;
2676                         guint newcnt = 0, unreadcnt = 0;
2677                         guint markedcnt = 0, unreadmarkedcnt = 0;
2678                         guint repliedcnt = 0, forwardedcnt = 0;
2679                         guint lockedcnt = 0, ignoredcnt = 0;
2680                         guint watchedcnt = 0;
2681                         MsgInfo *msginfo;
2682
2683                         item->cache = msgcache_new();
2684                         item->cache_dirty = TRUE;
2685                         item->mark_dirty = TRUE;
2686                         item->tags_dirty = TRUE;
2687                         folder_item_scan_full(item, TRUE);
2688
2689                         msgcache_read_mark(item->cache, mark_file);
2690
2691                         list = msgcache_get_msg_list(item->cache);
2692                         for (cur = list; cur != NULL; cur = g_slist_next(cur)) {
2693                                 msginfo = cur->data;
2694
2695                                 if (MSG_IS_NEW(msginfo->flags))
2696                                         newcnt++;
2697                                 if (MSG_IS_UNREAD(msginfo->flags))
2698                                         unreadcnt++;
2699                                 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
2700                                         unreadmarkedcnt++;
2701                                 if (MSG_IS_MARKED(msginfo->flags))
2702                                         markedcnt++;
2703                                 if (MSG_IS_REPLIED(msginfo->flags))
2704                                         repliedcnt++;
2705                                 if (MSG_IS_FORWARDED(msginfo->flags))
2706                                         forwardedcnt++;
2707                                 if (MSG_IS_LOCKED(msginfo->flags))
2708                                         lockedcnt++;
2709                                 if (MSG_IS_IGNORE_THREAD(msginfo->flags))
2710                                         ignoredcnt++;
2711                                 if (MSG_IS_WATCH_THREAD(msginfo->flags))
2712                                         watchedcnt++;
2713                                 procmsg_msginfo_unset_flags(msginfo, MSG_FULLY_CACHED, 0);
2714                         }
2715                         item->new_msgs = newcnt;
2716                         item->unread_msgs = unreadcnt;
2717                         item->unreadmarked_msgs = unreadmarkedcnt;
2718                         item->marked_msgs = markedcnt;
2719                         item->replied_msgs = repliedcnt;
2720                         item->forwarded_msgs = forwardedcnt;
2721                         item->locked_msgs = lockedcnt;
2722                         item->ignored_msgs = ignoredcnt;
2723                         item->watched_msgs = watchedcnt;
2724                         procmsg_msg_list_free(list);
2725                 } else
2726                         msgcache_read_mark(item->cache, mark_file);
2727
2728                 msgcache_read_tags(item->cache, tags_file);
2729
2730                 g_free(cache_file);
2731                 g_free(mark_file);
2732                 g_free(tags_file);
2733         } else {
2734                 item->cache = msgcache_new();
2735                 item->cache_dirty = TRUE;
2736                 item->mark_dirty = TRUE;
2737                 item->tags_dirty = TRUE;
2738         }
2739
2740         END_TIMING();
2741         folder_clean_cache_memory(item);
2742 }
2743
2744 void folder_item_write_cache(FolderItem *item)
2745 {
2746         gchar *cache_file = NULL, *mark_file = NULL, *tags_file = NULL;
2747         FolderItemPrefs *prefs;
2748         gint filemode = 0;
2749         gchar *id;
2750         time_t last_mtime = (time_t)0;
2751         gboolean need_scan = FALSE;
2752         
2753         if (!item || !item->path || !item->cache)
2754                 return;
2755
2756         last_mtime = item->mtime;
2757         if (item->folder->klass->set_mtime) {
2758                 if (item->folder->klass->scan_required)
2759                         need_scan = item->folder->klass->scan_required(item->folder, item);
2760                 else
2761                         need_scan = TRUE;
2762         }
2763
2764         id = folder_item_get_identifier(item);
2765         debug_print("Save cache for folder %s\n", id);
2766         g_free(id);
2767
2768         if (item->cache_dirty)
2769                 cache_file = folder_item_get_cache_file(item);
2770         if (item->cache_dirty || item->mark_dirty)
2771                 mark_file = folder_item_get_mark_file(item);
2772         if (item->cache_dirty || item->tags_dirty)
2773                 tags_file = folder_item_get_tags_file(item);
2774         if (msgcache_write(cache_file, mark_file, tags_file, item->cache) < 0) {
2775                 prefs = item->prefs;
2776                 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
2777                         /* for cache file */
2778                         filemode = prefs->folder_chmod;
2779                         if (filemode & S_IRGRP) filemode |= S_IWGRP;
2780                         if (filemode & S_IROTH) filemode |= S_IWOTH;
2781                         if (cache_file != NULL)
2782                                 chmod(cache_file, filemode);
2783                 }
2784         } else {
2785                 item->cache_dirty = FALSE;
2786                 item->mark_dirty = FALSE;
2787                 item->tags_dirty = FALSE;
2788         }
2789
2790         if (!need_scan && item->folder->klass->set_mtime) {
2791                 if (item->mtime == last_mtime) {
2792                         item->folder->klass->set_mtime(item->folder, item);
2793                 }
2794         }
2795
2796         g_free(cache_file);
2797         g_free(mark_file);
2798         g_free(tags_file);
2799 }
2800
2801 MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num)
2802 {
2803         MsgInfo *msginfo = NULL;
2804         
2805         cm_return_val_if_fail(item != NULL, NULL);
2806         if (item->no_select)
2807                 return NULL;
2808         if (!item->cache)
2809                 folder_item_read_cache(item);
2810         
2811         if ((msginfo = msgcache_get_msg(item->cache, num)) != NULL)
2812                 return msginfo;
2813         
2814         msginfo = get_msginfo(item, num);
2815         if (msginfo != NULL) {
2816                 msgcache_add_msg(item->cache, msginfo);
2817                 return msginfo;
2818         }
2819         
2820         return NULL;
2821 }
2822
2823 MsgInfo *folder_item_get_msginfo_by_msgid(FolderItem *item, const gchar *msgid)
2824 {
2825         MsgInfo *msginfo;
2826         
2827         cm_return_val_if_fail(item != NULL, NULL);
2828         cm_return_val_if_fail(msgid != NULL, NULL);
2829         if (item->no_select)
2830                 return NULL;
2831         
2832         if (!item->cache)
2833                 folder_item_read_cache(item);
2834         
2835         if ((msginfo = msgcache_get_msg_by_id(item->cache, msgid)) != NULL)
2836                 return msginfo;
2837
2838         return NULL;
2839 }
2840
2841 GSList *folder_item_get_msg_list(FolderItem *item)
2842 {
2843         cm_return_val_if_fail(item != NULL, NULL);
2844         if (item->no_select)
2845                 return NULL;
2846         
2847         if (item->cache == 0)
2848                 folder_item_read_cache(item);
2849
2850         cm_return_val_if_fail(item->cache != NULL, NULL);
2851         
2852         return msgcache_get_msg_list(item->cache);
2853 }
2854
2855 static void msginfo_set_mime_flags(GNode *node, gpointer data)
2856 {
2857         MsgInfo *msginfo = data;
2858         MimeInfo *mimeinfo = node->data;
2859         
2860         if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT
2861          && (!mimeinfo->subtype || (strcmp(mimeinfo->subtype, "pgp-signature") &&
2862              strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2863              strcmp(mimeinfo->subtype, "pkcs7-signature")))) {
2864                 procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2865         } else if (mimeinfo->disposition == DISPOSITIONTYPE_UNKNOWN && 
2866                  mimeinfo->id == NULL &&
2867                  mimeinfo->type != MIMETYPE_TEXT &&
2868                  mimeinfo->type != MIMETYPE_MULTIPART) {
2869                 if (!mimeinfo->subtype 
2870                 || (strcmp(mimeinfo->subtype, "pgp-signature") && 
2871                     strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2872                     strcmp(mimeinfo->subtype, "pkcs7-signature")))
2873                         procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2874         } else if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE &&
2875                  mimeinfo->id == NULL &&
2876                 (strcmp(mimeinfo->subtype, "pgp-signature") &&
2877                  strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2878                  strcmp(mimeinfo->subtype, "pkcs7-signature")) && 
2879                 (procmime_mimeinfo_get_parameter(mimeinfo, "name") != NULL ||
2880                  procmime_mimeinfo_get_parameter(mimeinfo, "filename") != NULL)) {
2881                 procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2882         } 
2883
2884         /* don't descend below top level message for signed and encrypted info */
2885         if (mimeinfo->type == MIMETYPE_MESSAGE)
2886                 return;
2887
2888         if (privacy_mimeinfo_is_signed(mimeinfo)) {
2889                 procmsg_msginfo_set_flags(msginfo, 0, MSG_SIGNED);
2890         }
2891
2892         if (privacy_mimeinfo_is_encrypted(mimeinfo)) {
2893                 procmsg_msginfo_set_flags(msginfo, 0, MSG_ENCRYPTED);
2894         } else {
2895                 /* searching inside encrypted parts doesn't really make sense */
2896                 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2897         }
2898 }
2899
2900 gchar *folder_item_fetch_msg(FolderItem *item, gint num)
2901 {
2902         Folder *folder;
2903         gchar *msgfile;
2904         MsgInfo *msginfo;
2905
2906         cm_return_val_if_fail(item != NULL, NULL);
2907
2908         folder = item->folder;
2909
2910         cm_return_val_if_fail(folder->klass->fetch_msg != NULL, NULL);
2911         if (item->no_select)
2912                 return NULL;
2913
2914         msgfile = folder->klass->fetch_msg(folder, item, num);
2915
2916         if (msgfile != NULL) {
2917                 msginfo = folder_item_get_msginfo(item, num);
2918                 if ((msginfo != NULL) && !MSG_IS_SCANNED(msginfo->flags)) {
2919                         MimeInfo *mimeinfo;
2920
2921                         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) && 
2922                             !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
2923                                 mimeinfo = procmime_scan_file(msgfile);
2924                         else
2925                                 mimeinfo = procmime_scan_queue_file(msgfile);
2926                         /* check for attachments */
2927                         if (mimeinfo != NULL) { 
2928                                 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2929                                 procmime_mimeinfo_free_all(&mimeinfo);
2930
2931                                 procmsg_msginfo_set_flags(msginfo, 0, MSG_SCANNED);
2932                         }
2933                 }
2934                 procmsg_msginfo_free(&msginfo);
2935         }
2936
2937         return msgfile;
2938 }
2939
2940 gchar *folder_item_fetch_msg_full(FolderItem *item, gint num, gboolean headers,
2941                                   gboolean body)
2942 {
2943         Folder *folder;
2944         gchar *msgfile;
2945         MsgInfo *msginfo;
2946
2947         cm_return_val_if_fail(item != NULL, NULL);
2948         if (item->no_select)
2949                 return NULL;
2950         
2951         folder = item->folder;
2952
2953         if (folder->klass->fetch_msg_full == NULL)
2954                 return folder_item_fetch_msg(item, num);
2955
2956         if (item->prefs->offlinesync && prefs_common.real_time_sync)
2957                 msgfile = folder->klass->fetch_msg_full(folder, item, num, 
2958                                                 TRUE, TRUE);
2959         else
2960                 msgfile = folder->klass->fetch_msg_full(folder, item, num, 
2961                                                 headers, body);
2962
2963         if (msgfile != NULL) {
2964                 msginfo = folder_item_get_msginfo(item, num);
2965                 if ((msginfo != NULL) && !MSG_IS_SCANNED(msginfo->flags)) {
2966                         MimeInfo *mimeinfo;
2967
2968                         if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2969                             !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
2970                                 mimeinfo = procmime_scan_file(msgfile);
2971                         else
2972                                 mimeinfo = procmime_scan_queue_file(msgfile);
2973                         /* check for attachments */
2974                         if (mimeinfo != NULL) { 
2975                                 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2976                                 procmime_mimeinfo_free_all(&mimeinfo);
2977
2978                                 procmsg_msginfo_set_flags(msginfo, 0, MSG_SCANNED);
2979                         }
2980                 }
2981                 procmsg_msginfo_free(&msginfo);
2982         }
2983
2984         return msgfile;
2985 }
2986
2987
2988 static gint folder_item_get_msg_num_by_file(FolderItem *dest, const gchar *file)
2989 {
2990         static HeaderEntry hentry[] = {{"Message-ID:",  NULL, TRUE},
2991                                        {NULL,           NULL, FALSE}};
2992         FILE *fp;
2993         MsgInfo *msginfo;
2994         gint msgnum = 0;
2995         gchar buf[BUFFSIZE];
2996
2997         if ((fp = claws_fopen(file, "rb")) == NULL)
2998                 return 0;
2999
3000         if ((folder_has_parent_of_type(dest, F_QUEUE)) || 
3001             (folder_has_parent_of_type(dest, F_DRAFT)))
3002                 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3003                         /* new way */
3004                         if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
3005                                 strlen("X-Claws-End-Special-Headers:"))) ||
3006                             (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
3007                                 strlen("X-Sylpheed-End-Special-Headers:"))))
3008                                 break;
3009                         /* old way */
3010                         if (buf[0] == '\r' || buf[0] == '\n') break;
3011                         /* from other mailers */
3012                         if (!strncmp(buf, "Date: ", 6)
3013                         ||  !strncmp(buf, "To: ", 4)
3014                         ||  !strncmp(buf, "From: ", 6)
3015                         ||  !strncmp(buf, "Subject: ", 9)) {
3016                                 rewind(fp);
3017                                 break;
3018                         }
3019                 }
3020
3021         procheader_get_header_fields(fp, hentry);
3022         debug_print("looking for %s\n", hentry[0].body);
3023         if (hentry[0].body) {
3024                 extract_parenthesis(hentry[0].body, '<', '>');
3025                 remove_space(hentry[0].body);
3026                 if ((msginfo = msgcache_get_msg_by_id(dest->cache, hentry[0].body)) != NULL) {
3027                         msgnum = msginfo->msgnum;
3028                         procmsg_msginfo_free(&msginfo);
3029
3030                         debug_print("found message as uid %d\n", msgnum);
3031                 }
3032         }
3033         
3034         g_free(hentry[0].body);
3035         hentry[0].body = NULL;
3036         claws_fclose(fp);
3037
3038         return msgnum;
3039 }
3040
3041 static void copy_msginfo_flags(MsgInfo *source, MsgInfo *dest)
3042 {
3043         MsgPermFlags perm_flags = 0;
3044         MsgTmpFlags tmp_flags = 0;
3045
3046         /* create new flags */
3047         if (source != NULL) {
3048                 /* copy original flags */
3049                 perm_flags = source->flags.perm_flags;
3050                 tmp_flags = source->flags.tmp_flags;
3051         } else {
3052                 perm_flags = dest->flags.perm_flags;
3053                 tmp_flags = dest->flags.tmp_flags;
3054         }
3055
3056         /* remove new, unread and deleted in special folders */
3057         if (folder_has_parent_of_type(dest->folder, F_OUTBOX) || 
3058             folder_has_parent_of_type(dest->folder, F_QUEUE) || 
3059             folder_has_parent_of_type(dest->folder, F_DRAFT) || 
3060             folder_has_parent_of_type(dest->folder, F_TRASH))
3061                 perm_flags &= ~(MSG_NEW | MSG_UNREAD | MSG_DELETED);
3062
3063         /* set ignore flag of ignored parent exists */
3064         if (procmsg_msg_has_flagged_parent(dest, MSG_IGNORE_THREAD))
3065                 perm_flags |= MSG_IGNORE_THREAD;
3066
3067         /* unset FULLY_CACHED flags */
3068         perm_flags &= ~MSG_FULLY_CACHED;
3069
3070         if (procmsg_msg_has_flagged_parent(dest, MSG_WATCH_THREAD))
3071                 perm_flags |= MSG_WATCH_THREAD;
3072
3073         /* Unset tmp flags that should not be copied */
3074         tmp_flags &= ~(MSG_MOVE | MSG_COPY | MSG_MOVE_DONE);
3075
3076         /* unset flags that are set but should not */
3077         /* and set new flags */
3078         procmsg_msginfo_change_flags(dest,
3079                                   ~dest->flags.perm_flags & perm_flags,
3080                                   ~dest->flags.tmp_flags  & tmp_flags,
3081                                    dest->flags.perm_flags & ~perm_flags,
3082                                    dest->flags.tmp_flags  & ~tmp_flags);
3083         
3084         if (source && source->tags) {
3085                 g_slist_free(dest->tags);
3086                 dest->tags = g_slist_copy(source->tags);
3087                 folder_item_commit_tags(dest->folder, dest, dest->tags, NULL);
3088         }
3089 }
3090
3091 static void add_msginfo_to_cache(FolderItem *item, MsgInfo *newmsginfo, MsgInfo *flagsource)
3092 {
3093         /* update folder stats */
3094         if (MSG_IS_NEW(newmsginfo->flags))
3095                 item->new_msgs++;
3096         if (MSG_IS_UNREAD(newmsginfo->flags))
3097                 item->unread_msgs++;
3098         if (MSG_IS_UNREAD(newmsginfo->flags) && procmsg_msg_has_marked_parent(newmsginfo))
3099                 item->unreadmarked_msgs++;
3100         if (MSG_IS_MARKED(newmsginfo->flags))
3101                 item->marked_msgs++;
3102         if (MSG_IS_REPLIED(newmsginfo->flags))
3103                 item->replied_msgs++;
3104         if (MSG_IS_FORWARDED(newmsginfo->flags))
3105                 item->forwarded_msgs++;
3106         if (MSG_IS_LOCKED(newmsginfo->flags))
3107                 item->locked_msgs++;
3108         if (MSG_IS_IGNORE_THREAD(newmsginfo->flags))
3109                 item->ignored_msgs++;
3110         if (MSG_IS_WATCH_THREAD(newmsginfo->flags))
3111                 item->watched_msgs++;
3112         item->total_msgs++;
3113
3114         folder_item_update_freeze();
3115
3116         if (!item->cache)
3117                 folder_item_read_cache(item);
3118
3119         msgcache_add_msg(item->cache, newmsginfo);
3120         copy_msginfo_flags(flagsource, newmsginfo);
3121         folder_item_update_with_msg(item,  F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_ADDMSG, newmsginfo);
3122         folder_item_update_thaw();
3123 }
3124
3125 static void remove_msginfo_from_cache(FolderItem *item, MsgInfo *msginfo)
3126 {
3127         MsgInfoUpdate msginfo_update;
3128
3129         if (!item->cache)
3130                 folder_item_read_cache(item);
3131
3132         if (MSG_IS_NEW(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3133                 msginfo->folder->new_msgs--;
3134         if (MSG_IS_UNREAD(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3135                 msginfo->folder->unread_msgs--;
3136         if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
3137                 msginfo->folder->unreadmarked_msgs--;
3138         if (MSG_IS_MARKED(msginfo->flags))
3139                 item->marked_msgs--;
3140         if (MSG_IS_REPLIED(msginfo->flags))
3141                 item->replied_msgs--;
3142         if (MSG_IS_FORWARDED(msginfo->flags))
3143                 item->forwarded_msgs--;
3144         if (MSG_IS_LOCKED(msginfo->flags))
3145                 item->locked_msgs--;
3146         if (MSG_IS_IGNORE_THREAD(msginfo->flags))
3147                 item->ignored_msgs--;
3148         if (MSG_IS_WATCH_THREAD(msginfo->flags))
3149                 item->watched_msgs--;
3150
3151         msginfo->folder->total_msgs--;
3152
3153         msginfo_update.msginfo = msginfo;
3154         msginfo_update.flags = MSGINFO_UPDATE_DELETED;
3155         hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
3156
3157         msgcache_remove_msg(item->cache, msginfo->msgnum);
3158         folder_item_update_with_msg(msginfo->folder, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_REMOVEMSG, msginfo);
3159 }
3160
3161 gint folder_item_add_msg(FolderItem *dest, const gchar *file,
3162                          MsgFlags *flags, gboolean remove_source)
3163 {
3164         GSList file_list;
3165         MsgFileInfo fileinfo;
3166
3167         cm_return_val_if_fail(dest != NULL, -1);
3168         cm_return_val_if_fail(file != NULL, -1);
3169  
3170         fileinfo.msginfo = NULL;
3171         fileinfo.file = (gchar *)file;
3172         fileinfo.flags = flags;
3173         file_list.data = &fileinfo;
3174         file_list.next = NULL;
3175
3176         return folder_item_add_msgs(dest, &file_list, remove_source);
3177 }
3178
3179 gint folder_item_add_msgs(FolderItem *dest, GSList *file_list,
3180                           gboolean remove_source)
3181 {
3182         Folder *folder;
3183         gint ret, num, lastnum = -1;
3184         GSList *file_cur;
3185         GHashTable *relation;
3186         MsgFileInfo *fileinfo = NULL;
3187         gboolean folderscan = FALSE;
3188
3189         cm_return_val_if_fail(dest != NULL, -1);
3190         cm_return_val_if_fail(file_list != NULL, -1);
3191         cm_return_val_if_fail(dest->folder != NULL, -1);
3192         if (dest->no_select)
3193                 return -1;
3194
3195         folder = dest->folder;
3196
3197         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3198
3199         if (folder->klass->add_msgs != NULL) {
3200                 ret = folder->klass->add_msgs(folder, dest, file_list, relation);
3201                 if (ret < 0) {
3202                         g_hash_table_destroy(relation);
3203                         return ret;
3204                 }
3205         } else {
3206                 for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3207                         fileinfo = (MsgFileInfo *) file_cur->data;
3208
3209                         ret = folder->klass->add_msg(folder, dest, fileinfo->file, fileinfo->flags);
3210                         if (ret < 0) {
3211                                 g_hash_table_destroy(relation);
3212                                 return ret;
3213                         }
3214                         g_hash_table_insert(relation, fileinfo, GINT_TO_POINTER(ret));
3215                 }
3216         }
3217
3218         for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3219                 gpointer data, old_key;
3220
3221                 fileinfo = (MsgFileInfo *) file_cur->data;
3222                 if (g_hash_table_lookup_extended(relation, fileinfo, &old_key, &data))
3223                         num = GPOINTER_TO_INT(data);
3224                 else
3225                         num = -1;
3226
3227                 if (num >= 0) {
3228                         MsgInfo *newmsginfo;
3229
3230                         if (num == 0) {
3231                                 if (!folderscan) {
3232                                         folder_item_scan_full(dest, FALSE);
3233                                         folderscan = TRUE;
3234                                 }
3235                                 num = folder_item_get_msg_num_by_file(dest, fileinfo->file);
3236                                 debug_print("got num %d\n", num);
3237                         }
3238
3239                         if (num > lastnum)
3240                                 lastnum = num;
3241
3242                         if (num >= 0 && remove_source) {
3243                                 if (claws_unlink(fileinfo->file) < 0)
3244                                         FILE_OP_ERROR(fileinfo->file, "unlink");
3245                         }
3246
3247                         if (num == 0)
3248                                 continue;
3249
3250                         if (!folderscan && 
3251                             ((newmsginfo = get_msginfo(dest, num)) != NULL)) {
3252                                 add_msginfo_to_cache(dest, newmsginfo, NULL);
3253                                 procmsg_msginfo_free(&newmsginfo);
3254                         } else if ((newmsginfo = msgcache_get_msg(dest->cache, num)) != NULL) {
3255                                 /* TODO: set default flags */
3256                                 procmsg_msginfo_free(&newmsginfo);
3257                         }
3258                 }
3259         }
3260
3261         g_hash_table_destroy(relation);
3262
3263         return lastnum;
3264 }
3265
3266 static FolderItem *folder_item_move_recursive(FolderItem *src, FolderItem *dest, gboolean copy) 
3267 {
3268         GSList *mlist;
3269         FolderItem *new_item;
3270         FolderItem *next_item;
3271         GNode *srcnode;
3272         gchar *old_id, *new_id;
3273         FolderUpdateData hookdata;
3274
3275         /* move messages */
3276         debug_print("%s %s to %s\n", copy?"Copying":"Moving", src->path, dest->path);
3277         new_item = folder_create_folder(dest, src->name);
3278         if (new_item == NULL) {
3279                 g_print("Can't create folder\n");
3280                 return NULL;
3281         }
3282         
3283         if (new_item->folder == NULL)
3284                 new_item->folder = dest->folder;
3285
3286         /* move messages */
3287         log_message(LOG_PROTOCOL, copy ?_("Copying %s to %s...\n"):_("Moving %s to %s...\n"), 
3288                         src->name, new_item->path);
3289
3290         /*copy prefs*/
3291         folder_item_prefs_copy_prefs(src, new_item);
3292         
3293         /* copy internal data */
3294         if (src->folder->klass == new_item->folder->klass &&
3295             src->folder->klass->copy_private_data != NULL)
3296                 src->folder->klass->copy_private_data(src->folder,
3297                                         src, new_item);
3298         new_item->collapsed = src->collapsed;
3299         new_item->thread_collapsed = src->thread_collapsed;
3300         new_item->threaded  = src->threaded;
3301         new_item->ret_rcpt  = src->ret_rcpt;
3302         new_item->hide_read_msgs = src->hide_read_msgs;
3303         new_item->hide_del_msgs = src->hide_del_msgs;
3304         new_item->hide_read_threads = src->hide_read_threads;
3305         new_item->sort_key  = src->sort_key;
3306         new_item->sort_type = src->sort_type;
3307
3308         mlist = folder_item_get_msg_list(src);
3309
3310         if (mlist != NULL) {
3311                 if (copy)
3312                         folder_item_copy_msgs(new_item, mlist);
3313                 else
3314                         folder_item_move_msgs(new_item, mlist);
3315                 procmsg_msg_list_free(mlist);
3316         }
3317
3318         prefs_matcher_write_config();
3319         
3320         /* recurse */
3321         srcnode = src->folder->node;    
3322         srcnode = g_node_find(srcnode, G_PRE_ORDER, G_TRAVERSE_ALL, src);
3323         srcnode = srcnode->children;
3324         while (srcnode != NULL) {
3325                 if (srcnode && srcnode->data) {
3326                         next_item = (FolderItem*) srcnode->data;
3327                         srcnode = srcnode->next;
3328                         if (folder_item_move_recursive(next_item, new_item, copy) == NULL) {
3329                                 return NULL;
3330                         }
3331                 }
3332         }
3333         old_id = folder_item_get_identifier(src);
3334         new_id = folder_item_get_identifier(new_item);
3335
3336         hookdata.folder = src->folder;
3337         hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_MOVE_FOLDERITEM;
3338         hookdata.item = src;
3339         hookdata.item2 = new_item;
3340         hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
3341
3342         /* if src supports removing, otherwise only copy folder */
3343         if (src->folder->klass->remove_folder != NULL && !copy) 
3344                 src->folder->klass->remove_folder(src->folder, src);
3345         folder_write_list();
3346
3347         if (!copy) {
3348                 debug_print("updating rules : %s => %s\n", old_id, new_id);
3349                 if (old_id != NULL && new_id != NULL) {
3350                         prefs_filtering_rename_path(old_id, new_id);
3351                         account_rename_path(old_id, new_id);
3352                 }
3353         }
3354         g_free(old_id);
3355         g_free(new_id);
3356         
3357         return new_item;
3358 }
3359
3360 gint folder_item_move_to(FolderItem *src, FolderItem *dest, FolderItem **new_item, gboolean copy)
3361 {
3362         FolderItem *tmp = folder_item_parent(dest);
3363         gchar * src_identifier, * dst_identifier;
3364         gchar * phys_srcpath, * phys_dstpath, *tmppath;
3365
3366         while (tmp) {
3367                 if (tmp == src) {
3368                         return F_MOVE_FAILED_DEST_IS_CHILD;
3369                 }
3370                 tmp = folder_item_parent(tmp);
3371         }
3372
3373         /* both dst and src can be root folders */
3374         src_identifier = folder_item_get_identifier(src);
3375         if (src_identifier == NULL && src->folder && folder_item_parent(src) == NULL) {
3376                 src_identifier = folder_get_identifier(src->folder);
3377         }
3378
3379         dst_identifier = folder_item_get_identifier(dest);
3380         if(dst_identifier == NULL && dest->folder && folder_item_parent(dest) == NULL) {
3381                 dst_identifier = folder_get_identifier(dest->folder);
3382         }
3383
3384         if (src_identifier == NULL || dst_identifier == NULL) {
3385                 debug_print("Can't get identifiers\n");
3386                 return F_MOVE_FAILED;
3387         }
3388
3389         if (src->folder != dest->folder && !copy) {
3390                 return F_MOVE_FAILED_DEST_OUTSIDE_MAILBOX;
3391         }
3392
3393         phys_srcpath = folder_item_get_path(src);
3394         tmppath = folder_item_get_path(dest);
3395         phys_dstpath = g_strconcat(tmppath,
3396                        G_DIR_SEPARATOR_S,
3397                        g_path_get_basename(phys_srcpath),
3398                        NULL);
3399         g_free(tmppath);
3400
3401         if (folder_item_parent(src) == dest || src == dest) {
3402                 g_free(src_identifier);
3403                 g_free(dst_identifier);
3404                 g_free(phys_srcpath);
3405                 g_free(phys_dstpath);
3406                 return F_MOVE_FAILED_DEST_IS_PARENT;
3407         }
3408         debug_print("moving \"%s\" to \"%s\"\n", phys_srcpath, phys_dstpath);
3409         if ((tmp = folder_item_move_recursive(src, dest, copy)) == NULL) {
3410                 return F_MOVE_FAILED;
3411         }
3412         
3413         g_free(src_identifier);
3414         g_free(dst_identifier);
3415         g_free(phys_srcpath);
3416         g_free(phys_dstpath);
3417
3418         *new_item = tmp;
3419
3420         return F_MOVE_OK;
3421 }
3422
3423 struct find_data
3424 {
3425         gboolean found;
3426 };      
3427 static void find_num(gpointer key, gpointer value, gpointer data)
3428 {
3429         struct find_data *fdata = (struct find_data *)data;
3430         if (GPOINTER_TO_INT(value) == 0)
3431                 fdata->found = TRUE;
3432 }
3433
3434 static gboolean some_msgs_have_zero_num(GHashTable *hashtable)
3435 {
3436         struct find_data fdata;
3437         
3438         fdata.found = FALSE;
3439         g_hash_table_foreach(hashtable, find_num, &fdata);
3440         
3441         return fdata.found;
3442 }
3443
3444 /**
3445  * Copy a list of message to a new folder and remove
3446  * source messages if wanted
3447  */
3448 static gint do_copy_msgs(FolderItem *dest, GSList *msglist, gboolean remove_source)
3449 {
3450         Folder *folder;
3451         GSList *l;
3452         gint num, lastnum = -1;
3453         gboolean folderscan = FALSE;
3454         GHashTable *relation;
3455         GSList *not_moved = NULL;
3456         gint total = 0, curmsg = 0;
3457         MsgInfo *msginfo = NULL;
3458
3459         cm_return_val_if_fail(dest != NULL, -1);
3460         cm_return_val_if_fail(msglist != NULL, -1);
3461
3462         folder = dest->folder;
3463
3464         cm_return_val_if_fail(folder->klass->copy_msg != NULL, -1);
3465         if (dest->no_select)
3466                 return -1;
3467
3468         msginfo = (MsgInfo *)msglist->data;
3469         
3470         if (!msginfo)
3471                 return -1;
3472         
3473         if (!MSG_IS_QUEUED(msginfo->flags) && 
3474             MSG_IS_DRAFT(msginfo->flags) && 
3475             folder_has_parent_of_type(dest, F_QUEUE)) {
3476                 GSList *cur = msglist;
3477                 gboolean queue_err = FALSE;
3478                 for (; cur; cur = cur->next) {
3479                         Compose *compose = NULL;
3480                         FolderItem *queue = dest;
3481                         ComposeQueueResult val = COMPOSE_QUEUE_SUCCESS;
3482                         
3483                         msginfo = (MsgInfo *)cur->data;
3484                         compose = compose_reedit(msginfo, TRUE);
3485                         if (compose == NULL) {
3486                                 queue_err = TRUE;
3487                                 continue;
3488                         }
3489                         val = compose_queue(compose, NULL, &queue, NULL,
3490                                         FALSE);
3491                         if (val != COMPOSE_QUEUE_SUCCESS) {
3492                                 queue_err = TRUE;
3493                         } else if (remove_source) {
3494                                 folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
3495                         }
3496                         if (val == COMPOSE_QUEUE_SUCCESS)
3497                                 compose_close(compose);
3498                 }
3499                 return queue_err ? -1:0;
3500         }
3501
3502         relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3503
3504         for (l = msglist ; l != NULL ; l = g_slist_next(l)) {
3505                 MsgInfo * msginfo = (MsgInfo *) l->data;
3506
3507                 if (msginfo->planned_download != 0) {
3508                         int old_planned = msginfo->planned_download;
3509                         partial_unmark(msginfo);
3510                         /* little hack to reenable after */
3511                         msginfo->planned_download = old_planned;
3512                 }
3513         }
3514
3515         /* 
3516          * Copy messages to destination folder and 
3517          * store new message numbers in newmsgnums
3518          */
3519         if (folder->klass->copy_msgs != NULL) {