** See bug #52817.
[platform/upstream/evolution-data-server.git] / camel / camel-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-store.c : Abstract class for an email store */
3
4 /* 
5  * Authors:
6  *  Bertrand Guiheneuf <bertrand@helixcode.com>
7  *  Dan Winship <danw@ximian.com>
8  *
9  * Copyright 1999-2003 Ximian, Inc. (www.ximian.com)
10  *
11  * This program is free software; you can redistribute it and/or 
12  * modify it under the terms of version 2 of the GNU General Public 
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23  * USA
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <string.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33
34 #include "camel-session.h"
35 #include "camel-store.h"
36 #include "camel-folder.h"
37 #include "camel-vtrash-folder.h"
38 #include "camel-exception.h"
39 #include "camel-private.h"
40
41 #define d(x)
42 #define w(x)
43
44 static CamelServiceClass *parent_class = NULL;
45
46 /* Returns the class for a CamelStore */
47 #define CS_CLASS(so) ((CamelStoreClass *)((CamelObject *)(so))->klass)
48
49 static CamelFolder *get_folder (CamelStore *store, const char *folder_name,
50                                 guint32 flags, CamelException *ex);
51 static CamelFolder *get_inbox (CamelStore *store, CamelException *ex);
52
53 static void        init_trash (CamelStore *store);
54 static CamelFolder *get_trash (CamelStore *store, CamelException *ex);
55
56 static void        init_junk (CamelStore *store);
57 static CamelFolder *get_junk (CamelStore *store, CamelException *ex);
58
59 static CamelFolderInfo *create_folder (CamelStore *store,
60                                        const char *parent_name,
61                                        const char *folder_name,
62                                        CamelException *ex);
63 static void delete_folder (CamelStore *store, const char *folder_name,
64                            CamelException *ex);
65 static void rename_folder (CamelStore *store, const char *old_name,
66                            const char *new_name, CamelException *ex);
67
68 static void store_sync (CamelStore *store, CamelException *ex);
69 static CamelFolderInfo *get_folder_info (CamelStore *store, const char *top,
70                                          guint32 flags, CamelException *ex);
71 static void free_folder_info (CamelStore *store, CamelFolderInfo *tree);
72
73 static gboolean folder_subscribed (CamelStore *store, const char *folder_name);
74 static void subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex);
75 static void unsubscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex);
76
77 static void noop (CamelStore *store, CamelException *ex);
78
79 static void construct (CamelService *service, CamelSession *session,
80                        CamelProvider *provider, CamelURL *url,
81                        CamelException *ex);
82
83 static int store_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
84 static int store_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
85
86 static void
87 camel_store_class_init (CamelStoreClass *camel_store_class)
88 {
89         CamelObjectClass *camel_object_class = CAMEL_OBJECT_CLASS (camel_store_class);
90         CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(camel_store_class);
91         
92         parent_class = CAMEL_SERVICE_CLASS (camel_type_get_global_classfuncs (camel_service_get_type ()));
93         
94         /* virtual method definition */
95         camel_store_class->hash_folder_name = g_str_hash;
96         camel_store_class->compare_folder_name = g_str_equal;
97         camel_store_class->get_folder = get_folder;
98         camel_store_class->get_inbox = get_inbox;
99         camel_store_class->init_trash = init_trash;
100         camel_store_class->get_trash = get_trash;
101         camel_store_class->init_junk = init_junk;
102         camel_store_class->get_junk = get_junk;
103         camel_store_class->create_folder = create_folder;
104         camel_store_class->delete_folder = delete_folder;
105         camel_store_class->rename_folder = rename_folder;
106         camel_store_class->sync = store_sync;
107         camel_store_class->get_folder_info = get_folder_info;
108         camel_store_class->free_folder_info = free_folder_info;
109         camel_store_class->folder_subscribed = folder_subscribed;
110         camel_store_class->subscribe_folder = subscribe_folder;
111         camel_store_class->unsubscribe_folder = unsubscribe_folder;
112         camel_store_class->noop = noop;
113         
114         /* virtual method overload */
115         camel_service_class->construct = construct;
116         
117         camel_object_class->setv = store_setv;
118         camel_object_class->getv = store_getv;
119         
120         camel_object_class_add_event(camel_object_class, "folder_created", NULL);
121         camel_object_class_add_event(camel_object_class, "folder_deleted", NULL);
122         camel_object_class_add_event(camel_object_class, "folder_renamed", NULL);
123         camel_object_class_add_event(camel_object_class, "folder_subscribed", NULL);
124         camel_object_class_add_event(camel_object_class, "folder_unsubscribed", NULL);
125 }
126
127 static void
128 camel_store_init (void *o)
129 {
130         CamelStore *store = o;
131         CamelStoreClass *store_class = (CamelStoreClass *)CAMEL_OBJECT_GET_CLASS (o);
132
133         if (store_class->hash_folder_name) {
134                 store->folders = camel_object_bag_new(store_class->hash_folder_name,
135                                                       store_class->compare_folder_name,
136                                                       (CamelCopyFunc)g_strdup, g_free);
137         } else
138                 store->folders = NULL;
139         
140         /* set vtrash and vjunk on by default */
141         store->flags = CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK;
142
143         store->dir_sep = '/';
144         
145         store->priv = g_malloc0 (sizeof (*store->priv));
146         store->priv->folder_lock = e_mutex_new (E_MUTEX_REC);
147 }
148
149 static void
150 camel_store_finalize (CamelObject *object)
151 {
152         CamelStore *store = CAMEL_STORE (object);
153
154         if (store->folders)
155                 camel_object_bag_destroy(store->folders);
156         
157         e_mutex_destroy (store->priv->folder_lock);
158         
159         g_free (store->priv);
160 }
161
162
163 CamelType
164 camel_store_get_type (void)
165 {
166         static CamelType camel_store_type = CAMEL_INVALID_TYPE;
167
168         if (camel_store_type == CAMEL_INVALID_TYPE) {
169                 camel_store_type = camel_type_register (CAMEL_SERVICE_TYPE, "CamelStore",
170                                                         sizeof (CamelStore),
171                                                         sizeof (CamelStoreClass),
172                                                         (CamelObjectClassInitFunc) camel_store_class_init,
173                                                         NULL,
174                                                         (CamelObjectInitFunc) camel_store_init,
175                                                         (CamelObjectFinalizeFunc) camel_store_finalize );
176         }
177
178         return camel_store_type;
179 }
180
181 static int
182 store_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
183 {
184         /* CamelStore doesn't currently have anything to set */
185         return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args);
186 }
187
188 static int
189 store_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
190 {
191         /* CamelStore doesn't currently have anything to get */
192         return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args);
193 }
194
195 static void
196 construct (CamelService *service, CamelSession *session,
197            CamelProvider *provider, CamelURL *url,
198            CamelException *ex)
199 {
200         CamelStore *store = CAMEL_STORE(service);
201
202         parent_class->construct(service, session, provider, url, ex);
203         if (camel_exception_is_set (ex))
204                 return;
205
206         if (camel_url_get_param(url, "filter"))
207                 store->flags |= CAMEL_STORE_FILTER_INBOX;
208 }
209
210 static CamelFolder *
211 get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
212 {
213         w(g_warning ("CamelStore::get_folder not implemented for `%s'",
214                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
215         
216         camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_INVALID,
217                               _("Cannot get folder: Invalid operation on this store"));
218         
219         return NULL;
220 }
221
222 /** 
223  * camel_store_get_folder: Return the folder corresponding to a path.
224  * @store: a CamelStore
225  * @folder_name: name of the folder to get
226  * @flags: folder flags (create, save body index, etc)
227  * @ex: a CamelException
228  * 
229  * Return value: the folder corresponding to the path @folder_name.
230  **/
231 CamelFolder *
232 camel_store_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
233 {
234         CamelFolder *folder = NULL;
235         
236         g_return_val_if_fail (folder_name != NULL, NULL);
237         
238         CAMEL_STORE_LOCK(store, folder_lock);
239         
240         if (store->folders)
241                 /* Try cache first. */
242                 folder = camel_object_bag_reserve(store->folders, folder_name);
243         
244         if (!folder) {
245                 folder = CS_CLASS (store)->get_folder (store, folder_name, flags, ex);
246                 if (folder) {
247                         /* Add the folder to the vTrash/vJunk folder if this store implements it */
248                         if (store->vtrash)
249                                 camel_vee_folder_add_folder (CAMEL_VEE_FOLDER (store->vtrash), folder);
250                         if (store->vjunk)
251                                 camel_vee_folder_add_folder (CAMEL_VEE_FOLDER (store->vjunk), folder);
252                         
253                         if (store->folders)
254                                 camel_object_bag_add(store->folders, folder_name, folder);
255                 } else {
256                         if (store->folders)
257                                 camel_object_bag_abort(store->folders, folder_name);
258                 }
259         }
260
261         CAMEL_STORE_UNLOCK(store, folder_lock);
262
263         return folder;
264 }
265
266 static CamelFolderInfo *
267 create_folder (CamelStore *store, const char *parent_name,
268                const char *folder_name, CamelException *ex)
269 {
270         w(g_warning ("CamelStore::create_folder not implemented for `%s'",
271                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
272         
273         camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_INVALID,
274                               _("Cannot create folder: Invalid operation on this store"));
275         
276         return NULL;
277 }
278
279 /** 
280  * camel_store_create_folder:
281  * @store: a CamelStore
282  * @parent_name: name of the new folder's parent, or %NULL
283  * @folder_name: name of the folder to create
284  * @ex: a CamelException
285  * 
286  * Creates a new folder as a child of an existing folder.
287  * @parent_name can be %NULL to create a new top-level folder.
288  *
289  * Return value: info about the created folder, which the caller must
290  * free with camel_store_free_folder_info().
291  **/
292 CamelFolderInfo *
293 camel_store_create_folder (CamelStore *store, const char *parent_name,
294                            const char *folder_name, CamelException *ex)
295 {
296         CamelFolderInfo *fi;
297
298         CAMEL_STORE_LOCK(store, folder_lock);
299         fi = CS_CLASS (store)->create_folder (store, parent_name, folder_name, ex);
300         CAMEL_STORE_UNLOCK(store, folder_lock);
301
302         return fi;
303 }
304
305
306 static void
307 delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
308 {
309         w(g_warning ("CamelStore::delete_folder not implemented for `%s'",
310                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
311 }
312
313 /** 
314  * camel_store_delete_folder: Delete the folder corresponding to a path.
315  * @store: a CamelStore
316  * @folder_name: name of the folder to delete
317  * @ex: a CamelException
318  * 
319  * Deletes the named folder. The folder must be empty.
320  **/
321 void
322 camel_store_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
323 {
324         CamelFolder *folder = NULL;
325         
326         CAMEL_STORE_LOCK(store, folder_lock);
327
328         /* NB: Note similarity of this code to unsubscribe_folder */
329         
330         /* if we deleted a folder, force it out of the cache, and also out of the vtrash/vjunk if setup */
331         if (store->folders) {
332                 folder = camel_object_bag_get(store->folders, folder_name);
333                 if (folder) {
334                         if (store->vtrash)
335                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vtrash, folder);
336                         if (store->vjunk)
337                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vjunk, folder);
338                         camel_folder_delete (folder);
339                 }
340         }
341
342         CS_CLASS (store)->delete_folder (store, folder_name, ex);
343         
344         if (folder) {
345                 if (store->folders)
346                         camel_object_bag_remove (store->folders, folder);
347                 
348                 camel_object_unref (folder);
349         }
350         
351         CAMEL_STORE_UNLOCK(store, folder_lock);
352 }
353
354 static void
355 rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex)
356 {
357         w(g_warning ("CamelStore::rename_folder not implemented for `%s'",
358                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
359 }
360
361 /**
362  * camel_store_rename_folder:
363  * @store: a CamelStore
364  * @old_name: the current name of the folder
365  * @new_name: the new name of the folder
366  * @ex: a CamelException
367  * 
368  * Rename a named folder to a new name.
369  **/
370 void
371 camel_store_rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex)
372 {
373         CamelFolder *folder;
374         int i, oldlen, namelen;
375         GPtrArray *folders;
376
377         d(printf("store rename folder %s '%s' '%s'\n", ((CamelService *)store)->url->protocol, old_name, new_name));
378
379         if (strcmp(old_name, new_name) == 0)
380                 return;
381
382         oldlen = strlen(old_name);
383
384         CAMEL_STORE_LOCK(store, folder_lock);
385
386         /* If the folder is open (or any subfolders of the open folder)
387            We need to rename them atomically with renaming the actual folder path */
388         if (store->folders) {
389                 folders = camel_object_bag_list(store->folders);
390                 for (i=0;i<folders->len;i++) {
391                         folder = folders->pdata[i];
392                         namelen = strlen(folder->full_name);
393                         if ((namelen == oldlen &&
394                              strcmp(folder->full_name, old_name) == 0)
395                             || ((namelen > oldlen)
396                                 && strncmp(folder->full_name, old_name, oldlen) == 0
397                                 && folder->full_name[oldlen] == store->dir_sep)) {
398                                 d(printf("Found subfolder of '%s' == '%s'\n", old_name, folder->full_name));
399                                 CAMEL_FOLDER_LOCK(folder, lock);
400                         } else {
401                                 g_ptr_array_remove_index_fast(folders, i);
402                                 i--;
403                                 camel_object_unref(folder);
404                         }
405                 }
406         }
407
408         /* Now try the real rename (will emit renamed event) */
409         CS_CLASS (store)->rename_folder (store, old_name, new_name, ex);
410
411         /* If it worked, update all open folders/unlock them */
412         if (!camel_exception_is_set(ex)) {
413                 guint32 flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE;
414                 CamelRenameInfo reninfo;
415
416                 for (i=0;i<folders->len;i++) {
417                         char *new;
418
419                         folder = folders->pdata[i];
420
421                         new = g_strdup_printf("%s%s", new_name, folder->full_name+strlen(old_name));
422                         camel_object_bag_remove(store->folders, folder);
423                         camel_object_bag_add(store->folders, new, folder);
424                         camel_folder_rename(folder, new);
425
426                         CAMEL_FOLDER_UNLOCK(folder, lock);
427                         camel_object_unref(folder);
428                 }
429
430                 /* Emit changed signal */
431                 if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
432                         flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
433                 
434                 reninfo.old_base = (char *)old_name;
435                 reninfo.new = ((CamelStoreClass *)((CamelObject *)store)->klass)->get_folder_info(store, new_name, flags, ex);
436                 if (reninfo.new != NULL) {
437                         camel_object_trigger_event (store, "folder_renamed", &reninfo);
438                         ((CamelStoreClass *)((CamelObject *)store)->klass)->free_folder_info(store, reninfo.new);
439                 }
440         } else {
441                 /* Failed, just unlock our folders for re-use */
442                 for (i=0;i<folders->len;i++) {
443                         folder = folders->pdata[i];
444                         CAMEL_FOLDER_UNLOCK(folder, lock);
445                         camel_object_unref(folder);
446                 }
447         }
448
449         CAMEL_STORE_UNLOCK(store, folder_lock);
450
451         g_ptr_array_free(folders, TRUE);
452 }
453
454
455 static CamelFolder *
456 get_inbox (CamelStore *store, CamelException *ex)
457 {
458         /* Default: assume the inbox's name is "inbox"
459          * and open with default flags.
460          */
461         return CS_CLASS (store)->get_folder (store, "inbox", 0, ex);
462 }
463
464 /** 
465  * camel_store_get_inbox:
466  * @store: a CamelStore
467  * @ex: a CamelException
468  *
469  * Return value: the folder in the store into which new mail is
470  * delivered, or %NULL if no such folder exists.
471  **/
472 CamelFolder *
473 camel_store_get_inbox (CamelStore *store, CamelException *ex)
474 {
475         CamelFolder *folder;
476
477         CAMEL_STORE_LOCK(store, folder_lock);
478         folder = CS_CLASS (store)->get_inbox (store, ex);
479         CAMEL_STORE_UNLOCK(store, folder_lock);
480
481         return folder;
482 }
483
484 static void
485 trash_finalize (CamelObject *trash, gpointer event_data, gpointer user_data)
486 {
487         CamelStore *store = CAMEL_STORE (user_data);
488         
489         store->vtrash = NULL;
490 }
491
492 static void
493 junk_finalize (CamelObject *junk, gpointer event_data, gpointer user_data)
494 {
495         CamelStore *store = CAMEL_STORE (user_data);
496         
497         store->vjunk = NULL;
498 }
499
500 /* FIXME: derive vjunk folder object from vee_folder */
501 #include "camel-vee-store.h"
502 static CamelFolder *
503 camel_vjunk_folder_new (CamelStore *parent_store, const char *name)
504 {
505         CamelFolder *vjunk;
506         
507         vjunk = (CamelFolder *)camel_object_new (camel_vee_folder_get_type ());
508         vjunk->folder_flags |= CAMEL_FOLDER_IS_JUNK;
509         camel_vee_folder_construct (CAMEL_VEE_FOLDER (vjunk), parent_store, name,
510                                     CAMEL_STORE_FOLDER_PRIVATE | CAMEL_STORE_FOLDER_CREATE | CAMEL_STORE_VEE_FOLDER_AUTO);
511         camel_vee_folder_set_expression((CamelVeeFolder *)vjunk, "(match-all (system-flag \"Junk\"))");
512
513         return vjunk;
514 }
515
516 static void
517 init_trash_or_junk (CamelStore *store, CamelFolder *folder, void (*finalize) (CamelObject *o, gpointer event_data, gpointer user_data))
518 {
519         if (folder) {
520                 /* FIXME: this should probably use the object bag or another one ? ... */
521                 /* attach to the finalise event of the vtrash/vjunk */
522                 camel_object_hook_event (folder, "finalize", finalize, store);
523                 
524                 /* add all the pre-opened folders to the vtrash/vjunk */
525                 if (store->folders) {
526                         GPtrArray *folders = camel_object_bag_list(store->folders);
527                         int i;
528
529                         for (i=0;i<folders->len;i++) {
530                                 camel_vee_folder_add_folder (CAMEL_VEE_FOLDER (folder), (CamelFolder *)folders->pdata[i]);
531                                 camel_object_unref(folders->pdata[i]);
532                         }
533                         g_ptr_array_free(folders, TRUE);
534                 }
535         }
536 }
537
538 static void
539 init_trash (CamelStore *store)
540 {
541         if ((store->flags & CAMEL_STORE_VTRASH) == 0)
542                 return;
543
544         store->vtrash = camel_vtrash_folder_new (store, CAMEL_VTRASH_NAME);
545         init_trash_or_junk (store, store->vtrash, trash_finalize);
546 }
547
548 static void
549 init_junk (CamelStore *store)
550 {
551         if ((store->flags & CAMEL_STORE_VJUNK) == 0)
552                 return;
553
554         store->vjunk = camel_vjunk_folder_new (store, CAMEL_VJUNK_NAME);
555         init_trash_or_junk (store, store->vjunk, junk_finalize);
556 }
557
558 static CamelFolder *
559 get_trash (CamelStore *store, CamelException *ex)
560 {
561         if (store->vtrash) {
562                 camel_object_ref (store->vtrash);
563                 return store->vtrash;
564         } else {
565                 CS_CLASS (store)->init_trash (store);
566                 if (store->vtrash) {
567                         /* We don't ref here because we don't want the
568                            store to own a ref on the trash folder */
569                         /*camel_object_ref (store->vtrash);*/
570                         return store->vtrash;
571                 } else {
572                         w(g_warning ("This store does not support vTrash."));
573                         return NULL;
574                 }
575         }
576 }
577
578 static CamelFolder *
579 get_junk (CamelStore *store, CamelException *ex)
580 {
581         if (store->vjunk) {
582                 camel_object_ref (CAMEL_OBJECT (store->vjunk));
583                 return store->vjunk;
584         } else {
585                 CS_CLASS (store)->init_junk (store);
586                 if (store->vjunk) {
587                         /* We don't ref here because we don't want the
588                            store to own a ref on the junk folder */
589                         /*camel_object_ref (CAMEL_OBJECT (store->vjunk));*/
590                         return store->vjunk;
591                 } else {
592                         w(g_warning ("This store does not support vJunk."));
593                         return NULL;
594                 }
595         }
596 }
597
598 /** 
599  * camel_store_get_trash:
600  * @store: a CamelStore
601  * @ex: a CamelException
602  *
603  * Return value: the folder in the store into which trash is
604  * delivered, or %NULL if no such folder exists.
605  **/
606 CamelFolder *
607 camel_store_get_trash (CamelStore *store, CamelException *ex)
608 {
609         CamelFolder *folder;
610
611         if ((store->flags & CAMEL_STORE_VTRASH) == 0)
612                 return NULL;
613         
614         CAMEL_STORE_LOCK(store, folder_lock);
615         folder = CS_CLASS (store)->get_trash (store, ex);
616         CAMEL_STORE_UNLOCK(store, folder_lock);
617         
618         return folder;
619 }
620
621 /** 
622  * camel_store_get_junk:
623  * @store: a CamelStore
624  * @ex: a CamelException
625  *
626  * Return value: the folder in the store into which junk is
627  * delivered, or %NULL if no such folder exists.
628  **/
629 CamelFolder *
630 camel_store_get_junk (CamelStore *store, CamelException *ex)
631 {
632         CamelFolder *folder;
633
634         if ((store->flags & CAMEL_STORE_VJUNK) == 0)
635                 return NULL;
636         
637         CAMEL_STORE_LOCK(store, folder_lock);
638         folder = CS_CLASS (store)->get_junk (store, ex);
639         CAMEL_STORE_UNLOCK(store, folder_lock);
640         
641         return folder;
642 }
643
644 static void
645 store_sync (CamelStore *store, CamelException *ex)
646 {
647         if (store->folders) {
648                 GPtrArray *folders;
649                 CamelFolder *folder;
650                 CamelException x;
651                 int i;
652
653                 camel_exception_init(&x);
654                 folders = camel_object_bag_list(store->folders);
655                 for (i=0;i<folders->len;i++) {
656                         folder = folders->pdata[i];
657                         if (!camel_exception_is_set(&x))
658                                 camel_folder_sync(folder, FALSE, &x);
659                         camel_object_unref(folder);
660                 }
661                 camel_exception_xfer(ex, &x);
662                 g_ptr_array_free(folders, TRUE);
663         }
664 }
665
666 /**
667  * camel_store_sync:
668  * @store: a CamelStore
669  * @ex: a CamelException
670  *
671  * Syncs any changes that have been made to the store object and its
672  * folders with the real store.
673  **/
674 void
675 camel_store_sync (CamelStore *store, CamelException *ex)
676 {
677         g_return_if_fail (CAMEL_IS_STORE (store));
678
679         CS_CLASS (store)->sync (store, ex);
680 }
681
682
683 static CamelFolderInfo *
684 get_folder_info (CamelStore *store, const char *top,
685                  guint32 flags, CamelException *ex)
686 {
687         w(g_warning ("CamelStore::get_folder_info not implemented for `%s'",
688                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
689         
690         return NULL;
691 }
692
693 static void
694 add_special_info (CamelStore *store, CamelFolderInfo *info, const char *name, const char *full_name,
695                   const char *url_base, gboolean unread_count)
696 {
697         CamelFolderInfo *fi, *vinfo, *parent;
698         char *uri, *path;
699         CamelURL *url;
700         
701         g_return_if_fail (info != NULL);
702         
703         parent = NULL;
704         for (fi = info; fi; fi = fi->sibling) {
705                 if (!strcmp (fi->name, name))
706                         break;
707                 parent = fi;
708         }
709         
710         /* create our vTrash/vJunk URL */
711         url = camel_url_new (info->url, NULL);
712         if (((CamelService *) store)->provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH) {
713                 camel_url_set_fragment (url, name);
714         } else {
715                 path = g_strdup_printf ("/%s", name);
716                 camel_url_set_path (url, path);
717                 g_free (path);
718         }
719         
720         uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
721         camel_url_free (url);
722         
723         if (fi) {
724                 /* We're going to replace the physical Trash/Junk folder with our vTrash/vJunk folder */
725                 vinfo = fi;
726                 g_free (vinfo->full_name);
727                 g_free (vinfo->name);
728                 g_free (vinfo->url);
729                 g_free (vinfo->path);
730         } else {
731                 /* There wasn't a Trash/Junk folder so create a new folder entry */
732                 vinfo = g_new0 (CamelFolderInfo, 1);
733                 
734                 g_assert(parent != NULL);
735                 
736                 vinfo->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_SUBSCRIBED;
737                 
738                 /* link it into the right spot */
739                 vinfo->sibling = parent->sibling;
740                 parent->sibling = vinfo;
741         }
742         
743         /* Fill in the new fields */
744         vinfo->full_name = g_strdup (full_name);
745         vinfo->name = g_strdup (vinfo->full_name);
746         vinfo->url = g_strdup_printf ("%s:%s", url_base, uri);
747         if (!unread_count)
748                 vinfo->unread_message_count = -1;
749         vinfo->path = g_strdup_printf ("/%s", vinfo->name);
750         g_free (uri);
751 }
752
753 /**
754  * camel_store_get_folder_info:
755  * @store: a CamelStore
756  * @top: the name of the folder to start from
757  * @flags: various CAMEL_STORE_FOLDER_INFO_* flags to control behavior
758  * @ex: a CamelException
759  *
760  * This fetches information about the folder structure of @store,
761  * starting with @top, and returns a tree of CamelFolderInfo
762  * structures. If @flags includes %CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
763  * only subscribed folders will be listed. (This flag can only be used
764  * for stores that support subscriptions.) If @flags includes
765  * %CAMEL_STORE_FOLDER_INFO_RECURSIVE, the returned tree will include
766  * all levels of hierarchy below @top. If not, it will only include
767  * the immediate subfolders of @top. If @flags includes
768  * %CAMEL_STORE_FOLDER_INFO_FAST, the unread_message_count fields of
769  * some or all of the structures may be set to -1, if the store cannot
770  * determine that information quickly.
771  * 
772  * Return value: a CamelFolderInfo tree, which must be freed with
773  * camel_store_free_folder_info.
774  **/
775 CamelFolderInfo *
776 camel_store_get_folder_info (CamelStore *store, const char *top,
777                              guint32 flags, CamelException *ex)
778 {
779         CamelFolderInfo *info;
780         
781         g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
782         g_return_val_if_fail ((store->flags & CAMEL_STORE_SUBSCRIPTIONS) ||
783                               !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED),
784                               NULL);
785
786         CAMEL_STORE_LOCK(store, folder_lock);
787         info = CS_CLASS (store)->get_folder_info (store, top, flags, ex);
788         CAMEL_STORE_UNLOCK(store, folder_lock);
789         
790         if (info && (top == NULL || *top == '\0')) {
791                 if (info->url && (store->flags & CAMEL_STORE_VTRASH))
792                         add_special_info (store, info, CAMEL_VTRASH_NAME, _("Trash"), "vtrash", FALSE);
793                 if (info->url && (store->flags & CAMEL_STORE_VJUNK))
794                         add_special_info (store, info, CAMEL_VJUNK_NAME, _("Junk"), "vjunk", TRUE);
795         }
796         
797         return info;
798 }
799
800
801 static void
802 free_folder_info (CamelStore *store, CamelFolderInfo *fi)
803 {
804         w(g_warning ("CamelStore::free_folder_info not implemented for `%s'",
805                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
806 }
807
808 /**
809  * camel_store_free_folder_info:
810  * @store: a CamelStore
811  * @tree: the tree returned by camel_store_get_folder_info()
812  *
813  * Frees the data returned by camel_store_get_folder_info().
814  **/
815 void
816 camel_store_free_folder_info (CamelStore *store, CamelFolderInfo *fi)
817 {
818         g_return_if_fail (CAMEL_IS_STORE (store));
819
820         CS_CLASS (store)->free_folder_info (store, fi);
821 }
822
823 /**
824  * camel_store_free_folder_info_full:
825  * @store: a CamelStore
826  * @tree: the tree returned by camel_store_get_folder_info()
827  *
828  * An implementation for CamelStore::free_folder_info. Frees all
829  * of the data.
830  **/
831 void
832 camel_store_free_folder_info_full (CamelStore *store, CamelFolderInfo *fi)
833 {
834         camel_folder_info_free (fi);
835 }
836
837 /**
838  * camel_store_free_folder_info_nop:
839  * @store: a CamelStore
840  * @tree: the tree returned by camel_store_get_folder_info()
841  *
842  * An implementation for CamelStore::free_folder_info. Does nothing.
843  **/
844 void
845 camel_store_free_folder_info_nop (CamelStore *store, CamelFolderInfo *fi)
846 {
847         ;
848 }
849
850
851 /**
852  * camel_folder_info_free:
853  * @fi: the CamelFolderInfo
854  *
855  * Frees @fi.
856  **/
857 void
858 camel_folder_info_free (CamelFolderInfo *fi)
859 {
860         if (fi) {
861                 camel_folder_info_free (fi->sibling);
862                 camel_folder_info_free (fi->child);
863                 g_free (fi->name);
864                 g_free (fi->full_name);
865                 g_free (fi->path);
866                 g_free (fi->url);
867                 g_free (fi);
868         }
869 }
870
871
872 /**
873  * camel_folder_info_build_path:
874  * @fi: folder info
875  * @separator: directory separator
876  *
877  * Sets the folder info path based on the folder's full name and
878  * directory separator.
879  **/
880 void
881 camel_folder_info_build_path (CamelFolderInfo *fi, char separator)
882 {
883         const char *full_name;
884         char *p;
885         
886         full_name = fi->full_name;
887         while (*full_name == separator)
888                 full_name++;
889         
890         fi->path = g_strdup_printf ("/%s", full_name);
891         if (separator != '/') {
892                 for (p = fi->path; *p; p++) {
893                         if (*p == separator)
894                                 *p = '/';
895                 }
896         }
897 }
898
899 static int
900 folder_info_cmp (const void *ap, const void *bp)
901 {
902         const CamelFolderInfo *a = ((CamelFolderInfo **)ap)[0];
903         const CamelFolderInfo *b = ((CamelFolderInfo **)bp)[0];
904         
905         return strcmp (a->full_name, b->full_name);
906 }
907
908 static void
909 free_name(void *key, void *data, void *user)
910 {
911         g_free(key);
912 }
913
914 /**
915  * camel_folder_info_build:
916  * @folders: an array of CamelFolderInfo
917  * @namespace: an ignorable prefix on the folder names
918  * @separator: the hieararchy separator character
919  * @short_names: %TRUE if the (short) name of a folder is the part after
920  * the last @separator in the full name. %FALSE if it is the full name.
921  *
922  * This takes an array of folders and attaches them together according
923  * to the hierarchy described by their full_names and @separator. If
924  * @namespace is non-%NULL, then it will be ignored as a full_name
925  * prefix, for purposes of comparison. If necessary,
926  * camel_folder_info_build will create additional CamelFolderInfo with
927  * %NULL urls to fill in gaps in the tree. The value of @short_names
928  * is used in constructing the names of these intermediate folders.
929  *
930  * Return value: the top level of the tree of linked folder info.
931  **/
932 CamelFolderInfo *
933 camel_folder_info_build (GPtrArray *folders, const char *namespace,
934                          char separator, gboolean short_names)
935 {
936         CamelFolderInfo *fi, *pfi, *top = NULL, *tail = NULL;
937         GHashTable *hash;
938         char *name, *p, *pname;
939         int i, nlen;
940         
941         if (!namespace)
942                 namespace = "";
943         nlen = strlen (namespace);
944
945         qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), folder_info_cmp);
946         
947         /* Hash the folders. */
948         hash = g_hash_table_new (g_str_hash, g_str_equal);
949         for (i = 0; i < folders->len; i++) {
950                 fi = folders->pdata[i];
951                 if (!strncmp (namespace, fi->full_name, nlen))
952                         name = fi->full_name + nlen;
953                 else
954                         name = fi->full_name;
955                 if (*name == separator)
956                         name++;
957
958                 g_hash_table_insert (hash, g_strdup(name), fi);
959         }
960         
961         /* Now find parents. */
962         for (i = 0; i < folders->len; i++) {
963                 fi = folders->pdata[i];
964                 if (!strncmp (namespace, fi->full_name, nlen))
965                         name = fi->full_name + nlen;
966                 else
967                         name = fi->full_name;
968                 if (*name == separator)
969                         name++;
970
971                 /* set the path if it isn't already set */
972                 if (!fi->path)
973                         camel_folder_info_build_path (fi, separator);
974
975                 p = strrchr (name, separator);
976                 if (p) {
977                         pname = g_strndup (name, p - name);
978                         pfi = g_hash_table_lookup (hash, pname);
979                         if (pfi) {
980                                 g_free (pname);
981                         } else {
982                                 /* we are missing a folder in the heirarchy so
983                                    create a fake folder node */
984                                 CamelURL *url;
985                                 char *sep;
986
987                                 pfi = g_new0 (CamelFolderInfo, 1);
988                                 if (short_names) {
989                                         pfi->name = strrchr (pname, separator);
990                                         if (pfi->name)
991                                                 pfi->name = g_strdup (pfi->name + 1);
992                                         else
993                                                 pfi->name = g_strdup (pname);
994                                 } else
995                                         pfi->name = g_strdup (pname);
996
997                                 /* FIXME: url's with fragments should have the fragment truncated, not path */
998                                 url = camel_url_new (fi->url, NULL);
999                                 sep = strrchr (url->path, separator);
1000                                 if (sep)
1001                                         *sep = '\0';
1002                                 else
1003                                         d(g_warning ("huh, no \"%c\" in \"%s\"?", separator, fi->url));
1004                                 
1005                                 pfi->full_name = g_strdup(url->path+1);
1006
1007                                 /* since this is a "fake" folder node, it is not selectable */
1008                                 camel_url_set_param (url, "noselect", "yes");
1009                                 pfi->url = camel_url_to_string (url, 0);
1010                                 camel_url_free (url);
1011
1012                                 g_hash_table_insert (hash, pname, pfi);
1013                                 g_ptr_array_add (folders, pfi);
1014                         }
1015                         tail = pfi->child;
1016                         if (tail == NULL) {
1017                                 pfi->child = fi;
1018                         } else {
1019                                 while (tail->sibling)
1020                                         tail = tail->sibling;
1021                                 tail->sibling = fi;
1022                         }
1023                         fi->parent = pfi;
1024                 } else if (!top)
1025                         top = fi;
1026         }
1027         g_hash_table_foreach(hash, free_name, NULL);
1028         g_hash_table_destroy (hash);
1029
1030         /* Link together the top-level folders */
1031         tail = top;
1032         for (i = 0; i < folders->len; i++) {
1033                 fi = folders->pdata[i];
1034                 if (fi->parent || fi == top)
1035                         continue;
1036                 if (tail == NULL) {
1037                         tail = fi;
1038                         top = fi;
1039                 } else {
1040                         tail->sibling = fi;
1041                         tail = fi;
1042                 }
1043         }
1044         
1045         return top;
1046 }
1047
1048 static CamelFolderInfo *folder_info_clone_rec(CamelFolderInfo *fi, CamelFolderInfo *parent)
1049 {
1050         CamelFolderInfo *info;
1051
1052         info = g_malloc(sizeof(*info));
1053         info->parent = parent;
1054         info->url = g_strdup(fi->url);
1055         info->name = g_strdup(fi->name);
1056         info->full_name = g_strdup(fi->full_name);
1057         info->path = g_strdup(fi->path);
1058         info->unread_message_count = fi->unread_message_count;
1059
1060         if (fi->sibling)
1061                 info->sibling = folder_info_clone_rec(fi->sibling, parent);
1062         else
1063                 info->sibling = NULL;
1064
1065         if (fi->child)
1066                 info->child = folder_info_clone_rec(fi->child, info);
1067         else
1068                 info->child = NULL;
1069
1070         return info;
1071 }
1072
1073 CamelFolderInfo *
1074 camel_folder_info_clone(CamelFolderInfo *fi)
1075 {
1076         if (fi == NULL)
1077                 return NULL;
1078
1079         return folder_info_clone_rec(fi, NULL);
1080 }
1081
1082 gboolean
1083 camel_store_supports_subscriptions (CamelStore *store)
1084 {
1085         return (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
1086 }
1087
1088
1089 static gboolean
1090 folder_subscribed (CamelStore *store, const char *folder_name)
1091 {
1092         w(g_warning ("CamelStore::folder_subscribed not implemented for `%s'",
1093                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
1094         
1095         return FALSE;
1096 }
1097
1098 /**
1099  * camel_store_folder_subscribed: Tell whether or not a folder has been subscribed to.
1100  * @store: a CamelStore
1101  * @folder_name: the folder on which we're querying subscribed status.
1102  * Return value: TRUE if folder is subscribed, FALSE if not.
1103  **/
1104 gboolean
1105 camel_store_folder_subscribed (CamelStore *store,
1106                                const char *folder_name)
1107 {
1108         gboolean ret;
1109
1110         g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
1111         g_return_val_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS, FALSE);
1112
1113         CAMEL_STORE_LOCK(store, folder_lock);
1114
1115         ret = CS_CLASS (store)->folder_subscribed (store, folder_name);
1116
1117         CAMEL_STORE_UNLOCK(store, folder_lock);
1118
1119         return ret;
1120 }
1121
1122 static void
1123 subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
1124 {
1125         w(g_warning ("CamelStore::subscribe_folder not implemented for `%s'",
1126                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
1127 }
1128
1129 /**
1130  * camel_store_subscribe_folder: marks a folder as subscribed.
1131  * @store: a CamelStore
1132  * @folder_name: the folder to subscribe to.
1133  **/
1134 void
1135 camel_store_subscribe_folder (CamelStore *store,
1136                               const char *folder_name,
1137                               CamelException *ex)
1138 {
1139         g_return_if_fail (CAMEL_IS_STORE (store));
1140         g_return_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
1141
1142         CAMEL_STORE_LOCK(store, folder_lock);
1143
1144         CS_CLASS (store)->subscribe_folder (store, folder_name, ex);
1145
1146         CAMEL_STORE_UNLOCK(store, folder_lock);
1147 }
1148
1149 static void
1150 unsubscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
1151 {
1152         w(g_warning ("CamelStore::unsubscribe_folder not implemented for `%s'",
1153                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
1154 }
1155
1156
1157 /**
1158  * camel_store_unsubscribe_folder: marks a folder as unsubscribed.
1159  * @store: a CamelStore
1160  * @folder_name: the folder to unsubscribe from.
1161  **/
1162 void
1163 camel_store_unsubscribe_folder (CamelStore *store,
1164                                 const char *folder_name,
1165                                 CamelException *ex)
1166 {
1167         CamelFolder *folder = NULL;
1168
1169         g_return_if_fail (CAMEL_IS_STORE (store));
1170         g_return_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
1171
1172         CAMEL_STORE_LOCK(store, folder_lock);
1173
1174         /* NB: Note similarity of this code to delete_folder */
1175
1176         /* if we deleted a folder, force it out of the cache, and also out of the vtrash/vjunk if setup */
1177         if (store->folders) {
1178                 folder = camel_object_bag_get(store->folders, folder_name);
1179                 if (folder) {
1180                         if (store->vtrash)
1181                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vtrash, folder);
1182                         if (store->vjunk)
1183                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vjunk, folder);
1184                         camel_folder_delete (folder);
1185                 }
1186         }
1187
1188         CS_CLASS (store)->unsubscribe_folder (store, folder_name, ex);
1189
1190         if (folder) {
1191                 if (store->folders)
1192                         camel_object_bag_remove(store->folders, folder);
1193
1194                 camel_object_unref(folder);
1195         }
1196
1197         CAMEL_STORE_UNLOCK(store, folder_lock);
1198 }
1199
1200
1201 static void
1202 noop (CamelStore *store, CamelException *ex)
1203 {
1204         /* no-op */
1205         ;
1206 }
1207
1208
1209 /**
1210  * camel_store_noop:
1211  * @store: CamelStore
1212  * @ex: exception
1213  *
1214  * Pings @store so that its connection doesn't timeout.
1215  **/
1216 void
1217 camel_store_noop (CamelStore *store, CamelException *ex)
1218 {
1219         CS_CLASS (store)->noop (store, ex);
1220 }
1221
1222
1223 /* Return true if these uri's refer to the same object */
1224 gboolean
1225 camel_store_uri_cmp(CamelStore *store, const char *uria, const char *urib)
1226 {
1227         g_assert(CAMEL_IS_STORE(store));
1228
1229         return CS_CLASS(store)->compare_folder_name(uria, urib);
1230 }