nntp_list(): return real error code instead of hardcoded NN_ERROR
[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                 break;
697         case F_NEWS:
698                 folder->get_msg_list = news_get_article_list;
699                 folder->fetch_msg    = news_fetch_msg;
700                 folder->scan         = news_scan_group;
701                 break;
702         default:
703         }
704
705         switch (type) {
706         case F_MH:
707         case F_MBOX:
708         case F_MAILDIR:
709                 LOCAL_FOLDER(folder)->rootpath = NULL;
710                 break;
711         case F_IMAP:
712         case F_NEWS:
713                 REMOTE_FOLDER(folder)->session   = NULL;
714                 break;
715         default:
716         }
717 }
718
719 static void local_folder_destroy(LocalFolder *lfolder)
720 {
721         g_return_if_fail(lfolder != NULL);
722
723         g_free(lfolder->rootpath);
724 }
725
726 static void remote_folder_destroy(RemoteFolder *rfolder)
727 {
728         g_return_if_fail(rfolder != NULL);
729
730         if (rfolder->session)
731                 session_destroy(rfolder->session);
732 }
733
734 static void mh_folder_destroy(MHFolder *folder)
735 {
736         local_folder_destroy(LOCAL_FOLDER(folder));
737 }
738
739 static void imap_folder_destroy(IMAPFolder *folder)
740 {
741         remote_folder_destroy(REMOTE_FOLDER(folder));
742 }
743
744 static void news_folder_destroy(NewsFolder *folder)
745 {
746         remote_folder_destroy(REMOTE_FOLDER(folder));
747 }
748
749 static gboolean folder_build_tree(GNode *node, gpointer data)
750 {
751         Folder *folder = FOLDER(data);
752         FolderItem *item;
753         XMLNode *xmlnode;
754         GList *list;
755         SpecialFolderItemType stype = F_NORMAL;
756         const gchar *name = NULL;
757         const gchar *path = NULL;
758         PrefsAccount *account = NULL;
759         gint mtime = 0, new = 0, unread = 0, total = 0;
760
761         g_return_val_if_fail(node->data != NULL, FALSE);
762         if (!node->parent) return FALSE;
763
764         xmlnode = node->data;
765         if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
766                 g_warning("tag name != \"folderitem\"\n");
767                 return FALSE;
768         }
769
770         list = xmlnode->tag->attr;
771         for (; list != NULL; list = list->next) {
772                 XMLAttr *attr = list->data;
773
774                 if (!attr || !attr->name || !attr->value) continue;
775                 if (!strcmp(attr->name, "type")) {
776                         if (!strcasecmp(attr->value, "normal"))
777                                 stype = F_NORMAL;
778                         else if (!strcasecmp(attr->value, "inbox"))
779                                 stype = F_INBOX;
780                         else if (!strcasecmp(attr->value, "outbox"))
781                                 stype = F_OUTBOX;
782                         else if (!strcasecmp(attr->value, "draft"))
783                                 stype = F_DRAFT;
784                         else if (!strcasecmp(attr->value, "queue"))
785                                 stype = F_QUEUE;
786                         else if (!strcasecmp(attr->value, "trash"))
787                                 stype = F_TRASH;
788                 } else if (!strcmp(attr->name, "name"))
789                         name = attr->value;
790                 else if (!strcmp(attr->name, "path"))
791                         path = attr->value;
792                 else if (!strcmp(attr->name, "account_id")) {
793                         account = account_find_from_id(atoi(attr->value));
794                         if (!account) g_warning("account_id: %s not found\n",
795                                                 attr->value);
796                 }
797                 else if (!strcmp(attr->name, "mtime"))
798                         mtime = atoi(attr->value);
799                 else if (!strcmp(attr->name, "new"))
800                         new = atoi(attr->value);
801                 else if (!strcmp(attr->name, "unread"))
802                         unread = atoi(attr->value);
803                 else if (!strcmp(attr->name, "total"))
804                         total = atoi(attr->value);
805         }
806
807         item = folder_item_new(name, path);
808         item->stype = stype;
809         item->account = account;
810         item->mtime = mtime;
811         item->new = new;
812         item->unread = unread;
813         item->total = total;
814         item->parent = FOLDER_ITEM(node->parent->data);
815         item->folder = folder;
816         switch (stype) {
817         case F_INBOX:  folder->inbox  = item; break;
818         case F_OUTBOX: folder->outbox = item; break;
819         case F_DRAFT:  folder->draft  = item; break;
820         case F_QUEUE:  folder->queue  = item; break;
821         case F_TRASH:  folder->trash  = item; break;
822         default:
823         }
824         node->data = item;
825         xml_free_node(xmlnode);
826
827         return FALSE;
828 }
829
830 static gboolean folder_read_folder_func(GNode *node, gpointer data)
831 {
832         Folder *folder;
833         XMLNode *xmlnode;
834         GList *list;
835         FolderType type = F_UNKNOWN;
836         const gchar *name = NULL;
837         const gchar *path = NULL;
838         PrefsAccount *account = NULL;
839
840         if (g_node_depth(node) != 2) return FALSE;
841         g_return_val_if_fail(node->data != NULL, FALSE);
842
843         xmlnode = node->data;
844         if (strcmp2(xmlnode->tag->tag, "folder") != 0) {
845                 g_warning("tag name != \"folder\"\n");
846                 return TRUE;
847         }
848         g_node_unlink(node);
849         list = xmlnode->tag->attr;
850         for (; list != NULL; list = list->next) {
851                 XMLAttr *attr = list->data;
852
853                 if (!attr || !attr->name || !attr->value) continue;
854                 if (!strcmp(attr->name, "type")) {
855                         if (!strcasecmp(attr->value, "mh"))
856                                 type = F_MH;
857                         else if (!strcasecmp(attr->value, "mbox"))
858                                 type = F_MBOX;
859                         else if (!strcasecmp(attr->value, "maildir"))
860                                 type = F_MAILDIR;
861                         else if (!strcasecmp(attr->value, "imap"))
862                                 type = F_IMAP;
863                         else if (!strcasecmp(attr->value, "news"))
864                                 type = F_NEWS;
865                 } else if (!strcmp(attr->name, "name"))
866                         name = attr->value;
867                 else if (!strcmp(attr->name, "path"))
868                         path = attr->value;
869                 else if (!strcmp(attr->name, "account_id")) {
870                         account = account_find_from_id(atoi(attr->value));
871                         if (!account) g_warning("account_id: %s not found\n",
872                                                 attr->value);
873                 }
874         }
875
876         folder = folder_new(type, name, path);
877         g_return_val_if_fail(folder != NULL, FALSE);
878         folder->account = account;
879         if (account && (type == F_IMAP || type == F_NEWS))
880                 account->folder = REMOTE_FOLDER(folder);
881         node->data = folder->node->data;
882         g_node_destroy(folder->node);
883         folder->node = node;
884         folder_add(folder);
885
886         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
887                         folder_build_tree, folder);
888
889         return FALSE;
890 }
891
892 static gchar *folder_get_list_path(void)
893 {
894         static gchar *filename = NULL;
895
896         if (!filename)
897                 filename =  g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
898                                         FOLDER_LIST, NULL);
899
900         return filename;
901 }
902
903 static void folder_write_list_recursive(GNode *node, gpointer data)
904 {
905         FILE *fp = (FILE *)data;
906         FolderItem *item = FOLDER_ITEM(node->data);
907         gint i, depth;
908         static gchar *folder_type_str[] = {"mh", "mbox", "maildir", "imap",
909                                            "news", "unknown"};
910         static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox",
911                                                  "draft", "queue", "trash"};
912
913         g_return_if_fail(item != NULL);
914
915         depth = g_node_depth(node);
916         for (i = 0; i < depth; i++)
917                 fputs("    ", fp);
918         if (depth == 1) {
919                 Folder *folder = item->folder;
920
921                 fprintf(fp, "<folder type=\"%s\"", folder_type_str[folder->type]);
922                 if (folder->name) {
923                         fputs(" name=\"", fp);
924                         xml_file_put_escape_str(fp, folder->name);
925                         fputs("\"", fp);
926                 }
927                 if (folder->type == F_MH) {
928                         fputs(" path=\"", fp);
929                         xml_file_put_escape_str
930                                 (fp, LOCAL_FOLDER(folder)->rootpath);
931                         fputs("\"", fp);
932                 }
933                 if (folder->account)
934                         fprintf(fp, " account_id=\"%d\"",
935                                 folder->account->account_id);
936         } else {
937                 fprintf(fp, "<folderitem type=\"%s\"",
938                         folder_item_stype_str[item->stype]);
939                 if (item->name) {
940                         fputs(" name=\"", fp);
941                         xml_file_put_escape_str(fp, item->name);
942                         fputs("\"", fp);
943                 }
944                 if (item->path) {
945                         fputs(" path=\"", fp);
946                         xml_file_put_escape_str(fp, item->path);
947                         fputs("\"", fp);
948                 }
949                 if (item->account)
950                         fprintf(fp, " account_id = \"%d\"",
951                                 item->account->account_id);
952                 fprintf(fp,
953                         " mtime=\"%ld\" new=\"%d\" unread=\"%d\" total=\"%d\"",
954                         item->mtime, item->new, item->unread, item->total);
955         }
956
957         if (node->children) {
958                 GNode *child;
959                 fputs(">\n", fp);
960
961                 child = node->children;
962                 while (child) {
963                         GNode *cur;
964
965                         cur = child;
966                         child = cur->next;
967                         folder_write_list_recursive(cur, data);
968                 }
969
970                 for (i = 0; i < depth; i++)
971                         fputs("    ", fp);
972                 fprintf(fp, "</%s>\n", depth == 1 ? "folder" : "folderitem");
973         } else
974                 fputs(" />\n", fp);
975 }