No issues found
1 /*
2 * Copyright (C) 2004 Free Software Foundation, Inc.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
18 *
19 * Authors:
20 * Mark McLoughlin <mark@skynet.ie>
21 * William Jon McCann <mccann@jhu.edu>
22 * Martin Grimme <martin@pycage.de>
23 * Christian Kellner <gicmo@xatom.net>
24 */
25
26 #include <config.h>
27
28 #include "calendar-sources.h"
29
30 #include <libintl.h>
31 #include <string.h>
32 #define HANDLE_LIBICAL_MEMORY
33 #include <libecal/libecal.h>
34
35 #undef CALENDAR_ENABLE_DEBUG
36 #include "calendar-debug.h"
37
38 #ifndef _
39 #define _(x) gettext(x)
40 #endif
41
42 #ifndef N_
43 #define N_(x) x
44 #endif
45
46 #define CALENDAR_SOURCES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesPrivate))
47
48 typedef struct _ClientData ClientData;
49 typedef struct _CalendarSourceData CalendarSourceData;
50
51 struct _ClientData
52 {
53 ECalClient *client;
54 gulong backend_died_id;
55 };
56
57 struct _CalendarSourceData
58 {
59 ECalClientSourceType source_type;
60 CalendarSources *sources;
61 guint changed_signal;
62
63 /* ESource -> EClient */
64 GHashTable *clients;
65
66 guint timeout_id;
67
68 guint loaded : 1;
69 };
70
71 struct _CalendarSourcesPrivate
72 {
73 ESourceRegistry *registry;
74 gulong source_added_id;
75 gulong source_changed_id;
76 gulong source_removed_id;
77
78 CalendarSourceData appointment_sources;
79 CalendarSourceData task_sources;
80 };
81
82 static void calendar_sources_class_init (CalendarSourcesClass *klass);
83 static void calendar_sources_init (CalendarSources *sources);
84 static void calendar_sources_finalize (GObject *object);
85
86 static void backend_died_cb (EClient *client, CalendarSourceData *source_data);
87 static void calendar_sources_registry_source_changed_cb (ESourceRegistry *registry,
88 ESource *source,
89 CalendarSources *sources);
90 static void calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
91 ESource *source,
92 CalendarSources *sources);
93
94 enum
95 {
96 APPOINTMENT_SOURCES_CHANGED,
97 TASK_SOURCES_CHANGED,
98 LAST_SIGNAL
99 };
100 static guint signals [LAST_SIGNAL] = { 0, };
101
102 static GObjectClass *parent_class = NULL;
103 static CalendarSources *calendar_sources_singleton = NULL;
104
105 static void
106 client_data_free (ClientData *data)
107 {
108 g_signal_handler_disconnect (data->client, data->backend_died_id);
109 g_object_unref (data->client);
110 g_slice_free (ClientData, data);
111 }
112
113 GType
114 calendar_sources_get_type (void)
115 {
116 static GType sources_type = 0;
117
118 if (!sources_type)
119 {
120 static const GTypeInfo sources_info =
121 {
122 sizeof (CalendarSourcesClass),
123 NULL, /* base_init */
124 NULL, /* base_finalize */
125 (GClassInitFunc) calendar_sources_class_init,
126 NULL, /* class_finalize */
127 NULL, /* class_data */
128 sizeof (CalendarSources),
129 0, /* n_preallocs */
130 (GInstanceInitFunc) calendar_sources_init,
131 };
132
133 sources_type = g_type_register_static (G_TYPE_OBJECT,
134 "CalendarSources",
135 &sources_info, 0);
136 }
137
138 return sources_type;
139 }
140
141 static void
142 calendar_sources_class_init (CalendarSourcesClass *klass)
143 {
144 GObjectClass *gobject_class = (GObjectClass *) klass;
145
146 parent_class = g_type_class_peek_parent (klass);
147
148 gobject_class->finalize = calendar_sources_finalize;
149
150 g_type_class_add_private (klass, sizeof (CalendarSourcesPrivate));
151
152 signals [APPOINTMENT_SOURCES_CHANGED] =
153 g_signal_new ("appointment-sources-changed",
154 G_TYPE_FROM_CLASS (gobject_class),
155 G_SIGNAL_RUN_LAST,
156 G_STRUCT_OFFSET (CalendarSourcesClass,
157 appointment_sources_changed),
158 NULL,
159 NULL,
160 NULL,
161 G_TYPE_NONE,
162 0);
163
164 signals [TASK_SOURCES_CHANGED] =
165 g_signal_new ("task-sources-changed",
166 G_TYPE_FROM_CLASS (gobject_class),
167 G_SIGNAL_RUN_LAST,
168 G_STRUCT_OFFSET (CalendarSourcesClass,
169 task_sources_changed),
170 NULL,
171 NULL,
172 NULL,
173 G_TYPE_NONE,
174 0);
175 }
176
177 static void
178 calendar_sources_init (CalendarSources *sources)
179 {
180 GError *error = NULL;
181
182 sources->priv = CALENDAR_SOURCES_GET_PRIVATE (sources);
183
184 /* XXX Not sure what to do if this fails.
185 * Should this class implement GInitable or pass the
186 * registry in as a G_PARAM_CONSTRUCT_ONLY property? */
187 sources->priv->registry = e_source_registry_new_sync (NULL, &error);
188 if (error != NULL)
189 {
190 g_error ("%s: %s", G_STRFUNC, error->message);
191 }
192
193 sources->priv->source_added_id = g_signal_connect (sources->priv->registry,
194 "source-added",
195 G_CALLBACK (calendar_sources_registry_source_changed_cb),
196 sources);
197 sources->priv->source_changed_id = g_signal_connect (sources->priv->registry,
198 "source-changed",
199 G_CALLBACK (calendar_sources_registry_source_changed_cb),
200 sources);
201 sources->priv->source_removed_id = g_signal_connect (sources->priv->registry,
202 "source-removed",
203 G_CALLBACK (calendar_sources_registry_source_removed_cb),
204 sources);
205
206 sources->priv->appointment_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
207 sources->priv->appointment_sources.sources = sources;
208 sources->priv->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED];
209 sources->priv->appointment_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
210 (GEqualFunc) e_source_equal,
211 (GDestroyNotify) g_object_unref,
212 (GDestroyNotify) client_data_free);
213 sources->priv->appointment_sources.timeout_id = 0;
214
215 sources->priv->task_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
216 sources->priv->task_sources.sources = sources;
217 sources->priv->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED];
218 sources->priv->task_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
219 (GEqualFunc) e_source_equal,
220 (GDestroyNotify) g_object_unref,
221 (GDestroyNotify) client_data_free);
222 sources->priv->task_sources.timeout_id = 0;
223 }
224
225 static void
226 calendar_sources_finalize_source_data (CalendarSources *sources,
227 CalendarSourceData *source_data)
228 {
229 if (source_data->loaded)
230 {
231 g_hash_table_destroy (source_data->clients);
232 source_data->clients = NULL;
233
234 if (source_data->timeout_id != 0)
235 {
236 g_source_remove (source_data->timeout_id);
237 source_data->timeout_id = 0;
238 }
239
240 source_data->loaded = FALSE;
241 }
242 }
243
244 static void
245 calendar_sources_finalize (GObject *object)
246 {
247 CalendarSources *sources = CALENDAR_SOURCES (object);
248
249 if (sources->priv->registry)
250 {
251 g_signal_handler_disconnect (sources->priv->registry,
252 sources->priv->source_added_id);
253 g_signal_handler_disconnect (sources->priv->registry,
254 sources->priv->source_changed_id);
255 g_signal_handler_disconnect (sources->priv->registry,
256 sources->priv->source_removed_id);
257 g_object_unref (sources->priv->registry);
258 }
259 sources->priv->registry = NULL;
260
261 calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources);
262 calendar_sources_finalize_source_data (sources, &sources->priv->task_sources);
263
264 if (G_OBJECT_CLASS (parent_class)->finalize)
265 G_OBJECT_CLASS (parent_class)->finalize (object);
266 }
267
268 CalendarSources *
269 calendar_sources_get (void)
270 {
271 gpointer singleton_location = &calendar_sources_singleton;
272
273 if (calendar_sources_singleton)
274 return g_object_ref (calendar_sources_singleton);
275
276 calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL);
277 g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton),
278 singleton_location);
279
280 return calendar_sources_singleton;
281 }
282
283 /* The clients are just created here but not loaded */
284 static void
285 create_client_for_source (ESource *source,
286 ECalClientSourceType source_type,
287 CalendarSourceData *source_data)
288 {
289 ClientData *data;
290 ECalClient *client;
291 GError *error = NULL;
292
293 client = g_hash_table_lookup (source_data->clients, source);
294 g_return_if_fail (client == NULL);
295
296 client = e_cal_client_new (source, source_type, &error);
297 if (!client)
298 {
299 g_warning ("Could not load source '%s': %s",
300 e_source_get_uid (source),
301 error->message);
302 g_clear_error(&error);
303 return;
304 }
305
306 data = g_slice_new0 (ClientData);
307 data->client = client; /* takes ownership */
308 data->backend_died_id = g_signal_connect (client,
309 "backend-died",
310 G_CALLBACK (backend_died_cb),
311 source_data);
312
313 g_hash_table_insert (source_data->clients, g_object_ref (source), data);
314 }
315
316 static inline void
317 debug_dump_ecal_list (GHashTable *clients)
318 {
319 #ifdef CALENDAR_ENABLE_DEBUG
320 GList *list, *link;
321
322 dprintf ("Loaded clients:\n");
323 list = g_hash_table_get_keys (clients);
324 for (link = list; link != NULL; link = g_list_next (link))
325 {
326 ESource *source = E_SOURCE (link->data);
327
328 dprintf (" %s %s\n",
329 e_source_get_uid (source),
330 e_source_get_display_name (source));
331 }
332 g_list_free (list);
333 #endif
334 }
335
336 static void
337 calendar_sources_load_esource_list (ESourceRegistry *registry,
338 CalendarSourceData *source_data);
339
340 static gboolean
341 backend_restart (gpointer data)
342 {
343 CalendarSourceData *source_data = data;
344 ESourceRegistry *registry;
345
346 registry = source_data->sources->priv->registry;
347 calendar_sources_load_esource_list (registry, source_data);
348 g_signal_emit (source_data->sources, source_data->changed_signal, 0);
349
350 source_data->timeout_id = 0;
351
352 return FALSE;
353 }
354
355 static void
356 backend_died_cb (EClient *client, CalendarSourceData *source_data)
357 {
358 ESource *source;
359 const char *display_name;
360
361 source = e_client_get_source (client);
362 display_name = e_source_get_display_name (source);
363 g_warning ("The calendar backend for '%s' has crashed.", display_name);
364 g_hash_table_remove (source_data->clients, source);
365
366 if (source_data->timeout_id != 0)
367 {
368 g_source_remove (source_data->timeout_id);
369 source_data->timeout_id = 0;
370 }
371
372 source_data->timeout_id = g_timeout_add_seconds (2, backend_restart,
373 source_data);
374 }
375
376 static void
377 calendar_sources_load_esource_list (ESourceRegistry *registry,
378 CalendarSourceData *source_data)
379 {
380 GList *list, *link;
381 const gchar *extension_name;
382
383 switch (source_data->source_type)
384 {
385 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
386 extension_name = E_SOURCE_EXTENSION_CALENDAR;
387 break;
388 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
389 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
390 break;
391 default:
392 g_return_if_reached ();
393 }
394
395 list = e_source_registry_list_sources (registry, extension_name);
396
397 for (link = list; link != NULL; link = g_list_next (link))
398 {
399 ESource *source = E_SOURCE (link->data);
400 ESourceSelectable *extension;
401 gboolean show_source;
402
403 extension = e_source_get_extension (source, extension_name);
404 show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension);
405
406 if (show_source)
407 create_client_for_source (source, source_data->source_type, source_data);
408 }
409
410 debug_dump_ecal_list (source_data->clients);
411
412 g_list_free_full (list, g_object_unref);
413 }
414
415 static void
416 calendar_sources_registry_source_changed_cb (ESourceRegistry *registry,
417 ESource *source,
418 CalendarSources *sources)
419 {
420 if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
421 {
422 CalendarSourceData *source_data;
423 ESourceSelectable *extension;
424 gboolean have_client;
425 gboolean show_source;
426
427 source_data = &sources->priv->appointment_sources;
428 extension = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR);
429 have_client = (g_hash_table_lookup (source_data->clients, source) != NULL);
430 show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension);
431
432 if (!show_source && have_client)
433 {
434 g_hash_table_remove (source_data->clients, source);
435 g_signal_emit (sources, source_data->changed_signal, 0);
436 }
437 if (show_source && !have_client)
438 {
439 create_client_for_source (source, source_data->source_type, source_data);
440 g_signal_emit (sources, source_data->changed_signal, 0);
441 }
442 }
443
444 if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
445 {
446 CalendarSourceData *source_data;
447 ESourceSelectable *extension;
448 gboolean have_client;
449 gboolean show_source;
450
451 source_data = &sources->priv->task_sources;
452 extension = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
453 have_client = (g_hash_table_lookup (source_data->clients, source) != NULL);
454 show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension);
455
456 if (!show_source && have_client)
457 {
458 g_hash_table_remove (source_data->clients, source);
459 g_signal_emit (sources, source_data->changed_signal, 0);
460 }
461 if (show_source && !have_client)
462 {
463 create_client_for_source (source, source_data->source_type, source_data);
464 g_signal_emit (sources, source_data->changed_signal, 0);
465 }
466 }
467 }
468
469 static void
470 calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
471 ESource *source,
472 CalendarSources *sources)
473 {
474 if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
475 {
476 CalendarSourceData *source_data;
477
478 source_data = &sources->priv->appointment_sources;
479 g_hash_table_remove (source_data->clients, source);
480 g_signal_emit (sources, source_data->changed_signal, 0);
481 }
482
483 if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
484 {
485 CalendarSourceData *source_data;
486
487 source_data = &sources->priv->task_sources;
488 g_hash_table_remove (source_data->clients, source);
489 g_signal_emit (sources, source_data->changed_signal, 0);
490 }
491 }
492
493 GList *
494 calendar_sources_get_appointment_clients (CalendarSources *sources)
495 {
496 GList *list, *link;
497
498 g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
499
500 if (!sources->priv->appointment_sources.loaded)
501 {
502 calendar_sources_load_esource_list (sources->priv->registry,
503 &sources->priv->appointment_sources);
504 sources->priv->appointment_sources.loaded = TRUE;
505 }
506
507 list = g_hash_table_get_values (sources->priv->appointment_sources.clients);
508
509 for (link = list; link != NULL; link = g_list_next (link))
510 link->data = ((ClientData *) link->data)->client;
511
512 return list;
513 }
514
515 GList *
516 calendar_sources_get_task_clients (CalendarSources *sources)
517 {
518 GList *list, *link;
519
520 g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
521
522 if (!sources->priv->task_sources.loaded)
523 {
524 calendar_sources_load_esource_list (sources->priv->registry,
525 &sources->priv->task_sources);
526 sources->priv->task_sources.loaded = TRUE;
527 }
528
529 list = g_hash_table_get_values (sources->priv->task_sources.clients);
530
531 for (link = list; link != NULL; link = g_list_next (link))
532 link->data = ((ClientData *) link->data)->client;
533
534 return list;
535 }