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