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