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, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors:
18 * Mark McLoughlin <mark@skynet.ie>
19 * William Jon McCann <mccann@jhu.edu>
20 * Martin Grimme <martin@pycage.de>
21 * Christian Kellner <gicmo@xatom.net>
22 */
23
24 #include <config.h>
25
26 #include "calendar-sources.h"
27
28 #include <libintl.h>
29 #include <string.h>
30 #define HANDLE_LIBICAL_MEMORY
31 #define EDS_DISABLE_DEPRECATED
32 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
33 #include <libecal/libecal.h>
34 G_GNUC_END_IGNORE_DEPRECATIONS
35
36 #undef CALENDAR_ENABLE_DEBUG
37 #include "calendar-debug.h"
38
39 typedef struct _ClientData ClientData;
40 typedef struct _CalendarSourceData CalendarSourceData;
41
42 struct _ClientData
43 {
44 ECalClient *client;
45 gulong backend_died_id;
46 };
47
48 typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate;
49
50 struct _CalendarSources
51 {
52 GObject parent;
53
54 ESourceRegistryWatcher *registry_watcher;
55 gulong filter_id;
56 gulong appeared_id;
57 gulong disappeared_id;
58
59 GMutex clients_lock;
60 GHashTable *clients; /* ESource -> ClientData */
61 };
62
63 G_DEFINE_TYPE (CalendarSources, calendar_sources, G_TYPE_OBJECT)
64
65 enum
66 {
67 CLIENT_APPEARED,
68 CLIENT_DISAPPEARED,
69 LAST_SIGNAL
70 };
71 static guint signals [LAST_SIGNAL] = { 0, };
72
73 static void
calendar_sources_client_connected_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)74 calendar_sources_client_connected_cb (GObject *source_object,
75 GAsyncResult *result,
76 gpointer user_data)
77 {
78 CalendarSources *sources = CALENDAR_SOURCES (source_object);
79 ESource *source = user_data;
80 EClient *client;
81 g_autoptr (GError) error = NULL;
82
83 /* The calendar_sources_connect_client_sync() already stored the 'client'
84 * into the sources->clients */
85 client = calendar_sources_connect_client_finish (sources, result, &error);
86 if (error)
87 {
88 g_warning ("Could not load source '%s': %s",
89 e_source_get_uid (source),
90 error->message);
91 }
92 else
93 {
94 g_signal_emit (sources, signals[CLIENT_APPEARED], 0, client, NULL);
95 }
96
97 g_clear_object (&client);
98 g_clear_object (&source);
99 }
100
101 static gboolean
registry_watcher_filter_cb(ESourceRegistryWatcher * watcher,ESource * source,CalendarSources * sources)102 registry_watcher_filter_cb (ESourceRegistryWatcher *watcher,
103 ESource *source,
104 CalendarSources *sources)
105 {
106 return e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR) &&
107 e_source_selectable_get_selected (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR));
108 }
109
110 static void
registry_watcher_source_appeared_cb(ESourceRegistryWatcher * watcher,ESource * source,CalendarSources * sources)111 registry_watcher_source_appeared_cb (ESourceRegistryWatcher *watcher,
112 ESource *source,
113 CalendarSources *sources)
114 {
115 ECalClientSourceType source_type;
116
117 if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
118 source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
119 else if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST))
120 source_type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS;
121 else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
122 source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
123 else
124 g_return_if_reached ();
125
126 calendar_sources_connect_client (sources, source, source_type, 30, NULL, calendar_sources_client_connected_cb, g_object_ref (source));
127 }
128
129 static void
registry_watcher_source_disappeared_cb(ESourceRegistryWatcher * watcher,ESource * source,CalendarSources * sources)130 registry_watcher_source_disappeared_cb (ESourceRegistryWatcher *watcher,
131 ESource *source,
132 CalendarSources *sources)
133 {
134 gboolean emit;
135
136 g_mutex_lock (&sources->clients_lock);
137
138 emit = g_hash_table_remove (sources->clients, source);
139
140 g_mutex_unlock (&sources->clients_lock);
141
142 if (emit)
143 g_signal_emit (sources, signals[CLIENT_DISAPPEARED], 0, e_source_get_uid (source), NULL);
144 }
145
146 static void
client_data_free(ClientData * data)147 client_data_free (ClientData *data)
148 {
149 g_signal_handler_disconnect (data->client, data->backend_died_id);
150 g_object_unref (data->client);
151 g_free (data);
152 }
153
154 static void
calendar_sources_constructed(GObject * object)155 calendar_sources_constructed (GObject *object)
156 {
157 CalendarSources *sources = CALENDAR_SOURCES (object);
158 ESourceRegistry *registry = NULL;
159 GError *error = NULL;
160
161 G_OBJECT_CLASS (calendar_sources_parent_class)->constructed (object);
162
163 registry = e_source_registry_new_sync (NULL, &error);
164 if (error != NULL)
165 {
166 /* Any error is fatal, but we don't want to crash gnome-shell-calendar-server
167 because of e-d-s problems. So just exit here.
168 */
169 g_warning ("Failed to start evolution-source-registry: %s", error->message);
170 exit (EXIT_FAILURE);
171 }
172
173 g_return_if_fail (registry != NULL);
174
175 sources->registry_watcher = e_source_registry_watcher_new (registry, NULL);
176
177 g_clear_object (®istry);
178
179 sources->clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
180 (GEqualFunc) e_source_equal,
181 (GDestroyNotify) g_object_unref,
182 (GDestroyNotify) client_data_free);
183 sources->filter_id = g_signal_connect (sources->registry_watcher,
184 "filter",
185 G_CALLBACK (registry_watcher_filter_cb),
186 sources);
187 sources->appeared_id = g_signal_connect (sources->registry_watcher,
188 "appeared",
189 G_CALLBACK (registry_watcher_source_appeared_cb),
190 sources);
191 sources->disappeared_id = g_signal_connect (sources->registry_watcher,
192 "disappeared",
193 G_CALLBACK (registry_watcher_source_disappeared_cb),
194 sources);
195
196 e_source_registry_watcher_reclaim (sources->registry_watcher);
197 }
198
199 static void
calendar_sources_finalize(GObject * object)200 calendar_sources_finalize (GObject *object)
201 {
202 CalendarSources *sources = CALENDAR_SOURCES (object);
203
204 g_clear_pointer (&sources->clients, g_hash_table_destroy);
205
206 if (sources->registry_watcher)
207 {
208 g_signal_handler_disconnect (sources->registry_watcher,
209 sources->filter_id);
210 g_signal_handler_disconnect (sources->registry_watcher,
211 sources->appeared_id);
212 g_signal_handler_disconnect (sources->registry_watcher,
213 sources->disappeared_id);
214 g_clear_object (&sources->registry_watcher);
215 }
216
217 g_mutex_clear (&sources->clients_lock);
218
219 G_OBJECT_CLASS (calendar_sources_parent_class)->finalize (object);
220 }
221
222 static void
calendar_sources_class_init(CalendarSourcesClass * klass)223 calendar_sources_class_init (CalendarSourcesClass *klass)
224 {
225 GObjectClass *gobject_class = (GObjectClass *) klass;
226
227 gobject_class->constructed = calendar_sources_constructed;
228 gobject_class->finalize = calendar_sources_finalize;
229
230 signals [CLIENT_APPEARED] =
231 g_signal_new ("client-appeared",
232 G_TYPE_FROM_CLASS (gobject_class),
233 G_SIGNAL_RUN_LAST,
234 0,
235 NULL,
236 NULL,
237 NULL,
238 G_TYPE_NONE,
239 1,
240 E_TYPE_CAL_CLIENT);
241
242 signals [CLIENT_DISAPPEARED] =
243 g_signal_new ("client-disappeared",
244 G_TYPE_FROM_CLASS (gobject_class),
245 G_SIGNAL_RUN_LAST,
246 0,
247 NULL,
248 NULL,
249 NULL,
250 G_TYPE_NONE,
251 1,
252 G_TYPE_STRING); /* ESource::uid of the disappeared client */
253 }
254
255 static void
calendar_sources_init(CalendarSources * sources)256 calendar_sources_init (CalendarSources *sources)
257 {
258 g_mutex_init (&sources->clients_lock);
259 }
260
261 CalendarSources *
calendar_sources_get(void)262 calendar_sources_get (void)
263 {
264 static CalendarSources *calendar_sources_singleton = NULL;
265 gpointer singleton_location = &calendar_sources_singleton;
266
267 if (calendar_sources_singleton)
268 return g_object_ref (calendar_sources_singleton);
269
270 calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL);
271 g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton),
272 singleton_location);
273
274 return calendar_sources_singleton;
275 }
276
277 ESourceRegistry *
calendar_sources_get_registry(CalendarSources * sources)278 calendar_sources_get_registry (CalendarSources *sources)
279 {
280 return e_source_registry_watcher_get_registry (sources->registry_watcher);
281 }
282
283 static void
gather_event_clients_cb(gpointer key,gpointer value,gpointer user_data)284 gather_event_clients_cb (gpointer key,
285 gpointer value,
286 gpointer user_data)
287 {
288 GSList **plist = user_data;
289 ClientData *cd = value;
290
291 if (cd)
292 *plist = g_slist_prepend (*plist, g_object_ref (cd->client));
293 }
294
295 GSList *
calendar_sources_ref_clients(CalendarSources * sources)296 calendar_sources_ref_clients (CalendarSources *sources)
297 {
298 GSList *list = NULL;
299
300 g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
301
302 g_mutex_lock (&sources->clients_lock);
303 g_hash_table_foreach (sources->clients, gather_event_clients_cb, &list);
304 g_mutex_unlock (&sources->clients_lock);
305
306 return list;
307 }
308
309 gboolean
calendar_sources_has_clients(CalendarSources * sources)310 calendar_sources_has_clients (CalendarSources *sources)
311 {
312 GHashTableIter iter;
313 gpointer value;
314 gboolean has = FALSE;
315
316 g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE);
317
318 g_mutex_lock (&sources->clients_lock);
319
320 g_hash_table_iter_init (&iter, sources->clients);
321 while (!has && g_hash_table_iter_next (&iter, NULL, &value))
322 {
323 ClientData *cd = value;
324
325 has = cd != NULL;
326 }
327
328 g_mutex_unlock (&sources->clients_lock);
329
330 return has;
331 }
332
333 static void
backend_died_cb(EClient * client,CalendarSources * sources)334 backend_died_cb (EClient *client,
335 CalendarSources *sources)
336 {
337 ESource *source;
338 const char *display_name;
339
340 source = e_client_get_source (client);
341 display_name = e_source_get_display_name (source);
342 g_warning ("The calendar backend for '%s' has crashed.", display_name);
343 g_mutex_lock (&sources->clients_lock);
344 g_hash_table_remove (sources->clients, source);
345 g_mutex_unlock (&sources->clients_lock);
346 }
347
348 static EClient *
calendar_sources_connect_client_sync(CalendarSources * sources,ESource * source,ECalClientSourceType source_type,guint32 wait_for_connected_seconds,GCancellable * cancellable,GError ** error)349 calendar_sources_connect_client_sync (CalendarSources *sources,
350 ESource *source,
351 ECalClientSourceType source_type,
352 guint32 wait_for_connected_seconds,
353 GCancellable *cancellable,
354 GError **error)
355 {
356 EClient *client = NULL;
357 ClientData *client_data;
358
359 g_mutex_lock (&sources->clients_lock);
360 client_data = g_hash_table_lookup (sources->clients, source);
361 if (client_data)
362 client = E_CLIENT (g_object_ref (client_data->client));
363 g_mutex_unlock (&sources->clients_lock);
364
365 if (client)
366 return client;
367
368 client = e_cal_client_connect_sync (source, source_type, wait_for_connected_seconds, cancellable, error);
369 if (!client)
370 return NULL;
371
372 g_mutex_lock (&sources->clients_lock);
373 client_data = g_hash_table_lookup (sources->clients, source);
374 if (client_data)
375 {
376 g_clear_object (&client);
377 client = E_CLIENT (g_object_ref (client_data->client));
378 }
379 else
380 {
381 client_data = g_new0 (ClientData, 1);
382 client_data->client = E_CAL_CLIENT (g_object_ref (client));
383 client_data->backend_died_id = g_signal_connect (client,
384 "backend-died",
385 G_CALLBACK (backend_died_cb),
386 sources);
387
388 g_hash_table_insert (sources->clients, g_object_ref (source), client_data);
389 }
390 g_mutex_unlock (&sources->clients_lock);
391
392 return client;
393 }
394
395 typedef struct _AsyncContext {
396 ESource *source;
397 ECalClientSourceType source_type;
398 guint32 wait_for_connected_seconds;
399 } AsyncContext;
400
401 static void
async_context_free(gpointer ptr)402 async_context_free (gpointer ptr)
403 {
404 AsyncContext *ctx = ptr;
405
406 if (ctx)
407 {
408 g_clear_object (&ctx->source);
409 g_free (ctx);
410 }
411 }
412
413 static void
calendar_sources_connect_client_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)414 calendar_sources_connect_client_thread (GTask *task,
415 gpointer source_object,
416 gpointer task_data,
417 GCancellable *cancellable)
418 {
419 CalendarSources *sources = source_object;
420 AsyncContext *ctx = task_data;
421 EClient *client;
422 GError *local_error = NULL;
423
424 client = calendar_sources_connect_client_sync (sources, ctx->source, ctx->source_type,
425 ctx->wait_for_connected_seconds, cancellable, &local_error);
426 if (!client)
427 {
428 if (local_error)
429 g_task_return_error (task, local_error);
430 else
431 g_task_return_pointer (task, NULL, NULL);
432 } else {
433 g_task_return_pointer (task, client, g_object_unref);
434 }
435 }
436
437 void
calendar_sources_connect_client(CalendarSources * sources,ESource * source,ECalClientSourceType source_type,guint32 wait_for_connected_seconds,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)438 calendar_sources_connect_client (CalendarSources *sources,
439 ESource *source,
440 ECalClientSourceType source_type,
441 guint32 wait_for_connected_seconds,
442 GCancellable *cancellable,
443 GAsyncReadyCallback callback,
444 gpointer user_data)
445 {
446 AsyncContext *ctx;
447 g_autoptr (GTask) task = NULL;
448
449 ctx = g_new0 (AsyncContext, 1);
450 ctx->source = g_object_ref (source);
451 ctx->source_type = source_type;
452 ctx->wait_for_connected_seconds = wait_for_connected_seconds;
453
454 task = g_task_new (sources, cancellable, callback, user_data);
455 g_task_set_source_tag (task, calendar_sources_connect_client);
456 g_task_set_task_data (task, ctx, async_context_free);
457
458 g_task_run_in_thread (task, calendar_sources_connect_client_thread);
459 }
460
461 EClient *
calendar_sources_connect_client_finish(CalendarSources * sources,GAsyncResult * result,GError ** error)462 calendar_sources_connect_client_finish (CalendarSources *sources,
463 GAsyncResult *result,
464 GError **error)
465 {
466 g_return_val_if_fail (g_task_is_valid (result, sources), NULL);
467 g_return_val_if_fail (g_async_result_is_tagged (result, calendar_sources_connect_client), NULL);
468
469 return g_task_propagate_pointer (G_TASK (result), error);
470 }
471
472
473 void
print_debug(const gchar * format,...)474 print_debug (const gchar *format,
475 ...)
476 {
477 g_autofree char *s = NULL;
478 g_autofree char *timestamp = NULL;
479 va_list ap;
480 g_autoptr (GDateTime) now = NULL;
481 static size_t once_init_value = 0;
482 static gboolean show_debug = FALSE;
483 static guint pid = 0;
484
485 if (g_once_init_enter (&once_init_value))
486 {
487 show_debug = (g_getenv ("CALENDAR_SERVER_DEBUG") != NULL);
488 pid = getpid ();
489 g_once_init_leave (&once_init_value, 1);
490 }
491
492 if (!show_debug)
493 goto out;
494
495 now = g_date_time_new_now_local ();
496 timestamp = g_date_time_format (now, "%H:%M:%S");
497
498 va_start (ap, format);
499 s = g_strdup_vprintf (format, ap);
500 va_end (ap);
501
502 g_print ("gnome-shell-calendar-server[%d]: %s.%03d: %s\n",
503 pid, timestamp, g_date_time_get_microsecond (now), s);
504 out:
505 ;
506 }
507