2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
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.
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.
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/>.
21 #include "claws-features.h"
27 #include <glib/gi18n.h>
30 #include <sys/types.h>
38 #include "alertpanel.h"
48 #include "prefs_gtk.h"
50 #include "filtering.h"
51 #include "procheader.h"
54 #include "folder_item_prefs.h"
55 #include "remotefolder.h"
56 #include "partial_download.h"
57 #include "statusbar.h"
64 #include "prefs_common.h"
65 #include "prefs_migration.h"
66 #include "file-utils.h"
68 /* Dependecies to be removed ?! */
69 #include "prefs_account.h"
71 /* Define possible missing constants for Windows. */
83 static GList *folder_list = NULL;
84 static GSList *class_list = NULL;
85 static GSList *folder_unloaded_list = NULL;
87 void folder_init (Folder *folder,
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);
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,
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);
109 void folder_system_init(void)
111 folder_register_class(mh_get_class());
112 folder_register_class(imap_get_class());
113 folder_register_class(news_get_class());
116 static GSList *folder_get_class_list(void)
121 void folder_register_class(FolderClass *klass)
123 GSList *xmllist, *cur;
125 debug_print("registering folder class %s\n", klass->idstr);
127 class_list = g_slist_append(class_list, klass);
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;
135 for (; cur != NULL; cur = g_list_next(cur)) {
136 XMLAttr *attr = (XMLAttr *) cur->data;
138 if (!attr || !attr->name || !attr->value) continue;
139 if (!strcmp(attr->name, "type") && !strcmp(attr->value, klass->idstr)) {
142 folder = folder_get_from_xml(node);
145 folder_unloaded_list = g_slist_remove(folder_unloaded_list, node);
153 g_slist_free(xmllist);
156 void folder_unregister_class(FolderClass *klass)
158 GList *folderlist, *cur;
160 debug_print("unregistering folder class %s\n", klass->idstr);
162 class_list = g_slist_remove(class_list, klass);
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;
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);
174 g_list_free(folderlist);
176 if (klass->prefs_pages)
177 g_slist_free(klass->prefs_pages);
180 Folder *folder_new(FolderClass *klass, const gchar *name, const gchar *path)
182 Folder *folder = NULL;
185 cm_return_val_if_fail(klass != NULL, NULL);
187 name = name ? name : path;
188 folder = klass->new_folder(name, path);
190 /* Create root folder item */
191 item = folder_item_new(folder, name, NULL);
195 item->folder = folder;
196 folder->node = item->node;
202 void folder_init(Folder *folder, const gchar *name)
204 cm_return_if_fail(folder != NULL);
206 folder_set_name(folder, name);
208 /* Init folder data */
209 folder->account = NULL;
211 folder->inbox = NULL;
212 folder->outbox = NULL;
213 folder->draft = NULL;
214 folder->queue = NULL;
215 folder->trash = NULL;
218 static void reset_parent_type(FolderItem *item, gpointer data) {
219 item->parent_stype = -1;
222 void folder_item_change_type(FolderItem *item, SpecialFolderItemType newtype)
224 Folder *folder = NULL;
225 FolderUpdateData hookdata;
230 folder = item->folder;
231 /* unset previous root of newtype */
234 folder_item_change_type(folder->inbox, F_NORMAL);
235 folder->inbox = item;
238 folder_item_change_type(folder->outbox, F_NORMAL);
239 folder->outbox = item;
242 folder_item_change_type(folder->queue, F_NORMAL);
243 folder->queue = item;
246 folder_item_change_type(folder->draft, F_NORMAL);
247 folder->draft = item;
250 folder_item_change_type(folder->trash, F_NORMAL);
251 folder->trash = item;
257 /* set new type for current folder and sons */
258 item->stype = newtype;
259 folder_func_to_all_folders(reset_parent_type, NULL);
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);
268 void folder_destroy(Folder *folder)
270 cm_return_if_fail(folder != NULL);
271 cm_return_if_fail(folder->klass->destroy_folder != NULL);
273 folder_remove(folder);
275 folder_tree_destroy(folder);
277 folder->klass->destroy_folder(folder);
279 g_free(folder->name);
283 void folder_set_xml(Folder *folder, XMLTag *tag)
286 FolderItem *rootitem = NULL;
288 if ((folder->node != NULL) && (folder->node->data != NULL))
289 rootitem = (FolderItem *) folder->node->data;
291 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
292 XMLAttr *attr = (XMLAttr *) cur->data;
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);
302 } else if (!strcmp(attr->name, "account_id")) {
303 PrefsAccount *account;
305 account = account_find_from_id(atoi(attr->value));
307 g_warning("account_id: %s not found", attr->value);
309 folder->account = account;
310 account->folder = folder;
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);
321 XMLTag *folder_get_xml(Folder *folder)
325 tag = xml_tag_new("folder");
328 xml_tag_add_attr(tag, xml_attr_new("name", folder->name));
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;
334 xml_tag_add_attr(tag, xml_attr_new("collapsed", rootitem->collapsed ? "1" : "0"));
336 xml_tag_add_attr(tag, xml_attr_new_int("sort", folder->sort));
341 FolderItem *folder_item_new(Folder *folder, const gchar *name, const gchar *path)
343 FolderItem *item = NULL;
345 cm_return_val_if_fail(folder != NULL, NULL);
347 if (folder->klass->item_new) {
348 item = folder->klass->item_new(folder);
350 item = g_new0(FolderItem, 1);
353 cm_return_val_if_fail(item != NULL, NULL);
355 item->stype = F_NORMAL;
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);
361 item->name = g_strdup(name);
364 item->path = g_strdup(path);
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;
379 item->no_sub = FALSE;
380 item->no_select = FALSE;
381 item->collapsed = FALSE;
382 item->thread_collapsed = FALSE;
383 item->threaded = TRUE;
384 item->ret_rcpt = FALSE;
385 item->opened = FALSE;
386 item->node = g_node_new(item);
388 item->account = NULL;
389 item->apply_sub = FALSE;
390 item->mark_queue = NULL;
392 item->parent_stype = -1;
394 item->sort_key = prefs_common.default_sort_key;
395 item->sort_type = prefs_common.default_sort_type;
397 item->prefs = folder_item_prefs_new();
402 void folder_item_append(FolderItem *parent, FolderItem *item)
404 cm_return_if_fail(parent != NULL);
405 cm_return_if_fail(parent->folder != NULL);
406 cm_return_if_fail(parent->node != NULL);
407 cm_return_if_fail(item != NULL);
409 item->folder = parent->folder;
410 g_node_append(parent->node, item->node);
413 void folder_item_remove(FolderItem *item)
415 GNode *node, *start_node;
416 FolderUpdateData hookdata;
417 gchar *tags_file = NULL, *tags_dir = NULL;
419 cm_return_if_fail(item != NULL);
420 cm_return_if_fail(item->folder != NULL);
421 cm_return_if_fail(item->folder->node != NULL);
423 start_node = item->node;
425 node = item->folder->node;
427 node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
428 node = node->children;
430 /* remove my children */
431 while (node != NULL) {
432 if (node && node->data) {
433 FolderItem *sub_item = (FolderItem*) node->data;
435 folder_item_remove(sub_item);
440 if (item->cache != NULL) {
441 msgcache_destroy(item->cache);
444 tags_file = folder_item_get_tags_file(item);
446 claws_unlink(tags_file);
447 tags_dir = g_path_get_dirname(tags_file);
454 hookdata.folder = item->folder;
455 hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_REMOVE_FOLDERITEM;
456 hookdata.item = item;
457 hookdata.item2 = NULL;
458 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
462 if (item->folder->node == node)
463 item->folder->node = NULL;
465 folder_item_destroy(item);
467 g_node_destroy(node);
470 void folder_item_remove_children(FolderItem *item)
474 cm_return_if_fail(item != NULL);
475 cm_return_if_fail(item->folder != NULL);
476 cm_return_if_fail(item->node != NULL);
478 node = item->node->children;
479 while (node != NULL) {
481 folder_item_remove(FOLDER_ITEM(node->data));
486 void folder_item_destroy(FolderItem *item)
490 cm_return_if_fail(item != NULL);
492 folder = item->folder;
494 if (folder->inbox == item)
495 folder->inbox = NULL;
496 else if (folder->outbox == item)
497 folder->outbox = NULL;
498 else if (folder->draft == item)
499 folder->draft = NULL;
500 else if (folder->queue == item)
501 folder->queue = NULL;
502 else if (folder->trash == item)
503 folder->trash = NULL;
507 folder_item_free_cache(item, TRUE);
509 folder_item_prefs_free(item->prefs);
513 if (item->folder != NULL) {
514 if(item->folder->klass->item_destroy) {
515 item->folder->klass->item_destroy(item->folder, item);
522 FolderItem *folder_item_parent(FolderItem *item)
524 cm_return_val_if_fail(item != NULL, NULL);
525 cm_return_val_if_fail(item->node != NULL, NULL);
527 if (item->node->parent == NULL)
529 return (FolderItem *) item->node->parent->data;
532 void folder_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
536 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
537 XMLAttr *attr = (XMLAttr *) cur->data;
539 if (!attr || !attr->name || !attr->value) continue;
540 if (!strcmp(attr->name, "type")) {
541 if (!g_ascii_strcasecmp(attr->value, "normal"))
542 item->stype = F_NORMAL;
543 else if (!g_ascii_strcasecmp(attr->value, "inbox"))
544 item->stype = F_INBOX;
545 else if (!g_ascii_strcasecmp(attr->value, "outbox"))
546 item->stype = F_OUTBOX;
547 else if (!g_ascii_strcasecmp(attr->value, "draft"))
548 item->stype = F_DRAFT;
549 else if (!g_ascii_strcasecmp(attr->value, "queue"))
550 item->stype = F_QUEUE;
551 else if (!g_ascii_strcasecmp(attr->value, "trash"))
552 item->stype = F_TRASH;
553 } else if (!strcmp(attr->name, "name")) {
555 item->name = g_strdup(attr->value);
556 } else if (!strcmp(attr->name, "path")) {
558 item->path = g_strdup(attr->value);
559 } else if (!strcmp(attr->name, "mtime"))
560 item->mtime = strtoul(attr->value, NULL, 10);
561 else if (!strcmp(attr->name, "new"))
562 item->new_msgs = atoi(attr->value);
563 else if (!strcmp(attr->name, "unread"))
564 item->unread_msgs = atoi(attr->value);
565 else if (!strcmp(attr->name, "unreadmarked"))
566 item->unreadmarked_msgs = atoi(attr->value);
567 else if (!strcmp(attr->name, "marked"))
568 item->marked_msgs = atoi(attr->value);
569 else if (!strcmp(attr->name, "replied"))
570 item->replied_msgs = atoi(attr->value);
571 else if (!strcmp(attr->name, "forwarded"))
572 item->forwarded_msgs = atoi(attr->value);
573 else if (!strcmp(attr->name, "locked"))
574 item->locked_msgs = atoi(attr->value);
575 else if (!strcmp(attr->name, "ignored"))
576 item->ignored_msgs = atoi(attr->value);
577 else if (!strcmp(attr->name, "watched"))
578 item->watched_msgs = atoi(attr->value);
579 else if (!strcmp(attr->name, "order"))
580 item->order = atoi(attr->value);
581 else if (!strcmp(attr->name, "total"))
582 item->total_msgs = atoi(attr->value);
583 else if (!strcmp(attr->name, "no_sub"))
584 item->no_sub = *attr->value == '1' ? TRUE : FALSE;
585 else if (!strcmp(attr->name, "no_select"))
586 item->no_select = *attr->value == '1' ? TRUE : FALSE;
587 else if (!strcmp(attr->name, "collapsed"))
588 item->collapsed = *attr->value == '1' ? TRUE : FALSE;
589 else if (!strcmp(attr->name, "thread_collapsed"))
590 item->thread_collapsed = *attr->value == '1' ? TRUE : FALSE;
591 else if (!strcmp(attr->name, "threaded"))
592 item->threaded = *attr->value == '1' ? TRUE : FALSE;
593 else if (!strcmp(attr->name, "hidereadmsgs"))
594 item->hide_read_msgs = *attr->value == '1' ? TRUE : FALSE;
595 else if (!strcmp(attr->name, "hidedelmsgs"))
596 item->hide_del_msgs = *attr->value == '1' ? TRUE : FALSE;
597 else if (!strcmp(attr->name, "hidereadthreads"))
598 item->hide_read_threads = *attr->value == '1' ? TRUE : FALSE;
599 else if (!strcmp(attr->name, "reqretrcpt"))
600 item->ret_rcpt = *attr->value == '1' ? TRUE : FALSE;
601 else if (!strcmp(attr->name, "sort_key")) {
602 if (!strcmp(attr->value, "none"))
603 item->sort_key = SORT_BY_NONE;
604 else if (!strcmp(attr->value, "number"))
605 item->sort_key = SORT_BY_NUMBER;
606 else if (!strcmp(attr->value, "size"))
607 item->sort_key = SORT_BY_SIZE;
608 else if (!strcmp(attr->value, "date"))
609 item->sort_key = SORT_BY_DATE;
610 else if (!strcmp(attr->value, "from"))
611 item->sort_key = SORT_BY_FROM;
612 else if (!strcmp(attr->value, "subject"))
613 item->sort_key = SORT_BY_SUBJECT;
614 else if (!strcmp(attr->value, "score"))
615 item->sort_key = SORT_BY_SCORE;
616 else if (!strcmp(attr->value, "label"))
617 item->sort_key = SORT_BY_LABEL;
618 else if (!strcmp(attr->value, "mark"))
619 item->sort_key = SORT_BY_MARK;
620 else if (!strcmp(attr->value, "unread"))
621 item->sort_key = SORT_BY_STATUS;
622 else if (!strcmp(attr->value, "mime"))
623 item->sort_key = SORT_BY_MIME;
624 else if (!strcmp(attr->value, "to"))
625 item->sort_key = SORT_BY_TO;
626 else if (!strcmp(attr->value, "locked"))
627 item->sort_key = SORT_BY_LOCKED;
628 else if (!strcmp(attr->value, "tags"))
629 item->sort_key = SORT_BY_TAGS;
630 else if (!strcmp(attr->value, "thread_date"))
631 item->sort_key = SORT_BY_THREAD_DATE;
632 } else if (!strcmp(attr->name, "sort_type")) {
633 if (!strcmp(attr->value, "ascending"))
634 item->sort_type = SORT_ASCENDING;
636 item->sort_type = SORT_DESCENDING;
637 } else if (!strcmp(attr->name, "account_id")) {
638 PrefsAccount *account;
640 account = account_find_from_id(atoi(attr->value));
642 g_warning("account_id: %s not found", attr->value);
644 item->account = account;
645 } else if (!strcmp(attr->name, "apply_sub")) {
646 item->apply_sub = *attr->value == '1' ? TRUE : FALSE;
647 } else if (!strcmp(attr->name, "last_seen")) {
648 if (!claws_crashed())
649 item->last_seen = atoi(attr->value);
654 /* options without meaning in drafts */
655 if (item->stype == F_DRAFT)
656 item->hide_read_msgs =
657 item->hide_del_msgs =
658 item->hide_read_threads = FALSE;
661 XMLTag *folder_item_get_xml(Folder *folder, FolderItem *item)
663 static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox",
664 "draft", "queue", "trash"};
665 static gchar *sort_key_str[] = {"none", "number", "size", "date",
666 "from", "subject", "score", "label",
667 "mark", "unread", "mime", "to",
668 "locked", "tags", "thread_date" };
672 tag = xml_tag_new("folderitem");
674 xml_tag_add_attr(tag, xml_attr_new("type", folder_item_stype_str[item->stype]));
676 xml_tag_add_attr(tag, xml_attr_new("name", item->name));
678 xml_tag_add_attr(tag, xml_attr_new("path", item->path));
680 xml_tag_add_attr(tag, xml_attr_new("no_sub", "1"));
682 xml_tag_add_attr(tag, xml_attr_new("no_select", "1"));
683 xml_tag_add_attr(tag, xml_attr_new("collapsed", item->collapsed && item->node->children ? "1" : "0"));
684 xml_tag_add_attr(tag, xml_attr_new("thread_collapsed", item->thread_collapsed ? "1" : "0"));
685 xml_tag_add_attr(tag, xml_attr_new("threaded", item->threaded ? "1" : "0"));
686 xml_tag_add_attr(tag, xml_attr_new("hidereadmsgs", item->hide_read_msgs ? "1" : "0"));
687 xml_tag_add_attr(tag, xml_attr_new("hidedelmsgs", item->hide_del_msgs ? "1" : "0"));
688 xml_tag_add_attr(tag, xml_attr_new("hidereadthreads", item->hide_read_threads ? "1" : "0"));
690 xml_tag_add_attr(tag, xml_attr_new("reqretrcpt", "1"));
692 if (item->sort_key != SORT_BY_NONE) {
693 xml_tag_add_attr(tag, xml_attr_new("sort_key", sort_key_str[item->sort_key]));
694 xml_tag_add_attr(tag, xml_attr_new("sort_type", item->sort_type == SORT_ASCENDING ? "ascending" : "descending"));
697 value = g_strdup_printf("%ld", (unsigned long int) item->mtime);
698 xml_tag_add_attr(tag, xml_attr_new("mtime", value));
700 xml_tag_add_attr(tag, xml_attr_new_int("new", item->new_msgs));
701 xml_tag_add_attr(tag, xml_attr_new_int("unread", item->unread_msgs));
702 xml_tag_add_attr(tag, xml_attr_new_int("unreadmarked", item->unreadmarked_msgs));
703 xml_tag_add_attr(tag, xml_attr_new_int("marked", item->marked_msgs));
704 xml_tag_add_attr(tag, xml_attr_new_int("total", item->total_msgs));
705 xml_tag_add_attr(tag, xml_attr_new_int("replied", item->replied_msgs));
706 xml_tag_add_attr(tag, xml_attr_new_int("forwarded", item->forwarded_msgs));
707 xml_tag_add_attr(tag, xml_attr_new_int("locked", item->locked_msgs));
708 xml_tag_add_attr(tag, xml_attr_new_int("ignore", item->ignored_msgs));
709 xml_tag_add_attr(tag, xml_attr_new_int("watched", item->watched_msgs));
710 xml_tag_add_attr(tag, xml_attr_new_int("order", item->order));
713 xml_tag_add_attr(tag, xml_attr_new_int("account_id", item->account->account_id));
715 xml_tag_add_attr(tag, xml_attr_new("apply_sub", "1"));
717 xml_tag_add_attr(tag, xml_attr_new_int("last_seen", item->last_seen));
722 void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data)
724 cm_return_if_fail(folder != NULL);
726 folder->ui_func = func;
727 folder->ui_func_data = data;
730 void folder_set_name(Folder *folder, const gchar *name)
732 cm_return_if_fail(folder != NULL);
734 g_free(folder->name);
735 folder->name = name ? g_strdup(name) : NULL;
736 if (folder->node && folder->node->data) {
737 FolderItem *item = (FolderItem *)folder->node->data;
740 item->name = name ? g_strdup(name) : NULL;
744 void folder_set_sort(Folder *folder, guint sort)
746 cm_return_if_fail(folder != NULL);
748 if (folder->sort != sort) {
749 folder_remove(folder);
755 static gboolean folder_tree_destroy_func(GNode *node, gpointer data) {
756 FolderItem *item = (FolderItem *) node->data;
758 folder_item_destroy(item);
762 void folder_tree_destroy(Folder *folder)
766 cm_return_if_fail(folder != NULL);
770 prefs_filtering_clear_folder(folder);
773 g_node_traverse(node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
774 folder_tree_destroy_func, NULL);
775 g_node_destroy(node);
780 void folder_add(Folder *folder)
785 FolderUpdateData hookdata;
787 cm_return_if_fail(folder != NULL);
789 if ((FOLDER_TYPE(folder) == F_IMAP ||
790 FOLDER_TYPE(folder) == F_NEWS) &&
791 folder->account == NULL) {
795 for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) {
796 cur_folder = FOLDER(cur->data);
797 if (cur_folder->sort < folder->sort)
801 folder_list = g_list_insert(folder_list, folder, i);
803 hookdata.folder = folder;
804 hookdata.update_flags = FOLDER_ADD_FOLDER;
805 hookdata.item = NULL;
806 hookdata.item2 = NULL;
807 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
810 void folder_remove(Folder *folder)
812 FolderUpdateData hookdata;
814 cm_return_if_fail(folder != NULL);
816 folder_list = g_list_remove(folder_list, folder);
818 hookdata.folder = folder;
819 hookdata.update_flags = FOLDER_REMOVE_FOLDER;
820 hookdata.item = NULL;
821 hookdata.item2 = NULL;
822 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
825 GList *folder_get_list(void)
830 gint folder_read_list(void)
836 gint config_version = -1;
838 path = folder_get_list_path();
839 if (!is_file_exist(path)) return -1;
840 node = xml_parse_file(path);
841 if (!node) return -1;
843 xmlnode = node->data;
844 if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) {
845 g_warning("wrong folder list");
850 cur = node->children;
851 while (cur != NULL) {
854 folder = folder_get_from_xml(cur);
858 folder_unloaded_list = g_slist_append(folder_unloaded_list,
859 (gpointer) xml_copy_tree(cur));
863 for (list = xmlnode->tag->attr; list != NULL; list = list->next) {
864 XMLAttr *attr = list->data;
866 if (!attr || !attr->name || !attr->value) continue;
867 if (!strcmp(attr->name, "config_version")) {
868 config_version = atoi(attr->value);
869 debug_print("Found folderlist config_version %d\n", config_version);
875 if (prefs_update_config_version_folderlist(config_version) < 0) {
876 debug_print("Folderlist configuration file version upgrade failed\n");
880 if (folder_list || folder_unloaded_list)
886 void folder_write_list(void)
897 path = folder_get_list_path();
898 if ((pfile = prefs_write_open(path)) == NULL) return;
900 if (xml_file_put_xml_decl(pfile->fp) < 0) {
901 prefs_file_close_revert(pfile);
902 g_warning("failed to start write folder list.");
905 tag = xml_tag_new("folderlist");
906 xml_tag_add_attr(tag, xml_attr_new_int("config_version",
907 CLAWS_CONFIG_VERSION));
909 xmlnode = xml_node_new(tag, NULL);
911 rootnode = g_node_new(xmlnode);
913 for (list = folder_list; list != NULL; list = list->next) {
917 node = folder_get_xml_node(folder);
919 g_node_append(rootnode, node);
922 for (slist = folder_unloaded_list; slist != NULL; slist = g_slist_next(slist)) {
923 GNode *node = (GNode *) slist->data;
925 g_node_append(rootnode, (gpointer) xml_copy_tree(node));
928 if (xml_write_tree(rootnode, pfile->fp) < 0) {
929 prefs_file_close_revert(pfile);
930 g_warning("failed to write folder list.");
931 } else if (prefs_file_close(pfile) < 0) {
932 g_warning("failed to write folder list.");
934 xml_free_tree(rootnode);
937 static gboolean folder_scan_tree_func(GNode *node, gpointer data)
939 GHashTable *pptable = (GHashTable *)data;
940 FolderItem *item = (FolderItem *)node->data;
942 folder_item_restore_persist_prefs(item, pptable);
943 folder_item_scan_full(item, FALSE);
948 static gboolean folder_restore_prefs_func(GNode *node, gpointer data)
950 GHashTable *pptable = (GHashTable *)data;
951 FolderItem *item = (FolderItem *)node->data;
953 folder_item_restore_persist_prefs(item, pptable);
958 void folder_scan_tree(Folder *folder, gboolean rebuild)
961 FolderUpdateData hookdata;
962 Folder *old_folder = folder;
964 if (!folder->klass->scan_tree)
967 pptable = folder_persist_prefs_new(folder);
970 folder_remove(folder);
972 if (folder->klass->scan_tree(folder) < 0) {
974 folder_add(old_folder);
979 hookdata.folder = folder;
980 hookdata.update_flags = FOLDER_TREE_CHANGED;
981 hookdata.item = NULL;
982 hookdata.item2 = NULL;
983 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
986 g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, folder_scan_tree_func, pptable);
988 g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1, folder_restore_prefs_func, pptable);
990 folder_persist_prefs_free(pptable);
992 prefs_matcher_read_config();
997 FolderItem *folder_create_folder(FolderItem *parent, const gchar *name)
999 FolderItem *new_item;
1001 cm_return_val_if_fail(parent != NULL, NULL);
1003 new_item = parent->folder->klass->create_folder(parent->folder, parent, name);
1005 FolderUpdateData hookdata;
1007 new_item->cache = msgcache_new();
1008 new_item->cache_dirty = TRUE;
1009 new_item->mark_dirty = TRUE;
1010 new_item->tags_dirty = TRUE;
1012 hookdata.folder = new_item->folder;
1013 hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_ADD_FOLDERITEM;
1014 hookdata.item = new_item;
1015 hookdata.item2 = NULL;
1016 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
1022 gint folder_item_rename(FolderItem *item, gchar *newname)
1026 cm_return_val_if_fail(item != NULL, -1);
1027 cm_return_val_if_fail(newname != NULL, -1);
1029 retval = item->folder->klass->rename_folder(item->folder, item, newname);
1032 FolderItemUpdateData hookdata;
1033 FolderUpdateData hookdata2;
1035 hookdata.item = item;
1036 hookdata.update_flags = F_ITEM_UPDATE_NAME;
1037 hookdata.msg = NULL;
1038 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &hookdata);
1040 hookdata2.folder = item->folder;
1041 hookdata2.item = item;
1042 hookdata2.item2 = NULL;
1043 hookdata2.update_flags = FOLDER_RENAME_FOLDERITEM;
1044 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata2);
1050 struct TotalMsgCount
1054 guint unreadmarked_msgs;
1058 guint forwarded_msgs;
1064 struct FuncToAllFoldersData
1066 FolderItemFunc function;
1070 static gboolean folder_func_to_all_folders_func(GNode *node, gpointer data)
1073 struct FuncToAllFoldersData *function_data = (struct FuncToAllFoldersData *) data;
1075 cm_return_val_if_fail(node->data != NULL, FALSE);
1077 item = FOLDER_ITEM(node->data);
1078 cm_return_val_if_fail(item != NULL, FALSE);
1080 function_data->function(item, function_data->data);
1085 void folder_func_to_all_folders(FolderItemFunc function, gpointer data)
1089 struct FuncToAllFoldersData function_data;
1091 function_data.function = function;
1092 function_data.data = data;
1094 for (list = folder_list; list != NULL; list = list->next) {
1095 folder = FOLDER(list->data);
1097 g_node_traverse(folder->node, G_PRE_ORDER,
1099 folder_func_to_all_folders_func,
1104 static void folder_count_total_msgs_func(FolderItem *item, gpointer data)
1106 struct TotalMsgCount *count = (struct TotalMsgCount *)data;
1108 count->new_msgs += item->new_msgs;
1109 count->unread_msgs += item->unread_msgs;
1110 count->unreadmarked_msgs += item->unreadmarked_msgs;
1111 count->marked_msgs += item->marked_msgs;
1112 count->total_msgs += item->total_msgs;
1113 count->replied_msgs += item->replied_msgs;
1114 count->forwarded_msgs += item->forwarded_msgs;
1115 count->locked_msgs += item->locked_msgs;
1116 count->ignored_msgs += item->ignored_msgs;
1117 count->watched_msgs += item->watched_msgs;
1120 struct TotalMsgStatus
1128 static gboolean folder_get_status_full_all_func(GNode *node, gpointer data)
1131 struct TotalMsgStatus *status = (struct TotalMsgStatus *)data;
1134 cm_return_val_if_fail(node->data != NULL, FALSE);
1136 item = FOLDER_ITEM(node->data);
1138 if (!item->path) return FALSE;
1140 status->new += item->new_msgs;
1141 status->unread += item->unread_msgs;
1142 status->total += item->total_msgs;
1145 id = folder_item_get_identifier(item);
1146 g_string_append_printf(status->str, "%5d %5d %5d %s\n",
1147 item->new_msgs, item->unread_msgs,
1148 item->total_msgs, id);
1155 static void folder_get_status_full_all(GString *str, guint *new, guint *unread,
1160 struct TotalMsgStatus status;
1162 status.new = status.unread = status.total = 0;
1165 debug_print("Counting total number of messages...\n");
1167 for (list = folder_list; list != NULL; list = list->next) {
1168 folder = FOLDER(list->data);
1170 g_node_traverse(folder->node, G_PRE_ORDER,
1172 folder_get_status_full_all_func,
1177 *unread = status.unread;
1178 *total = status.total;
1181 gchar *folder_get_status(GPtrArray *folders, gboolean full)
1183 guint new, unread, total;
1188 new = unread = total = 0;
1190 str = g_string_new(NULL);
1193 for (i = 0; i < folders->len; i++) {
1196 item = g_ptr_array_index(folders, i);
1197 new += item->new_msgs;
1198 unread += item->unread_msgs;
1199 total += item->total_msgs;
1204 id = folder_item_get_identifier(item);
1205 g_string_append_printf(str, "%5d %5d %5d %s\n",
1206 item->new_msgs, item->unread_msgs,
1207 item->total_msgs, id);
1212 folder_get_status_full_all(full ? str : NULL,
1213 &new, &unread, &total);
1217 g_string_append_printf(str, "%5d %5d %5d\n", new, unread, total);
1219 g_string_append_printf(str, "%d %d %d\n", new, unread, total);
1222 g_string_free(str, FALSE);
1227 void folder_count_total_msgs(guint *new_msgs, guint *unread_msgs,
1228 guint *unreadmarked_msgs, guint *marked_msgs,
1229 guint *total_msgs, guint *replied_msgs,
1230 guint *forwarded_msgs, guint *locked_msgs,
1231 guint *ignored_msgs, guint *watched_msgs)
1233 struct TotalMsgCount count;
1235 count.new_msgs = count.unread_msgs = count.unreadmarked_msgs = 0;
1236 count.total_msgs = count.replied_msgs = count.forwarded_msgs = 0;
1237 count.locked_msgs = count.ignored_msgs = count.watched_msgs = 0;
1238 count.marked_msgs = 0;
1240 debug_print("Counting total number of messages...\n");
1242 folder_func_to_all_folders(folder_count_total_msgs_func, &count);
1244 *new_msgs = count.new_msgs;
1245 *unread_msgs = count.unread_msgs;
1246 *unreadmarked_msgs = count.unreadmarked_msgs;
1247 *marked_msgs = count.marked_msgs;
1248 *total_msgs = count.total_msgs;
1249 *replied_msgs = count.replied_msgs;
1250 *forwarded_msgs = count.forwarded_msgs;
1251 *locked_msgs = count.locked_msgs;
1252 *ignored_msgs = count.ignored_msgs;
1253 *watched_msgs = count.watched_msgs;
1256 Folder *folder_find_from_path(const gchar *path)
1261 for (list = folder_list; list != NULL; list = list->next) {
1262 folder = list->data;
1263 if ((FOLDER_TYPE(folder) == F_MH ||
1264 FOLDER_TYPE(folder) == F_MBOX) &&
1265 !path_cmp(LOCAL_FOLDER(folder)->rootpath, path))
1272 Folder *folder_find_from_name(const gchar *name, FolderClass *klass)
1277 for (list = folder_list; list != NULL; list = list->next) {
1278 folder = list->data;
1279 if (folder->klass == klass &&
1280 strcmp2(name, folder->name) == 0)
1287 static gboolean folder_item_find_func(GNode *node, gpointer data)
1289 FolderItem *item = node->data;
1291 const gchar *path = d[0];
1293 if (path_cmp(path, item->path) != 0)
1301 FolderItem *folder_find_item_from_path(const gchar *path)
1305 GList *list = folder_get_list();
1307 folder = list ? list->data:NULL;
1309 cm_return_val_if_fail(folder != NULL, NULL);
1311 d[0] = (gpointer)path;
1313 while (d[1] == NULL && list) {
1314 folder = FOLDER(list->data);
1315 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1316 folder_item_find_func, d);
1322 static gboolean folder_item_find_func_real_path(GNode *node, gpointer data)
1324 FolderItem *item = node->data;
1326 const gchar *path = d[0];
1327 gchar *tmp = folder_item_get_path(item);
1328 if (path_cmp(path, tmp) != 0) {
1338 FolderItem *folder_find_item_from_real_path(const gchar *path)
1342 GList *list = folder_get_list();
1344 folder = list ? list->data:NULL;
1346 cm_return_val_if_fail(folder != NULL, NULL);
1348 d[0] = (gpointer)path;
1350 while (d[1] == NULL && list) {
1351 folder = FOLDER(list->data);
1352 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1353 folder_item_find_func_real_path, d);
1359 FolderItem *folder_find_child_item_by_name(FolderItem *item, const gchar *name)
1364 for (node = item->node->children; node != NULL; node = node->next) {
1365 child = FOLDER_ITEM(node->data);
1366 if (strcmp2(child->name, name) == 0) {
1374 FolderClass *folder_get_class_from_string(const gchar *str)
1378 classlist = folder_get_class_list();
1379 for (; classlist != NULL; classlist = g_slist_next(classlist)) {
1380 FolderClass *class = (FolderClass *) classlist->data;
1381 if (g_ascii_strcasecmp(class->idstr, str) == 0)
1388 gchar *folder_get_identifier(Folder *folder)
1392 cm_return_val_if_fail(folder != NULL, NULL);
1394 type_str = folder->klass->idstr;
1395 return g_strconcat("#", type_str, "/", folder->name, NULL);
1398 gchar *folder_item_get_identifier(FolderItem *item)
1401 gchar *folder_id = NULL;
1403 cm_return_val_if_fail(item != NULL, NULL);
1405 if (item->path == NULL)
1408 folder_id = folder_get_identifier(item->folder);
1409 id = g_strconcat(folder_id, "/", item->path, NULL);
1415 Folder *folder_find_from_identifier(const gchar *identifier)
1422 cm_return_val_if_fail(identifier != NULL, NULL);
1424 if (*identifier != '#')
1427 Xstrdup_a(str, identifier, return NULL);
1429 p = strchr(str, '/');
1434 class = folder_get_class_from_string(&str[1]);
1443 return folder_find_from_name(name, class);
1446 FolderItem *folder_find_item_from_identifier(const gchar *identifier)
1456 cm_return_val_if_fail(identifier != NULL, NULL);
1458 if (*identifier != '#')
1459 return folder_find_item_from_path(identifier);
1461 Xstrdup_a(str, identifier, return NULL);
1463 p = strchr(str, '/');
1465 return folder_find_item_from_path(identifier);
1468 class = folder_get_class_from_string(&str[1]);
1470 return folder_find_item_from_path(identifier);
1475 return folder_find_item_from_path(identifier);
1479 folder = folder_find_from_name(name, class);
1481 return folder_find_item_from_path(identifier);
1485 d[0] = (gpointer)path;
1487 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1488 folder_item_find_func, d);
1492 /** Returns the FolderItem from a given identifier
1494 * The FolderItem is created if it doesn't already exist.
1495 * If creation failed, the function returns NULL.
1497 * Identifiers are of the form #type/Mailbox/FolderA/FolderB/FolderC
1499 FolderItem *folder_get_item_from_identifier(const gchar *identifier)
1501 FolderItem *item, *last_parent;
1503 gchar *p1, *p2, *str;
1506 gboolean created_something = FALSE;
1508 item = folder_find_item_from_identifier(identifier);
1512 /* trivial sanity check: need at least # and two slashes */
1513 len = strlen(identifier);
1517 /* make sure identifier ends with a slash */
1518 if(identifier[len-1] == G_DIR_SEPARATOR) {
1519 Xstrdup_a(str, identifier, return NULL);
1522 Xstrndup_a(str, identifier, len+1, return NULL);
1523 str[len] = G_DIR_SEPARATOR;
1526 /* find folder class */
1527 p1 = strchr(str, G_DIR_SEPARATOR);
1531 class = folder_get_class_from_string(&str[1]);
1534 *p1 = G_DIR_SEPARATOR;
1537 /* find folder from class and name */
1538 p2 = strchr(p1, G_DIR_SEPARATOR);
1542 folder = folder_find_from_name(p1, class);
1545 *p2 = G_DIR_SEPARATOR;
1549 /* Now, move forward and make sure all sections in the path exist */
1550 last_parent = folder->node->data;
1551 while((p1 = strchr(p1, G_DIR_SEPARATOR)) != NULL) {
1553 item = folder_find_item_from_identifier(str);
1555 item = folder_create_folder(last_parent, p2);
1558 debug_print("Created folder '%s'\n", str);
1559 created_something = TRUE;
1560 if(prefs_common.inherit_folder_props && (last_parent != item->folder->node->data)) {
1561 folder_item_prefs_copy_prefs(last_parent, item);
1565 *p1 = G_DIR_SEPARATOR;
1570 if(created_something)
1571 folder_write_list();
1578 * Get a displayable name for a FolderItem
1580 * \param item FolderItem for that a name should be created
1581 * \return Displayable name for item, returned string has to
1584 gchar *folder_item_get_name(FolderItem *item)
1588 cm_return_val_if_fail(item != NULL, g_strdup(""));
1590 switch (item->stype) {
1592 name = g_strdup(!strcmp2(item->name, INBOX_DIR) ? _("Inbox") :
1596 name = g_strdup(!strcmp2(item->name, OUTBOX_DIR) ? _("Sent") :
1600 name = g_strdup(!strcmp2(item->name, QUEUE_DIR) ? _("Queue") :
1604 name = g_strdup(!strcmp2(item->name, TRASH_DIR) ? _("Trash") :
1608 name = g_strdup(!strcmp2(item->name, DRAFT_DIR) ? _("Drafts") :
1617 * should probably be done by a virtual function,
1618 * the folder knows the ui string and how to abbrev
1620 if (folder_item_parent(item) == NULL) {
1621 name = g_strconcat(item->name, " (", item->folder->klass->uistr, ")", NULL);
1623 if (FOLDER_CLASS(item->folder) == news_get_class() &&
1624 item->path && !strcmp2(item->name, item->path))
1625 name = get_abbrev_newsgroup_name
1627 prefs_common.ng_abbrev_len);
1629 name = g_strdup(item->name);
1634 name = g_strdup("");
1639 gboolean folder_have_mailbox (void)
1642 for (cur = folder_list; cur != NULL; cur = g_list_next(cur)) {
1643 Folder *folder = FOLDER(cur->data);
1644 if (folder->inbox && folder->outbox)
1650 FolderItem *folder_get_default_inbox(void)
1654 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1655 Folder * folder = FOLDER(flist->data);
1659 if (folder->inbox == NULL)
1661 if (folder->klass->type == F_UNKNOWN)
1664 return folder->inbox;
1670 FolderItem *folder_get_default_inbox_for_class(FolderType type)
1674 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1675 Folder * folder = FOLDER(flist->data);
1679 if (folder->inbox == NULL)
1681 if (folder->klass->type != type)
1684 return folder->inbox;
1690 FolderItem *folder_get_default_outbox(void)
1694 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1695 Folder * folder = FOLDER(flist->data);
1699 if (folder->outbox == NULL)
1701 if (folder->klass->type == F_UNKNOWN)
1704 return folder->outbox;
1710 FolderItem *folder_get_default_outbox_for_class(FolderType type)
1714 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1715 Folder * folder = FOLDER(flist->data);
1719 if (folder->outbox == NULL)
1721 if (folder->klass->type != type)
1724 return folder->outbox;
1730 FolderItem *folder_get_default_draft(void)
1734 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1735 Folder * folder = FOLDER(flist->data);
1739 if (folder->draft == NULL)
1741 if (folder->klass->type == F_UNKNOWN)
1744 return folder->draft;
1750 FolderItem *folder_get_default_draft_for_class(FolderType type)
1754 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1755 Folder * folder = FOLDER(flist->data);
1759 if (folder->draft == NULL)
1761 if (folder->klass->type != type)
1764 return folder->draft;
1770 FolderItem *folder_get_default_queue(void)
1774 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1775 Folder * folder = FOLDER(flist->data);
1779 if (folder->queue == NULL)
1781 if (folder->klass->type == F_UNKNOWN)
1784 return folder->queue;
1790 FolderItem *folder_get_default_queue_for_class(FolderType type)
1794 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1795 Folder * folder = FOLDER(flist->data);
1799 if (folder->queue == NULL)
1801 if (folder->klass->type != type)
1804 return folder->queue;
1810 FolderItem *folder_get_default_trash(void)
1814 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1815 Folder * folder = FOLDER(flist->data);
1819 if (folder->trash == NULL)
1821 if (folder->klass->type == F_UNKNOWN)
1824 return folder->trash;
1830 FolderItem *folder_get_default_trash_for_class(FolderType type)
1834 for (flist = folder_list; flist != NULL; flist = g_list_next(flist)) {
1835 Folder * folder = FOLDER(flist->data);
1839 if (folder->trash == NULL)
1841 if (folder->klass->type != type)
1844 return folder->trash;
1850 #define CREATE_FOLDER_IF_NOT_EXIST(member, dir, type) \
1852 if (!folder->member) { \
1853 item = folder_item_new(folder, dir, dir); \
1854 item->stype = type; \
1855 folder_item_append(rootitem, item); \
1856 folder->member = item; \
1860 void folder_set_missing_folders(void)
1863 FolderItem *rootitem;
1867 for (list = folder_list; list != NULL; list = list->next) {
1868 folder = list->data;
1869 if (FOLDER_TYPE(folder) != F_MH) continue;
1870 rootitem = FOLDER_ITEM(folder->node->data);
1871 cm_return_if_fail(rootitem != NULL);
1873 if (folder->inbox && folder->outbox && folder->draft &&
1874 folder->queue && folder->trash)
1877 if (folder->klass->create_tree(folder) < 0) {
1878 g_warning("%s: can't create the folder tree.",
1879 LOCAL_FOLDER(folder)->rootpath);
1883 CREATE_FOLDER_IF_NOT_EXIST(inbox, INBOX_DIR, F_INBOX);
1884 CREATE_FOLDER_IF_NOT_EXIST(outbox, OUTBOX_DIR, F_OUTBOX);
1885 CREATE_FOLDER_IF_NOT_EXIST(draft, DRAFT_DIR, F_DRAFT);
1886 CREATE_FOLDER_IF_NOT_EXIST(queue, QUEUE_DIR, F_QUEUE);
1887 CREATE_FOLDER_IF_NOT_EXIST(trash, TRASH_DIR, F_TRASH);
1891 static gboolean folder_unref_account_func(GNode *node, gpointer data)
1893 FolderItem *item = node->data;
1894 PrefsAccount *account = data;
1896 if (item->account == account)
1897 item->account = NULL;
1902 void folder_unref_account_all(PrefsAccount *account)
1907 if (!account) return;
1909 for (list = folder_list; list != NULL; list = list->next) {
1910 folder = list->data;
1911 if (folder->account == account)
1912 folder->account = NULL;
1913 g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1914 folder_unref_account_func, account);
1918 #undef CREATE_FOLDER_IF_NOT_EXIST
1920 gchar *folder_item_get_path(FolderItem *item)
1924 cm_return_val_if_fail(item != NULL, NULL);
1925 folder = item->folder;
1926 cm_return_val_if_fail(folder != NULL, NULL);
1928 return folder->klass->item_get_path(folder, item);
1931 static gint folder_sort_cache_list_by_msgnum(gconstpointer a, gconstpointer b)
1933 MsgInfo *msginfo_a = (MsgInfo *) a;
1934 MsgInfo *msginfo_b = (MsgInfo *) b;
1936 return (msginfo_a->msgnum - msginfo_b->msgnum);
1939 static gint folder_sort_folder_list(gconstpointer a, gconstpointer b)
1941 guint gint_a = GPOINTER_TO_INT(a);
1942 guint gint_b = GPOINTER_TO_INT(b);
1944 return (gint_a - gint_b);
1947 static gint syncronize_flags(FolderItem *item, MsgInfoList *msglist)
1949 GHashTable *relation;
1955 if(item->folder->klass->get_flags == NULL)
1957 if (item->no_select)
1960 relation = g_hash_table_new(g_direct_hash, g_direct_equal);
1961 if ((ret = item->folder->klass->get_flags(
1962 item->folder, item, msglist, relation)) == 0) {
1963 gpointer data, old_key;
1965 MsgPermFlags permflags = 0;
1967 folder_item_update_freeze();
1968 folder_item_set_batch(item, TRUE);
1969 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1970 msginfo = (MsgInfo *) cur->data;
1972 if (g_hash_table_lookup_extended(relation, msginfo, &old_key, &data)) {
1973 permflags = GPOINTER_TO_INT(data);
1975 if (msginfo->flags.perm_flags != permflags) {
1976 procmsg_msginfo_change_flags(msginfo,
1977 permflags & ~msginfo->flags.perm_flags, 0,
1978 ~permflags & msginfo->flags.perm_flags, 0);
1982 folder_item_set_batch(item, FALSE);
1983 folder_item_update_thaw();
1985 g_hash_table_destroy(relation);
1990 static gint folder_item_syncronize_flags(FolderItem *item)
1992 MsgInfoList *msglist = NULL;
1996 cm_return_val_if_fail(item != NULL, -1);
1997 cm_return_val_if_fail(item->folder != NULL, -1);
1998 cm_return_val_if_fail(item->folder->klass != NULL, -1);
1999 if (item->no_select)
2002 item->scanning = ITEM_SCANNING_WITH_FLAGS;
2004 if (item->cache == NULL)
2005 folder_item_read_cache(item);
2007 msglist = msgcache_get_msg_list(item->cache);
2009 ret = syncronize_flags(item, msglist);
2011 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
2012 procmsg_msginfo_free((MsgInfo **)&(cur->data));
2015 g_slist_free(msglist);
2017 item->scanning = ITEM_NOT_SCANNING;
2022 static void folder_item_process_open (FolderItem *item,
2023 void (*before_proc_func)(gpointer data),
2024 void (*after_proc_func)(gpointer data),
2030 if((item->folder->klass->scan_required != NULL) &&
2031 (item->folder->klass->scan_required(item->folder, item))) {
2032 folder_item_scan_full(item, TRUE);
2034 folder_item_syncronize_flags(item);
2038 if (item->prefs->enable_processing_when_opening) {
2039 buf = g_strdup_printf(_("Processing (%s)...\n"),
2040 item->path ? item->path : item->name);
2043 if (before_proc_func)
2044 before_proc_func(data);
2046 folder_item_apply_processing(item);
2048 if (after_proc_func)
2049 after_proc_func(data);
2051 item->processing_pending = FALSE;
2055 gint folder_item_open(FolderItem *item)
2058 if (item->no_select)
2061 if (item->scanning != ITEM_NOT_SCANNING) {
2062 debug_print("%s is scanning... \n", item->path ? item->path : item->name);
2066 item->processing_pending = TRUE;
2067 folder_item_process_open (item, NULL, NULL, NULL);
2069 item->opened = TRUE;
2074 gint folder_item_close(FolderItem *item)
2076 GSList *mlist, *cur;
2079 cm_return_val_if_fail(item != NULL, -1);
2081 if (item->no_select)
2084 if (item->new_msgs) {
2085 folder_item_update_freeze();
2086 mlist = folder_item_get_msg_list(item);
2087 for (cur = mlist ; cur != NULL ; cur = cur->next) {
2090 msginfo = (MsgInfo *) cur->data;
2091 if (MSG_IS_NEW(msginfo->flags))
2092 procmsg_msginfo_unset_flags(msginfo, MSG_NEW, 0);
2093 procmsg_msginfo_free(&msginfo);
2095 g_slist_free(mlist);
2096 folder_item_update_thaw();
2099 folder_item_write_cache(item);
2101 folder_item_update(item, F_ITEM_UPDATE_MSGCNT);
2103 item->opened = FALSE;
2104 folder = item->folder;
2106 if (folder->klass->close == NULL)
2109 return folder->klass->close(folder, item);
2112 static MsgInfoList *get_msginfos(FolderItem *item, MsgNumberList *numlist)
2114 MsgInfoList *msglist = NULL;
2115 Folder *folder = item->folder;
2116 if (item->no_select)
2119 if (folder->klass->get_msginfos != NULL)
2120 msglist = folder->klass->get_msginfos(folder, item, numlist);
2122 MsgNumberList *elem;
2124 for (elem = numlist; elem != NULL; elem = g_slist_next(elem)) {
2128 num = GPOINTER_TO_INT(elem->data);
2129 msginfo = folder->klass->get_msginfo(folder, item, num);
2130 if (msginfo != NULL)
2131 msglist = g_slist_prepend(msglist, msginfo);
2138 static MsgInfo *get_msginfo(FolderItem *item, guint num)
2140 MsgNumberList numlist;
2141 MsgInfoList *msglist;
2142 MsgInfo *msginfo = NULL;
2144 numlist.data = GINT_TO_POINTER(num);
2145 numlist.next = NULL;
2146 msglist = get_msginfos(item, &numlist);
2147 if (msglist != NULL)
2148 msginfo = procmsg_msginfo_new_ref(msglist->data);
2149 procmsg_msg_list_free(msglist);
2154 gint folder_item_scan_full(FolderItem *item, gboolean filtering)
2157 GSList *folder_list = NULL, *cache_list = NULL;
2158 GSList *folder_list_cur, *cache_list_cur, *new_list = NULL;
2159 GSList *exists_list = NULL, *elem;
2160 GSList *newmsg_list = NULL;
2161 guint newcnt = 0, unreadcnt = 0, totalcnt = 0;
2162 guint markedcnt = 0, unreadmarkedcnt = 0;
2163 guint repliedcnt = 0, forwardedcnt = 0;
2164 guint lockedcnt = 0, ignoredcnt = 0, watchedcnt = 0;
2166 guint cache_max_num, folder_max_num, cache_cur_num, folder_cur_num;
2167 gboolean update_flags = 0, old_uids_valid = FALSE;
2168 GHashTable *subject_table = NULL;
2170 cm_return_val_if_fail(item != NULL, -1);
2171 if (item->path == NULL) return -1;
2173 folder = item->folder;
2175 cm_return_val_if_fail(folder != NULL, -1);
2176 cm_return_val_if_fail(folder->klass->get_num_list != NULL, -1);
2178 item->scanning = ITEM_SCANNING_WITH_FLAGS;
2180 debug_print("Scanning folder %s for cache changes.\n", item->path ? item->path : "(null)");
2182 /* Get list of messages for folder and cache */
2183 if (folder->klass->get_num_list(item->folder, item, &folder_list, &old_uids_valid) < 0) {
2184 debug_print("Error fetching list of message numbers\n");
2185 item->scanning = ITEM_NOT_SCANNING;
2189 if(prefs_common.thread_by_subject) {
2190 subject_table = g_hash_table_new(g_str_hash, g_str_equal);
2193 if (old_uids_valid) {
2195 folder_item_read_cache(item);
2196 cache_list = msgcache_get_msg_list(item->cache);
2199 msgcache_destroy(item->cache);
2200 item->cache = msgcache_new();
2201 item->cache_dirty = TRUE;
2202 item->mark_dirty = TRUE;
2203 item->tags_dirty = TRUE;
2207 /* Sort both lists */
2208 cache_list = g_slist_sort(cache_list, folder_sort_cache_list_by_msgnum);
2209 folder_list = g_slist_sort(folder_list, folder_sort_folder_list);
2211 cache_list_cur = cache_list;
2212 folder_list_cur = folder_list;
2214 if (cache_list_cur != NULL) {
2215 GSList *cache_list_last;
2217 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
2218 cache_list_last = g_slist_last(cache_list);
2219 cache_max_num = ((MsgInfo *)cache_list_last->data)->msgnum;
2221 cache_cur_num = G_MAXUINT;
2225 if (folder_list_cur != NULL) {
2226 GSList *folder_list_last;
2228 folder_cur_num = GPOINTER_TO_UINT(folder_list_cur->data);
2229 folder_list_last = g_slist_last(folder_list);
2230 folder_max_num = GPOINTER_TO_UINT(folder_list_last->data);
2232 folder_cur_num = G_MAXUINT;
2236 while ((cache_cur_num != G_MAXUINT) || (folder_cur_num != G_MAXUINT)) {
2238 * Message only exists in the folder
2239 * Remember message for fetching
2241 if (folder_cur_num < cache_cur_num) {
2242 gboolean add = FALSE;
2244 switch(FOLDER_TYPE(folder)) {
2246 if (folder_cur_num < cache_max_num)
2249 if (folder->account->max_articles == 0) {
2253 if (folder_max_num <= folder->account->max_articles) {
2255 } else if (folder_cur_num > (folder_max_num - folder->account->max_articles)) {
2265 new_list = g_slist_prepend(new_list, GUINT_TO_POINTER(folder_cur_num));
2266 debug_print("Remembered message %u for fetching\n", folder_cur_num);
2269 /* Move to next folder number */
2270 if (folder_list_cur)
2271 folder_list_cur = folder_list_cur->next;
2273 if (folder_list_cur != NULL)
2274 folder_cur_num = GPOINTER_TO_UINT(folder_list_cur->data);
2276 folder_cur_num = G_MAXUINT;
2282 * Message only exists in the cache
2283 * Remove the message from the cache
2285 if (cache_cur_num < folder_cur_num) {
2286 msgcache_remove_msg(item->cache, cache_cur_num);
2287 debug_print("Removed message %u from cache.\n", cache_cur_num);
2289 /* Move to next cache number */
2291 cache_list_cur = cache_list_cur->next;
2293 if (cache_list_cur != NULL)
2294 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
2296 cache_cur_num = G_MAXUINT;
2298 update_flags |= F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT;
2304 * Message number exists in folder and cache!
2305 * Check if the message has been modified
2307 if (cache_cur_num == folder_cur_num) {
2310 msginfo = msgcache_get_msg(item->cache, folder_cur_num);
2311 if (msginfo && folder->klass->is_msg_changed && folder->klass->is_msg_changed(folder, item, msginfo)) {
2312 msgcache_remove_msg(item->cache, msginfo->msgnum);
2313 new_list = g_slist_prepend(new_list, GINT_TO_POINTER(msginfo->msgnum));
2314 procmsg_msginfo_free(&msginfo);
2316 debug_print("Remembering message %u to update...\n", folder_cur_num);
2317 } else if (msginfo) {
2318 exists_list = g_slist_prepend(exists_list, msginfo);
2320 if(prefs_common.thread_by_subject &&
2321 MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2322 !subject_table_lookup(subject_table, msginfo->subject)) {
2323 subject_table_insert(subject_table, msginfo->subject, msginfo);
2327 /* Move to next folder and cache number */
2329 cache_list_cur = cache_list_cur->next;
2331 if (folder_list_cur)
2332 folder_list_cur = folder_list_cur->next;
2334 if (cache_list_cur != NULL)
2335 cache_cur_num = ((MsgInfo *)cache_list_cur->data)->msgnum;
2337 cache_cur_num = G_MAXUINT;
2339 if (folder_list_cur != NULL)
2340 folder_cur_num = GPOINTER_TO_UINT(folder_list_cur->data);
2342 folder_cur_num = G_MAXUINT;
2348 for(cache_list_cur = cache_list; cache_list_cur != NULL; cache_list_cur = g_slist_next(cache_list_cur))
2349 procmsg_msginfo_free((MsgInfo **)&(cache_list_cur->data));
2351 g_slist_free(cache_list);
2352 g_slist_free(folder_list);
2354 if (new_list != NULL) {
2355 GSList *tmp_list = NULL;
2356 newmsg_list = get_msginfos(item, new_list);
2357 g_slist_free(new_list);
2358 tmp_list = g_slist_concat(g_slist_copy(exists_list), g_slist_copy(newmsg_list));
2359 syncronize_flags(item, tmp_list);
2360 g_slist_free(tmp_list);
2362 syncronize_flags(item, exists_list);
2365 folder_item_update_freeze();
2367 item->scanning = ITEM_SCANNING;
2369 if (newmsg_list != NULL) {
2370 GSList *elem, *to_filter = NULL;
2371 gboolean do_filter = (filtering == TRUE) &&
2372 (item->stype == F_INBOX) &&
2373 (item->folder->account != NULL) &&
2374 (item->folder->account->filter_on_recv);
2376 for (elem = newmsg_list; elem != NULL; elem = g_slist_next(elem)) {
2377 MsgInfo *msginfo = (MsgInfo *) elem->data;
2379 msgcache_add_msg(item->cache, msginfo);
2381 exists_list = g_slist_prepend(exists_list, msginfo);
2383 if(prefs_common.thread_by_subject &&
2384 MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2385 !subject_table_lookup(subject_table, msginfo->subject)) {
2386 subject_table_insert(subject_table, msginfo->subject, msginfo);
2394 folder_item_set_batch(item, TRUE);
2395 procmsg_msglist_filter(newmsg_list, item->folder->account,
2396 &to_filter, &unfiltered,
2398 folder_item_set_batch(item, FALSE);
2400 filtering_move_and_copy_msgs(newmsg_list);
2401 if (to_filter != NULL) {
2402 for (elem = to_filter; elem; elem = g_slist_next(elem)) {
2403 MsgInfo *msginfo = (MsgInfo *)elem->data;
2404 procmsg_msginfo_free(&msginfo);
2406 g_slist_free(to_filter);
2408 if (unfiltered != NULL) {
2409 for (elem = unfiltered; elem; elem = g_slist_next(elem)) {
2410 MsgInfo *msginfo = (MsgInfo *)elem->data;
2411 exists_list = g_slist_prepend(exists_list, msginfo);
2413 if(prefs_common.thread_by_subject &&
2414 MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2415 !subject_table_lookup(subject_table, msginfo->subject)) {
2416 subject_table_insert(subject_table, msginfo->subject, msginfo);
2419 g_slist_free(unfiltered);
2421 if (prefs_common.real_time_sync)
2422 folder_item_synchronise(item);
2424 if (prefs_common.real_time_sync)
2425 folder_item_synchronise(item);
2428 g_slist_free(newmsg_list);
2430 update_flags |= F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT;
2433 folder_item_set_batch(item, TRUE);
2434 for (elem = exists_list; elem != NULL; elem = g_slist_next(elem)) {
2435 MsgInfo *msginfo, *parent_msginfo;
2437 msginfo = elem->data;
2438 if (MSG_IS_IGNORE_THREAD(msginfo->flags) && (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
2439 procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
2440 if (!MSG_IS_IGNORE_THREAD(msginfo->flags) && procmsg_msg_has_flagged_parent(msginfo, MSG_IGNORE_THREAD)) {
2441 procmsg_msginfo_change_flags(msginfo, MSG_IGNORE_THREAD, 0, MSG_NEW | MSG_UNREAD, 0);
2443 if (!MSG_IS_WATCH_THREAD(msginfo->flags) && procmsg_msg_has_flagged_parent(msginfo, MSG_WATCH_THREAD)) {
2444 procmsg_msginfo_set_flags(msginfo, MSG_WATCH_THREAD, 0);
2446 if(prefs_common.thread_by_subject && !msginfo->inreplyto &&
2447 !msginfo->references && !MSG_IS_IGNORE_THREAD(msginfo->flags) &&
2448 (parent_msginfo = subject_table_lookup(subject_table, msginfo->subject)))
2450 if(MSG_IS_IGNORE_THREAD(parent_msginfo->flags)) {
2451 procmsg_msginfo_change_flags(msginfo, MSG_IGNORE_THREAD, 0,
2452 MSG_NEW | MSG_UNREAD, 0);
2455 if ((folder_has_parent_of_type(item, F_OUTBOX) ||
2456 folder_has_parent_of_type(item, F_QUEUE) ||
2457 folder_has_parent_of_type(item, F_TRASH)) &&
2458 (MSG_IS_NEW(msginfo->flags) || MSG_IS_UNREAD(msginfo->flags)))
2459 procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
2460 if (MSG_IS_NEW(msginfo->flags))
2462 if (MSG_IS_UNREAD(msginfo->flags))
2464 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
2466 if (MSG_IS_MARKED(msginfo->flags))
2468 if (MSG_IS_REPLIED(msginfo->flags))
2470 if (MSG_IS_FORWARDED(msginfo->flags))
2472 if (MSG_IS_LOCKED(msginfo->flags))
2474 if (MSG_IS_IGNORE_THREAD(msginfo->flags))
2476 if (MSG_IS_WATCH_THREAD(msginfo->flags))
2481 procmsg_msginfo_free(&msginfo);
2483 folder_item_set_batch(item, FALSE);
2484 g_slist_free(exists_list);
2486 if(prefs_common.thread_by_subject) {
2487 g_hash_table_destroy(subject_table);
2490 if (item->new_msgs != newcnt || item->unread_msgs != unreadcnt
2491 || item->total_msgs != totalcnt || item->marked_msgs != markedcnt
2492 || item->unreadmarked_msgs != unreadmarkedcnt
2493 || item->replied_msgs != repliedcnt || item->forwarded_msgs != forwardedcnt
2494 || item->locked_msgs != lockedcnt || item->ignored_msgs != ignoredcnt
2495 || item->watched_msgs != watchedcnt) {
2496 update_flags |= F_ITEM_UPDATE_CONTENT;
2499 item->new_msgs = newcnt;
2500 item->unread_msgs = unreadcnt;
2501 item->total_msgs = totalcnt;
2502 item->unreadmarked_msgs = unreadmarkedcnt;
2503 item->marked_msgs = markedcnt;
2504 item->replied_msgs = repliedcnt;
2505 item->forwarded_msgs = forwardedcnt;
2506 item->locked_msgs = lockedcnt;
2507 item->ignored_msgs = ignoredcnt;
2508 item->watched_msgs = watchedcnt;
2510 update_flags |= F_ITEM_UPDATE_MSGCNT;
2512 folder_item_update(item, update_flags);
2513 folder_item_update_thaw();
2515 item->scanning = ITEM_NOT_SCANNING;
2520 gint folder_item_scan(FolderItem *item)
2522 return folder_item_scan_full(item, TRUE);
2525 static void folder_count_total_cache_memusage(FolderItem *item, gpointer data)
2527 gint *memusage = (gint *)data;
2529 if (item->cache == NULL)
2532 *memusage += msgcache_get_memory_usage(item->cache);
2535 static gint folder_cache_time_compare_func(gconstpointer a, gconstpointer b)
2537 FolderItem *fa = (FolderItem *)a;
2538 FolderItem *fb = (FolderItem *)b;
2540 return (gint) (msgcache_get_last_access_time(fa->cache) - msgcache_get_last_access_time(fb->cache));
2543 static void folder_find_expired_caches(FolderItem *item, gpointer data)
2545 GSList **folder_item_list = (GSList **)data;
2546 gint difftime, expiretime;
2548 if (item->cache == NULL)
2551 if (item->opened > 0)
2554 difftime = (gint) (time(NULL) - msgcache_get_last_access_time(item->cache));
2555 expiretime = prefs_common.cache_min_keep_time * 60;
2556 debug_print("Cache unused time: %d (Expire time: %d)\n", difftime, expiretime);
2558 if (difftime > expiretime && !item->opened && !item->processing_pending) {
2559 *folder_item_list = g_slist_insert_sorted(*folder_item_list, item, folder_cache_time_compare_func);
2563 gboolean folder_item_free_cache(FolderItem *item, gboolean force)
2565 cm_return_val_if_fail(item != NULL, TRUE);
2567 if (item->cache == NULL)
2570 if (item->opened > 0 && !force)
2573 folder_item_write_cache(item);
2574 msgcache_destroy(item->cache);
2579 void folder_clean_cache_memory_force(void)
2581 int old_cache_max_mem_usage = prefs_common.cache_max_mem_usage;
2582 int old_cache_min_keep_time = prefs_common.cache_min_keep_time;
2584 prefs_common.cache_max_mem_usage = 0;
2585 prefs_common.cache_min_keep_time = 0;
2587 folder_clean_cache_memory(NULL);
2589 prefs_common.cache_max_mem_usage = old_cache_max_mem_usage;
2590 prefs_common.cache_min_keep_time = old_cache_min_keep_time;
2593 void folder_clean_cache_memory(FolderItem *protected_item)
2597 folder_func_to_all_folders(folder_count_total_cache_memusage, &memusage);
2598 debug_print("Total cache memory usage: %d\n", memusage);
2600 if (memusage > (prefs_common.cache_max_mem_usage * 1024)) {
2601 GSList *folder_item_list = NULL, *listitem;
2603 debug_print("Trying to free cache memory\n");
2605 folder_func_to_all_folders(folder_find_expired_caches, &folder_item_list);
2606 listitem = folder_item_list;
2607 while((listitem != NULL) && (memusage > (prefs_common.cache_max_mem_usage * 1024))) {
2608 FolderItem *item = (FolderItem *)(listitem->data);
2609 gint cache_size = 0;
2610 if (item == protected_item) {
2611 listitem = listitem->next;
2614 debug_print("Freeing cache memory for %s\n", item->path ? item->path : item->name);
2615 cache_size = msgcache_get_memory_usage(item->cache);
2616 if (folder_item_free_cache(item, FALSE))
2617 memusage -= cache_size;
2619 listitem = listitem->next;
2621 g_slist_free(folder_item_list);
2625 static void folder_item_remove_cached_msg(FolderItem *item, MsgInfo *msginfo)
2627 Folder *folder = item->folder;
2629 cm_return_if_fail(folder != NULL);
2631 if (folder->klass->remove_cached_msg == NULL)
2634 folder->klass->remove_cached_msg(folder, item, msginfo);
2637 static void folder_item_clean_local_files(FolderItem *item, gint days)
2639 cm_return_if_fail(item != NULL);
2640 cm_return_if_fail(item->folder != NULL);
2642 if (FOLDER_TYPE(item->folder) == F_IMAP ||
2643 FOLDER_TYPE(item->folder) == F_NEWS) {
2644 GSList *msglist = folder_item_get_msg_list(item);
2646 time_t t = time(NULL);
2647 for (cur = msglist; cur; cur = cur->next) {
2648 MsgInfo *msginfo = (MsgInfo *)cur->data;
2649 gint age = (t - msginfo->date_t) / (60*60*24);
2651 folder_item_remove_cached_msg(item, msginfo);
2653 procmsg_msg_list_free(msglist);
2657 static void folder_item_read_cache(FolderItem *item)
2659 gchar *cache_file, *mark_file, *tags_file;
2661 cm_return_if_fail(item != NULL);
2663 if (item->path != NULL) {
2664 cache_file = folder_item_get_cache_file(item);
2665 mark_file = folder_item_get_mark_file(item);
2666 tags_file = folder_item_get_tags_file(item);
2667 item->cache = msgcache_read_cache(item, cache_file);
2668 item->cache_dirty = FALSE;
2669 item->mark_dirty = FALSE;
2670 item->tags_dirty = FALSE;
2672 MsgInfoList *list, *cur;
2673 guint newcnt = 0, unreadcnt = 0;
2674 guint markedcnt = 0, unreadmarkedcnt = 0;
2675 guint repliedcnt = 0, forwardedcnt = 0;
2676 guint lockedcnt = 0, ignoredcnt = 0;
2677 guint watchedcnt = 0;
2680 item->cache = msgcache_new();
2681 item->cache_dirty = TRUE;
2682 item->mark_dirty = TRUE;
2683 item->tags_dirty = TRUE;
2684 folder_item_scan_full(item, TRUE);
2686 msgcache_read_mark(item->cache, mark_file);
2688 list = msgcache_get_msg_list(item->cache);
2689 for (cur = list; cur != NULL; cur = g_slist_next(cur)) {
2690 msginfo = cur->data;
2692 if (MSG_IS_NEW(msginfo->flags))
2694 if (MSG_IS_UNREAD(msginfo->flags))
2696 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
2698 if (MSG_IS_MARKED(msginfo->flags))
2700 if (MSG_IS_REPLIED(msginfo->flags))
2702 if (MSG_IS_FORWARDED(msginfo->flags))
2704 if (MSG_IS_LOCKED(msginfo->flags))
2706 if (MSG_IS_IGNORE_THREAD(msginfo->flags))
2708 if (MSG_IS_WATCH_THREAD(msginfo->flags))
2710 procmsg_msginfo_unset_flags(msginfo, MSG_FULLY_CACHED, 0);
2712 item->new_msgs = newcnt;
2713 item->unread_msgs = unreadcnt;
2714 item->unreadmarked_msgs = unreadmarkedcnt;
2715 item->marked_msgs = markedcnt;
2716 item->replied_msgs = repliedcnt;
2717 item->forwarded_msgs = forwardedcnt;
2718 item->locked_msgs = lockedcnt;
2719 item->ignored_msgs = ignoredcnt;
2720 item->watched_msgs = watchedcnt;
2721 procmsg_msg_list_free(list);
2723 msgcache_read_mark(item->cache, mark_file);
2725 msgcache_read_tags(item->cache, tags_file);
2731 item->cache = msgcache_new();
2732 item->cache_dirty = TRUE;
2733 item->mark_dirty = TRUE;
2734 item->tags_dirty = TRUE;
2738 folder_clean_cache_memory(item);
2741 void folder_item_write_cache(FolderItem *item)
2743 gchar *cache_file = NULL, *mark_file = NULL, *tags_file = NULL;
2744 FolderItemPrefs *prefs;
2747 time_t last_mtime = (time_t)0;
2748 gboolean need_scan = FALSE;
2750 if (!item || !item->path || !item->cache)
2753 last_mtime = item->mtime;
2754 if (item->folder->klass->set_mtime) {
2755 if (item->folder->klass->scan_required)
2756 need_scan = item->folder->klass->scan_required(item->folder, item);
2761 id = folder_item_get_identifier(item);
2762 debug_print("Save cache for folder %s\n", id);
2765 if (item->cache_dirty)
2766 cache_file = folder_item_get_cache_file(item);
2767 if (item->cache_dirty || item->mark_dirty)
2768 mark_file = folder_item_get_mark_file(item);
2769 if (item->cache_dirty || item->tags_dirty)
2770 tags_file = folder_item_get_tags_file(item);
2771 if (msgcache_write(cache_file, mark_file, tags_file, item->cache) < 0) {
2772 prefs = item->prefs;
2773 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
2774 /* for cache file */
2775 filemode = prefs->folder_chmod;
2776 if (filemode & S_IRGRP) filemode |= S_IWGRP;
2777 if (filemode & S_IROTH) filemode |= S_IWOTH;
2778 if (cache_file != NULL)
2779 chmod(cache_file, filemode);
2782 item->cache_dirty = FALSE;
2783 item->mark_dirty = FALSE;
2784 item->tags_dirty = FALSE;
2787 if (!need_scan && item->folder->klass->set_mtime) {
2788 if (item->mtime == last_mtime) {
2789 item->folder->klass->set_mtime(item->folder, item);
2798 MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num)
2800 MsgInfo *msginfo = NULL;
2802 cm_return_val_if_fail(item != NULL, NULL);
2803 if (item->no_select)
2806 folder_item_read_cache(item);
2808 if ((msginfo = msgcache_get_msg(item->cache, num)) != NULL)
2811 msginfo = get_msginfo(item, num);
2812 if (msginfo != NULL) {
2813 msgcache_add_msg(item->cache, msginfo);
2820 MsgInfo *folder_item_get_msginfo_by_msgid(FolderItem *item, const gchar *msgid)
2824 cm_return_val_if_fail(item != NULL, NULL);
2825 cm_return_val_if_fail(msgid != NULL, NULL);
2826 if (item->no_select)
2830 folder_item_read_cache(item);
2832 if ((msginfo = msgcache_get_msg_by_id(item->cache, msgid)) != NULL)
2838 GSList *folder_item_get_msg_list(FolderItem *item)
2840 cm_return_val_if_fail(item != NULL, NULL);
2841 if (item->no_select)
2844 if (item->cache == 0)
2845 folder_item_read_cache(item);
2847 cm_return_val_if_fail(item->cache != NULL, NULL);
2849 return msgcache_get_msg_list(item->cache);
2852 static void msginfo_set_mime_flags(GNode *node, gpointer data)
2854 MsgInfo *msginfo = data;
2855 MimeInfo *mimeinfo = node->data;
2857 if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT
2858 && (!mimeinfo->subtype || (strcmp(mimeinfo->subtype, "pgp-signature") &&
2859 strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2860 strcmp(mimeinfo->subtype, "pkcs7-signature")))) {
2861 procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2862 } else if (mimeinfo->disposition == DISPOSITIONTYPE_UNKNOWN &&
2863 mimeinfo->id == NULL &&
2864 mimeinfo->type != MIMETYPE_TEXT &&
2865 mimeinfo->type != MIMETYPE_MULTIPART) {
2866 if (!mimeinfo->subtype
2867 || (strcmp(mimeinfo->subtype, "pgp-signature") &&
2868 strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2869 strcmp(mimeinfo->subtype, "pkcs7-signature")))
2870 procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2871 } else if (mimeinfo->disposition == DISPOSITIONTYPE_INLINE &&
2872 mimeinfo->id == NULL &&
2873 (strcmp(mimeinfo->subtype, "pgp-signature") &&
2874 strcmp(mimeinfo->subtype, "x-pkcs7-signature") &&
2875 strcmp(mimeinfo->subtype, "pkcs7-signature")) &&
2876 (procmime_mimeinfo_get_parameter(mimeinfo, "name") != NULL ||
2877 procmime_mimeinfo_get_parameter(mimeinfo, "filename") != NULL)) {
2878 procmsg_msginfo_set_flags(msginfo, 0, MSG_HAS_ATTACHMENT);
2881 /* don't descend below top level message for signed and encrypted info */
2882 if (mimeinfo->type == MIMETYPE_MESSAGE)
2885 if (privacy_mimeinfo_is_signed(mimeinfo)) {
2886 procmsg_msginfo_set_flags(msginfo, 0, MSG_SIGNED);
2889 if (privacy_mimeinfo_is_encrypted(mimeinfo)) {
2890 procmsg_msginfo_set_flags(msginfo, 0, MSG_ENCRYPTED);
2892 /* searching inside encrypted parts doesn't really make sense */
2893 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2897 gchar *folder_item_fetch_msg(FolderItem *item, gint num)
2903 cm_return_val_if_fail(item != NULL, NULL);
2905 folder = item->folder;
2907 cm_return_val_if_fail(folder->klass->fetch_msg != NULL, NULL);
2908 if (item->no_select)
2911 msgfile = folder->klass->fetch_msg(folder, item, num);
2913 if (msgfile != NULL) {
2914 msginfo = folder_item_get_msginfo(item, num);
2915 if ((msginfo != NULL) && !MSG_IS_SCANNED(msginfo->flags)) {
2918 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2919 !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
2920 mimeinfo = procmime_scan_file(msgfile);
2922 mimeinfo = procmime_scan_queue_file(msgfile);
2923 /* check for attachments */
2924 if (mimeinfo != NULL) {
2925 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2926 procmime_mimeinfo_free_all(&mimeinfo);
2928 procmsg_msginfo_set_flags(msginfo, 0, MSG_SCANNED);
2931 procmsg_msginfo_free(&msginfo);
2937 gchar *folder_item_fetch_msg_full(FolderItem *item, gint num, gboolean headers,
2944 cm_return_val_if_fail(item != NULL, NULL);
2945 if (item->no_select)
2948 folder = item->folder;
2950 if (folder->klass->fetch_msg_full == NULL)
2951 return folder_item_fetch_msg(item, num);
2953 if (item->prefs->offlinesync && prefs_common.real_time_sync)
2954 msgfile = folder->klass->fetch_msg_full(folder, item, num,
2957 msgfile = folder->klass->fetch_msg_full(folder, item, num,
2960 if (msgfile != NULL) {
2961 msginfo = folder_item_get_msginfo(item, num);
2962 if ((msginfo != NULL) && !MSG_IS_SCANNED(msginfo->flags)) {
2965 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
2966 !folder_has_parent_of_type(msginfo->folder, F_DRAFT))
2967 mimeinfo = procmime_scan_file(msgfile);
2969 mimeinfo = procmime_scan_queue_file(msgfile);
2970 /* check for attachments */
2971 if (mimeinfo != NULL) {
2972 g_node_children_foreach(mimeinfo->node, G_TRAVERSE_ALL, msginfo_set_mime_flags, msginfo);
2973 procmime_mimeinfo_free_all(&mimeinfo);
2975 procmsg_msginfo_set_flags(msginfo, 0, MSG_SCANNED);
2978 procmsg_msginfo_free(&msginfo);
2985 static gint folder_item_get_msg_num_by_file(FolderItem *dest, const gchar *file)
2987 static HeaderEntry hentry[] = {{"Message-ID:", NULL, TRUE},
2988 {NULL, NULL, FALSE}};
2992 gchar buf[BUFFSIZE];
2994 if ((fp = claws_fopen(file, "rb")) == NULL)
2997 if ((folder_has_parent_of_type(dest, F_QUEUE)) ||
2998 (folder_has_parent_of_type(dest, F_DRAFT)))
2999 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3001 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
3002 strlen("X-Claws-End-Special-Headers:"))) ||
3003 (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
3004 strlen("X-Sylpheed-End-Special-Headers:"))))
3007 if (buf[0] == '\r' || buf[0] == '\n') break;
3008 /* from other mailers */
3009 if (!strncmp(buf, "Date: ", 6)
3010 || !strncmp(buf, "To: ", 4)
3011 || !strncmp(buf, "From: ", 6)
3012 || !strncmp(buf, "Subject: ", 9)) {
3018 procheader_get_header_fields(fp, hentry);
3019 debug_print("looking for %s\n", hentry[0].body);
3020 if (hentry[0].body) {
3021 extract_parenthesis(hentry[0].body, '<', '>');
3022 remove_space(hentry[0].body);
3023 if ((msginfo = msgcache_get_msg_by_id(dest->cache, hentry[0].body)) != NULL) {
3024 msgnum = msginfo->msgnum;
3025 procmsg_msginfo_free(&msginfo);
3027 debug_print("found message as uid %d\n", msgnum);
3031 g_free(hentry[0].body);
3032 hentry[0].body = NULL;
3038 static void copy_msginfo_flags(MsgInfo *source, MsgInfo *dest)
3040 MsgPermFlags perm_flags = 0;
3041 MsgTmpFlags tmp_flags = 0;
3043 /* create new flags */
3044 if (source != NULL) {
3045 /* copy original flags */
3046 perm_flags = source->flags.perm_flags;
3047 tmp_flags = source->flags.tmp_flags;
3049 perm_flags = dest->flags.perm_flags;
3050 tmp_flags = dest->flags.tmp_flags;
3053 /* remove new, unread and deleted in special folders */
3054 if (folder_has_parent_of_type(dest->folder, F_OUTBOX) ||
3055 folder_has_parent_of_type(dest->folder, F_QUEUE) ||
3056 folder_has_parent_of_type(dest->folder, F_DRAFT) ||
3057 folder_has_parent_of_type(dest->folder, F_TRASH))
3058 perm_flags &= ~(MSG_NEW | MSG_UNREAD | MSG_DELETED);
3060 /* set ignore flag of ignored parent exists */
3061 if (procmsg_msg_has_flagged_parent(dest, MSG_IGNORE_THREAD))
3062 perm_flags |= MSG_IGNORE_THREAD;
3064 /* unset FULLY_CACHED flags */
3065 perm_flags &= ~MSG_FULLY_CACHED;
3067 if (procmsg_msg_has_flagged_parent(dest, MSG_WATCH_THREAD))
3068 perm_flags |= MSG_WATCH_THREAD;
3070 /* Unset tmp flags that should not be copied */
3071 tmp_flags &= ~(MSG_MOVE | MSG_COPY | MSG_MOVE_DONE);
3073 /* unset flags that are set but should not */
3074 /* and set new flags */
3075 procmsg_msginfo_change_flags(dest,
3076 ~dest->flags.perm_flags & perm_flags,
3077 ~dest->flags.tmp_flags & tmp_flags,
3078 dest->flags.perm_flags & ~perm_flags,
3079 dest->flags.tmp_flags & ~tmp_flags);
3081 if (source && source->tags) {
3082 g_slist_free(dest->tags);
3083 dest->tags = g_slist_copy(source->tags);
3084 folder_item_commit_tags(dest->folder, dest, dest->tags, NULL);
3088 static void add_msginfo_to_cache(FolderItem *item, MsgInfo *newmsginfo, MsgInfo *flagsource)
3090 /* update folder stats */
3091 if (MSG_IS_NEW(newmsginfo->flags))
3093 if (MSG_IS_UNREAD(newmsginfo->flags))
3094 item->unread_msgs++;
3095 if (MSG_IS_UNREAD(newmsginfo->flags) && procmsg_msg_has_marked_parent(newmsginfo))
3096 item->unreadmarked_msgs++;
3097 if (MSG_IS_MARKED(newmsginfo->flags))
3098 item->marked_msgs++;
3099 if (MSG_IS_REPLIED(newmsginfo->flags))
3100 item->replied_msgs++;
3101 if (MSG_IS_FORWARDED(newmsginfo->flags))
3102 item->forwarded_msgs++;
3103 if (MSG_IS_LOCKED(newmsginfo->flags))
3104 item->locked_msgs++;
3105 if (MSG_IS_IGNORE_THREAD(newmsginfo->flags))
3106 item->ignored_msgs++;
3107 if (MSG_IS_WATCH_THREAD(newmsginfo->flags))
3108 item->watched_msgs++;
3111 folder_item_update_freeze();
3114 folder_item_read_cache(item);
3116 msgcache_add_msg(item->cache, newmsginfo);
3117 copy_msginfo_flags(flagsource, newmsginfo);
3118 folder_item_update_with_msg(item, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_ADDMSG, newmsginfo);
3119 folder_item_update_thaw();
3122 static void remove_msginfo_from_cache(FolderItem *item, MsgInfo *msginfo)
3124 MsgInfoUpdate msginfo_update;
3127 folder_item_read_cache(item);
3129 if (MSG_IS_NEW(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3130 msginfo->folder->new_msgs--;
3131 if (MSG_IS_UNREAD(msginfo->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
3132 msginfo->folder->unread_msgs--;
3133 if (MSG_IS_UNREAD(msginfo->flags) && procmsg_msg_has_marked_parent(msginfo))
3134 msginfo->folder->unreadmarked_msgs--;
3135 if (MSG_IS_MARKED(msginfo->flags))
3136 item->marked_msgs--;
3137 if (MSG_IS_REPLIED(msginfo->flags))
3138 item->replied_msgs--;
3139 if (MSG_IS_FORWARDED(msginfo->flags))
3140 item->forwarded_msgs--;
3141 if (MSG_IS_LOCKED(msginfo->flags))
3142 item->locked_msgs--;
3143 if (MSG_IS_IGNORE_THREAD(msginfo->flags))
3144 item->ignored_msgs--;
3145 if (MSG_IS_WATCH_THREAD(msginfo->flags))
3146 item->watched_msgs--;
3148 msginfo->folder->total_msgs--;
3150 msginfo_update.msginfo = msginfo;
3151 msginfo_update.flags = MSGINFO_UPDATE_DELETED;
3152 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
3154 msgcache_remove_msg(item->cache, msginfo->msgnum);
3155 folder_item_update_with_msg(msginfo->folder, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT | F_ITEM_UPDATE_REMOVEMSG, msginfo);
3158 gint folder_item_add_msg(FolderItem *dest, const gchar *file,
3159 MsgFlags *flags, gboolean remove_source)
3162 MsgFileInfo fileinfo;
3164 cm_return_val_if_fail(dest != NULL, -1);
3165 cm_return_val_if_fail(file != NULL, -1);
3167 fileinfo.msginfo = NULL;
3168 fileinfo.file = (gchar *)file;
3169 fileinfo.flags = flags;
3170 file_list.data = &fileinfo;
3171 file_list.next = NULL;
3173 return folder_item_add_msgs(dest, &file_list, remove_source);
3176 gint folder_item_add_msgs(FolderItem *dest, GSList *file_list,
3177 gboolean remove_source)
3180 gint ret, num, lastnum = -1;
3182 GHashTable *relation;
3183 MsgFileInfo *fileinfo = NULL;
3184 gboolean folderscan = FALSE;
3186 cm_return_val_if_fail(dest != NULL, -1);
3187 cm_return_val_if_fail(file_list != NULL, -1);
3188 cm_return_val_if_fail(dest->folder != NULL, -1);
3189 if (dest->no_select)
3192 folder = dest->folder;
3194 relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3196 if (folder->klass->add_msgs != NULL) {
3197 ret = folder->klass->add_msgs(folder, dest, file_list, relation);
3199 g_hash_table_destroy(relation);
3203 for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3204 fileinfo = (MsgFileInfo *) file_cur->data;
3206 ret = folder->klass->add_msg(folder, dest, fileinfo->file, fileinfo->flags);
3208 g_hash_table_destroy(relation);
3211 g_hash_table_insert(relation, fileinfo, GINT_TO_POINTER(ret));
3215 for (file_cur = file_list; file_cur != NULL; file_cur = g_slist_next(file_cur)) {
3216 gpointer data, old_key;
3218 fileinfo = (MsgFileInfo *) file_cur->data;
3219 if (g_hash_table_lookup_extended(relation, fileinfo, &old_key, &data))
3220 num = GPOINTER_TO_INT(data);
3225 MsgInfo *newmsginfo;
3229 folder_item_scan_full(dest, FALSE);
3232 num = folder_item_get_msg_num_by_file(dest, fileinfo->file);
3233 debug_print("got num %d\n", num);
3239 if (num >= 0 && remove_source) {
3240 if (claws_unlink(fileinfo->file) < 0)
3241 FILE_OP_ERROR(fileinfo->file, "unlink");
3248 ((newmsginfo = get_msginfo(dest, num)) != NULL)) {
3249 add_msginfo_to_cache(dest, newmsginfo, NULL);
3250 procmsg_msginfo_free(&newmsginfo);
3251 } else if ((newmsginfo = msgcache_get_msg(dest->cache, num)) != NULL) {
3252 /* TODO: set default flags */
3253 procmsg_msginfo_free(&newmsginfo);
3258 g_hash_table_destroy(relation);
3263 static FolderItem *folder_item_move_recursive(FolderItem *src, FolderItem *dest, gboolean copy)
3266 FolderItem *new_item;
3267 FolderItem *next_item;
3269 gchar *old_id, *new_id;
3270 FolderUpdateData hookdata;
3273 debug_print("%s %s to %s\n", copy?"Copying":"Moving", src->path, dest->path);
3274 new_item = folder_create_folder(dest, src->name);
3275 if (new_item == NULL) {
3276 g_print("Can't create folder\n");
3280 if (new_item->folder == NULL)
3281 new_item->folder = dest->folder;
3284 log_message(LOG_PROTOCOL, copy ?_("Copying %s to %s...\n"):_("Moving %s to %s...\n"),
3285 src->name, new_item->path);
3288 folder_item_prefs_copy_prefs(src, new_item);
3290 /* copy internal data */
3291 if (src->folder->klass == new_item->folder->klass &&
3292 src->folder->klass->copy_private_data != NULL)
3293 src->folder->klass->copy_private_data(src->folder,
3295 new_item->collapsed = src->collapsed;
3296 new_item->thread_collapsed = src->thread_collapsed;
3297 new_item->threaded = src->threaded;
3298 new_item->ret_rcpt = src->ret_rcpt;
3299 new_item->hide_read_msgs = src->hide_read_msgs;
3300 new_item->hide_del_msgs = src->hide_del_msgs;
3301 new_item->hide_read_threads = src->hide_read_threads;
3302 new_item->sort_key = src->sort_key;
3303 new_item->sort_type = src->sort_type;
3305 mlist = folder_item_get_msg_list(src);
3307 if (mlist != NULL) {
3309 folder_item_copy_msgs(new_item, mlist);
3311 folder_item_move_msgs(new_item, mlist);
3312 procmsg_msg_list_free(mlist);
3315 prefs_matcher_write_config();
3318 srcnode = src->folder->node;
3319 srcnode = g_node_find(srcnode, G_PRE_ORDER, G_TRAVERSE_ALL, src);
3320 srcnode = srcnode->children;
3321 while (srcnode != NULL) {
3322 if (srcnode && srcnode->data) {
3323 next_item = (FolderItem*) srcnode->data;
3324 srcnode = srcnode->next;
3325 if (folder_item_move_recursive(next_item, new_item, copy) == NULL) {
3330 old_id = folder_item_get_identifier(src);
3331 new_id = folder_item_get_identifier(new_item);
3333 hookdata.folder = src->folder;
3334 hookdata.update_flags = FOLDER_TREE_CHANGED | FOLDER_MOVE_FOLDERITEM;
3335 hookdata.item = src;
3336 hookdata.item2 = new_item;
3337 hooks_invoke(FOLDER_UPDATE_HOOKLIST, &hookdata);
3339 /* if src supports removing, otherwise only copy folder */
3340 if (src->folder->klass->remove_folder != NULL && !copy)
3341 src->folder->klass->remove_folder(src->folder, src);
3342 folder_write_list();
3345 debug_print("updating rules : %s => %s\n", old_id, new_id);
3346 if (old_id != NULL && new_id != NULL) {
3347 prefs_filtering_rename_path(old_id, new_id);
3348 account_rename_path(old_id, new_id);
3357 gint folder_item_move_to(FolderItem *src, FolderItem *dest, FolderItem **new_item, gboolean copy)
3359 FolderItem *tmp = folder_item_parent(dest);
3360 gchar * src_identifier, * dst_identifier;
3361 gchar * phys_srcpath, * phys_dstpath, *tmppath;
3365 return F_MOVE_FAILED_DEST_IS_CHILD;
3367 tmp = folder_item_parent(tmp);
3370 /* both dst and src can be root folders */
3371 src_identifier = folder_item_get_identifier(src);
3372 if (src_identifier == NULL && src->folder && folder_item_parent(src) == NULL) {
3373 src_identifier = folder_get_identifier(src->folder);
3376 dst_identifier = folder_item_get_identifier(dest);
3377 if(dst_identifier == NULL && dest->folder && folder_item_parent(dest) == NULL) {
3378 dst_identifier = folder_get_identifier(dest->folder);
3381 if (src_identifier == NULL || dst_identifier == NULL) {
3382 debug_print("Can't get identifiers\n");
3383 return F_MOVE_FAILED;
3386 if (src->folder != dest->folder && !copy) {
3387 return F_MOVE_FAILED_DEST_OUTSIDE_MAILBOX;
3390 phys_srcpath = folder_item_get_path(src);
3391 tmppath = folder_item_get_path(dest);
3392 phys_dstpath = g_strconcat(tmppath,
3394 g_path_get_basename(phys_srcpath),
3398 if (folder_item_parent(src) == dest || src == dest) {
3399 g_free(src_identifier);
3400 g_free(dst_identifier);
3401 g_free(phys_srcpath);
3402 g_free(phys_dstpath);
3403 return F_MOVE_FAILED_DEST_IS_PARENT;
3405 debug_print("moving \"%s\" to \"%s\"\n", phys_srcpath, phys_dstpath);
3406 if ((tmp = folder_item_move_recursive(src, dest, copy)) == NULL) {
3407 return F_MOVE_FAILED;
3410 g_free(src_identifier);
3411 g_free(dst_identifier);
3412 g_free(phys_srcpath);
3413 g_free(phys_dstpath);
3424 static void find_num(gpointer key, gpointer value, gpointer data)
3426 struct find_data *fdata = (struct find_data *)data;
3427 if (GPOINTER_TO_INT(value) == 0)
3428 fdata->found = TRUE;
3431 static gboolean some_msgs_have_zero_num(GHashTable *hashtable)
3433 struct find_data fdata;
3435 fdata.found = FALSE;
3436 g_hash_table_foreach(hashtable, find_num, &fdata);
3442 * Copy a list of message to a new folder and remove
3443 * source messages if wanted
3445 static gint do_copy_msgs(FolderItem *dest, GSList *msglist, gboolean remove_source)
3449 gint num, lastnum = -1;
3450 gboolean folderscan = FALSE;
3451 GHashTable *relation;
3452 GSList *not_moved = NULL;
3453 gint total = 0, curmsg = 0;
3454 MsgInfo *msginfo = NULL;
3456 cm_return_val_if_fail(dest != NULL, -1);
3457 cm_return_val_if_fail(msglist != NULL, -1);
3459 folder = dest->folder;
3461 cm_return_val_if_fail(folder->klass->copy_msg != NULL, -1);
3462 if (dest->no_select)
3465 msginfo = (MsgInfo *)msglist->data;
3470 if (!MSG_IS_QUEUED(msginfo->flags) &&
3471 MSG_IS_DRAFT(msginfo->flags) &&
3472 folder_has_parent_of_type(dest, F_QUEUE)) {
3473 GSList *cur = msglist;
3474 gboolean queue_err = FALSE;
3475 for (; cur; cur = cur->next) {
3476 Compose *compose = NULL;
3477 FolderItem *queue = dest;
3478 ComposeQueueResult val = COMPOSE_QUEUE_SUCCESS;
3480 msginfo = (MsgInfo *)cur->data;
3481 compose = compose_reedit(msginfo, TRUE);
3482 if (compose == NULL) {
3486 val = compose_queue(compose, NULL, &queue, NULL,
3488 if (val != COMPOSE_QUEUE_SUCCESS) {
3490 } else if (remove_source) {
3491 folder_item_remove_msg(msginfo->folder, msginfo->msgnum);
3493 if (val == COMPOSE_QUEUE_SUCCESS)
3494 compose_close(compose);
3496 return queue_err ? -1:0;
3499 relation = g_hash_table_new(g_direct_hash, g_direct_equal);
3501 for (l = msglist ; l != NULL ; l = g_slist_next(l)) {
3502 MsgInfo * msginfo = (MsgInfo *) l->data;
3504 if (msginfo->planned_download != 0) {
3505 int old_planned = msginfo->planned_download;
3506 partial_unmark(msginfo);
3507 /* little hack to reenable after */
3508 msginfo->planned_download = old_planned;
3513 * Copy messages to destination folder and
3514 * store new message numbers in newmsgnums
3516 if (folder->klass->copy_msgs != NULL) {
3517 if (folder->klass->copy_msgs(folder, dest, msglist, relation) < 0) {
3518 g_hash_table_destroy(relation);
3525 /* immediately stop if src and dest folders are identical */
3527 msginfo = (MsgInfo *) l->data;
3528 if (msginfo != NULL && msginfo->folder == dest) {
3529 g_hash_table_destroy(relation);
3534 for (; l != NULL ; l = g_slist_next(l)) {
3535 msginfo = (MsgInfo *) l->data;
3537 num = folder->klass->copy_msg(folder, dest, msginfo);
3539 g_hash_table_insert(relation, msginfo, GINT_TO_POINTER(num));
3541 not_moved = g_slist_prepend(not_moved, msginfo);
3545 if (remove_source) {
3546 MsgInfo *msginfo = (MsgInfo *) msglist->data;
3547 FolderItem *item = msginfo->folder;
3549 * Remove source messages from their folders if
3550 * copying was successfull and update folder
3553 if (not_moved == NULL && item->folder->klass->remove_msgs) {
3554 item->folder->klass->remove_msgs(item->folder,
3559 for (l = msglist; l != NULL; l = g_slist_next(l)) {
3560 gpointer old_key, data;
3561 msginfo = (MsgInfo *) l->data;
3562 item = msginfo->folder;
3564 if (g_hash_table_lookup_extended(relation, msginfo, &old_key, &data))
3565 num = GPOINTER_TO_INT(data);
3569 if (g_slist_find(not_moved, msginfo))
3572 if ((num >= 0) && (item->folder->klass->remove_msg != NULL)) {
3573 if (!item->folder->klass->remove_msgs)
3574 item->folder->klass->remove_msg(item->folder,
3577 remove_msginfo_from_cache(item, msginfo);
3582 /* Read cache for dest folder */
3583 if (!dest->cache) folder_item_read_cache(dest);
3586 * Fetch new MsgInfos for new messages in dest folder,
3587 * add them to the msgcache and update folder message counts
3589 if (some_msgs_have_zero_num(relation)) {
3590 folder_item_scan_full(dest, FALSE);
3594 statusbar_print_all(_("Updating cache for %s..."), dest->path ? dest->path : "(null)");
3595 total = g_slist_length(msglist);
3597 if (FOLDER_TYPE(dest->folder) == F_IMAP && total > 1) {
3598 folder_item_scan_full(dest, FALSE);
3601 folder_item_set_batch(dest, TRUE);
3602 for (l = msglist; l != NULL; l = g_slist_next(l)) {
3603 MsgInfo *msginfo = (MsgInfo *) l->data;
3604 gpointer data, old_key;
3609 if (g_hash_table_lookup_extended(relation, msginfo, &old_key, &data))
3610 num = GPOINTER_TO_INT(data);
3614 statusbar_progress_all(curmsg++,total, 100);
3615 if (curmsg % 100 == 0)
3619 MsgInfo *newmsginfo = NULL;
3621 if (!folderscan && num > 0) {
3622 newmsginfo = get_msginfo(dest, num);
3623 if (newmsginfo != NULL) {
3624 add_msginfo_to_cache(dest, newmsginfo, msginfo);
3627 if (newmsginfo == NULL) {
3629 folder_item_scan_full(dest, FALSE);
3632 if (msginfo->msgid != NULL) {
3633 newmsginfo = folder_item_get_msginfo_by_msgid(dest, msginfo->msgid);
3634 if (newmsginfo != NULL) {
3635 copy_msginfo_flags(msginfo, newmsginfo);
3636 num = newmsginfo->msgnum;
3640 if (newmsginfo != NULL
3641 && msginfo->planned_download == POP3_PARTIAL_DLOAD_DELE) {
3642 partial_mark_for_delete(newmsginfo);
3644 if (newmsginfo != NULL
3645 && msginfo->planned_download == POP3_PARTIAL_DLOAD_DLOAD) {
3646 partial_mark_for_download(newmsginfo);
3648 if (!MSG_IS_POSTFILTERED (msginfo->flags)) {
3649 procmsg_msginfo_set_flags ( msginfo, MSG_POSTFILTERED, 0);
3651 procmsg_msginfo_set_flags (newmsginfo, MSG_POSTFILTERED, 0);
3652 hooks_invoke (MAIL_POSTFILTERING_HOOKLIST, newmsginfo);
3655 procmsg_msginfo_free(&newmsginfo);
3662 folder_item_set_batch(dest, FALSE);
3663 statusbar_progress_all(0,0,0);
3664 statusbar_pop_all();
3666 g_hash_table_destroy(relation);
3667 if (not_moved != NULL) {
3668 g_slist_free(not_moved);
3675 * Move a message to a new folder.
3677 * \param dest Destination folder
3678 * \param msginfo The message
3680 gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
3684 cm_return_val_if_fail(dest != NULL, -1);
3685 cm_return_val_if_fail(msginfo != NULL, -1);
3687 list.data = msginfo;
3690 return do_copy_msgs(dest, &list, TRUE);
3694 * Move a list of messages to a new folder.
3696 * \param dest Destination folder
3697 * \param msglist List of messages
3699 gint folder_item_move_msgs(FolderItem *dest, GSList *msglist)
3702 cm_return_val_if_fail(dest != NULL, -1);
3703 cm_return_val_if_fail(msglist != NULL, -1);
3705 result = do_copy_msgs(dest, msglist, TRUE);
3711 * Copy a message to a new folder.
3713 * \param dest Destination folder
3714 * \param msginfo The message
3716 gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo)
3720 cm_return_val_if_fail(dest != NULL, -1);
3721 cm_return_val_if_fail(msginfo != NULL, -1);
3723 list.data = msginfo;
3726 return do_copy_msgs(dest, &list, FALSE);
3730 * Copy a list of messages to a new folder.
3732 * \param dest Destination folder
3733 * \param msglist List of messages
3735 gint folder_item_copy_msgs(FolderItem *dest, GSList *msglist)
3738 cm_return_val_if_fail(dest != NULL, -1);
3739 cm_return_val_if_fail(msglist != NULL, -1);
3742 result = do_copy_msgs(dest, msglist, FALSE);
3748 gint folder_item_remove_msg(FolderItem *item, gint num)
3754 cm_return_val_if_fail(item != NULL, -1);
3755 folder = item->folder;
3756 cm_return_val_if_fail(folder->klass->remove_msg != NULL, -1);
3757 if (item->no_select)
3760 if (!item->cache) folder_item_read_cache(item);
3762 msginfo = msgcache_get_msg(item->cache, num);
3763 if (msginfo && MSG_IS_LOCKED(msginfo->flags)) {
3764 procmsg_msginfo_free(&msginfo);
3767 ret = folder->klass->remove_msg(folder, item, num);
3769 if (msginfo != NULL) {
3771 remove_msginfo_from_cache(item, msginfo);
3772 procmsg_msginfo_free(&msginfo);
3778 gint folder_item_remove_msgs(FolderItem *item, GSList *msglist)
3782 GSList *real_list = NULL, *cur = NULL;
3784 cm_return_val_if_fail(item != NULL, -1);
3785 folder = item->folder;
3786 cm_return_val_if_fail(folder != NULL, -1);
3787 if (item->no_select)
3790 if (!item->cache) folder_item_read_cache(item);
3792 folder_item_update_freeze();
3794 /* filter out locked mails */
3795 for (cur = msglist; cur; cur = cur->next) {
3796 MsgInfo *info = (MsgInfo *)cur->data;
3797 if (!MSG_IS_LOCKED(info->flags))
3798 real_list = g_slist_prepend(real_list, info);
3801 real_list = g_slist_reverse(real_list);
3803 if (item->folder->klass->remove_msgs) {
3804 ret = item->folder->klass->remove_msgs(item->folder,
3810 while (ret == 0 && cur != NULL) {
3811 MsgInfo *msginfo = (MsgInfo *)cur->data;
3812 if (msginfo && MSG_IS_LOCKED(msginfo->flags)) {
3816 if (!item->folder->klass->remove_msgs)
3817 ret = folder_item_remove_msg(item, msginfo->msgnum);
3818 if (ret != 0) break;
3819 msgcache_remove_msg(item->cache, msginfo->msgnum);
3822 g_slist_free(real_list);
3823 folder_item_scan_full(item, FALSE);
3824 folder_item_update_thaw();
3829 gint folder_item_expunge(FolderItem *item)
3831 Folder *folder = item->folder;
3835 if (folder->klass->expunge) {
3836 GSList *msglist = folder_item_get_msg_list(item);
3838 result = folder->klass->expunge(folder, item);
3840 for (cur = msglist; cur; cur = cur->next) {
3841 MsgInfo *msginfo = (MsgInfo *)cur->data;
3842 if (MSG_IS_DELETED(msginfo->flags)) {
3843 remove_msginfo_from_cache(item, msginfo);
3847 procmsg_msg_list_free(msglist);
3852 gint folder_item_remove_all_msg(FolderItem *item)
3857 cm_return_val_if_fail(item != NULL, -1);
3858 if (item->no_select)
3861 folder = item->folder;
3864 if (folder->klass->remove_all_msg != NULL) {
3865 result = folder->klass->remove_all_msg(folder, item);
3868 folder_item_free_cache(item, TRUE);
3869 item->cache = msgcache_new();
3870 item->cache_dirty = TRUE;
3871 item->mark_dirty = TRUE;
3872 item->tags_dirty = TRUE;
3875 MsgInfoList *msglist;
3877 msglist = folder_item_get_msg_list(item);
3878 result = folder_item_remove_msgs(item, msglist);
3879 procmsg_msg_list_free(msglist);
3884 item->unread_msgs = 0;
3885 item->unreadmarked_msgs = 0;
3886 item->marked_msgs = 0;
3887 item->total_msgs = 0;
3888 item->replied_msgs = 0;
3889 item->forwarded_msgs = 0;
3890 item->locked_msgs = 0;
3891 item->ignored_msgs = 0;
3892 item->watched_msgs = 0;
3893 folder_item_update(item, F_ITEM_UPDATE_MSGCNT | F_ITEM_UPDATE_CONTENT);
3900 void folder_item_change_msg_flags(FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3902 cm_return_if_fail(item != NULL);
3903 cm_return_if_fail(msginfo != NULL);
3905 item->mark_dirty = TRUE;
3907 if (item->no_select)
3910 if (item->folder->klass->change_flags != NULL && item->scanning != ITEM_SCANNING_WITH_FLAGS) {
3911 item->folder->klass->change_flags(item->folder, item, msginfo, newflags);
3913 msginfo->flags.perm_flags = newflags;
3917 void folder_item_commit_tags(FolderItem *item, MsgInfo *msginfo, GSList *tags_set, GSList *tags_unset)
3919 Folder *folder = NULL;
3925 if (!tags_set && !tags_unset)
3928 folder = item->folder;
3932 item->tags_dirty = TRUE;
3934 if (folder->klass->commit_tags == NULL)
3937 folder->klass->commit_tags(item, msginfo, tags_set, tags_unset);
3940 gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo)
3944 cm_return_val_if_fail(item != NULL, FALSE);
3945 if (item->no_select)
3948 folder = item->folder;
3950 cm_return_val_if_fail(folder->klass->is_msg_changed != NULL, -1);
3952 return folder->klass->is_msg_changed(folder, item, msginfo);
3955 void folder_item_discard_cache(FolderItem *item)
3964 msgcache_destroy(item->cache);
3967 dir = folder_item_get_path(item);
3968 if (is_dir_exist(dir))
3969 remove_all_numbered_files(dir);
3972 cache = folder_item_get_cache_file(item);
3973 if (is_file_exist(cache))
3974 claws_unlink(cache);
3979 static gchar *folder_item_get_cache_file(FolderItem *item)
3985 cm_return_val_if_fail(item != NULL, NULL);
3986 cm_return_val_if_fail(item->path != NULL, NULL);
3988 path = folder_item_get_path(item);
3989 cm_return_val_if_fail(path != NULL, NULL);
3990 if (!is_dir_exist(path))
3991 make_dir_hier(path);
3992 file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL);
3993 old_file = g_strconcat(path, G_DIR_SEPARATOR_S, OLD_CACHE_FILE, NULL);
3995 if (!is_file_exist(file) && is_file_exist(old_file))
3996 move_file(old_file, file, FALSE);
4003 static gchar *folder_item_get_mark_file(FolderItem *item)
4009 cm_return_val_if_fail(item != NULL, NULL);
4010 cm_return_val_if_fail(item->path != NULL, NULL);
4012 path = folder_item_get_path(item);
4013 cm_return_val_if_fail(path != NULL, NULL);
4014 if (!is_dir_exist(path))
4015 make_dir_hier(path);
4016 file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL);
4017 old_file = g_strconcat(path, G_DIR_SEPARATOR_S, OLD_MARK_FILE, NULL);
4019 if (!is_file_exist(file) && is_file_exist(old_file))
4020 move_file(old_file, file, FALSE);
4027 static gchar *folder_item_get_tags_file(FolderItem *item)
4033 /* we save tags files in rc_dir, because tagsrc is there too,
4034 * and storing tags directly in the mailboxes would give strange
4035 * result when using another Claws mailbox from another install
4036 * with different tags. */
4038 cm_return_val_if_fail(item != NULL, NULL);
4040 identifier = folder_item_get_identifier(item);
4041 cm_return_val_if_fail(identifier != NULL, NULL);
4044 while (strchr(identifier, '/'))
4045 *strchr(identifier, '/') = '\\';
4048 path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
4049 TAGS_DIR, G_DIR_SEPARATOR_S,
4054 if (!is_dir_exist(path))
4055 make_dir_hier(path);
4057 file = g_strconcat(path, G_DIR_SEPARATOR_S, TAGS_FILE, NULL);
4064 static gpointer xml_to_folder_item(gpointer nodedata, gpointer data)
4066 XMLNode *xmlnode = (XMLNode *) nodedata;
4067 Folder *folder = (Folder *) data;
4070 cm_return_val_if_fail(xmlnode != NULL, NULL);
4071 cm_return_val_if_fail(folder != NULL, NULL);
4073 if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
4074 g_warning("tag name != \"folderitem\"");
4078 item = folder_item_new(folder, "", "");
4079 g_node_destroy(item->node);
4080 if (folder->klass->item_set_xml != NULL)
4081 folder->klass->item_set_xml(folder, item, xmlnode->tag);
4083 folder_item_set_xml(folder, item, xmlnode->tag);
4085 item->folder = folder;
4087 switch (item->stype) {
4088 case F_INBOX: folder->inbox = item; break;
4089 case F_OUTBOX: folder->outbox = item; break;
4090 case F_DRAFT: folder->draft = item; break;
4091 case F_QUEUE: folder->queue = item; break;
4092 case F_TRASH: folder->trash = item; break;
4095 folder_item_prefs_read_config(item);
4100 static gboolean folder_item_set_node(GNode *node, gpointer data)
4102 cm_return_val_if_fail(node->data != NULL, -1);
4104 FolderItem *item = (FolderItem *) node->data;
4110 static Folder *folder_get_from_xml(GNode *node)
4115 FolderClass *klass = NULL;
4118 cm_return_val_if_fail(node->data != NULL, NULL);
4120 xmlnode = node->data;
4121 if (strcmp2(xmlnode->tag->tag, "folder") != 0) {
4122 g_warning("tag name != \"folder\"");
4125 list = xmlnode->tag->attr;
4126 for (; list != NULL; list = list->next) {
4127 XMLAttr *attr = list->data;
4129 if (!attr || !attr->name || !attr->value) continue;
4130 if (!strcmp(attr->name, "type"))
4131 klass = folder_get_class_from_string(attr->value);
4136 folder = folder_new(klass, "", "");
4137 cm_return_val_if_fail(folder != NULL, NULL);
4140 klass->set_xml(folder, xmlnode->tag);
4142 folder_set_xml(folder, xmlnode->tag);
4144 cur = node->children;
4145 while (cur != NULL) {
4148 itemnode = g_node_map(cur, xml_to_folder_item, (gpointer) folder);
4149 g_node_append(folder->node, itemnode);
4152 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, folder_item_set_node, NULL);
4157 gchar *folder_get_list_path(void)
4159 static gchar *filename = NULL;
4162 filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
4168 static gpointer folder_item_to_xml(gpointer nodedata, gpointer data)
4170 FolderItem *item = (FolderItem *) nodedata;
4173 cm_return_val_if_fail(item != NULL, NULL);
4175 if (item->folder->klass->item_get_xml != NULL)
4176 tag = item->folder->klass->item_get_xml(item->folder, item);
4178 tag = folder_item_get_xml(item->folder, item);
4180 return xml_node_new(tag, NULL);
4183 static GNode *folder_get_xml_node(Folder *folder)
4189 cm_return_val_if_fail(folder != NULL, NULL);
4191 if (folder->klass->get_xml != NULL)
4192 tag = folder->klass->get_xml(folder);
4194 tag = folder_get_xml(folder);
4196 xml_tag_add_attr(tag, xml_attr_new("type", folder->klass->idstr));
4198 xmlnode = xml_node_new(tag, NULL);
4200 node = g_node_new(xmlnode);
4202 cm_return_val_if_fail (folder->node != NULL, NULL);
4204 if (folder->node->children) {
4207 cur = folder->node->children;
4211 xmlnode = g_node_map(cur, folder_item_to_xml, (gpointer) folder);
4212 g_node_append(node, xmlnode);
4220 static void folder_update_op_count_rec(GNode *node)
4222 FolderItem *fitem = FOLDER_ITEM(node->data);
4224 if (g_node_depth(node) > 0) {
4225 if (fitem->op_count > 0) {
4226 fitem->op_count = 0;
4227 folder_item_update(fitem, F_ITEM_UPDATE_MSGCNT);
4229 if (node->children) {
4232 child = node->children;
4238 folder_update_op_count_rec(cur);
4244 void folder_update_op_count(void)
4249 for (cur = folder_list; cur != NULL; cur = cur->next) {
4251 folder_update_op_count_rec(folder->node);
4255 typedef struct _type_str {
4262 static gchar * folder_item_get_tree_identifier(FolderItem * item)
4264 if (item->parent != NULL) {
4268 path = folder_item_get_tree_identifier(item->parent);
4272 id = g_strconcat(path, "/", item->name, NULL);
4278 return g_strconcat("/", item->name, NULL);
4283 /* CLAWS: temporary local folder for filtering */
4284 #define TEMP_FOLDER "TEMP_FOLDER"
4285 #define PROCESSING_FOLDER_ITEM "processing"
4287 static FolderItem *folder_create_processing_folder(int account_id)
4289 static Folder *processing_folder = NULL;
4290 FolderItem *processing_folder_item;
4292 gchar *processing_folder_item_name = NULL;
4294 processing_folder_item_name = g_strdup_printf("%s-%d", PROCESSING_FOLDER_ITEM, account_id);
4296 if (processing_folder == NULL) {
4298 g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
4299 "tempfolder", NULL);
4301 folder_new(mh_get_class(), TEMP_FOLDER, tmppath);
4305 g_assert(processing_folder != NULL);
4306 processing_folder->klass->scan_tree(processing_folder);
4308 g_assert(processing_folder != NULL);
4310 processing_folder_item = folder_find_child_item_by_name(FOLDER_ITEM(processing_folder->node->data),
4311 processing_folder_item_name);
4312 if (processing_folder_item) {
4313 debug_print("*TMP* already created %s\n", folder_item_get_path(processing_folder_item));
4315 processing_folder_item = processing_folder->klass->create_folder(processing_folder,
4316 processing_folder->node->data,
4317 processing_folder_item_name);
4318 folder_item_append(FOLDER_ITEM(processing_folder->node->data), processing_folder_item);
4319 debug_print("*TMP* creating %s\n", folder_item_get_path(processing_folder_item));
4321 g_free(processing_folder_item_name);
4322 g_assert(processing_folder_item != NULL);
4324 return(processing_folder_item);
4327 FolderItem *folder_get_default_processing(int account_id)
4329 return folder_create_processing_folder(account_id);
4332 /* folder_persist_prefs_new() - return hash table with persistent
4333 * settings (and folder name as key).
4334 * (note that in claws other options are in the folder_item_prefs_RC
4335 * file, so those don't need to be included in PersistPref yet)
4337 static GHashTable *folder_persist_prefs_new(Folder *folder)
4339 GHashTable *pptable;
4341 cm_return_val_if_fail(folder, NULL);
4342 pptable = g_hash_table_new(g_str_hash, g_str_equal);
4343 folder_get_persist_prefs_recursive(folder->node, pptable);
4347 static void folder_persist_prefs_free(GHashTable *pptable)
4349 cm_return_if_fail(pptable);
4350 g_hash_table_foreach_remove(pptable, persist_prefs_free, NULL);
4351 g_hash_table_destroy(pptable);
4354 static const PersistPrefs *folder_get_persist_prefs(GHashTable *pptable, const char *name)
4356 if (pptable == NULL || name == NULL) return NULL;
4357 return g_hash_table_lookup(pptable, name);
4360 static void folder_item_restore_persist_prefs(FolderItem *item, GHashTable *pptable)
4362 const PersistPrefs *pp;
4363 gchar *id = folder_item_get_identifier(item);
4365 pp = folder_get_persist_prefs(pptable, id);
4370 /* CLAWS: since not all folder properties have been migrated to
4371 * folderlist.xml, we need to call the old stuff first before
4372 * setting things that apply both to Main and Claws. */
4373 folder_item_prefs_read_config(item);
4375 item->collapsed = pp->collapsed;
4376 item->thread_collapsed = pp->thread_collapsed;
4377 item->threaded = pp->threaded;
4378 item->ret_rcpt = pp->ret_rcpt;
4379 item->hide_read_msgs = pp->hide_read_msgs;
4380 item->hide_del_msgs = pp->hide_del_msgs;
4381 item->hide_read_threads = pp->hide_read_threads;
4382 item->sort_key = pp->sort_key;
4383 item->sort_type = pp->sort_type;
4386 static void folder_get_persist_prefs_recursive(GNode *node, GHashTable *pptable)
4388 FolderItem *item = FOLDER_ITEM(node->data);
4393 cm_return_if_fail(node != NULL);
4394 cm_return_if_fail(item != NULL);
4396 /* NOTE: item->path == NULL means top level folder; not interesting
4397 * to store preferences of that one. */
4399 id = folder_item_get_identifier(item);
4400 pp = g_new0(PersistPrefs, 1);
4401 cm_return_if_fail(pp != NULL);
4402 pp->collapsed = item->collapsed;
4403 pp->thread_collapsed = item->thread_collapsed;
4404 pp->threaded = item->threaded;
4405 pp->ret_rcpt = item->ret_rcpt;
4406 pp->hide_read_msgs = item->hide_read_msgs;
4407 pp->hide_del_msgs = item->hide_del_msgs;
4408 pp->hide_read_threads = item->hide_read_threads;
4409 pp->sort_key = item->sort_key;
4410 pp->sort_type = item->sort_type;
4411 g_hash_table_insert(pptable, id, pp);
4414 if (node->children) {
4415 child = node->children;
4419 folder_get_persist_prefs_recursive(cur, pptable);
4424 static gboolean persist_prefs_free(gpointer key, gpointer val, gpointer data)
4431 void folder_item_apply_processing(FolderItem *item)
4433 GSList *processing_list;
4434 GSList *mlist, *cur;
4435 guint total = 0, curmsg = 0;
4436 gint last_apply_per_account;
4438 cm_return_if_fail(item != NULL);
4440 if (item->no_select)
4443 processing_list = item->prefs->processing;
4445 if (!pre_global_processing && !processing_list
4446 && !post_global_processing)
4449 debug_print("processing %s\n", item->name);
4450 folder_item_update_freeze();
4454 mlist = folder_item_get_msg_list(item);
4455 total = g_slist_length(mlist);
4456 statusbar_print_all(_("Processing messages..."));
4458 last_apply_per_account = prefs_common.apply_per_account_filtering_rules;
4459 prefs_common.apply_per_account_filtering_rules = FILTERING_ACCOUNT_RULES_SKIP;
4461 folder_item_set_batch(item, TRUE);
4462 for (cur = mlist ; cur != NULL ; cur = cur->next) {
4465 msginfo = (MsgInfo *) cur->data;
4467 /* reset parameters that can be modified by processing */
4468 msginfo->hidden = 0;
4471 statusbar_progress_all(curmsg++,total, 10);
4473 /* apply pre global rules */
4474 filter_message_by_msginfo(pre_global_processing, msginfo, NULL,
4475 FILTERING_PRE_PROCESSING, NULL);
4477 /* apply rules of the folder */
4478 filter_message_by_msginfo(processing_list, msginfo, NULL,
4479 FILTERING_FOLDER_PROCESSING, item->name);
4481 /* apply post global rules */
4482 filter_message_by_msginfo(post_global_processing, msginfo, NULL,
4483 FILTERING_POST_PROCESSING, NULL);
4484 if (curmsg % 1000 == 0)
4487 folder_item_set_batch(item, FALSE);
4489 prefs_common.apply_per_account_filtering_rules = last_apply_per_account;
4491 if (pre_global_processing || processing_list
4492 || post_global_processing)
4493 filtering_move_and_copy_msgs(mlist);
4494 for (cur = mlist ; cur != NULL ; cur = cur->next) {
4495 procmsg_msginfo_free((MsgInfo **)&(cur->data));
4497 g_slist_free(mlist);
4499 statusbar_progress_all(0,0,0);
4500 statusbar_pop_all();
4504 folder_item_update_thaw();
4508 * functions for handling FolderItem content changes
4510 static gint folder_item_update_freeze_cnt = 0;
4512 static void folder_item_update_with_msg(FolderItem *item, FolderItemUpdateFlags update_flags, MsgInfo *msg)
4514 if (folder_item_update_freeze_cnt == 0 /* || (msg != NULL && item->opened) */) {
4515 FolderItemUpdateData source;
4518 source.update_flags = update_flags;
4520 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &source);
4522 item->update_flags |= update_flags & ~(F_ITEM_UPDATE_ADDMSG | F_ITEM_UPDATE_REMOVEMSG);
4527 * Notify the folder system about changes to a folder. If the
4528 * update system is not frozen the FOLDER_ITEM_UPDATE_HOOKLIST will
4529 * be invoked, otherwise the changes will be remebered until
4530 * the folder system is thawed.
4532 * \param item The FolderItem that was changed
4533 * \param update_flags Type of changed that was made
4535 void folder_item_update(FolderItem *item, FolderItemUpdateFlags update_flags)
4537 folder_item_update_with_msg(item, update_flags, NULL);
4540 void folder_item_update_recursive(FolderItem *item, FolderItemUpdateFlags update_flags)
4542 GNode *node = item->folder->node;
4544 node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
4545 node = node->children;
4547 folder_item_update(item, update_flags);
4548 while (node != NULL) {
4549 if (node && node->data) {
4550 FolderItem *next_item = (FolderItem*) node->data;
4552 folder_item_update(next_item, update_flags);
4558 void folder_item_update_freeze(void)
4560 folder_item_update_freeze_cnt++;
4563 static void folder_item_update_func(FolderItem *item, gpointer data)
4565 FolderItemUpdateData source;
4567 if (item->update_flags) {
4569 source.update_flags = item->update_flags;
4571 hooks_invoke(FOLDER_ITEM_UPDATE_HOOKLIST, &source);
4572 item->update_flags = 0;
4576 void folder_item_update_thaw(void)
4578 if (folder_item_update_freeze_cnt > 0)
4579 folder_item_update_freeze_cnt--;
4580 if (folder_item_update_freeze_cnt == 0) {
4581 /* Update all folders */
4582 folder_func_to_all_folders(folder_item_update_func, NULL);
4586 void folder_item_synchronise(FolderItem *item)
4590 if (item->prefs->offlinesync && item->folder->klass->synchronise) {
4591 statusbar_print_all(_("Synchronising %s for offline use...\n"), item->path ? item->path : "(null)");
4592 item->folder->klass->synchronise(item,
4593 item->prefs->offlinesync_days);
4594 if (item->prefs->offlinesync_days > 0 &&
4595 item->prefs->remove_old_bodies)
4596 folder_item_clean_local_files(item, item->prefs->offlinesync_days);
4597 statusbar_pop_all();
4601 static void folder_item_synchronise_func(FolderItem *item, gpointer data)
4603 Folder *folder = (Folder *)data;
4604 if (folder == NULL || item->folder == folder) {
4605 folder_item_synchronise(item);
4609 void folder_synchronise(Folder *folder)
4611 folder_func_to_all_folders(folder_item_synchronise_func, folder);
4614 typedef struct _WantSyncData {
4619 static void folder_item_want_synchronise_func(FolderItem *item, gpointer data)
4621 WantSyncData *want_sync_data = (WantSyncData *)data;
4623 if (want_sync_data->folder == NULL || item->folder == want_sync_data->folder) {
4624 if (item->prefs->offlinesync && item->folder->klass->synchronise)
4625 want_sync_data->want_sync |= TRUE;
4629 gboolean folder_want_synchronise(Folder *folder)
4631 WantSyncData *want_sync_data = g_new0(WantSyncData, 1);
4633 want_sync_data->folder = folder;
4634 want_sync_data->want_sync = FALSE;
4636 folder_func_to_all_folders(folder_item_want_synchronise_func, want_sync_data);
4637 result = want_sync_data->want_sync;
4638 g_free(want_sync_data);
4640 debug_print("Folder %s wants sync\n", folder->name);
4644 void folder_item_set_batch (FolderItem *item, gboolean batch)
4646 if (!item || !item->folder)
4648 if (item->folder->klass->set_batch) {
4649 item->folder->klass->set_batch(item->folder, item, batch);
4653 gboolean folder_has_parent_of_type(FolderItem *item,
4654 SpecialFolderItemType type)
4656 FolderItem *cur = item;
4660 /* if we already know it, make it short */
4661 if (item->parent_stype != -1) {
4662 return (item->parent_stype == type);
4665 /* if we don't, find the type from the first possible parent,
4666 * and set our parent type to be faster next time */
4668 if (cur->stype == type || cur->parent_stype == type) {
4669 item->parent_stype = type;
4672 cur = folder_item_parent(cur);
4675 /* if we didn't match what was asked, we didn't return. If our
4676 * parent type is unknown, we may as well find it now to be faster
4678 if (item->parent_stype == -1) {
4681 /* here's an exception: Inbox subfolders are normal. */
4682 if (item->parent_stype == -1 && cur->stype == F_INBOX
4684 item->parent_stype = F_NORMAL;
4687 /* ah, we know this parent's parent's type, we may as
4688 * well copy it instead of going up the full way */
4689 if (cur->parent_stype != -1) {
4690 item->parent_stype = cur->parent_stype;
4693 /* we found a parent that has a special type. That's
4694 * our parent type. */
4695 if (cur->stype != F_NORMAL) {
4696 cur->parent_stype = cur->stype;
4697 item->parent_stype = cur->stype;
4700 /* if we didn't find anything, go up once more */
4701 cur = folder_item_parent(cur);
4703 /* as we still didn't find anything, our parents must all be
4705 if (item->parent_stype == -1) {
4706 item->parent_stype = F_NORMAL;
4712 gboolean folder_is_child_of(FolderItem *item, FolderItem *parent)
4714 if (item == NULL || parent == NULL)
4717 while (item != NULL) {
4721 item = folder_item_parent(item);
4728 gboolean folder_subscribe (const gchar *uri)
4731 for (cur = folder_get_list(); cur != NULL; cur = g_list_next(cur)) {
4732 Folder *folder = (Folder *) cur->data;
4734 if (folder->klass->subscribe
4735 && folder->klass->subscribe(folder, uri)) {
4743 gboolean folder_get_sort_type (Folder *folder,
4744 FolderSortKey *sort_key,
4745 FolderSortType *sort_type)
4747 if (!folder || !sort_key || !sort_type)
4749 if (folder->klass->get_sort_type == NULL)
4751 folder->klass->get_sort_type(folder, sort_key, sort_type);
4755 gint folder_item_search_msgs (Folder *folder,
4756 FolderItem *container,
4757 MsgNumberList **msgs,
4758 gboolean *on_server,
4759 MatcherList *predicate,
4760 SearchProgressNotify progress_cb,
4761 gpointer progress_data)
4765 folder_item_update_freeze();
4767 if (folder->klass->search_msgs)
4768 result = folder->klass->search_msgs(folder, container,
4769 msgs, on_server, predicate, progress_cb, progress_data);
4771 result = folder_item_search_msgs_local(folder, container,
4772 msgs, on_server, predicate, progress_cb, progress_data);
4774 folder_item_update_thaw();
4779 MsgNumberList *folder_item_get_number_list(FolderItem *item)
4781 GSList *nums = NULL;
4782 GSList *msglist = folder_item_get_msg_list(item);
4784 nums = procmsg_get_number_list_for_msgs(msglist);
4785 procmsg_msg_list_free(msglist);
4790 gint folder_item_search_msgs_local (Folder *folder,
4791 FolderItem *container,
4792 MsgNumberList **msgs,
4793 gboolean *on_server,
4794 MatcherList *predicate,
4795 SearchProgressNotify progress_cb,
4796 gpointer progress_data)
4798 GSList *result = NULL;
4800 gint matched_count = 0;
4801 guint processed_count = 0;
4803 GSList *nums = NULL;
4805 if (*msgs == NULL) {
4806 nums = folder_item_get_number_list(container);
4811 msgcount = g_slist_length(nums);
4816 for (cur = nums; cur != NULL; cur = cur->next) {
4817 guint msgnum = GPOINTER_TO_UINT(cur->data);
4818 MsgInfo *msg = folder_item_get_msginfo(container, msgnum);
4821 g_slist_free(result);
4825 if (matcherlist_match(predicate, msg)) {
4826 result = g_slist_prepend(result, GUINT_TO_POINTER(msg->msgnum));
4831 procmsg_msginfo_free(&msg);
4833 if (progress_cb != NULL
4834 && !progress_cb(progress_data, FALSE, processed_count,
4835 matched_count, msgcount))
4840 *msgs = g_slist_reverse(result);
4842 return matched_count;
4845 /* Tests if a local (on disk) folder name is acceptable. */
4846 gboolean folder_local_name_ok(const gchar *name)
4849 if (name[0] == '.' || name[strlen(name) - 1] == '.') {
4850 alertpanel_error(_("A folder name cannot begin or end with a dot."));
4853 if (name[strlen(name) - 1] == ' ') {
4854 alertpanel_error(_("A folder name can not end with a space."));