changed attachement into attachment
[claws.git] / src / folder.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999,2000 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <stdio.h>
28 #include <string.h>
29
30 #include "intl.h"
31 #include "folder.h"
32 #include "session.h"
33 #include "imap.h"
34 #include "news.h"
35 #include "mh.h"
36 #include "utils.h"
37 #include "xml.h"
38 #include "codeconv.h"
39 #include "prefs.h"
40 #include "account.h"
41 #include "prefs_account.h"
42
43 static GList *folder_list = NULL;
44
45 static void folder_init         (Folder         *folder,
46                                  FolderType      type,
47                                  const gchar    *name);
48
49 static void local_folder_destroy        (LocalFolder    *lfolder);
50 static void remote_folder_destroy       (RemoteFolder   *rfolder);
51 static void mh_folder_destroy           (MHFolder       *folder);
52 static void imap_folder_destroy         (IMAPFolder     *folder);
53 static void news_folder_destroy         (NewsFolder     *folder);
54
55 static gboolean folder_read_folder_func (GNode          *node,
56                                          gpointer        data);
57 static gchar *folder_get_list_path      (void);
58 static void folder_write_list_recursive (GNode          *node,
59                                          gpointer        data);
60
61
62 Folder *folder_new(FolderType type, const gchar *name, const gchar *path)
63 {
64         Folder *folder = NULL;
65
66         name = name ? name : path;
67         switch (type) {
68         case F_MH:
69                 folder = mh_folder_new(name, path);
70                 break;
71         case F_IMAP:
72                 folder = imap_folder_new(name, path);
73                 break;
74         case F_NEWS:
75                 folder = news_folder_new(name, path);
76                 break;
77         default:
78                 return NULL;
79         }
80
81         return folder;
82 }
83
84 Folder *mh_folder_new(const gchar *name, const gchar *path)
85 {
86         Folder *folder;
87
88         folder = (Folder *)g_new0(MHFolder, 1);
89         folder_init(folder, F_MH, name);
90         LOCAL_FOLDER(folder)->rootpath = g_strdup(path);
91
92         return folder;
93 }
94
95 Folder *mbox_folder_new(const gchar *name, const gchar *path)
96 {
97         /* not yet implemented */
98         return NULL;
99 }
100
101 Folder *maildir_folder_new(const gchar *name, const gchar *path)
102 {
103         /* not yet implemented */
104         return NULL;
105 }
106
107 Folder *imap_folder_new(const gchar *name, const gchar *path)
108 {
109         Folder *folder;
110
111         folder = (Folder *)g_new0(IMAPFolder, 1);
112         folder_init(folder, F_IMAP, name);
113
114         return folder;
115 }
116
117 Folder *news_folder_new(const gchar *name, const gchar *path)
118 {
119         Folder *folder;
120
121         folder = (Folder *)g_new0(NewsFolder, 1);
122         folder_init(folder, F_NEWS, name);
123
124         return folder;
125 }
126
127 FolderItem *folder_item_new(const gchar *name, const gchar *path)
128 {
129         FolderItem *item;
130
131         item = g_new0(FolderItem, 1);
132
133         item->stype = F_NORMAL;
134         item->name = g_strdup(name);
135         item->path = g_strdup(path);
136         item->account = NULL;
137         item->mtime = 0;
138         item->new = 0;
139         item->unread = 0;
140         item->total = 0;
141         item->last_num = -1;
142         item->parent = NULL;
143         item->folder = NULL;
144         item->data = NULL;
145
146         item->prefs = prefs_folder_item_new();
147
148         return item;
149 }
150
151 void folder_item_append(FolderItem *parent, FolderItem *item)
152 {
153         GNode *node;
154
155         g_return_if_fail(parent != NULL);
156         g_return_if_fail(parent->folder != NULL);
157         g_return_if_fail(item != NULL);
158
159         node = parent->folder->node;
160         node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, parent);
161         g_return_if_fail(node != NULL);
162
163         item->parent = parent;
164         item->folder = parent->folder;
165         g_node_append_data(node, item);
166 }
167
168 void folder_item_remove(FolderItem *item)
169 {
170         GNode *node;
171
172         g_return_if_fail(item != NULL);
173         g_return_if_fail(item->folder != NULL);
174
175         node = item->folder->node;
176         node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
177         g_return_if_fail(node != NULL);
178
179         /* TODO: free all FolderItem's first */
180         if (item->folder->node == node)
181                 item->folder->node = NULL;
182         g_node_destroy(node);
183 }
184
185 void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data)
186 {
187         g_return_if_fail(folder != NULL);
188
189         folder->ui_func = func;
190         folder->ui_func_data = data;
191 }
192
193 void folder_set_name(Folder *folder, const gchar *name)
194 {
195         g_return_if_fail(folder != NULL);
196
197         g_free(folder->name);
198         folder->name = name ? g_strdup(name) : NULL;
199         if (folder->node && folder->node->data) {
200                 FolderItem *item = (FolderItem *)folder->node->data;
201
202                 g_free(item->name);
203                 item->name = name ? g_strdup(name) : NULL;
204         }
205 }
206
207 void folder_destroy(Folder *folder)
208 {
209         g_return_if_fail(folder != NULL);
210
211         folder_list = g_list_remove(folder_list, folder);
212
213         switch (folder->type) {
214         case F_MH:
215                 mh_folder_destroy(MH_FOLDER(folder));
216                 break;
217         case F_IMAP:
218                 imap_folder_destroy(IMAP_FOLDER(folder));
219                 break;
220         case F_NEWS:
221                 news_folder_destroy(NEWS_FOLDER(folder));
222                 break;
223         default:
224         }
225
226         folder_tree_destroy(folder);
227         g_free(folder->name);
228         g_free(folder);
229 }
230
231 void folder_tree_destroy(Folder *folder)
232 {
233         /* TODO: destroy all FolderItem before */
234         g_node_destroy(folder->node);
235 }
236
237 void folder_add(Folder *folder)
238 {
239         Folder *cur_folder;
240         GList *cur;
241         gint i;
242
243         g_return_if_fail(folder != NULL);
244
245         for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) {
246                 cur_folder = FOLDER(cur->data);
247                 if (folder->type == F_MH) {
248                         if (cur_folder->type != F_MH) break;
249                 } else if (folder->type == F_IMAP) {
250                         if (cur_folder->type != F_MH &&
251                             cur_folder->type != F_IMAP) break;
252                 } else if (folder->type == F_NEWS) {
253                         if (cur_folder->type != F_MH &&
254                             cur_folder->type != F_IMAP &&
255                             cur_folder->type != F_NEWS) break;
256                 }
257         }
258
259         folder_list = g_list_insert(folder_list, folder, i);
260 }
261
262 GList *folder_get_list(void)
263 {
264         return folder_list;
265 }
266
267 gint folder_read_list(void)
268 {
269         GNode *node;
270         XMLNode *xmlnode;
271
272         node = xml_parse_file(folder_get_list_path());
273         if (!node) return -1;
274
275         xmlnode = node->data;
276         if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) {
277                 g_warning("wrong folder list\n");
278                 xml_free_tree(node);
279                 return -1;
280         }
281
282         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
283                         folder_read_folder_func, NULL);
284
285         xml_free_tree(node);
286         if (folder_list)
287                 return 0;
288         else
289                 return -1;
290 }
291
292 void folder_write_list(void)
293 {
294         GList *list;
295         Folder *folder;
296         gchar *path;
297         PrefFile *pfile;
298
299         path = folder_get_list_path();
300         if ((pfile = prefs_write_open(path)) == NULL) return;
301
302         fprintf(pfile->fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
303                 conv_get_current_charset_str());
304         fputs("\n<folderlist>\n", pfile->fp);
305
306         for (list = folder_list; list != NULL; list = list->next) {
307                 folder = list->data;
308                 folder_write_list_recursive(folder->node, pfile->fp);
309         }
310
311         fputs("</folderlist>\n", pfile->fp);
312
313         if (prefs_write_close(pfile) < 0)
314                 g_warning("failed to write folder list.\n");
315 }
316
317 Folder *folder_find_from_path(const gchar *path)
318 {
319         GList *list;
320         Folder *folder;
321
322         for (list = folder_list; list != NULL; list = list->next) {
323                 folder = list->data;
324                 if (folder->type == F_MH &&
325                     !path_cmp(LOCAL_FOLDER(folder)->rootpath, path))
326                         return folder;
327         }
328
329         return NULL;
330 }
331
332 static gboolean folder_item_find_func(GNode *node, gpointer data)
333 {
334         FolderItem *item = node->data;
335         gpointer *d = data;
336         const gchar *path = d[0];
337
338         if (path_cmp(path, item->path) != 0)
339                 return FALSE;
340
341         d[1] = item;
342
343         return TRUE;
344 }
345
346 FolderItem *folder_find_item_from_path(const gchar *path)
347 {
348         Folder *folder;
349         gpointer d[2];
350
351         folder = folder_get_default_folder();
352         g_return_val_if_fail(folder != NULL, NULL);
353
354         d[0] = (gpointer)path;
355         d[1] = NULL;
356         g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
357                         folder_item_find_func, d);
358         return d[1];
359 }
360
361 Folder *folder_get_default_folder(void)
362 {
363         return FOLDER(folder_list->data);
364 }
365
366 FolderItem *folder_get_default_inbox(void)
367 {
368         Folder *folder;
369
370         folder = FOLDER(folder_list->data);
371         g_return_val_if_fail(folder != NULL, NULL);
372         return folder->inbox;
373 }
374
375 FolderItem *folder_get_default_outbox(void)
376 {
377         Folder *folder;
378
379         folder = FOLDER(folder_list->data);
380         g_return_val_if_fail(folder != NULL, NULL);
381         return folder->outbox;
382 }
383
384 FolderItem *folder_get_default_draft(void)
385 {
386         Folder *folder;
387
388         folder = FOLDER(folder_list->data);
389         g_return_val_if_fail(folder != NULL, NULL);
390         return folder->draft;
391 }
392
393 FolderItem *folder_get_default_queue(void)
394 {
395         Folder *folder;
396
397         folder = FOLDER(folder_list->data);
398         g_return_val_if_fail(folder != NULL, NULL);
399         return folder->queue;
400 }
401
402 FolderItem *folder_get_default_trash(void)
403 {
404         Folder *folder;
405
406         folder = FOLDER(folder_list->data);
407         g_return_val_if_fail(folder != NULL, NULL);
408         return folder->trash;
409 }
410
411 gchar *folder_item_get_path(FolderItem *item)
412 {
413         gchar *folder_path;
414         gchar *path;
415
416         g_return_val_if_fail(item != NULL, NULL);
417
418         if (FOLDER_TYPE(item->folder) == F_MH)
419                 folder_path = g_strdup(LOCAL_FOLDER(item->folder)->rootpath);
420         else if (FOLDER_TYPE(item->folder) == F_IMAP) {
421                 g_return_val_if_fail(item->folder->account != NULL, NULL);
422                 folder_path = g_strconcat(get_imap_cache_dir(),
423                                           G_DIR_SEPARATOR_S,
424                                           item->folder->account->recv_server,
425                                           G_DIR_SEPARATOR_S,
426                                           item->folder->account->userid,
427                                           NULL);
428         } else if (FOLDER_TYPE(item->folder) == F_NEWS) {
429                 g_return_val_if_fail(item->folder->account != NULL, NULL);
430                 folder_path = g_strconcat(get_news_cache_dir(),
431                                           G_DIR_SEPARATOR_S,
432                                           item->folder->account->nntp_server,
433                                           NULL);
434         } else
435                 return NULL;
436
437         g_return_val_if_fail(folder_path != NULL, NULL);
438
439         if (folder_path[0] == G_DIR_SEPARATOR) {
440                 if (item->path)
441                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
442                                            item->path, NULL);
443                 else
444                         path = g_strdup(folder_path);
445         } else {
446                 if (item->path)
447                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
448                                            folder_path, G_DIR_SEPARATOR_S,
449                                            item->path, NULL);
450                 else
451                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
452                                            folder_path, NULL);
453         }
454
455         g_free(folder_path);
456         return path;
457 }
458
459 void folder_item_scan(FolderItem *item)
460 {
461         Folder *folder;
462
463         g_return_if_fail(item != NULL);
464
465         folder = item->folder;
466         folder->scan(folder, item);
467 }
468
469 static void folder_item_scan_foreach_func(gpointer key, gpointer val,
470                                           gpointer data)
471 {
472         folder_item_scan(FOLDER_ITEM(key));
473 }
474
475 void folder_item_scan_foreach(GHashTable *table)
476 {
477         g_hash_table_foreach(table, folder_item_scan_foreach_func, NULL);
478 }
479
480 gchar *folder_item_fetch_msg(FolderItem *item, gint num)
481 {
482         Folder *folder;
483
484         g_return_val_if_fail(item != NULL, NULL);
485
486         folder = item->folder;
487         if (item->last_num < 0) folder->scan(folder, item);
488
489         return folder->fetch_msg(folder, item, num);
490 }
491
492 gint folder_item_add_msg(FolderItem *dest, const gchar *file,
493                          gboolean remove_source)
494 {
495         Folder *folder;
496         gint num;
497
498         g_return_val_if_fail(dest != NULL, -1);
499         g_return_val_if_fail(file != NULL, -1);
500         g_return_val_if_fail(dest->folder->add_msg != NULL, -1);
501
502         folder = dest->folder;
503         if (dest->last_num < 0) folder->scan(folder, dest);
504
505         num = folder->add_msg(folder, dest, file, remove_source);
506         if (num > 0) dest->last_num = num;
507
508         return num;
509 }
510
511 gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
512 {
513         Folder *folder;
514         gint num;
515
516         g_return_val_if_fail(dest != NULL, -1);
517         g_return_val_if_fail(msginfo != NULL, -1);
518
519         folder = dest->folder;
520         if (dest->last_num < 0) folder->scan(folder, dest);
521
522         num = folder->move_msg(folder, dest, msginfo);
523         if (num > 0) dest->last_num = num;
524
525         return num;
526 }
527
528 gint folder_item_move_msgs_with_dest(FolderItem *dest, GSList *msglist)
529 {
530         Folder *folder;
531         gint num;
532
533         g_return_val_if_fail(dest != NULL, -1);
534         g_return_val_if_fail(msglist != NULL, -1);
535
536         folder = dest->folder;
537         if (dest->last_num < 0) folder->scan(folder, dest);
538
539         num = folder->move_msgs_with_dest(folder, dest, msglist);
540         if (num > 0) dest->last_num = num;
541
542         return num;
543 }
544
545 gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo)
546 {
547         Folder *folder;
548         gint num;
549
550         g_return_val_if_fail(dest != NULL, -1);
551         g_return_val_if_fail(msginfo != NULL, -1);
552
553         folder = dest->folder;
554         if (dest->last_num < 0) folder->scan(folder, dest);
555
556         num = folder->copy_msg(folder, dest, msginfo);
557         if (num > 0) dest->last_num = num;
558
559         return num;
560 }
561
562 gint folder_item_copy_msgs_with_dest(FolderItem *dest, GSList *msglist)
563 {
564         Folder *folder;
565         gint num;
566
567         g_return_val_if_fail(dest != NULL, -1);
568         g_return_val_if_fail(msglist != NULL, -1);
569
570         folder = dest->folder;
571         if (dest->last_num < 0) folder->scan(folder, dest);
572
573         num = folder->copy_msgs_with_dest(folder, dest, msglist);
574         if (num > 0) dest->last_num = num;
575
576         return num;
577 }
578
579 gint folder_item_remove_msg(FolderItem *item, gint num)
580 {
581         Folder *folder;
582
583         g_return_val_if_fail(item != NULL, -1);
584
585         folder = item->folder;
586         if (item->last_num < 0) folder->scan(folder, item);
587
588         return folder->remove_msg(folder, item, num);
589 }
590
591 gint folder_item_remove_all_msg(FolderItem *item)
592 {
593         Folder *folder;
594
595         g_return_val_if_fail(item != NULL, -1);
596
597         folder = item->folder;
598         if (item->last_num < 0) folder->scan(folder, item);
599
600         return folder->remove_all_msg(folder, item);
601 }
602
603 gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo)
604 {
605         Folder *folder;
606
607         g_return_val_if_fail(item != NULL, FALSE);
608
609         folder = item->folder;
610         return folder->is_msg_changed(folder, item, msginfo);
611 }
612
613 gchar *folder_item_get_cache_file(FolderItem *item)
614 {
615         gchar *path;
616         gchar *file;
617
618         g_return_val_if_fail(item != NULL, NULL);
619         g_return_val_if_fail(item->path != NULL, NULL);
620
621         path = folder_item_get_path(item);
622         g_return_val_if_fail(path != NULL, NULL);
623         if (!is_dir_exist(path))
624                 make_dir_hier(path);
625         file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL);
626         g_free(path);
627
628         return file;
629 }
630
631 gchar *folder_item_get_mark_file(FolderItem *item)
632 {
633         gchar *path;
634         gchar *file;
635
636         g_return_val_if_fail(item != NULL, NULL);
637         g_return_val_if_fail(item->path != NULL, NULL);
638
639         path = folder_item_get_path(item);
640         g_return_val_if_fail(path != NULL, NULL);
641         if (!is_dir_exist(path))
642                 make_dir_hier(path);
643         file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL);
644         g_free(path);
645
646         return file;
647 }
648
649
650 static void folder_init(Folder *folder, FolderType type, const gchar *name)
651 {
652         FolderItem *item;
653
654         g_return_if_fail(folder != NULL);
655
656         folder->type = type;
657         folder_set_name(folder, name);
658         folder->account = NULL;
659         folder->inbox = NULL;
660         folder->outbox = NULL;
661         folder->draft = NULL;
662         folder->queue = NULL;
663         folder->trash = NULL;
664         folder->ui_func = NULL;
665         folder->ui_func_data = NULL;
666         item = folder_item_new(name, NULL);
667         item->folder = folder;
668         folder->node = g_node_new(item);
669         folder->data = NULL;
670
671         switch (type) {
672         case F_MH:
673                 folder->get_msg_list        = mh_get_msg_list;
674                 folder->fetch_msg           = mh_fetch_msg;
675                 folder->add_msg             = mh_add_msg;
676                 folder->move_msg            = mh_move_msg;
677                 folder->move_msgs_with_dest = mh_move_msgs_with_dest;
678                 folder->copy_msg            = mh_copy_msg;
679                 folder->copy_msgs_with_dest = mh_copy_msgs_with_dest;
680                 folder->remove_msg          = mh_remove_msg;
681                 folder->remove_all_msg      = mh_remove_all_msg;
682                 folder->is_msg_changed      = mh_is_msg_changed;
683                 folder->scan                = mh_scan_folder;
684                 folder->scan_tree           = mh_scan_tree;
685                 folder->create_tree         = mh_create_tree;
686                 folder->create_folder       = mh_create_folder;
687                 folder->rename_folder       = mh_rename_folder;
688                 folder->remove_folder       = mh_remove_folder;
689                 break;
690         case F_IMAP:
691                 folder->get_msg_list        = imap_get_msg_list;
692                 folder->fetch_msg           = imap_fetch_msg;
693                 folder->move_msg            = imap_move_msg;
694                 folder->move_msgs_with_dest = imap_move_msgs_with_dest;
695                 folder->remove_msg          = imap_remove_msg;
696                 folder->remove_all_msg      = imap_remove_all_msg;
697                 folder->scan                = imap_scan_folder;
698                 folder->create_folder       = imap_create_folder;
699                 folder->remove_folder       = imap_remove_folder;               
700                 break;
701         case F_NEWS:
702                 folder->get_msg_list = news_get_article_list;
703                 folder->fetch_msg    = news_fetch_msg;
704                 folder->scan         = news_scan_group;
705                 break;
706         default:
707         }
708
709         switch (type) {
710         case F_MH:
711         case F_MBOX:
712         case F_MAILDIR:
713                 LOCAL_FOLDER(folder)->rootpath = NULL;
714                 break;
715         case F_IMAP:
716         case F_NEWS:
717                 REMOTE_FOLDER(folder)->session   = NULL;
718                 break;
719         default:
720         }
721 }
722
723 static void local_folder_destroy(LocalFolder *lfolder)
724 {
725         g_return_if_fail(lfolder != NULL);
726
727         g_free(lfolder->rootpath);
728 }
729
730 static void remote_folder_destroy(RemoteFolder *rfolder)
731 {
732         g_return_if_fail(rfolder != NULL);
733
734         if (rfolder->session)
735                 session_destroy(rfolder->session);
736 }
737
738 static void mh_folder_destroy(MHFolder *folder)
739 {
740         local_folder_destroy(LOCAL_FOLDER(folder));
741 }
742
743 static void imap_folder_destroy(IMAPFolder *folder)
744 {
745         remote_folder_destroy(REMOTE_FOLDER(folder));
746 }
747
748 static void news_folder_destroy(NewsFolder *folder)
749 {
750         remote_folder_destroy(REMOTE_FOLDER(folder));
751 }
752
753 static gboolean folder_build_tree(GNode *node, gpointer data)
754 {
755         Folder *folder = FOLDER(data);
756         FolderItem *item;
757         XMLNode *xmlnode;
758         GList *list;
759         SpecialFolderItemType stype = F_NORMAL;
760         const gchar *name = NULL;
761         const gchar *path = NULL;
762         PrefsAccount *account = NULL;
763         gint mtime = 0, new = 0, unread = 0, total = 0;
764
765         g_return_val_if_fail(node->data != NULL, FALSE);
766         if (!node->parent) return FALSE;
767
768         xmlnode = node->data;
769         if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
770                 g_warning("tag name != \"folderitem\"\n");
771                 return FALSE;
772         }
773
774         list = xmlnode->tag->attr;
775         for (; list != NULL; list = list->next) {
776                 XMLAttr *attr = list->data;
777
778                 if (!attr || !attr->name || !attr->value) continue;
779                 if (!strcmp(attr->name, "type")) {
780                         if (!strcasecmp(attr->value, "normal"))
781                                 stype = F_NORMAL;
782                         else if (!strcasecmp(attr->value, "inbox"))
783                                 stype = F_INBOX;
784                         else if (!strcasecmp(attr->value, "outbox"))
785                                 stype = F_OUTBOX;
786                         else if (!strcasecmp(attr->value, "draft"))
787                                 stype = F_DRAFT;
788                         else if (!strcasecmp(attr->value, "queue"))
789                                 stype = F_QUEUE;
790                         else if (!strcasecmp(attr->value, "trash"))
791                                 stype = F_TRASH;
792                 } else if (!strcmp(attr->name, "name"))
793                         name = attr->value;
794                 else if (!strcmp(attr->name, "path"))
795                         path = attr->value;
796                 else if (!strcmp(attr->name, "account_id")) {
797                         account = account_find_from_id(atoi(attr->value));
798                         if (!account) g_warning("account_id: %s not found\n",
799                                                 attr->value);
800                 }
801                 else if (!strcmp(attr->name, "mtime"))
802                         mtime = atoi(attr->value);
803                 else if (!strcmp(attr->name, "new"))
804                         new = atoi(attr->value);
805                 else if (!strcmp(attr->name, "unread"))
806                         unread = atoi(attr->value);
807                 else if (!strcmp(attr->name, "total"))
808                         total = atoi(attr->value);
809         }
810
811         item = folder_item_new(name, path);
812         item->stype = stype;
813         item->account = account;
814         item->mtime = mtime;
815         item->new = new;
816         item->unread = unread;
817         item->total = total;
818         item->parent = FOLDER_ITEM(node->parent->data);
819         item->folder = folder;
820         switch (stype) {
821         case F_INBOX:  folder->inbox  = item; break;
822         case F_OUTBOX: folder->outbox = item; break;
823         case F_DRAFT:  folder->draft  = item; break;
824         case F_QUEUE:  folder->queue  = item; break;
825         case F_TRASH:  folder->trash  = item; break;
826         default:
827         }
828
829         prefs_folder_item_read_config(item);
830
831         node->data = item;
832         xml_free_node(xmlnode);
833
834         return FALSE;
835 }
836
837 static gboolean folder_read_folder_func(GNode *node, gpointer data)
838 {
839         Folder *folder;
840         XMLNode *xmlnode;
841         GList *list;
842         FolderType type = F_UNKNOWN;
843         const gchar *name = NULL;
844         const gchar *path = NULL;
845         PrefsAccount *account = NULL;
846
847         if (g_node_depth(node) != 2) return FALSE;
848         g_return_val_if_fail(node->data != NULL, FALSE);
849
850         xmlnode = node->data;
851         if (strcmp2(xmlnode->tag->tag, "folder") != 0) {
852                 g_warning("tag name != \"folder\"\n");
853                 return TRUE;
854         }
855         g_node_unlink(node);
856         list = xmlnode->tag->attr;
857         for (; list != NULL; list = list->next) {
858                 XMLAttr *attr = list->data;
859
860                 if (!attr || !attr->name || !attr->value) continue;
861                 if (!strcmp(attr->name, "type")) {
862                         if (!strcasecmp(attr->value, "mh"))
863                                 type = F_MH;
864                         else if (!strcasecmp(attr->value, "mbox"))
865                                 type = F_MBOX;
866                         else if (!strcasecmp(attr->value, "maildir"))
867                                 type = F_MAILDIR;
868                         else if (!strcasecmp(attr->value, "imap"))
869                                 type = F_IMAP;
870                         else if (!strcasecmp(attr->value, "news"))
871                                 type = F_NEWS;
872                 } else if (!strcmp(attr->name, "name"))
873                         name = attr->value;
874                 else if (!strcmp(attr->name, "path"))
875                         path = attr->value;
876                 else if (!strcmp(attr->name, "account_id")) {
877                         account = account_find_from_id(atoi(attr->value));
878                         if (!account) g_warning("account_id: %s not found\n",
879                                                 attr->value);
880                 }
881         }
882
883         folder = folder_new(type, name, path);
884         g_return_val_if_fail(folder != NULL, FALSE);
885         folder->account = account;
886         if (account && (type == F_IMAP || type == F_NEWS))
887                 account->folder = REMOTE_FOLDER(folder);
888         node->data = folder->node->data;
889         g_node_destroy(folder->node);
890         folder->node = node;
891         folder_add(folder);
892
893         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
894                         folder_build_tree, folder);
895
896         return FALSE;
897 }
898
899 static gchar *folder_get_list_path(void)
900 {
901         static gchar *filename = NULL;
902
903         if (!filename)
904                 filename =  g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
905                                         FOLDER_LIST, NULL);
906
907         return filename;
908 }
909
910 static void folder_write_list_recursive(GNode *node, gpointer data)
911 {
912         FILE *fp = (FILE *)data;
913         FolderItem *item = FOLDER_ITEM(node->data);
914         gint i, depth;
915         static gchar *folder_type_str[] = {"mh", "mbox", "maildir", "imap",
916                                            "news", "unknown"};
917         static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox",
918                                                  "draft", "queue", "trash"};
919
920         g_return_if_fail(item != NULL);
921
922         depth = g_node_depth(node);
923         for (i = 0; i < depth; i++)
924                 fputs("    ", fp);
925         if (depth == 1) {
926                 Folder *folder = item->folder;
927
928                 fprintf(fp, "<folder type=\"%s\"", folder_type_str[folder->type]);
929                 if (folder->name) {
930                         fputs(" name=\"", fp);
931                         xml_file_put_escape_str(fp, folder->name);
932                         fputs("\"", fp);
933                 }
934                 if (folder->type == F_MH) {
935                         fputs(" path=\"", fp);
936                         xml_file_put_escape_str
937                                 (fp, LOCAL_FOLDER(folder)->rootpath);
938                         fputs("\"", fp);
939                 }
940                 if (folder->account)
941                         fprintf(fp, " account_id=\"%d\"",
942                                 folder->account->account_id);
943         } else {
944                 fprintf(fp, "<folderitem type=\"%s\"",
945                         folder_item_stype_str[item->stype]);
946                 if (item->name) {
947                         fputs(" name=\"", fp);
948                         xml_file_put_escape_str(fp, item->name);
949                         fputs("\"", fp);
950                 }
951                 if (item->path) {
952                         fputs(" path=\"", fp);
953                         xml_file_put_escape_str(fp, item->path);
954                         fputs("\"", fp);
955                 }
956                 if (item->account)
957                         fprintf(fp, " account_id = \"%d\"",
958                                 item->account->account_id);
959                 fprintf(fp,
960                         " mtime=\"%ld\" new=\"%d\" unread=\"%d\" total=\"%d\"",
961                         item->mtime, item->new, item->unread, item->total);
962         }
963
964         if (node->children) {
965                 GNode *child;
966                 fputs(">\n", fp);
967
968                 child = node->children;
969                 while (child) {
970                         GNode *cur;
971
972                         cur = child;
973                         child = cur->next;
974                         folder_write_list_recursive(cur, data);
975                 }
976
977                 for (i = 0; i < depth; i++)
978                         fputs("    ", fp);
979                 fprintf(fp, "</%s>\n", depth == 1 ? "folder" : "folderitem");
980         } else
981                 fputs(" />\n", fp);
982 }