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 * This file is part of mission-control
5 *
6 * Copyright © 2007-2011 Nokia Corporation.
7 * Copyright © 2009-2011 Collabora Ltd.
8 *
9 * Contact: Naba Kumar <naba.kumar@nokia.com>
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public License
13 * version 2.1 as published by the Free Software Foundation.
14 *
15 * This library is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23 * 02110-1301 USA
24 *
25 */
26
27 /**
28 * SECTION:mcd-dispatcher
29 * @title: McdDispatcher
30 * @short_description: Dispatcher class to dispatch channels to handlers
31 * @see_also:
32 * @stability: Unstable
33 * @include: mcd-dispatcher.h
34 *
35 * FIXME
36 */
37
38 #include "config.h"
39
40 #include <dlfcn.h>
41 #include <glib.h>
42 #include <glib/gprintf.h>
43
44 #include <dbus/dbus-glib-lowlevel.h>
45
46 #include "mission-control-plugins/mission-control-plugins.h"
47
48 #include "client-registry.h"
49 #include "mcd-account-priv.h"
50 #include "mcd-client-priv.h"
51 #include "mcd-connection.h"
52 #include "mcd-connection-priv.h"
53 #include "mcd-channel.h"
54 #include "mcd-master.h"
55 #include "mcd-channel-priv.h"
56 #include "mcd-dispatcher-priv.h"
57 #include "mcd-dispatch-operation-priv.h"
58 #include "mcd-handler-map-priv.h"
59 #include "mcd-misc.h"
60 #include "plugin-loader.h"
61
62 #include "_gen/svc-dispatcher.h"
63
64 #include <telepathy-glib/telepathy-glib.h>
65 #include <telepathy-glib/telepathy-glib-dbus.h>
66
67 #include <telepathy-glib/proxy-subclass.h>
68
69 #include <stdlib.h>
70 #include <string.h>
71 #include "sp_timestamp.h"
72
73 #define CREATE_CHANNEL TP_IFACE_CONNECTION_INTERFACE_REQUESTS ".CreateChannel"
74 #define ENSURE_CHANNEL TP_IFACE_CONNECTION_INTERFACE_REQUESTS ".EnsureChannel"
75 #define SEND_MESSAGE \
76 TP_IFACE_CHANNEL_DISPATCHER ".Interface.Messages.DRAFT.SendMessage"
77
78 #define MCD_DISPATCHER_PRIV(dispatcher) (MCD_DISPATCHER (dispatcher)->priv)
79
80 static void dispatcher_iface_init (gpointer, gpointer);
81 static void messages_iface_init (gpointer, gpointer);
82
83
84 G_DEFINE_TYPE_WITH_CODE (McdDispatcher, mcd_dispatcher, G_TYPE_OBJECT,
85 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_DISPATCHER,
86 dispatcher_iface_init);
87 G_IMPLEMENT_INTERFACE (MC_TYPE_SVC_CHANNEL_DISPATCHER_INTERFACE_MESSAGES_DRAFT,
88 messages_iface_init);
89 G_IMPLEMENT_INTERFACE (
90 TP_TYPE_SVC_CHANNEL_DISPATCHER_INTERFACE_OPERATION_LIST,
91 NULL);
92 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
93 tp_dbus_properties_mixin_iface_init))
94
95 typedef struct
96 {
97 McdDispatcher *dispatcher;
98 McdChannel *channel;
99 gint handler_locks;
100 gboolean handled;
101 } McdChannelRecover;
102
103 typedef struct
104 {
105 McdDispatcher *dispatcher;
106 gchar *account_path;
107 GHashTable *properties;
108 gint64 user_action_time;
109 gchar *preferred_handler;
110 GHashTable *request_metadata;
111 gboolean ensure;
112 } McdChannelRequestACL;
113
114 struct _McdDispatcherPrivate
115 {
116 /* Dispatching contexts */
117 GList *contexts;
118
119 /* Channel dispatch operations */
120 GList *operations;
121
122 TpDBusDaemon *dbus_daemon;
123
124 /* hash table containing clients
125 * char *bus_name -> McdClientProxy */
126 McdClientRegistry *clients;
127
128 McdHandlerMap *handler_map;
129
130 McdMaster *master;
131
132 /* connection => itself, borrowed */
133 GHashTable *connections;
134
135 /* Initially FALSE, meaning we suppress OperationList.DispatchOperations
136 * change notification signals because nobody has retrieved that property
137 * yet. Set to TRUE the first time someone reads the DispatchOperations
138 * property. */
139 gboolean operation_list_active;
140
141 gboolean is_disposed;
142 };
143
144 struct cancel_call_data
145 {
146 DBusGProxy *handler_proxy;
147 DBusGProxyCall *call;
148 McdDispatcher *dispatcher;
149 };
150
151 enum
152 {
153 PROP_0,
154 PROP_DBUS_DAEMON,
155 PROP_MCD_MASTER,
156 PROP_INTERFACES,
157 PROP_SUPPORTS_REQUEST_HINTS,
158 PROP_DISPATCH_OPERATIONS,
159 };
160
161 static void on_operation_finished (McdDispatchOperation *operation,
162 McdDispatcher *self);
163
164 static void
on_master_abort(McdMaster * master,McdDispatcherPrivate * priv)165 on_master_abort (McdMaster *master, McdDispatcherPrivate *priv)
166 {
167 tp_clear_object (&priv->master);
168 }
169
170 static GStrv
mcd_dispatcher_dup_internal_handlers(void)171 mcd_dispatcher_dup_internal_handlers (void)
172 {
173 const gchar * const internal_handlers[] = { CDO_INTERNAL_HANDLER, NULL };
174
175 return g_strdupv ((GStrv) internal_handlers);
176 }
177
178 static GStrv
mcd_dispatcher_dup_possible_handlers(McdDispatcher * self,McdRequest * request,TpChannel * channel,const gchar * must_have_unique_name)179 mcd_dispatcher_dup_possible_handlers (McdDispatcher *self,
180 McdRequest *request,
181 TpChannel *channel,
182 const gchar *must_have_unique_name)
183 {
184 GList *handlers;
185 guint n_handlers;
186 guint i;
187 GStrv ret;
188 const GList *iter;
189 GVariant *request_properties = NULL;
190
191 if (request != NULL)
192 request_properties = mcd_request_dup_properties (request);
193
194 handlers = _mcd_client_registry_list_possible_handlers (
195 self->priv->clients,
196 request != NULL ? _mcd_request_get_preferred_handler (request) : NULL,
197 request_properties,
198 channel, must_have_unique_name);
199 n_handlers = g_list_length (handlers);
200
201 tp_clear_pointer (&request_properties, g_variant_unref);
202
203 if (handlers == NULL)
204 return NULL;
205
206 ret = g_new0 (gchar *, n_handlers + 1);
207
208 for (iter = handlers, i = 0; iter != NULL; iter = iter->next, i++)
209 {
210 ret[i] = g_strdup (tp_proxy_get_bus_name (iter->data));
211 }
212
213 ret[n_handlers] = NULL;
214
215 g_list_free (handlers);
216
217 return ret;
218 }
219
220 static void
on_operation_finished(McdDispatchOperation * operation,McdDispatcher * self)221 on_operation_finished (McdDispatchOperation *operation,
222 McdDispatcher *self)
223 {
224 GList *its_link;
225
226 g_signal_handlers_disconnect_by_func (operation,
227 on_operation_finished,
228 self);
229
230 /* don't emit the signal if the CDO never appeared on D-Bus */
231 if (self->priv->operation_list_active &&
232 _mcd_dispatch_operation_needs_approval (operation))
233 {
234 tp_svc_channel_dispatcher_interface_operation_list_emit_dispatch_operation_finished (
235 self, _mcd_dispatch_operation_get_path (operation));
236 }
237
238 its_link = g_list_find (self->priv->operations, operation);
239
240 if (its_link != NULL)
241 {
242 self->priv->operations = g_list_delete_link (self->priv->operations,
243 its_link);
244 g_object_unref (operation);
245 }
246 }
247
248 static void
_mcd_dispatcher_enter_state_machine(McdDispatcher * dispatcher,McdChannel * channel,const gchar * const * possible_handlers,gboolean requested,gboolean only_observe)249 _mcd_dispatcher_enter_state_machine (McdDispatcher *dispatcher,
250 McdChannel *channel,
251 const gchar * const *possible_handlers,
252 gboolean requested,
253 gboolean only_observe)
254 {
255 McdDispatchOperation *operation;
256 McdDispatcherPrivate *priv;
257 McdAccount *account;
258
259 g_return_if_fail (MCD_IS_DISPATCHER (dispatcher));
260 g_return_if_fail (MCD_IS_CHANNEL (channel));
261 g_return_if_fail (requested || !only_observe);
262
263 account = mcd_channel_get_account (channel);
264 if (G_UNLIKELY (!account))
265 {
266 g_warning ("%s called with no account", G_STRFUNC);
267 return;
268 }
269
270 priv = dispatcher->priv;
271
272 DEBUG ("new dispatch operation for %s channel %p: %s",
273 requested ? "requested" : "unrequested",
274 channel,
275 mcd_channel_get_object_path (channel));
276
277 operation = _mcd_dispatch_operation_new (priv->clients,
278 priv->handler_map, !requested, only_observe, channel,
279 (const gchar * const *) possible_handlers);
280
281 if (!requested)
282 {
283 if (priv->operation_list_active)
284 {
285 tp_svc_channel_dispatcher_interface_operation_list_emit_new_dispatch_operation (
286 dispatcher,
287 _mcd_dispatch_operation_get_path (operation),
288 _mcd_dispatch_operation_get_properties (operation));
289 }
290
291 priv->operations = g_list_prepend (priv->operations,
292 g_object_ref (operation));
293
294 g_signal_connect (operation, "finished",
295 G_CALLBACK (on_operation_finished), dispatcher);
296 }
297
298 if (_mcd_dispatch_operation_get_cancelled (operation))
299 {
300 GError error = { TP_ERROR, TP_ERROR_CANCELLED,
301 "Channel request cancelled" };
302 McdChannel *cancelled;
303
304 cancelled = _mcd_dispatch_operation_dup_channel (operation);
305
306 if (cancelled != NULL)
307 {
308 if (mcd_channel_get_error (cancelled) == NULL)
309 mcd_channel_take_error (cancelled, g_error_copy (&error));
310
311 _mcd_channel_undispatchable (cancelled);
312
313 g_object_unref (cancelled);
314 }
315 }
316 else if (_mcd_dispatch_operation_peek_channel (operation) == NULL)
317 {
318 DEBUG ("No channels left");
319 }
320 else
321 {
322 _mcd_dispatch_operation_run_clients (operation);
323 }
324
325 g_object_unref (operation);
326 }
327
328 static void
_mcd_dispatcher_set_property(GObject * obj,guint prop_id,const GValue * val,GParamSpec * pspec)329 _mcd_dispatcher_set_property (GObject * obj, guint prop_id,
330 const GValue * val, GParamSpec * pspec)
331 {
332 McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (obj);
333 McdMaster *master;
334
335 switch (prop_id)
336 {
337 case PROP_DBUS_DAEMON:
338 tp_clear_object (&priv->dbus_daemon);
339 priv->dbus_daemon = TP_DBUS_DAEMON (g_value_dup_object (val));
340 break;
341 case PROP_MCD_MASTER:
342 master = g_value_get_object (val);
343 g_object_ref (G_OBJECT (master));
344 if (priv->master)
345 {
346 g_signal_handlers_disconnect_by_func (G_OBJECT (master), G_CALLBACK (on_master_abort), NULL);
347 g_object_unref (priv->master);
348 }
349 priv->master = master;
350 g_signal_connect (G_OBJECT (master), "abort", G_CALLBACK (on_master_abort), priv);
351 break;
352 default:
353 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
354 break;
355 }
356 }
357
358 static const char * const interfaces[] = {
359 TP_IFACE_CHANNEL_DISPATCHER_INTERFACE_OPERATION_LIST,
360 NULL
361 };
362
363 static void
_mcd_dispatcher_get_property(GObject * obj,guint prop_id,GValue * val,GParamSpec * pspec)364 _mcd_dispatcher_get_property (GObject * obj, guint prop_id,
365 GValue * val, GParamSpec * pspec)
366 {
367 McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (obj);
368
369 switch (prop_id)
370 {
371 case PROP_DBUS_DAEMON:
372 g_value_set_object (val, priv->dbus_daemon);
373 break;
374
375 case PROP_MCD_MASTER:
376 g_value_set_object (val, priv->master);
377 break;
378
379 case PROP_INTERFACES:
380 g_value_set_static_boxed (val, interfaces);
381 break;
382
383 case PROP_SUPPORTS_REQUEST_HINTS:
384 g_value_set_boolean (val, TRUE);
385 break;
386
387 case PROP_DISPATCH_OPERATIONS:
388 {
389 GList *iter;
390 GPtrArray *operations = g_ptr_array_new ();
391
392 /* Side-effect: from now on, emit change notification signals for
393 * this property */
394 priv->operation_list_active = TRUE;
395
396 for (iter = priv->operations; iter != NULL; iter = iter->next)
397 {
398 McdDispatchOperation *op = iter->data;
399
400 if (_mcd_dispatch_operation_needs_approval (op) &&
401 !_mcd_dispatch_operation_is_finished (op))
402 {
403 GValueArray *va = g_value_array_new (2);
404
405 g_value_array_append (va, NULL);
406 g_value_array_append (va, NULL);
407
408 g_value_init (va->values + 0, DBUS_TYPE_G_OBJECT_PATH);
409 g_value_init (va->values + 1,
410 TP_HASH_TYPE_STRING_VARIANT_MAP);
411
412 g_value_set_boxed (va->values + 0,
413 _mcd_dispatch_operation_get_path (op));
414 g_value_set_boxed (va->values + 1,
415 _mcd_dispatch_operation_get_properties (op));
416
417 g_ptr_array_add (operations, va);
418 }
419 }
420
421 g_value_take_boxed (val, operations);
422 }
423 break;
424
425 default:
426 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
427 break;
428 }
429 }
430
431 static void
mcd_dispatcher_client_handling_channel_cb(McdClientProxy * client,const gchar * object_path,McdDispatcher * self)432 mcd_dispatcher_client_handling_channel_cb (McdClientProxy *client,
433 const gchar *object_path,
434 McdDispatcher *self)
435 {
436 const gchar *bus_name = tp_proxy_get_bus_name (client);
437 const gchar *unique_name = _mcd_client_proxy_get_unique_name (client);
438
439 if (unique_name == NULL || unique_name[0] == '\0')
440 {
441 /* if it said it was handling channels but it doesn't seem to exist
442 * (or worse, doesn't know whether it exists) then we don't believe
443 * it */
444 DEBUG ("%s doesn't seem to exist, assuming it's not handling %s",
445 bus_name, object_path);
446 return;
447 }
448
449 DEBUG ("%s (%s) is handling %s", bus_name, unique_name,
450 object_path);
451
452 _mcd_handler_map_set_path_handled (self->priv->handler_map,
453 object_path, unique_name, bus_name);
454 }
455
456 static void mcd_dispatcher_update_client_caps (McdDispatcher *self,
457 McdClientProxy *client);
458
459 static void
mcd_dispatcher_client_capabilities_changed_cb(McdClientProxy * client,McdDispatcher * self)460 mcd_dispatcher_client_capabilities_changed_cb (McdClientProxy *client,
461 McdDispatcher *self)
462 {
463 mcd_dispatcher_update_client_caps (self, client);
464 }
465
466 static void mcd_dispatcher_client_gone_cb (McdClientProxy *client,
467 McdDispatcher *self);
468
469 static void mcd_dispatcher_client_needs_recovery_cb (McdClientProxy *client,
470 McdDispatcher *self);
471
472 static void
mcd_dispatcher_discard_client(McdDispatcher * self,McdClientProxy * client)473 mcd_dispatcher_discard_client (McdDispatcher *self,
474 McdClientProxy *client)
475 {
476 g_signal_handlers_disconnect_by_func (client,
477 mcd_dispatcher_client_capabilities_changed_cb, self);
478
479 g_signal_handlers_disconnect_by_func (client,
480 mcd_dispatcher_client_handling_channel_cb, self);
481
482 g_signal_handlers_disconnect_by_func (client,
483 mcd_dispatcher_client_gone_cb,
484 self);
485
486 g_signal_handlers_disconnect_by_func (client,
487 mcd_dispatcher_client_needs_recovery_cb, self);
488 }
489
490 static void
mcd_dispatcher_client_gone_cb(McdClientProxy * client,McdDispatcher * self)491 mcd_dispatcher_client_gone_cb (McdClientProxy *client,
492 McdDispatcher *self)
493 {
494 mcd_dispatcher_discard_client (self, client);
495 }
496
497 /*
498 * @channel: a non-null TpChannel which has already been dispatched
499 * @request: (allow-none): if not NULL, a request that resulted in
500 * @channel
501 *
502 * Return a Handler to which @channel could be re-dispatched,
503 * for instance as a result of a re-request or a PresentChannel call.
504 *
505 * If @channel was dispatched to a Handler, return that Handler.
506 * Otherwise, if it was Claimed by a process, and that process
507 * has a Handler to which the channel could have been dispatched,
508 * return that Handler. Otherwise return NULL.
509 */
510 static McdClientProxy *
_mcd_dispatcher_lookup_handler(McdDispatcher * self,TpChannel * channel,McdRequest * request)511 _mcd_dispatcher_lookup_handler (McdDispatcher *self,
512 TpChannel *channel,
513 McdRequest *request)
514 {
515 McdClientProxy *handler = NULL;
516 const gchar *object_path;
517 const gchar *unique_name;
518 const gchar *well_known_name;
519
520 object_path = tp_proxy_get_object_path (channel);
521
522 unique_name = _mcd_handler_map_get_handler (self->priv->handler_map,
523 object_path,
524 &well_known_name);
525
526 if (unique_name == NULL)
527 {
528 DEBUG ("No process is handling channel %s", object_path);
529 return NULL;
530 }
531
532 if (well_known_name != NULL)
533 {
534 /* We know which Handler well-known name was responsible: use it if it
535 * still exists */
536 DEBUG ("Channel %s is handler by %s", object_path, well_known_name);
537 handler = _mcd_client_registry_lookup (self->priv->clients,
538 well_known_name);
539 }
540
541 if (handler == NULL)
542 {
543 GList *possible_handlers;
544 GVariant *request_properties = NULL;
545
546 /* Failing that, maybe the Handler it was dispatched to was temporary;
547 * try to pick another Handler that can deal with it, on the same
548 * unique name (i.e. in the same process).
549 * It can also happen in the case an Observer/Approver Claimed the
550 * channel; in that case we did not get its handler well known name.
551 */
552 if (request != NULL)
553 request_properties = mcd_request_dup_properties (request);
554
555 possible_handlers = _mcd_client_registry_list_possible_handlers (
556 self->priv->clients,
557 request != NULL ? _mcd_request_get_preferred_handler (request) : NULL,
558 request_properties, channel, unique_name);
559 tp_clear_pointer (&request_properties, g_variant_unref);
560
561 if (possible_handlers != NULL)
562 {
563 DEBUG ("Pick first possible handler for channel %s", object_path);
564 handler = possible_handlers->data;
565 }
566 else
567 {
568 /* The process is still running (otherwise it wouldn't be in the
569 * handler map), but none of its well-known names is still
570 * interested in channels of that sort. Oh well, not our problem.
571 */
572 DEBUG ("process %s no longer interested in channel %s",
573 unique_name, object_path);
574 }
575
576 g_list_free (possible_handlers);
577 }
578
579 return handler;
580 }
581
582 static void
mcd_dispatcher_client_needs_recovery_cb(McdClientProxy * client,McdDispatcher * self)583 mcd_dispatcher_client_needs_recovery_cb (McdClientProxy *client,
584 McdDispatcher *self)
585 {
586 const GList *channels =
587 _mcd_handler_map_get_handled_channels (self->priv->handler_map);
588 const GList *observer_filters;
589 const GList *list;
590
591 DEBUG ("called");
592
593 observer_filters = _mcd_client_proxy_get_observer_filters (client);
594
595 for (list = channels; list; list = list->next)
596 {
597 TpChannel *channel = list->data;
598 const gchar *object_path = tp_proxy_get_object_path (channel);
599 GVariant *properties;
600 McdClientProxy *handler;
601
602 /* FIXME: This is not exactly the right behaviour, see fd.o#40305 */
603 handler = _mcd_dispatcher_lookup_handler (self, channel, NULL);
604 if (handler && _mcd_client_proxy_get_bypass_observers (handler))
605 {
606 DEBUG ("skipping unobservable channel %s", object_path);
607 continue;
608 }
609
610 properties = tp_channel_dup_immutable_properties (channel);
611
612 if (_mcd_client_match_filters (properties, observer_filters,
613 FALSE))
614 {
615 const gchar *account_path =
616 _mcd_handler_map_get_channel_account (self->priv->handler_map,
617 tp_proxy_get_object_path (channel));
618
619 _mcd_client_recover_observer (client, channel, account_path);
620 }
621
622 g_variant_unref (properties);
623 }
624
625 /* we also need to think about channels that are still being dispatched,
626 * but have got far enough that this client wouldn't otherwise see them */
627 for (list = self->priv->operations; list != NULL; list = list->next)
628 {
629 McdDispatchOperation *op = list->data;
630
631 if (_mcd_dispatch_operation_has_invoked_observers (op))
632 {
633 McdChannel *mcd_channel =
634 _mcd_dispatch_operation_peek_channel (op);
635
636 if (mcd_channel != NULL)
637 {
638 GVariant *properties =
639 mcd_channel_dup_immutable_properties (mcd_channel);
640
641 if (_mcd_client_match_filters (properties, observer_filters,
642 FALSE))
643 {
644 _mcd_client_recover_observer (client,
645 mcd_channel_get_tp_channel (mcd_channel),
646 _mcd_dispatch_operation_get_account_path (op));
647 }
648
649 g_variant_unref (properties);
650 }
651 }
652 }
653 }
654
655 static void
mcd_dispatcher_client_added_cb(McdClientRegistry * clients,McdClientProxy * client,McdDispatcher * self)656 mcd_dispatcher_client_added_cb (McdClientRegistry *clients,
657 McdClientProxy *client,
658 McdDispatcher *self)
659 {
660 g_signal_connect (client, "gone",
661 G_CALLBACK (mcd_dispatcher_client_gone_cb),
662 self);
663
664 g_signal_connect (client, "is-handling-channel",
665 G_CALLBACK (mcd_dispatcher_client_handling_channel_cb),
666 self);
667
668 g_signal_connect (client, "handler-capabilities-changed",
669 G_CALLBACK (mcd_dispatcher_client_capabilities_changed_cb),
670 self);
671
672 g_signal_connect (client, "need-recovery",
673 G_CALLBACK (mcd_dispatcher_client_needs_recovery_cb),
674 self);
675
676 }
677
678 static void
mcd_dispatcher_client_registry_ready_cb(McdClientRegistry * clients,McdDispatcher * self)679 mcd_dispatcher_client_registry_ready_cb (McdClientRegistry *clients,
680 McdDispatcher *self)
681 {
682 GHashTableIter iter;
683 gpointer p;
684 GPtrArray *vas;
685
686 DEBUG ("All initial clients have been inspected");
687
688 vas = _mcd_client_registry_dup_client_caps (clients);
689
690 g_hash_table_iter_init (&iter, self->priv->connections);
691
692 while (g_hash_table_iter_next (&iter, &p, NULL))
693 {
694 _mcd_connection_start_dispatching (p, vas);
695 }
696
697 g_ptr_array_foreach (vas, (GFunc) g_value_array_free, NULL);
698 g_ptr_array_unref (vas);
699 }
700
701 static void
drop_each_operation(gpointer operation,gpointer dispatcher)702 drop_each_operation (gpointer operation,
703 gpointer dispatcher)
704 {
705 g_signal_handlers_disconnect_by_func (operation,
706 on_operation_finished,
707 dispatcher);
708 g_object_unref (operation);
709 }
710
711 static void
_mcd_dispatcher_dispose(GObject * object)712 _mcd_dispatcher_dispose (GObject * object)
713 {
714 McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (object);
715
716 if (priv->is_disposed)
717 {
718 return;
719 }
720 priv->is_disposed = TRUE;
721
722 if (priv->operations != NULL)
723 {
724 g_list_foreach (priv->operations, drop_each_operation, object);
725 tp_clear_pointer (&priv->operations, g_list_free);
726 }
727
728 tp_clear_object (&priv->handler_map);
729
730 if (priv->clients != NULL)
731 {
732 gpointer client_p;
733 GHashTableIter iter;
734
735 _mcd_client_registry_init_hash_iter (priv->clients, &iter);
736
737 while (g_hash_table_iter_next (&iter, NULL, &client_p))
738 {
739 mcd_dispatcher_discard_client ((McdDispatcher *) object, client_p);
740 }
741
742 g_signal_handlers_disconnect_by_func (priv->clients,
743 mcd_dispatcher_client_added_cb,
744 object);
745
746 g_signal_handlers_disconnect_by_func (priv->clients,
747 mcd_dispatcher_client_registry_ready_cb, object);
748
749 tp_clear_object (&priv->clients);
750 }
751
752 tp_clear_pointer (&priv->connections, g_hash_table_unref);
753 tp_clear_object (&priv->master);
754 tp_clear_object (&priv->dbus_daemon);
755
756 G_OBJECT_CLASS (mcd_dispatcher_parent_class)->dispose (object);
757 }
758
759 static void
mcd_dispatcher_update_client_caps(McdDispatcher * self,McdClientProxy * client)760 mcd_dispatcher_update_client_caps (McdDispatcher *self,
761 McdClientProxy *client)
762 {
763 GPtrArray *vas;
764 GHashTableIter iter;
765 gpointer k;
766
767 /* If we haven't finished inspecting initial clients yet, we'll push all
768 * the client caps into all connections when we do, so do nothing.
769 *
770 * If we don't have any connections, on the other hand, then there's
771 * nothing to do. */
772 if (!_mcd_client_registry_is_ready (self->priv->clients)
773 || g_hash_table_size (self->priv->connections) == 0)
774 {
775 return;
776 }
777
778 vas = g_ptr_array_sized_new (1);
779 g_ptr_array_add (vas, _mcd_client_proxy_dup_handler_capabilities (client));
780
781 g_hash_table_iter_init (&iter, self->priv->connections);
782
783 while (g_hash_table_iter_next (&iter, &k, NULL))
784 {
785 _mcd_connection_update_client_caps (k, vas);
786 }
787
788 g_ptr_array_foreach (vas, (GFunc) g_value_array_free, NULL);
789 g_ptr_array_unref (vas);
790 }
791
792 static void
mcd_dispatcher_constructed(GObject * object)793 mcd_dispatcher_constructed (GObject *object)
794 {
795 DBusGConnection *dgc;
796 McdDispatcherPrivate *priv = MCD_DISPATCHER_PRIV (object);
797 GError *error = NULL;
798
799 priv->handler_map = _mcd_handler_map_new (priv->dbus_daemon);
800
801 priv->clients = _mcd_client_registry_new (priv->dbus_daemon);
802 g_signal_connect (priv->clients, "client-added",
803 G_CALLBACK (mcd_dispatcher_client_added_cb), object);
804 g_signal_connect (priv->clients, "ready",
805 G_CALLBACK (mcd_dispatcher_client_registry_ready_cb),
806 object);
807
808 dgc = tp_proxy_get_dbus_connection (TP_PROXY (priv->dbus_daemon));
809
810 if (!tp_dbus_daemon_request_name (priv->dbus_daemon,
811 TP_CHANNEL_DISPATCHER_BUS_NAME,
812 TRUE /* idempotent */, &error))
813 {
814 /* FIXME: put in proper error handling when MC gains the ability to
815 * be the AM or the CD but not both */
816 g_warning ("Failed registering '%s' service: %s",
817 TP_CHANNEL_DISPATCHER_BUS_NAME, error->message);
818 g_error_free (error);
819 exit (1);
820 }
821
822 dbus_g_connection_register_g_object (dgc,
823 TP_CHANNEL_DISPATCHER_OBJECT_PATH,
824 object);
825 }
826
827 static void
mcd_dispatcher_class_init(McdDispatcherClass * klass)828 mcd_dispatcher_class_init (McdDispatcherClass * klass)
829 {
830 static TpDBusPropertiesMixinPropImpl cd_props[] = {
831 { "Interfaces", "interfaces", NULL },
832 { "SupportsRequestHints", "supports-request-hints", NULL },
833 { NULL }
834 };
835 static TpDBusPropertiesMixinPropImpl op_list_props[] = {
836 { "DispatchOperations", "dispatch-operations", NULL },
837 { NULL }
838 };
839 static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
840 { TP_IFACE_CHANNEL_DISPATCHER,
841 tp_dbus_properties_mixin_getter_gobject_properties,
842 NULL,
843 cd_props,
844 },
845 { TP_IFACE_CHANNEL_DISPATCHER_INTERFACE_OPERATION_LIST,
846 tp_dbus_properties_mixin_getter_gobject_properties,
847 NULL,
848 op_list_props,
849 },
850 { NULL }
851 };
852 GObjectClass *object_class = G_OBJECT_CLASS (klass);
853
854 g_type_class_add_private (object_class, sizeof (McdDispatcherPrivate));
855
856 object_class->constructed = mcd_dispatcher_constructed;
857 object_class->set_property = _mcd_dispatcher_set_property;
858 object_class->get_property = _mcd_dispatcher_get_property;
859 object_class->dispose = _mcd_dispatcher_dispose;
860
861 /* Properties */
862 g_object_class_install_property
863 (object_class, PROP_DBUS_DAEMON,
864 g_param_spec_object ("dbus-daemon", "DBus daemon", "DBus daemon",
865 TP_TYPE_DBUS_DAEMON,
866 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
867 g_object_class_install_property
868 (object_class, PROP_MCD_MASTER,
869 g_param_spec_object ("mcd-master", "McdMaster", "McdMaster",
870 MCD_TYPE_MASTER,
871 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
872
873 g_object_class_install_property
874 (object_class, PROP_INTERFACES,
875 g_param_spec_boxed ("interfaces", "Interfaces", "Interfaces",
876 G_TYPE_STRV,
877 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
878
879 g_object_class_install_property (
880 object_class, PROP_SUPPORTS_REQUEST_HINTS,
881 g_param_spec_boolean ("supports-request-hints", "SupportsRequestHints",
882 "Yes, we support CreateChannelWithHints etc.",
883 TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
884
885 g_object_class_install_property
886 (object_class, PROP_DISPATCH_OPERATIONS,
887 g_param_spec_boxed ("dispatch-operations",
888 "ChannelDispatchOperation details",
889 "A dbus-glib a(oa{sv})",
890 TP_ARRAY_TYPE_DISPATCH_OPERATION_DETAILS_LIST,
891 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
892
893 klass->dbus_properties_class.interfaces = prop_interfaces,
894 tp_dbus_properties_mixin_class_init (object_class,
895 G_STRUCT_OFFSET (McdDispatcherClass, dbus_properties_class));
896 }
897
898 static void
mcd_dispatcher_init(McdDispatcher * dispatcher)899 mcd_dispatcher_init (McdDispatcher * dispatcher)
900 {
901 McdDispatcherPrivate *priv;
902
903 priv = G_TYPE_INSTANCE_GET_PRIVATE (dispatcher, MCD_TYPE_DISPATCHER,
904 McdDispatcherPrivate);
905 dispatcher->priv = priv;
906
907 priv->operation_list_active = FALSE;
908
909 priv->connections = g_hash_table_new (NULL, NULL);
910
911 /* idempotent, not guaranteed to have been called yet */
912 _mcd_plugin_loader_init ();
913 }
914
915 McdDispatcher *
mcd_dispatcher_new(TpDBusDaemon * dbus_daemon,McdMaster * master)916 mcd_dispatcher_new (TpDBusDaemon *dbus_daemon, McdMaster *master)
917 {
918 McdDispatcher *obj;
919 obj = MCD_DISPATCHER (g_object_new (MCD_TYPE_DISPATCHER,
920 "dbus-daemon", dbus_daemon,
921 "mcd-master", master,
922 NULL));
923 return obj;
924 }
925
926 /*
927 * _mcd_dispatcher_add_channel:
928 * @dispatcher: the #McdDispatcher.
929 * @channel: (transfer none): a #McdChannel which must own a #TpChannel
930 * @requested: whether the channels were requested by MC.
931 *
932 * Add @channel to the dispatching state machine.
933 */
934 void
_mcd_dispatcher_add_channel(McdDispatcher * dispatcher,McdChannel * channel,gboolean requested,gboolean only_observe)935 _mcd_dispatcher_add_channel (McdDispatcher *dispatcher,
936 McdChannel *channel,
937 gboolean requested,
938 gboolean only_observe)
939 {
940 TpChannel *tp_channel = NULL;
941 GStrv possible_handlers;
942 McdRequest *request = NULL;
943 gboolean internal_request = FALSE;
944
945 g_return_if_fail (MCD_IS_DISPATCHER (dispatcher));
946 g_return_if_fail (MCD_IS_CHANNEL (channel));
947
948 DEBUG ("%s channel %p: %s",
949 requested ? "requested" : "unrequested",
950 channel,
951 mcd_channel_get_object_path (channel));
952
953 if (only_observe)
954 {
955 g_return_if_fail (requested);
956
957 /* these channels were requested "behind our back", so only call
958 * ObserveChannels on them */
959 _mcd_dispatcher_enter_state_machine (dispatcher, channel, NULL,
960 TRUE, TRUE);
961 return;
962 }
963
964 /* The channel must have the TpChannel part of McdChannel's double life.
965 * It might also have the McdRequest part. */
966 tp_channel = mcd_channel_get_tp_channel (channel);
967 g_assert (tp_channel != NULL);
968
969 request = _mcd_channel_get_request (channel);
970 internal_request = _mcd_request_is_internal (request);
971
972 /* See if there are any handlers that can take all these channels */
973 if (internal_request)
974 possible_handlers = mcd_dispatcher_dup_internal_handlers ();
975 else
976 possible_handlers = mcd_dispatcher_dup_possible_handlers (dispatcher,
977 request,
978 tp_channel,
979 NULL);
980
981 if (possible_handlers == NULL)
982 {
983 DEBUG ("Channel cannot be handled - making a CDO "
984 "anyway, to get Observers run");
985 }
986 else
987 {
988 DEBUG ("%s handler(s) found, dispatching channel",
989 internal_request ? "internal" : "possible");
990 }
991
992 _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHING);
993
994 _mcd_dispatcher_enter_state_machine (dispatcher, channel,
995 (const gchar * const *) possible_handlers, requested, FALSE);
996
997 g_strfreev (possible_handlers);
998 }
999
1000 static void
mcd_dispatcher_finish_reinvocation(McdChannel * request)1001 mcd_dispatcher_finish_reinvocation (McdChannel *request)
1002 {
1003 _mcd_channel_set_status (request, MCD_CHANNEL_STATUS_DISPATCHED);
1004 /* no need to keep it around any more */
1005 mcd_mission_abort (MCD_MISSION (request));
1006 }
1007
1008 static void
reinvoke_handle_channels_cb(TpClient * client,const GError * error,gpointer user_data G_GNUC_UNUSED,GObject * weak_object)1009 reinvoke_handle_channels_cb (TpClient *client,
1010 const GError *error,
1011 gpointer user_data G_GNUC_UNUSED,
1012 GObject *weak_object)
1013 {
1014 McdChannel *request = MCD_CHANNEL (weak_object);
1015
1016 if (error != NULL)
1017 {
1018 /* The handler is already dealing with this channel, but refuses to
1019 * re-handle it. Oh well, we tried. */
1020 DEBUG ("handler %s refused re-notification about channel %p:%s: "
1021 "%s:%d: %s", tp_proxy_get_bus_name (client),
1022 request, mcd_channel_get_object_path (request),
1023 g_quark_to_string (error->domain), error->code, error->message);
1024 }
1025 else
1026 {
1027 DEBUG ("handler %s successfully notified about channel %p:%s",
1028 tp_proxy_get_bus_name (client),
1029 request, mcd_channel_get_object_path (request));
1030 }
1031
1032 /* either way, consider the request to have been dispatched */
1033 mcd_dispatcher_finish_reinvocation (request);
1034 }
1035
1036 /*
1037 * _mcd_dispatcher_reinvoke_handler:
1038 * @dispatcher: The #McdDispatcher.
1039 * @request: a #McdChannel that has both a #TpChannel and a #McdRequest
1040 *
1041 * Re-invoke the channel handler for @request.
1042 */
1043 static void
_mcd_dispatcher_reinvoke_handler(McdDispatcher * dispatcher,McdChannel * request)1044 _mcd_dispatcher_reinvoke_handler (McdDispatcher *dispatcher,
1045 McdChannel *request)
1046 {
1047 GList *request_as_list;
1048 McdClientProxy *handler = NULL;
1049 McdRequest *real_request = _mcd_channel_get_request (request);
1050 TpChannel *tp_channel = mcd_channel_get_tp_channel (request);
1051 GHashTable *handler_info;
1052 GHashTable *request_properties;
1053
1054 g_assert (real_request != NULL);
1055 g_assert (tp_channel != NULL);
1056
1057 request_as_list = g_list_append (NULL, request);
1058
1059 request_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
1060 g_free, (GDestroyNotify) g_hash_table_unref);
1061 g_hash_table_insert (request_properties,
1062 g_strdup (_mcd_request_get_object_path (real_request)),
1063 _mcd_request_dup_immutable_properties (real_request));
1064
1065 handler_info = tp_asv_new (NULL, NULL);
1066 /* hand over ownership of request_properties */
1067 tp_asv_take_boxed (handler_info, "request-properties",
1068 TP_HASH_TYPE_OBJECT_IMMUTABLE_PROPERTIES_MAP,
1069 request_properties);
1070 request_properties = NULL;
1071
1072 handler = _mcd_dispatcher_lookup_handler (dispatcher,
1073 tp_channel, real_request);
1074 if (handler == NULL)
1075 {
1076 mcd_dispatcher_finish_reinvocation (request);
1077 goto finally;
1078 }
1079
1080 /* This is deliberately not the same call as for normal dispatching,
1081 * and it doesn't go through a dispatch operation - the error handling
1082 * is completely different, because the channel is already being
1083 * handled perfectly well. */
1084
1085 _mcd_client_proxy_handle_channels (handler,
1086 -1, request_as_list,
1087 0, /* the request's user action time will be used automatically */
1088 handler_info,
1089 reinvoke_handle_channels_cb, NULL, NULL, (GObject *) request);
1090
1091 finally:
1092 g_hash_table_unref (handler_info);
1093 g_list_free (request_as_list);
1094 }
1095
1096 static McdDispatchOperation *
find_operation_from_channel(McdDispatcher * dispatcher,McdChannel * channel)1097 find_operation_from_channel (McdDispatcher *dispatcher,
1098 McdChannel *channel)
1099 {
1100 GList *list;
1101
1102 g_return_val_if_fail (MCD_IS_CHANNEL (channel), NULL);
1103 for (list = dispatcher->priv->operations; list != NULL; list = list->next)
1104 {
1105 McdDispatchOperation *op = list->data;
1106
1107 if (_mcd_dispatch_operation_has_channel (op, channel))
1108 return op;
1109 }
1110 return NULL;
1111 }
1112
1113 void
_mcd_dispatcher_add_channel_request(McdDispatcher * dispatcher,McdChannel * channel,McdChannel * request)1114 _mcd_dispatcher_add_channel_request (McdDispatcher *dispatcher,
1115 McdChannel *channel, McdChannel *request)
1116 {
1117 McdChannelStatus status;
1118 McdRequest *origin = _mcd_channel_get_request (request);
1119 gboolean internal = _mcd_request_is_internal (origin);
1120
1121 status = mcd_channel_get_status (channel);
1122
1123 /* if the channel is already dispatched, just reinvoke the handler; if it
1124 * is not, @request must mirror the status of @channel */
1125 if (status == MCD_CHANNEL_STATUS_DISPATCHED)
1126 {
1127 DEBUG ("reinvoking handler on channel %p", request);
1128
1129 /* copy the object path and the immutable properties from the
1130 * existing channel */
1131 _mcd_channel_copy_details (request, channel);
1132
1133 if (internal)
1134 _mcd_request_handle_internally (origin, request, FALSE);
1135 else
1136 _mcd_dispatcher_reinvoke_handler (dispatcher, request);
1137 }
1138 else
1139 {
1140 DEBUG ("non-reinvoked handling of channel %p", request);
1141 _mcd_channel_set_request_proxy (request, channel);
1142
1143 if (internal)
1144 {
1145 _mcd_request_handle_internally (origin, request, FALSE);
1146 }
1147 else if (status == MCD_CHANNEL_STATUS_DISPATCHING)
1148 {
1149 McdDispatchOperation *op = find_operation_from_channel (dispatcher,
1150 channel);
1151 const gchar *preferred_handler =
1152 _mcd_channel_get_request_preferred_handler (request);
1153
1154 g_return_if_fail (op != NULL);
1155
1156 DEBUG ("channel %p is in CDO %p", channel, op);
1157 _mcd_dispatch_operation_approve (op, preferred_handler);
1158 }
1159 DEBUG ("channel %p is proxying %p", request, channel);
1160 }
1161 }
1162
1163 void
_mcd_dispatcher_recover_channel(McdDispatcher * dispatcher,McdChannel * channel,const gchar * account_path)1164 _mcd_dispatcher_recover_channel (McdDispatcher *dispatcher,
1165 McdChannel *channel,
1166 const gchar *account_path)
1167 {
1168 McdDispatcherPrivate *priv;
1169 const gchar *path;
1170 const gchar *unique_name;
1171 const gchar *well_known_name = NULL;
1172 gboolean requested;
1173 TpChannel *tp_channel;
1174
1175 /* we must check if the channel is already being handled by some client; to
1176 * do this, we can examine the active handlers' "HandledChannel" property.
1177 * By now, we should already have done this, because startup has completed.
1178 */
1179 g_return_if_fail (MCD_IS_DISPATCHER (dispatcher));
1180 priv = dispatcher->priv;
1181 g_return_if_fail (_mcd_client_registry_is_ready (
1182 dispatcher->priv->clients));
1183
1184 path = mcd_channel_get_object_path (channel);
1185 tp_channel = mcd_channel_get_tp_channel (channel);
1186 g_return_if_fail (tp_channel != NULL);
1187
1188 unique_name = _mcd_handler_map_get_handler (priv->handler_map, path,
1189 &well_known_name);
1190
1191 if (unique_name != NULL)
1192 {
1193 DEBUG ("Channel %s is already handled by process %s",
1194 path, unique_name);
1195 _mcd_channel_set_status (channel,
1196 MCD_CHANNEL_STATUS_DISPATCHED);
1197 _mcd_handler_map_set_channel_handled (priv->handler_map, tp_channel,
1198 unique_name, well_known_name,
1199 account_path);
1200 }
1201 else
1202 {
1203 DEBUG ("%s is unhandled, redispatching", path);
1204
1205 requested = mcd_channel_is_requested (channel);
1206 _mcd_dispatcher_add_channel (dispatcher, channel, requested, FALSE);
1207 }
1208 }
1209
1210 static gboolean
check_preferred_handler(const gchar * preferred_handler,GError ** error)1211 check_preferred_handler (const gchar *preferred_handler,
1212 GError **error)
1213 {
1214 g_assert (error != NULL);
1215
1216 if (preferred_handler[0] == '\0')
1217 return TRUE;
1218
1219 if (!tp_dbus_check_valid_bus_name (preferred_handler,
1220 TP_DBUS_NAME_TYPE_WELL_KNOWN,
1221 error))
1222 {
1223 /* The error is TP_DBUS_ERROR_INVALID_BUS_NAME, which has no D-Bus
1224 * representation; re-map to InvalidArgument. */
1225 (*error)->domain = TP_ERROR;
1226 (*error)->code = TP_ERROR_INVALID_ARGUMENT;
1227 return FALSE;
1228 }
1229
1230 if (!g_str_has_prefix (preferred_handler, TP_CLIENT_BUS_NAME_BASE))
1231 {
1232 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1233 "Not a Telepathy Client: %s", preferred_handler);
1234 return FALSE;
1235 }
1236
1237 return TRUE;
1238 }
1239
1240 static void
dispatcher_request_channel(McdDispatcher * self,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,GHashTable * request_metadata,DBusGMethodInvocation * context,gboolean ensure)1241 dispatcher_request_channel (McdDispatcher *self,
1242 const gchar *account_path,
1243 GHashTable *requested_properties,
1244 gint64 user_action_time,
1245 const gchar *preferred_handler,
1246 GHashTable *request_metadata,
1247 DBusGMethodInvocation *context,
1248 gboolean ensure)
1249 {
1250 McdAccountManager *am;
1251 McdAccount *account;
1252 McdChannel *channel = NULL;
1253 McdRequest *request = NULL;
1254 GError *error = NULL;
1255 const gchar *path;
1256
1257 g_return_if_fail (account_path != NULL);
1258 g_return_if_fail (requested_properties != NULL);
1259 g_return_if_fail (preferred_handler != NULL);
1260
1261 g_object_get (self->priv->master,
1262 "account-manager", &am,
1263 NULL);
1264
1265 g_assert (am != NULL);
1266
1267 account = mcd_account_manager_lookup_account_by_path (am,
1268 account_path);
1269
1270 if (account == NULL)
1271 {
1272 g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1273 "No such account: %s", account_path);
1274 goto despair;
1275 }
1276
1277 if (!check_preferred_handler (preferred_handler, &error))
1278 goto despair;
1279
1280 channel = _mcd_account_create_request (self->priv->clients,
1281 account, requested_properties,
1282 user_action_time, preferred_handler,
1283 request_metadata, ensure,
1284 &request, &error);
1285
1286 if (channel == NULL)
1287 {
1288 /* FIXME: ideally this would be emitted as a Failed signal after
1289 * Proceed is called, but for the particular failure case here (low
1290 * memory) perhaps we don't want to */
1291 goto despair;
1292 }
1293
1294 g_assert (request != NULL);
1295 path = _mcd_request_get_object_path (request);
1296 g_assert (path != NULL);
1297
1298 /* This is OK because the signatures of CreateChannel and EnsureChannel
1299 * are the same */
1300 tp_svc_channel_dispatcher_return_from_create_channel (context, path);
1301
1302 _mcd_request_predict_handler (request);
1303
1304 /* We've done all we need to with this channel: the ChannelRequests code
1305 * keeps it alive as long as is necessary. The finally clause will
1306 * free it */
1307 goto finally;
1308
1309 despair:
1310 dbus_g_method_return_error (context, error);
1311 g_error_free (error);
1312
1313 finally:
1314 tp_clear_object (&channel);
1315 tp_clear_object (&request);
1316 g_object_unref (am);
1317 }
1318
1319 static void
dispatcher_channel_request_acl_cleanup(gpointer data)1320 dispatcher_channel_request_acl_cleanup (gpointer data)
1321 {
1322 McdChannelRequestACL *crd = data;
1323
1324 DEBUG ("cleanup acl (%p)", data);
1325
1326 g_free (crd->account_path);
1327 g_free (crd->preferred_handler);
1328 g_hash_table_unref (crd->properties);
1329 g_object_unref (crd->dispatcher);
1330 tp_clear_pointer (&crd->request_metadata, g_hash_table_unref);
1331
1332 g_slice_free (McdChannelRequestACL, crd);
1333 }
1334
1335 static void
dispatcher_channel_request_acl_success(DBusGMethodInvocation * context,gpointer data)1336 dispatcher_channel_request_acl_success (DBusGMethodInvocation *context,
1337 gpointer data)
1338 {
1339 McdChannelRequestACL *crd = data;
1340
1341 DEBUG ("complete acl (%p)", crd);
1342
1343 dispatcher_request_channel (MCD_DISPATCHER (crd->dispatcher),
1344 crd->account_path,
1345 crd->properties,
1346 crd->user_action_time,
1347 crd->preferred_handler,
1348 crd->request_metadata,
1349 context,
1350 crd->ensure);
1351 }
1352
1353 static void
free_gvalue(gpointer gvalue)1354 free_gvalue (gpointer gvalue)
1355 {
1356 GValue *gv = gvalue;
1357
1358 g_value_unset (gv);
1359 g_slice_free (GValue, gv);
1360 }
1361
1362 static void
dispatcher_channel_request_acl_start(McdDispatcher * dispatcher,const gchar * method,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,GHashTable * request_metadata,DBusGMethodInvocation * context,gboolean ensure)1363 dispatcher_channel_request_acl_start (McdDispatcher *dispatcher,
1364 const gchar *method,
1365 const gchar *account_path,
1366 GHashTable *requested_properties,
1367 gint64 user_action_time,
1368 const gchar *preferred_handler,
1369 GHashTable *request_metadata,
1370 DBusGMethodInvocation *context,
1371 gboolean ensure)
1372 {
1373 McdChannelRequestACL *crd = g_slice_new0 (McdChannelRequestACL);
1374 GValue *account = g_slice_new0 (GValue);
1375 GHashTable *params =
1376 g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_gvalue);
1377
1378 g_value_init (account, G_TYPE_STRING);
1379 g_value_set_string (account, account_path);
1380 g_hash_table_insert (params, "account-path", account);
1381
1382 crd->dispatcher = g_object_ref (dispatcher);
1383 crd->account_path = g_strdup (account_path);
1384 crd->preferred_handler = g_strdup (preferred_handler);
1385 crd->properties = g_hash_table_ref (requested_properties);
1386 crd->user_action_time = user_action_time;
1387 crd->ensure = ensure;
1388 crd->request_metadata = request_metadata != NULL ?
1389 g_hash_table_ref (request_metadata) : NULL;
1390
1391 DEBUG ("start %s.%s acl (%p)", account_path, method, crd);
1392
1393 mcp_dbus_acl_authorised_async (dispatcher->priv->dbus_daemon,
1394 context,
1395 DBUS_ACL_TYPE_METHOD,
1396 method,
1397 params,
1398 dispatcher_channel_request_acl_success,
1399 crd,
1400 dispatcher_channel_request_acl_cleanup);
1401
1402 g_hash_table_unref (params);
1403 }
1404
1405 static void
dispatcher_create_channel(TpSvcChannelDispatcher * iface,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,DBusGMethodInvocation * context)1406 dispatcher_create_channel (TpSvcChannelDispatcher *iface,
1407 const gchar *account_path,
1408 GHashTable *requested_properties,
1409 gint64 user_action_time,
1410 const gchar *preferred_handler,
1411 DBusGMethodInvocation *context)
1412 {
1413 dispatcher_channel_request_acl_start (MCD_DISPATCHER (iface),
1414 CREATE_CHANNEL,
1415 account_path,
1416 requested_properties,
1417 user_action_time,
1418 preferred_handler,
1419 NULL,
1420 context,
1421 FALSE);
1422 }
1423
1424 static void
dispatcher_ensure_channel(TpSvcChannelDispatcher * iface,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,DBusGMethodInvocation * context)1425 dispatcher_ensure_channel (TpSvcChannelDispatcher *iface,
1426 const gchar *account_path,
1427 GHashTable *requested_properties,
1428 gint64 user_action_time,
1429 const gchar *preferred_handler,
1430 DBusGMethodInvocation *context)
1431 {
1432 dispatcher_channel_request_acl_start (MCD_DISPATCHER (iface),
1433 ENSURE_CHANNEL,
1434 account_path,
1435 requested_properties,
1436 user_action_time,
1437 preferred_handler,
1438 NULL,
1439 context,
1440 TRUE);
1441 }
1442
1443 static void
dispatcher_create_channel_with_hints(TpSvcChannelDispatcher * iface,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,GHashTable * hints,DBusGMethodInvocation * context)1444 dispatcher_create_channel_with_hints (TpSvcChannelDispatcher *iface,
1445 const gchar *account_path,
1446 GHashTable *requested_properties,
1447 gint64 user_action_time,
1448 const gchar *preferred_handler,
1449 GHashTable *hints,
1450 DBusGMethodInvocation *context)
1451 {
1452 dispatcher_channel_request_acl_start (MCD_DISPATCHER (iface),
1453 CREATE_CHANNEL,
1454 account_path,
1455 requested_properties,
1456 user_action_time,
1457 preferred_handler,
1458 hints,
1459 context,
1460 FALSE);
1461 }
1462
1463 static void
dispatcher_ensure_channel_with_hints(TpSvcChannelDispatcher * iface,const gchar * account_path,GHashTable * requested_properties,gint64 user_action_time,const gchar * preferred_handler,GHashTable * hints,DBusGMethodInvocation * context)1464 dispatcher_ensure_channel_with_hints (TpSvcChannelDispatcher *iface,
1465 const gchar *account_path,
1466 GHashTable *requested_properties,
1467 gint64 user_action_time,
1468 const gchar *preferred_handler,
1469 GHashTable *hints,
1470 DBusGMethodInvocation *context)
1471 {
1472 dispatcher_channel_request_acl_start (MCD_DISPATCHER (iface),
1473 ENSURE_CHANNEL,
1474 account_path,
1475 requested_properties,
1476 user_action_time,
1477 preferred_handler,
1478 hints,
1479 context,
1480 TRUE);
1481 }
1482
1483
1484 static void
mcd_dispatcher_lost_connection(gpointer data,GObject * corpse)1485 mcd_dispatcher_lost_connection (gpointer data,
1486 GObject *corpse)
1487 {
1488 McdDispatcher *self = MCD_DISPATCHER (data);
1489
1490 /* not safe to dereference corpse any more, so just print its address -
1491 * that's enough to pair up with add_connection calls */
1492 DEBUG ("%p: %p", self, corpse);
1493
1494 g_hash_table_remove (self->priv->connections, corpse);
1495 g_object_unref (self);
1496 }
1497
1498 /* FIXME: this only needs to exist because McdConnection calls it in order
1499 * to preload caps before Connect */
1500 GPtrArray *
_mcd_dispatcher_dup_client_caps(McdDispatcher * self)1501 _mcd_dispatcher_dup_client_caps (McdDispatcher *self)
1502 {
1503 g_return_val_if_fail (MCD_IS_DISPATCHER (self), NULL);
1504
1505 /* if we're not ready, return NULL to tell the connection not to preload */
1506 if (!_mcd_client_registry_is_ready (self->priv->clients))
1507 {
1508 return NULL;
1509 }
1510
1511 return _mcd_client_registry_dup_client_caps (self->priv->clients);
1512 }
1513
1514 void
_mcd_dispatcher_add_connection(McdDispatcher * self,McdConnection * connection)1515 _mcd_dispatcher_add_connection (McdDispatcher *self,
1516 McdConnection *connection)
1517 {
1518 g_return_if_fail (MCD_IS_DISPATCHER (self));
1519
1520 DEBUG ("%p: %p (%s)", self, connection,
1521 mcd_connection_get_object_path (connection));
1522
1523 g_hash_table_insert (self->priv->connections, connection, connection);
1524 g_object_weak_ref ((GObject *) connection, mcd_dispatcher_lost_connection,
1525 g_object_ref (self));
1526
1527 if (_mcd_client_registry_is_ready (self->priv->clients))
1528 {
1529 GPtrArray *vas =
1530 _mcd_client_registry_dup_client_caps (self->priv->clients);
1531
1532 _mcd_connection_start_dispatching (connection, vas);
1533
1534 g_ptr_array_foreach (vas, (GFunc) g_value_array_free, NULL);
1535 g_ptr_array_unref (vas);
1536 }
1537 /* else _mcd_connection_start_dispatching will be called when we're ready
1538 * for it */
1539 }
1540
1541 /* org.freedesktop.Telepathy.ChannelDispatcher.Messages */
1542 typedef struct
1543 {
1544 McdDispatcher *dispatcher;
1545 gchar *account_path;
1546 gchar *target_id;
1547 GPtrArray *payload;
1548 guint flags;
1549 guint tries;
1550 gboolean close_after;
1551 DBusGMethodInvocation *dbus_context;
1552 } MessageContext;
1553
1554 static MessageContext *
message_context_steal(MessageContext * from)1555 message_context_steal (MessageContext *from)
1556 {
1557 MessageContext *stolen = g_slice_new0 (MessageContext);
1558
1559 g_memmove (stolen, from, sizeof (MessageContext));
1560 memset (from, 0, sizeof (MessageContext));
1561
1562 return stolen;
1563 }
1564
1565 static MessageContext *
message_context_new(McdDispatcher * dispatcher,const gchar * account_path,const gchar * target_id,const GPtrArray * payload,guint flags)1566 message_context_new (McdDispatcher *dispatcher,
1567 const gchar *account_path,
1568 const gchar *target_id,
1569 const GPtrArray *payload,
1570 guint flags)
1571 {
1572 guint i;
1573 const guint size = payload->len;
1574 MessageContext *context = g_slice_new0 (MessageContext);
1575 GPtrArray *msg_copy = g_ptr_array_sized_new (size);
1576
1577 g_ptr_array_set_free_func (msg_copy, (GDestroyNotify) g_hash_table_unref);
1578
1579 for (i = 0; i < size; i++)
1580 {
1581 GHashTable *part = g_ptr_array_index (payload, i);
1582
1583 g_ptr_array_add (msg_copy, _mcd_deepcopy_asv (part));
1584 }
1585
1586 context->dispatcher = g_object_ref (dispatcher);
1587 context->account_path = g_strdup (account_path);
1588 context->target_id = g_strdup (target_id);
1589 context->payload = msg_copy;
1590 context->flags = flags;
1591 context->dbus_context = NULL;
1592
1593 return context;
1594 }
1595
1596 static void
message_context_return_error(MessageContext * context,const GError * error)1597 message_context_return_error (MessageContext *context, const GError *error)
1598 {
1599 if (context->dbus_context == NULL)
1600 return;
1601
1602 dbus_g_method_return_error (context->dbus_context, error);
1603 context->dbus_context = NULL;
1604 }
1605
1606 static void
message_context_set_return_context(MessageContext * context,DBusGMethodInvocation * dbus_context)1607 message_context_set_return_context (MessageContext *context,
1608 DBusGMethodInvocation *dbus_context)
1609 {
1610 context->dbus_context = dbus_context;
1611 }
1612
1613 static void
message_context_free(gpointer ctx)1614 message_context_free (gpointer ctx)
1615 {
1616 MessageContext *context = ctx;
1617
1618 tp_clear_pointer (&context->payload, g_ptr_array_unref);
1619 tp_clear_pointer (&context->account_path, g_free);
1620 tp_clear_pointer (&context->target_id, g_free);
1621
1622 if (context->dbus_context != NULL)
1623 {
1624 GError *error;
1625
1626 error = g_error_new_literal (TP_ERROR, TP_ERROR_TERMINATED,
1627 "Channel request failed");
1628 dbus_g_method_return_error (context->dbus_context, error);
1629 g_error_free (error);
1630 }
1631
1632 tp_clear_object (&context->dispatcher);
1633
1634 g_slice_free (MessageContext, context);
1635 }
1636
1637 static void
send_message_submitted(TpChannel * proxy,const gchar * token,const GError * error,gpointer data,GObject * weak)1638 send_message_submitted (TpChannel *proxy,
1639 const gchar *token,
1640 const GError *error,
1641 gpointer data,
1642 GObject *weak)
1643 {
1644 MessageContext *message = data;
1645 DBusGMethodInvocation *context = message->dbus_context;
1646 McdChannel *channel = MCD_CHANNEL (weak);
1647 McdRequest *request = _mcd_channel_get_request (channel);
1648 gboolean close_after = message->close_after;
1649
1650 /* this frees the dbus context, so clear it from our cache afterwards */
1651 if (error == NULL)
1652 {
1653 mc_svc_channel_dispatcher_interface_messages_draft_return_from_send_message (context, token);
1654 message_context_set_return_context (message, NULL);
1655 }
1656 else
1657 {
1658 DEBUG ("error: %s", error->message);
1659 message_context_return_error (message, error);
1660 }
1661
1662 _mcd_request_unblock_account (message->account_path);
1663 _mcd_request_clear_internal_handler (request);
1664
1665 if (close_after)
1666 _mcd_channel_close (channel);
1667 }
1668
1669 static void messages_send_message_start (DBusGMethodInvocation *context,
1670 MessageContext *message);
1671
1672 static void
send_message_got_channel(McdRequest * request,McdChannel * channel,gpointer data,gboolean close_after)1673 send_message_got_channel (McdRequest *request,
1674 McdChannel *channel,
1675 gpointer data,
1676 gboolean close_after)
1677 {
1678 MessageContext *message = data;
1679
1680 DEBUG ("received internal request/channel");
1681
1682 /* successful channel creation */
1683 if (channel != NULL)
1684 {
1685 message->close_after = close_after;
1686
1687 DEBUG ("calling send on channel interface");
1688 tp_cli_channel_interface_messages_call_send_message
1689 (mcd_channel_get_tp_channel (channel),
1690 -1,
1691 message->payload,
1692 message->flags,
1693 send_message_submitted,
1694 message,
1695 NULL,
1696 G_OBJECT (channel));
1697 }
1698 else /* doom and despair: no channel */
1699 {
1700 if (message->tries++ == 0)
1701 {
1702 messages_send_message_start (message->dbus_context, message);
1703 /* we created a new lock above, we can now release the old one: */
1704 _mcd_request_unblock_account (message->account_path);
1705 }
1706 else
1707 {
1708 GError *error = g_error_new_literal (TP_ERROR, TP_ERROR_CANCELLED,
1709 "Channel closed by owner");
1710
1711 _mcd_request_unblock_account (message->account_path);
1712 message_context_return_error (message, error);
1713 _mcd_request_clear_internal_handler (request);
1714 g_error_free (error);
1715 }
1716 }
1717 }
1718
1719 static void
messages_send_message_acl_success(DBusGMethodInvocation * dbus_context,gpointer data)1720 messages_send_message_acl_success (DBusGMethodInvocation *dbus_context,
1721 gpointer data)
1722 {
1723 /* steal the contents of the message context from the ACL framework: *
1724 * this avoids a nasty double-free (and means we don't have to dup *
1725 * the message payload memory twice) */
1726 messages_send_message_start (dbus_context, message_context_steal (data));
1727 }
1728
1729 static void
messages_send_message_start(DBusGMethodInvocation * dbus_context,MessageContext * message)1730 messages_send_message_start (DBusGMethodInvocation *dbus_context,
1731 MessageContext *message)
1732 {
1733 McdAccountManager *am;
1734 McdAccount *account;
1735 McdChannel *channel = NULL;
1736 McdRequest *request = NULL;
1737 GError *error = NULL;
1738 GHashTable *props = NULL;
1739 GValue c_type = G_VALUE_INIT;
1740 GValue h_type = G_VALUE_INIT;
1741 GValue target = G_VALUE_INIT;
1742 McdDispatcher *self = message->dispatcher;
1743
1744 DEBUG ("messages_send_message_acl_success [attempt #%u]", message->tries);
1745 /* the message request can now take posession of the dbus method context */
1746 message_context_set_return_context (message, dbus_context);
1747
1748 if (tp_str_empty (message->account_path))
1749 {
1750 g_set_error_literal (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1751 "Account path not specified");
1752 goto failure;
1753 }
1754
1755 g_object_get (self->priv->master, "account-manager", &am, NULL);
1756
1757 g_assert (am != NULL);
1758
1759 account =
1760 mcd_account_manager_lookup_account_by_path (am, message->account_path);
1761
1762 if (account == NULL)
1763 {
1764 g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1765 "No such account: %s", message->account_path);
1766 goto failure;
1767 }
1768
1769 props = g_hash_table_new_full (g_str_hash, g_str_equal,
1770 NULL, (GDestroyNotify) g_value_unset);
1771
1772 g_value_init (&c_type, G_TYPE_STRING);
1773 g_value_init (&h_type, G_TYPE_UINT);
1774 g_value_init (&target, G_TYPE_STRING);
1775
1776 g_value_set_static_string (&c_type, TP_IFACE_CHANNEL_TYPE_TEXT);
1777 g_value_set_uint (&h_type, TP_HANDLE_TYPE_CONTACT);
1778 g_value_set_string (&target, message->target_id);
1779
1780 g_hash_table_insert (props, TP_PROP_CHANNEL_CHANNEL_TYPE, &c_type);
1781 g_hash_table_insert (props, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, &h_type);
1782 g_hash_table_insert (props, TP_PROP_CHANNEL_TARGET_ID, &target);
1783
1784 /* compare dispatcher_request_channel: we _are_ the handler for *
1785 * this channel so we don't need to check_preferred_handler here *
1786 * Also: this deep-copies the props hash, so we can throw ours away */
1787 channel = _mcd_account_create_request (self->priv->clients,
1788 account, props, time (NULL),
1789 NULL, NULL, TRUE,
1790 &request, &error);
1791 g_hash_table_unref (props);
1792
1793 if (channel == NULL || request == NULL)
1794 {
1795 g_set_error (&error, TP_ERROR, TP_ERROR_RESOURCE_UNAVAILABLE,
1796 "Could not create channel request");
1797 goto failure;
1798 }
1799
1800 _mcd_request_set_internal_handler (request,
1801 send_message_got_channel,
1802 message_context_free,
1803 message);
1804
1805 /* we don't need to predict the handler either, same reason as above *
1806 * we do, however, want to call proceed on the request, as it is ours */
1807 _mcd_request_proceed (request, NULL);
1808
1809 goto finished;
1810
1811 failure:
1812 message_context_return_error (message, error);
1813 message_context_free (message);
1814 g_error_free (error);
1815
1816 finished:
1817 /* these are reffed and held open by the request infrastructure */
1818 tp_clear_object (&channel);
1819 tp_clear_object (&request);
1820 }
1821
1822 static void
messages_send_message_acl_cleanup(gpointer data)1823 messages_send_message_acl_cleanup (gpointer data)
1824 {
1825 MessageContext *message = data;
1826
1827 /* At this point either the messages framework or the ACL framework *
1828 * is expected to have handled the DBus return, so we must not try to */
1829 message_context_set_return_context (message, NULL);
1830 message_context_free (message);
1831 }
1832
1833 static void
messages_send_message(McSvcChannelDispatcherInterfaceMessagesDraft * iface,const gchar * account_path,const gchar * target_id,const GPtrArray * payload,guint flags,DBusGMethodInvocation * context)1834 messages_send_message (McSvcChannelDispatcherInterfaceMessagesDraft *iface,
1835 const gchar *account_path,
1836 const gchar *target_id,
1837 const GPtrArray *payload,
1838 guint flags,
1839 DBusGMethodInvocation *context)
1840 {
1841 McdDispatcher *self= MCD_DISPATCHER (iface);
1842 MessageContext *message =
1843 message_context_new (self, account_path, target_id, payload, flags);
1844
1845 /* these are for the ACL itself */
1846 GValue *account = g_slice_new0 (GValue);
1847 GHashTable *params =
1848 g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_gvalue);
1849
1850 g_value_init (account, G_TYPE_STRING);
1851 g_value_set_string (account, account_path);
1852 g_hash_table_insert (params, "account-path", account);
1853
1854 mcp_dbus_acl_authorised_async (self->priv->dbus_daemon,
1855 context,
1856 DBUS_ACL_TYPE_METHOD,
1857 SEND_MESSAGE,
1858 params,
1859 messages_send_message_acl_success,
1860 message,
1861 messages_send_message_acl_cleanup);
1862 }
1863
1864 static void
messages_iface_init(gpointer iface,gpointer data G_GNUC_UNUSED)1865 messages_iface_init (gpointer iface, gpointer data G_GNUC_UNUSED)
1866 {
1867 #define IMPLEMENT(x) \
1868 mc_svc_channel_dispatcher_interface_messages_draft_implement_##x (iface, messages_##x)
1869 IMPLEMENT (send_message);
1870 #undef IMPLEMENT
1871 }
1872
1873 typedef struct
1874 {
1875 McdDispatcher *self;
1876 gint64 user_action_time;
1877 DBusGMethodInvocation *context;
1878 /* List of owned ChannelToDelegate */
1879 GList *channels;
1880 /* array of owned channel path */
1881 GPtrArray *delegated;
1882 /* owned channel path -> owned GValueArray representing a
1883 * TP_STRUCT_TYPE_NOT_DELEGATED_ERROR */
1884 GHashTable *not_delegated;
1885 } DelegateChannelsCtx;
1886
1887 typedef struct
1888 {
1889 /* borrowed reference */
1890 DelegateChannelsCtx *ctx;
1891 McdAccount *account;
1892 McdChannel *channel;
1893 /* Queue of reffed McdClientProxy */
1894 GQueue *handlers;
1895 GError *error;
1896 } ChannelToDelegate;
1897
1898 static ChannelToDelegate *
channel_to_delegate_new(DelegateChannelsCtx * ctx,McdAccount * account,McdChannel * channel)1899 channel_to_delegate_new (DelegateChannelsCtx *ctx,
1900 McdAccount *account,
1901 McdChannel *channel)
1902 {
1903 ChannelToDelegate *chan = g_slice_new0 (ChannelToDelegate);
1904
1905 chan->ctx = ctx;
1906 chan->account = g_object_ref (account);
1907 chan->channel = g_object_ref (channel);
1908 chan->handlers = g_queue_new ();
1909 chan->error = NULL;
1910 return chan;
1911 }
1912
1913 static void
channel_to_delegate_free(ChannelToDelegate * chan)1914 channel_to_delegate_free (ChannelToDelegate *chan)
1915 {
1916 g_object_unref (chan->account);
1917 g_object_unref (chan->channel);
1918 g_queue_foreach (chan->handlers, (GFunc) g_object_unref, NULL);
1919 g_queue_free (chan->handlers);
1920 tp_clear_pointer (&chan->error, g_error_free);
1921 g_slice_free (ChannelToDelegate, chan);
1922 }
1923
1924 static void
free_not_delegated_error(gpointer data)1925 free_not_delegated_error (gpointer data)
1926 {
1927 g_boxed_free (TP_STRUCT_TYPE_NOT_DELEGATED_ERROR, data);
1928 }
1929
1930 static DelegateChannelsCtx *
delegate_channels_ctx_new(McdDispatcher * self,gint64 user_action_time,DBusGMethodInvocation * context)1931 delegate_channels_ctx_new (McdDispatcher *self,
1932 gint64 user_action_time,
1933 DBusGMethodInvocation *context)
1934 {
1935 DelegateChannelsCtx *ctx = g_slice_new0 (DelegateChannelsCtx);
1936
1937 ctx->self = g_object_ref (self);
1938 ctx->user_action_time = user_action_time;
1939 ctx->context = context;
1940 ctx->channels = NULL;
1941 ctx->delegated = g_ptr_array_new_with_free_func (g_free);
1942 ctx->not_delegated = g_hash_table_new_full (g_str_hash, g_str_equal,
1943 g_free, free_not_delegated_error);
1944 return ctx;
1945 }
1946
1947 static void
delegate_channels_ctx_free(DelegateChannelsCtx * ctx)1948 delegate_channels_ctx_free (DelegateChannelsCtx *ctx)
1949 {
1950 g_object_unref (ctx->self);
1951 g_ptr_array_unref (ctx->delegated);
1952 g_hash_table_unref (ctx->not_delegated);
1953 g_list_free_full (ctx->channels, (GDestroyNotify) channel_to_delegate_free);
1954 g_slice_free (DelegateChannelsCtx, ctx);
1955 }
1956
1957 static void try_delegating (ChannelToDelegate *to_delegate);
1958
1959 static void
delegation_done(ChannelToDelegate * to_delegate)1960 delegation_done (ChannelToDelegate *to_delegate)
1961 {
1962 DelegateChannelsCtx *ctx = to_delegate->ctx;
1963
1964 ctx->channels = g_list_remove (ctx->channels, to_delegate);
1965 channel_to_delegate_free (to_delegate);
1966
1967 if (ctx->channels == NULL)
1968 {
1969 /* We are done */
1970 tp_svc_channel_dispatcher_return_from_delegate_channels (
1971 ctx->context, ctx->delegated, ctx->not_delegated);
1972
1973 delegate_channels_ctx_free (ctx);
1974 }
1975 }
1976
1977 static void
delegate_channels_cb(TpClient * client,const GError * error,gpointer user_data G_GNUC_UNUSED,GObject * weak_object)1978 delegate_channels_cb (TpClient *client,
1979 const GError *error,
1980 gpointer user_data G_GNUC_UNUSED,
1981 GObject *weak_object)
1982 {
1983 ChannelToDelegate *to_delegate = user_data;
1984 DelegateChannelsCtx *ctx = to_delegate->ctx;
1985 McdClientProxy *clt_proxy = MCD_CLIENT_PROXY (client);
1986
1987 /* If the delegation succeeded, the channel has a new handler. If
1988 * the delegation failed, the channel still has the old
1989 * handler. Either way, the channel still has a handler, so it has
1990 * been successfully dispatched (from 'handler invoked'). */
1991 _mcd_channel_set_status (to_delegate->channel,
1992 MCD_CHANNEL_STATUS_DISPATCHED);
1993
1994 if (error != NULL)
1995 {
1996 DEBUG ("Handler refused delegated channels");
1997
1998 if (to_delegate->error == NULL)
1999 to_delegate->error = g_error_copy (error);
2000
2001 try_delegating (to_delegate);
2002 return;
2003 }
2004
2005 DEBUG ("Channel %s has been delegated", mcd_channel_get_object_path (
2006 to_delegate->channel));
2007
2008 _mcd_handler_map_set_path_handled (ctx->self->priv->handler_map,
2009 mcd_channel_get_object_path (to_delegate->channel),
2010 _mcd_client_proxy_get_unique_name (clt_proxy),
2011 tp_proxy_get_bus_name (client));
2012
2013 g_ptr_array_add (ctx->delegated, g_strdup (
2014 mcd_channel_get_object_path (to_delegate->channel)));
2015
2016 delegation_done (to_delegate);
2017 }
2018
2019 static void
try_delegating(ChannelToDelegate * to_delegate)2020 try_delegating (ChannelToDelegate *to_delegate)
2021 {
2022 McdClientProxy *client;
2023 GList *channels = NULL;
2024
2025 DEBUG ("%s",
2026 mcd_channel_get_object_path (to_delegate->channel));
2027
2028 if (g_queue_get_length (to_delegate->handlers) == 0)
2029 {
2030 GValueArray *v;
2031 const gchar *dbus_error;
2032
2033 if (to_delegate->error == NULL)
2034 {
2035 g_set_error (&to_delegate->error, TP_ERROR, TP_ERROR_NOT_CAPABLE,
2036 "There is no other suitable handler");
2037 }
2038
2039 if (to_delegate->error->domain == TP_ERROR)
2040 dbus_error = tp_error_get_dbus_name (to_delegate->error->code);
2041 else
2042 dbus_error = TP_ERROR_STR_NOT_AVAILABLE;
2043
2044 /* We failed to delegate this channel */
2045 v = tp_value_array_build (2,
2046 G_TYPE_STRING, dbus_error,
2047 G_TYPE_STRING, to_delegate->error->message,
2048 G_TYPE_INVALID);
2049
2050 g_hash_table_insert (to_delegate->ctx->not_delegated,
2051 g_strdup (mcd_channel_get_object_path (to_delegate->channel)),
2052 v);
2053
2054 DEBUG ("...but failed to delegate %s: %s",
2055 mcd_channel_get_object_path (to_delegate->channel),
2056 to_delegate->error->message);
2057
2058 delegation_done (to_delegate);
2059 return;
2060 }
2061
2062 client = g_queue_pop_head (to_delegate->handlers);
2063
2064 DEBUG ("...trying client %s", _mcd_client_proxy_get_unique_name (
2065 client));
2066
2067 channels = g_list_prepend (channels, to_delegate->channel);
2068
2069 _mcd_client_proxy_handle_channels (client, -1, channels,
2070 to_delegate->ctx->user_action_time, NULL, delegate_channels_cb,
2071 to_delegate, NULL, NULL);
2072
2073 g_object_unref (client);
2074 g_list_free (channels);
2075 }
2076
2077 static void
add_possible_handlers(McdDispatcher * self,ChannelToDelegate * to_delegate,TpChannel * tp_channel,const gchar * sender,const gchar * preferred_handler)2078 add_possible_handlers (McdDispatcher *self,
2079 ChannelToDelegate *to_delegate,
2080 TpChannel *tp_channel,
2081 const gchar *sender,
2082 const gchar *preferred_handler)
2083 {
2084 GStrv possible_handlers;
2085 guint i;
2086
2087 possible_handlers = mcd_dispatcher_dup_possible_handlers (self,
2088 NULL, tp_channel, NULL);
2089
2090 for (i = 0; possible_handlers[i] != NULL; i++)
2091 {
2092 McdClientProxy *client;
2093 const gchar *unique_name;
2094
2095 client = _mcd_client_registry_lookup (self->priv->clients,
2096 possible_handlers[i]);
2097 g_return_if_fail (client != NULL);
2098
2099 unique_name = _mcd_client_proxy_get_unique_name (client);
2100
2101 /* Skip the caller */
2102 if (!tp_strdiff (unique_name, sender))
2103 continue;
2104
2105 /* Put the preferred handler at the head of the list so it will be tried
2106 * first */
2107 if (!tp_strdiff (possible_handlers[i], preferred_handler))
2108 g_queue_push_head (to_delegate->handlers, g_object_ref (client));
2109 else
2110 g_queue_push_tail (to_delegate->handlers, g_object_ref (client));
2111 }
2112
2113 g_strfreev (possible_handlers);
2114 }
2115
2116 static void
dispatcher_delegate_channels(TpSvcChannelDispatcher * iface,const GPtrArray * channels,gint64 user_action_time,const gchar * preferred_handler,DBusGMethodInvocation * context)2117 dispatcher_delegate_channels (
2118 TpSvcChannelDispatcher *iface,
2119 const GPtrArray *channels,
2120 gint64 user_action_time,
2121 const gchar *preferred_handler,
2122 DBusGMethodInvocation *context)
2123 {
2124 McdDispatcher *self = (McdDispatcher *) iface;
2125 GError *error = NULL;
2126 gchar *sender = NULL;
2127 McdConnection *conn = NULL;
2128 DelegateChannelsCtx *ctx = NULL;
2129 McdAccountManager *am = NULL;
2130 guint i;
2131 GList *l;
2132
2133 DEBUG ("called");
2134
2135 if (!check_preferred_handler (preferred_handler, &error))
2136 goto error;
2137
2138 if (channels->len == 0)
2139 {
2140 g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2141 "Need at least one channel to delegate");
2142 goto error;
2143 }
2144
2145 ctx = delegate_channels_ctx_new (self, user_action_time, context);
2146
2147 sender = dbus_g_method_get_sender (context);
2148
2149 g_object_get (self->priv->master, "account-manager", &am, NULL);
2150 g_assert (am != NULL);
2151
2152 for (i = 0; i < channels->len; i++)
2153 {
2154 const gchar *chan_path = g_ptr_array_index (channels, i);
2155 const gchar *chan_account;
2156 const gchar *handler;
2157 McdChannel *mcd_channel;
2158 TpChannel *tp_channel;
2159 McdAccount *account;
2160 ChannelToDelegate *to_delegate;
2161
2162 chan_account = _mcd_handler_map_get_channel_account (
2163 self->priv->handler_map, chan_path);
2164
2165 if (chan_account == NULL)
2166 {
2167 g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2168 "Unknown channel: %s", chan_path);
2169 goto error;
2170 }
2171
2172 account = mcd_account_manager_lookup_account_by_path (am,
2173 chan_account);
2174 g_return_if_fail (account != NULL);
2175
2176 /* Check the caller is handling the channel */
2177 handler = _mcd_handler_map_get_handler (self->priv->handler_map,
2178 chan_path, NULL);
2179 if (tp_strdiff (sender, handler))
2180 {
2181 g_set_error (&error, TP_ERROR, TP_ERROR_NOT_YOURS,
2182 "Your are not handling channel %s", chan_path);
2183 goto error;
2184 }
2185
2186 conn = mcd_account_get_connection (account);
2187 g_return_if_fail (conn != NULL);
2188
2189 mcd_channel = mcd_connection_find_channel_by_path (conn, chan_path);
2190 g_return_if_fail (mcd_channel != NULL);
2191
2192 tp_channel = mcd_channel_get_tp_channel (mcd_channel);
2193 g_return_if_fail (tp_channel != NULL);
2194
2195 to_delegate = channel_to_delegate_new (ctx, account, mcd_channel);
2196
2197 add_possible_handlers (self, to_delegate, tp_channel, sender,
2198 preferred_handler);
2199
2200 ctx->channels = g_list_prepend (ctx->channels, to_delegate);
2201 }
2202
2203 /* All the channels were ok, we can start delegating */
2204 for (l = ctx->channels; l != NULL; l = g_list_next (l))
2205 {
2206 ChannelToDelegate *to_delegate = l->data;
2207
2208 try_delegating (to_delegate);
2209 }
2210
2211 g_free (sender);
2212 g_object_unref (am);
2213
2214 return;
2215
2216 error:
2217 g_free (sender);
2218 dbus_g_method_return_error (context, error);
2219 g_error_free (error);
2220
2221 tp_clear_pointer (&ctx, delegate_channels_ctx_free);
2222 tp_clear_object (&am);
2223 }
2224
2225 static void
present_handle_channels_cb(TpClient * client,const GError * error,gpointer user_data,GObject * weak_object)2226 present_handle_channels_cb (TpClient *client,
2227 const GError *error,
2228 gpointer user_data,
2229 GObject *weak_object)
2230 {
2231 DBusGMethodInvocation *context = user_data;
2232 McdChannel *mcd_channel = MCD_CHANNEL (weak_object);
2233
2234 /* Whether presenting the channel succeeded or failed, the
2235 * channel's handler hasn't been altered, so it must be set back
2236 * to the dispatched state (from 'handler invoked'). */
2237 _mcd_channel_set_status (mcd_channel,
2238 MCD_CHANNEL_STATUS_DISPATCHED);
2239
2240 if (error != NULL)
2241 {
2242 dbus_g_method_return_error (context, error);
2243 return;
2244 }
2245
2246 tp_svc_channel_dispatcher_return_from_present_channel (context);
2247 }
2248
2249 static void
dispatcher_present_channel(TpSvcChannelDispatcher * iface,const gchar * channel_path,gint64 user_action_time,DBusGMethodInvocation * context)2250 dispatcher_present_channel (
2251 TpSvcChannelDispatcher *iface,
2252 const gchar *channel_path,
2253 gint64 user_action_time,
2254 DBusGMethodInvocation *context)
2255 {
2256 McdDispatcher *self = (McdDispatcher *) iface;
2257 McdAccountManager *am;
2258 const gchar *chan_account;
2259 McdAccount *account;
2260 McdConnection *conn;
2261 McdChannel *mcd_channel;
2262 GError *error = NULL;
2263 McdClientProxy *client;
2264 GList *channels = NULL;
2265
2266 chan_account = _mcd_handler_map_get_channel_account (
2267 self->priv->handler_map, channel_path);
2268
2269 if (chan_account == NULL)
2270 {
2271 g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2272 "Unknown channel: %s", channel_path);
2273 goto error;
2274 }
2275
2276 g_object_get (self->priv->master, "account-manager", &am, NULL);
2277 g_assert (am != NULL);
2278
2279 account = mcd_account_manager_lookup_account_by_path (am, chan_account);
2280 g_return_if_fail (account != NULL);
2281 g_object_unref (am);
2282
2283 conn = mcd_account_get_connection (account);
2284 g_return_if_fail (conn != NULL);
2285
2286 mcd_channel = mcd_connection_find_channel_by_path (conn, channel_path);
2287 g_return_if_fail (mcd_channel != NULL);
2288
2289 /* We take mcd_channel's request to base the search for a suitable Handler
2290 * on the handler that was preferred by the request that initially created
2291 * the Channel, if any.
2292 * Actually not, because of fd.o#41031 */
2293 client = _mcd_dispatcher_lookup_handler (self,
2294 mcd_channel_get_tp_channel (mcd_channel),
2295 _mcd_channel_get_request (mcd_channel));
2296 if (client == NULL)
2297 {
2298 g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
2299 "Channel %s is currently not handled", channel_path);
2300 goto error;
2301 }
2302
2303 channels = g_list_append (channels, mcd_channel);
2304
2305 _mcd_client_proxy_handle_channels (client, -1, channels,
2306 user_action_time, NULL, present_handle_channels_cb,
2307 context, NULL, G_OBJECT (mcd_channel));
2308
2309 g_list_free (channels);
2310 return;
2311
2312 error:
2313 dbus_g_method_return_error (context, error);
2314 g_error_free (error);
2315 }
2316
2317 static void
dispatcher_iface_init(gpointer g_iface,gpointer iface_data G_GNUC_UNUSED)2318 dispatcher_iface_init (gpointer g_iface,
2319 gpointer iface_data G_GNUC_UNUSED)
2320 {
2321 #define IMPLEMENT(x) tp_svc_channel_dispatcher_implement_##x (\
2322 g_iface, dispatcher_##x)
2323 IMPLEMENT (create_channel);
2324 IMPLEMENT (ensure_channel);
2325 IMPLEMENT (create_channel_with_hints);
2326 IMPLEMENT (ensure_channel_with_hints);
2327 IMPLEMENT (delegate_channels);
2328 IMPLEMENT (present_channel);
2329 #undef IMPLEMENT
2330 }
2331