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