/*
* 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 <http://www.gnu.org/licenses/>
*
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "e-mail-store.h"
#include <glib/gi18n.h>
#include <camel/camel.h>
#include <libedataserver/e-account.h>
#include <libedataserver/e-account-list.h>
#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 (EMailSession *session,
CamelStore *store,
AddStoreCallback callback)
{
EMFolderTreeModel *default_model;
MailFolderCache *folder_cache;
StoreInfo *store_info;
g_return_if_fail (store_table != NULL);
g_return_if_fail (CAMEL_IS_STORE (store));
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 (EMailSession *session,
const gchar *data_dir)
{
CamelStore *local_store;
EAccountList *account_list;
EIterator *iter;
/* Add the local store. */
e_mail_local_init (session, data_dir);
local_store = e_mail_local_get_store ();
mail_store_add (
session, 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 (session, account);
}
g_object_unref (iter);
}
void
e_mail_store_init (EMailSession *session,
const gchar *data_dir)
{
static gboolean initialized = FALSE;
g_return_if_fail (E_IS_MAIL_SESSION (session));
/* 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 (session, data_dir);
initialized = TRUE;
}
void
e_mail_store_add (EMailSession *session,
CamelStore *store)
{
g_return_if_fail (E_IS_MAIL_SESSION (session));
g_return_if_fail (CAMEL_IS_STORE (store));
mail_store_add (session, store, NULL);
}
CamelStore *
e_mail_store_add_by_account (EMailSession *session,
EAccount *account)
{
CamelService *service = NULL;
CamelProvider *provider;
CamelURL *url;
gboolean transport_only;
gboolean service_is_local_delivery;
gboolean service_belongs_in_tree_model;
GError *error = NULL;
g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
g_return_val_if_fail (E_IS_ACCOUNT (account), NULL);
/* check whether it's transport-only accounts */
transport_only =
(account->source == NULL) ||
(account->source->url == NULL) ||
(*account->source->url == '\0');
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. */
url = camel_url_new (account->source->url, NULL);
if (url != NULL) {
service_is_local_delivery =
em_utils_is_local_delivery_mbox_file (url);
provider = camel_provider_get (url->protocol, NULL);
camel_url_free (url);
} else {
service_is_local_delivery = FALSE;
provider = NULL;
}
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, provider->protocol,
CAMEL_PROVIDER_STORE, &error);
if (!CAMEL_IS_STORE (service))
goto fail;
camel_service_set_display_name (service, account->name);
service_belongs_in_tree_model =
(provider->flags & CAMEL_PROVIDER_IS_STORAGE) &&
!service_is_local_delivery;
if (service_belongs_in_tree_model && store_table != NULL)
e_mail_store_add (session, CAMEL_STORE (service));
handle_transport:
/* While we're at it, add the account's transport (if it has one)
* to the CamelSession. The transport's UID is a kludge for now.
* We take the EAccount's UID and tack on "-transport". */
if (account->transport) {
GError *transport_error = NULL;
url = camel_url_new (
account->transport->url,
&transport_error);
if (url != NULL) {
provider = camel_provider_get (
url->protocol, &transport_error);
camel_url_free (url);
} else
provider = NULL;
if (provider != NULL) {
gchar *transport_uid;
transport_uid = g_strconcat (
account->uid, "-transport", NULL);
camel_session_add_service (
CAMEL_SESSION (session),
transport_uid, provider->protocol,
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;
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 (EMailSession *session,
CamelStore *store)
{
MailFolderCache *folder_cache;
EMFolderTreeModel *default_model;
g_return_if_fail (E_IS_MAIL_SESSION (session));
g_return_if_fail (CAMEL_IS_STORE (store));
g_return_if_fail (store_table != NULL);
/* 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 (EMailSession *session,
EAccount *account)
{
CamelService *service;
CamelProvider *provider;
const gchar *uid;
g_return_if_fail (E_IS_MAIL_SESSION (session));
g_return_if_fail (E_IS_ACCOUNT (account));
uid = account->uid;
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) || store_table == NULL)
return;
e_mail_store_remove (session, CAMEL_STORE (service));
}
void
e_mail_store_foreach (EMailSession *session,
GFunc func,
gpointer user_data)
{
GList *list, *link;
/* XXX This is a silly convenience function.
* Could probably just get rid of it. */
g_return_if_fail (E_IS_MAIL_SESSION (session));
g_return_if_fail (func != NULL);
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);
}