1 /* vi: set et sw=4 ts=8 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 8 -*- */
3 /*
4  * Keep track of which handlers own which channels.
5  *
6  * Copyright (C) 2009 Nokia Corporation
7  * Copyright (C) 2009 Collabora Ltd.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22  * 02110-1301 USA
23  *
24  */
25 
26 #include "config.h"
27 #include "mcd-handler-map-priv.h"
28 
29 #include <telepathy-glib/telepathy-glib.h>
30 
31 #include "channel-utils.h"
32 #include "mcd-channel-priv.h"
33 
34 G_DEFINE_TYPE (McdHandlerMap, _mcd_handler_map, G_TYPE_OBJECT);
35 
36 struct _McdHandlerMapPrivate
37 {
38     TpDBusDaemon *dbus_daemon;
39     /* The handler for each channel currently being handled
40      * owned gchar *object_path => owned gchar *unique_name */
41     GHashTable *channel_processes;
42     /* The well-known bus name we invoked in channel_processes[path]
43      * owned gchar *object_path => owned gchar *well_known_name */
44     GHashTable *channel_clients;
45     /* owned gchar *unique_name => malloc'd gsize, number of channels */
46     GHashTable *handler_processes;
47     /* owned gchar *object_path => ref'd TpChannel */
48     GHashTable *handled_channels;
49     /* owned gchar *object_path =>  owned gchar *account_path */
50     GHashTable *channel_accounts;
51 };
52 
53 enum {
54     PROP_0,
55     PROP_DBUS_DAEMON
56 };
57 
58 static void
slice_free_gsize(gpointer p)59 slice_free_gsize (gpointer p)
60 {
61     g_slice_free (gsize, p);
62 }
63 
64 static void
_mcd_handler_map_init(McdHandlerMap * self)65 _mcd_handler_map_init (McdHandlerMap *self)
66 {
67     self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MCD_TYPE_HANDLER_MAP,
68                                               McdHandlerMapPrivate);
69 
70     self->priv->channel_processes = g_hash_table_new_full (g_str_hash,
71                                                            g_str_equal,
72                                                            g_free, g_free);
73 
74     self->priv->channel_clients = g_hash_table_new_full (g_str_hash,
75                                                          g_str_equal,
76                                                          g_free, g_free);
77 
78     self->priv->handler_processes = g_hash_table_new_full (g_str_hash,
79                                                            g_str_equal,
80                                                            g_free,
81                                                            slice_free_gsize);
82 
83     self->priv->handled_channels = g_hash_table_new_full (g_str_hash,
84                                                           g_str_equal,
85                                                           g_free,
86                                                           g_object_unref);
87 
88     self->priv->channel_accounts = g_hash_table_new_full (g_str_hash,
89                                                           g_str_equal,
90                                                           g_free,
91                                                           g_free);
92 }
93 
94 static void
_mcd_handler_map_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)95 _mcd_handler_map_get_property (GObject *object,
96                                guint prop_id,
97                                GValue *value,
98                                GParamSpec *pspec)
99 {
100     McdHandlerMap *self = MCD_HANDLER_MAP (object);
101 
102     switch (prop_id)
103     {
104         case PROP_DBUS_DAEMON:
105             g_value_set_object (value, self->priv->dbus_daemon);
106             break;
107 
108         default:
109             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
110     }
111 }
112 
113 static void
_mcd_handler_map_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)114 _mcd_handler_map_set_property (GObject *object,
115                                guint prop_id,
116                                const GValue *value,
117                                GParamSpec *pspec)
118 {
119     McdHandlerMap *self = MCD_HANDLER_MAP (object);
120 
121     switch (prop_id)
122     {
123         case PROP_DBUS_DAEMON:
124             g_assert (self->priv->dbus_daemon == NULL); /* construct-only */
125             self->priv->dbus_daemon =
126                 TP_DBUS_DAEMON (g_value_dup_object (value));
127             break;
128 
129         default:
130             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
131     }
132 }
133 
134 static void mcd_handler_map_name_owner_cb (TpDBusDaemon *dbus_daemon,
135                                            const gchar *name,
136                                            const gchar *new_owner,
137                                            gpointer user_data);
138 
139 static void
_mcd_handler_map_dispose(GObject * object)140 _mcd_handler_map_dispose (GObject *object)
141 {
142     McdHandlerMap *self = MCD_HANDLER_MAP (object);
143 
144     tp_clear_pointer (&self->priv->handled_channels, g_hash_table_unref);
145 
146     if (self->priv->handler_processes != NULL)
147     {
148         GHashTableIter iter;
149         gpointer k;
150 
151         g_assert (self->priv->dbus_daemon != NULL);
152 
153         g_hash_table_iter_init (&iter, self->priv->handler_processes);
154 
155         while (g_hash_table_iter_next (&iter, &k, NULL))
156         {
157             tp_dbus_daemon_cancel_name_owner_watch (self->priv->dbus_daemon,
158                 k, mcd_handler_map_name_owner_cb, object);
159         }
160 
161     }
162 
163     tp_clear_pointer (&self->priv->handler_processes, g_hash_table_unref);
164     tp_clear_object (&self->priv->dbus_daemon);
165 
166     G_OBJECT_CLASS (_mcd_handler_map_parent_class)->dispose (object);
167 }
168 
169 static void
_mcd_handler_map_finalize(GObject * object)170 _mcd_handler_map_finalize (GObject *object)
171 {
172     McdHandlerMap *self = MCD_HANDLER_MAP (object);
173 
174     tp_clear_pointer (&self->priv->channel_processes, g_hash_table_unref);
175     tp_clear_pointer (&self->priv->channel_clients, g_hash_table_unref);
176     tp_clear_pointer (&self->priv->channel_accounts, g_hash_table_unref);
177 
178     G_OBJECT_CLASS (_mcd_handler_map_parent_class)->finalize (object);
179 }
180 
181 static void
_mcd_handler_map_class_init(McdHandlerMapClass * klass)182 _mcd_handler_map_class_init (McdHandlerMapClass *klass)
183 {
184     GObjectClass *object_class = (GObjectClass *) klass;
185 
186     g_type_class_add_private (object_class, sizeof (McdHandlerMapPrivate));
187     object_class->dispose = _mcd_handler_map_dispose;
188     object_class->get_property = _mcd_handler_map_get_property;
189     object_class->set_property = _mcd_handler_map_set_property;
190     object_class->finalize = _mcd_handler_map_finalize;
191 
192     g_object_class_install_property (object_class, PROP_DBUS_DAEMON,
193         g_param_spec_object ("dbus-daemon", "D-Bus daemon", "D-Bus daemon",
194             TP_TYPE_DBUS_DAEMON,
195             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
196             G_PARAM_STATIC_STRINGS));
197 }
198 
199 McdHandlerMap *
_mcd_handler_map_new(TpDBusDaemon * dbus_daemon)200 _mcd_handler_map_new (TpDBusDaemon *dbus_daemon)
201 {
202     return g_object_new (MCD_TYPE_HANDLER_MAP,
203                          "dbus-daemon", dbus_daemon,
204                          NULL);
205 }
206 
207 /*
208  * @well_known_name: (out): the well-known Client name of the handler,
209  *  or %NULL if not known (or if it's Mission Control itself)
210  *
211  * Returns: (transfer none): the unique name of the handler
212  */
213 const gchar *
_mcd_handler_map_get_handler(McdHandlerMap * self,const gchar * channel_path,const gchar ** well_known_name)214 _mcd_handler_map_get_handler (McdHandlerMap *self,
215                               const gchar *channel_path,
216                               const gchar **well_known_name)
217 {
218     if (well_known_name != NULL)
219         *well_known_name = g_hash_table_lookup (self->priv->channel_clients,
220                                                 channel_path);
221 
222     return g_hash_table_lookup (self->priv->channel_processes, channel_path);
223 }
224 
225 /*
226  * @channel_path: a channel
227  * @unique_name: the unique name of the handler
228  * @well_known_name: the well-known name of the handler, or %NULL if not known
229  *  (or if it's Mission Control itself)
230  *
231  * Record that @channel_path is being handled by the Client
232  * @well_known_name, whose unique name is @unique_name.
233  *
234  * Returns: (transfer none): the unique name of the handler
235  */
236 void
_mcd_handler_map_set_path_handled(McdHandlerMap * self,const gchar * channel_path,const gchar * unique_name,const gchar * well_known_name)237 _mcd_handler_map_set_path_handled (McdHandlerMap *self,
238                                    const gchar *channel_path,
239                                    const gchar *unique_name,
240                                    const gchar *well_known_name)
241 {
242     const gchar *old;
243     gsize *counter;
244 
245     /* In case we want to re-invoke the same client later, remember its
246      * well-known name, if we know it. (In edge cases where we're recovering
247      * from an MC crash, we can only guess, so we get NULL.) */
248     if (well_known_name == NULL)
249         g_hash_table_remove (self->priv->channel_clients, channel_path);
250     else
251         g_hash_table_insert (self->priv->channel_clients,
252                              g_strdup (channel_path),
253                              g_strdup (well_known_name));
254 
255     old = g_hash_table_lookup (self->priv->channel_processes, channel_path);
256 
257     if (!tp_strdiff (old, unique_name))
258     {
259         /* no-op - the new handler is the same as the old */
260         return;
261     }
262 
263     if (old != NULL)
264     {
265         counter = g_hash_table_lookup (self->priv->handler_processes,
266                                        old);
267 
268         if (--*counter == 0)
269         {
270             tp_dbus_daemon_cancel_name_owner_watch (self->priv->dbus_daemon,
271                 old, mcd_handler_map_name_owner_cb, self);
272             g_hash_table_remove (self->priv->handler_processes, old);
273         }
274     }
275 
276     g_hash_table_insert (self->priv->channel_processes,
277                          g_strdup (channel_path), g_strdup (unique_name));
278 
279     counter = g_hash_table_lookup (self->priv->handler_processes,
280                                    unique_name);
281 
282     if (counter == NULL)
283     {
284         counter = g_slice_new (gsize);
285         *counter = 1;
286         g_hash_table_insert (self->priv->handler_processes,
287                              g_strdup (unique_name), counter);
288         tp_dbus_daemon_watch_name_owner (self->priv->dbus_daemon, unique_name,
289                                          mcd_handler_map_name_owner_cb, self,
290                                          NULL);
291     }
292     else
293     {
294         ++*counter;
295     }
296 }
297 
298 static void
handled_channel_invalidated_cb(TpChannel * channel,guint domain,gint code,const gchar * message,gpointer user_data)299 handled_channel_invalidated_cb (TpChannel *channel,
300                                 guint domain,
301                                 gint code,
302                                 const gchar *message,
303                                 gpointer user_data)
304 {
305     McdHandlerMap *self = MCD_HANDLER_MAP (user_data);
306     const gchar *path = tp_proxy_get_object_path (channel);
307     gchar *handler;
308 
309     g_signal_handlers_disconnect_by_func (channel,
310                                           handled_channel_invalidated_cb,
311                                           user_data);
312 
313     handler = g_hash_table_lookup (self->priv->channel_processes, path);
314 
315     if (handler != NULL)
316     {
317         gsize *counter = g_hash_table_lookup (self->priv->handler_processes,
318                                               handler);
319 
320         g_assert (counter != NULL);
321 
322         if (--*counter == 0)
323         {
324             g_hash_table_remove (self->priv->handler_processes, handler);
325         }
326 
327         g_hash_table_remove (self->priv->channel_processes, path);
328     }
329 
330     g_hash_table_remove (self->priv->handled_channels, path);
331     g_hash_table_remove (self->priv->channel_accounts, path);
332 
333     g_object_unref (self);
334 }
335 
336 /*
337  * @channel: a channel
338  * @unique_name: the unique name of the handler
339  * @well_known_name: the well-known name of the handler, or %NULL if not known
340  *  (or if it's Mission Control itself)
341  * @account_path: the account that @channel came from, or %NULL if not known
342  *
343  * Record that @channel_path is being handled by the Client
344  * @well_known_name, whose unique name is @unique_name.
345  * The record will be removed if the channel closes or is invalidated.
346  *
347  * Returns: (transfer none): the unique name of the handler
348  */
349 void
_mcd_handler_map_set_channel_handled(McdHandlerMap * self,TpChannel * channel,const gchar * unique_name,const gchar * well_known_name,const gchar * account_path)350 _mcd_handler_map_set_channel_handled (McdHandlerMap *self,
351                                       TpChannel *channel,
352                                       const gchar *unique_name,
353                                       const gchar *well_known_name,
354                                       const gchar *account_path)
355 {
356     const gchar *path = tp_proxy_get_object_path (channel);
357 
358     g_hash_table_insert (self->priv->handled_channels,
359                          g_strdup (path),
360                          g_object_ref (channel));
361 
362     g_hash_table_insert (self->priv->channel_accounts,
363                          g_strdup (path),
364                          g_strdup (account_path));
365 
366     g_signal_connect (channel, "invalidated",
367                       G_CALLBACK (handled_channel_invalidated_cb),
368                       g_object_ref (self));
369 
370     _mcd_handler_map_set_path_handled (self, path, unique_name,
371                                        well_known_name);
372 }
373 
374 static void
_mcd_handler_map_set_handler_crashed(McdHandlerMap * self,const gchar * unique_name)375 _mcd_handler_map_set_handler_crashed (McdHandlerMap *self,
376                                       const gchar *unique_name)
377 {
378     gsize *counter = g_hash_table_lookup (self->priv->handler_processes,
379                                           unique_name);
380 
381     if (counter != NULL)
382     {
383         GHashTableIter iter;
384         gpointer path_p, name_p;
385         GList *paths = NULL;
386 
387         tp_dbus_daemon_cancel_name_owner_watch (self->priv->dbus_daemon,
388                                                 unique_name,
389                                                 mcd_handler_map_name_owner_cb,
390                                                 self);
391         g_hash_table_remove (self->priv->handler_processes, unique_name);
392 
393         /* This is O(number of channels being handled) but then again
394          * it only happens if a handler crashes */
395         g_hash_table_iter_init (&iter, self->priv->channel_processes);
396 
397         while (g_hash_table_iter_next (&iter, &path_p, &name_p))
398         {
399             if (!tp_strdiff (name_p, unique_name))
400             {
401                 DEBUG ("%s lost its handler %s", (const gchar *) path_p,
402                        (const gchar *) name_p);
403                 paths = g_list_prepend (paths, g_strdup (path_p));
404                 g_hash_table_iter_remove (&iter);
405             }
406         }
407 
408         while (paths != NULL)
409         {
410             gchar *path = paths->data;
411             TpChannel *channel = g_hash_table_lookup (
412                 self->priv->handled_channels, path);
413 
414             /* this is NULL-safe */
415             if (_mcd_tp_channel_should_close (channel, "closing"))
416             {
417                 DEBUG ("Closing channel %s", path);
418                 /* the corresponding McdChannel will get aborted when the
419                  * Channel actually closes */
420                 tp_cli_channel_call_close (channel, -1,
421                                            NULL, NULL, NULL, NULL);
422             }
423 
424             paths = g_list_delete_link (paths, paths);
425             g_free (path);
426         }
427     }
428 }
429 
430 static void
mcd_handler_map_name_owner_cb(TpDBusDaemon * dbus_daemon,const gchar * name,const gchar * new_owner,gpointer user_data)431 mcd_handler_map_name_owner_cb (TpDBusDaemon *dbus_daemon,
432                                const gchar *name,
433                                const gchar *new_owner,
434                                gpointer user_data)
435 {
436     if (new_owner == NULL || new_owner[0] == '\0')
437     {
438         _mcd_handler_map_set_handler_crashed (user_data, name);
439     }
440 }
441 
442 /*
443  * Returns: (transfer container): all channels that are being handled
444  */
445 GList *
_mcd_handler_map_get_handled_channels(McdHandlerMap * self)446 _mcd_handler_map_get_handled_channels (McdHandlerMap *self)
447 {
448     return g_hash_table_get_values (self->priv->handled_channels);
449 }
450 
451 /*
452  * Returns: (transfer none): the account that @channel_path belongs to,
453  *  or %NULL if not known
454  */
455 const gchar *
_mcd_handler_map_get_channel_account(McdHandlerMap * self,const gchar * channel_path)456 _mcd_handler_map_get_channel_account (McdHandlerMap *self,
457     const gchar *channel_path)
458 {
459     return g_hash_table_lookup (self->priv->channel_accounts,
460         channel_path);
461 }
462 
463 /*
464  * Record that MC itself is handling this channel, internally.
465  */
466 void
_mcd_handler_map_set_channel_handled_internally(McdHandlerMap * self,TpChannel * channel,const gchar * account_path)467 _mcd_handler_map_set_channel_handled_internally (McdHandlerMap *self,
468                                                  TpChannel *channel,
469                                                  const gchar *account_path)
470 {
471     _mcd_handler_map_set_channel_handled (self, channel,
472         tp_dbus_daemon_get_unique_name (self->priv->dbus_daemon),
473         NULL, account_path);
474 }
475