/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-provider.c: provider framework
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library 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.
*
* This library 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 this library. If not, see .
*
* Authors: Bertrand Guiheneuf
* Dan Winship
* Jeffrey Stedfast
*/
/* FIXME: Shouldn't we add a version number to providers ? */
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include "camel-provider.h"
#include "camel-string-utils.h"
#include "camel-vee-store.h"
#include "camel-win32.h"
/* table of CamelProviderModule's */
static GHashTable *module_table;
/* table of CamelProvider's */
static GHashTable *provider_table;
static GRecMutex provider_lock;
#define LOCK() (g_rec_mutex_lock(&provider_lock))
#define UNLOCK() (g_rec_mutex_unlock(&provider_lock))
/* The vfolder provider is always available */
static CamelProvider vee_provider = {
"vfolder",
N_("Virtual folder email provider"),
N_("For reading mail as a query of another set of folders"),
"vfolder",
CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_IS_LOCAL,
CAMEL_URL_NEED_PATH | CAMEL_URL_PATH_IS_ABSOLUTE | CAMEL_URL_FRAGMENT_IS_PATH,
NULL, /* extra conf */
NULL, /* port providers */
/* ... */
};
static GOnce setup_once = G_ONCE_INIT;
static void
provider_register_internal (CamelProvider *provider)
{
CamelProviderConfEntry *conf;
CamelProviderPortEntry *port;
GList *link;
gint ii;
g_return_if_fail (provider != NULL);
g_return_if_fail (provider->protocol != NULL);
LOCK ();
if (g_hash_table_lookup (provider_table, provider->protocol) != NULL) {
g_warning (
"Trying to re-register CamelProvider for protocol '%s'",
provider->protocol);
UNLOCK ();
return;
}
/* Translate all strings here */
#define P_(string) dgettext (provider->translation_domain, string)
provider->name = P_(provider->name);
provider->description = P_(provider->description);
conf = provider->extra_conf;
if (conf != NULL) {
for (ii = 0; conf[ii].type != CAMEL_PROVIDER_CONF_END; ii++) {
if (conf[ii].text != NULL)
conf[ii].text = P_(conf[ii].text);
}
}
for (link = provider->authtypes; link != NULL; link = link->next) {
CamelServiceAuthType *auth = link->data;
auth->name = P_(auth->name);
auth->description = P_(auth->description);
}
if (provider->port_entries != NULL) {
provider->url_flags |= CAMEL_URL_NEED_PORT;
port = provider->port_entries;
for (ii = 0; port[ii].port != 0; ii++)
if (port[ii].desc != NULL)
port[ii].desc = P_(port[ii].desc);
} else {
provider->url_flags &= ~CAMEL_URL_NEED_PORT;
}
g_hash_table_insert (
provider_table,
(gpointer) provider->protocol, provider);
UNLOCK ();
}
static gpointer
provider_setup (gpointer param)
{
module_table = g_hash_table_new (
(GHashFunc) camel_strcase_hash,
(GEqualFunc) camel_strcase_equal);
provider_table = g_hash_table_new (
(GHashFunc) camel_strcase_hash,
(GEqualFunc) camel_strcase_equal);
vee_provider.object_types[CAMEL_PROVIDER_STORE] = CAMEL_TYPE_VEE_STORE;
vee_provider.url_hash = camel_url_hash;
vee_provider.url_equal = camel_url_equal;
provider_register_internal (&vee_provider);
return NULL;
}
/**
* camel_provider_init:
*
* Initialize the Camel provider system by reading in the .urls
* files in the provider directory and creating a hash table mapping
* URLs to module names.
*
* A .urls file has the same initial prefix as the shared library it
* correspond to, and consists of a series of lines containing the URL
* protocols that that library handles.
*
* TODO: This should be pathed?
* TODO: This should be plugin-d?
**/
void
camel_provider_init (void)
{
GDir *dir;
const gchar *entry;
gchar *p, *name, buf[80];
CamelProviderModule *m;
static gint loaded = 0;
const gchar *provider_dir;
provider_dir = g_getenv (EDS_CAMEL_PROVIDER_DIR);
if (!provider_dir)
provider_dir = CAMEL_PROVIDERDIR;
g_once (&setup_once, provider_setup, NULL);
if (loaded)
return;
loaded = 1;
dir = g_dir_open (provider_dir, 0, NULL);
if (!dir) {
g_warning (
"Could not open camel provider directory (%s): %s",
provider_dir, g_strerror (errno));
return;
}
while ((entry = g_dir_read_name (dir))) {
FILE *fp;
p = strrchr (entry, '.');
if (!p || strcmp (p, ".urls") != 0)
continue;
name = g_strdup_printf ("%s/%s", provider_dir, entry);
fp = g_fopen (name, "r");
if (!fp) {
g_warning (
"Could not read provider info file %s: %s",
name, g_strerror (errno));
g_free (name);
continue;
}
p = strrchr (name, '.');
if (p)
strcpy (p, "." G_MODULE_SUFFIX);
m = g_malloc0 (sizeof (*m));
m->path = name;
while ((fgets (buf, sizeof (buf), fp))) {
buf[sizeof (buf) - 1] = '\0';
p = strchr (buf, '\n');
if (p)
*p = '\0';
if (*buf) {
gchar *protocol = g_strdup (buf);
m->types = g_slist_prepend (m->types, protocol);
g_hash_table_insert (module_table, protocol, m);
}
}
fclose (fp);
}
g_dir_close (dir);
}
/**
* camel_provider_load:
* @path: the path to a shared library
* @error: return location for a #GError, or %NULL
*
* Loads the provider at @path, and calls its initialization function,
* passing @session as an argument. The provider should then register
* itself with @session.
*
* Returns: %TRUE on success, %FALSE on failure
**/
gboolean
camel_provider_load (const gchar *path,
GError **error)
{
GModule *module;
CamelProvider *(*provider_module_init) (void);
g_once (&setup_once, provider_setup, NULL);
if (!g_module_supported ()) {
g_set_error (
error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Could not load %s: Module loading "
"not supported on this system."), path);
return FALSE;
}
module = g_module_open (path, G_MODULE_BIND_LAZY);
if (module == NULL) {
g_set_error (
error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Could not load %s: %s"),
path, g_module_error ());
return FALSE;
}
if (!g_module_symbol (module, "camel_provider_module_init",
(gpointer *) &provider_module_init)) {
g_set_error (
error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Could not load %s: No initialization "
"code in module."), path);
g_module_close (module);
return FALSE;
}
provider_module_init ();
return TRUE;
}
/**
* camel_provider_register:
* @provider: provider object
*
* Registers a provider.
**/
void
camel_provider_register (CamelProvider *provider)
{
g_once (&setup_once, provider_setup, NULL);
provider_register_internal (provider);
}
static gint
provider_compare (gconstpointer a,
gconstpointer b)
{
const CamelProvider *cpa = (const CamelProvider *) a;
const CamelProvider *cpb = (const CamelProvider *) b;
return strcmp (cpa->name, cpb->name);
}
static void
add_to_list (gpointer key,
gpointer value,
gpointer user_data)
{
GList **list = user_data;
*list = g_list_prepend(*list, value);
}
/**
* camel_provider_list:
* @load: whether or not to load in providers that are not already loaded
*
* This returns a list of available providers. If @load is %TRUE, it will
* first load in all available providers that haven't yet been loaded.
*
* Free the returned list with g_list_free(). The #CamelProvider structs
* in the list are owned by Camel and should not be modified or freed.
*
* Returns: a #GList of #CamelProvider structs
**/
GList *
camel_provider_list (gboolean load)
{
GList *list = NULL;
/* provider_table can be NULL, so initialize it */
if (G_UNLIKELY (provider_table == NULL))
camel_provider_init ();
g_return_val_if_fail (provider_table != NULL, NULL);
LOCK ();
if (load) {
GList *w;
g_hash_table_foreach (module_table, add_to_list, &list);
for (w = list; w; w = w->next) {
CamelProviderModule *m = w->data;
GError *error = NULL;
if (!m->loaded) {
camel_provider_load (m->path, &error);
m->loaded = 1;
}
if (error != NULL) {
g_critical (
"%s: %s", G_STRFUNC,
error->message);
g_error_free (error);
}
}
g_list_free (list);
list = NULL;
}
g_hash_table_foreach (provider_table, add_to_list, &list);
UNLOCK ();
list = g_list_sort (list, provider_compare);
return list;
}
/**
* camel_provider_get:
* @protocol: a #CamelProvider protocol name
* @error: return location for a #GError, or %NULL
*
* Returns the registered #CamelProvider for @protocol, loading it
* from disk if necessary. If no #CamelProvider can be found for
* @protocol, or the provider module fails to load, the function
* sets @error and returns %NULL.
*
* The returned #CamelProvider is owned by Camel and should not be
* modified or freed.
*
* Returns: a #CamelProvider for %protocol, or %NULL
**/
CamelProvider *
camel_provider_get (const gchar *protocol,
GError **error)
{
CamelProvider *provider = NULL;
g_return_val_if_fail (protocol != NULL, NULL);
g_return_val_if_fail (provider_table != NULL, NULL);
LOCK ();
provider = g_hash_table_lookup (provider_table, protocol);
if (provider == NULL) {
CamelProviderModule *module;
module = g_hash_table_lookup (module_table, protocol);
if (module != NULL && !module->loaded) {
module->loaded = 1;
if (!camel_provider_load (module->path, error))
goto fail;
}
provider = g_hash_table_lookup (provider_table, protocol);
}
if (provider == NULL)
g_set_error (
error, CAMEL_SERVICE_ERROR,
CAMEL_SERVICE_ERROR_URL_INVALID,
_("No provider available for protocol '%s'"),
protocol);
fail:
UNLOCK ();
return provider;
}
/**
* camel_provider_auto_detect:
* @provider: camel provider
* @url: a #CamelURL
* @auto_detected: output hash table of auto-detected values
* @error: return location for a #GError, or %NULL
*
* After filling in the standard Username/Hostname/Port/Path settings
* (which must be set in @url), if the provider supports it, you
* may wish to have the provider auto-detect further settings based on
* the aformentioned settings.
*
* If the provider does not support auto-detection, @auto_detected
* will be set to %NULL. Otherwise the provider will attempt to
* auto-detect whatever it can and file them into @auto_detected. If
* for some reason it cannot auto-detect anything (not enough
* information provided in @url?) then @auto_detected will be
* set to %NULL and an exception may be set to explain why it failed.
*
* Returns: 0 on success or -1 on fail.
**/
gint
camel_provider_auto_detect (CamelProvider *provider,
CamelURL *url,
GHashTable **auto_detected,
GError **error)
{
g_return_val_if_fail (provider != NULL, -1);
if (provider->auto_detect) {
return provider->auto_detect (url, auto_detected, error);
} else {
*auto_detected = NULL;
return 0;
}
}