Enable GUI option for 'custom command' connection. Don't g_free strings in
[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 /**
693  * camel_store_get_folder_info:
694  * @store: a CamelStore
695  * @top: the name of the folder to start from
696  * @flags: various CAMEL_STORE_FOLDER_INFO_* flags to control behavior
697  * @ex: a CamelException
698  *
699  * This fetches information about the folder structure of @store,
700  * starting with @top, and returns a tree of CamelFolderInfo
701  * structures. If @flags includes %CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
702  * only subscribed folders will be listed. (This flag can only be used
703  * for stores that support subscriptions.) If @flags includes
704  * %CAMEL_STORE_FOLDER_INFO_RECURSIVE, the returned tree will include
705  * all levels of hierarchy below @top. If not, it will only include
706  * the immediate subfolders of @top. If @flags includes
707  * %CAMEL_STORE_FOLDER_INFO_FAST, the unread_message_count fields of
708  * some or all of the structures may be set to -1, if the store cannot
709  * determine that information quickly.
710  * 
711  * Return value: a CamelFolderInfo tree, which must be freed with
712  * camel_store_free_folder_info.
713  **/
714 CamelFolderInfo *
715 camel_store_get_folder_info (CamelStore *store, const char *top,
716                              guint32 flags, CamelException *ex)
717 {
718         CamelFolderInfo *ret;
719
720         g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
721         g_return_val_if_fail ((store->flags & CAMEL_STORE_SUBSCRIPTIONS) ||
722                               !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED),
723                               NULL);
724
725         CAMEL_STORE_LOCK(store, folder_lock);
726         ret = CS_CLASS (store)->get_folder_info (store, top, flags, ex);
727         CAMEL_STORE_UNLOCK(store, folder_lock);
728
729         return ret;
730 }
731
732
733 static void
734 free_folder_info (CamelStore *store, CamelFolderInfo *fi)
735 {
736         w(g_warning ("CamelStore::free_folder_info not implemented for `%s'",
737                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
738 }
739
740 /**
741  * camel_store_free_folder_info:
742  * @store: a CamelStore
743  * @tree: the tree returned by camel_store_get_folder_info()
744  *
745  * Frees the data returned by camel_store_get_folder_info().
746  **/
747 void
748 camel_store_free_folder_info (CamelStore *store, CamelFolderInfo *fi)
749 {
750         g_return_if_fail (CAMEL_IS_STORE (store));
751
752         CS_CLASS (store)->free_folder_info (store, fi);
753 }
754
755 /**
756  * camel_store_free_folder_info_full:
757  * @store: a CamelStore
758  * @tree: the tree returned by camel_store_get_folder_info()
759  *
760  * An implementation for CamelStore::free_folder_info. Frees all
761  * of the data.
762  **/
763 void
764 camel_store_free_folder_info_full (CamelStore *store, CamelFolderInfo *fi)
765 {
766         camel_folder_info_free (fi);
767 }
768
769 /**
770  * camel_store_free_folder_info_nop:
771  * @store: a CamelStore
772  * @tree: the tree returned by camel_store_get_folder_info()
773  *
774  * An implementation for CamelStore::free_folder_info. Does nothing.
775  **/
776 void
777 camel_store_free_folder_info_nop (CamelStore *store, CamelFolderInfo *fi)
778 {
779         ;
780 }
781
782
783 /**
784  * camel_folder_info_free:
785  * @fi: the CamelFolderInfo
786  *
787  * Frees @fi.
788  **/
789 void
790 camel_folder_info_free (CamelFolderInfo *fi)
791 {
792         if (fi) {
793                 camel_folder_info_free (fi->sibling);
794                 camel_folder_info_free (fi->child);
795                 g_free (fi->name);
796                 g_free (fi->full_name);
797                 g_free (fi->path);
798                 g_free (fi->url);
799                 g_free (fi);
800         }
801 }
802
803
804 /**
805  * camel_folder_info_build_path:
806  * @fi: folder info
807  * @separator: directory separator
808  *
809  * Sets the folder info path based on the folder's full name and
810  * directory separator.
811  **/
812 void
813 camel_folder_info_build_path (CamelFolderInfo *fi, char separator)
814 {
815         const char *full_name;
816         char *p;
817         
818         full_name = fi->full_name;
819         while (*full_name == separator)
820                 full_name++;
821         
822         fi->path = g_strdup_printf ("/%s", full_name);
823         if (separator != '/') {
824                 for (p = fi->path; *p; p++) {
825                         if (*p == separator)
826                                 *p = '/';
827                 }
828         }
829 }
830
831 static int
832 folder_info_cmp (const void *ap, const void *bp)
833 {
834         const CamelFolderInfo *a = ((CamelFolderInfo **)ap)[0];
835         const CamelFolderInfo *b = ((CamelFolderInfo **)bp)[0];
836         
837         return strcmp (a->full_name, b->full_name);
838 }
839
840 static void
841 free_name(void *key, void *data, void *user)
842 {
843         g_free(key);
844 }
845
846 /**
847  * camel_folder_info_build:
848  * @folders: an array of CamelFolderInfo
849  * @namespace: an ignorable prefix on the folder names
850  * @separator: the hieararchy separator character
851  * @short_names: %TRUE if the (short) name of a folder is the part after
852  * the last @separator in the full name. %FALSE if it is the full name.
853  *
854  * This takes an array of folders and attaches them together according
855  * to the hierarchy described by their full_names and @separator. If
856  * @namespace is non-%NULL, then it will be ignored as a full_name
857  * prefix, for purposes of comparison. If necessary,
858  * camel_folder_info_build will create additional CamelFolderInfo with
859  * %NULL urls to fill in gaps in the tree. The value of @short_names
860  * is used in constructing the names of these intermediate folders.
861  *
862  * Return value: the top level of the tree of linked folder info.
863  **/
864 CamelFolderInfo *
865 camel_folder_info_build (GPtrArray *folders, const char *namespace,
866                          char separator, gboolean short_names)
867 {
868         CamelFolderInfo *fi, *pfi, *top = NULL, *tail = NULL;
869         GHashTable *hash;
870         char *name, *p, *pname;
871         int i, nlen;
872         
873         if (!namespace)
874                 namespace = "";
875         nlen = strlen (namespace);
876
877         qsort (folders->pdata, folders->len, sizeof (folders->pdata[0]), folder_info_cmp);
878         
879         /* Hash the folders. */
880         hash = g_hash_table_new (g_str_hash, g_str_equal);
881         for (i = 0; i < folders->len; i++) {
882                 fi = folders->pdata[i];
883                 if (!strncmp (namespace, fi->full_name, nlen))
884                         name = fi->full_name + nlen;
885                 else
886                         name = fi->full_name;
887                 if (*name == separator)
888                         name++;
889                 g_hash_table_insert (hash, g_strdup(name), fi);
890         }
891         
892         /* Now find parents. */
893         for (i = 0; i < folders->len; i++) {
894                 fi = folders->pdata[i];
895                 if (!strncmp (namespace, fi->full_name, nlen))
896                         name = fi->full_name + nlen;
897                 else
898                         name = fi->full_name;
899                 if (*name == separator)
900                         name++;
901
902                 /* set the path if it isn't already set */
903                 if (!fi->path)
904                         camel_folder_info_build_path (fi, separator);
905
906                 p = strrchr (name, separator);
907                 if (p) {
908                         pname = g_strndup (name, p - name);
909                         pfi = g_hash_table_lookup (hash, pname);
910                         if (pfi) {
911                                 g_free (pname);
912                         } else {
913                                 /* we are missing a folder in the heirarchy so
914                                    create a fake folder node */
915                                 CamelURL *url;
916                                 char *sep;
917
918                                 pfi = g_new0 (CamelFolderInfo, 1);
919                                 if (short_names) {
920                                         pfi->name = strrchr (pname, separator);
921                                         if (pfi->name)
922                                                 pfi->name = g_strdup (pfi->name + 1);
923                                         else
924                                                 pfi->name = g_strdup (pname);
925                                 } else
926                                         pfi->name = g_strdup (pname);
927
928                                 /* FIXME: url's with fragments should have the fragment truncated, not path */
929                                 url = camel_url_new (fi->url, NULL);
930                                 sep = strrchr (url->path, separator);
931                                 if (sep)
932                                         *sep = '\0';
933                                 else
934                                         d(g_warning ("huh, no \"%c\" in \"%s\"?", separator, fi->url));
935                                 
936                                 pfi->full_name = g_strdup(url->path+1);
937
938                                 /* since this is a "fake" folder node, it is not selectable */
939                                 camel_url_set_param (url, "noselect", "yes");
940                                 pfi->url = camel_url_to_string (url, 0);
941                                 camel_url_free (url);
942
943                                 g_hash_table_insert (hash, pname, pfi);
944                                 g_ptr_array_add (folders, pfi);
945                         }
946                         tail = pfi->child;
947                         if (tail == NULL) {
948                                 pfi->child = fi;
949                         } else {
950                                 while (tail->sibling)
951                                         tail = tail->sibling;
952                                 tail->sibling = fi;
953                         }
954                         fi->parent = pfi;
955                 } else if (!top)
956                         top = fi;
957         }
958         g_hash_table_foreach(hash, free_name, NULL);
959         g_hash_table_destroy (hash);
960
961         /* Link together the top-level folders */
962         tail = top;
963         for (i = 0; i < folders->len; i++) {
964                 fi = folders->pdata[i];
965                 if (fi->parent || fi == top)
966                         continue;
967                 if (tail == NULL) {
968                         tail = fi;
969                         top = fi;
970                 } else {
971                         tail->sibling = fi;
972                         tail = fi;
973                 }
974         }
975         
976         return top;
977 }
978
979 static CamelFolderInfo *folder_info_clone_rec(CamelFolderInfo *fi, CamelFolderInfo *parent)
980 {
981         CamelFolderInfo *info;
982
983         info = g_malloc(sizeof(*info));
984         info->parent = parent;
985         info->url = g_strdup(fi->url);
986         info->name = g_strdup(fi->name);
987         info->full_name = g_strdup(fi->full_name);
988         info->path = g_strdup(fi->path);
989         info->unread_message_count = fi->unread_message_count;
990
991         if (fi->sibling)
992                 info->sibling = folder_info_clone_rec(fi->sibling, parent);
993         else
994                 info->sibling = NULL;
995
996         if (fi->child)
997                 info->child = folder_info_clone_rec(fi->child, info);
998         else
999                 info->child = NULL;
1000
1001         return info;
1002 }
1003
1004 CamelFolderInfo *
1005 camel_folder_info_clone(CamelFolderInfo *fi)
1006 {
1007         if (fi == NULL)
1008                 return NULL;
1009
1010         return folder_info_clone_rec(fi, NULL);
1011 }
1012
1013 gboolean
1014 camel_store_supports_subscriptions (CamelStore *store)
1015 {
1016         return (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
1017 }
1018
1019
1020 static gboolean
1021 folder_subscribed (CamelStore *store, const char *folder_name)
1022 {
1023         w(g_warning ("CamelStore::folder_subscribed not implemented for `%s'",
1024                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
1025         
1026         return FALSE;
1027 }
1028
1029 /**
1030  * camel_store_folder_subscribed: Tell whether or not a folder has been subscribed to.
1031  * @store: a CamelStore
1032  * @folder_name: the folder on which we're querying subscribed status.
1033  * Return value: TRUE if folder is subscribed, FALSE if not.
1034  **/
1035 gboolean
1036 camel_store_folder_subscribed (CamelStore *store,
1037                                const char *folder_name)
1038 {
1039         gboolean ret;
1040
1041         g_return_val_if_fail (CAMEL_IS_STORE (store), FALSE);
1042         g_return_val_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS, FALSE);
1043
1044         CAMEL_STORE_LOCK(store, folder_lock);
1045
1046         ret = CS_CLASS (store)->folder_subscribed (store, folder_name);
1047
1048         CAMEL_STORE_UNLOCK(store, folder_lock);
1049
1050         return ret;
1051 }
1052
1053 static void
1054 subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
1055 {
1056         w(g_warning ("CamelStore::subscribe_folder not implemented for `%s'",
1057                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
1058 }
1059
1060 /**
1061  * camel_store_subscribe_folder: marks a folder as subscribed.
1062  * @store: a CamelStore
1063  * @folder_name: the folder to subscribe to.
1064  **/
1065 void
1066 camel_store_subscribe_folder (CamelStore *store,
1067                               const char *folder_name,
1068                               CamelException *ex)
1069 {
1070         g_return_if_fail (CAMEL_IS_STORE (store));
1071         g_return_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
1072
1073         CAMEL_STORE_LOCK(store, folder_lock);
1074
1075         CS_CLASS (store)->subscribe_folder (store, folder_name, ex);
1076
1077         CAMEL_STORE_UNLOCK(store, folder_lock);
1078 }
1079
1080 static void
1081 unsubscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
1082 {
1083         w(g_warning ("CamelStore::unsubscribe_folder not implemented for `%s'",
1084                      camel_type_to_name (CAMEL_OBJECT_GET_TYPE (store))));
1085 }
1086
1087
1088 /**
1089  * camel_store_unsubscribe_folder: marks a folder as unsubscribed.
1090  * @store: a CamelStore
1091  * @folder_name: the folder to unsubscribe from.
1092  **/
1093 void
1094 camel_store_unsubscribe_folder (CamelStore *store,
1095                                 const char *folder_name,
1096                                 CamelException *ex)
1097 {
1098         CamelFolder *folder = NULL;
1099
1100         g_return_if_fail (CAMEL_IS_STORE (store));
1101         g_return_if_fail (store->flags & CAMEL_STORE_SUBSCRIPTIONS);
1102
1103         CAMEL_STORE_LOCK(store, folder_lock);
1104
1105         /* NB: Note similarity of this code to delete_folder */
1106
1107         /* if we deleted a folder, force it out of the cache, and also out of the vtrash/vjunk if setup */
1108         if (store->folders) {
1109                 folder = camel_object_bag_get(store->folders, folder_name);
1110                 if (folder) {
1111                         if (store->vtrash)
1112                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vtrash, folder);
1113                         if (store->vjunk)
1114                                 camel_vee_folder_remove_folder((CamelVeeFolder *)store->vjunk, folder);
1115                         camel_folder_delete (folder);
1116                 }
1117         }
1118
1119         CS_CLASS (store)->unsubscribe_folder (store, folder_name, ex);
1120
1121         if (folder) {
1122                 if (store->folders)
1123                         camel_object_bag_remove(store->folders, folder);
1124
1125                 camel_object_unref(folder);
1126         }
1127
1128         CAMEL_STORE_UNLOCK(store, folder_lock);
1129 }
1130
1131
1132 static void
1133 noop (CamelStore *store, CamelException *ex)
1134 {
1135         /* no-op */
1136         ;
1137 }
1138
1139
1140 /**
1141  * camel_store_noop:
1142  * @store: CamelStore
1143  * @ex: exception
1144  *
1145  * Pings @store so that its connection doesn't timeout.
1146  **/
1147 void
1148 camel_store_noop (CamelStore *store, CamelException *ex)
1149 {
1150         CS_CLASS (store)->noop (store, ex);
1151 }
1152
1153
1154 /* Return true if these uri's refer to the same object */
1155 gboolean
1156 camel_store_uri_cmp(CamelStore *store, const char *uria, const char *urib)
1157 {
1158         g_assert(CAMEL_IS_STORE(store));
1159
1160         return CS_CLASS(store)->compare_folder_name(uria, urib);
1161 }