1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-store.c : class for a imap store */
5 * Authors: Michael Zucchi <notzed@ximian.com>
7 * Copyright (C) 2000-2002 Ximian, Inc. (www.ximian.com)
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of version 2 of the GNU General Public
11 * License as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
28 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <netinet/in.h>
41 #include <glib/gstdio.h>
42 #include <glib/gi18n-lib.h>
44 #include <camel/camel-private.h>
46 #include "camel-imapx-store.h"
47 #include "camel-imapx-folder.h"
48 #include "camel-imapx-exception.h"
49 #include "camel-imapx-utils.h"
50 #include "camel-imapx-server.h"
51 #include "camel-imapx-summary.h"
53 /* Specified in RFC 2060 section 2.1 */
56 #define FINFO_REFRESH_INTERVAL 60
58 static CamelOfflineStoreClass *parent_class = NULL;
61 imapx_name_hash(gconstpointer key)
63 if (g_ascii_strcasecmp(key, "INBOX") == 0)
64 return g_str_hash("INBOX");
66 return g_str_hash(key);
70 imapx_name_equal(gconstpointer a, gconstpointer b)
72 gconstpointer aname = a, bname = b;
74 if (g_ascii_strcasecmp(a, "INBOX") == 0)
76 if (g_ascii_strcasecmp(b, "INBOX") == 0)
78 return g_str_equal(aname, bname);
82 imapx_parse_receiving_options (CamelIMAPXStore *istore, CamelURL *url)
84 if (camel_url_get_param (url, "use_lsub"))
85 istore->rec_options |= IMAPX_SUBSCRIPTIONS;
87 if (camel_url_get_param (url, "override_namespace") && camel_url_get_param (url, "namespace")) {
88 istore->rec_options |= IMAPX_OVERRIDE_NAMESPACE;
89 g_free(istore->namespace);
90 istore->namespace = g_strdup (camel_url_get_param (url, "namespace"));
93 if (camel_url_get_param (url, "check_all"))
94 istore->rec_options |= IMAPX_CHECK_ALL;
96 if (camel_url_get_param (url, "check_lsub"))
97 istore->rec_options |= IMAPX_CHECK_LSUB;
99 if (camel_url_get_param (url, "filter")) {
100 istore->rec_options |= IMAPX_FILTER_INBOX;
101 ((CamelStore *) istore)->flags |= CAMEL_STORE_FILTER_INBOX;
104 if (camel_url_get_param (url, "filter_junk"))
105 istore->rec_options |= IMAPX_FILTER_JUNK;
107 if (camel_url_get_param (url, "filter_junk_inbox"))
108 istore->rec_options |= IMAPX_FILTER_JUNK_INBOX;
110 if (camel_url_get_param (url, "use_idle"))
111 istore->rec_options |= IMAPX_USE_IDLE;
115 imapx_construct(CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
118 CamelIMAPXStore *store = (CamelIMAPXStore *)service;
120 CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
121 if (camel_exception_is_set(ex))
124 store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD |
125 CAMEL_URL_HIDE_PARAMS |
126 CAMEL_URL_HIDE_AUTH));
127 imapx_parse_receiving_options (store, service->url);
129 store->summary = camel_imapx_store_summary_new();
130 store->storage_path = camel_session_get_storage_path(session, service, ex);
131 if (store->storage_path) {
132 summary = g_build_filename(store->storage_path, ".ev-store-summary", NULL);
133 camel_store_summary_set_filename((CamelStoreSummary *)store->summary, summary);
134 /* FIXME: need to remove params, passwords, etc */
135 camel_store_summary_set_uri_base((CamelStoreSummary *)store->summary, service->url);
136 camel_store_summary_load((CamelStoreSummary *)store->summary);
140 extern CamelServiceAuthType camel_imapx_password_authtype;
143 imapx_query_auth_types (CamelService *service, CamelException *ex)
145 CamelIMAPXStore *istore = CAMEL_IMAPX_STORE (service);
146 CamelServiceAuthType *authtype;
147 GList *sasl_types, *t, *next;
150 if (CAMEL_OFFLINE_STORE (istore)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
151 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
152 _("You must be working online to complete this operation"));
156 CAMEL_SERVICE_REC_LOCK (istore, connect_lock);
158 if (istore->server == NULL)
159 istore->server = camel_imapx_server_new((CamelStore *)istore, service->url);
161 connected = istore->server->stream != NULL;
163 connected = imapx_connect_to_server (istore->server, ex);
164 CAMEL_SERVICE_REC_UNLOCK (istore, connect_lock);
168 sasl_types = camel_sasl_authtype_list (FALSE);
169 for (t = sasl_types; t; t = next) {
173 if (!g_hash_table_lookup (istore->server->cinfo->auth_types, authtype->authproto)) {
174 sasl_types = g_list_remove_link (sasl_types, t);
179 return g_list_prepend (sasl_types, &camel_imapx_password_authtype);
183 imapx_get_name (CamelService *service, gboolean brief)
186 return g_strdup_printf (_("IMAP server %s"), service->url->host);
188 return g_strdup_printf (_("IMAP service for %s on %s"),
189 service->url->user, service->url->host);
193 imapx_connect (CamelService *service, CamelException *ex)
195 CamelIMAPXStore *istore = (CamelIMAPXStore *)service;
197 /* We never really are 'connected' or 'disconnected' */
198 if (istore->server == NULL)
199 istore->server = camel_imapx_server_new((CamelStore *)istore, service->url);
201 return camel_imapx_server_connect (istore->server, TRUE, ex);
205 imapx_disconnect (CamelService *service, gboolean clean, CamelException *ex)
207 CamelIMAPXStore *istore = CAMEL_IMAPX_STORE (service);
209 CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex);
212 camel_imapx_server_connect(istore->server, FALSE, ex);
218 imapx_get_junk(CamelStore *store, CamelException *ex)
220 CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_junk(store, ex);
223 gchar *state = g_build_filename(((CamelIMAPXStore *)store)->storage_path, "system", "Junk.cmeta", NULL);
225 camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
228 camel_object_state_read(folder);
235 imapx_get_trash (CamelStore *store, CamelException *ex)
237 CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_trash(store, ex);
240 gchar *state = g_build_filename(((CamelIMAPXStore *)store)->storage_path, "system", "Trash.cmeta", NULL);
242 camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
245 camel_object_state_read(folder);
252 imapx_noop (CamelStore *store, CamelException *ex)
254 CamelIMAPXStore *istore = (CamelIMAPXStore *) store;
256 if (CAMEL_OFFLINE_STORE(store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
259 if (istore->server && camel_imapx_server_connect (istore->server, TRUE, ex))
260 camel_imapx_server_noop (istore->server, NULL, ex);
264 imapx_hash_folder_name (gconstpointer key)
266 if (g_ascii_strcasecmp (key, "INBOX") == 0)
267 return g_str_hash ("INBOX");
269 return g_str_hash (key);
273 imapx_compare_folder_name (gconstpointer a, gconstpointer b)
275 gconstpointer aname = a, bname = b;
277 if (g_ascii_strcasecmp (a, "INBOX") == 0)
279 if (g_ascii_strcasecmp (b, "INBOX") == 0)
281 return g_str_equal (aname, bname);
285 get_folder_offline (CamelStore *store, const gchar *folder_name,
286 guint32 flags, CamelException *ex)
288 CamelIMAPXStore *imapx_store = CAMEL_IMAPX_STORE (store);
289 CamelFolder *new_folder = NULL;
292 si = camel_store_summary_path((CamelStoreSummary *)imapx_store->summary, folder_name);
294 gchar *folder_dir, *storage_path;
296 /* Note: Although the INBOX is defined to be case-insensitive in the IMAP RFC
297 * it is still up to the server how to acutally name it in a LIST response. Since
298 * we stored the name as the server provided it us in the summary we take that name
299 * to look up the folder.
300 * But for the on-disk cache we do always capitalize the Inbox no matter what the
303 if (!g_ascii_strcasecmp (folder_name, "INBOX"))
304 folder_name = "INBOX";
306 storage_path = g_strdup_printf("%s/folders", imapx_store->storage_path);
307 folder_dir = imapx_path_to_physical (storage_path, folder_name);
308 g_free(storage_path);
310 new_folder = camel_imapx_folder_new (store, folder_dir, folder_name, ex);
313 camel_store_summary_info_free((CamelStoreSummary *)imapx_store->summary, si);
315 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
316 _("No such folder %s"), folder_name);
323 imapx_get_folder (CamelStore *store, const gchar *folder_name, guint32 flags, CamelException *ex)
327 folder = get_folder_offline(store, folder_name, flags, ex);
328 if (folder == NULL) {
329 camel_exception_setv(ex, 2, "No such folder: %s", folder_name);
337 imapx_get_inbox(CamelStore *store, CamelException *ex)
339 camel_exception_setv(ex, 1, "get_inbox::unimplemented");
344 /* folder_name is path name */
345 static CamelFolderInfo *
346 imapx_build_folder_info (CamelIMAPXStore *imapx_store, const gchar *folder_name)
352 fi = camel_folder_info_new ();
353 fi->full_name = g_strdup(folder_name);
357 url = camel_url_new (imapx_store->base_url, NULL);
359 url->path = g_strdup_printf ("/%s", folder_name);
360 fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
362 name = strrchr (fi->full_name, '/');
364 name = fi->full_name;
367 if (!g_ascii_strcasecmp (fi->full_name, "INBOX"))
368 fi->name = g_strdup (_("Inbox"));
369 /* Do not localize the rest, these are from a server, thus shouldn't be localized */
370 /*else if (!g_ascii_strcasecmp (fi->full_name, "Drafts"))
371 fi->name = g_strdup (_("Drafts"));
372 else if (!g_ascii_strcasecmp (fi->full_name, "Sent"))
373 fi->name = g_strdup (_("Sent"));
374 else if (!g_ascii_strcasecmp (fi->full_name, "Templates"))
375 fi->name = g_strdup (_("Templates"));
376 else if (!g_ascii_strcasecmp (fi->full_name, "Trash"))
377 fi->name = g_strdup (_("Trash"));*/
379 fi->name = g_strdup (name);
385 fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
389 folder = camel_object_bag_peek(store->folders, fi->full_name);
391 CamelIMAPXSummary *ims;
394 ims = (CamelIMAPXSummary *) folder->summary;
396 ims = (CamelIMAPXSummary *) camel_imapx_summary_new (folder, NULL);
398 fi->unread = ((CamelFolderSummary *)ims)->unread_count;
399 fi->total = ((CamelFolderSummary *)ims)->saved_count;
401 if (!folder->summary)
402 camel_object_unref (ims);
403 camel_object_unref(folder);
407 /* imap needs to treat inbox case insensitive */
408 /* we'll assume the names are normalized already */
410 folder_hash(gconstpointer ap)
414 if (g_ascii_strcasecmp(a, "INBOX") == 0)
417 return g_str_hash(a);
421 folder_eq(gconstpointer ap, gconstpointer bp)
426 if (g_ascii_strcasecmp(a, "INBOX") == 0)
428 if (g_ascii_strcasecmp(b, "INBOX") == 0)
431 return g_str_equal(a, b);
435 imapx_match_pattern(CamelIMAPXStoreNamespace *ns, const gchar *pattern, const gchar *name)
449 } else if (p == '%') {
455 } else if (p == '*') {
461 return n == 0 && (p == '%' || p == 0);
465 imapx_unmark_folder_subscribed (CamelIMAPXStore *istore, const gchar *folder_name, gboolean emit_signal, CamelException *ex)
469 si = camel_store_summary_path((CamelStoreSummary *)istore->summary, folder_name);
471 if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
472 si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
473 camel_store_summary_touch((CamelStoreSummary *)istore->summary);
474 camel_store_summary_save((CamelStoreSummary *)istore->summary);
476 camel_store_summary_info_free((CamelStoreSummary *)istore->summary, si);
482 fi = imapx_build_folder_info(istore, folder_name);
483 camel_object_trigger_event (CAMEL_OBJECT (istore), "folder_unsubscribed", fi);
484 camel_folder_info_free (fi);
489 imapx_mark_folder_subscribed (CamelIMAPXStore *istore, const gchar *folder_name, gboolean emit_signal, CamelException *ex)
493 si = camel_store_summary_path((CamelStoreSummary *)istore->summary, folder_name);
495 if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
496 si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
497 camel_store_summary_touch((CamelStoreSummary *)istore->summary);
498 camel_store_summary_save((CamelStoreSummary *)istore->summary);
500 camel_store_summary_info_free((CamelStoreSummary *)istore->summary, si);
506 fi = imapx_build_folder_info(istore, folder_name);
507 camel_object_trigger_event (CAMEL_OBJECT (istore), "folder_subscribed", fi);
508 camel_folder_info_free (fi);
513 imapx_subscribe_folder (CamelStore *store, const gchar *folder_name, gboolean emit_signal, CamelException *ex)
515 CamelIMAPXStore *istore = (CamelIMAPXStore *) store;
517 if (CAMEL_OFFLINE_STORE(store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
520 if (istore->server && camel_imapx_server_connect (istore->server, TRUE, ex))
521 camel_imapx_server_manage_subscription (istore->server, folder_name, TRUE, ex);
523 if (!camel_exception_is_set (ex))
524 imapx_mark_folder_subscribed (istore, folder_name, emit_signal, ex);
528 imapx_unsubscribe_folder (CamelStore *store, const gchar *folder_name, gboolean emit_signal, CamelException *ex)
530 CamelIMAPXStore *istore = (CamelIMAPXStore *) store;
532 if (CAMEL_OFFLINE_STORE(store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
535 if (istore->server && camel_imapx_server_connect (istore->server, TRUE, ex))
536 camel_imapx_server_manage_subscription (istore->server, folder_name, FALSE, ex);
538 if (!camel_exception_is_set (ex))
539 imapx_unmark_folder_subscribed (istore, folder_name, emit_signal, ex);
543 imapx_store_subscribe_folder (CamelStore *store, const gchar *folder_name, CamelException *ex)
545 imapx_subscribe_folder (store, folder_name, TRUE, ex);
549 imapx_store_unsubscribe_folder (CamelStore *store, const gchar *folder_name, CamelException *ex)
551 CamelException eex = CAMEL_EXCEPTION_INITIALISER;
556 imapx_unsubscribe_folder (store, folder_name, TRUE, ex);
560 imapx_delete_folder_from_cache (CamelIMAPXStore *istore, const gchar *folder_name, CamelException *ex)
563 gchar *folder_dir, *storage_path;
566 storage_path = g_strdup_printf ("%s/folders", istore->storage_path);
567 folder_dir = imapx_path_to_physical (storage_path, folder_name);
568 g_free (storage_path);
569 if (g_access (folder_dir, F_OK) != 0) {
574 /* Delete summary and all the data */
575 state_file = g_strdup_printf ("%s/cmeta", folder_dir);
576 g_unlink (state_file);
579 camel_db_delete_folder (((CamelStore *)istore)->cdb_w, folder_name, ex);
580 g_rmdir (folder_dir);
582 state_file = g_strdup_printf("%s/subfolders", folder_dir);
586 g_rmdir (folder_dir);
590 camel_store_summary_remove_path((CamelStoreSummary *)istore->summary, folder_name);
591 camel_store_summary_save((CamelStoreSummary *)istore->summary);
593 fi = imapx_build_folder_info(istore, folder_name);
594 camel_object_trigger_event (CAMEL_OBJECT (istore), "folder_deleted", fi);
595 camel_folder_info_free (fi);
599 imapx_delete_folder (CamelStore *store, const gchar *folder_name, CamelException *ex)
601 CamelIMAPXStore *istore = (CamelIMAPXStore *) store;
603 if (CAMEL_OFFLINE_STORE (store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
604 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
605 _("You must be working online to complete this operation"));
609 if (istore->server && camel_imapx_server_connect (istore->server, TRUE, ex))
610 camel_imapx_server_delete_folder (istore->server, folder_name, ex);
612 if (!camel_exception_is_set (ex))
613 imapx_delete_folder_from_cache (istore, folder_name, ex);
618 rename_folder_info (CamelIMAPXStore *istore, const gchar *old_name, const gchar *new_name, CamelException *ex)
622 gint olen = strlen(old_name);
624 gchar *npath, *nfull;
626 count = camel_store_summary_count((CamelStoreSummary *)istore->summary);
627 for (i=0;i<count;i++) {
628 si = camel_store_summary_index((CamelStoreSummary *)istore->summary, i);
631 path = camel_store_info_path(istore->summary, si);
632 if (strncmp(path, old_name, olen) == 0) {
633 if (strlen(path) > olen)
634 npath = g_strdup_printf("%s/%s", new_name, path+olen+1);
636 npath = g_strdup(new_name);
637 nfull = camel_imapx_store_summary_path_to_full(istore->summary, npath, istore->dir_sep);
639 /* workaround for broken server (courier uses '.') that doesn't rename
640 subordinate folders as required by rfc 2060 */
641 if (istore->dir_sep == '.') {
642 camel_imapx_server_rename_folder (istore->server, path, nfull, ex);
645 camel_store_info_set_string((CamelStoreSummary *)istore->summary, si, CAMEL_STORE_INFO_PATH, npath);
646 camel_store_info_set_string((CamelStoreSummary *)istore->summary, si, CAMEL_IMAPX_STORE_INFO_FULL_NAME, nfull);
648 camel_store_summary_touch((CamelStoreSummary *)istore->summary);
652 camel_store_summary_info_free((CamelStoreSummary *)istore->summary, si);
657 imapx_rename_folder (CamelStore *store, const gchar *old, const gchar *new, CamelException *ex)
659 CamelIMAPXStore *istore = (CamelIMAPXStore *) store;
660 gchar *oldpath, *newpath, *storage_path;
663 if (CAMEL_OFFLINE_STORE (store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
664 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
665 _("You must be working online to complete this operation"));
669 if (istore->rec_options & IMAPX_SUBSCRIPTIONS)
670 imapx_unsubscribe_folder (store, old, FALSE, ex);
672 if (istore->server && camel_imapx_server_connect (istore->server, TRUE, ex))
673 camel_imapx_server_rename_folder (istore->server, old, new, ex);
675 if (camel_exception_is_set (ex)) {
676 imapx_subscribe_folder (store, old, FALSE, ex);
680 /* rename summary, and handle broken server */
681 rename_folder_info(istore, old, new, ex);
683 if (istore->rec_options & IMAPX_SUBSCRIPTIONS)
684 imapx_subscribe_folder (store, new, FALSE, ex);
686 storage_path = g_strdup_printf("%s/folders", istore->storage_path);
687 oldpath = imapx_path_to_physical (storage_path, old);
688 newpath = imapx_path_to_physical (storage_path, new);
689 g_free(storage_path);
691 /* So do we care if this didn't work? Its just a cache? */
692 if (g_rename (oldpath, newpath) == -1) {
693 g_warning ("Could not rename message cache '%s' to '%s': %s: cache reset",
694 oldpath, newpath, g_strerror (errno));
701 static CamelFolderInfo *
702 imapx_create_folder (CamelStore *store, const gchar *parent_name, const gchar *folder_name, CamelException *ex)
706 CamelIMAPXStoreNamespace *ns;
707 CamelIMAPXStore *istore = (CamelIMAPXStore *) store;
708 gchar *real_name, *full_name, *parent_real;
709 CamelFolderInfo *fi = NULL;
712 if (CAMEL_OFFLINE_STORE (store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
713 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
714 _("You must be working online to complete this operation"));
718 if (!(istore->server && camel_imapx_server_connect (istore->server, TRUE, ex)))
724 ns = camel_imapx_store_summary_namespace_find_path (istore->summary, parent_name);
731 while (*c && *c != dir_sep && !strchr ("#%*", *c))
735 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
736 _("The folder name \"%s\" is invalid because it contains the character \"%c\""),
741 parent_real = camel_imapx_store_summary_full_from_path(istore->summary, parent_name);
742 if (parent_real == NULL) {
743 camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
744 _("Unknown parent folder: %s"), parent_name);
748 si = camel_store_summary_path ((CamelStoreSummary *)istore->summary, parent_name);
749 if (si && si->flags & CAMEL_STORE_INFO_FOLDER_NOINFERIORS) {
750 camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
751 _("The parent folder is not allowed to contain subfolders"));
756 camel_store_summary_info_free ((CamelStoreSummary *) istore->summary, si);
758 real_name = camel_imapx_store_summary_path_to_full (istore->summary, folder_name, dir_sep);
759 full_name = imapx_concat (istore, parent_real, real_name);
762 camel_imapx_server_create_folder (istore->server, full_name, ex);
764 if (!camel_exception_is_set (ex)) {
765 CamelIMAPXStoreInfo *si;
767 si = camel_imapx_store_summary_add_from_full(istore->summary, full_name, dir_sep);
768 camel_store_summary_save((CamelStoreSummary *)istore->summary);
769 fi = imapx_build_folder_info(istore, camel_store_info_path(istore->summary, si));
770 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
771 camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", fi);
782 static CamelFolderInfo *
783 get_folder_info_offline (CamelStore *store, const gchar *top,
784 guint32 flags, CamelException *ex)
786 CamelIMAPXStore *imapx_store = CAMEL_IMAPX_STORE (store);
787 gboolean include_inbox = FALSE;
790 gchar *pattern, *name;
793 /* FIXME: obey other flags */
795 folders = g_ptr_array_new ();
797 if (top == NULL || top[0] == '\0') {
798 include_inbox = TRUE;
802 /* get starting point */
804 if (imapx_store->namespace && imapx_store->namespace[0]) {
805 name = g_strdup(imapx_store->summary->namespaces->personal->full_name);
806 top = imapx_store->summary->namespaces->personal->path;
810 name = camel_imapx_store_summary_full_from_path(imapx_store->summary, top);
812 name = camel_imapx_store_summary_path_to_full(imapx_store->summary, top, imapx_store->dir_sep);
815 pattern = imapx_concat(imapx_store, name, "*");
817 /* folder_info_build will insert parent nodes as necessary and mark
818 * them as noselect, which is information we actually don't have at
819 * the moment. So let it do the right thing by bailing out if it's
820 * not a folder we're explicitly interested in. */
822 for (i=0;i<camel_store_summary_count((CamelStoreSummary *)imapx_store->summary);i++) {
823 CamelStoreInfo *si = camel_store_summary_index((CamelStoreSummary *)imapx_store->summary, i);
824 const gchar *full_name;
825 CamelIMAPXStoreNamespace *ns;
830 full_name = camel_imapx_store_info_full_name (imapx_store->summary, si);
831 if (!full_name || !*full_name) {
832 camel_store_summary_info_free ((CamelStoreSummary *)imapx_store->summary, si);
836 ns = camel_imapx_store_summary_namespace_find_full (imapx_store->summary, full_name);
838 /* Modify the checks to see match the namespaces from preferences */
839 if ((g_str_equal (name, full_name)
840 || imapx_match_pattern (ns, pattern, full_name)
841 || (include_inbox && !g_ascii_strcasecmp (full_name, "INBOX")))
842 && ( ((imapx_store->rec_options & IMAPX_SUBSCRIPTIONS) == 0
843 || (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) == 0)
844 || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)
845 || (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST) != 0)) {
847 fi = imapx_build_folder_info(imapx_store, camel_store_info_path((CamelStoreSummary *)imapx_store->summary, si));
848 fi->unread = si->unread;
849 fi->total = si->total;
850 fi->flags = si->flags;
851 /* HACK: some servers report noinferiors for all folders (uw-imapd)
852 We just translate this into nochildren, and let the imap layer enforce
853 it. See create folder */
854 if (fi->flags & CAMEL_FOLDER_NOINFERIORS)
855 fi->flags = (fi->flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN;
857 /* blah, this gets lost somewhere, i can't be bothered finding out why */
858 if (!g_ascii_strcasecmp(fi->full_name, "inbox")) {
859 fi->flags = (fi->flags & ~CAMEL_FOLDER_TYPE_MASK) | CAMEL_FOLDER_TYPE_INBOX;
860 fi->flags |= CAMEL_FOLDER_SYSTEM;
863 if (si->flags & CAMEL_FOLDER_NOSELECT) {
864 CamelURL *url = camel_url_new(fi->uri, NULL);
866 camel_url_set_param (url, "noselect", "yes");
868 fi->uri = camel_url_to_string (url, 0);
869 camel_url_free (url);
871 fill_fi((CamelStore *)imapx_store, fi, 0);
874 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
875 g_ptr_array_add (folders, fi);
877 camel_store_summary_info_free((CamelStoreSummary *)imapx_store->summary, si);
881 fi = camel_folder_info_build (folders, top, '/', TRUE);
882 g_ptr_array_free (folders, TRUE);
889 add_folders_to_summary (CamelIMAPXStore *istore, GPtrArray *folders, GHashTable *table, gboolean subcribed)
893 for (i = 0; i < folders->len; i++) {
894 struct _list_info *li = folders->pdata[i];
895 CamelIMAPXStoreInfo *si;
897 CamelFolderInfo *fi, *hfi;
901 si = camel_imapx_store_summary_add_from_full (istore->summary, li->name, li->separator);
905 new_flags = (si->info.flags & (CAMEL_STORE_INFO_FOLDER_SUBSCRIBED | CAMEL_STORE_INFO_FOLDER_CHECK_FOR_NEW)) |
906 (li->flags & ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED);
908 if (!(istore->server->cinfo->capa & IMAPX_CAPABILITY_NAMESPACE))
909 istore->dir_sep = li->separator;
911 if (si->info.flags != new_flags) {
912 si->info.flags = new_flags;
913 camel_store_summary_touch ((CamelStoreSummary *) istore->summary);
916 fi = camel_folder_info_new ();
917 fi->full_name = g_strdup(camel_store_info_path(istore->summary, si));
918 if (!g_ascii_strcasecmp(fi->full_name, "inbox")) {
919 li->flags |= CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_TYPE_INBOX;
920 fi->name = g_strdup (_("Inbox"));
922 fi->name = g_strdup(camel_store_info_name(istore->summary, si));
924 hfi = g_hash_table_lookup (table, fi->full_name);
927 hfi->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
929 camel_folder_info_free (fi);
933 /* HACK: some servers report noinferiors for all folders (uw-imapd)
934 We just translate this into nochildren, and let the imap layer enforce
935 it. See create folder */
936 if (li->flags & CAMEL_FOLDER_NOINFERIORS)
937 li->flags = (li->flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN;
938 fi->flags = li->flags;
941 fi->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
943 url = camel_url_new (istore->base_url, NULL);
944 path = alloca(strlen(fi->full_name)+2);
945 sprintf(path, "/%s", fi->full_name);
946 camel_url_set_path(url, path);
948 if (li->flags & CAMEL_FOLDER_NOSELECT || fi->name[0] == 0)
949 camel_url_set_param (url, "noselect", "yes");
950 fi->uri = camel_url_to_string (url, 0);
951 camel_url_free (url);
956 g_hash_table_insert (table, fi->full_name, fi);
961 free_list (gpointer data, gpointer user_data)
963 struct _list_info *li = data;
964 imapx_free_list (li);
968 imapx_get_folders_free(gpointer k, gpointer v, gpointer d)
970 camel_folder_info_free(v);
974 fetch_folders_for_pattern (CamelIMAPXStore *istore, const gchar *pattern, guint32 flags, GHashTable *table, CamelException *ex)
976 GPtrArray *folders = NULL;
978 folders = camel_imapx_server_list (istore->server, pattern, flags, ex);
979 if (camel_exception_is_set (ex))
982 add_folders_to_summary (istore, folders, table, (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED));
984 g_ptr_array_foreach (folders, free_list, folders);
985 g_ptr_array_free (folders, TRUE);
989 get_namespaces (CamelIMAPXStore *istore)
991 GSList *namespaces = NULL;
992 CamelIMAPXNamespaceList *nsl = NULL;
994 /* Add code to return the namespaces from preference else all of them */
995 nsl = istore->summary->namespaces;
997 namespaces = g_slist_append (namespaces, nsl->personal);
999 namespaces = g_slist_append (namespaces, nsl->other);
1001 namespaces = g_slist_append (namespaces, nsl->shared);
1007 fetch_folders_for_namespaces (CamelIMAPXStore *istore, const gchar *pattern, gboolean sync, CamelException *ex)
1009 GHashTable *folders = NULL;
1010 GSList *namespaces = NULL, *l;
1012 folders = g_hash_table_new (folder_hash, folder_eq);
1013 namespaces = get_namespaces (istore);
1015 for (l = namespaces; l != NULL; l = g_slist_next (l))
1017 CamelIMAPXStoreNamespace *ns = l->data;
1025 pat = g_strdup ("");
1027 pat = g_strdup_printf ("%s%c", ns->path, ns->sep);
1029 pat = g_strdup (pattern);
1032 flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST;
1034 flags |= CAMEL_STORE_FOLDER_INFO_RECURSIVE;
1035 fetch_folders_for_pattern (istore, pat, flags, folders, ex);
1036 if (camel_exception_is_set (ex)) {
1041 flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
1042 fetch_folders_for_pattern (istore, pat, flags, folders, ex);
1043 if (camel_exception_is_set (ex)) {
1060 g_hash_table_destroy (folders);
1065 sync_folders (CamelIMAPXStore *istore, const gchar *pattern, gboolean sync, CamelException *ex)
1067 GHashTable *folders_from_server;
1070 folders_from_server = fetch_folders_for_namespaces (istore, pattern, sync, ex);
1071 if (camel_exception_is_set (ex))
1074 total = camel_store_summary_count ((CamelStoreSummary *) istore->summary);
1075 for (i = 0; i < total; i++) {
1077 const gchar *full_name;
1078 CamelFolderInfo *fi;
1080 si = camel_store_summary_index ((CamelStoreSummary *) istore->summary, i);
1084 full_name = camel_imapx_store_info_full_name (istore->summary, si);
1085 if (!full_name || !*full_name) {
1086 camel_store_summary_info_free ((CamelStoreSummary *)istore->summary, si);
1090 if (!pattern || !*pattern || imapx_match_pattern (camel_imapx_store_summary_namespace_find_full (istore->summary, full_name), pattern, full_name)) {
1091 if ((fi = g_hash_table_lookup(folders_from_server, camel_store_info_path(istore->summary, si))) != NULL) {
1092 if (((fi->flags ^ si->flags) & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
1093 si->flags = (si->flags & ~CAMEL_FOLDER_SUBSCRIBED) | (fi->flags & CAMEL_FOLDER_SUBSCRIBED);
1094 camel_store_summary_touch((CamelStoreSummary *)istore->summary);
1096 camel_object_trigger_event (CAMEL_OBJECT (istore), "folder_created", fi);
1097 camel_object_trigger_event (CAMEL_OBJECT (istore), "folder_subscribed", fi);
1100 gchar *dup_folder_name = g_strdup (camel_store_info_path (istore->summary, si));
1102 if (dup_folder_name) {
1105 camel_exception_init (&eex);
1106 imapx_unmark_folder_subscribed (istore,dup_folder_name, TRUE, &eex);
1107 imapx_delete_folder_from_cache (istore, dup_folder_name, &eex);
1109 g_free (dup_folder_name);
1110 camel_exception_clear (&eex);
1112 camel_store_summary_remove ((CamelStoreSummary *)istore->summary, si);
1119 camel_store_summary_info_free((CamelStoreSummary *)istore->summary, si);
1122 g_hash_table_foreach (folders_from_server, imapx_get_folders_free, NULL);
1123 g_hash_table_destroy (folders_from_server);
1126 struct _imapx_refresh_msg {
1127 CamelSessionThreadMsg msg;
1134 imapx_refresh_finfo (CamelSession *session, CamelSessionThreadMsg *msg)
1136 struct _imapx_refresh_msg *m = (struct _imapx_refresh_msg *)msg;
1137 CamelIMAPXStore *istore = (CamelIMAPXStore *)m->store;
1139 if (CAMEL_OFFLINE_STORE(istore)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
1142 if (!camel_service_connect((CamelService *)istore, &m->ex))
1145 /* look in all namespaces */
1146 sync_folders (istore, "", FALSE, &m->ex);
1147 camel_store_summary_save ((CamelStoreSummary *)istore->summary);
1151 imapx_refresh_free(CamelSession *session, CamelSessionThreadMsg *msg)
1153 struct _imapx_refresh_msg *m = (struct _imapx_refresh_msg *)msg;
1155 camel_object_unref(m->store);
1156 camel_exception_clear(&m->ex);
1159 static CamelSessionThreadOps imapx_refresh_ops = {
1160 imapx_refresh_finfo,
1165 discover_inbox (CamelStore *store, CamelException *ex)
1168 CamelIMAPXStore *istore = (CamelIMAPXStore *)store;
1170 si = camel_store_summary_path((CamelStoreSummary *) istore->summary, "INBOX");
1171 if (si == NULL || (si->flags & CAMEL_FOLDER_SUBSCRIBED) == 0) {
1172 imapx_subscribe_folder (store, "INBOX", FALSE, ex);
1174 if (!camel_exception_is_set(ex) && !si)
1175 sync_folders (istore, "INBOX", TRUE, ex);
1178 camel_store_summary_info_free((CamelStoreSummary *) istore->summary, si);
1182 static CamelFolderInfo *
1183 imapx_get_folder_info(CamelStore *store, const gchar *top, guint32 flags, CamelException *ex)
1185 CamelIMAPXStore *istore = (CamelIMAPXStore *)store;
1186 CamelFolderInfo * fi= NULL;
1187 gboolean initial_setup = FALSE;
1193 g_mutex_lock (istore->get_finfo_lock);
1195 if (CAMEL_OFFLINE_STORE(store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
1196 fi = get_folder_info_offline (store, top, flags, ex);
1198 g_mutex_unlock (istore->get_finfo_lock);
1202 if (camel_store_summary_count ((CamelStoreSummary *) istore->summary) == 0)
1203 initial_setup = TRUE;
1205 if (!initial_setup && flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) {
1206 time_t now = time (NULL);
1208 if (now - istore->last_refresh_time > FINFO_REFRESH_INTERVAL) {
1209 struct _imapx_refresh_msg *m;
1211 istore->last_refresh_time = time (NULL);
1212 m = camel_session_thread_msg_new(((CamelService *)store)->session, &imapx_refresh_ops, sizeof(*m));
1214 camel_object_ref(store);
1215 camel_exception_init(&m->ex);
1216 camel_session_thread_queue(((CamelService *)store)->session, &m->msg, 0);
1219 fi = get_folder_info_offline (store, top, flags, ex);
1220 g_mutex_unlock (istore->get_finfo_lock);
1224 if (!camel_service_connect((CamelService *)store, ex)) {
1225 g_mutex_unlock (istore->get_finfo_lock);
1229 if (*top && flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST) {
1230 fi = get_folder_info_offline (store, top, flags, ex);
1231 g_mutex_unlock (istore->get_finfo_lock);
1239 name = camel_imapx_store_summary_full_from_path(istore->summary, top);
1241 name = camel_imapx_store_summary_path_to_full(istore->summary, top, istore->dir_sep);
1244 pattern = g_alloca(i+5);
1245 strcpy(pattern, name);
1248 pattern = g_alloca (1);
1252 sync_folders (istore, pattern, TRUE, ex);
1253 if (camel_exception_is_set (ex)) {
1254 g_mutex_unlock (istore->get_finfo_lock);
1258 camel_store_summary_save((CamelStoreSummary *) istore->summary);
1260 /* ensure the INBOX is subscribed if lsub was preferred*/
1261 if (initial_setup && istore->rec_options & IMAPX_SUBSCRIPTIONS)
1262 discover_inbox (store, ex);
1264 fi = get_folder_info_offline (store, top, flags, ex);
1265 g_mutex_unlock (istore->get_finfo_lock);
1270 imapx_can_refresh_folder (CamelStore *store, CamelFolderInfo *info, CamelException *ex)
1274 res = CAMEL_STORE_CLASS(parent_class)->can_refresh_folder (store, info, ex) ||
1275 (camel_url_get_param (((CamelService *)store)->url, "check_all") != NULL) ||
1276 (camel_url_get_param (((CamelService *)store)->url, "check_lsub") != NULL && (info->flags & CAMEL_FOLDER_SUBSCRIBED) != 0);
1278 if (!res && !camel_exception_is_set (ex) && CAMEL_IS_IMAP_STORE (store)) {
1280 CamelStoreSummary *sm = CAMEL_STORE_SUMMARY (((CamelIMAPXStore *)(store))->summary);
1285 si = camel_store_summary_path (sm, info->full_name);
1287 res = (si->flags & CAMEL_STORE_INFO_FOLDER_CHECK_FOR_NEW) != 0 ? TRUE : FALSE;
1289 camel_store_summary_info_free (sm, si);
1297 imapx_folder_subscribed (CamelStore *store, const gchar *folder_name)
1299 CamelIMAPXStore *istore = CAMEL_IMAPX_STORE (store);
1301 gint is_subscribed = FALSE;
1303 si = camel_store_summary_path((CamelStoreSummary *)istore->summary, folder_name);
1305 is_subscribed = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
1306 camel_store_summary_info_free((CamelStoreSummary *)istore->summary, si);
1309 return is_subscribed;
1313 camel_imapx_store_class_init(CamelIMAPXStoreClass *klass)
1315 CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(klass);
1316 CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(klass);
1318 parent_class = CAMEL_OFFLINE_STORE_CLASS (camel_type_get_global_classfuncs (camel_offline_store_get_type ()));
1320 camel_service_class->construct = imapx_construct;
1321 camel_service_class->query_auth_types = imapx_query_auth_types;
1322 camel_service_class->get_name = imapx_get_name;
1323 camel_service_class->connect = imapx_connect;
1324 camel_service_class->disconnect = imapx_disconnect;
1326 camel_store_class->get_trash = imapx_get_trash;
1327 camel_store_class->get_junk = imapx_get_junk;
1328 camel_store_class->noop = imapx_noop;
1329 camel_store_class->get_folder = imapx_get_folder;
1330 camel_store_class->get_inbox = imapx_get_inbox;
1331 camel_store_class->hash_folder_name = imapx_hash_folder_name;
1332 camel_store_class->compare_folder_name = imapx_compare_folder_name;
1334 camel_store_class->can_refresh_folder = imapx_can_refresh_folder;
1335 camel_store_class->create_folder = imapx_create_folder;
1336 camel_store_class->rename_folder = imapx_rename_folder;
1337 camel_store_class->delete_folder = imapx_delete_folder;
1338 camel_store_class->subscribe_folder = imapx_store_subscribe_folder;
1339 camel_store_class->unsubscribe_folder = imapx_store_unsubscribe_folder;
1340 camel_store_class->get_folder_info = imapx_get_folder_info;
1341 camel_store_class->folder_subscribed = imapx_folder_subscribed;
1342 camel_store_class->free_folder_info = camel_store_free_folder_info_full;
1344 ((CamelStoreClass *)klass)->hash_folder_name = imapx_name_hash;
1345 ((CamelStoreClass *)klass)->compare_folder_name = imapx_name_equal;
1349 camel_imapx_store_init (gpointer object, gpointer klass)
1351 CamelStore *store = (CamelStore *) object;
1352 CamelIMAPXStore *istore = CAMEL_IMAPX_STORE (object);
1354 store->flags |= CAMEL_STORE_ASYNC | CAMEL_STORE_SUBSCRIPTIONS;
1355 istore->get_finfo_lock = g_mutex_new ();
1356 istore->last_refresh_time = time (NULL) - (FINFO_REFRESH_INTERVAL + 10);
1357 istore->dir_sep = '/';
1361 imapx_store_finalize(CamelObject *object)
1363 CamelIMAPXStore *imapx_store = CAMEL_IMAPX_STORE (object);
1365 /* force disconnect so we dont have it run later, after we've cleaned up some stuff */
1368 camel_service_disconnect((CamelService *)imapx_store, TRUE, NULL);
1369 g_mutex_free (imapx_store->get_finfo_lock);
1371 if (imapx_store->base_url)
1372 g_free (imapx_store->base_url);
1376 camel_imapx_store_get_type (void)
1378 static CamelType camel_imapx_store_type = CAMEL_INVALID_TYPE;
1380 if (!camel_imapx_store_type) {
1381 camel_imapx_store_type = camel_type_register(camel_offline_store_get_type (),
1383 sizeof (CamelIMAPXStore),
1384 sizeof (CamelIMAPXStoreClass),
1385 (CamelObjectClassInitFunc) camel_imapx_store_class_init,
1387 (CamelObjectInitFunc) camel_imapx_store_init,
1388 imapx_store_finalize);
1391 return camel_imapx_store_type;