add persistence for collapsed / expanded folder trees
[claws.git] / src / folder.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 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 "mbox_folder.h"
37 #include "utils.h"
38 #include "xml.h"
39 #include "codeconv.h"
40 #include "prefs.h"
41 #include "account.h"
42 #include "prefs_account.h"
43 #include "mbox_folder.h"
44
45 static GList *folder_list = NULL;
46
47 static void folder_init         (Folder         *folder,
48                                  FolderType      type,
49                                  const gchar    *name);
50
51 static void local_folder_destroy        (LocalFolder    *lfolder);
52 static void remote_folder_destroy       (RemoteFolder   *rfolder);
53 static void mh_folder_destroy           (MHFolder       *folder);
54 static void mbox_folder_destroy         (MboxFolder     *folder);
55 static void imap_folder_destroy         (IMAPFolder     *folder);
56 static void news_folder_destroy         (NewsFolder     *folder);
57
58 static gboolean folder_read_folder_func (GNode          *node,
59                                          gpointer        data);
60 static gchar *folder_get_list_path      (void);
61 static void folder_write_list_recursive (GNode          *node,
62                                          gpointer        data);
63
64
65 Folder *folder_new(FolderType type, const gchar *name, const gchar *path)
66 {
67         Folder *folder = NULL;
68
69         name = name ? name : path;
70         switch (type) {
71         case F_MBOX:
72                 folder = mbox_folder_new(name, path);
73                 break;
74         case F_MH:
75                 folder = mh_folder_new(name, path);
76                 break;
77         case F_IMAP:
78                 folder = imap_folder_new(name, path);
79                 break;
80         case F_NEWS:
81                 folder = news_folder_new(name, path);
82                 break;
83         default:
84                 return NULL;
85         }
86
87         return folder;
88 }
89
90 Folder *mh_folder_new(const gchar *name, const gchar *path)
91 {
92         Folder *folder;
93
94         folder = (Folder *)g_new0(MHFolder, 1);
95         folder_init(folder, F_MH, name);
96         LOCAL_FOLDER(folder)->rootpath = g_strdup(path);
97
98         return folder;
99 }
100
101 Folder *mbox_folder_new(const gchar *name, const gchar *path)
102 {
103         /* implementing */
104         Folder *folder;
105
106         folder = (Folder *)g_new0(MboxFolder, 1);
107         folder_init(folder, F_MBOX, name);
108         LOCAL_FOLDER(folder)->rootpath = g_strdup(path);
109
110         return folder;
111 }
112
113 Folder *maildir_folder_new(const gchar *name, const gchar *path)
114 {
115         /* not yet implemented */
116         return NULL;
117 }
118
119 Folder *imap_folder_new(const gchar *name, const gchar *path)
120 {
121         Folder *folder;
122
123         folder = (Folder *)g_new0(IMAPFolder, 1);
124         folder_init(folder, F_IMAP, name);
125
126         return folder;
127 }
128
129 Folder *news_folder_new(const gchar *name, const gchar *path)
130 {
131         Folder *folder;
132
133         folder = (Folder *)g_new0(NewsFolder, 1);
134         folder_init(folder, F_NEWS, name);
135
136         return folder;
137 }
138
139 FolderItem *folder_item_new(const gchar *name, const gchar *path)
140 {
141         FolderItem *item;
142
143         item = g_new0(FolderItem, 1);
144
145         item->stype = F_NORMAL;
146         item->name = g_strdup(name);
147         item->path = g_strdup(path);
148         item->account = NULL;
149         item->mtime = 0;
150         item->new = 0;
151         item->unread = 0;
152         item->total = 0;
153         item->last_num = -1;
154         item->no_sub = FALSE;
155         item->no_select = FALSE;
156         item->collapsed = FALSE; /* default is open */
157         item->parent = NULL;
158         item->folder = NULL;
159         item->data = NULL;
160
161         item->prefs = prefs_folder_item_new();
162
163         return item;
164 }
165
166 void folder_item_append(FolderItem *parent, FolderItem *item)
167 {
168         GNode *node;
169
170         g_return_if_fail(parent != NULL);
171         g_return_if_fail(parent->folder != NULL);
172         g_return_if_fail(item != NULL);
173
174         node = parent->folder->node;
175         node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, parent);
176         g_return_if_fail(node != NULL);
177
178         item->parent = parent;
179         item->folder = parent->folder;
180         g_node_append_data(node, item);
181 }
182
183 void folder_item_remove(FolderItem *item)
184 {
185         GNode *node;
186
187         g_return_if_fail(item != NULL);
188         g_return_if_fail(item->folder != NULL);
189
190         node = item->folder->node;
191         node = g_node_find(node, G_PRE_ORDER, G_TRAVERSE_ALL, item);
192         g_return_if_fail(node != NULL);
193
194         /* TODO: free all FolderItem's first */
195         if (item->folder->node == node)
196                 item->folder->node = NULL;
197         g_node_destroy(node);
198 }
199
200 void folder_item_destroy(FolderItem *item)
201 {
202         g_return_if_fail(item != NULL);
203
204         g_free(item->name);
205         g_free(item->path);
206         g_free(item);
207 }
208
209 void folder_set_ui_func(Folder *folder, FolderUIFunc func, gpointer data)
210 {
211         g_return_if_fail(folder != NULL);
212
213         folder->ui_func = func;
214         folder->ui_func_data = data;
215 }
216
217 void folder_set_name(Folder *folder, const gchar *name)
218 {
219         g_return_if_fail(folder != NULL);
220
221         g_free(folder->name);
222         folder->name = name ? g_strdup(name) : NULL;
223         if (folder->node && folder->node->data) {
224                 FolderItem *item = (FolderItem *)folder->node->data;
225
226                 g_free(item->name);
227                 item->name = name ? g_strdup(name) : NULL;
228         }
229 }
230
231 void folder_destroy(Folder *folder)
232 {
233         g_return_if_fail(folder != NULL);
234
235         folder_list = g_list_remove(folder_list, folder);
236
237         switch (folder->type) {
238         case F_MH:
239                 mh_folder_destroy(MH_FOLDER(folder));
240                 break;
241         case F_MBOX:
242                 mbox_folder_destroy(MBOX_FOLDER(folder));
243                 break;
244         case F_IMAP:
245                 imap_folder_destroy(IMAP_FOLDER(folder));
246                 break;
247         case F_NEWS:
248                 news_folder_destroy(NEWS_FOLDER(folder));
249                 break;
250         default:
251                 break;
252         }
253
254         folder_tree_destroy(folder);
255         g_free(folder->name);
256         g_free(folder);
257 }
258
259 void folder_tree_destroy(Folder *folder)
260 {
261         /* TODO: destroy all FolderItem before */
262         g_node_destroy(folder->node);
263
264         folder->inbox = NULL;
265         folder->outbox = NULL;
266         folder->draft = NULL;
267         folder->queue = NULL;
268         folder->trash = NULL;
269         folder->node = NULL;
270 }
271
272 void folder_add(Folder *folder)
273 {
274         Folder *cur_folder;
275         GList *cur;
276         gint i;
277
278         g_return_if_fail(folder != NULL);
279
280         for (i = 0, cur = folder_list; cur != NULL; cur = cur->next, i++) {
281                 cur_folder = FOLDER(cur->data);
282                 if (folder->type == F_MH) {
283                         if (cur_folder->type != F_MH) break;
284                 } else if (folder->type == F_MBOX) {
285                         if (cur_folder->type != F_MH &&
286                             cur_folder->type != F_MBOX) break;
287                 } else if (folder->type == F_IMAP) {
288                         if (cur_folder->type != F_MH &&
289                             cur_folder->type != F_MBOX &&
290                             cur_folder->type != F_IMAP) break;
291                 } else if (folder->type == F_NEWS) {
292                         if (cur_folder->type != F_MH &&
293                             cur_folder->type != F_MBOX &&
294                             cur_folder->type != F_IMAP &&
295                             cur_folder->type != F_NEWS) break;
296                 }
297         }
298
299         folder_list = g_list_insert(folder_list, folder, i);
300 }
301
302 GList *folder_get_list(void)
303 {
304         return folder_list;
305 }
306
307 gint folder_read_list(void)
308 {
309         GNode *node;
310         XMLNode *xmlnode;
311
312         node = xml_parse_file(folder_get_list_path());
313         if (!node) return -1;
314
315         xmlnode = node->data;
316         if (strcmp2(xmlnode->tag->tag, "folderlist") != 0) {
317                 g_warning("wrong folder list\n");
318                 xml_free_tree(node);
319                 return -1;
320         }
321
322         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, 2,
323                         folder_read_folder_func, NULL);
324
325         xml_free_tree(node);
326         if (folder_list)
327                 return 0;
328         else
329                 return -1;
330 }
331
332 void folder_write_list(void)
333 {
334         GList *list;
335         Folder *folder;
336         gchar *path;
337         PrefFile *pfile;
338
339         path = folder_get_list_path();
340         if ((pfile = prefs_write_open(path)) == NULL) return;
341
342         fprintf(pfile->fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
343                 conv_get_current_charset_str());
344         fputs("\n<folderlist>\n", pfile->fp);
345
346         for (list = folder_list; list != NULL; list = list->next) {
347                 folder = list->data;
348                 folder_write_list_recursive(folder->node, pfile->fp);
349         }
350
351         fputs("</folderlist>\n", pfile->fp);
352
353         if (prefs_write_close(pfile) < 0)
354                 g_warning("failed to write folder list.\n");
355 }
356
357 Folder *folder_find_from_path(const gchar *path)
358 {
359         GList *list;
360         Folder *folder;
361
362         for (list = folder_list; list != NULL; list = list->next) {
363                 folder = list->data;
364                 if ((folder->type == F_MH || folder->type == F_MBOX) &&
365                     !path_cmp(LOCAL_FOLDER(folder)->rootpath, path))
366                         return folder;
367         }
368
369         return NULL;
370 }
371
372 static gboolean folder_item_find_func(GNode *node, gpointer data)
373 {
374         FolderItem *item = node->data;
375         gpointer *d = data;
376         const gchar *path = d[0];
377
378         if (path_cmp(path, item->path) != 0)
379                 return FALSE;
380
381         d[1] = item;
382
383         return TRUE;
384 }
385
386 FolderItem *folder_find_item_from_path(const gchar *path)
387 {
388         Folder *folder;
389         gpointer d[2];
390
391         folder = folder_get_default_folder();
392         g_return_val_if_fail(folder != NULL, NULL);
393
394         d[0] = (gpointer)path;
395         d[1] = NULL;
396         g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
397                         folder_item_find_func, d);
398         return d[1];
399 }
400
401 Folder *folder_get_default_folder(void)
402 {
403         return FOLDER(folder_list->data);
404 }
405
406 FolderItem *folder_get_default_inbox(void)
407 {
408         Folder *folder;
409
410         folder = FOLDER(folder_list->data);
411         g_return_val_if_fail(folder != NULL, NULL);
412         return folder->inbox;
413 }
414
415 FolderItem *folder_get_default_outbox(void)
416 {
417         Folder *folder;
418
419         folder = FOLDER(folder_list->data);
420         g_return_val_if_fail(folder != NULL, NULL);
421         return folder->outbox;
422 }
423
424 FolderItem *folder_get_default_draft(void)
425 {
426         Folder *folder;
427
428         folder = FOLDER(folder_list->data);
429         g_return_val_if_fail(folder != NULL, NULL);
430         return folder->draft;
431 }
432
433 FolderItem *folder_get_default_queue(void)
434 {
435         Folder *folder;
436
437         folder = FOLDER(folder_list->data);
438         g_return_val_if_fail(folder != NULL, NULL);
439         return folder->queue;
440 }
441
442 FolderItem *folder_get_default_trash(void)
443 {
444         Folder *folder;
445
446         folder = FOLDER(folder_list->data);
447         g_return_val_if_fail(folder != NULL, NULL);
448         return folder->trash;
449 }
450
451 gchar *folder_item_get_path(FolderItem *item)
452 {
453         gchar *folder_path;
454         gchar *path;
455
456         g_return_val_if_fail(item != NULL, NULL);
457
458         if (FOLDER_TYPE(item->folder) == F_MH)
459                 folder_path = g_strdup(LOCAL_FOLDER(item->folder)->rootpath);
460         else if (FOLDER_TYPE(item->folder) == F_MBOX) {
461                 path = mbox_get_virtual_path(item);
462                 if (path == NULL)
463                         return NULL;
464                 folder_path = g_strconcat(get_mbox_cache_dir(),
465                                           G_DIR_SEPARATOR_S, path, NULL);
466                 g_free(path);
467
468                 return folder_path;
469         }
470         else if (FOLDER_TYPE(item->folder) == F_IMAP) {
471                 g_return_val_if_fail(item->folder->account != NULL, NULL);
472                 folder_path = g_strconcat(get_imap_cache_dir(),
473                                           G_DIR_SEPARATOR_S,
474                                           item->folder->account->recv_server,
475                                           G_DIR_SEPARATOR_S,
476                                           item->folder->account->userid,
477                                           NULL);
478         } else if (FOLDER_TYPE(item->folder) == F_NEWS) {
479                 g_return_val_if_fail(item->folder->account != NULL, NULL);
480                 folder_path = g_strconcat(get_news_cache_dir(),
481                                           G_DIR_SEPARATOR_S,
482                                           item->folder->account->nntp_server,
483                                           NULL);
484         } else
485                 return NULL;
486
487         g_return_val_if_fail(folder_path != NULL, NULL);
488
489         if (folder_path[0] == G_DIR_SEPARATOR) {
490                 if (item->path)
491                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
492                                            item->path, NULL);
493                 else
494                         path = g_strdup(folder_path);
495         } else {
496                 if (item->path)
497                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
498                                            folder_path, G_DIR_SEPARATOR_S,
499                                            item->path, NULL);
500                 else
501                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
502                                            folder_path, NULL);
503         }
504
505         g_free(folder_path);
506         return path;
507 }
508
509 void folder_item_scan(FolderItem *item)
510 {
511         Folder *folder;
512
513         g_return_if_fail(item != NULL);
514
515         folder = item->folder;
516
517         g_return_if_fail(folder->scan != NULL);
518
519         folder->scan(folder, item);
520 }
521
522 static void folder_item_scan_foreach_func(gpointer key, gpointer val,
523                                           gpointer data)
524 {
525         folder_item_scan(FOLDER_ITEM(key));
526 }
527
528 void folder_item_scan_foreach(GHashTable *table)
529 {
530         g_hash_table_foreach(table, folder_item_scan_foreach_func, NULL);
531 }
532
533 gchar *folder_item_fetch_msg(FolderItem *item, gint num)
534 {
535         Folder *folder;
536
537         g_return_val_if_fail(item != NULL, NULL);
538
539         folder = item->folder;
540
541         g_return_val_if_fail(folder->scan != NULL, NULL);
542         g_return_val_if_fail(folder->fetch_msg != NULL, NULL);
543
544         if (item->last_num < 0) folder->scan(folder, item);
545
546         return folder->fetch_msg(folder, item, num);
547 }
548
549 gint folder_item_add_msg(FolderItem *dest, const gchar *file,
550                          gboolean remove_source)
551 {
552         Folder *folder;
553         gint num;
554
555         g_return_val_if_fail(dest != NULL, -1);
556         g_return_val_if_fail(file != NULL, -1);
557
558         folder = dest->folder;
559
560         g_return_val_if_fail(folder->scan != NULL, -1);
561         g_return_val_if_fail(folder->add_msg != NULL, -1);
562
563         if (dest->last_num < 0) folder->scan(folder, dest);
564
565         num = folder->add_msg(folder, dest, file, remove_source);
566         if (num > 0) dest->last_num = num;
567
568         return num;
569 }
570
571 /*
572 gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
573 {
574         Folder *folder;
575         gint num;
576
577         g_return_val_if_fail(dest != NULL, -1);
578         g_return_val_if_fail(msginfo != NULL, -1);
579
580         folder = dest->folder;
581
582         g_return_val_if_fail(folder->scan != NULL, -1);
583         g_return_val_if_fail(folder->move_msg != NULL, -1);
584
585         if (dest->last_num < 0) folder->scan(folder, dest);
586
587         num = folder->move_msg(folder, dest, msginfo);
588         if (num > 0) dest->last_num = num;
589
590         return num;
591 }
592 */
593
594 gint folder_item_move_msg(FolderItem *dest, MsgInfo *msginfo)
595 {
596         Folder *folder;
597         gint num;
598         gchar * filename;
599         Folder * src_folder;
600
601         g_return_val_if_fail(dest != NULL, -1);
602         g_return_val_if_fail(msginfo != NULL, -1);
603
604         folder = dest->folder;
605
606         g_return_val_if_fail(folder->scan != NULL, -1);
607         g_return_val_if_fail(folder->remove_msg != NULL, -1);
608         g_return_val_if_fail(folder->copy_msg != NULL, -1);
609
610         if (dest->last_num < 0) folder->scan(folder, dest);
611
612         src_folder = msginfo->folder->folder;
613
614         num = folder->copy_msg(folder, dest, msginfo);
615         if (num != -1)
616                 src_folder->remove_msg(src_folder,
617                                        msginfo->folder,
618                                        msginfo->msgnum);
619         
620         if (folder->finished_copy)
621                 folder->finished_copy(folder, dest);
622
623         src_folder = msginfo->folder->folder;
624
625         if (msginfo->folder && src_folder->scan)
626                 src_folder->scan(src_folder, msginfo->folder);
627         folder->scan(folder, dest);
628
629         return num;
630 }
631
632 /*
633 gint folder_item_move_msgs_with_dest(FolderItem *dest, GSList *msglist)
634 {
635         Folder *folder;
636         gint num;
637
638         g_return_val_if_fail(dest != NULL, -1);
639         g_return_val_if_fail(msglist != NULL, -1);
640
641         folder = dest->folder;
642
643         g_return_val_if_fail(folder->scan != NULL, -1);
644         g_return_val_if_fail(folder->move_msgs_with_dest != NULL, -1);
645
646         if (dest->last_num < 0) folder->scan(folder, dest);
647
648         num = folder->move_msgs_with_dest(folder, dest, msglist);
649         if (num > 0) dest->last_num = num;
650
651         return num;
652 }
653 */
654
655 gint folder_item_move_msgs_with_dest(FolderItem *dest, GSList *msglist)
656 {
657         Folder *folder;
658         FolderItem * item;
659         GSList * l;
660         gchar * filename;
661
662         g_return_val_if_fail(dest != NULL, -1);
663         g_return_val_if_fail(msglist != NULL, -1);
664
665         folder = dest->folder;
666
667         g_return_val_if_fail(folder->scan != NULL, -1);
668         g_return_val_if_fail(folder->copy_msg != NULL, -1);
669         g_return_val_if_fail(folder->remove_msg != NULL, -1);
670
671         if (dest->last_num < 0) folder->scan(folder, dest);
672
673         item = NULL;
674         for(l = msglist ; l != NULL ; l = g_slist_next(l)) {
675                 MsgInfo * msginfo = (MsgInfo *) l->data;
676
677                 if (!item && msginfo->folder != NULL)
678                         item = msginfo->folder;
679
680                 if (folder->copy_msg(folder, dest, msginfo) != -1)
681                         item->folder->remove_msg(item->folder,
682                                                  msginfo->folder,
683                                                  msginfo->msgnum);
684         }
685
686         if (folder->finished_copy)
687                 folder->finished_copy(folder, dest);
688
689         if (item && item->folder->scan)
690                 item->folder->scan(item->folder, item);
691         folder->scan(folder, dest);
692
693         return dest->last_num;
694 }
695
696 /*
697 gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo)
698 {
699         Folder *folder;
700         gint num;
701
702         g_return_val_if_fail(dest != NULL, -1);
703         g_return_val_if_fail(msginfo != NULL, -1);
704
705         folder = dest->folder;
706
707         g_return_val_if_fail(folder->scan != NULL, -1);
708         g_return_val_if_fail(folder->copy_msg != NULL, -1);
709
710         if (dest->last_num < 0) folder->scan(folder, dest);
711
712         num = folder->copy_msg(folder, dest, msginfo);
713         if (num > 0) dest->last_num = num;
714
715         return num;
716 }
717 */
718
719 gint folder_item_copy_msg(FolderItem *dest, MsgInfo *msginfo)
720 {
721         Folder *folder;
722         gint num;
723         gchar * filename;
724         Folder * src_folder;
725
726         g_return_val_if_fail(dest != NULL, -1);
727         g_return_val_if_fail(msginfo != NULL, -1);
728
729         folder = dest->folder;
730
731         g_return_val_if_fail(folder->scan != NULL, -1);
732         g_return_val_if_fail(folder->copy_msg != NULL, -1);
733
734         if (dest->last_num < 0) folder->scan(folder, dest);
735         
736         num = folder->copy_msg(folder, dest, msginfo);
737
738         if (folder->finished_copy)
739                 folder->finished_copy(folder, dest);
740
741         folder->scan(folder, dest);
742
743         return num;
744 }
745
746 /*
747 gint folder_item_copy_msgs_with_dest(FolderItem *dest, GSList *msglist)
748 {
749         Folder *folder;
750         gint num;
751
752         g_return_val_if_fail(dest != NULL, -1);
753         g_return_val_if_fail(msglist != NULL, -1);
754
755         folder = dest->folder;
756
757         g_return_val_if_fail(folder->scan != NULL, -1);
758         g_return_val_if_fail(folder->copy_msgs_with_dest != NULL, -1);
759
760         if (dest->last_num < 0) folder->scan(folder, dest);
761
762         num = folder->copy_msgs_with_dest(folder, dest, msglist);
763         if (num > 0) dest->last_num = num;
764
765         return num;
766 }
767 */
768
769 gint folder_item_copy_msgs_with_dest(FolderItem *dest, GSList *msglist)
770 {
771         Folder *folder;
772         gint num;
773         GSList * l;
774         gchar * filename;
775
776         g_return_val_if_fail(dest != NULL, -1);
777         g_return_val_if_fail(msglist != NULL, -1);
778
779         folder = dest->folder;
780  
781         g_return_val_if_fail(folder->scan != NULL, -1);
782         g_return_val_if_fail(folder->copy_msg != NULL, -1);
783
784         if (dest->last_num < 0) folder->scan(folder, dest);
785
786         for(l = msglist ; l != NULL ; l = g_slist_next(l)) {
787                 MsgInfo * msginfo = (MsgInfo *) l->data;
788
789                 folder->copy_msg(folder, dest, msginfo);
790         }
791
792         if (folder->finished_copy)
793                 folder->finished_copy(folder, dest);
794
795         folder->scan(folder, dest);
796
797         return dest->last_num;
798 }
799
800 gint folder_item_remove_msg(FolderItem *item, gint num)
801 {
802         Folder *folder;
803         gint result;
804
805         g_return_val_if_fail(item != NULL, -1);
806
807         folder = item->folder;
808
809         g_return_val_if_fail(folder->scan != NULL, -1);
810         g_return_val_if_fail(folder->remove_msg != NULL, -1);
811
812         if (folder->finished_remove)
813                 folder->finished_remove(folder, item);
814
815         result = folder->remove_msg(folder, item, num);
816
817         if (item->last_num < 0) folder->scan(folder, item);
818
819         if (result == 0){
820                 if (folder->finished_remove)
821                         folder->finished_remove(folder, item);
822         }
823
824         return result;
825 }
826
827 gint folder_item_remove_all_msg(FolderItem *item)
828 {
829         Folder *folder;
830         gint result;
831
832         g_return_val_if_fail(item != NULL, -1);
833
834         folder = item->folder;
835
836         g_return_val_if_fail(folder->scan != NULL, -1);
837         g_return_val_if_fail(folder->remove_all_msg != NULL, -1);
838
839         if (item->last_num < 0) folder->scan(folder, item);
840
841         result = folder->remove_all_msg(folder, item);
842
843         if (result == 0){
844                 if (folder->finished_remove)
845                         folder->finished_remove(folder, item);
846         }
847
848         return result;
849 }
850
851 gboolean folder_item_is_msg_changed(FolderItem *item, MsgInfo *msginfo)
852 {
853         Folder *folder;
854
855         g_return_val_if_fail(item != NULL, FALSE);
856
857         folder = item->folder;
858
859         g_return_val_if_fail(folder->is_msg_changed != NULL, -1);
860
861         return folder->is_msg_changed(folder, item, msginfo);
862 }
863
864 gchar *folder_item_get_cache_file(FolderItem *item)
865 {
866         gchar *path;
867         gchar *file;
868
869         g_return_val_if_fail(item != NULL, NULL);
870         g_return_val_if_fail(item->path != NULL, NULL);
871
872         path = folder_item_get_path(item);
873         g_return_val_if_fail(path != NULL, NULL);
874         if (!is_dir_exist(path))
875                 make_dir_hier(path);
876         file = g_strconcat(path, G_DIR_SEPARATOR_S, CACHE_FILE, NULL);
877         g_free(path);
878
879         return file;
880 }
881
882 gchar *folder_item_get_mark_file(FolderItem *item)
883 {
884         gchar *path;
885         gchar *file;
886
887         g_return_val_if_fail(item != NULL, NULL);
888         g_return_val_if_fail(item->path != NULL, NULL);
889
890         path = folder_item_get_path(item);
891         g_return_val_if_fail(path != NULL, NULL);
892         if (!is_dir_exist(path))
893                 make_dir_hier(path);
894         file = g_strconcat(path, G_DIR_SEPARATOR_S, MARK_FILE, NULL);
895         g_free(path);
896
897         return file;
898 }
899
900
901 static void folder_init(Folder *folder, FolderType type, const gchar *name)
902 {
903         FolderItem *item;
904
905         g_return_if_fail(folder != NULL);
906
907         folder->type = type;
908         folder_set_name(folder, name);
909         folder->account = NULL;
910         folder->inbox = NULL;
911         folder->outbox = NULL;
912         folder->draft = NULL;
913         folder->queue = NULL;
914         folder->trash = NULL;
915         folder->ui_func = NULL;
916         folder->ui_func_data = NULL;
917         item = folder_item_new(name, NULL);
918         item->folder = folder;
919         folder->node = g_node_new(item);
920         folder->data = NULL;
921
922         switch (type) {
923         case F_MH:
924                 folder->get_msg_list        = mh_get_msg_list;
925                 folder->fetch_msg           = mh_fetch_msg;
926                 folder->add_msg             = mh_add_msg;
927                 /*
928                 folder->move_msg            = mh_move_msg;
929                 folder->move_msgs_with_dest = mh_move_msgs_with_dest;
930                 folder->copy_msg            = mh_copy_msg;
931                 folder->copy_msgs_with_dest = mh_copy_msgs_with_dest;
932                 */
933                 folder->copy_msg            = mh_copy_msg;
934                 folder->remove_msg          = mh_remove_msg;
935                 folder->remove_all_msg      = mh_remove_all_msg;
936                 folder->is_msg_changed      = mh_is_msg_changed;
937                 folder->scan                = mh_scan_folder;
938                 folder->scan_tree           = mh_scan_tree;
939                 folder->create_tree         = mh_create_tree;
940                 folder->create_folder       = mh_create_folder;
941                 folder->rename_folder       = mh_rename_folder;
942                 folder->remove_folder       = mh_remove_folder;
943                 break;
944         case F_IMAP:
945                 folder->get_msg_list        = imap_get_msg_list;
946                 folder->fetch_msg           = imap_fetch_msg;
947                 folder->add_msg             = imap_add_msg;
948                 folder->move_msg            = imap_move_msg;
949                 folder->move_msgs_with_dest = imap_move_msgs_with_dest;
950                 folder->copy_msg            = imap_copy_msg;
951                 folder->copy_msgs_with_dest = imap_copy_msgs_with_dest;
952                 folder->remove_msg          = imap_remove_msg;
953                 folder->remove_all_msg      = imap_remove_all_msg;
954                 folder->scan                = imap_scan_folder;
955                 folder->scan_tree           = imap_scan_tree;
956                 folder->create_tree         = imap_create_tree;
957                 folder->create_folder       = imap_create_folder;
958                 folder->remove_folder       = imap_remove_folder;
959                 break;
960         case F_NEWS:
961                 folder->get_msg_list        = news_get_article_list;
962                 folder->fetch_msg           = news_fetch_msg;
963                 folder->scan                = news_scan_group;
964                 break;
965         case F_MBOX:
966                 folder->get_msg_list        = mbox_get_msg_list;
967                 folder->fetch_msg           = mbox_fetch_msg;
968                 folder->scan                = mbox_scan_folder;
969                 folder->add_msg             = mbox_add_msg;
970                 folder->remove_all_msg      = mbox_remove_all_msg;
971                 folder->remove_msg          = mbox_remove_msg;
972                 /*
973                 folder->move_msg            = mbox_move_msg;
974                 folder->move_msgs_with_dest = mbox_move_msgs_with_dest;
975                 folder->copy_msg            = mbox_copy_msg;
976                 folder->copy_msgs_with_dest = mbox_copy_msgs_with_dest;
977                 */
978                 folder->copy_msg            = mbox_copy_msg;
979
980                 folder->create_tree         = mbox_create_tree;
981                 folder->create_folder       = mbox_create_folder;
982                 folder->rename_folder       = mbox_rename_folder;
983                 folder->remove_folder       = mbox_remove_folder;
984
985                 folder->update_mark         = mbox_update_mark;
986                 folder->change_flags        = mbox_change_flags;
987                 folder->finished_copy       = mbox_finished_copy;
988
989                 break;
990         default:
991                 break;
992         }
993
994         switch (type) {
995         case F_MH:
996         case F_MBOX:
997         case F_MAILDIR:
998                 LOCAL_FOLDER(folder)->rootpath = NULL;
999                 break;
1000         case F_IMAP:
1001         case F_NEWS:
1002                 REMOTE_FOLDER(folder)->session = NULL;
1003                 break;
1004         default:
1005                 break;
1006         }
1007 }
1008
1009 static void local_folder_destroy(LocalFolder *lfolder)
1010 {
1011         g_return_if_fail(lfolder != NULL);
1012
1013         g_free(lfolder->rootpath);
1014 }
1015
1016 static void remote_folder_destroy(RemoteFolder *rfolder)
1017 {
1018         g_return_if_fail(rfolder != NULL);
1019
1020         if (rfolder->session)
1021                 session_destroy(rfolder->session);
1022 }
1023
1024 static void mh_folder_destroy(MHFolder *folder)
1025 {
1026         local_folder_destroy(LOCAL_FOLDER(folder));
1027 }
1028
1029 static void mbox_folder_destroy(MboxFolder *folder)
1030 {
1031         local_folder_destroy(LOCAL_FOLDER(folder));
1032 }
1033
1034 static void imap_folder_destroy(IMAPFolder *folder)
1035 {
1036         remote_folder_destroy(REMOTE_FOLDER(folder));
1037 }
1038
1039 static void news_folder_destroy(NewsFolder *folder)
1040 {
1041         remote_folder_destroy(REMOTE_FOLDER(folder));
1042 }
1043
1044 static gboolean folder_build_tree(GNode *node, gpointer data)
1045 {
1046         Folder *folder = FOLDER(data);
1047         FolderItem *item;
1048         XMLNode *xmlnode;
1049         GList *list;
1050         SpecialFolderItemType stype = F_NORMAL;
1051         const gchar *name = NULL;
1052         const gchar *path = NULL;
1053         PrefsAccount *account = NULL;
1054         gboolean no_sub = FALSE, no_select = FALSE, collapsed = FALSE;
1055         gint mtime = 0, new = 0, unread = 0, total = 0;
1056
1057         g_return_val_if_fail(node->data != NULL, FALSE);
1058         if (!node->parent) return FALSE;
1059
1060         xmlnode = node->data;
1061         if (strcmp2(xmlnode->tag->tag, "folderitem") != 0) {
1062                 g_warning("tag name != \"folderitem\"\n");
1063                 return FALSE;
1064         }
1065
1066         list = xmlnode->tag->attr;
1067         for (; list != NULL; list = list->next) {
1068                 XMLAttr *attr = list->data;
1069
1070                 if (!attr || !attr->name || !attr->value) continue;
1071                 if (!strcmp(attr->name, "type")) {
1072                         if (!strcasecmp(attr->value, "normal"))
1073                                 stype = F_NORMAL;
1074                         else if (!strcasecmp(attr->value, "inbox"))
1075                                 stype = F_INBOX;
1076                         else if (!strcasecmp(attr->value, "outbox"))
1077                                 stype = F_OUTBOX;
1078                         else if (!strcasecmp(attr->value, "draft"))
1079                                 stype = F_DRAFT;
1080                         else if (!strcasecmp(attr->value, "queue"))
1081                                 stype = F_QUEUE;
1082                         else if (!strcasecmp(attr->value, "trash"))
1083                                 stype = F_TRASH;
1084                 } else if (!strcmp(attr->name, "name"))
1085                         name = attr->value;
1086                 else if (!strcmp(attr->name, "path"))
1087                         path = attr->value;
1088                 else if (!strcmp(attr->name, "account_id")) {
1089                         account = account_find_from_id(atoi(attr->value));
1090                         if (!account) g_warning("account_id: %s not found\n",
1091                                                 attr->value);
1092                 } else if (!strcmp(attr->name, "mtime"))
1093                         mtime = atoi(attr->value);
1094                 else if (!strcmp(attr->name, "new"))
1095                         new = atoi(attr->value);
1096                 else if (!strcmp(attr->name, "unread"))
1097                         unread = atoi(attr->value);
1098                 else if (!strcmp(attr->name, "total"))
1099                         total = atoi(attr->value);
1100                 else if (!strcmp(attr->name, "no_sub"))
1101                         no_sub = *attr->value == '1' ? TRUE : FALSE;
1102                 else if (!strcmp(attr->name, "no_select"))
1103                         no_select = *attr->value == '1' ? TRUE : FALSE;
1104                 else if (!strcmp(attr->name, "collapsed"))
1105                         collapsed = *attr->value == '1' ? TRUE : FALSE;
1106         }
1107
1108         item = folder_item_new(name, path);
1109         item->stype = stype;
1110         item->account = account;
1111         item->mtime = mtime;
1112         item->new = new;
1113         item->unread = unread;
1114         item->total = total;
1115         item->no_sub = no_sub;
1116         item->no_select = no_select;
1117         item->collapsed = collapsed;
1118         item->parent = FOLDER_ITEM(node->parent->data);
1119         item->folder = folder;
1120         switch (stype) {
1121         case F_INBOX:  folder->inbox  = item; break;
1122         case F_OUTBOX: folder->outbox = item; break;
1123         case F_DRAFT:  folder->draft  = item; break;
1124         case F_QUEUE:  folder->queue  = item; break;
1125         case F_TRASH:  folder->trash  = item; break;
1126         default:
1127                 break;
1128         }
1129
1130         prefs_folder_item_read_config(item);
1131
1132         node->data = item;
1133         xml_free_node(xmlnode);
1134
1135         return FALSE;
1136 }
1137
1138 static gboolean folder_read_folder_func(GNode *node, gpointer data)
1139 {
1140         Folder *folder;
1141         XMLNode *xmlnode;
1142         GList *list;
1143         FolderType type = F_UNKNOWN;
1144         const gchar *name = NULL;
1145         const gchar *path = NULL;
1146         PrefsAccount *account = NULL;
1147         gboolean collapsed = FALSE;
1148
1149         if (g_node_depth(node) != 2) return FALSE;
1150         g_return_val_if_fail(node->data != NULL, FALSE);
1151
1152         xmlnode = node->data;
1153         if (strcmp2(xmlnode->tag->tag, "folder") != 0) {
1154                 g_warning("tag name != \"folder\"\n");
1155                 return TRUE;
1156         }
1157         g_node_unlink(node);
1158         list = xmlnode->tag->attr;
1159         for (; list != NULL; list = list->next) {
1160                 XMLAttr *attr = list->data;
1161
1162                 if (!attr || !attr->name || !attr->value) continue;
1163                 if (!strcmp(attr->name, "type")) {
1164                         if (!strcasecmp(attr->value, "mh"))
1165                                 type = F_MH;
1166                         else if (!strcasecmp(attr->value, "mbox"))
1167                                 type = F_MBOX;
1168                         else if (!strcasecmp(attr->value, "maildir"))
1169                                 type = F_MAILDIR;
1170                         else if (!strcasecmp(attr->value, "imap"))
1171                                 type = F_IMAP;
1172                         else if (!strcasecmp(attr->value, "news"))
1173                                 type = F_NEWS;
1174                 } else if (!strcmp(attr->name, "name"))
1175                         name = attr->value;
1176                 else if (!strcmp(attr->name, "path"))
1177                         path = attr->value;
1178                 else if (!strcmp(attr->name, "account_id")) {
1179                         account = account_find_from_id(atoi(attr->value));
1180                         if (!account) g_warning("account_id: %s not found\n",
1181                                                 attr->value);
1182                 }
1183                 else if (!strcmp(attr->name, "collapsed")) {
1184                         collapsed = *(attr->value) == '1' ? 1 : 0;
1185                 }
1186         }
1187
1188         folder = folder_new(type, name, path);
1189         g_return_val_if_fail(folder != NULL, FALSE);
1190         folder->account = account;
1191         if (account && (type == F_IMAP || type == F_NEWS))
1192                 account->folder = REMOTE_FOLDER(folder);
1193                 
1194         node->data = folder->node->data;
1195         g_node_destroy(folder->node);
1196         folder->node = node;
1197
1198         folder_add(folder);
1199
1200         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1201                         folder_build_tree, folder);
1202
1203         /* ALFONS_NOTE: after a folder_new() the folder system also created a 
1204          * FolderItem for the folder (which makes insertion in the GtkCTree
1205          * easier because it deals with FolderItems only). put the collapsed
1206          * state for this Folder in its associated FolderItem */
1207
1208         FOLDER_ITEM((folder->node->data))->collapsed = collapsed; 
1209
1210         return FALSE;
1211 }
1212
1213 static gchar *folder_get_list_path(void)
1214 {
1215         static gchar *filename = NULL;
1216
1217         if (!filename)
1218                 filename =  g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1219                                         FOLDER_LIST, NULL);
1220
1221         return filename;
1222 }
1223
1224 static void folder_write_list_recursive(GNode *node, gpointer data)
1225 {
1226         FILE *fp = (FILE *)data;
1227         FolderItem *item = FOLDER_ITEM(node->data);
1228         gint i, depth;
1229         static gchar *folder_type_str[] = {"mh", "mbox", "maildir", "imap",
1230                                            "news", "unknown"};
1231         static gchar *folder_item_stype_str[] = {"normal", "inbox", "outbox",
1232                                                  "draft", "queue", "trash"};
1233
1234         g_return_if_fail(item != NULL);
1235
1236         depth = g_node_depth(node);
1237         for (i = 0; i < depth; i++)
1238                 fputs("    ", fp);
1239         if (depth == 1) {
1240                 Folder *folder = item->folder;
1241
1242                 fprintf(fp, "<folder type=\"%s\"", folder_type_str[folder->type]);
1243                 if (folder->name) {
1244                         fputs(" name=\"", fp);
1245                         xml_file_put_escape_str(fp, folder->name);
1246                         fputs("\"", fp);
1247                 }
1248                 if ((folder->type == F_MH) || (folder->type == F_MBOX)) {
1249                         fputs(" path=\"", fp);
1250                         xml_file_put_escape_str
1251                                 (fp, LOCAL_FOLDER(folder)->rootpath);
1252                         fputs("\"", fp);
1253                 }
1254                 if (folder->account)
1255                         fprintf(fp, " account_id=\"%d\"",
1256                                 folder->account->account_id);
1257                                 
1258                 /* ALFONS_NOTE: for each Folder structure, also a FolderItem is created;
1259                  * this is very clever, but undocumented. (it is clever because the
1260                  * folderview's GtkCTree only deals with FolderItems.) */
1261                 if (item->collapsed )
1262                         fprintf(fp, " collapsed=\"1\"");
1263                 
1264         } else {
1265                 fprintf(fp, "<folderitem type=\"%s\"",
1266                         folder_item_stype_str[item->stype]);
1267                 if (item->name) {
1268                         fputs(" name=\"", fp);
1269                         xml_file_put_escape_str(fp, item->name);
1270                         fputs("\"", fp);
1271                 }
1272                 if (item->path) {
1273                         fputs(" path=\"", fp);
1274                         xml_file_put_escape_str(fp, item->path);
1275                         fputs("\"", fp);
1276                 }
1277                 if (item->account)
1278                         fprintf(fp, " account_id=\"%d\"",
1279                                 item->account->account_id);
1280                 if (item->no_sub)
1281                         fputs(" no_sub=\"1\"", fp);
1282                 if (item->no_select)
1283                         fputs(" no_select=\"1\"", fp);
1284                 if (item->collapsed) 
1285                         fputs(" collapsed=\"1\"", fp);
1286                 fprintf(fp,
1287                         " mtime=\"%ld\" new=\"%d\" unread=\"%d\" total=\"%d\"",
1288                         item->mtime, item->new, item->unread, item->total);
1289         }
1290
1291         if (node->children) {
1292                 GNode *child;
1293                 fputs(">\n", fp);
1294
1295                 child = node->children;
1296                 while (child) {
1297                         GNode *cur;
1298
1299                         cur = child;
1300                         child = cur->next;
1301                         folder_write_list_recursive(cur, data);
1302                 }
1303
1304                 for (i = 0; i < depth; i++)
1305                         fputs("    ", fp);
1306                 fprintf(fp, "</%s>\n", depth == 1 ? "folder" : "folderitem");
1307         } else
1308                 fputs(" />\n", fp);
1309 }
1310
1311 typedef struct _type_str {
1312         gchar * str;
1313         gint type;
1314 } type_str;
1315
1316
1317 static type_str type_str_table[] = 
1318 {
1319         {"#mh", F_MH},
1320         {"#mbox", F_MBOX},
1321         {"#maildir", F_MAILDIR},
1322         {"#imap", F_IMAP},
1323         {"#news", F_NEWS}
1324 };
1325
1326
1327 static gchar * folder_get_type_string(gint type)
1328 {
1329         gint i;
1330
1331         for(i = 0 ; i < sizeof(type_str_table) / sizeof(type_str) ; i++) {
1332                 if (type_str_table[i].type == type)
1333                         return type_str_table[i].str;
1334         }
1335         return NULL;
1336 }
1337
1338 static gint folder_get_type_from_string(gchar * str)
1339 {
1340         gint i;
1341
1342         for(i = 0 ; i < sizeof(type_str_table) / sizeof(type_str) ; i++) {
1343                 if (g_strcasecmp(type_str_table[i].str, str))
1344                         return type_str_table[i].type;
1345         }
1346         return F_UNKNOWN;
1347 }
1348
1349 gchar * folder_get_identifier(Folder * folder)
1350 {
1351         gchar * type_str;
1352         type_str = folder_get_type_string(folder->type);
1353
1354         return g_strconcat(type_str, "/", folder->name, NULL);
1355 }
1356
1357 /*
1358 static gchar * folder_item_get_tree_identifier(FolderItem * item)
1359 {
1360         if (item->parent != NULL) {
1361                 gchar * path;
1362                 gchar * id;
1363
1364                 path = folder_item_get_tree_identifier(item->parent);
1365                 if (path == NULL)
1366                         return NULL;
1367
1368                 id = g_strconcat(path, "/", item->name, NULL);
1369                 g_free(path);
1370
1371                 return id;
1372         }
1373         else {
1374                 return g_strconcat("/", item->name, NULL);
1375         }
1376 }
1377 */
1378
1379 gchar * folder_item_get_identifier(FolderItem * item)
1380 {
1381         gchar * path;
1382         gchar * id;
1383         gchar * folder_str;
1384
1385         folder_str = folder_get_identifier(item->folder);
1386
1387         if (item->path == NULL) {
1388                 g_free(folder_str);
1389                 return NULL;
1390         }
1391
1392         id = g_strconcat(folder_str, "/", item->path, NULL);
1393
1394         return id;
1395 }
1396
1397 Folder * folder_find_from_name(const gchar * name)
1398 {
1399         GList *list;
1400         Folder *folder;
1401
1402         for (list = g_list_first(folder_list); list != NULL;
1403              list = list->next) {
1404                 folder = list->data;
1405                 if (strcmp(name, folder->name) == 0)
1406                         return folder;
1407         }
1408         return NULL;
1409 }
1410
1411 FolderItem * folder_find_item_from_identifier(const gchar *identifier)
1412 {
1413         Folder *folder;
1414         gpointer d[2];
1415         gchar * str;
1416         gchar * p;
1417         gint type;
1418         gchar * name;
1419         gchar * path;
1420
1421         Xalloca(str, strlen(identifier) + 1, return NULL);
1422         strcpy(str, identifier);
1423
1424         /* extract box type */
1425
1426         p = strchr(str, '/');
1427
1428         if (p == NULL)
1429                 return NULL;
1430
1431         *p = '\0';
1432
1433         type = folder_get_type_from_string(str);
1434
1435         str = p + 1;
1436
1437         /* extract box name */
1438
1439         p = strchr(str, '/');
1440
1441         if (p == NULL)
1442                 return NULL;
1443
1444         *p = '\0';
1445
1446         name = str;
1447
1448         folder = folder_find_from_name(name);
1449         if (folder == NULL)
1450                 return;
1451
1452         path = p + 1;
1453
1454         d[0] = (gpointer)path;
1455         d[1] = NULL;
1456         g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1457                         folder_item_find_func, d);
1458         return d[1];
1459 }