/* Evolution calendar factory
*
* Copyright (C) 2000 Ximian, Inc.
* Copyright (C) 2000 Ximian, Inc.
*
* Author: Federico Mena-Quintero <federico@ximian.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#include <config.h>
#include <ctype.h>
#include <stdio.h>
#include <bonobo-activation/bonobo-activation.h>
#include "e-util/e-url.h"
#include "evolution-calendar.h"
#include "cal.h"
#include "cal-backend.h"
#include "cal-factory.h"
#include "job.h"
#define PARENT_TYPE BONOBO_TYPE_OBJECT
#define DEFAULT_CAL_FACTORY_OAF_ID "OAFIID:GNOME_Evolution_Wombat_CalendarFactory"
static BonoboObjectClass *parent_class;
/* Private part of the CalFactory structure */
struct _CalFactoryPrivate {
/* Hash table from URI method strings to GType * for backend class types */
GHashTable *methods;
/* Hash table from GnomeVFSURI structures to CalBackend objects */
GHashTable *backends;
/* OAFIID of the factory */
char *iid;
/* Whether we have been registered with OAF yet */
guint registered : 1;
};
typedef struct
{
CalFactory *factory;
GNOME_Evolution_Calendar_CalMode mode;
GNOME_Evolution_Calendar_StringSeq *list;
} CalFactoryUriData;
/* Signal IDs */
enum SIGNALS {
LAST_CALENDAR_GONE,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
/* Frees a method/GType * pair from the methods hash table */
static void
free_method (gpointer key, gpointer value, gpointer data)
{
char *method;
GType *type;
method = key;
type = value;
g_free (method);
g_free (type);
}
/* Frees a uri/backend pair from the backends hash table */
static void
free_backend (gpointer key, gpointer value, gpointer data)
{
char *uri;
CalBackend *backend;
uri = key;
backend = value;
g_free (uri);
g_object_unref (backend);
}
/* Opening calendars */
/* Looks up a calendar backend in a factory's hash table of uri->cal. If
* *non-NULL, orig_uri_return will be set to point to the original key in the
* *hash table.
*/
static CalBackend *
lookup_backend (CalFactory *factory, const char *uristr, char **orig_uri_return)
{
CalFactoryPrivate *priv;
EUri *uri;
char *tmp;
gboolean found;
gpointer orig_key;
gpointer data;
priv = factory->priv;
uri = e_uri_new (uristr);
if (!uri) {
if (orig_uri_return)
*orig_uri_return = NULL;
return NULL;
}
tmp = e_uri_to_string (uri, FALSE);
found = g_hash_table_lookup_extended (priv->backends, tmp, &orig_key, &data);
g_free (tmp);
e_uri_free (uri);
if (found) {
if (orig_uri_return)
*orig_uri_return = orig_key;
return CAL_BACKEND (data);
} else {
if (orig_uri_return)
*orig_uri_return = NULL;
return NULL;
}
}
/* Callback used when a backend loses its last connected client */
static void
backend_last_client_gone_cb (CalBackend *backend, gpointer data)
{
CalFactory *factory;
CalFactoryPrivate *priv;
CalBackend *ret_backend;
const char *uristr;
char *orig_uristr;
fprintf (stderr, "backend_last_client_gone_cb() called!\n");
factory = CAL_FACTORY (data);
priv = factory->priv;
/* Remove the backend from the hash table */
uristr = cal_backend_get_uri (backend);
g_assert (uristr != NULL);
ret_backend = lookup_backend (factory, uristr, &orig_uristr);
g_assert (ret_backend != NULL);
g_assert (ret_backend == backend);
g_hash_table_remove (priv->backends, orig_uristr);
g_free (orig_uristr);
g_object_unref (backend);
/* Notify upstream if there are no more backends */
if (g_hash_table_size (priv->backends) == 0)
g_signal_emit (G_OBJECT (factory), signals[LAST_CALENDAR_GONE], 0);
}
/* Adds a backend to the calendar factory's hash table */
static void
add_backend (CalFactory *factory, const char *uristr, CalBackend *backend)
{
CalFactoryPrivate *priv;
EUri *uri;
char *tmp;
priv = factory->priv;
uri = e_uri_new (uristr);
if (!uri)
return;
tmp = e_uri_to_string (uri, FALSE);
g_hash_table_insert (priv->backends, tmp, backend);
e_uri_free (uri);
g_signal_connect (G_OBJECT (backend), "last_client_gone",
G_CALLBACK (backend_last_client_gone_cb),
factory);
}
/* Tries to launch a backend for the method of the specified URI. If there is
* no such method registered in the factory, it sends the listener the
* MethodNotSupported error code.
*/
static CalBackend *
launch_backend_for_uri (CalFactory *factory,
const char *uristr,
GNOME_Evolution_Calendar_Listener listener)
{
CalFactoryPrivate *priv;
const char *method;
GType *type;
CalBackend *backend;
EUri *uri;
priv = factory->priv;
uri = e_uri_new (uristr);
if (!uri)
return NULL;
method = uri->protocol;
type = g_hash_table_lookup (priv->methods, method);
e_uri_free (uri);
if (!type) {
CORBA_Environment ev;
CORBA_exception_init (&ev);
GNOME_Evolution_Calendar_Listener_notifyCalOpened (
listener,
GNOME_Evolution_Calendar_Listener_METHOD_NOT_SUPPORTED,
CORBA_OBJECT_NIL,
&ev);
if (ev._major != CORBA_NO_EXCEPTION)
g_message ("launch_backend_for_uri(): could not notify the listener");
CORBA_exception_free (&ev);
return NULL;
}
backend = g_object_new (*type, NULL);
if (!backend)
g_message ("launch_backend_for_uri(): could not launch the backend");
return backend;
}
/* Opens a calendar backend and puts it in the factory's backend hash table */
static CalBackend *
open_backend (CalFactory *factory, const char *uristr, gboolean only_if_exists,
GNOME_Evolution_Calendar_Listener listener)
{
CalFactoryPrivate *priv;
CalBackend *backend;
CalBackendOpenStatus status;
CORBA_Environment ev;
priv = factory->priv;
backend = launch_backend_for_uri (factory, uristr, listener);
if (!backend)
return NULL;
status = cal_backend_open (backend, uristr, only_if_exists);
switch (status) {
case CAL_BACKEND_OPEN_SUCCESS:
add_backend (factory, uristr, backend);
return backend;
case CAL_BACKEND_OPEN_ERROR:
g_object_unref (backend);
CORBA_exception_init (&ev);
GNOME_Evolution_Calendar_Listener_notifyCalOpened (
listener,
GNOME_Evolution_Calendar_Listener_ERROR,
CORBA_OBJECT_NIL,
&ev);
if (ev._major != CORBA_NO_EXCEPTION)
g_message ("open_backend(): could not notify the listener");
CORBA_exception_free (&ev);
return NULL;
case CAL_BACKEND_OPEN_NOT_FOUND:
g_object_unref (backend);
CORBA_exception_init (&ev);
GNOME_Evolution_Calendar_Listener_notifyCalOpened (
listener,
GNOME_Evolution_Calendar_Listener_NOT_FOUND,
CORBA_OBJECT_NIL,
&ev);
if (ev._major != CORBA_NO_EXCEPTION)
g_message ("open_backend(): could not notify the listener");
CORBA_exception_free (&ev);
return NULL;
case CAL_BACKEND_OPEN_PERMISSION_DENIED :
g_object_unref (backend);
CORBA_exception_init (&ev);
GNOME_Evolution_Calendar_Listener_notifyCalOpened (
listener,
GNOME_Evolution_Calendar_Listener_PERMISSION_DENIED,
CORBA_OBJECT_NIL,
&ev);
if (ev._major != CORBA_NO_EXCEPTION)
g_message ("open_backend(): could not notify the listener");
CORBA_exception_free (&ev);
return NULL;
default:
g_assert_not_reached ();
return NULL;
}
}
/* Adds a listener to a calendar backend by creating a calendar client interface
* object.
*/
static void
add_calendar_client (CalFactory *factory,
CalBackend *backend,
GNOME_Evolution_Calendar_Listener listener)
{
Cal *cal;
CORBA_Environment ev;
cal = cal_new (backend, listener);
if (!cal) {
g_message ("add_calendar_client(): could not create the calendar client interface");
CORBA_exception_init (&ev);
GNOME_Evolution_Calendar_Listener_notifyCalOpened (
listener,
GNOME_Evolution_Calendar_Listener_ERROR,
CORBA_OBJECT_NIL,
&ev);
if (ev._major != CORBA_NO_EXCEPTION)
g_message ("add_calendar_client(): could not notify the listener");
CORBA_exception_free (&ev);
return;
}
cal_backend_add_cal (backend, cal);
CORBA_exception_init (&ev);
GNOME_Evolution_Calendar_Listener_notifyCalOpened (
listener,
GNOME_Evolution_Calendar_Listener_SUCCESS,
BONOBO_OBJREF (cal),
&ev);
if (ev._major != CORBA_NO_EXCEPTION) {
g_message ("add_calendar_client(): could not notify the listener");
bonobo_object_unref (BONOBO_OBJECT (cal));
}
CORBA_exception_free (&ev);
}
/* Add a uri to a string list */
static void
add_uri (gpointer key, gpointer value, gpointer data)
{
CalFactoryUriData *cfud = data;
CalFactory *factory = cfud->factory;
GNOME_Evolution_Calendar_StringSeq *list = cfud->list;
GNOME_Evolution_Calendar_CalMode mode = cfud->mode;
char *uri_string = key;
CalBackend *backend;
switch (mode) {
case GNOME_Evolution_Calendar_MODE_LOCAL:
backend = lookup_backend (factory, uri_string, NULL);
if (backend == NULL || cal_backend_get_mode (backend) != CAL_MODE_LOCAL)
return;
break;
case GNOME_Evolution_Calendar_MODE_REMOTE:
backend = lookup_backend (factory, uri_string, NULL);
if (backend == NULL || cal_backend_get_mode (backend) != CAL_MODE_REMOTE)
return;
break;
case GNOME_Evolution_Calendar_MODE_ANY:
break;
}
list->_buffer[list->_length] = CORBA_string_dup (uri_string);
list->_length++;
}
/* Job data */
typedef struct {
CalFactory *factory;
char *uri;
gboolean only_if_exists;
GNOME_Evolution_Calendar_Listener listener;
} OpenJobData;
/* Job handler for the open calendar command */
static void
open_fn (gpointer data)
{
OpenJobData *jd;
CalFactory *factory;
gboolean only_if_exists;
GNOME_Evolution_Calendar_Listener listener;
CalBackend *backend;
CORBA_Environment ev;
char *uri_string;
jd = data;
g_assert (jd->uri != NULL);
/* Check the URI */
uri_string = g_strdup (jd->uri);
g_free (jd->uri);
only_if_exists = jd->only_if_exists;
factory = jd->factory;
listener = jd->listener;
g_free (jd);
if (!uri_string) {
CORBA_exception_init (&ev);
GNOME_Evolution_Calendar_Listener_notifyCalOpened (
listener,
GNOME_Evolution_Calendar_Listener_ERROR,
CORBA_OBJECT_NIL,
&ev);
if (ev._major != CORBA_NO_EXCEPTION)
g_message ("open_fn(): Could not notify the listener!");
CORBA_exception_free (&ev);
goto out;
}
/* Look up the backend and create it if needed */
backend = lookup_backend (factory, uri_string, NULL);
if (!backend)
backend = open_backend (factory, uri_string, only_if_exists, listener);
g_free (uri_string);
if (backend)
add_calendar_client (factory, backend, listener);
out:
CORBA_exception_init (&ev);
CORBA_Object_release (listener, &ev);
if (ev._major != CORBA_NO_EXCEPTION)
g_message ("open_fn(): could not release the listener");
CORBA_exception_free (&ev);
}
static void
impl_CalFactory_open (PortableServer_Servant servant,
const CORBA_char *str_uri,
CORBA_boolean only_if_exists,
GNOME_Evolution_Calendar_Listener listener,
CORBA_Environment *ev)
{
CalFactory *factory;
CalFactoryPrivate *priv;
CORBA_Environment ev2;
gboolean result;
OpenJobData *jd;
GNOME_Evolution_Calendar_Listener listener_copy;
GType *type;
EUri *uri;
factory = CAL_FACTORY (bonobo_object_from_servant (servant));
priv = factory->priv;
/* check URI to see if we support it */
uri = e_uri_new (str_uri);
if (!uri) {
CORBA_exception_set (ev, CORBA_USER_EXCEPTION,
ex_GNOME_Evolution_Calendar_CalFactory_InvalidURI,
NULL);
return;
}
type = g_hash_table_lookup (priv->methods, uri->protocol);
e_uri_free (uri);
if (!type) {
CORBA_exception_set (ev, CORBA_USER_EXCEPTION,
ex_GNOME_Evolution_Calendar_CalFactory_UnsupportedMethod,
NULL);
return;
}
/* duplicate the listener object */
CORBA_exception_init (&ev2);
result = CORBA_Object_is_nil (listener, &ev2);
if (ev2._major != CORBA_NO_EXCEPTION || result) {
CORBA_exception_set (ev, CORBA_USER_EXCEPTION,
ex_GNOME_Evolution_Calendar_CalFactory_NilListener,
NULL);
CORBA_exception_free (&ev2);
return;
}
CORBA_exception_free (&ev2);
CORBA_exception_init (&ev2);
listener_copy = CORBA_Object_duplicate (listener, &ev2);
if (ev2._major != CORBA_NO_EXCEPTION) {
g_message ("CalFactory_open(): could not duplicate the listener");
CORBA_exception_free (&ev2);
CORBA_exception_set (ev, CORBA_USER_EXCEPTION,
ex_GNOME_Evolution_Calendar_CalFactory_NilListener,
NULL);
return;
}
CORBA_exception_free (&ev2);
/* add new asynchronous job */
jd = g_new (OpenJobData, 1);
jd->factory = factory;
jd->uri = g_strdup (str_uri);
jd->only_if_exists = only_if_exists;
jd->listener = listener_copy;
job_add (open_fn, jd);
}
static GNOME_Evolution_Calendar_StringSeq *
impl_CalFactory_uriList (PortableServer_Servant servant,
GNOME_Evolution_Calendar_CalMode mode,
CORBA_Environment *ev)
{
CalFactory *factory;
CalFactoryPrivate *priv;
CalFactoryUriData cfud;
GNOME_Evolution_Calendar_StringSeq *list;
factory = CAL_FACTORY (bonobo_object_from_servant (servant));
priv = factory->priv;
list = GNOME_Evolution_Calendar_StringSeq__alloc ();
list->_length = 0;
list->_maximum = g_hash_table_size (priv->backends);
list->_buffer = CORBA_sequence_CORBA_string_allocbuf (list->_maximum);
cfud.factory = factory;
cfud.mode = mode;
cfud.list = list;
g_hash_table_foreach (priv->backends, add_uri, &cfud);
return list;
}
/**
* cal_factory_new:
* @void:
*
* Creates a new #CalFactory object.
*
* Return value: A newly-created #CalFactory, or NULL if its corresponding CORBA
* object could not be created.
**/
CalFactory *
cal_factory_new (void)
{
CalFactory *factory;
factory = g_object_new (CAL_FACTORY_TYPE, NULL);
return factory;
}
/* Destroy handler for the calendar */
static void
cal_factory_finalize (GObject *object)
{
CalFactory *factory;
CalFactoryPrivate *priv;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_CAL_FACTORY (object));
factory = CAL_FACTORY (object);
priv = factory->priv;
g_hash_table_foreach (priv->methods, free_method, NULL);
g_hash_table_destroy (priv->methods);
priv->methods = NULL;
/* Should we assert that there are no more backends? */
g_hash_table_foreach (priv->backends, free_backend, NULL);
g_hash_table_destroy (priv->backends);
priv->backends = NULL;
if (priv->registered) {
bonobo_activation_active_server_unregister (priv->iid, BONOBO_OBJREF (factory));
priv->registered = FALSE;
}
g_free (priv->iid);
g_free (priv);
factory->priv = NULL;
if (G_OBJECT_CLASS (parent_class)->finalize)
(* G_OBJECT_CLASS (parent_class)->finalize) (object);
}
/* Class initialization function for the calendar factory */
static void
cal_factory_class_init (CalFactoryClass *klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
POA_GNOME_Evolution_Calendar_CalFactory__epv *epv = &klass->epv;
parent_class = g_type_class_peek_parent (klass);
signals[LAST_CALENDAR_GONE] =
g_signal_new ("last_calendar_gone",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (CalFactoryClass, last_calendar_gone),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/* Class method overrides */
object_class->finalize = cal_factory_finalize;
/* Epv methods */
epv->open = impl_CalFactory_open;
epv->uriList = impl_CalFactory_uriList;
}
/* Object initialization function for the calendar factory */
static void
cal_factory_init (CalFactory *factory, CalFactoryClass *klass)
{
CalFactoryPrivate *priv;
priv = g_new0 (CalFactoryPrivate, 1);
factory->priv = priv;
priv->methods = g_hash_table_new (g_str_hash, g_str_equal);
priv->backends = g_hash_table_new (g_str_hash, g_str_equal);
priv->registered = FALSE;
}
BONOBO_TYPE_FUNC_FULL (CalFactory,
GNOME_Evolution_Calendar_CalFactory,
PARENT_TYPE,
cal_factory);
/* Returns the lowercase version of a string */
static char *
str_tolower (const char *s)
{
char *str;
unsigned char *p;
str = g_strdup (s);
for (p = str; *p; p++)
if (isalpha (*p))
*p = tolower (*p);
return str;
}
/**
* cal_factory_oaf_register:
* @factory: A calendar factory.
* @iid: OAFIID for the factory to be registered.
*
* Registers a calendar factory with the OAF object activation daemon. This
* function must be called before any clients can activate the factory.
*
* Return value: TRUE on success, FALSE otherwise.
**/
gboolean
cal_factory_oaf_register (CalFactory *factory, const char *iid)
{
CalFactoryPrivate *priv;
Bonobo_RegistrationResult result;
char *tmp_iid;
g_return_val_if_fail (factory != NULL, FALSE);
g_return_val_if_fail (IS_CAL_FACTORY (factory), FALSE);
priv = factory->priv;
g_return_val_if_fail (!priv->registered, FALSE);
/* if iid is NULL, use the default factory OAFIID */
if (iid)
tmp_iid = g_strdup (iid);
else
tmp_iid = g_strdup (DEFAULT_CAL_FACTORY_OAF_ID);
result = bonobo_activation_active_server_register (tmp_iid, BONOBO_OBJREF (factory));
switch (result) {
case Bonobo_ACTIVATION_REG_SUCCESS:
priv->registered = TRUE;
priv->iid = tmp_iid;
return TRUE;
case Bonobo_ACTIVATION_REG_NOT_LISTED:
g_message ("cal_factory_oaf_register(): Cannot register the calendar factory: "
"not listed");
break;
case Bonobo_ACTIVATION_REG_ALREADY_ACTIVE:
g_message ("cal_factory_oaf_register(): Cannot register the calendar factory: "
"already active");
break;
case Bonobo_ACTIVATION_REG_ERROR:
default:
g_message ("cal_factory_oaf_register(): Cannot register the calendar factory: "
"generic error");
break;
}
g_free (tmp_iid);
return FALSE;
}
/**
* cal_factory_register_method:
* @factory: A calendar factory.
* @method: Method for the URI, i.e. "http", "file", etc.
* @backend_type: Class type of the backend to create for this @method.
*
* Registers the type of a #CalBackend subclass that will be used to handle URIs
* with a particular method. When the factory is asked to open a particular
* URI, it will look in its list of registered methods and create a backend of
* the appropriate type.
**/
void
cal_factory_register_method (CalFactory *factory, const char *method, GType backend_type)
{
CalFactoryPrivate *priv;
GType *type;
char *method_str;
g_return_if_fail (factory != NULL);
g_return_if_fail (IS_CAL_FACTORY (factory));
g_return_if_fail (method != NULL);
g_return_if_fail (backend_type != 0);
g_return_if_fail (g_type_is_a (backend_type, CAL_BACKEND_TYPE));
priv = factory->priv;
method_str = str_tolower (method);
type = g_hash_table_lookup (priv->methods, method_str);
if (type) {
g_message ("cal_factory_register_method(): Method `%s' already registered!",
method_str);
g_free (method_str);
return;
}
type = g_new (GType, 1);
*type = backend_type;
g_hash_table_insert (priv->methods, method_str, type);
}
/**
* cal_factory_get_n_backends:
* @factory: A calendar factory.
*
* Queries the number of running calendar backends in a calendar factory.
*
* Return value: Number of running backends.
**/
int
cal_factory_get_n_backends (CalFactory *factory)
{
CalFactoryPrivate *priv;
g_return_val_if_fail (factory != NULL, -1);
g_return_val_if_fail (IS_CAL_FACTORY (factory), -1);
priv = factory->priv;
return g_hash_table_size (priv->backends);
}
/* Frees a uri/backend pair from the backends hash table */
static void
dump_backend (gpointer key, gpointer value, gpointer data)
{
char *uri;
CalBackend *backend;
uri = key;
backend = value;
g_message (" %s: %p", uri, backend);
}
void
cal_factory_dump_active_backends (CalFactory *factory)
{
CalFactoryPrivate *priv;
g_message ("Active PCS backends");
priv = factory->priv;
g_hash_table_foreach (priv->backends, dump_backend, NULL);
}