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