1 /* vi: set et sw=4 ts=4 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /*
4 * This file is part of mission-control
5 *
6 * Copyright © 2008-2011 Nokia Corporation.
7 * Copyright © 2009-2011 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 License
11 * version 2.1 as published by the Free Software Foundation.
12 *
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25 #include "config.h"
26 #include "mcd-dispatch-operation-priv.h"
27
28 #include <stdio.h>
29 #include <string.h>
30
31 #include <dbus/dbus.h>
32 #include <dbus/dbus-glib-lowlevel.h>
33
34 #include <glib/gstdio.h>
35
36 #include <telepathy-glib/telepathy-glib.h>
37 #include <telepathy-glib/telepathy-glib-dbus.h>
38
39 #include "channel-utils.h"
40 #include "mcd-channel-priv.h"
41 #include "mcd-dbusprop.h"
42 #include "mcd-master-priv.h"
43 #include "mcd-misc.h"
44 #include "plugin-dispatch-operation.h"
45 #include "plugin-loader.h"
46
47 #define MCD_DISPATCH_OPERATION_PRIV(operation) (MCD_DISPATCH_OPERATION (operation)->priv)
48
49 static void
50 dispatch_operation_iface_init (TpSvcChannelDispatchOperationClass *iface,
51 gpointer iface_data);
52 static void properties_iface_init (TpSvcDBusPropertiesClass *iface,
53 gpointer iface_data);
54
55 static const McdDBusProp dispatch_operation_properties[];
56
57 static const McdInterfaceData dispatch_operation_interfaces[] = {
58 MCD_IMPLEMENT_IFACE (tp_svc_channel_dispatch_operation_get_type,
59 dispatch_operation,
60 TP_IFACE_CHANNEL_DISPATCH_OPERATION),
61 { NULL, }
62 };
63
64 G_DEFINE_TYPE_WITH_CODE (McdDispatchOperation, _mcd_dispatch_operation,
65 G_TYPE_OBJECT,
66 MCD_DBUS_INIT_INTERFACES (dispatch_operation_interfaces);
67 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, properties_iface_init);
68 )
69
70 typedef enum {
71 APPROVAL_TYPE_REQUESTED,
72 APPROVAL_TYPE_HANDLE_WITH,
73 APPROVAL_TYPE_CLAIM,
74 APPROVAL_TYPE_CHANNELS_LOST,
75 APPROVAL_TYPE_NO_APPROVERS
76 } ApprovalType;
77
78 typedef struct {
79 ApprovalType type;
80 /* NULL unless type is REQUESTED or HANDLE_WITH; may be NULL even in those
81 * cases, to signify "any handler will do" */
82 gchar *client_bus_name;
83 /* NULL unless type is CLAIM or HANDLE_WITH */
84 DBusGMethodInvocation *context;
85 } Approval;
86
87 static Approval *
approval_new_handle_with(const gchar * client_bus_name,DBusGMethodInvocation * context)88 approval_new_handle_with (const gchar *client_bus_name,
89 DBusGMethodInvocation *context)
90 {
91 Approval *approval = g_slice_new0 (Approval);
92
93 g_assert (context != NULL);
94
95 if (client_bus_name != NULL && client_bus_name[0] != '\0')
96 approval->client_bus_name = g_strdup (client_bus_name);
97
98 approval->type = APPROVAL_TYPE_HANDLE_WITH;
99 approval->context = context;
100 return approval;
101 }
102
103 static Approval *
approval_new_claim(DBusGMethodInvocation * context)104 approval_new_claim (DBusGMethodInvocation *context)
105 {
106 Approval *approval = g_slice_new0 (Approval);
107
108 g_assert (context != NULL);
109 approval->type = APPROVAL_TYPE_CLAIM;
110 approval->context = context;
111 return approval;
112 }
113
114 static Approval *
approval_new_requested(const gchar * preferred_bus_name)115 approval_new_requested (const gchar *preferred_bus_name)
116 {
117 Approval *approval = g_slice_new0 (Approval);
118
119 if (preferred_bus_name != NULL && preferred_bus_name[0] != '\0')
120 approval->client_bus_name = g_strdup (preferred_bus_name);
121
122 approval->type = APPROVAL_TYPE_REQUESTED;
123 return approval;
124 }
125
126 static Approval *
approval_new(ApprovalType type)127 approval_new (ApprovalType type)
128 {
129 Approval *approval = g_slice_new0 (Approval);
130
131 switch (type)
132 {
133 case APPROVAL_TYPE_CLAIM:
134 case APPROVAL_TYPE_HANDLE_WITH:
135 case APPROVAL_TYPE_REQUESTED:
136 g_assert_not_reached ();
137 default:
138 {} /* do nothing */
139 }
140
141 approval->type = type;
142 return approval;
143 }
144
145 static void
approval_free(Approval * approval)146 approval_free (Approval *approval)
147 {
148 /* we should have replied to the method call by now */
149 g_assert (approval->context == NULL);
150
151 g_free (approval->client_bus_name);
152 g_slice_free (Approval, approval);
153 }
154
155 struct _McdDispatchOperationPrivate
156 {
157 const gchar *unique_name; /* borrowed from object_path */
158 gchar *object_path;
159 GStrv possible_handlers;
160 GHashTable *properties;
161
162 /* If FALSE, we're not actually on D-Bus; an object path is reserved,
163 * but we're inaccessible. */
164 guint needs_approval : 1;
165
166 /* set of handlers we already tried
167 * dup'd bus name (string) => dummy non-NULL pointer */
168 GHashTable *failed_handlers;
169
170 /* if non-NULL, we will emit finished as soon as we can; on success,
171 * this is NotYours, and on failure, it's something else */
172 GError *result;
173
174 /* The time of the latest call to HandleWith(), for focus-stealing
175 * prevention.
176 *
177 * This is shared between calls, so if the user makes contradictory
178 * choices, like HandleWith("...Empathy") and HandleWith("...Kopete") in
179 * quick succession, the channel will be handled with Empathy, but the
180 * timestamp for focus-stealing purposes will be that of the call that
181 * wanted Kopete; we consider this to be reasonable, since the user did
182 * expect *something* to happen at the time of the second call. */
183 gint64 handle_with_time;
184
185 /* queue of Approval */
186 GQueue *approvals;
187 /* if not NULL, the handler that accepted it */
188 TpClient *successful_handler;
189
190 /* Reference to a global handler map */
191 McdHandlerMap *handler_map;
192
193 /* Reference to a global registry of clients */
194 McdClientRegistry *client_registry;
195
196 McdAccount *account;
197 McdConnection *connection;
198
199 /* Owned McdChannel we're dispatching */
200 McdChannel *channel;
201 /* If non-NULL, we have lost the McdChannel but can't emit
202 * ChannelLost yet */
203 McdChannel *lost_channel;
204
205 /* If TRUE, either the channel being dispatched was requested, or it
206 * was pre-approved by being returned as a response to another request,
207 * or a client approved processing with arbitrary handlers */
208 gboolean approved;
209
210 /* If TRUE, at least one Approver accepted this dispatch operation, and
211 * we're waiting for one of them to call HandleWith or Claim. */
212 gboolean accepted_by_an_approver;
213
214 /* If FALSE, we're still working out what Observers and/or Approvers to
215 * run. These are temporary client locks.
216 */
217 gboolean invoked_observers_if_needed;
218 gboolean invoked_approvers_if_needed;
219
220 /* The number of observers that have not yet returned from ObserveChannels.
221 * Until they have done so, we can't allow the dispatch operation to
222 * finish. This is a client lock.
223 *
224 * A reference is held for each pending observer. */
225 gsize observers_pending;
226
227 /* The number of observers that are pending which have
228 * DelayApprovers=TRUE. This is used to know if
229 * AddDispatchOperation can be called yet. */
230 gsize delay_approver_observers_pending;
231
232 /* The number of approvers that have not yet returned from
233 * AddDispatchOperation. Until they have done so, we can't allow the
234 * dispatch operation to finish. This is a client lock.
235 *
236 * A reference is held for each pending approver. */
237 gsize ado_pending;
238
239 /* The number of plugins whose decision we're waiting for,
240 * regarding whether a handler is in fact suitable. */
241 gsize handler_suitable_pending;
242
243 /* If non-NULL, a plugin has decided the selected handler is unsuitable. */
244 GError *handler_unsuitable;
245
246 /* If TRUE, we're dispatching a channel request and it was cancelled */
247 gboolean cancelled;
248
249 /* if TRUE, this channel was requested "behind our back", so stop
250 * after observers */
251 gboolean observe_only;
252
253 /* If non-NULL, we're in the middle of asking plugins whether we may call
254 * HandleChannels, or doing so. This is a client lock. */
255 McdClientProxy *trying_handler;
256
257 /* If TRUE, we've tried all the BypassApproval handlers, which happens
258 * before we run approvers. */
259 gboolean tried_handlers_before_approval;
260
261 McdPluginDispatchOperation *plugin_api;
262 gsize plugins_pending;
263 gboolean did_post_observer_actions;
264 };
265
266 static void _mcd_dispatch_operation_check_finished (
267 McdDispatchOperation *self);
268 static void _mcd_dispatch_operation_finish (McdDispatchOperation *,
269 GQuark domain, gint code, const gchar *format, ...) G_GNUC_PRINTF (4, 5);
270
271 static void _mcd_dispatch_operation_check_client_locks (
272 McdDispatchOperation *self);
273
274 /* To give clients time to connect to our "destructive" signals (ChannelLost
275 * and Finished), we guarantee not to emit them if we have called methods on
276 * an observer or approver, but they have not returned.
277 *
278 * Returns: TRUE if we may emit Finished or ChannelLost */
279 static inline gboolean
mcd_dispatch_operation_may_signal_finished(McdDispatchOperation * self)280 mcd_dispatch_operation_may_signal_finished (McdDispatchOperation *self)
281 {
282 return (self->priv->invoked_observers_if_needed &&
283 self->priv->observers_pending == 0 &&
284 self->priv->ado_pending == 0);
285 }
286
287 static void
_mcd_dispatch_operation_inc_observers_pending(McdDispatchOperation * self,McdClientProxy * client)288 _mcd_dispatch_operation_inc_observers_pending (McdDispatchOperation *self,
289 McdClientProxy *client)
290 {
291 g_return_if_fail (self->priv->result == NULL);
292
293 g_object_ref (self);
294
295 DEBUG ("%" G_GSIZE_FORMAT " -> %" G_GSIZE_FORMAT,
296 self->priv->observers_pending,
297 self->priv->observers_pending + 1);
298 self->priv->observers_pending++;
299
300 if (_mcd_client_proxy_get_delay_approvers (client))
301 self->priv->delay_approver_observers_pending++;
302 }
303
304 static void
_mcd_dispatch_operation_dec_observers_pending(McdDispatchOperation * self,McdClientProxy * client)305 _mcd_dispatch_operation_dec_observers_pending (McdDispatchOperation *self,
306 McdClientProxy *client)
307 {
308 DEBUG ("%" G_GSIZE_FORMAT " -> %" G_GSIZE_FORMAT,
309 self->priv->observers_pending,
310 self->priv->observers_pending - 1);
311 g_return_if_fail (self->priv->observers_pending > 0);
312 self->priv->observers_pending--;
313
314 if (_mcd_client_proxy_get_delay_approvers (client))
315 self->priv->delay_approver_observers_pending--;
316
317 _mcd_dispatch_operation_check_finished (self);
318 _mcd_dispatch_operation_check_client_locks (self);
319 g_object_unref (self);
320 }
321
322 static void
_mcd_dispatch_operation_inc_ado_pending(McdDispatchOperation * self)323 _mcd_dispatch_operation_inc_ado_pending (McdDispatchOperation *self)
324 {
325 g_return_if_fail (self->priv->result == NULL);
326
327 g_object_ref (self);
328
329 DEBUG ("%" G_GSIZE_FORMAT " -> %" G_GSIZE_FORMAT,
330 self->priv->ado_pending,
331 self->priv->ado_pending + 1);
332 self->priv->ado_pending++;
333 }
334
335 static void
_mcd_dispatch_operation_dec_ado_pending(McdDispatchOperation * self)336 _mcd_dispatch_operation_dec_ado_pending (McdDispatchOperation *self)
337 {
338 DEBUG ("%" G_GSIZE_FORMAT " -> %" G_GSIZE_FORMAT,
339 self->priv->ado_pending,
340 self->priv->ado_pending - 1);
341 g_return_if_fail (self->priv->ado_pending > 0);
342 self->priv->ado_pending--;
343
344 _mcd_dispatch_operation_check_finished (self);
345
346 if (self->priv->ado_pending == 0 && !self->priv->accepted_by_an_approver)
347 {
348 DEBUG ("No approver accepted the channel; considering it to be "
349 "approved");
350 g_queue_push_tail (self->priv->approvals,
351 approval_new (APPROVAL_TYPE_NO_APPROVERS));
352 }
353
354 _mcd_dispatch_operation_check_client_locks (self);
355
356 g_object_unref (self);
357 }
358
359 gboolean
_mcd_dispatch_operation_get_cancelled(McdDispatchOperation * self)360 _mcd_dispatch_operation_get_cancelled (McdDispatchOperation *self)
361 {
362 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), FALSE);
363 return self->priv->cancelled;
364 }
365
366 gboolean
_mcd_dispatch_operation_is_internal(McdDispatchOperation * self)367 _mcd_dispatch_operation_is_internal (McdDispatchOperation *self)
368 {
369 GStrv handlers = self->priv->possible_handlers;
370
371 return (handlers != NULL && !tp_strdiff (CDO_INTERNAL_HANDLER, *handlers));
372 }
373
374 static inline gboolean
_mcd_dispatch_operation_is_approved(McdDispatchOperation * self)375 _mcd_dispatch_operation_is_approved (McdDispatchOperation *self)
376 {
377 return (!self->priv->needs_approval ||
378 !g_queue_is_empty (self->priv->approvals));
379 }
380
381 static gboolean _mcd_dispatch_operation_try_next_handler (
382 McdDispatchOperation *self);
383 static void _mcd_dispatch_operation_close_as_undispatchable (
384 McdDispatchOperation *self, const GError *error);
385 static gboolean mcd_dispatch_operation_idle_run_approvers (gpointer p);
386 static void mcd_dispatch_operation_set_channel_handled_by (
387 McdDispatchOperation *self, McdChannel *channel, const gchar *unique_name,
388 const gchar *well_known_name);
389 static gboolean _mcd_dispatch_operation_handlers_can_bypass_approval (
390 McdDispatchOperation *self);
391 static gboolean _mcd_dispatch_operation_handlers_can_bypass_observers (
392 McdDispatchOperation *self);
393
394 static void
_mcd_dispatch_operation_check_client_locks(McdDispatchOperation * self)395 _mcd_dispatch_operation_check_client_locks (McdDispatchOperation *self)
396 {
397 Approval *approval;
398 guint approver_event_id = 0;
399
400 if (!self->priv->invoked_observers_if_needed)
401 {
402 DEBUG ("waiting for Observers to be called");
403 return;
404 }
405
406 if (self->priv->plugins_pending > 0)
407 {
408 DEBUG ("waiting for plugins to stop delaying");
409 return;
410 }
411
412 /* Check whether plugins' requests to close channels later should be
413 * honoured. We want to do this before running Approvers (if any). */
414 if (self->priv->observers_pending == 0 &&
415 !self->priv->did_post_observer_actions)
416 {
417 _mcd_plugin_dispatch_operation_observers_finished (
418 self->priv->plugin_api);
419 self->priv->did_post_observer_actions = TRUE;
420 }
421
422 /* If nobody is bypassing approval, then we want to run approvers as soon
423 * as possible, without waiting for observers, to improve responsiveness.
424 * (The regression test dispatcher/exploding-bundles.py asserts that we
425 * do this.)
426 *
427 * However, if a handler bypasses approval, we must wait til the observers
428 * return, then run that handler, then proceed with the other handlers. */
429 if (!self->priv->tried_handlers_before_approval &&
430 !_mcd_dispatch_operation_handlers_can_bypass_approval (self)
431 && self->priv->delay_approver_observers_pending == 0
432 && self->priv->channel != NULL &&
433 ! _mcd_plugin_dispatch_operation_will_terminate (
434 self->priv->plugin_api))
435 {
436 self->priv->tried_handlers_before_approval = TRUE;
437
438 approver_event_id = g_idle_add_full (G_PRIORITY_HIGH,
439 mcd_dispatch_operation_idle_run_approvers,
440 g_object_ref (self), g_object_unref);
441 }
442
443 /* we may not continue until we've called all the Observers, and they've
444 * all replied "I'm ready" */
445 if (self->priv->observers_pending > 0)
446 {
447 return;
448 }
449
450 /* if we've called the first Approver, we may not continue until we've
451 * called them all, and they all replied "I'm ready" */
452 if (self->priv->ado_pending > 0)
453 {
454 DEBUG ("waiting for AddDispatchOperation to return");
455 return;
456 }
457
458 /* if we've called one Handler, we may not continue until it responds
459 * with an error */
460 if (self->priv->trying_handler != NULL)
461 {
462 DEBUG ("waiting for handler_is_suitable or HandleChannels to return");
463 return;
464 }
465
466 /* if a handler has claimed or accepted the channel, we have nothing to
467 * do */
468 if (self->priv->result != NULL)
469 {
470 DEBUG ("already finished (or finishing): %s",
471 self->priv->result->message);
472 return;
473 }
474
475 /* If we're only meant to be observing, do nothing */
476 if (self->priv->observe_only)
477 {
478 DEBUG ("only observing");
479 return;
480 }
481
482 if (_mcd_dispatch_operation_is_internal (self))
483 {
484 DEBUG ("Invoking internal handlers for requests");
485 if (self->priv->channel != NULL)
486 {
487 McdChannel *channel = self->priv->channel;
488 McdRequest *request = _mcd_channel_get_request (channel);
489
490 if (request != NULL)
491 {
492 DEBUG ("Internal handler for request channel");
493 _mcd_handler_map_set_channel_handled_internally (
494 self->priv->handler_map,
495 mcd_channel_get_tp_channel (channel),
496 _mcd_dispatch_operation_get_account_path (self));
497 _mcd_request_handle_internally (request, channel, TRUE);
498 }
499 }
500
501 /* The rest of this function deals with externally handled requests: *
502 * Since these requests were internal, we need not trouble ourselves *
503 * further (and infact would probably trigger errors if we tried) */
504 return;
505 }
506
507 /* If there are no potential handlers, the story ends here: we don't
508 * want to run approvers in this case */
509 if (self->priv->possible_handlers == NULL)
510 {
511 GError incapable = { TP_ERROR, TP_ERROR_NOT_CAPABLE,
512 "No possible handlers, giving up" };
513
514 DEBUG ("%s", incapable.message);
515 _mcd_dispatch_operation_close_as_undispatchable (self, &incapable);
516 return;
517 }
518
519 approval = g_queue_peek_head (self->priv->approvals);
520
521 /* if we've been claimed, respond, then do not call HandleChannels */
522 if (approval != NULL && approval->type == APPROVAL_TYPE_CLAIM)
523 {
524 gchar *caller = dbus_g_method_get_sender (approval->context);
525
526 /* remove this approval from the list, so it won't be treated as a
527 * failure */
528 g_queue_pop_head (self->priv->approvals);
529
530 if (self->priv->channel != NULL)
531 {
532 McdChannel *channel = self->priv->channel;
533
534 mcd_dispatch_operation_set_channel_handled_by (self, channel,
535 caller, NULL);
536 }
537
538 DEBUG ("Replying to Claim call from %s", caller);
539
540 tp_svc_channel_dispatch_operation_return_from_claim (
541 approval->context);
542 approval->context = NULL;
543
544 _mcd_dispatch_operation_finish (self, TP_ERROR, TP_ERROR_NOT_YOURS,
545 "Channel successfully claimed by %s",
546 caller);
547 g_free (caller);
548
549 if (approver_event_id > 0)
550 {
551 DEBUG ("Cancelling call to approvers as dispatch operation has been Claimed");
552 g_source_remove (approver_event_id);
553 }
554
555 approval_free (approval);
556 return;
557 }
558 else if (approval != NULL && approval->type == APPROVAL_TYPE_HANDLE_WITH)
559 {
560 /* We set this to TRUE so that the handlers are called. */
561 self->priv->invoked_approvers_if_needed = TRUE;
562
563 if (approver_event_id > 0)
564 {
565 DEBUG ("Cancelling call to approvers as dispatch operation has been HandledWith'd");
566 g_source_remove (approver_event_id);
567 }
568 }
569
570 if (self->priv->invoked_approvers_if_needed)
571 {
572 if (_mcd_dispatch_operation_is_approved (self))
573 {
574 DEBUG ("trying next handler");
575
576 if (!_mcd_dispatch_operation_try_next_handler (self))
577 {
578 GError incapable = { TP_ERROR, TP_ERROR_NOT_CAPABLE,
579 "No possible handler still exists, giving up" };
580
581 DEBUG ("ran out of handlers");
582 _mcd_dispatch_operation_close_as_undispatchable (self,
583 &incapable);
584 }
585 }
586 else
587 {
588 DEBUG ("waiting for approval");
589 }
590 }
591 else if (!self->priv->tried_handlers_before_approval)
592 {
593 DEBUG ("trying next pre-approval handler");
594
595 if (!_mcd_dispatch_operation_try_next_handler (self))
596 {
597 DEBUG ("ran out of pre-approval handlers");
598
599 self->priv->tried_handlers_before_approval = TRUE;
600
601 g_idle_add_full (G_PRIORITY_HIGH,
602 mcd_dispatch_operation_idle_run_approvers,
603 g_object_ref (self), g_object_unref);
604 }
605 }
606 }
607
608 enum
609 {
610 PROP_0,
611 PROP_CHANNEL,
612 PROP_CLIENT_REGISTRY,
613 PROP_HANDLER_MAP,
614 PROP_POSSIBLE_HANDLERS,
615 PROP_NEEDS_APPROVAL,
616 PROP_OBSERVE_ONLY,
617 };
618
619 /*
620 * _mcd_dispatch_operation_get_connection_path:
621 * @self: the #McdDispatchOperation.
622 *
623 * Returns: the D-Bus object path of the Connection associated with @self,
624 * or "/" if none.
625 */
626 const gchar *
_mcd_dispatch_operation_get_connection_path(McdDispatchOperation * self)627 _mcd_dispatch_operation_get_connection_path (McdDispatchOperation *self)
628 {
629 const gchar *path;
630
631 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), "/");
632
633 if (self->priv->connection == NULL)
634 return "/";
635
636 path = mcd_connection_get_object_path (self->priv->connection);
637
638 g_return_val_if_fail (path != NULL, "/");
639
640 return path;
641 }
642
643 static void
get_connection(TpSvcDBusProperties * self,const gchar * name,GValue * value)644 get_connection (TpSvcDBusProperties *self, const gchar *name, GValue *value)
645 {
646 McdDispatchOperationPrivate *priv = MCD_DISPATCH_OPERATION_PRIV (self);
647
648 DEBUG ("called for %s", priv->unique_name);
649 g_value_init (value, DBUS_TYPE_G_OBJECT_PATH);
650 g_value_set_boxed (value,
651 _mcd_dispatch_operation_get_connection_path (
652 MCD_DISPATCH_OPERATION (self)));
653 }
654
655 const gchar *
_mcd_dispatch_operation_get_cm_name(McdDispatchOperation * self)656 _mcd_dispatch_operation_get_cm_name (McdDispatchOperation *self)
657 {
658 const gchar *ret;
659
660 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), NULL);
661 g_return_val_if_fail (self->priv->account != NULL, NULL);
662 ret = mcd_account_get_manager_name (self->priv->account);
663 g_return_val_if_fail (ret != NULL, NULL);
664 return ret;
665 }
666
667 const gchar *
_mcd_dispatch_operation_get_protocol(McdDispatchOperation * self)668 _mcd_dispatch_operation_get_protocol (McdDispatchOperation *self)
669 {
670 const gchar *ret;
671
672 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), NULL);
673 g_return_val_if_fail (self->priv->account != NULL, NULL);
674 ret = mcd_account_get_protocol_name (self->priv->account);
675 g_return_val_if_fail (ret != NULL, NULL);
676 return ret;
677 }
678
679 /*
680 * _mcd_dispatch_operation_get_account_path:
681 * @operation: the #McdDispatchOperation.
682 *
683 * Returns: the D-Bus object path of the Account associated with @operation,
684 * or "/" if none.
685 */
686 const gchar *
_mcd_dispatch_operation_get_account_path(McdDispatchOperation * self)687 _mcd_dispatch_operation_get_account_path (McdDispatchOperation *self)
688 {
689 const gchar *path;
690
691 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), "/");
692
693 if (self->priv->account == NULL)
694 return "/";
695
696 path = mcd_account_get_object_path (self->priv->account);
697
698 g_return_val_if_fail (path != NULL, "/");
699
700 return path;
701 }
702
703 static void
get_account(TpSvcDBusProperties * self,const gchar * name,GValue * value)704 get_account (TpSvcDBusProperties *self, const gchar *name, GValue *value)
705 {
706 g_value_init (value, DBUS_TYPE_G_OBJECT_PATH);
707 g_value_set_boxed (value,
708 _mcd_dispatch_operation_get_account_path
709 (MCD_DISPATCH_OPERATION (self)));
710 }
711
712 static void
get_channels(TpSvcDBusProperties * iface,const gchar * name,GValue * value)713 get_channels (TpSvcDBusProperties *iface, const gchar *name, GValue *value)
714 {
715 McdDispatchOperation *self = MCD_DISPATCH_OPERATION (iface);
716
717 DEBUG ("called for %s", self->priv->unique_name);
718
719 g_value_init (value, TP_ARRAY_TYPE_CHANNEL_DETAILS_LIST);
720
721 if (self->priv->channel == NULL)
722 {
723 g_value_take_boxed (value, g_ptr_array_sized_new (0));
724 return;
725 }
726
727 g_value_take_boxed (value,
728 _mcd_tp_channel_details_build_from_tp_chan (
729 mcd_channel_get_tp_channel (self->priv->channel)));
730 }
731
732 static void
get_possible_handlers(TpSvcDBusProperties * self,const gchar * name,GValue * value)733 get_possible_handlers (TpSvcDBusProperties *self, const gchar *name,
734 GValue *value)
735 {
736 McdDispatchOperationPrivate *priv = MCD_DISPATCH_OPERATION_PRIV (self);
737
738 DEBUG ("called for %s", priv->unique_name);
739 g_value_init (value, G_TYPE_STRV);
740 g_value_set_boxed (value, priv->possible_handlers);
741 }
742
743
744 static const McdDBusProp dispatch_operation_properties[] = {
745 { "Interfaces", NULL, mcd_dbus_get_interfaces },
746 { "Connection", NULL, get_connection },
747 { "Account", NULL, get_account },
748 { "Channels", NULL, get_channels },
749 { "PossibleHandlers", NULL, get_possible_handlers },
750 { 0 },
751 };
752
753 static void
properties_iface_init(TpSvcDBusPropertiesClass * iface,gpointer iface_data)754 properties_iface_init (TpSvcDBusPropertiesClass *iface, gpointer iface_data)
755 {
756 #define IMPLEMENT(x) tp_svc_dbus_properties_implement_##x (\
757 iface, dbusprop_##x)
758 IMPLEMENT(set);
759 IMPLEMENT(get);
760 IMPLEMENT(get_all);
761 #undef IMPLEMENT
762 }
763
764 static void
mcd_dispatch_operation_set_channel_handled_by(McdDispatchOperation * self,McdChannel * channel,const gchar * unique_name,const gchar * well_known_name)765 mcd_dispatch_operation_set_channel_handled_by (McdDispatchOperation *self,
766 McdChannel *channel,
767 const gchar *unique_name,
768 const gchar *well_known_name)
769 {
770 TpChannel *tp_channel;
771
772 g_assert (unique_name != NULL);
773
774 tp_channel = mcd_channel_get_tp_channel (channel);
775 g_return_if_fail (tp_channel != NULL);
776
777 _mcd_channel_set_status (channel, MCD_CHANNEL_STATUS_DISPATCHED);
778
779 _mcd_handler_map_set_channel_handled (self->priv->handler_map,
780 tp_channel, unique_name, well_known_name,
781 _mcd_dispatch_operation_get_account_path (self));
782 }
783
784 static void
mcd_dispatch_operation_actually_finish(McdDispatchOperation * self)785 mcd_dispatch_operation_actually_finish (McdDispatchOperation *self)
786 {
787 g_object_ref (self);
788
789 DEBUG ("%s/%p: finished", self->priv->unique_name, self);
790 tp_svc_channel_dispatch_operation_emit_finished (self);
791
792 _mcd_dispatch_operation_check_client_locks (self);
793
794 g_object_unref (self);
795 }
796
797 static void
_mcd_dispatch_operation_finish(McdDispatchOperation * operation,GQuark domain,gint code,const gchar * format,...)798 _mcd_dispatch_operation_finish (McdDispatchOperation *operation,
799 GQuark domain, gint code,
800 const gchar *format, ...)
801 {
802 McdDispatchOperationPrivate *priv = operation->priv;
803 Approval *approval;
804 const gchar *successful_handler = NULL;
805 va_list ap;
806
807 if (priv->successful_handler != NULL)
808 {
809 successful_handler = tp_proxy_get_bus_name (priv->successful_handler);
810 }
811
812 if (priv->result != NULL)
813 {
814 DEBUG ("already finished (or about to): %s", priv->result->message);
815 return;
816 }
817
818 va_start (ap, format);
819 priv->result = g_error_new_valist (domain, code, format, ap);
820 va_end (ap);
821 DEBUG ("Result: %s", priv->result->message);
822
823 for (approval = g_queue_pop_head (priv->approvals);
824 approval != NULL;
825 approval = g_queue_pop_head (priv->approvals))
826 {
827 gchar *caller;
828
829 switch (approval->type)
830 {
831 case APPROVAL_TYPE_CLAIM:
832 /* someone else got it - either another Claim() or a handler */
833 g_assert (approval->context != NULL);
834 caller = dbus_g_method_get_sender (approval->context);
835 DEBUG ("denying Claim call from %s", caller);
836 g_free (caller);
837 dbus_g_method_return_error (approval->context, priv->result);
838 approval->context = NULL;
839 break;
840
841 case APPROVAL_TYPE_HANDLE_WITH:
842 g_assert (approval->context != NULL);
843
844 if (successful_handler != NULL)
845 {
846 /* Some Handler got it. If this Approver would have been
847 * happy with that Handler, then it succeeds, otherwise,
848 * it loses. */
849 if (approval->client_bus_name == NULL ||
850 !tp_strdiff (approval->client_bus_name,
851 successful_handler))
852 {
853 DEBUG ("successful HandleWith, channel went to %s",
854 successful_handler);
855
856 /* HandleWith and HandleWithTime both return void, so
857 * it's OK to not distinguish */
858 tp_svc_channel_dispatch_operation_return_from_handle_with (
859 approval->context);
860 approval->context = NULL;
861 }
862 else
863 {
864 DEBUG ("HandleWith -> NotYours: wanted %s but "
865 "%s got it instead", approval->client_bus_name,
866 successful_handler);
867 dbus_g_method_return_error (approval->context,
868 priv->result);
869 approval->context = NULL;
870 }
871 }
872 else
873 {
874 /* Handling finished for some other reason: perhaps the
875 * channel was claimed, or perhaps it closed or we were
876 * told to forget about it.
877 */
878 DEBUG ("HandleWith -> error: %s %d: %s",
879 g_quark_to_string (priv->result->domain),
880 priv->result->code, priv->result->message);
881 dbus_g_method_return_error (approval->context, priv->result);
882 approval->context = NULL;
883 }
884
885 break;
886
887 default:
888 { /* there shouldn't be a dbus context for these: */
889 g_assert (approval->context == NULL);
890 }
891 }
892
893 approval_free (approval);
894 }
895
896 if (mcd_dispatch_operation_may_signal_finished (operation))
897 {
898 DEBUG ("%s/%p has finished", priv->unique_name, operation);
899 mcd_dispatch_operation_actually_finish (operation);
900 }
901 else
902 {
903 DEBUG ("%s/%p not finishing just yet: "
904 "waiting for %" G_GSIZE_FORMAT " observers, "
905 "%" G_GSIZE_FORMAT " approvers",
906 priv->unique_name, operation,
907 priv->observers_pending, priv->ado_pending);
908 }
909 }
910
911 static gboolean mcd_dispatch_operation_check_handle_with (
912 McdDispatchOperation *self, const gchar *handler_name, GError **error);
913
914 static void
dispatch_operation_handle_with_time(TpSvcChannelDispatchOperation * cdo,const gchar * handler_name,gint64 user_action_timestamp,DBusGMethodInvocation * context)915 dispatch_operation_handle_with_time (TpSvcChannelDispatchOperation *cdo,
916 const gchar *handler_name,
917 gint64 user_action_timestamp,
918 DBusGMethodInvocation *context)
919 {
920 GError *error = NULL;
921 McdDispatchOperation *self = MCD_DISPATCH_OPERATION (cdo);
922
923 DEBUG ("%s/%p", self->priv->unique_name, self);
924
925 if (!mcd_dispatch_operation_check_handle_with (self, handler_name, &error))
926 {
927 dbus_g_method_return_error (context, error);
928 g_error_free (error);
929 return;
930 }
931
932 self->priv->handle_with_time = user_action_timestamp;
933
934 g_queue_push_tail (self->priv->approvals,
935 approval_new_handle_with (handler_name, context));
936 _mcd_dispatch_operation_check_client_locks (self);
937 }
938
939 static void
dispatch_operation_handle_with(TpSvcChannelDispatchOperation * cdo,const gchar * handler_name,DBusGMethodInvocation * context)940 dispatch_operation_handle_with (TpSvcChannelDispatchOperation *cdo,
941 const gchar *handler_name,
942 DBusGMethodInvocation *context)
943 {
944 dispatch_operation_handle_with_time (cdo, handler_name,
945 TP_USER_ACTION_TIME_NOT_USER_ACTION,
946 context);
947 }
948
949 typedef struct {
950 McdDispatchOperation *self;
951 DBusGMethodInvocation *context;
952 gsize handler_suitable_pending;
953 } ClaimAttempt;
954
955 static void
claim_attempt_resolve(ClaimAttempt * claim_attempt)956 claim_attempt_resolve (ClaimAttempt *claim_attempt)
957 {
958 if (claim_attempt->context != NULL)
959 {
960 g_queue_push_tail (claim_attempt->self->priv->approvals,
961 approval_new_claim (claim_attempt->context));
962 _mcd_dispatch_operation_check_client_locks (claim_attempt->self);
963 }
964
965 g_object_unref (claim_attempt->self);
966 g_slice_free (ClaimAttempt, claim_attempt);
967 }
968
969 static void
claim_attempt_suitability_cb(GObject * source,GAsyncResult * res,gpointer user_data)970 claim_attempt_suitability_cb (GObject *source,
971 GAsyncResult *res,
972 gpointer user_data)
973 {
974 ClaimAttempt *claim_attempt = user_data;
975 GError *error = NULL;
976
977 if (!mcp_dispatch_operation_policy_handler_is_suitable_finish (
978 MCP_DISPATCH_OPERATION_POLICY (source), res, &error))
979 {
980 if (claim_attempt->context != NULL)
981 dbus_g_method_return_error (claim_attempt->context, error);
982
983 claim_attempt->context = NULL;
984 g_error_free (error);
985 }
986
987 if (--claim_attempt->handler_suitable_pending == 0)
988 {
989 DEBUG ("all plugins have finished, resolving claim attempt");
990 claim_attempt_resolve (claim_attempt);
991 }
992 }
993
994 static void
dispatch_operation_claim(TpSvcChannelDispatchOperation * cdo,DBusGMethodInvocation * context)995 dispatch_operation_claim (TpSvcChannelDispatchOperation *cdo,
996 DBusGMethodInvocation *context)
997 {
998 McdDispatchOperation *self = MCD_DISPATCH_OPERATION (cdo);
999 ClaimAttempt *claim_attempt;
1000 gchar *sender = dbus_g_method_get_sender (context);
1001 McpDispatchOperation *plugin_api = MCP_DISPATCH_OPERATION (
1002 self->priv->plugin_api);
1003 const GList *p;
1004
1005 if (self->priv->result != NULL)
1006 {
1007
1008 DEBUG ("Giving error to %s: %s", sender, self->priv->result->message);
1009 dbus_g_method_return_error (context, self->priv->result);
1010 goto finally;
1011 }
1012
1013 claim_attempt = g_slice_new0 (ClaimAttempt);
1014 claim_attempt->self = g_object_ref (self);
1015 claim_attempt->context = context;
1016 claim_attempt->handler_suitable_pending = 0;
1017
1018 for (p = mcp_list_objects (); p != NULL; p = g_list_next (p))
1019 {
1020 if (MCP_IS_DISPATCH_OPERATION_POLICY (p->data))
1021 {
1022 McpDispatchOperationPolicy *plugin = p->data;
1023
1024 DEBUG ("%s: checking policy for %s",
1025 G_OBJECT_TYPE_NAME (plugin), sender);
1026
1027 claim_attempt->handler_suitable_pending++;
1028 mcp_dispatch_operation_policy_handler_is_suitable_async (plugin,
1029 NULL, sender, plugin_api,
1030 claim_attempt_suitability_cb,
1031 claim_attempt);
1032 }
1033 }
1034
1035 if (claim_attempt->handler_suitable_pending == 0)
1036 claim_attempt_resolve (claim_attempt);
1037
1038 finally:
1039 g_free (sender);
1040 }
1041
1042 static void
dispatch_operation_iface_init(TpSvcChannelDispatchOperationClass * iface,gpointer iface_data)1043 dispatch_operation_iface_init (TpSvcChannelDispatchOperationClass *iface,
1044 gpointer iface_data)
1045 {
1046 #define IMPLEMENT(x) tp_svc_channel_dispatch_operation_implement_##x (\
1047 iface, dispatch_operation_##x)
1048 IMPLEMENT(handle_with);
1049 IMPLEMENT(claim);
1050 IMPLEMENT(handle_with_time);
1051 #undef IMPLEMENT
1052 }
1053
1054 static void
create_object_path(McdDispatchOperationPrivate * priv)1055 create_object_path (McdDispatchOperationPrivate *priv)
1056 {
1057 static guint cpt = 0;
1058 priv->object_path =
1059 g_strdup_printf (MC_DISPATCH_OPERATION_DBUS_OBJECT_BASE "do%u",
1060 cpt++);
1061 priv->unique_name = priv->object_path +
1062 (sizeof (MC_DISPATCH_OPERATION_DBUS_OBJECT_BASE) - 1);
1063 }
1064
1065 static GObject *
mcd_dispatch_operation_constructor(GType type,guint n_params,GObjectConstructParam * params)1066 mcd_dispatch_operation_constructor (GType type, guint n_params,
1067 GObjectConstructParam *params)
1068 {
1069 GObjectClass *object_class =
1070 (GObjectClass *)_mcd_dispatch_operation_parent_class;
1071 GObject *object;
1072 McdDispatchOperation *operation;
1073 McdDispatchOperationPrivate *priv;
1074
1075 object = object_class->constructor (type, n_params, params);
1076 operation = MCD_DISPATCH_OPERATION (object);
1077
1078 g_return_val_if_fail (operation != NULL, NULL);
1079 priv = operation->priv;
1080
1081 if (!priv->client_registry || !priv->handler_map || !priv->channel)
1082 goto error;
1083
1084 if (priv->needs_approval && priv->observe_only)
1085 {
1086 g_critical ("observe_only => needs_approval must not be TRUE");
1087 goto error;
1088 }
1089
1090 create_object_path (priv);
1091
1092 DEBUG ("%s/%p: needs_approval=%c", priv->unique_name, object,
1093 priv->needs_approval ? 'T' : 'F');
1094
1095 if (priv->channel != NULL)
1096 {
1097 DEBUG ("Channel: %s",
1098 mcd_channel_get_object_path (priv->channel));
1099 g_assert (mcd_channel_get_account (priv->channel) ==
1100 priv->account);
1101 }
1102
1103 /* If approval is not needed, we don't appear on D-Bus (and approvers
1104 * don't run) */
1105 if (priv->needs_approval)
1106 {
1107 TpDBusDaemon *dbus_daemon;
1108 DBusGConnection *dbus_connection;
1109
1110 g_object_get (priv->client_registry,
1111 "dbus-daemon", &dbus_daemon,
1112 NULL);
1113
1114 /* can be NULL if we have fallen off the bus (in the real MC libdbus
1115 * would exit in this situation, but in the debug build, we stay
1116 * active briefly) */
1117 dbus_connection = tp_proxy_get_dbus_connection (dbus_daemon);
1118
1119 if (G_LIKELY (dbus_connection != NULL))
1120 dbus_g_connection_register_g_object (dbus_connection,
1121 priv->object_path, object);
1122
1123 g_object_unref (dbus_daemon);
1124 }
1125
1126 priv->plugin_api = _mcd_plugin_dispatch_operation_new (operation);
1127
1128 return object;
1129 error:
1130 g_object_unref (object);
1131 g_return_val_if_reached (NULL);
1132 }
1133
1134 static void _mcd_dispatch_operation_lose_channel (McdDispatchOperation *self,
1135 McdChannel *channel);
1136
1137 static void
mcd_dispatch_operation_channel_aborted_cb(McdChannel * channel,McdDispatchOperation * self)1138 mcd_dispatch_operation_channel_aborted_cb (McdChannel *channel,
1139 McdDispatchOperation *self)
1140 {
1141 const GError *error;
1142
1143 g_object_ref (self); /* FIXME: use a GObject closure or something */
1144
1145 DEBUG ("Channel %p aborted while in a dispatch operation", channel);
1146
1147 /* if it was a channel request, and it was cancelled, then the whole
1148 * dispatch operation should be aborted, closing any related channels */
1149 error = mcd_channel_get_error (channel);
1150 if (error && error->code == TP_ERROR_CANCELLED)
1151 self->priv->cancelled = TRUE;
1152
1153 _mcd_dispatch_operation_lose_channel (self, channel);
1154
1155 if (self->priv->channel == NULL)
1156 {
1157 DEBUG ("Nothing left in this context");
1158 }
1159
1160 g_object_unref (self);
1161 }
1162
1163 static void
mcd_dispatch_operation_set_property(GObject * obj,guint prop_id,const GValue * val,GParamSpec * pspec)1164 mcd_dispatch_operation_set_property (GObject *obj, guint prop_id,
1165 const GValue *val, GParamSpec *pspec)
1166 {
1167 McdDispatchOperation *operation = MCD_DISPATCH_OPERATION (obj);
1168 McdDispatchOperationPrivate *priv = operation->priv;
1169
1170 switch (prop_id)
1171 {
1172 case PROP_CLIENT_REGISTRY:
1173 g_assert (priv->client_registry == NULL); /* construct-only */
1174 priv->client_registry = MCD_CLIENT_REGISTRY (g_value_dup_object (val));
1175 break;
1176
1177 case PROP_HANDLER_MAP:
1178 g_assert (priv->handler_map == NULL); /* construct-only */
1179 priv->handler_map = MCD_HANDLER_MAP (g_value_dup_object (val));
1180 break;
1181
1182 case PROP_CHANNEL:
1183 /* because this is construct-only, we can assert that: */
1184 g_assert (priv->channel == NULL);
1185 g_assert (g_queue_is_empty (priv->approvals));
1186
1187 priv->channel = g_value_dup_object (val);
1188
1189 if (G_LIKELY (priv->channel))
1190 {
1191 const gchar *preferred_handler;
1192
1193 priv->connection = (McdConnection *)
1194 mcd_mission_get_parent (MCD_MISSION (priv->channel));
1195
1196 if (G_LIKELY (priv->connection))
1197 {
1198 g_object_ref (priv->connection);
1199 }
1200 else
1201 {
1202 /* shouldn't happen? */
1203 g_warning ("Channel has no Connection?!");
1204 }
1205
1206 /* if the channel is actually a channel request, get the
1207 * preferred handler from it */
1208 preferred_handler =
1209 _mcd_channel_get_request_preferred_handler (priv->channel);
1210
1211 if (preferred_handler != NULL &&
1212 g_str_has_prefix (preferred_handler, TP_CLIENT_BUS_NAME_BASE) &&
1213 tp_dbus_check_valid_bus_name (preferred_handler,
1214 TP_DBUS_NAME_TYPE_WELL_KNOWN,
1215 NULL))
1216 {
1217 DEBUG ("Extracted preferred handler: %s",
1218 preferred_handler);
1219 g_queue_push_tail (priv->approvals,
1220 approval_new_requested (preferred_handler));
1221 }
1222
1223 priv->account = mcd_channel_get_account (priv->channel);
1224
1225 if (G_LIKELY (priv->account != NULL))
1226 {
1227 g_object_ref (priv->account);
1228 }
1229 else
1230 {
1231 /* shouldn't happen? */
1232 g_warning ("Channel given to McdDispatchOperation has no "
1233 "Account?!");
1234 }
1235
1236 /* connect to its signals */
1237 g_signal_connect_after (priv->channel, "abort",
1238 G_CALLBACK (mcd_dispatch_operation_channel_aborted_cb),
1239 operation);
1240 }
1241 break;
1242
1243 case PROP_POSSIBLE_HANDLERS:
1244 g_assert (priv->possible_handlers == NULL);
1245 priv->possible_handlers = g_value_dup_boxed (val);
1246 break;
1247
1248 case PROP_NEEDS_APPROVAL:
1249 priv->needs_approval = g_value_get_boolean (val);
1250 break;
1251
1252 case PROP_OBSERVE_ONLY:
1253 priv->observe_only = g_value_get_boolean (val);
1254 break;
1255
1256 default:
1257 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
1258 break;
1259 }
1260 }
1261
1262 static void
mcd_dispatch_operation_get_property(GObject * obj,guint prop_id,GValue * val,GParamSpec * pspec)1263 mcd_dispatch_operation_get_property (GObject *obj, guint prop_id,
1264 GValue *val, GParamSpec *pspec)
1265 {
1266 McdDispatchOperationPrivate *priv = MCD_DISPATCH_OPERATION_PRIV (obj);
1267
1268 switch (prop_id)
1269 {
1270 case PROP_CLIENT_REGISTRY:
1271 g_value_set_object (val, priv->client_registry);
1272 break;
1273
1274 case PROP_HANDLER_MAP:
1275 g_value_set_object (val, priv->handler_map);
1276 break;
1277
1278 case PROP_POSSIBLE_HANDLERS:
1279 g_value_set_boxed (val, priv->possible_handlers);
1280 break;
1281
1282 case PROP_NEEDS_APPROVAL:
1283 g_value_set_boolean (val, priv->needs_approval);
1284 break;
1285
1286 case PROP_OBSERVE_ONLY:
1287 g_value_set_boolean (val, priv->observe_only);
1288 break;
1289
1290 default:
1291 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
1292 break;
1293 }
1294 }
1295
1296 static void
mcd_dispatch_operation_finalize(GObject * object)1297 mcd_dispatch_operation_finalize (GObject *object)
1298 {
1299 McdDispatchOperationPrivate *priv = MCD_DISPATCH_OPERATION_PRIV (object);
1300
1301 tp_clear_pointer (&priv->possible_handlers, g_strfreev);
1302 tp_clear_pointer (&priv->properties, g_hash_table_unref);
1303 tp_clear_pointer (&priv->failed_handlers, g_hash_table_unref);
1304 g_clear_error (&priv->result);
1305 g_free (priv->object_path);
1306
1307 G_OBJECT_CLASS (_mcd_dispatch_operation_parent_class)->finalize (object);
1308 }
1309
1310 static void
mcd_dispatch_operation_dispose(GObject * object)1311 mcd_dispatch_operation_dispose (GObject *object)
1312 {
1313 McdDispatchOperationPrivate *priv = MCD_DISPATCH_OPERATION_PRIV (object);
1314
1315 tp_clear_object (&priv->plugin_api);
1316 tp_clear_object (&priv->successful_handler);
1317
1318 if (priv->channel != NULL)
1319 {
1320 g_signal_handlers_disconnect_by_func (priv->channel,
1321 mcd_dispatch_operation_channel_aborted_cb, object);
1322 }
1323
1324 tp_clear_object (&priv->channel);
1325 tp_clear_object (&priv->lost_channel);
1326 tp_clear_object (&priv->account);
1327 tp_clear_object (&priv->handler_map);
1328 tp_clear_object (&priv->client_registry);
1329
1330 if (priv->approvals != NULL)
1331 {
1332 g_queue_foreach (priv->approvals, (GFunc) approval_free, NULL);
1333 tp_clear_pointer (&priv->approvals, g_queue_free);
1334 }
1335
1336 G_OBJECT_CLASS (_mcd_dispatch_operation_parent_class)->dispose (object);
1337 }
1338
1339 static void
_mcd_dispatch_operation_class_init(McdDispatchOperationClass * klass)1340 _mcd_dispatch_operation_class_init (McdDispatchOperationClass * klass)
1341 {
1342 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1343 g_type_class_add_private (object_class,
1344 sizeof (McdDispatchOperationPrivate));
1345
1346 object_class->constructor = mcd_dispatch_operation_constructor;
1347 object_class->dispose = mcd_dispatch_operation_dispose;
1348 object_class->finalize = mcd_dispatch_operation_finalize;
1349 object_class->set_property = mcd_dispatch_operation_set_property;
1350 object_class->get_property = mcd_dispatch_operation_get_property;
1351
1352 g_object_class_install_property (object_class, PROP_CLIENT_REGISTRY,
1353 g_param_spec_object ("client-registry", "Client registry",
1354 "Reference to a global registry of Telepathy clients",
1355 MCD_TYPE_CLIENT_REGISTRY,
1356 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
1357 G_PARAM_STATIC_STRINGS));
1358
1359 g_object_class_install_property (object_class, PROP_HANDLER_MAP,
1360 g_param_spec_object ("handler-map", "Handler map",
1361 "Reference to a global map from handled channels to handlers",
1362 MCD_TYPE_HANDLER_MAP,
1363 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
1364 G_PARAM_STATIC_STRINGS));
1365
1366 g_object_class_install_property (object_class, PROP_CHANNEL,
1367 g_param_spec_object ("channel", "Channel", "the channel to dispatch",
1368 MCD_TYPE_CHANNEL,
1369 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
1370 G_PARAM_STATIC_STRINGS));
1371
1372 g_object_class_install_property (object_class, PROP_POSSIBLE_HANDLERS,
1373 g_param_spec_boxed ("possible-handlers", "Possible handlers",
1374 "Well-known bus names of possible handlers",
1375 G_TYPE_STRV,
1376 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
1377 G_PARAM_STATIC_STRINGS));
1378
1379 g_object_class_install_property (object_class, PROP_NEEDS_APPROVAL,
1380 g_param_spec_boolean ("needs-approval", "Needs approval?",
1381 "TRUE if this CDO should run Approvers and "
1382 "appear on D-Bus", FALSE,
1383 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
1384 G_PARAM_STATIC_STRINGS));
1385
1386 g_object_class_install_property (object_class, PROP_OBSERVE_ONLY,
1387 g_param_spec_boolean ("observe-only", "Observe only?",
1388 "TRUE if this CDO should stop dispatching "
1389 "as soon as Observers have been run",
1390 FALSE,
1391 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
1392 G_PARAM_STATIC_STRINGS));
1393 }
1394
1395 static void
_mcd_dispatch_operation_init(McdDispatchOperation * operation)1396 _mcd_dispatch_operation_init (McdDispatchOperation *operation)
1397 {
1398 McdDispatchOperationPrivate *priv;
1399
1400 priv = G_TYPE_INSTANCE_GET_PRIVATE ((operation),
1401 MCD_TYPE_DISPATCH_OPERATION,
1402 McdDispatchOperationPrivate);
1403 operation->priv = priv;
1404 operation->priv->approvals = g_queue_new ();
1405
1406 /* initializes the interfaces */
1407 mcd_dbus_init_interfaces_instances (operation);
1408 }
1409
1410 /*
1411 * _mcd_dispatch_operation_new:
1412 * @client_registry: the client registry.
1413 * @handler_map: the handler map
1414 * @channel: the channel to dispatch
1415 * @possible_handlers: the bus names of possible handlers for this channel
1416 *
1417 * Creates a #McdDispatchOperation.
1418 */
1419 McdDispatchOperation *
_mcd_dispatch_operation_new(McdClientRegistry * client_registry,McdHandlerMap * handler_map,gboolean needs_approval,gboolean observe_only,McdChannel * channel,const gchar * const * possible_handlers)1420 _mcd_dispatch_operation_new (McdClientRegistry *client_registry,
1421 McdHandlerMap *handler_map,
1422 gboolean needs_approval,
1423 gboolean observe_only,
1424 McdChannel *channel,
1425 const gchar * const *possible_handlers)
1426 {
1427 gpointer *obj;
1428
1429 /* If we're only observing, then the channel was requested "behind MC's
1430 * back", so they can't need approval (i.e. observe_only implies
1431 * !needs_approval) */
1432 g_return_val_if_fail (!observe_only || !needs_approval, NULL);
1433 g_return_val_if_fail (MCD_IS_CHANNEL (channel), NULL);
1434
1435 obj = g_object_new (MCD_TYPE_DISPATCH_OPERATION,
1436 "client-registry", client_registry,
1437 "handler-map", handler_map,
1438 "channel", channel,
1439 "possible-handlers", possible_handlers,
1440 "needs-approval", needs_approval,
1441 "observe-only", observe_only,
1442 NULL);
1443
1444 return MCD_DISPATCH_OPERATION (obj);
1445 }
1446
1447 /*
1448 * _mcd_dispatch_operation_get_path:
1449 * @operation: the #McdDispatchOperation.
1450 *
1451 * Returns: the D-Bus object path of @operation.
1452 */
1453 const gchar *
_mcd_dispatch_operation_get_path(McdDispatchOperation * operation)1454 _mcd_dispatch_operation_get_path (McdDispatchOperation *operation)
1455 {
1456 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (operation), NULL);
1457 return operation->priv->object_path;
1458 }
1459
1460 /*
1461 * _mcd_dispatch_operation_get_properties:
1462 * @operation: the #McdDispatchOperation.
1463 *
1464 * Gets the immutable properties of @operation.
1465 *
1466 * Returns: a #GHashTable with the operation properties. The reference count is
1467 * not incremented.
1468 */
1469 GHashTable *
_mcd_dispatch_operation_get_properties(McdDispatchOperation * operation)1470 _mcd_dispatch_operation_get_properties (McdDispatchOperation *operation)
1471 {
1472 McdDispatchOperationPrivate *priv;
1473
1474 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (operation), NULL);
1475 priv = operation->priv;
1476 if (!priv->properties)
1477 {
1478 const McdDBusProp *property;
1479
1480 priv->properties =
1481 g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1482 (GDestroyNotify)tp_g_value_slice_free);
1483
1484 for (property = dispatch_operation_properties;
1485 property->name != NULL;
1486 property++)
1487 {
1488 GValue *value;
1489 gchar *name;
1490
1491 if (!property->getprop) continue;
1492
1493 /* The Channels property is mutable, so cannot be returned
1494 * here */
1495 if (!tp_strdiff (property->name, "Channels")) continue;
1496
1497 value = g_slice_new0 (GValue);
1498 property->getprop ((TpSvcDBusProperties *)operation,
1499 property->name, value);
1500 name = g_strconcat (TP_IFACE_CHANNEL_DISPATCH_OPERATION, ".",
1501 property->name, NULL);
1502 g_hash_table_insert (priv->properties, name, value);
1503 }
1504 }
1505 return priv->properties;
1506 }
1507
1508 gboolean
_mcd_dispatch_operation_needs_approval(McdDispatchOperation * self)1509 _mcd_dispatch_operation_needs_approval (McdDispatchOperation *self)
1510 {
1511 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), FALSE);
1512
1513 return self->priv->needs_approval;
1514 }
1515
1516 /*
1517 * _mcd_dispatch_operation_is_finished:
1518 * @self: the #McdDispatchOperation.
1519 *
1520 * Returns: %TRUE if the operation has finished, %FALSE otherwise.
1521 */
1522 gboolean
_mcd_dispatch_operation_is_finished(McdDispatchOperation * self)1523 _mcd_dispatch_operation_is_finished (McdDispatchOperation *self)
1524 {
1525 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), FALSE);
1526 /* if we want to finish, and we can, then we have */
1527 return (self->priv->result != NULL &&
1528 mcd_dispatch_operation_may_signal_finished (self));
1529 }
1530
1531 static gboolean
mcd_dispatch_operation_check_handle_with(McdDispatchOperation * self,const gchar * handler_name,GError ** error)1532 mcd_dispatch_operation_check_handle_with (McdDispatchOperation *self,
1533 const gchar *handler_name,
1534 GError **error)
1535 {
1536 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), FALSE);
1537
1538 if (self->priv->result != NULL)
1539 {
1540 DEBUG ("already finished, %s", self->priv->result->message);
1541 if (error != NULL)
1542 *error = g_error_copy (self->priv->result);
1543 return FALSE;
1544 }
1545
1546 if (!g_queue_is_empty (self->priv->approvals))
1547 {
1548 DEBUG ("NotYours: already finished or approved");
1549 g_set_error (error, TP_ERROR, TP_ERROR_NOT_YOURS,
1550 "CDO already finished or approved");
1551 return FALSE;
1552 }
1553
1554 if (handler_name == NULL || handler_name[0] == '\0')
1555 {
1556 /* no handler name given */
1557 return TRUE;
1558 }
1559
1560 if (!g_str_has_prefix (handler_name, TP_CLIENT_BUS_NAME_BASE) ||
1561 !tp_dbus_check_valid_bus_name (handler_name,
1562 TP_DBUS_NAME_TYPE_WELL_KNOWN, NULL))
1563 {
1564 DEBUG ("InvalidArgument: handler name %s is bad", handler_name);
1565 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
1566 "Invalid handler name");
1567 return FALSE;
1568 }
1569
1570 return TRUE;
1571 }
1572
1573 void
_mcd_dispatch_operation_approve(McdDispatchOperation * self,const gchar * preferred_handler)1574 _mcd_dispatch_operation_approve (McdDispatchOperation *self,
1575 const gchar *preferred_handler)
1576 {
1577 g_return_if_fail (MCD_IS_DISPATCH_OPERATION (self));
1578
1579 /* NULL-safety: treat both NULL and "" as "unspecified" */
1580 if (preferred_handler == NULL)
1581 preferred_handler = "";
1582
1583 DEBUG ("%s/%p (preferred handler: '%s')", self->priv->unique_name, self,
1584 preferred_handler);
1585
1586 if (!g_str_has_prefix (preferred_handler, TP_CLIENT_BUS_NAME_BASE) ||
1587 !tp_dbus_check_valid_bus_name (preferred_handler,
1588 TP_DBUS_NAME_TYPE_WELL_KNOWN, NULL))
1589 {
1590 DEBUG ("preferred handler name '%s' is bad, treating as unspecified",
1591 preferred_handler);
1592 preferred_handler = "";
1593 }
1594
1595 g_queue_push_tail (self->priv->approvals,
1596 approval_new_requested (preferred_handler));
1597
1598 _mcd_dispatch_operation_check_client_locks (self);
1599 }
1600
1601 static void
_mcd_dispatch_operation_lose_channel(McdDispatchOperation * self,McdChannel * channel)1602 _mcd_dispatch_operation_lose_channel (McdDispatchOperation *self,
1603 McdChannel *channel)
1604 {
1605 const gchar *object_path;
1606 const GError *error = NULL;
1607
1608 if (G_UNLIKELY (channel != self->priv->channel))
1609 {
1610 g_warning ("%s: apparently lost %p but my channel is %p",
1611 G_STRFUNC, channel, self->priv->channel);
1612 return;
1613 }
1614
1615 /* steal the reference */
1616 self->priv->channel = NULL;
1617
1618 object_path = mcd_channel_get_object_path (channel);
1619 error = mcd_channel_get_error (channel);
1620
1621 if (object_path == NULL)
1622 {
1623 /* This shouldn't happen, but McdChannel is twisty enough that I
1624 * can't be sure */
1625 g_critical ("McdChannel has already lost its TpChannel: %p",
1626 channel);
1627 }
1628 else if (!mcd_dispatch_operation_may_signal_finished (self))
1629 {
1630 /* We're still invoking approvers, so we're not allowed to talk
1631 * about it right now. Instead, save the signal for later. */
1632 DEBUG ("%s/%p not losing channel %s just yet: "
1633 "waiting for %" G_GSIZE_FORMAT " observers, "
1634 "%" G_GSIZE_FORMAT " approvers",
1635 self->priv->unique_name, self, object_path,
1636 self->priv->observers_pending, self->priv->ado_pending);
1637 g_assert (self->priv->lost_channel == NULL);
1638 self->priv->lost_channel = g_object_ref (channel);
1639 }
1640 else
1641 {
1642 gchar *error_name = _mcd_build_error_string (error);
1643
1644 DEBUG ("%s/%p losing channel %s: %s: %s",
1645 self->priv->unique_name, self, object_path, error_name,
1646 error->message);
1647 tp_svc_channel_dispatch_operation_emit_channel_lost (self, object_path,
1648 error_name,
1649 error->message);
1650 g_free (error_name);
1651 }
1652
1653 /* We previously stole this ref from self->priv->channel - drop it */
1654 g_object_unref (channel);
1655
1656 /* no channels left, so the CDO finishes (if it hasn't already) */
1657 _mcd_dispatch_operation_finish (self, error->domain, error->code,
1658 "%s", error->message);
1659 }
1660
1661 static void
_mcd_dispatch_operation_check_finished(McdDispatchOperation * self)1662 _mcd_dispatch_operation_check_finished (McdDispatchOperation *self)
1663 {
1664 if (mcd_dispatch_operation_may_signal_finished (self))
1665 {
1666 McdChannel *lost_channel = self->priv->lost_channel;
1667
1668 /* steal it */
1669 self->priv->lost_channel = NULL;
1670
1671 if (lost_channel != NULL)
1672 {
1673 const gchar *object_path = mcd_channel_get_object_path (
1674 lost_channel);
1675
1676 if (object_path == NULL)
1677 {
1678 /* This shouldn't happen, but McdChannel is twisty enough
1679 * that I can't be sure */
1680 g_critical ("McdChannel has already lost its TpChannel: %p",
1681 lost_channel);
1682 }
1683 else
1684 {
1685 const GError *error = mcd_channel_get_error (lost_channel);
1686 gchar *error_name = _mcd_build_error_string (error);
1687
1688 DEBUG ("%s/%p losing channel %s: %s: %s",
1689 self->priv->unique_name, self, object_path, error_name,
1690 error->message);
1691 tp_svc_channel_dispatch_operation_emit_channel_lost (self,
1692 object_path, error_name, error->message);
1693 g_free (error_name);
1694 }
1695
1696 g_object_unref (lost_channel);
1697 }
1698
1699 if (self->priv->result != NULL)
1700 {
1701 DEBUG ("%s/%p finished", self->priv->unique_name, self);
1702 mcd_dispatch_operation_actually_finish (self);
1703 }
1704 }
1705 else if (self->priv->result != NULL)
1706 {
1707 DEBUG ("%s/%p still unable to finish: "
1708 "waiting for %" G_GSIZE_FORMAT " observers, "
1709 "%" G_GSIZE_FORMAT " approvers",
1710 self->priv->unique_name, self,
1711 self->priv->observers_pending, self->priv->ado_pending);
1712 }
1713 }
1714
1715 static void
_mcd_dispatch_operation_set_handler_failed(McdDispatchOperation * self,const gchar * bus_name,const GError * error)1716 _mcd_dispatch_operation_set_handler_failed (McdDispatchOperation *self,
1717 const gchar *bus_name,
1718 const GError *error)
1719 {
1720 GList *iter, *next;
1721 gchar **handler;
1722
1723 if (self->priv->failed_handlers == NULL)
1724 {
1725 self->priv->failed_handlers = g_hash_table_new_full (g_str_hash,
1726 g_str_equal,
1727 g_free, NULL);
1728 }
1729
1730 /* the value is an arbitrary non-NULL pointer - the hash table itself
1731 * will do nicely */
1732 g_hash_table_insert (self->priv->failed_handlers, g_strdup (bus_name),
1733 self->priv->failed_handlers);
1734
1735 for (iter = g_queue_peek_head_link (self->priv->approvals);
1736 iter != NULL;
1737 iter = next)
1738 {
1739 Approval *approval = iter->data;
1740
1741 /* do this before we potentially free the list element */
1742 next = iter->next;
1743
1744 /* If this approval wanted the same handler that just failed, then
1745 * we can assume that's not going to happen. */
1746 if (approval->type == APPROVAL_TYPE_HANDLE_WITH &&
1747 !tp_strdiff (approval->client_bus_name, bus_name))
1748 {
1749 dbus_g_method_return_error (approval->context, (GError *) error);
1750 approval->context = NULL;
1751 approval_free (approval);
1752 g_queue_delete_link (self->priv->approvals, iter);
1753 }
1754 }
1755
1756 for (handler = self->priv->possible_handlers;
1757 handler != NULL && *handler != NULL;
1758 handler++)
1759 {
1760 if (g_hash_table_lookup (self->priv->failed_handlers, *handler)
1761 == NULL)
1762 {
1763 /* we'll try this one soon */
1764 return;
1765 }
1766 }
1767
1768 DEBUG ("All possible handlers failed: failing with the last error");
1769 _mcd_dispatch_operation_close_as_undispatchable (self, error);
1770 }
1771
1772 static gboolean
_mcd_dispatch_operation_get_handler_failed(McdDispatchOperation * self,const gchar * bus_name)1773 _mcd_dispatch_operation_get_handler_failed (McdDispatchOperation *self,
1774 const gchar *bus_name)
1775 {
1776 g_assert (MCD_IS_DISPATCH_OPERATION (self));
1777 g_assert (bus_name != NULL);
1778
1779 if (self->priv->failed_handlers == NULL)
1780 return FALSE;
1781
1782 return (g_hash_table_lookup (self->priv->failed_handlers, bus_name)
1783 != NULL);
1784 }
1785
1786 static gboolean
_mcd_dispatch_operation_handlers_can_bypass_approval(McdDispatchOperation * self)1787 _mcd_dispatch_operation_handlers_can_bypass_approval (
1788 McdDispatchOperation *self)
1789 {
1790 gchar **iter;
1791
1792 /* special case: internally handled request, not subject to approval */
1793 if (_mcd_dispatch_operation_is_internal (self))
1794 return TRUE;
1795
1796 /* special case: we don't have any handlers at all, so we don't want
1797 * approval - we're just going to fail */
1798 if (self->priv->possible_handlers == NULL)
1799 return TRUE;
1800
1801 for (iter = self->priv->possible_handlers;
1802 *iter != NULL;
1803 iter++)
1804 {
1805 McdClientProxy *handler = _mcd_client_registry_lookup (
1806 self->priv->client_registry, *iter);
1807
1808 /* If the best handler that still exists bypasses approval, then
1809 * we're going to bypass approval.
1810 *
1811 * Also, because handlers are sorted with the best ones first, and
1812 * handlers with BypassApproval are "better", we can be sure that if
1813 * we've found a handler that still exists and does not bypass
1814 * approval, no handler bypasses approval. */
1815 if (handler != NULL)
1816 {
1817 gboolean bypass = _mcd_client_proxy_get_bypass_approval (
1818 handler);
1819
1820 DEBUG ("%s has BypassApproval=%c", *iter, bypass ? 'T' : 'F');
1821 return bypass;
1822 }
1823 }
1824
1825 /* If no handler still exists, we don't bypass approval, although if that
1826 * happens we're basically doomed anyway. (unless this is an internal
1827 * request, in which case we should be ok) */
1828 return FALSE;
1829 }
1830
1831 /* this is analogous to *_can_bypass_handlers() method above */
1832 static gboolean
_mcd_dispatch_operation_handlers_can_bypass_observers(McdDispatchOperation * self)1833 _mcd_dispatch_operation_handlers_can_bypass_observers (
1834 McdDispatchOperation *self)
1835 {
1836 gchar **iter;
1837
1838 for (iter = self->priv->possible_handlers;
1839 iter != NULL && *iter != NULL;
1840 iter++)
1841 {
1842 McdClientProxy *handler = _mcd_client_registry_lookup (
1843 self->priv->client_registry, *iter);
1844
1845 if (handler != NULL)
1846 {
1847 gboolean bypass = _mcd_client_proxy_get_bypass_observers (
1848 handler);
1849
1850 DEBUG ("%s has BypassObservers=%c", *iter, bypass ? 'T' : 'F');
1851 return bypass;
1852 }
1853 }
1854
1855 return FALSE;
1856 }
1857
1858
1859 gboolean
_mcd_dispatch_operation_has_channel(McdDispatchOperation * self,McdChannel * channel)1860 _mcd_dispatch_operation_has_channel (McdDispatchOperation *self,
1861 McdChannel *channel)
1862 {
1863 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), FALSE);
1864
1865 return (self->priv->channel != NULL &&
1866 self->priv->channel == channel);
1867 }
1868
1869 McdChannel *
_mcd_dispatch_operation_peek_channel(McdDispatchOperation * self)1870 _mcd_dispatch_operation_peek_channel (McdDispatchOperation *self)
1871 {
1872 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), NULL);
1873
1874 return self->priv->channel;
1875 }
1876
1877 McdChannel *
_mcd_dispatch_operation_dup_channel(McdDispatchOperation * self)1878 _mcd_dispatch_operation_dup_channel (McdDispatchOperation *self)
1879 {
1880 g_return_val_if_fail (MCD_IS_DISPATCH_OPERATION (self), NULL);
1881
1882 if (self->priv->channel != NULL)
1883 return g_object_ref (self->priv->channel);
1884
1885 return NULL;
1886 }
1887
1888 static void
_mcd_dispatch_operation_handle_channels_cb(TpClient * client,const GError * error,gpointer user_data,GObject * weak G_GNUC_UNUSED)1889 _mcd_dispatch_operation_handle_channels_cb (TpClient *client,
1890 const GError *error,
1891 gpointer user_data,
1892 GObject *weak G_GNUC_UNUSED)
1893 {
1894 McdDispatchOperation *self = user_data;
1895
1896 if (error)
1897 {
1898 DEBUG ("error: %s", error->message);
1899
1900 _mcd_dispatch_operation_set_handler_failed (self,
1901 tp_proxy_get_bus_name (client), error);
1902 }
1903 else
1904 {
1905 /* FIXME: can channel ever be NULL here? */
1906 if (self->priv->channel != NULL)
1907 {
1908 McdChannel *channel = MCD_CHANNEL (self->priv->channel);
1909 const gchar *unique_name;
1910
1911 unique_name = _mcd_client_proxy_get_unique_name (MCD_CLIENT_PROXY (client));
1912
1913 /* This should always be false in practice - either we already know
1914 * the handler's unique name (because active handlers' unique names
1915 * are discovered before their handler filters), or the handler
1916 * is activatable and was not running, the handler filter came
1917 * from a .client file, and the bus daemon activated the handler
1918 * as a side-effect of HandleChannels (in which case
1919 * NameOwnerChanged should have already been emitted by the time
1920 * we got a reply to HandleChannels).
1921 *
1922 * We recover by whining to stderr and closing the channels, in the
1923 * interests of at least failing visibly.
1924 *
1925 * If dbus-glib exposed more of the details of the D-Bus message
1926 * passing system, then we could just look at the sender of the
1927 * reply and bypass this rubbish...
1928 */
1929 if (G_UNLIKELY (unique_name == NULL || unique_name[0] == '\0'))
1930 {
1931 g_warning ("Client %s returned successfully but doesn't "
1932 "exist? dbus-daemon bug suspected",
1933 tp_proxy_get_bus_name (client));
1934 g_warning ("Closing channel %s as a result",
1935 mcd_channel_get_object_path (channel));
1936 _mcd_channel_undispatchable (channel);
1937 }
1938 else
1939 {
1940 mcd_dispatch_operation_set_channel_handled_by (self, channel,
1941 unique_name, tp_proxy_get_bus_name (client));
1942 }
1943 }
1944
1945 /* emit Finished, if we haven't already; but first make a note of the
1946 * handler we used, so we can reply to all the HandleWith calls with
1947 * success or failure */
1948 self->priv->successful_handler = g_object_ref (client);
1949 _mcd_dispatch_operation_finish (self, TP_ERROR, TP_ERROR_NOT_YOURS,
1950 "Channel successfully handled by %s",
1951 tp_proxy_get_bus_name (client));
1952 }
1953
1954 tp_clear_object (&self->priv->trying_handler);
1955 _mcd_dispatch_operation_check_client_locks (self);
1956 }
1957
1958 static void
observe_channels_cb(TpClient * proxy,const GError * error,gpointer user_data,GObject * weak_object)1959 observe_channels_cb (TpClient *proxy, const GError *error,
1960 gpointer user_data, GObject *weak_object)
1961 {
1962 McdDispatchOperation *self = user_data;
1963
1964 /* we display the error just for debugging, but we don't really care */
1965 if (error)
1966 DEBUG ("Observer %s returned error: %s",
1967 tp_proxy_get_object_path (proxy), error->message);
1968 else
1969 DEBUG ("success from %s", tp_proxy_get_object_path (proxy));
1970
1971 _mcd_dispatch_operation_dec_observers_pending (self, MCD_CLIENT_PROXY (proxy));
1972 }
1973
1974 /*
1975 * @paths_out: (out) (transfer container) (element-type utf8):
1976 * Requests_Satisfied
1977 * @props_out: (out) (transfer container) (element-type utf8 GHashTable):
1978 * request-properties for Observer_Info or Handler_Info
1979 */
1980 static void
collect_satisfied_requests(McdChannel * channel,GPtrArray ** paths_out,GHashTable ** props_out)1981 collect_satisfied_requests (McdChannel *channel,
1982 GPtrArray **paths_out,
1983 GHashTable **props_out)
1984 {
1985 GHashTableIter it;
1986 gpointer path, value;
1987 GPtrArray *satisfied_requests;
1988 GHashTable *request_properties;
1989 GHashTable *reqs;
1990
1991 reqs = _mcd_channel_get_satisfied_requests (channel, NULL);
1992
1993 satisfied_requests = g_ptr_array_sized_new (g_hash_table_size (reqs));
1994 g_ptr_array_set_free_func (satisfied_requests, g_free);
1995
1996 request_properties = g_hash_table_new_full (g_str_hash, g_str_equal,
1997 g_free, (GDestroyNotify) g_hash_table_unref);
1998
1999 g_hash_table_iter_init (&it, reqs);
2000
2001 while (g_hash_table_iter_next (&it, &path, &value))
2002 {
2003 GHashTable *props;
2004
2005 g_ptr_array_add (satisfied_requests, g_strdup (path));
2006 props = _mcd_request_dup_immutable_properties (value);
2007 g_assert (props != NULL);
2008 g_hash_table_insert (request_properties, g_strdup (path), props);
2009 }
2010
2011 g_hash_table_unref (reqs);
2012
2013 if (paths_out != NULL)
2014 *paths_out = satisfied_requests;
2015 else
2016 g_ptr_array_unref (satisfied_requests);
2017
2018 if (props_out != NULL)
2019 *props_out = request_properties;
2020 else
2021 g_hash_table_unref (request_properties);
2022 }
2023
2024 static void
_mcd_dispatch_operation_run_observers(McdDispatchOperation * self)2025 _mcd_dispatch_operation_run_observers (McdDispatchOperation *self)
2026 {
2027 const gchar *dispatch_operation_path = "/";
2028 GHashTable *observer_info;
2029 GHashTableIter iter;
2030 gpointer client_p;
2031
2032 observer_info = tp_asv_new (NULL, NULL);
2033
2034 _mcd_client_registry_init_hash_iter (self->priv->client_registry, &iter);
2035
2036 while (g_hash_table_iter_next (&iter, NULL, &client_p))
2037 {
2038 McdClientProxy *client = MCD_CLIENT_PROXY (client_p);
2039 gboolean observed = FALSE;
2040 const gchar *account_path, *connection_path;
2041 GPtrArray *channels_array, *satisfied_requests;
2042 GHashTable *request_properties;
2043
2044 if (!tp_proxy_has_interface_by_id (client,
2045 TP_IFACE_QUARK_CLIENT_OBSERVER))
2046 continue;
2047
2048 if (self->priv->channel != NULL)
2049 {
2050 McdChannel *channel = MCD_CHANNEL (self->priv->channel);
2051 GVariant *properties;
2052
2053 properties = mcd_channel_dup_immutable_properties (channel);
2054 g_assert (properties != NULL);
2055
2056 if (_mcd_client_match_filters (properties,
2057 _mcd_client_proxy_get_observer_filters (client),
2058 FALSE))
2059 observed = TRUE;
2060
2061 g_variant_unref (properties);
2062 }
2063
2064 /* in particular this happens if there is no channel at all */
2065 if (!observed) continue;
2066
2067 /* build up the parameters and invoke the observer */
2068
2069 connection_path = _mcd_dispatch_operation_get_connection_path (self);
2070 account_path = _mcd_dispatch_operation_get_account_path (self);
2071
2072 /* TODO: there's room for optimization here: reuse the channels_array,
2073 * if the observed list is the same */
2074 channels_array = _mcd_tp_channel_details_build_from_tp_chan (
2075 mcd_channel_get_tp_channel (self->priv->channel));
2076
2077 collect_satisfied_requests (self->priv->channel, &satisfied_requests,
2078 &request_properties);
2079
2080 /* transfer ownership into observer_info */
2081 tp_asv_take_boxed (observer_info, "request-properties",
2082 TP_HASH_TYPE_OBJECT_IMMUTABLE_PROPERTIES_MAP,
2083 request_properties);
2084 request_properties = NULL;
2085
2086 if (_mcd_dispatch_operation_needs_approval (self))
2087 {
2088 dispatch_operation_path = _mcd_dispatch_operation_get_path (self);
2089 }
2090
2091 _mcd_dispatch_operation_inc_observers_pending (self, client);
2092
2093 DEBUG ("calling ObserveChannels on %s for CDO %p",
2094 tp_proxy_get_bus_name (client), self);
2095 tp_cli_client_observer_call_observe_channels (
2096 (TpClient *) client, -1,
2097 account_path, connection_path, channels_array,
2098 dispatch_operation_path, satisfied_requests, observer_info,
2099 observe_channels_cb,
2100 g_object_ref (self), g_object_unref, NULL);
2101
2102 g_ptr_array_unref (satisfied_requests);
2103
2104 _mcd_tp_channel_details_free (channels_array);
2105 }
2106
2107 g_hash_table_unref (observer_info);
2108 }
2109
2110 static void
add_dispatch_operation_cb(TpClient * proxy,const GError * error,gpointer user_data,GObject * weak_object)2111 add_dispatch_operation_cb (TpClient *proxy,
2112 const GError *error,
2113 gpointer user_data,
2114 GObject *weak_object)
2115 {
2116 McdDispatchOperation *self = user_data;
2117
2118 if (error)
2119 {
2120 DEBUG ("AddDispatchOperation %s (%p) on approver %s failed: "
2121 "%s",
2122 _mcd_dispatch_operation_get_path (self), self,
2123 tp_proxy_get_object_path (proxy), error->message);
2124 }
2125 else
2126 {
2127 DEBUG ("Approver %s accepted AddDispatchOperation %s (%p)",
2128 tp_proxy_get_object_path (proxy),
2129 _mcd_dispatch_operation_get_path (self), self);
2130
2131 if (!self->priv->accepted_by_an_approver)
2132 {
2133 self->priv->accepted_by_an_approver = TRUE;
2134 }
2135 }
2136
2137 /* If all approvers fail to add the DO, then we behave as if no
2138 * approver was registered: i.e., we continue dispatching. If at least
2139 * one approver accepted it, then we can still continue dispatching,
2140 * since it will be stalled until an approval is received. */
2141 _mcd_dispatch_operation_dec_ado_pending (self);
2142 }
2143
2144 static void
_mcd_dispatch_operation_run_approvers(McdDispatchOperation * self)2145 _mcd_dispatch_operation_run_approvers (McdDispatchOperation *self)
2146 {
2147 GHashTableIter iter;
2148 gpointer client_p;
2149
2150 /* we temporarily increment this count and decrement it at the end of the
2151 * function, to make sure it won't become 0 while we are still invoking
2152 * approvers */
2153 _mcd_dispatch_operation_inc_ado_pending (self);
2154
2155 _mcd_client_registry_init_hash_iter (self->priv->client_registry, &iter);
2156 while (g_hash_table_iter_next (&iter, NULL, &client_p))
2157 {
2158 McdClientProxy *client = MCD_CLIENT_PROXY (client_p);
2159 GPtrArray *channel_details;
2160 const gchar *dispatch_operation;
2161 GHashTable *properties;
2162 gboolean matched = FALSE;
2163
2164 if (!tp_proxy_has_interface_by_id (client,
2165 TP_IFACE_QUARK_CLIENT_APPROVER))
2166 continue;
2167
2168 if (self->priv->channel != NULL)
2169 {
2170 McdChannel *channel = MCD_CHANNEL (self->priv->channel);
2171 GVariant *channel_properties;
2172
2173 channel_properties = mcd_channel_dup_immutable_properties (channel);
2174 g_assert (channel_properties != NULL);
2175
2176 if (_mcd_client_match_filters (channel_properties,
2177 _mcd_client_proxy_get_approver_filters (client),
2178 FALSE))
2179 {
2180 matched = TRUE;
2181 }
2182
2183 g_variant_unref (channel_properties);
2184 }
2185
2186 /* in particular, after this point, self->priv->channel can't
2187 * be NULL */
2188 if (!matched) continue;
2189
2190 dispatch_operation = _mcd_dispatch_operation_get_path (self);
2191 properties = _mcd_dispatch_operation_get_properties (self);
2192 channel_details = _mcd_tp_channel_details_build_from_tp_chan (
2193 mcd_channel_get_tp_channel (self->priv->channel));
2194
2195 DEBUG ("Calling AddDispatchOperation on approver %s for CDO %s @ %p",
2196 tp_proxy_get_bus_name (client), dispatch_operation, self);
2197
2198 _mcd_dispatch_operation_inc_ado_pending (self);
2199
2200 tp_cli_client_approver_call_add_dispatch_operation (
2201 (TpClient *) client, -1,
2202 channel_details, dispatch_operation, properties,
2203 add_dispatch_operation_cb,
2204 g_object_ref (self), g_object_unref, NULL);
2205
2206 g_boxed_free (TP_ARRAY_TYPE_CHANNEL_DETAILS_LIST, channel_details);
2207 }
2208
2209 /* This matches the approvers count set to 1 at the beginning of the
2210 * function */
2211 _mcd_dispatch_operation_dec_ado_pending (self);
2212 }
2213
2214 static gboolean
mcd_dispatch_operation_idle_run_approvers(gpointer p)2215 mcd_dispatch_operation_idle_run_approvers (gpointer p)
2216 {
2217 McdDispatchOperation *self = p;
2218
2219 if (_mcd_dispatch_operation_needs_approval (self))
2220 {
2221 if (!_mcd_dispatch_operation_is_approved (self))
2222 _mcd_dispatch_operation_run_approvers (self);
2223 }
2224
2225 self->priv->invoked_approvers_if_needed = TRUE;
2226 _mcd_dispatch_operation_check_client_locks (self);
2227
2228 return FALSE;
2229 }
2230
2231 /* After this function is called, the McdDispatchOperation takes over its
2232 * own life-cycle, and the caller needn't hold an explicit reference to it. */
2233 void
_mcd_dispatch_operation_run_clients(McdDispatchOperation * self)2234 _mcd_dispatch_operation_run_clients (McdDispatchOperation *self)
2235 {
2236 g_object_ref (self);
2237 DEBUG ("%s %p", self->priv->unique_name, self);
2238
2239 if (self->priv->channel != NULL)
2240 {
2241 const GList *mini_plugins;
2242
2243 if (_mcd_dispatch_operation_handlers_can_bypass_observers (self))
2244 {
2245 DEBUG ("Bypassing observers");
2246 }
2247 else
2248 {
2249 DEBUG ("Running observers");
2250 _mcd_dispatch_operation_run_observers (self);
2251 }
2252
2253 for (mini_plugins = mcp_list_objects ();
2254 mini_plugins != NULL;
2255 mini_plugins = mini_plugins->next)
2256 {
2257 if (MCP_IS_DISPATCH_OPERATION_POLICY (mini_plugins->data))
2258 {
2259 mcp_dispatch_operation_policy_check (mini_plugins->data,
2260 MCP_DISPATCH_OPERATION (self->priv->plugin_api));
2261 }
2262 }
2263 }
2264
2265 DEBUG ("All necessary observers invoked");
2266 self->priv->invoked_observers_if_needed = TRUE;
2267
2268 DEBUG ("Checking finished/locks");
2269 _mcd_dispatch_operation_check_finished (self);
2270 _mcd_dispatch_operation_check_client_locks (self);
2271
2272 g_object_unref (self);
2273 }
2274
2275 /*
2276 * mcd_dispatch_operation_handle_channels:
2277 * @self: the dispatch operation
2278 * @handler: the selected handler
2279 *
2280 * Invoke the handler for the given channels.
2281 */
2282 static void
mcd_dispatch_operation_handle_channels(McdDispatchOperation * self)2283 mcd_dispatch_operation_handle_channels (McdDispatchOperation *self)
2284 {
2285 GList *channels = NULL;
2286 GHashTable *handler_info;
2287 GHashTable *request_properties;
2288
2289 g_assert (self->priv->trying_handler != NULL);
2290
2291 if (self->priv->handler_unsuitable != NULL)
2292 {
2293 GError *tmp = self->priv->handler_unsuitable;
2294
2295 /* move the error out of the way first, in case the callback
2296 * tries a different handler which will also want to check
2297 * handler_unsuitable */
2298 self->priv->handler_unsuitable = NULL;
2299
2300 _mcd_dispatch_operation_handle_channels_cb (
2301 (TpClient *) self->priv->trying_handler,
2302 tmp, self, NULL);
2303 g_error_free (tmp);
2304
2305 return;
2306 }
2307
2308 /* FIXME: it shouldn't be possible to get here without a channel */
2309 if (self->priv->channel != NULL)
2310 {
2311 collect_satisfied_requests (self->priv->channel, NULL,
2312 &request_properties);
2313 channels = g_list_prepend (NULL, self->priv->channel);
2314 }
2315 else
2316 {
2317 request_properties = g_hash_table_new_full (g_str_hash,
2318 g_str_equal, g_free, (GDestroyNotify) g_hash_table_unref);
2319 }
2320
2321 handler_info = tp_asv_new (NULL, NULL);
2322 tp_asv_take_boxed (handler_info, "request-properties",
2323 TP_HASH_TYPE_OBJECT_IMMUTABLE_PROPERTIES_MAP, request_properties);
2324 request_properties = NULL;
2325
2326 _mcd_client_proxy_handle_channels (self->priv->trying_handler,
2327 -1, channels, self->priv->handle_with_time,
2328 handler_info, _mcd_dispatch_operation_handle_channels_cb,
2329 g_object_ref (self), g_object_unref, NULL);
2330
2331 g_hash_table_unref (handler_info);
2332 g_list_free (channels);
2333 }
2334
2335 static void
mcd_dispatch_operation_handler_decision_cb(GObject * source,GAsyncResult * res,gpointer user_data)2336 mcd_dispatch_operation_handler_decision_cb (GObject *source,
2337 GAsyncResult *res,
2338 gpointer user_data)
2339 {
2340 McdDispatchOperation *self = user_data;
2341 GError *error = NULL;
2342
2343 if (!mcp_dispatch_operation_policy_handler_is_suitable_finish (
2344 MCP_DISPATCH_OPERATION_POLICY (source), res, &error))
2345 {
2346 /* ignore any errors after the first */
2347 if (self->priv->handler_unsuitable == NULL)
2348 g_propagate_error (&self->priv->handler_unsuitable, error);
2349 else
2350 g_error_free (error);
2351 }
2352
2353 if (--self->priv->handler_suitable_pending == 0)
2354 {
2355 mcd_dispatch_operation_handle_channels (self);
2356 }
2357
2358 g_object_unref (self);
2359 }
2360
2361 static void
mcd_dispatch_operation_try_handler(McdDispatchOperation * self,McdClientProxy * handler)2362 mcd_dispatch_operation_try_handler (McdDispatchOperation *self,
2363 McdClientProxy *handler)
2364 {
2365 TpClient *handler_client = (TpClient *) handler;
2366 const GList *p;
2367 McpDispatchOperation *plugin_api = MCP_DISPATCH_OPERATION (
2368 self->priv->plugin_api);
2369
2370 g_assert (self->priv->trying_handler == NULL);
2371 self->priv->trying_handler = g_object_ref (handler);
2372
2373 self->priv->handler_suitable_pending = 0;
2374
2375 DEBUG ("%s: channel ACL verification", self->priv->unique_name);
2376
2377 for (p = mcp_list_objects (); p != NULL; p = g_list_next (p))
2378 {
2379 if (MCP_IS_DISPATCH_OPERATION_POLICY (p->data))
2380 {
2381 McpDispatchOperationPolicy *plugin = p->data;
2382
2383 DEBUG ("%s: checking policy for %s",
2384 G_OBJECT_TYPE_NAME (plugin),
2385 tp_proxy_get_object_path (handler));
2386
2387 self->priv->handler_suitable_pending++;
2388 mcp_dispatch_operation_policy_handler_is_suitable_async (plugin,
2389 handler_client,
2390 _mcd_client_proxy_get_unique_name (handler),
2391 plugin_api,
2392 mcd_dispatch_operation_handler_decision_cb,
2393 g_object_ref (self));
2394 }
2395 }
2396
2397 if (self->priv->handler_suitable_pending == 0)
2398 {
2399 mcd_dispatch_operation_handle_channels (self);
2400 }
2401 }
2402
2403 static gboolean
_mcd_dispatch_operation_try_next_handler(McdDispatchOperation * self)2404 _mcd_dispatch_operation_try_next_handler (McdDispatchOperation *self)
2405 {
2406 gchar **iter;
2407 gboolean is_approved = _mcd_dispatch_operation_is_approved (self);
2408 Approval *approval = g_queue_peek_head (self->priv->approvals);
2409
2410 /* If there is a preferred Handler chosen by the first Approver or
2411 * request, it's the first one we'll consider. We'll even consider
2412 * it even if its filter doesn't match.
2413 *
2414 * In the case of an Approver calling HandleWith, we'll also try again
2415 * even if it already failed - perhaps the Approver is feeling lucky. */
2416 if (approval != NULL && approval->client_bus_name != NULL)
2417 {
2418 McdClientProxy *handler = _mcd_client_registry_lookup (
2419 self->priv->client_registry, approval->client_bus_name);
2420 gboolean failed = _mcd_dispatch_operation_get_handler_failed (self,
2421 approval->client_bus_name);
2422
2423 DEBUG ("Approved handler is %s (still exists: %c, "
2424 "already failed: %c)", approval->client_bus_name,
2425 handler != NULL ? 'Y' : 'N',
2426 failed ? 'Y' : 'N');
2427
2428 /* Maybe the handler has exited since we chose it, or maybe we
2429 * already tried it? Otherwise, it's the right choice. */
2430 if (handler != NULL &&
2431 (approval->type == APPROVAL_TYPE_HANDLE_WITH || !failed))
2432 {
2433 mcd_dispatch_operation_try_handler (self, handler);
2434 return TRUE;
2435 }
2436
2437 /* If the Handler has disappeared, a HandleWith call should fail,
2438 * but a request (for which the client_bus_name is merely advisory)
2439 * can legitimately try more handlers. */
2440 if (approval->type == APPROVAL_TYPE_HANDLE_WITH)
2441 {
2442 GError gone = { TP_ERROR,
2443 TP_ERROR_NOT_IMPLEMENTED,
2444 "The requested Handler does not exist" };
2445
2446 g_queue_pop_head (self->priv->approvals);
2447
2448 dbus_g_method_return_error (approval->context, &gone);
2449
2450 approval->context = NULL;
2451 approval_free (approval);
2452 return TRUE;
2453 }
2454 }
2455
2456 for (iter = self->priv->possible_handlers;
2457 iter != NULL && *iter != NULL;
2458 iter++)
2459 {
2460 McdClientProxy *handler = _mcd_client_registry_lookup (
2461 self->priv->client_registry, *iter);
2462 gboolean failed = _mcd_dispatch_operation_get_handler_failed
2463 (self, *iter);
2464
2465 DEBUG ("Possible handler: %s (still exists: %c, already failed: %c)",
2466 *iter, handler != NULL ? 'Y' : 'N', failed ? 'Y' : 'N');
2467
2468 if (handler != NULL && !failed &&
2469 (is_approved || _mcd_client_proxy_get_bypass_approval (handler)))
2470 {
2471 mcd_dispatch_operation_try_handler (self, handler);
2472 return TRUE;
2473 }
2474 }
2475
2476 return FALSE;
2477 }
2478
2479 static void
_mcd_dispatch_operation_close_as_undispatchable(McdDispatchOperation * self,const GError * error)2480 _mcd_dispatch_operation_close_as_undispatchable (McdDispatchOperation *self,
2481 const GError *error)
2482 {
2483 /* All of the usable handlers vanished while we were thinking about it
2484 * (this can only happen if non-activatable handlers exit after we
2485 * include them in the list of possible handlers, but before we .
2486 * We should recover in some better way, perhaps by asking all the
2487 * approvers again (?), but for now we'll just close all the channels. */
2488
2489 DEBUG ("%s", error->message);
2490 _mcd_dispatch_operation_finish (self, error->domain, error->code,
2491 "%s", error->message);
2492
2493 if (self->priv->channel != NULL)
2494 {
2495 McdChannel *channel = MCD_CHANNEL (self->priv->channel);
2496 GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE,
2497 "Handler no longer available" };
2498
2499 g_object_ref (channel);
2500 mcd_channel_take_error (channel, g_error_copy (&e));
2501 _mcd_channel_undispatchable (channel);
2502 g_object_unref (channel);
2503 }
2504 }
2505
2506 void
_mcd_dispatch_operation_start_plugin_delay(McdDispatchOperation * self)2507 _mcd_dispatch_operation_start_plugin_delay (McdDispatchOperation *self)
2508 {
2509 g_object_ref (self);
2510 DEBUG ("%" G_GSIZE_FORMAT " -> %" G_GSIZE_FORMAT,
2511 self->priv->plugins_pending,
2512 self->priv->plugins_pending + 1);
2513 self->priv->plugins_pending++;
2514 }
2515
2516 void
_mcd_dispatch_operation_end_plugin_delay(McdDispatchOperation * self)2517 _mcd_dispatch_operation_end_plugin_delay (McdDispatchOperation *self)
2518 {
2519 DEBUG ("%" G_GSIZE_FORMAT " -> %" G_GSIZE_FORMAT,
2520 self->priv->plugins_pending,
2521 self->priv->plugins_pending - 1);
2522 g_return_if_fail (self->priv->plugins_pending > 0);
2523 self->priv->plugins_pending--;
2524
2525 _mcd_dispatch_operation_check_client_locks (self);
2526 g_object_unref (self);
2527 }
2528
2529 void
_mcd_dispatch_operation_forget_channels(McdDispatchOperation * self)2530 _mcd_dispatch_operation_forget_channels (McdDispatchOperation *self)
2531 {
2532 if (self->priv->channel != NULL)
2533 {
2534 /* Take a temporary copy, because self->priv->channels is going
2535 * to be modified as a result of mcd_mission_abort() */
2536 McdChannel *channel = g_object_ref (self->priv->channel);
2537
2538 mcd_mission_abort (MCD_MISSION (channel));
2539 g_object_unref (channel);
2540 }
2541
2542 /* There should now be no channel left (it was aborted) */
2543 g_return_if_fail (self->priv->channel == NULL);
2544 }
2545
2546 void
_mcd_dispatch_operation_leave_channels(McdDispatchOperation * self,TpChannelGroupChangeReason reason,const gchar * message)2547 _mcd_dispatch_operation_leave_channels (McdDispatchOperation *self,
2548 TpChannelGroupChangeReason reason,
2549 const gchar *message)
2550 {
2551 if (message == NULL)
2552 {
2553 message = "";
2554 }
2555
2556 if (self->priv->channel != NULL)
2557 {
2558 /* Take a temporary copy, because self->priv->channels could
2559 * be modified as a result */
2560 McdChannel *channel = g_object_ref (self->priv->channel);
2561
2562 _mcd_channel_depart (channel, reason, message);
2563 g_object_unref (channel);
2564 }
2565
2566 _mcd_dispatch_operation_forget_channels (self);
2567 }
2568
2569 void
_mcd_dispatch_operation_close_channels(McdDispatchOperation * self)2570 _mcd_dispatch_operation_close_channels (McdDispatchOperation *self)
2571 {
2572 if (self->priv->channel != NULL)
2573 {
2574 /* Take a temporary copy, because self->priv->channels could
2575 * be modified as a result */
2576 McdChannel *channel = g_object_ref (self->priv->channel);
2577
2578 _mcd_channel_close (channel);
2579 g_object_unref (channel);
2580 }
2581
2582 _mcd_dispatch_operation_forget_channels (self);
2583 }
2584
2585 void
_mcd_dispatch_operation_destroy_channels(McdDispatchOperation * self)2586 _mcd_dispatch_operation_destroy_channels (McdDispatchOperation *self)
2587 {
2588 if (self->priv->channel != NULL)
2589 {
2590 /* Take a temporary copy, because self->priv->channels could
2591 * be modified as a result */
2592 McdChannel *channel = g_object_ref (self->priv->channel);
2593
2594 _mcd_channel_undispatchable (channel);
2595 g_object_unref (channel);
2596 }
2597
2598 _mcd_dispatch_operation_forget_channels (self);
2599 }
2600
2601 /* This should really be called ..._has_invoked_observers_if_needed,
2602 * but that name would be ridiculous. */
2603 gboolean
_mcd_dispatch_operation_has_invoked_observers(McdDispatchOperation * self)2604 _mcd_dispatch_operation_has_invoked_observers (McdDispatchOperation *self)
2605 {
2606 return self->priv->invoked_observers_if_needed;
2607 }
2608