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 (&registry);
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