c076128c384ec0ae92455aa585d205e8e7876185
[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         return folder;
263 }
264
265 static CamelFolderInfo *
266 create_folder (CamelStore *store, const char *parent_name,
267                const char *folder_name, CamelException *ex)
268 {
269         w(g_warning ("CamelStore::create_folder not implemented for `%s'",
270                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
271         
272         camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_INVALID,
273                               _("Cannot create folder: Invalid operation on this store"));
274         
275         return NULL;
276 }
277
278 /** 
279  * camel_store_create_folder:
280  * @store: a CamelStore
281  * @parent_name: name of the new folder's parent, or %NULL
282  * @folder_name: name of the folder to create
283  * @ex: a CamelException
284  * 
285  * Creates a new folder as a child of an existing folder.
286  * @parent_name can be %NULL to create a new top-level folder.
287  *
288  * Return value: info about the created folder, which the caller must
289  * free with camel_store_free_folder_info().
290  **/
291 CamelFolderInfo *
292 camel_store_create_folder (CamelStore *store, const char *parent_name,
293                            const char *folder_name, CamelException *ex)
294 {
295         CamelFolderInfo *fi;
296
297         CAMEL_STORE_LOCK(store, folder_lock);
298         fi = CS_CLASS (store)->create_folder (store, parent_name, folder_name, ex);
299         CAMEL_STORE_UNLOCK(store, folder_lock);
300
301         return fi;
302 }
303
304
305 static void
306 delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
307 {
308         w(g_warning ("CamelStore::delete_folder not implemented for `%s'",
309                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
310 }
311
312 /** 
313  * camel_store_delete_folder: Delete the folder corresponding to a path.
314  * @store: a CamelStore
315  * @folder_name: name of the folder to delete
316  * @ex: a CamelException
317  * 
318  * Deletes the named folder. The folder must be empty.
319  **/
320 void
321 camel_store_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
322 {
323         CamelFolder *folder = NULL;
324         
325         CAMEL_STORE_LOCK(store, folder_lock);
326
327         /* NB: Note similarity of this code to unsubscribe_folder */
328         
329         /* if we deleted a folder, force it out of the cache, and also out of the vtrash/vjunk if setup */
330         if (store->folders) {
331                 folder = camel_object_bag_get(store->folders, folder_name);
332                 if (folder) {
333                         if (store->vtrash)
334                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vtrash, folder);
335                         if (store->vjunk)
336                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vjunk, folder);
337                         camel_folder_delete (folder);
338                 }
339         }
340
341         CS_CLASS (store)->delete_folder (store, folder_name, ex);
342         
343         if (folder) {
344                 if (store->folders)
345                         camel_object_bag_remove (store->folders, folder);
346                 
347                 camel_object_unref (folder);
348         }
349         
350         CAMEL_STORE_UNLOCK(store, folder_lock);
351 }
352
353 static void
354 rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex)
355 {
356         w(g_warning ("CamelStore::rename_folder not implemented for `%s'",
357                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
358 }
359
360 /**
361  * camel_store_rename_folder:
362  * @store: a CamelStore
363  * @old_name: the current name of the folder
364  * @new_name: the new name of the folder
365  * @ex: a CamelException
366  * 
367  * Rename a named folder to a new name.
368  **/
369 void
370 camel_store_rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex)
371 {
372         CamelFolder *folder;
373         int i, oldlen, namelen;
374         GPtrArray *folders;
375
376         d(printf("store rename folder %s '%s' '%s'\n", ((CamelService *)store)->url->protocol, old_name, new_name));
377
378         if (strcmp(old_name, new_name) == 0)
379                 return;
380
381         oldlen = strlen(old_name);
382
383         CAMEL_STORE_LOCK(store, folder_lock);
384
385         /* If the folder is open (or any subfolders of the open folder)
386            We need to rename them atomically with renaming the actual folder path */
387         if (store->folders) {
388                 folders = camel_object_bag_list(store->folders);
389                 for (i=0;i<folders->len;i++) {
390                         folder = folders->pdata[i];
391                         namelen = strlen(folder->full_name);
392                         if ((namelen == oldlen &&
393                              strcmp(folder->full_name, old_name) == 0)
394                             || ((namelen > oldlen)
395                                 && strncmp(folder->full_name, old_name, oldlen) == 0
396                                 && folder->full_name[oldlen] == store->dir_sep)) {
397                                 d(printf("Found subfolder of '%s' == '%s'\n", old_name, folder->full_name));
398                                 CAMEL_FOLDER_LOCK(folder, lock);
399                         } else {
400                                 g_ptr_array_remove_index_fast(folders, i);
401                                 i--;
402                                 camel_object_unref(folder);
403                         }
404                 }
405         }
406
407         /* Now try the real rename (will emit renamed event) */
408         CS_CLASS (store)->rename_folder (store, old_name, new_name, ex);
409
410         /* If it worked, update all open folders/unlock them */
411         if (!camel_exception_is_set(ex)) {
412                 guint32 flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE;
413                 CamelRenameInfo reninfo;
414
415                 for (i=0;i<folders->len;i++) {
416                         char *new;
417
418                         folder = folders->pdata[i];
419
420                         new = g_strdup_printf("%s%s", new_name, folder->full_name+strlen(old_name));
421                         camel_object_bag_remove(store->folders, folder);
422                         camel_object_bag_add(store->folders, new, folder);
423                         camel_folder_rename(folder, new);
424
425                         CAMEL_FOLDER_UNLOCK(folder, lock);
426                         camel_object_unref(folder);
427                 }
428
429                 /* Emit changed signal */
430                 if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
431                         flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
432                 
433                 reninfo.old_base = (char *)old_name;
434                 reninfo.new = ((CamelStoreClass *)((CamelObject *)store)->klass)->get_folder_info(store, new_name, flags, ex);
435                 if (reninfo.new != NULL) {
436                         camel_object_trigger_event (store, "folder_renamed", &reninfo);
437                         ((CamelStoreClass *)((CamelObject *)store)->klass)->free_folder_info(store, reninfo.new);
438                 }
439         } else {
440                 /* Failed, just unlock our folders for re-use */
441                 for (i=0;i<folders->len;i++) {
442                         folder = folders->pdata[i];
443                         CAMEL_FOLDER_UNLOCK(folder, lock);
444                         camel_object_unref(folder);
445                 }
446         }
447
448         CAMEL_STORE_UNLOCK(store, folder_lock);
449
450         g_ptr_array_free(folders, TRUE);
451 }
452
453
454 static CamelFolder *
455 get_inbox (CamelStore *store, CamelException *ex)
456 {
457         /* Default: assume the inbox's name is "inbox"
458          * and open with default flags.
459          */
460         return CS_CLASS (store)->get_folder (store, "inbox", 0, ex);
461 }
462
463 /** 
464  * camel_store_get_inbox:
465  * @store: a CamelStore
466  * @ex: a CamelException
467  *
468  * Return value: the folder in the store into which new mail is
469  * delivered, or %NULL if no such folder exists.
470  **/
471 CamelFolder *
472 camel_store_get_inbox (CamelStore *store, CamelException *ex)
473 {
474         CamelFolder *folder;
475
476         CAMEL_STORE_LOCK(store, folder_lock);
477         folder = CS_CLASS (store)->get_inbox (store, ex);
478         CAMEL_STORE_UNLOCK(store, folder_lock);
479
480         return folder;
481 }
482
483 static void
484 trash_finalize (CamelObject *trash, gpointer event_data, gpointer user_data)
485 {
486         CamelStore *store = CAMEL_STORE (user_data);
487         
488         store->vtrash = NULL;
489 }
490
491 static void
492 junk_finalize (CamelObject *junk, gpointer event_data, gpointer user_data)
493 {
494         CamelStore *store = CAMEL_STORE (user_data);
495         
496         store->vjunk = NULL;
497 }
498
499 /* FIXME: derive vjunk folder object from vee_folder */
500 #include "camel-vee-store.h"
501 static CamelFolder *
502 camel_vjunk_folder_new (CamelStore *parent_store, const char *name)
503 {
504         CamelFolder *vjunk;
505         
506         vjunk = (CamelFolder *)camel_object_new (camel_vee_folder_get_type ());
507         vjunk->folder_flags |= CAMEL_FOLDER_IS_JUNK;
508         camel_vee_folder_construct (CAMEL_VEE_FOLDER (vjunk), parent_store, name,
509                                     CAMEL_STORE_FOLDER_PRIVATE | CAMEL_STORE_FOLDER_CREATE | CAMEL_STORE_VEE_FOLDER_AUTO);
510         camel_vee_folder_set_expression((CamelVeeFolder *)vjunk, "(match-all (system-flag \"Junk\"))");
511
512         return vjunk;
513 }
514
515 static void
516 init_trash_or_junk (CamelStore *store, CamelFolder *folder, void (*finalize) (CamelObject *o, gpointer event_data, gpointer user_data))
517 {
518         if (folder) {
519                 /* FIXME: this should probably use the object bag or another one ? ... */
520                 /* attach to the finalise event of the vtrash/vjunk */
521                 camel_object_hook_event (folder, "finalize", finalize, store);
522                 
523                 /* add all the pre-opened folders to the vtrash/vjunk */
524                 if (store->folders) {
525                         GPtrArray *folders = camel_object_bag_list(store->folders);
526                         int i;
527
528                         for (i=0;i<folders->len;i++) {
529                                 camel_vee_folder_add_folder (CAMEL_VEE_FOLDER (folder), (CamelFolder *)folders->pdata[i]);
530                                 camel_object_unref(folders->pdata[i]);
531                         }
532                         g_ptr_array_free(folders, TRUE);
533                 }
534         }
535 }
536
537 static void
538 init_trash (CamelStore *store)
539 {
540         if ((store->flags & CAMEL_STORE_VTRASH) == 0)
541                 return;
542
543         store->vtrash = camel_vtrash_folder_new (store, CAMEL_VTRASH_NAME);
544         init_trash_or_junk (store, store->vtrash, trash_finalize);
545 }
546
547 static void
548 init_junk (CamelStore *store)
549 {
550         if ((store->flags & CAMEL_STORE_VJUNK) == 0)
551                 return;
552
553         store->vjunk = camel_vjunk_folder_new (store, CAMEL_VJUNK_NAME);
554         init_trash_or_junk (store, store->vjunk, junk_finalize);
555 }
556
557 static CamelFolder *
558 get_trash (CamelStore *store, CamelException *ex)
559 {
560         if (store->vtrash) {
561                 camel_object_ref (store->vtrash);
562                 return store->vtrash;
563         } else {
564                 CS_CLASS (store)->init_trash (store);
565                 if (store->vtrash) {
566                         /* We don't ref here because we don't want the
567                            store to own a ref on the trash folder */
568                         /*camel_object_ref (store->vtrash);*/
569                         return store->vtrash;
570                 } else {
571                         w(g_warning ("This store does not support vTrash."));
572                         return NULL;
573                 }
574         }
575 }
576
577 static CamelFolder *
578 get_junk (CamelStore *store, CamelException *ex)
579 {
580         if (store->vjunk) {
581                 camel_object_ref (CAMEL_OBJECT (store->vjunk));
582                 return store->vjunk;
583         } else {
584                 CS_CLASS (store)->init_junk (store);
585                 if (store->vjunk) {
586                         /* We don't ref here because we don't want the
587                            store to own a ref on the junk folder */
588                         /*camel_object_ref (CAMEL_OBJECT (store->vjunk));*/
589                         return store->vjunk;
590                 } else {
591                         w(g_warning ("This store does not support vJunk."));
592                         return NULL;
593                 }
594         }
595 }
596
597 /** 
598  * camel_store_get_trash:
599  * @store: a CamelStore
600  * @ex: a CamelException
601  *
602  * Return value: the folder in the store into which trash is
603  * delivered, or %NULL if no such folder exists.
604  **/
605 CamelFolder *
606 camel_store_get_trash (CamelStore *store, CamelException *ex)
607 {
608         CamelFolder *folder;
609
610         if ((store->flags & CAMEL_STORE_VTRASH) == 0)
611                 return NULL;
612         
613         CAMEL_STORE_LOCK(store, folder_lock);
614         folder = CS_CLASS (store)->get_trash (store, ex);
615         CAMEL_STORE_UNLOCK(store, folder_lock);
616         
617         return folder;
618 }
619
620 /** 
621  * camel_store_get_junk:
622  * @store: a CamelStore
623  * @ex: a CamelException
624  *
625  * Return value: the folder in the store into which junk is
626  * delivered, or %NULL if no such folder exists.
627  **/
628 CamelFolder *
629 camel_store_get_junk (CamelStore *store, CamelException *ex)
630 {
631         CamelFolder *folder;
632
633         if ((store->flags & CAMEL_STORE_VJUNK) == 0)
634                 return NULL;
635         
636         CAMEL_STORE_LOCK(store, folder_lock);
637         folder = CS_CLASS (store)->get_junk (store, ex);
638         CAMEL_STORE_UNLOCK(store, folder_lock);
639         
640         return folder;
641 }
642
643 static void
644 store_sync (CamelStore *store, CamelException *ex)
645 {
646         if (store->folders) {
647                 GPtrArray *folders;
648                 CamelFolder *folder;
649                 CamelException x;
650                 int i;
651
652                 camel_exception_init(&x);
653                 folders = camel_object_bag_list(store->folders);
654                 for (i=0;i<folders->len;i++) {
655                         folder = folders->pdata[i];
656                         if (!camel_exception_is_set(&x))
657                                 camel_folder_sync(folder, FALSE, &x);
658                         camel_object_unref(folder);
659                 }
660                 camel_exception_xfer(ex, &x);
661                 g_ptr_array_free(folders, TRUE);
662         }
663 }
664
665 /**
666  * camel_store_sync:
667  * @store: a CamelStore
668  * @ex: a CamelException
669  *
670  * Syncs any changes that have been made to the store object and its
671  * folders with the real store.
672  **/
673 void
674 camel_store_sync (CamelStore *store, CamelException *ex)
675 {
676         g_return_if_fail (CAMEL_IS_STORE (store));
677
678         CS_CLASS (store)->sync (store, ex);
679 }
680
681
682 static CamelFolderInfo *
683 get_folder_info (CamelStore *store, const char *top,
684                  guint32 flags, CamelException *ex)
685 {
686         w(g_warning ("CamelStore::get_folder_info not implemented for `%s'",
687                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
688         
689         return NULL;
690 }
691
692 static void
693 add_special_info (CamelStore *store, CamelFolderInfo *info, const char *name, const char *full_name,
694                   const char *url_base, gboolean unread_count)
695 {
696         CamelFolderInfo *fi, *vinfo, *parent;
697         char *uri, *path;
698         CamelURL *url;
699         
700         g_return_if_fail (info != NULL);
701         
702         parent = NULL;
703         for (fi = info; fi; fi = fi->sibling) {
704                 if (!strcmp (fi->name, name))
705                         break;
706                 parent = fi;
707         }
708         
709         /* create our vTrash/vJunk URL */
710         url = camel_url_new (info->url, NULL);
711         if (((CamelService *) store)->provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH) {
712                 camel_url_set_fragment (url, name);
713         } else {
714                 path = g_strdup_printf ("/%s", name);
715                 camel_url_set_path (url, path);
716                 g_free (path);
717         }
718         
719         uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
720         camel_url_free (url);
721         
722         if (fi) {
723                 /* We're going to replace the physical Trash/Junk folder with our vTrash/vJunk folder */
724                 vinfo = fi;
725                 g_free (vinfo->full_name);
726                 g_free (vinfo->name);
727                 g_free (vinfo->url);
728                 g_free (vinfo->path);
729         } else {
730                 /* There wasn't a Trash/Junk folder so create a new folder entry */
731                 vinfo = g_new0 (CamelFolderInfo, 1);
732                 
733                 g_assert(parent != NULL);
734                 
735                 vinfo->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_SUBSCRIBED;
736                 
737                 /* link it into the right spot */
738                 vinfo->sibling = parent->sibling;
739                 parent->sibling = vinfo;
740         }
741         
742         /* Fill in the new fields */
743         vinfo->full_name = g_strdup (full_name);
744         vinfo->name = g_strdup (vinfo->full_name);
745         vinfo->url = g_strdup_printf ("%s:%s", url_base, uri);
746         if (!unread_count)
747                 vinfo->unread_message_count = -1;
748         vinfo->path = g_strdup_printf ("/%s", vinfo->name);
749         g_free (uri);
750 }
751
752 /**
753  * camel_store_get_folder_info:
754  * @store: a CamelStore
755  * @top: the name of the folder to start from
756  * @flags: various CAMEL_STORE_FOLDER_INFO_* flags to control behavior
757  * @ex: a CamelException
758  *
759  * This fetches information about the folder structure of @store,
760  * starting with @top, and returns a tree of CamelFolderInfo
761  * structures. If @flags includes %CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
762  * only subscribed folders will be listed. (This flag can only be used
763  * for stores that support subscriptions.) If @flags includes
764  * %CAMEL_STORE_FOLDER_INFO_RECURSIVE, the returned tree will include
765  * all levels of hierarchy below @top. If not, it will only include
766  * the immediate subfolders of @top. If @flags includes
767  * %CAMEL_STORE_FOLDER_INFO_FAST, the unread_message_count fields of
768  * some or all of the structures may be set to -1, if the store cannot
769  * determine that information quickly.
770  * 
771  * Return value: a CamelFolderInfo tree, which must be freed with
772  * camel_store_free_folder_info.
773  **/
774 CamelFolderInfo *
775 camel_store_get_folder_info (CamelStore *store, const char *top,
776                              guint32 flags, CamelException *ex)
777 {
778         CamelFolderInfo *info;
779         
780         g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
781         g_return_val_if_fail ((store->flags & CAMEL_STORE_SUBSCRIPTIONS) ||
782                               !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED),
783                               NULL);
784         
785         CAMEL_STORE_LOCK(store, folder_lock);
786         info = CS_CLASS (store)->get_folder_info (store, top, flags, ex);
787         CAMEL_STORE_UNLOCK(store, folder_lock);
788         
789         if (info && (top == NULL || *top == '\0')) {
790                 if (info->url && (store->flags & CAMEL_STORE_VTRASH))
791                         add_special_info (store, info, CAMEL_VTRASH_NAME, _("Trash"), "vtrash", FALSE);
792                 if (info->url && (store->flags & CAMEL_STORE_VJUNK))
793                         add_special_info (store, info, CAMEL_VJUNK_NAME, _("Junk"), "vjunk", TRUE);
794         }
795         
796         return info;
797 }
798
799
800 static void
801 free_folder_info (CamelStore *store, CamelFolderInfo *fi)
802 {
803         w(g_warning ("CamelStore::free_folder_info not implemented for `%s'",
804                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
805 }
806
807 /**
808  * camel_store_free_folder_info:
809  * @store: a CamelStore
810  * @tree: the tree returned by camel_store_get_folder_info()
811  *
812  * Frees the data returned by camel_store_get_folder_info().
813  **/
814 void
815 camel_store_free_folder_info (CamelStore *store, CamelFolderInfo *fi)
816 {
817         g_return_if_fail (CAMEL_IS_STORE (store));
818
819         CS_CLASS (store)->free_folder_info (store, fi);
820 }
821
822 /**
823  * camel_store_free_folder_info_full:
824  * @store: a CamelStore
825  * @tree: the tree returned by camel_store_get_folder_info()
826  *
827  * An implementation for CamelStore::free_folder_info. Frees all
828  * of the data.
829  **/
830 void
831 camel_store_free_folder_info_full (CamelStore *store, CamelFolderInfo *fi)
832 {
833         camel_folder_info_free (fi);
834 }
835
836 /**
837  * camel_store_free_folder_info_nop:
838  * @store: a CamelStore
839  * @tree: the tree returned by camel_store_get_folder_info()
840  *
841  * An implementation for CamelStore::free_folder_info. Does nothing.
842  **/
843 void
844 camel_store_free_folder_info_nop (CamelStore *store, CamelFolderInfo *fi)
845 {
846         ;
847 }
848
849
850 /**
851  * camel_folder_info_free:
852  * @fi: the CamelFolderInfo
853  *
854  * Frees @fi.
855  **/
856 void
857 camel_folder_info_free (CamelFolderInfo *fi)
858 {
859         if (fi) {
860                 camel_folder_info_free (fi->sibling);
861                 camel_folder_info_free (fi->child);
862                 g_free (fi->name);
863                 g_free (fi->full_name);
864                 g_free (fi->path);
865                 g_free (fi->url);
866                 g_free (fi);
867         }
868 }
869
870
871 /**
872  * camel_folder_info_build_path:
873  * @fi: folder info
874  * @separator: directory separator
875  *
876  * Sets the folder info path based on the folder's full name and
877  * directory separator.
878  **/
879 void
880 camel_folder_info_build_path (CamelFolderInfo *fi, char separator)
881 {
882         const char *full_name;
883         char *p;
884         
885         full_name = fi->full_name;
886         while (*full_name == separator)
887                 full_name++;
888         
889         fi->path = g_strdup_printf ("/%s", full_name);
890         if (separator != '/') {
891                 for (p = fi->path; *p; p++) {
892                         if (*p == separator)
893                                 *p = '/';
894                 }
895         }
896 }
897
898 static int
899 folder_info_cmp (const void *ap, const void *bp)
900 {
901         const CamelFolderInfo *a = ((CamelFolderInfo **)ap)[0];
902         const CamelFolderInfo *b = ((CamelFolderInfo **)bp)[0];
903         
904         return strcmp (a->full_name, b->full_name);
905 }
906
907 static void
908 free_name(void *key, void *data, void *user)
909 {
910         g_free(key);
911 }
912
913 /**
914  * camel_folder_info_build:
915  * @folders: an array of CamelFolderInfo
916  * @namespace: an ignorable prefix on the folder names
917  * @separator: the hieararchy separator character
918  * @short_names: %TRUE if the (short) name of a folder is the part after
919  * the last @separator in the full name. %FALSE if it is the full name.
920  *
921  * This takes an array of folders and attaches them together according
922  * to the hierarchy described by their full_names and @separator. If
923  * @namespace is non-%NULL, then it will be ignored as a full_name
924  * prefix, for purposes of comparison. If necessary,
925  * camel_folder_info_build will create additional CamelFolderInfo with
926  * %NULL urls to fill in gaps in the tree. The value of @short_names
927  * is used in constructing the names of these intermediate folders.
928  *
929  * Return value: the top level of the tree of linked folder info.
930  **/
931 CamelFolderInfo *
932 camel_folder_info_build (GPtrArray *folders, const char *namespace,
933                          char separator, gboolean short_names)
934 {
935         CamelFolderInfo *fi, *pfi, *top = NULL, *tail = NULL;
936         GHashTable *hash;
937         char *name, *p, *pname;
938         int i, nlen;
939         
940         if (!namespace)
941                 namespace = "";
942         nlen = strlen (namespace);
943
944         qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), folder_info_cmp);
945         
946         /* Hash the folders. */
947         hash = g_hash_table_new (g_str_hash, g_str_equal);
948         for (i = 0; i < folders->len; i++) {
949                 fi = folders->pdata[i];
950                 if (!strncmp (namespace, fi->full_name, nlen))
951                         name = fi->full_name + nlen;
952                 else
953                         name = fi->full_name;
954                 if (*name == separator)
955                         name++;
956
957                 g_hash_table_insert (hash, g_strdup(name), fi);
958         }
959         
960         /* Now find parents. */
961         for (i = 0; i < folders->len; i++) {
962                 fi = folders->pdata[i];
963                 if (!strncmp (namespace, fi->full_name, nlen))
964                         name = fi->full_name + nlen;
965                 else
966                         name = fi->full_name;
967                 if (*name == separator)
968                         name++;
969
970                 /* set the path if it isn't already set */
971                 if (!fi->path)
972                         camel_folder_info_build_path (fi, separator);
973
974                 p = strrchr (name, separator);
975                 if (p) {
976                         pname = g_strndup (name, p - name);
977                         pfi = g_hash_table_lookup (hash, pname);
978                         if (pfi) {
979                                 g_free (pname);
980                         } else {
981                                 /* we are missing a folder in the heirarchy so
982                                    create a fake folder node */
983                                 CamelURL *url;
984                                 char *sep;
985
986                                 pfi = g_new0 (CamelFolderInfo, 1);
987                                 if (short_names) {
988                                         pfi->name = strrchr (pname, separator);
989                                         if (pfi->name)
990                                                 pfi->name = g_strdup (pfi->name + 1);
991                                         else
992                                                 pfi->name = g_strdup (pname);
993                                 } else
994                                         pfi->name = g_strdup (pname);
995
996                                 /* FIXME: url's with fragments should have the fragment truncated, not path */
997                                 url = camel_url_new (fi->url, NULL);
998                                 sep = strrchr (url->path, separator);
999                                 if (sep)
1000                                         *sep = '\0';
1001                                 else
1002                                         d(g_warning ("huh, no \"%c\" in \"%s\"?", separator, fi->url));
1003                                 
1004                                 pfi->full_name = g_strdup(url->path+1);
1005
1006                                 /* since this is a "fake" folder node, it is not selectable */
1007                                 camel_url_set_param (url, "noselect", "yes");
1008                                 pfi->url = camel_url_to_string (url, 0);
1009                                 camel_url_free (url);
1010
1011                                 g_hash_table_insert (hash, pname, pfi);
1012                                 g_ptr_array_add (folders, pfi);
1013                         }
1014                         tail = pfi->child;
1015                         if (tail == NULL) {
1016                                 pfi->child = fi;
1017                         } else {
1018                                 while (tail->sibling)
1019                                         tail = tail->sibling;
1020                                 tail->sibling = fi;
1021                         }
1022                         fi->parent = pfi;
1023                 } else if (!top)
1024                         top = fi;
1025         }
1026         g_hash_table_foreach(hash, free_name, NULL);
1027         g_hash_table_destroy (hash);
1028
1029         /* Link together the top-level folders */
1030         tail = top;
1031         for (i = 0; i < folders->len; i++) {
1032                 fi = folders->pdata[i];
1033                 if (fi->parent || fi == top)
1034                         continue;
1035                 if (tail == NULL) {
1036                         tail = fi;
1037                         top = fi;
1038                 } else {
1039                         tail->sibling = fi;
1040                         tail = fi;
1041                 }
1042         }
1043         
1044         return top;
1045 }
1046
1047 static CamelFolderInfo *folder_info_clone_rec(CamelFolderInfo *fi, CamelFolderInfo *parent)
1048 {
1049         CamelFolderInfo *info;
1050
1051         info = g_malloc(sizeof(*info));
1052         info->parent = parent;
1053         info->url = g_strdup(fi->url);
1054         info->name = g_strdup(fi->name);
1055         info->full_name = g_strdup(fi->full_name);
1056         info->path = g_strdup(fi->path);
1057         info->unread_message_count = fi->unread_message_count;
1058
1059         if (fi->sibling)
1060                 info->sibling = folder_info_clone_rec(fi->sibling, parent);
1061         else
1062                 info->sibling = NULL;
1063
1064         if (fi->child)
1065                 info->child = folder_info_clone_rec(fi->child, info);
1066         else
1067                 info->child = NULL;
1068
1069         return info;
1070 }
1071
1072 CamelFolderInfo *
1073 camel_folder_info_clone(CamelFolderInfo *fi)
1074 {
1075         if (fi == NULL)
1076                 return NULL;
1077
1078         return folder_info_clone_rec(fi, NULL);
1079 }
1080
1081 gboolean
1082 camel_store_supports_subscriptions (CamelStore *store)
1083 {
1084         return (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
1085 }
1086
1087
1088 static gboolean
1089 folder_subscribed (CamelStore *store, const char *folder_name)
1090 {
1091         w(g_warning ("CamelStore::folder_subscribed not implemented for `%s'",
1092                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
1093         
1094         return FALSE;
1095 }
1096
1097 /**
1098  * camel_store_folder_subscribed: Tell whether or not a folder has been subscribed to.
1099  * @store: a CamelStore
1100  * @folder_name: the folder on which we're querying subscribed status.
1101  * Return value: TRUE if folder is subscribed, FALSE if not.
1102  **/
1103 gboolean
1104 camel_store_folder_subscribed (CamelStore *store,
1105                                const char *folder_name)
1106 {
1107         gboolean ret;
1108
1109         g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
1110         g_return_val_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS, FALSE);
1111
1112         CAMEL_STORE_LOCK(store, folder_lock);
1113
1114         ret = CS_CLASS (store)->folder_subscribed (store, folder_name);
1115
1116         CAMEL_STORE_UNLOCK(store, folder_lock);
1117
1118         return ret;
1119 }
1120
1121 static void
1122 subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
1123 {
1124         w(g_warning ("CamelStore::subscribe_folder not implemented for `%s'",
1125                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
1126 }
1127
1128 /**
1129  * camel_store_subscribe_folder: marks a folder as subscribed.
1130  * @store: a CamelStore
1131  * @folder_name: the folder to subscribe to.
1132  **/
1133 void
1134 camel_store_subscribe_folder (CamelStore *store,
1135                               const char *folder_name,
1136                               CamelException *ex)
1137 {
1138         g_return_if_fail (CAMEL_IS_STORE (store));
1139         g_return_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
1140
1141         CAMEL_STORE_LOCK(store, folder_lock);
1142
1143         CS_CLASS (store)->subscribe_folder (store, folder_name, ex);
1144
1145         CAMEL_STORE_UNLOCK(store, folder_lock);
1146 }
1147
1148 static void
1149 unsubscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
1150 {
1151         w(g_warning ("CamelStore::unsubscribe_folder not implemented for `%s'",
1152                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
1153 }
1154
1155
1156 /**
1157  * camel_store_unsubscribe_folder: marks a folder as unsubscribed.
1158  * @store: a CamelStore
1159  * @folder_name: the folder to unsubscribe from.
1160  **/
1161 void
1162 camel_store_unsubscribe_folder (CamelStore *store,
1163                                 const char *folder_name,
1164                                 CamelException *ex)
1165 {
1166         CamelFolder *folder = NULL;
1167
1168         g_return_if_fail (CAMEL_IS_STORE (store));
1169         g_return_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
1170
1171         CAMEL_STORE_LOCK(store, folder_lock);
1172
1173         /* NB: Note similarity of this code to delete_folder */
1174
1175         /* if we deleted a folder, force it out of the cache, and also out of the vtrash/vjunk if setup */
1176         if (store->folders) {
1177                 folder = camel_object_bag_get(store->folders, folder_name);
1178                 if (folder) {
1179                         if (store->vtrash)
1180                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vtrash, folder);
1181                         if (store->vjunk)
1182                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vjunk, folder);
1183                         camel_folder_delete (folder);
1184                 }
1185         }
1186
1187         CS_CLASS (store)->unsubscribe_folder (store, folder_name, ex);
1188
1189         if (folder) {
1190                 if (store->folders)
1191                         camel_object_bag_remove(store->folders, folder);
1192
1193                 camel_object_unref(folder);
1194         }
1195
1196         CAMEL_STORE_UNLOCK(store, folder_lock);
1197 }
1198
1199
1200 static void
1201 noop (CamelStore *store, CamelException *ex)
1202 {
1203         /* no-op */
1204         ;
1205 }
1206
1207
1208 /**
1209  * camel_store_noop:
1210  * @store: CamelStore
1211  * @ex: exception
1212  *
1213  * Pings @store so that its connection doesn't timeout.
1214  **/
1215 void
1216 camel_store_noop (CamelStore *store, CamelException *ex)
1217 {
1218         CS_CLASS (store)->noop (store, ex);
1219 }
1220
1221
1222 /* Return true if these uri's refer to the same object */
1223 gboolean
1224 camel_store_uri_cmp(CamelStore *store, const char *uria, const char *urib)
1225 {
1226         g_assert(CAMEL_IS_STORE(store));
1227
1228         return CS_CLASS(store)->compare_folder_name(uria, urib);
1229 }