/* * e-mail-store.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include "e-mail-store.h" #include #include #include #include #include "e-util/e-account-utils.h" #include "mail/e-mail-local.h" #include "mail/em-folder-tree-model.h" #include "mail/em-utils.h" #include "mail/mail-folder-cache.h" #include "mail/mail-mt.h" #include "mail/mail-ops.h" #include "shell/e-shell.h" #include "shell/e-shell-settings.h" typedef struct _StoreInfo StoreInfo; typedef void (*AddStoreCallback) (MailFolderCache *folder_cache, CamelStore *store, CamelFolderInfo *info, StoreInfo *store_info); struct _StoreInfo { gint ref_count; CamelStore *store; /* Hold a reference to keep them alive. */ CamelFolder *vtrash; CamelFolder *vjunk; AddStoreCallback callback; guint removed : 1; }; CamelStore *vfolder_store; /* XXX write a get () function for this */ static GHashTable *store_table; static StoreInfo * store_info_new (CamelStore *store) { StoreInfo *store_info; g_return_val_if_fail (CAMEL_IS_STORE (store), NULL); store_info = g_slice_new0 (StoreInfo); store_info->ref_count = 1; store_info->store = g_object_ref (store); /* If these are vfolders then they need to be opened now, * otherwise they won't keep track of all folders. */ if (store->flags & CAMEL_STORE_VTRASH) store_info->vtrash = camel_store_get_trash_folder_sync (store, NULL, NULL); if (store->flags & CAMEL_STORE_VJUNK) store_info->vjunk = camel_store_get_junk_folder_sync (store, NULL, NULL); return store_info; } static StoreInfo * store_info_ref (StoreInfo *store_info) { g_return_val_if_fail (store_info != NULL, store_info); g_return_val_if_fail (store_info->ref_count > 0, store_info); g_atomic_int_inc (&store_info->ref_count); return store_info; } static void store_info_unref (StoreInfo *store_info) { g_return_if_fail (store_info != NULL); g_return_if_fail (store_info->ref_count > 0); if (g_atomic_int_dec_and_test (&store_info->ref_count)) { g_object_unref (store_info->store); if (store_info->vtrash != NULL) g_object_unref (store_info->vtrash); if (store_info->vjunk != NULL) g_object_unref (store_info->vjunk); g_slice_free (StoreInfo, store_info); } } static void store_table_free (StoreInfo *store_info) { store_info->removed = 1; store_info_unref (store_info); } static gboolean mail_store_note_store_cb (MailFolderCache *folder_cache, CamelStore *store, CamelFolderInfo *info, gpointer user_data) { StoreInfo *store_info = user_data; if (store_info->callback != NULL) store_info->callback ( folder_cache, store, info, store_info); if (!store_info->removed) { /* This keeps message counters up-to-date. */ if (store_info->vtrash != NULL) mail_folder_cache_note_folder ( folder_cache, store_info->vtrash); if (store_info->vjunk != NULL) mail_folder_cache_note_folder ( folder_cache, store_info->vjunk); } store_info_unref (store_info); return TRUE; } static gboolean special_mail_store_is_enabled (CamelStore *store) { CamelService *service; EShell *shell; EShellSettings *shell_settings; const gchar *uid, *prop = NULL; service = CAMEL_SERVICE (store); g_return_val_if_fail (service, FALSE); uid = camel_service_get_uid (service); if (g_strcmp0 (uid, "local") == 0) prop = "mail-enable-local-folders"; else if (g_strcmp0 (uid, "vfolder") == 0) prop = "mail-enable-search-folders"; if (!prop) return TRUE; shell = e_shell_get_default (); shell_settings = e_shell_get_shell_settings (shell); return e_shell_settings_get_boolean (shell_settings, prop); } static void mail_store_add (EMailBackend *backend, CamelStore *store, AddStoreCallback callback) { EMailSession *session; EMFolderTreeModel *default_model; MailFolderCache *folder_cache; StoreInfo *store_info; g_return_if_fail (store_table != NULL); g_return_if_fail (store != NULL); g_return_if_fail (CAMEL_IS_STORE (store)); session = e_mail_backend_get_session (backend); default_model = em_folder_tree_model_get_default (); folder_cache = e_mail_session_get_folder_cache (session); store_info = store_info_new (store); store_info->callback = callback; g_hash_table_insert (store_table, store, store_info); if (special_mail_store_is_enabled (store)) em_folder_tree_model_add_store (default_model, store); mail_folder_cache_note_store ( folder_cache, CAMEL_SESSION (session), store, NULL, mail_store_note_store_cb, store_info_ref (store_info)); } static void mail_store_add_local_done_cb (MailFolderCache *folder_cache, CamelStore *store, CamelFolderInfo *info, StoreInfo *store_info) { CamelFolder *folder; gint ii; for (ii = 0; ii < E_MAIL_NUM_LOCAL_FOLDERS; ii++) { folder = e_mail_local_get_folder (ii); if (folder == NULL) continue; mail_folder_cache_note_folder (folder_cache, folder); } } static void mail_store_load_accounts (EMailBackend *backend, const gchar *data_dir) { CamelStore *local_store; EMailSession *session; EAccountList *account_list; EIterator *iter; session = e_mail_backend_get_session (backend); /* Add the local store. */ e_mail_local_init (session, data_dir); local_store = e_mail_local_get_store (); mail_store_add ( backend, local_store, (AddStoreCallback) mail_store_add_local_done_cb); /* Add mail accounts.. */ account_list = e_get_account_list (); for (iter = e_list_get_iterator ((EList *) account_list); e_iterator_is_valid (iter); e_iterator_next (iter)) { EAccount *account; account = (EAccount *) e_iterator_get (iter); if (!account->enabled) continue; e_mail_store_add_by_account (backend, account); } g_object_unref (iter); } void e_mail_store_init (EMailBackend *backend, const gchar *data_dir) { static gboolean initialized = FALSE; g_return_if_fail (E_IS_MAIL_BACKEND (backend)); /* This function is idempotent because mail * migration code may need to call it early. */ if (initialized) return; /* Initialize global variables. */ store_table = g_hash_table_new_full ( g_direct_hash, g_direct_equal, (GDestroyNotify) NULL, (GDestroyNotify) store_table_free); mail_store_load_accounts (backend, data_dir); initialized = TRUE; } void e_mail_store_add (EMailBackend *backend, CamelStore *store) { g_return_if_fail (E_IS_MAIL_BACKEND (backend)); g_return_if_fail (CAMEL_IS_STORE (store)); mail_store_add (backend, store, NULL); } CamelStore * e_mail_store_add_by_account (EMailBackend *backend, EAccount *account) { EMailSession *session; CamelService *service = NULL; CamelProvider *provider; CamelURL *url; gboolean skip = FALSE, transport_only; GError *error = NULL; g_return_val_if_fail (E_IS_MAIL_BACKEND (backend), NULL); g_return_val_if_fail (E_IS_ACCOUNT (account), NULL); session = e_mail_backend_get_session (backend); /* check whether it's transport-only accounts */ transport_only = !account->source || !account->source->url || !*account->source->url; if (transport_only) goto handle_transport; /* Load the service, but don't connect. Check its provider, * and if this belongs in the folder tree model, add it. */ provider = camel_provider_get (account->source->url, &error); if (provider == NULL) { /* In case we do not have a provider here, we handle * the special case of having multiple mail identities * eg. a dummy account having just SMTP server defined */ goto handle_transport; } service = camel_session_add_service ( CAMEL_SESSION (session), account->uid, account->source->url, CAMEL_PROVIDER_STORE, &error); camel_service_set_display_name (service, account->name); handle_transport: if (account->transport) { /* While we're at it, add the account's transport to the * CamelSession. The transport's UID is a kludge for now. * We take the EAccount's UID and tack on "-transport". */ gchar *transport_uid; GError *transport_error = NULL; transport_uid = g_strconcat ( account->uid, "-transport", NULL); camel_session_add_service ( CAMEL_SESSION (session), transport_uid, account->transport->url, CAMEL_PROVIDER_TRANSPORT, &transport_error); g_free (transport_uid); if (transport_error) { g_warning ( "%s: Failed to add transport service: %s", G_STRFUNC, transport_error->message); g_error_free (transport_error); } } if (transport_only) return NULL; if (!CAMEL_IS_STORE (service)) goto fail; /* Do not add local-delivery files, * but make them ready for later use. */ url = camel_url_new (account->source->url, NULL); if (url != NULL) { skip = em_utils_is_local_delivery_mbox_file (url); camel_url_free (url); } if (!skip && (provider->flags & CAMEL_PROVIDER_IS_STORAGE)) e_mail_store_add (backend, CAMEL_STORE (service)); return CAMEL_STORE (service); fail: /* FIXME: Show an error dialog. */ g_warning ( "Couldn't get service: %s: %s", account->name, error ? error->message : "Not a CamelStore"); if (error) g_error_free (error); return NULL; } void e_mail_store_remove (EMailBackend *backend, CamelStore *store) { EMailSession *session; MailFolderCache *folder_cache; EMFolderTreeModel *default_model; g_return_if_fail (E_IS_MAIL_BACKEND (backend)); g_return_if_fail (CAMEL_IS_STORE (store)); g_return_if_fail (store_table != NULL); session = e_mail_backend_get_session (backend); /* Because the store table holds a reference to each store used * as a key in it, none of them will ever be gc'ed, meaning any * call to camel_session_get_{service,store} with the same URL * will always return the same object. So this works. */ if (g_hash_table_lookup (store_table, store) == NULL) return; g_object_ref (store); g_hash_table_remove (store_table, store); folder_cache = e_mail_session_get_folder_cache (session); mail_folder_cache_note_store_remove (folder_cache, store); default_model = em_folder_tree_model_get_default (); em_folder_tree_model_remove_store (default_model, store); mail_disconnect_store (store); g_object_unref (store); } void e_mail_store_remove_by_account (EMailBackend *backend, EAccount *account) { EMailSession *session; CamelService *service; CamelProvider *provider; const gchar *uid; g_return_if_fail (E_IS_MAIL_BACKEND (backend)); g_return_if_fail (E_IS_ACCOUNT (account)); uid = account->uid; session = e_mail_backend_get_session (backend); service = camel_session_get_service (CAMEL_SESSION (session), uid); g_return_if_fail (CAMEL_IS_STORE (service)); provider = camel_service_get_provider (service); g_return_if_fail (provider != NULL); if (!(provider->flags & CAMEL_PROVIDER_IS_STORAGE)) return; e_mail_store_remove (backend, CAMEL_STORE (service)); } void e_mail_store_foreach (EMailBackend *backend, GFunc func, gpointer user_data) { EMailSession *session; GList *list, *link; /* XXX This is a silly convenience function. * Could probably just get rid of it. */ g_return_if_fail (E_IS_MAIL_BACKEND (backend)); g_return_if_fail (func != NULL); session = e_mail_backend_get_session (backend); list = camel_session_list_services (CAMEL_SESSION (session)); for (link = list; link != NULL; link = g_list_next (link)) { CamelService *service = CAMEL_SERVICE (link->data); if (CAMEL_IS_STORE (service)) func (service, user_data); } g_list_free (list); }