Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / calendar / libedata-cal / e-data-cal-factory.c
1 /* Evolution calendar factory
2  *
3  * Copyright (C) 2000-2003 Ximian, Inc.
4  *
5  * Authors: 
6  *   Federico Mena-Quintero <federico@ximian.com>
7  *   JP Rosevear <jpr@ximian.com>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of version 2 of the GNU Lesser General Public
11  * License as published by the Free Software Foundation.
12  *
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 Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  */
22
23 #include <bonobo-activation/bonobo-activation.h>
24 #include <bonobo/bonobo-exception.h>
25 #include <bonobo/bonobo-main.h>
26 #include "libedataserver/e-url.h"
27 #include "libedataserver/e-source.h"
28 #include "libedataserver/e-data-server-module.h"
29 #include "e-cal-backend.h"
30 #include "e-data-cal.h"
31 #include "e-data-cal-factory.h"
32
33 #define PARENT_TYPE                BONOBO_TYPE_OBJECT
34 #define DEFAULT_E_DATA_CAL_FACTORY_OAF_ID "OAFIID:GNOME_Evolution_DataServer_CalFactory:" BASE_VERSION
35
36 static BonoboObjectClass *parent_class;
37
38 /* Private part of the CalFactory structure */
39 struct _EDataCalFactoryPrivate {
40         /* Hash table from URI method strings to GType * for backend class types */
41         GHashTable *methods;
42
43         /* Hash table from GnomeVFSURI structures to CalBackend objects */
44         GHashTable *backends;
45
46         /* OAFIID of the factory */
47         char *iid;
48
49         /* Whether we have been registered with OAF yet */
50         guint registered : 1;
51         
52         int mode;
53         
54 };
55
56 /* Signal IDs */
57 enum SIGNALS {
58         LAST_CALENDAR_GONE,
59         LAST_SIGNAL
60 };
61
62 static guint signals[LAST_SIGNAL];
63
64 /* Opening calendars */
65
66 static icalcomponent_kind
67 calobjtype_to_icalkind (const GNOME_Evolution_Calendar_CalObjType type)
68 {
69         switch (type){
70         case GNOME_Evolution_Calendar_TYPE_EVENT:
71                 return ICAL_VEVENT_COMPONENT;
72         case GNOME_Evolution_Calendar_TYPE_TODO:
73                 return ICAL_VTODO_COMPONENT;
74         case GNOME_Evolution_Calendar_TYPE_JOURNAL:
75                 return ICAL_VJOURNAL_COMPONENT;
76         }
77         
78         return ICAL_NO_COMPONENT;
79 }
80
81 static ECalBackendFactory*
82 get_backend_factory (GHashTable *methods, const char *method, icalcomponent_kind kind)
83 {
84         GHashTable *kinds;
85         ECalBackendFactory *factory;
86         
87         kinds = g_hash_table_lookup (methods, method);
88         if (!kinds)
89                 return NULL;
90
91         factory = g_hash_table_lookup (kinds, GINT_TO_POINTER (kind));
92
93         return factory;
94 }
95
96 /* Callback used when a backend loses its last connected client */
97 static void
98 backend_last_client_gone_cb (ECalBackend *backend, gpointer data)
99 {
100         EDataCalFactory *factory;
101         EDataCalFactoryPrivate *priv;
102         ECalBackend *ret_backend;
103         const char *uristr;
104         char *uri;
105
106         fprintf (stderr, "backend_last_client_gone_cb() called!\n");
107
108         factory = E_DATA_CAL_FACTORY (data);
109         priv = factory->priv;
110
111         /* Remove the backend from the hash table */
112
113         uristr = e_cal_backend_get_uri (backend);
114         g_assert (uristr != NULL);
115         uri = g_strdup_printf("%s:%d", uristr, (int)e_cal_backend_get_kind(backend));
116
117         ret_backend = g_hash_table_lookup (factory->priv->backends, uri);
118         g_assert (ret_backend != NULL);
119         g_assert (ret_backend == backend);
120
121         g_hash_table_remove (priv->backends, uri);
122         g_free(uri);
123
124         g_signal_handlers_disconnect_matched (backend, G_SIGNAL_MATCH_DATA,
125                                               0, 0, NULL, NULL, data);
126
127         /* Notify upstream if there are no more backends */
128         if (g_hash_table_size (priv->backends) == 0)
129                 g_signal_emit (G_OBJECT (factory), signals[LAST_CALENDAR_GONE], 0);
130 }
131
132 \f
133
134 static GNOME_Evolution_Calendar_Cal
135 impl_CalFactory_getCal (PortableServer_Servant servant,
136                         const CORBA_char *source_xml,
137                         const GNOME_Evolution_Calendar_CalObjType type,
138                         const GNOME_Evolution_Calendar_CalListener listener,
139                         CORBA_Environment *ev)
140 {
141         GNOME_Evolution_Calendar_Cal ret_cal = NULL;
142         EDataCalFactory *factory;
143         EDataCalFactoryPrivate *priv;
144         EDataCal *cal = CORBA_OBJECT_NIL;
145         ECalBackend *backend;
146         CORBA_Environment ev2;
147         GNOME_Evolution_Calendar_CalListener listener_copy;
148         ECalBackendFactory *backend_factory;
149         ESource *source;
150         char *str_uri;
151         EUri *uri;
152         char *uri_type_string;
153         
154         factory = E_DATA_CAL_FACTORY (bonobo_object_from_servant (servant));
155         priv = factory->priv;
156
157         source = e_source_new_from_standalone_xml (source_xml);
158         if (!source) {
159                 bonobo_exception_set (ev, ex_GNOME_Evolution_Calendar_CalFactory_InvalidURI);
160
161                 return CORBA_OBJECT_NIL;
162         }
163
164         /* Get the URI so we can extract the protocol */
165         str_uri = e_source_get_uri (source);
166         if (!str_uri) {
167                 g_object_unref (source);
168                 bonobo_exception_set (ev, ex_GNOME_Evolution_Calendar_CalFactory_InvalidURI);
169
170                 return CORBA_OBJECT_NIL;
171         }
172
173         /* Parse the uri */
174         uri = e_uri_new (str_uri);
175         if (!uri) {
176                 bonobo_exception_set (ev, ex_GNOME_Evolution_Calendar_CalFactory_InvalidURI);
177
178                 return CORBA_OBJECT_NIL;
179         }
180
181         uri_type_string = g_strdup_printf ("%s:%d", str_uri, (int)calobjtype_to_icalkind (type));
182         g_free(str_uri);
183
184         /* Find the associated backend factory (if any) */
185         backend_factory = get_backend_factory (priv->methods, uri->protocol, calobjtype_to_icalkind (type));
186         if (!backend_factory) {
187                 /* FIXME Distinguish between method and kind failures? */
188                 bonobo_exception_set (ev, ex_GNOME_Evolution_Calendar_CalFactory_UnsupportedMethod);
189                 goto cleanup;
190         }
191                 
192         /* Duplicate the listener object */
193         CORBA_exception_init (&ev2);
194         listener_copy = CORBA_Object_duplicate (listener, &ev2);
195
196         if (BONOBO_EX (&ev2)) {
197                 g_warning (G_STRLOC ": could not duplicate the listener");
198                 bonobo_exception_set (ev, ex_GNOME_Evolution_Calendar_CalFactory_NilListener);
199                 CORBA_exception_free (&ev2);
200                 goto cleanup;
201         }
202         CORBA_exception_free (&ev2);
203
204         /* Look for an existing backend */
205         backend = g_hash_table_lookup (factory->priv->backends, uri_type_string);
206         if (!backend) {
207                 /* There was no existing backend, create a new one */
208                 backend = e_cal_backend_factory_new_backend (backend_factory, source);
209
210                 if (!backend) {
211                         g_warning (G_STRLOC ": could not instantiate backend");
212                         bonobo_exception_set (ev, ex_GNOME_Evolution_Calendar_CalFactory_UnsupportedMethod);
213                         goto cleanup;
214                 }
215
216                 /* Track the backend */
217                 g_hash_table_insert (priv->backends, g_strdup (uri_type_string), backend);
218
219                 g_signal_connect (G_OBJECT (backend), "last_client_gone",
220                                   G_CALLBACK (backend_last_client_gone_cb),
221                                   factory);
222         }
223         
224         /* Create the corba calendar */
225         cal = e_data_cal_new (backend, listener);
226         printf ("cal = %p\n", cal);
227         if (!cal) {
228                 g_warning (G_STRLOC ": could not create the corba calendar");
229                 bonobo_exception_set (ev, ex_GNOME_Evolution_Calendar_CalFactory_UnsupportedMethod);
230                 goto cleanup;
231         }
232
233         /* Let the backend know about its clients corba clients */
234         e_cal_backend_add_client (backend, cal);
235         e_cal_backend_set_mode (backend, priv->mode);
236         
237         ret_cal = CORBA_Object_duplicate (BONOBO_OBJREF (cal), ev);
238  cleanup:
239         e_uri_free (uri);
240         g_free (uri_type_string);
241         g_object_unref (source);
242
243         return ret_cal;
244 }
245
246 \f
247
248 /**
249  * e_data_cal_factory_new:
250  * @void:
251  *
252  * Creates a new #EDataCalFactory object.
253  *
254  * Return value: A newly-created #EDataCalFactory, or NULL if its corresponding CORBA
255  * object could not be created.
256  **/
257 EDataCalFactory *
258 e_data_cal_factory_new (void)
259 {
260         EDataCalFactory *factory;
261
262         factory = g_object_new (E_TYPE_DATA_CAL_FACTORY, 
263                                 "poa", bonobo_poa_get_threaded (ORBIT_THREAD_HINT_PER_REQUEST, NULL), 
264                                 NULL);
265
266         return factory;
267 }
268
269 /* Destroy handler for the calendar */
270 static void
271 e_data_cal_factory_finalize (GObject *object)
272 {
273         EDataCalFactory *factory;
274         EDataCalFactoryPrivate *priv;
275
276         g_return_if_fail (object != NULL);
277         g_return_if_fail (E_IS_DATA_CAL_FACTORY (object));
278
279         factory = E_DATA_CAL_FACTORY (object);
280         priv = factory->priv;
281
282         g_hash_table_destroy (priv->methods);
283         priv->methods = NULL;
284
285         /* Should we assert that there are no more backends? */
286         g_hash_table_destroy (priv->backends);
287         priv->backends = NULL;
288
289         if (priv->registered) {
290                 bonobo_activation_active_server_unregister (priv->iid, BONOBO_OBJREF (factory));
291                 priv->registered = FALSE;
292         }
293         g_free (priv->iid);
294
295         g_free (priv);
296         factory->priv = NULL;
297
298         if (G_OBJECT_CLASS (parent_class)->finalize)
299                 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
300 }
301
302 /* Class initialization function for the calendar factory */
303 static void
304 e_data_cal_factory_class_init (EDataCalFactoryClass *klass)
305 {
306         GObjectClass *object_class = (GObjectClass *) klass;
307         POA_GNOME_Evolution_Calendar_CalFactory__epv *epv = &klass->epv;
308
309         parent_class = g_type_class_peek_parent (klass);
310
311         signals[LAST_CALENDAR_GONE] =
312                 g_signal_new ("last_calendar_gone",
313                               G_TYPE_FROM_CLASS (klass),
314                               G_SIGNAL_RUN_FIRST,
315                               G_STRUCT_OFFSET (EDataCalFactoryClass, last_calendar_gone),
316                               NULL, NULL,
317                               g_cclosure_marshal_VOID__VOID,
318                               G_TYPE_NONE, 0);
319
320         /* Class method overrides */
321         object_class->finalize = e_data_cal_factory_finalize;
322
323         /* Epv methods */
324         epv->getCal = impl_CalFactory_getCal;
325 }
326
327 static void 
328 set_backend_online_status (gpointer key, gpointer value, gpointer data)
329 {
330         ECalBackend *backend = E_CAL_BACKEND (value);
331
332         e_cal_backend_set_mode (backend,  GPOINTER_TO_INT (data));
333 }
334
335 /**
336  * e_data_cal_factory_set_backend_mode:
337  * @factory: A calendar factory.
338  * @mode: Online mode to set.
339  *
340  * Sets the online mode for all backends created by the given factory.
341  */
342 void 
343 e_data_cal_factory_set_backend_mode (EDataCalFactory *factory, int mode)
344 {
345         EDataCalFactoryPrivate *priv = factory->priv;
346         
347         
348         priv->mode = mode;
349         g_hash_table_foreach (priv->backends, set_backend_online_status, GINT_TO_POINTER (priv->mode));
350 }
351
352
353 /* Object initialization function for the calendar factory */
354 static void
355 e_data_cal_factory_init (EDataCalFactory *factory, EDataCalFactoryClass *klass)
356 {
357         EDataCalFactoryPrivate *priv;
358
359         priv = g_new0 (EDataCalFactoryPrivate, 1);
360         factory->priv = priv;
361
362         priv->methods = g_hash_table_new_full (g_str_hash, g_str_equal, 
363                                                (GDestroyNotify) g_free, (GDestroyNotify) g_hash_table_destroy);
364         priv->backends = g_hash_table_new_full (g_str_hash, g_str_equal, 
365                                                 (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref);
366         priv->registered = FALSE;
367 }
368
369 BONOBO_TYPE_FUNC_FULL (EDataCalFactory,
370                        GNOME_Evolution_Calendar_CalFactory,
371                        PARENT_TYPE,
372                        e_data_cal_factory);
373
374 /**
375  * e_data_cal_factory_register_storage:
376  * @factory: A calendar factory.
377  * @iid: OAFIID for the factory to be registered.
378  * 
379  * Registers a calendar factory with the OAF object activation daemon.  This
380  * function must be called before any clients can activate the factory.
381  * 
382  * Return value: TRUE on success, FALSE otherwise.
383  **/
384 gboolean
385 e_data_cal_factory_register_storage (EDataCalFactory *factory, const char *iid)
386 {
387         EDataCalFactoryPrivate *priv;
388         Bonobo_RegistrationResult result;
389         char *tmp_iid;
390
391         g_return_val_if_fail (factory != NULL, FALSE);
392         g_return_val_if_fail (E_IS_DATA_CAL_FACTORY (factory), FALSE);
393
394         priv = factory->priv;
395
396         g_return_val_if_fail (!priv->registered, FALSE);
397
398         /* if iid is NULL, use the default factory OAFIID */
399         if (iid)
400                 tmp_iid = g_strdup (iid);
401         else
402                 tmp_iid = g_strdup (DEFAULT_E_DATA_CAL_FACTORY_OAF_ID);
403
404         result = bonobo_activation_active_server_register (tmp_iid, BONOBO_OBJREF (factory));
405
406         switch (result) {
407         case Bonobo_ACTIVATION_REG_SUCCESS:
408                 priv->registered = TRUE;
409                 priv->iid = tmp_iid;
410                 return TRUE;
411
412         case Bonobo_ACTIVATION_REG_NOT_LISTED:
413                 g_warning (G_STRLOC ": cannot register the calendar factory %s (not listed)", tmp_iid);
414                 break;
415
416         case Bonobo_ACTIVATION_REG_ALREADY_ACTIVE:
417                 g_warning (G_STRLOC ": cannot register the calendar factory (already active)");
418                 break;
419
420         case Bonobo_ACTIVATION_REG_ERROR:
421         default:
422                 g_warning (G_STRLOC ": cannot register the calendar factory (generic error)");
423                 break;
424         }
425
426         g_free (tmp_iid);
427
428         return FALSE;
429 }
430
431 /**
432  * e_data_cal_factory_register_backend:
433  * @factory: A calendar factory.
434  * @backend_factory: The object responsible for creating backends.
435  * 
436  * Registers an #ECalBackend subclass that will be used to handle URIs
437  * with a particular method.  When the factory is asked to open a
438  * particular URI, it will look in its list of registered methods and
439  * create a backend of the appropriate type.
440  **/
441 void
442 e_data_cal_factory_register_backend (EDataCalFactory *factory, ECalBackendFactory *backend_factory)
443 {
444         EDataCalFactoryPrivate *priv;
445         const char *method;
446         char *method_str;
447         GHashTable *kinds;
448         GType type;
449         icalcomponent_kind kind;
450
451         g_return_if_fail (factory && E_IS_DATA_CAL_FACTORY (factory));
452         g_return_if_fail (backend_factory && E_IS_CAL_BACKEND_FACTORY (backend_factory));
453
454         priv = factory->priv;
455
456         method = E_CAL_BACKEND_FACTORY_GET_CLASS (backend_factory)->get_protocol (backend_factory);
457         kind = E_CAL_BACKEND_FACTORY_GET_CLASS (backend_factory)->get_kind (backend_factory);
458
459         method_str = g_ascii_strdown (method, -1);
460
461         kinds = g_hash_table_lookup (priv->methods, method_str);
462         if (kinds) {
463                 type = GPOINTER_TO_INT (g_hash_table_lookup (kinds, GINT_TO_POINTER (kind)));
464                 if (type) {
465                         g_warning (G_STRLOC ": method `%s' already registered", method_str);
466                         g_free (method_str);
467
468                         return;
469                 }
470
471                 g_free (method_str);
472         } else {
473                 kinds = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
474                 g_hash_table_insert (priv->methods, method_str, kinds);
475         }
476         
477         g_hash_table_insert (kinds, GINT_TO_POINTER (kind), backend_factory);
478 }
479
480 /**
481  * e_data_cal_factory_register_backends:
482  * @cal_factory: A calendar factory.
483  *
484  * Register all backends for the given factory.
485  */
486 void
487 e_data_cal_factory_register_backends (EDataCalFactory *cal_factory)
488 {
489         GList *factories, *f;
490
491         factories = e_data_server_get_extensions_for_type (E_TYPE_CAL_BACKEND_FACTORY);
492         for (f = factories; f; f = f->next) {
493                 ECalBackendFactory *backend_factory = f->data;
494
495                 e_data_cal_factory_register_backend (cal_factory, g_object_ref (backend_factory));
496         }
497
498         e_data_server_extension_list_free (factories);
499 }
500
501 /**
502  * e_data_cal_factory_get_n_backends
503  * @factory: A calendar factory.
504  *
505  * Get the number of backends currently active in the given factory.
506  *
507  * Returns: the number of backends.
508  */
509 int
510 e_data_cal_factory_get_n_backends (EDataCalFactory *factory)
511 {
512         EDataCalFactoryPrivate *priv;
513
514         g_return_val_if_fail (E_IS_DATA_CAL_FACTORY (factory), 0);
515
516         priv = factory->priv;
517         return g_hash_table_size (priv->backends);
518 }
519
520 /* Frees a uri/backend pair from the backends hash table */
521 static void
522 dump_backend (gpointer key, gpointer value, gpointer data)
523 {
524         char *uri;
525         ECalBackend *backend;
526
527         uri = key;
528         backend = value;
529
530         g_message ("  %s: %p", uri, backend);
531 }
532
533 /**
534  * e_data_cal_factory_dump_active_backends:
535  * @factory: A calendar factory.
536  *
537  * Dumps to standard output a list of all active backends for the given
538  * factory.
539  */
540 void
541 e_data_cal_factory_dump_active_backends (EDataCalFactory *factory)
542 {
543         EDataCalFactoryPrivate *priv;
544
545         g_message ("Active PCS backends");
546
547         priv = factory->priv;
548         g_hash_table_foreach (priv->backends, dump_backend, NULL);
549 }