1 /* A Telepathy ChannelRequest object
2  *
3  * Copyright © 2009-2011 Nokia Corporation.
4  * Copyright © 2009-2011 Collabora Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA
20  *
21  */
22 
23 #include "config.h"
24 
25 #include "request.h"
26 
27 #include <dbus/dbus-glib.h>
28 #include <telepathy-glib/telepathy-glib.h>
29 #include <telepathy-glib/telepathy-glib-dbus.h>
30 
31 #include "mcd-account-priv.h"
32 #include "mcd-connection-priv.h"
33 #include "mcd-debug.h"
34 #include "mcd-misc.h"
35 #include "plugin-loader.h"
36 #include "plugin-request.h"
37 #include "_gen/interfaces.h"
38 
39 enum {
40     PROP_0,
41     PROP_CLIENT_REGISTRY,
42     PROP_USE_EXISTING,
43     PROP_ACCOUNT,
44     PROP_ACCOUNT_PATH,
45     PROP_PROPERTIES,
46     PROP_USER_ACTION_TIME,
47     PROP_PREFERRED_HANDLER,
48     PROP_HINTS,
49     PROP_REQUESTS,
50     PROP_INTERFACES
51 };
52 
53 static guint sig_id_cancelling = 0;
54 static guint sig_id_ready_to_request = 0;
55 
56 struct _McdRequest {
57     GObject parent;
58 
59     gboolean use_existing;
60     McdClientRegistry *clients;
61     TpDBusDaemon *dbus_daemon;
62     McdAccount *account;
63     GHashTable *properties;
64     gint64 user_action_time;
65     gchar *preferred_handler;
66     GHashTable *hints;
67     gchar *object_path;
68 
69     /* if the request is an internally handled special case: */
70     McdRequestInternalHandler internal_handler;
71     GFreeFunc internal_handler_clear;
72     gpointer internal_handler_data;
73 
74     /* Number of reasons to not make the request yet.
75      *
76      * We hold one extra ref to ourselves per delay. The object starts with
77      * one delay in _mcd_request_init, representing the Proceed() call
78      * that hasn't happened; to get the refcounting right, we take the
79      * corresponding ref in _mcd_request_constructed. */
80     gsize delay;
81     TpClient *predicted_handler;
82 
83     /* TRUE if either succeeded[-with-channel] or failed was emitted */
84     gboolean is_complete;
85 
86     gboolean cancellable;
87     GQuark failure_domain;
88     gint failure_code;
89     gchar *failure_message;
90 
91     gboolean proceeding;
92 };
93 
94 struct _McdRequestClass {
95     GObjectClass parent;
96     TpDBusPropertiesMixinClass dbus_properties_class;
97 };
98 
99 static void request_iface_init (TpSvcChannelRequestClass *);
100 
101 G_DEFINE_TYPE_WITH_CODE (McdRequest, _mcd_request, G_TYPE_OBJECT,
102     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_REQUEST, request_iface_init);
103     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
104                            tp_dbus_properties_mixin_iface_init))
105 
106 #define REQUEST_OBJ_BASE "/org/freedesktop/Telepathy/ChannelDispatcher/Request"
107 
108 static guint last_req_id = 1;
109 
110 static void
_mcd_request_init(McdRequest * self)111 _mcd_request_init (McdRequest *self)
112 {
113   DEBUG ("%p", self);
114 
115   self->delay = 1;
116   self->cancellable = TRUE;
117   self->object_path = g_strdup_printf (REQUEST_OBJ_BASE "%u", last_req_id++);
118 }
119 
120 static void
_mcd_request_constructed(GObject * object)121 _mcd_request_constructed (GObject *object)
122 {
123   McdRequest *self = (McdRequest *) object;
124   void (*constructed) (GObject *) =
125     G_OBJECT_CLASS (_mcd_request_parent_class)->constructed;
126 
127   /* this is paired with the delay in _mcd_request_init */
128   g_object_ref (self);
129 
130   if (constructed != NULL)
131     constructed (object);
132 
133   g_return_if_fail (self->account != NULL);
134   g_return_if_fail (self->clients != NULL);
135 
136   self->dbus_daemon = _mcd_client_registry_get_dbus_daemon (self->clients);
137   tp_dbus_daemon_register_object (self->dbus_daemon, self->object_path, self);
138 }
139 
140 static void
_mcd_request_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)141 _mcd_request_get_property (GObject *object,
142     guint prop_id,
143     GValue *value,
144     GParamSpec *pspec)
145 {
146   McdRequest *self = (McdRequest *) object;
147 
148   switch (prop_id)
149     {
150     case PROP_USE_EXISTING:
151       g_value_set_boolean (value, self->use_existing);
152       break;
153 
154     case PROP_CLIENT_REGISTRY:
155       g_value_set_object (value, self->clients);
156       break;
157 
158     case PROP_ACCOUNT:
159       g_value_set_object (value, self->account);
160       break;
161 
162     case PROP_ACCOUNT_PATH:
163       g_value_set_boxed (value, mcd_account_get_object_path (self->account));
164       break;
165 
166     case PROP_PROPERTIES:
167       g_value_set_boxed (value, self->properties);
168       break;
169 
170     case PROP_PREFERRED_HANDLER:
171       if (self->preferred_handler == NULL)
172         {
173           g_value_set_static_string (value, "");
174         }
175       else
176         {
177           g_value_set_string (value, self->preferred_handler);
178         }
179       break;
180 
181     case PROP_USER_ACTION_TIME:
182       g_value_set_int64 (value, self->user_action_time);
183       break;
184 
185     case PROP_HINTS:
186       if (self->hints == NULL)
187         {
188           g_value_take_boxed (value, g_hash_table_new (NULL, NULL));
189         }
190       else
191         {
192           g_value_set_boxed (value, self->hints);
193         }
194       break;
195 
196     case PROP_REQUESTS:
197         {
198           GPtrArray *arr = g_ptr_array_sized_new (1);
199 
200           g_ptr_array_add (arr, g_hash_table_ref (self->properties));
201           g_value_take_boxed (value, arr);
202         }
203       break;
204 
205     case PROP_INTERFACES:
206       /* we have no interfaces */
207       g_value_set_static_boxed (value, NULL);
208       break;
209 
210     default:
211       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
212       break;
213     }
214 }
215 
216 static void
_mcd_request_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)217 _mcd_request_set_property (GObject *object,
218     guint prop_id,
219     const GValue *value,
220     GParamSpec *pspec)
221 {
222   McdRequest *self = (McdRequest *) object;
223 
224   switch (prop_id)
225     {
226     case PROP_USE_EXISTING:
227       self->use_existing = g_value_get_boolean (value);
228       break;
229 
230     case PROP_CLIENT_REGISTRY:
231       g_assert (self->clients == NULL); /* construct-only */
232       self->clients = g_value_dup_object (value);
233       break;
234 
235     case PROP_ACCOUNT:
236       g_assert (self->account == NULL); /* construct-only */
237       self->account = g_value_dup_object (value);
238       break;
239 
240     case PROP_PROPERTIES:
241       g_assert (self->properties == NULL); /* construct-only */
242       self->properties = g_hash_table_ref (g_value_get_boxed (value));
243       break;
244 
245     case PROP_USER_ACTION_TIME:
246       g_assert (self->user_action_time == 0); /* construct-only */
247       self->user_action_time = g_value_get_int64 (value);
248       break;
249 
250     case PROP_PREFERRED_HANDLER:
251       if (self->preferred_handler != NULL)
252         g_free (self->preferred_handler);
253 
254       self->preferred_handler = g_value_dup_string (value);
255       break;
256 
257     case PROP_HINTS:
258       g_assert (self->hints == NULL); /* construct-only */
259       self->hints = g_value_dup_boxed (value);
260       break;
261 
262     default:
263       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
264       break;
265     }
266 }
267 
268 static void
_mcd_request_dispose(GObject * object)269 _mcd_request_dispose (GObject *object)
270 {
271   McdRequest *self = (McdRequest *) object;
272   GObjectFinalizeFunc dispose =
273     G_OBJECT_CLASS (_mcd_request_parent_class)->dispose;
274 
275   DEBUG ("%p", object);
276 
277   /* shouldn't ever actually get this far with a blocked account, *
278    * but we have to clear the lock if we do or we'll deadlock     */
279   if (_mcd_request_is_internal (self) && self->account != NULL)
280     {
281       const gchar *path = mcd_account_get_object_path (self->account);
282       _mcd_request_unblock_account (path);
283       g_warning ("internal request disposed without being handled or failed");
284     }
285 
286   tp_clear_object (&self->account);
287   tp_clear_object (&self->clients);
288   tp_clear_object (&self->predicted_handler);
289   tp_clear_pointer (&self->hints, g_hash_table_unref);
290 
291   if (dispose != NULL)
292     dispose (object);
293 }
294 
295 static void
_mcd_request_finalize(GObject * object)296 _mcd_request_finalize (GObject *object)
297 {
298   McdRequest *self = (McdRequest *) object;
299   GObjectFinalizeFunc finalize =
300     G_OBJECT_CLASS (_mcd_request_parent_class)->finalize;
301 
302   DEBUG ("%p", object);
303 
304   _mcd_request_clear_internal_handler (self);
305 
306   g_free (self->preferred_handler);
307   g_free (self->object_path);
308   g_free (self->failure_message);
309   tp_clear_pointer (&self->properties, g_hash_table_unref);
310 
311   if (finalize != NULL)
312     finalize (object);
313 }
314 
315 static void
_mcd_request_class_init(McdRequestClass * cls)316 _mcd_request_class_init (
317     McdRequestClass *cls)
318 {
319   static TpDBusPropertiesMixinPropImpl request_props[] = {
320       { "Account", "account-path", NULL },
321       { "UserActionTime", "user-action-time", NULL },
322       { "PreferredHandler", "preferred-handler", NULL },
323       { "Interfaces", "interfaces", NULL },
324       { "Requests", "requests", NULL },
325       { "Hints", "hints", NULL },
326       { NULL }
327   };
328   static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
329       { TP_IFACE_CHANNEL_REQUEST,
330           tp_dbus_properties_mixin_getter_gobject_properties,
331           NULL,
332           request_props,
333       },
334       { NULL }
335   };
336   GObjectClass *object_class = (GObjectClass *) cls;
337 
338   object_class->constructed = _mcd_request_constructed;
339   object_class->get_property = _mcd_request_get_property;
340   object_class->set_property = _mcd_request_set_property;
341   object_class->dispose = _mcd_request_dispose;
342   object_class->finalize = _mcd_request_finalize;
343 
344   g_object_class_install_property (object_class, PROP_USE_EXISTING,
345       g_param_spec_boolean ("use-existing", "Use EnsureChannel?",
346           "TRUE if EnsureChannel should be used for this request",
347           FALSE,
348           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
349           G_PARAM_STATIC_STRINGS));
350 
351   g_object_class_install_property (object_class, PROP_CLIENT_REGISTRY,
352       g_param_spec_object ("client-registry", "Client registry",
353           "The client registry",
354           MCD_TYPE_CLIENT_REGISTRY,
355           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
356           G_PARAM_STATIC_STRINGS));
357 
358   g_object_class_install_property (object_class, PROP_ACCOUNT,
359       g_param_spec_object ("account", "Account",
360           "The underlying McdAccount",
361           MCD_TYPE_ACCOUNT,
362           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
363           G_PARAM_STATIC_STRINGS));
364 
365   g_object_class_install_property (object_class, PROP_ACCOUNT_PATH,
366       g_param_spec_boxed ("account-path", "Account path",
367           "The object path of McdRequest:account",
368           DBUS_TYPE_G_OBJECT_PATH,
369           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
370 
371   g_object_class_install_property (object_class, PROP_PROPERTIES,
372        g_param_spec_boxed ("properties", "Properties",
373          "Properties requested for the channel",
374          TP_HASH_TYPE_QUALIFIED_PROPERTY_VALUE_MAP,
375          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
376 
377   g_object_class_install_property (object_class, PROP_USER_ACTION_TIME,
378        g_param_spec_int64 ("user-action-time", "UserActionTime",
379          "Time of user action as for TpAccountChannelRequest:user-action-time",
380          G_MININT64, G_MAXINT64, TP_USER_ACTION_TIME_NOT_USER_ACTION,
381          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
382 
383   g_object_class_install_property (object_class, PROP_PREFERRED_HANDLER,
384        g_param_spec_string ("preferred-handler", "PreferredHandler",
385          "Preferred handler for this request, or the empty string",
386          "",
387          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
388 
389   g_object_class_install_property (object_class, PROP_HINTS,
390       g_param_spec_boxed ("hints", "Hints",
391         "GHashTable",
392         TP_HASH_TYPE_STRING_VARIANT_MAP,
393         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
394 
395   g_object_class_install_property (object_class, PROP_REQUESTS,
396       g_param_spec_boxed ("requests", "Requests", "A dbus-glib aa{sv}",
397         TP_ARRAY_TYPE_QUALIFIED_PROPERTY_VALUE_MAP_LIST,
398         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
399 
400   g_object_class_install_property (object_class, PROP_INTERFACES,
401       g_param_spec_boxed ("interfaces", "Interfaces", "A dbus-glib 'as'",
402         G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
403 
404   sig_id_cancelling = g_signal_new ("cancelling",
405       G_OBJECT_CLASS_TYPE (cls), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
406       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
407 
408   sig_id_ready_to_request = g_signal_new ("ready-to-request",
409       G_OBJECT_CLASS_TYPE (cls), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
410       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
411 
412   cls->dbus_properties_class.interfaces = prop_interfaces,
413   tp_dbus_properties_mixin_class_init (object_class,
414       G_STRUCT_OFFSET (McdRequestClass, dbus_properties_class));
415 }
416 
417 McdRequest *
_mcd_request_new(McdClientRegistry * clients,gboolean use_existing,McdAccount * account,GHashTable * properties,gint64 user_action_time,const gchar * preferred_handler,GHashTable * hints)418 _mcd_request_new (McdClientRegistry *clients,
419     gboolean use_existing,
420     McdAccount *account,
421     GHashTable *properties,
422     gint64 user_action_time,
423     const gchar *preferred_handler,
424     GHashTable *hints)
425 {
426   McdRequest *self;
427 
428   self = g_object_new (MCD_TYPE_REQUEST,
429       "client-registry", clients,
430       "use-existing", use_existing,
431       "account", account,
432       "properties", properties,
433       "user-action-time", user_action_time,
434       "preferred-handler", preferred_handler,
435       "hints", hints,
436       NULL);
437   DEBUG ("%p (for %p)", self, account);
438 
439   return self;
440 }
441 
442 void
_mcd_request_set_internal_handler(McdRequest * self,McdRequestInternalHandler handler,GFreeFunc free_func,gpointer data)443 _mcd_request_set_internal_handler (McdRequest *self,
444     McdRequestInternalHandler handler,
445     GFreeFunc free_func,
446     gpointer data)
447 {
448   g_assert (self->internal_handler == NULL);
449   g_assert (self->internal_handler_data == NULL);
450   g_assert (self->internal_handler_clear == NULL);
451 
452   self->internal_handler = handler;
453   self->internal_handler_clear = free_func;
454   self->internal_handler_data = data;
455 }
456 
457 gboolean
_mcd_request_handle_internally(McdRequest * self,McdChannel * channel,gboolean close_after)458 _mcd_request_handle_internally (McdRequest *self,
459     McdChannel *channel,
460     gboolean close_after)
461 {
462   if (self->internal_handler != NULL)
463     {
464       DEBUG ("Handling request %p, channel %p internally", self, channel);
465       self->internal_handler (self, channel, self->internal_handler_data,
466           close_after);
467 
468       return TRUE;
469     }
470 
471   return FALSE;
472 }
473 
474 void
_mcd_request_clear_internal_handler(McdRequest * self)475 _mcd_request_clear_internal_handler (McdRequest *self)
476 {
477   if (self->internal_handler_clear != NULL)
478     self->internal_handler_clear (self->internal_handler_data);
479 
480   self->internal_handler = NULL;
481   self->internal_handler_data = NULL;
482   self->internal_handler_clear = NULL;
483 }
484 
485 gboolean
_mcd_request_is_internal(McdRequest * self)486 _mcd_request_is_internal (McdRequest *self)
487 {
488   return self != NULL && self->internal_handler != NULL;
489 }
490 
491 gboolean
_mcd_request_get_use_existing(McdRequest * self)492 _mcd_request_get_use_existing (McdRequest *self)
493 {
494   return self->use_existing;
495 }
496 
497 McdAccount *
_mcd_request_get_account(McdRequest * self)498 _mcd_request_get_account (McdRequest *self)
499 {
500   return self->account;
501 }
502 
503 gint64
_mcd_request_get_user_action_time(McdRequest * self)504 _mcd_request_get_user_action_time (McdRequest *self)
505 {
506   return self->user_action_time;
507 }
508 
509 const gchar *
_mcd_request_get_preferred_handler(McdRequest * self)510 _mcd_request_get_preferred_handler (McdRequest *self)
511 {
512   if (self->preferred_handler == NULL)
513     return "";
514 
515   return self->preferred_handler;
516 }
517 
518 const gchar *
_mcd_request_get_object_path(McdRequest * self)519 _mcd_request_get_object_path (McdRequest *self)
520 {
521   return self->object_path;
522 }
523 
524 GHashTable *
_mcd_request_get_hints(McdRequest * self)525 _mcd_request_get_hints (McdRequest *self)
526 {
527   return self->hints;
528 }
529 
530 static GList *
_mcd_request_policy_plugins(void)531 _mcd_request_policy_plugins (void)
532 {
533   static gboolean cached = FALSE;
534   static GList *policies = NULL;
535 
536   if (G_UNLIKELY (!cached))
537     {
538       const GList *p = NULL;
539 
540       for (p = mcp_list_objects (); p != NULL; p = g_list_next (p))
541         {
542           if (MCP_IS_REQUEST_POLICY (p->data))
543             policies = g_list_prepend (policies, g_object_ref (p->data));
544         }
545 
546       cached = TRUE;
547     }
548 
549   return policies;
550 }
551 
552 /* hash keys on account paths: value is the lock-count */
553 static GHashTable *account_locks = NULL;
554 static GHashTable *blocked_reqs = NULL;
555 
556 static void
_init_blocked_account_request_queue(void)557 _init_blocked_account_request_queue (void)
558 {
559   account_locks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
560   blocked_reqs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
561 }
562 
563 guint
_mcd_request_block_account(const gchar * account)564 _mcd_request_block_account (const gchar *account)
565 {
566   guint count;
567   gchar *key = g_strdup (account);
568 
569   if (G_UNLIKELY (account_locks == NULL))
570     _init_blocked_account_request_queue ();
571 
572   count = GPOINTER_TO_UINT (g_hash_table_lookup (account_locks, account));
573   g_hash_table_replace (account_locks, key, GUINT_TO_POINTER (++count));
574   DEBUG ("lock count for account %s is now: %u", account, count);
575 
576   return count;
577 }
578 
579 static void
_unblock_request(gpointer object,gpointer data)580 _unblock_request (gpointer object, gpointer data)
581 {
582   DEBUG ("ending delay for internally locked request %p on account %s",
583       object, (const gchar *) data);
584   _mcd_request_end_delay (MCD_REQUEST (object));
585 }
586 
587 guint
_mcd_request_unblock_account(const gchar * account)588 _mcd_request_unblock_account (const gchar *account)
589 {
590   guint count = 0;
591   gchar *key = NULL;
592 
593   if (G_UNLIKELY (account_locks == NULL))
594     {
595       g_warning ("Unbalanced account-request-unblock for %s", account);
596       return 0;
597     }
598 
599   count = GPOINTER_TO_UINT (g_hash_table_lookup (account_locks, account));
600 
601   switch (count)
602     {
603       GQueue *queue;
604 
605       case 0:
606         g_warning ("Unbalanced account-request-unblock for %s", account);
607         return 0;
608 
609       case 1:
610         DEBUG ("removing lock from account %s", account);
611         g_hash_table_remove (account_locks, account);
612         queue = g_hash_table_lookup (blocked_reqs, account);
613 
614         if (queue == NULL)
615           return 0;
616 
617         g_queue_foreach (queue, _unblock_request, NULL);
618         g_queue_clear (queue);
619 
620         return 0;
621 
622       default:
623         DEBUG ("reducing lock count for %s", account);
624         key = g_strdup (account);
625         g_hash_table_replace (account_locks, key, GUINT_TO_POINTER (--count));
626         return count;
627     }
628 }
629 
630 static gboolean
_queue_blocked_requests(McdRequest * self)631 _queue_blocked_requests (McdRequest *self)
632 {
633   const gchar *path = NULL;
634   guint locks = 0;
635 
636   /* this is an internal request and therefore not subject to blocking *
637      BUT the fact that this internal request is in-flight means other  *
638      requests on the same account/handle type should be blocked        */
639   if (self->internal_handler != NULL)
640     {
641       path = mcd_account_get_object_path (_mcd_request_get_account (self));
642       _mcd_request_block_account (path);
643 
644       return FALSE;
645     }
646 
647   /* no internal requests in flight, nothing to queue, nothing to do */
648   if (account_locks == NULL)
649     return FALSE;
650 
651   if (path == NULL)
652     path = mcd_account_get_object_path (_mcd_request_get_account (self));
653 
654   /* account_locks tracks the # of in-flight internal requests per account */
655   locks = GPOINTER_TO_UINT (g_hash_table_lookup (account_locks, path));
656 
657   /* internal reqeusts in flight => other requests on that account must wait */
658   if (locks > 0)
659     {
660       GQueue *queue;
661 
662       queue = g_hash_table_lookup (blocked_reqs, path);
663 
664       if (queue == NULL)
665         {
666           queue = g_queue_new ();
667           g_hash_table_insert (blocked_reqs, g_strdup (path), queue);
668         }
669 
670       _mcd_request_start_delay (self);
671       g_queue_push_tail (queue, self);
672 
673       return TRUE;
674     }
675 
676   return FALSE;
677 }
678 
679 void
_mcd_request_proceed(McdRequest * self,DBusGMethodInvocation * context)680 _mcd_request_proceed (McdRequest *self,
681     DBusGMethodInvocation *context)
682 {
683   McdConnection *connection = NULL;
684   McdPluginRequest *plugin_api = NULL;
685   gboolean urgent = FALSE;
686   gboolean blocked = FALSE;
687   const GList *mini_plugins;
688 
689   if (self->proceeding)
690     {
691       GError na = { TP_ERROR, TP_ERROR_NOT_AVAILABLE,
692           "Proceed has already been called; stop calling it" };
693 
694       if (context != NULL)
695         dbus_g_method_return_error (context, &na);
696 
697       return;
698     }
699 
700   self->proceeding = TRUE;
701 
702   tp_clear_pointer (&context, tp_svc_channel_request_return_from_proceed);
703 
704   connection = mcd_account_get_connection (self->account);
705 
706   if (connection != NULL)
707     {
708       const gchar *name =
709         tp_asv_get_string (self->properties, TP_PROP_CHANNEL_TARGET_ID);
710 
711       if (name != NULL)
712         {
713           urgent = _mcd_connection_target_id_is_urgent (connection, name);
714         }
715       else
716         {
717           guint handle = tp_asv_get_uint32 (self->properties,
718               TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
719 
720           urgent = _mcd_connection_target_handle_is_urgent (connection, handle);
721         }
722     }
723 
724   /* urgent calls (eg emergency numbers) are not subject to policy *
725    * delays: they automatically pass go and collect 200 qwatloos   */
726   if (urgent)
727     goto proceed;
728 
729   /* requests can pick up an extra delay (and ref) here */
730   blocked = _queue_blocked_requests (self);
731 
732   if (blocked)
733     DEBUG ("Request delayed in favour of internal request on %s",
734         mcd_account_get_object_path (self->account));
735 
736   /* now regular request policy plugins get their shot at denying/delaying */
737   for (mini_plugins = _mcd_request_policy_plugins ();
738        mini_plugins != NULL;
739        mini_plugins = mini_plugins->next)
740     {
741       DEBUG ("Checking request with policy");
742 
743       /* Lazily create a plugin-API object if anything cares */
744       if (plugin_api == NULL)
745         {
746           plugin_api = _mcd_plugin_request_new (self->account, self);
747         }
748 
749       mcp_request_policy_check (mini_plugins->data, MCP_REQUEST (plugin_api));
750     }
751 
752   /* this is paired with the delay set when the request was created */
753  proceed:
754   _mcd_request_end_delay (self);
755 
756   tp_clear_object (&plugin_api);
757 }
758 
759 GHashTable *
_mcd_request_get_properties(McdRequest * self)760 _mcd_request_get_properties (McdRequest *self)
761 {
762   return self->properties;
763 }
764 
765 GVariant *
mcd_request_dup_properties(McdRequest * self)766 mcd_request_dup_properties (McdRequest *self)
767 {
768   GValue value = G_VALUE_INIT;
769   GVariant *ret;
770 
771   g_value_init (&value, TP_HASH_TYPE_STRING_VARIANT_MAP);
772   g_value_set_boxed (&value, self->properties);
773   ret = dbus_g_value_build_g_variant (&value);
774   g_value_unset (&value);
775   return g_variant_ref_sink (ret);
776 }
777 
778 void
_mcd_request_start_delay(McdRequest * self)779 _mcd_request_start_delay (McdRequest *self)
780 {
781     g_object_ref (self);
782     self->delay++;
783 }
784 
785 void
_mcd_request_end_delay(McdRequest * self)786 _mcd_request_end_delay (McdRequest *self)
787 {
788     g_return_if_fail (self->delay > 0);
789 
790     if (--self->delay == 0)
791     {
792       g_signal_emit (self, sig_id_ready_to_request, 0);
793     }
794 
795     g_object_unref (self);
796 }
797 
798 static void
_mcd_request_clean_up(McdRequest * self)799 _mcd_request_clean_up (McdRequest *self)
800 {
801   tp_clear_object (&self->predicted_handler);
802   tp_dbus_daemon_unregister_object (self->dbus_daemon, self);
803 }
804 
805 void
_mcd_request_set_success(McdRequest * self,TpChannel * channel)806 _mcd_request_set_success (McdRequest *self,
807     TpChannel *channel)
808 {
809   g_return_if_fail (TP_IS_CHANNEL (channel));
810 
811   if (!self->is_complete)
812     {
813       /* might be used for the connection's properties in future; empty
814        * for now */
815       GHashTable *future_conn_props = g_hash_table_new (g_str_hash,
816           g_str_equal);
817       GVariant *variant;
818       GValue value = G_VALUE_INIT;
819 
820       DEBUG ("Request succeeded");
821       self->is_complete = TRUE;
822       self->cancellable = FALSE;
823 
824       variant = tp_channel_dup_immutable_properties (channel);
825       dbus_g_value_parse_g_variant (variant, &value);
826       g_assert (G_VALUE_HOLDS (&value, TP_HASH_TYPE_STRING_VARIANT_MAP));
827 
828       tp_svc_channel_request_emit_succeeded_with_channel (self,
829           tp_proxy_get_object_path (tp_channel_get_connection (channel)),
830           future_conn_props,
831           tp_proxy_get_object_path (channel),
832           g_value_get_boxed (&value));
833       tp_svc_channel_request_emit_succeeded (self);
834 
835       g_hash_table_unref (future_conn_props);
836       g_value_unset (&value);
837       g_variant_unref (variant);
838 
839       _mcd_request_clean_up (self);
840     }
841   else
842     {
843       DEBUG ("Ignoring an attempt to succeed after already complete");
844     }
845 }
846 
847 void
_mcd_request_set_failure(McdRequest * self,GQuark domain,gint code,const gchar * message)848 _mcd_request_set_failure (McdRequest *self,
849     GQuark domain,
850     gint code,
851     const gchar *message)
852 {
853   if (!self->is_complete)
854     {
855       GError e = { domain, code, (gchar *) message };
856       gchar *err_string;
857 
858       DEBUG ("Request failed: %s %d: %s", g_quark_to_string (domain),
859           code, message);
860 
861       err_string = _mcd_build_error_string (&e);
862 
863       self->is_complete = TRUE;
864       self->cancellable = FALSE;
865       self->failure_domain = domain;
866       self->failure_code = code;
867       self->failure_message = g_strdup (message);
868 
869       if (self->predicted_handler != NULL)
870         {
871           /* no callback, as we don't really care: this method call acts as a
872            * pseudo-signal */
873           DEBUG ("calling RemoveRequest on %s for %s",
874                  tp_proxy_get_object_path (self->predicted_handler),
875                  self->object_path);
876           tp_cli_client_interface_requests_call_remove_request (
877               self->predicted_handler, -1, self->object_path, err_string,
878               message, NULL, NULL, NULL, NULL);
879         }
880 
881       tp_svc_channel_request_emit_failed (self, err_string, message);
882 
883       g_free (err_string);
884 
885       _mcd_request_clean_up (self);
886     }
887   else
888     {
889       DEBUG ("Ignoring an attempt to fail after already complete");
890     }
891 }
892 
893 gboolean
_mcd_request_is_complete(McdRequest * self)894 _mcd_request_is_complete (McdRequest *self)
895 {
896   return self->is_complete;
897 }
898 
899 GError *
_mcd_request_dup_failure(McdRequest * self)900 _mcd_request_dup_failure (McdRequest *self)
901 {
902   if (self->failure_domain == 0)
903     return NULL;
904 
905   return g_error_new_literal (self->failure_domain, self->failure_code,
906       self->failure_message);
907 }
908 
909 void
_mcd_request_set_uncancellable(McdRequest * self)910 _mcd_request_set_uncancellable (McdRequest *self)
911 {
912   self->cancellable = FALSE;
913 }
914 
915 static void
channel_request_cancel(TpSvcChannelRequest * iface,DBusGMethodInvocation * context)916 channel_request_cancel (TpSvcChannelRequest *iface,
917                         DBusGMethodInvocation *context)
918 {
919   McdRequest *self = MCD_REQUEST (iface);
920   GError *error = NULL;
921 
922   if (_mcd_request_cancel (self, &error))
923     {
924       tp_svc_channel_request_return_from_cancel (context);
925     }
926   else
927     {
928       dbus_g_method_return_error (context, error);
929       g_error_free (error);
930     }
931 }
932 
933 static void
channel_request_proceed(TpSvcChannelRequest * iface,DBusGMethodInvocation * context)934 channel_request_proceed (TpSvcChannelRequest *iface,
935     DBusGMethodInvocation *context)
936 {
937   McdRequest *self = MCD_REQUEST (iface);
938 
939   _mcd_request_proceed (self, context);
940 }
941 
942 static void
request_iface_init(TpSvcChannelRequestClass * iface)943 request_iface_init (TpSvcChannelRequestClass *iface)
944 {
945 #define IMPLEMENT(x) tp_svc_channel_request_implement_##x (\
946     iface, channel_request_##x)
947   IMPLEMENT (proceed);
948   IMPLEMENT (cancel);
949 #undef IMPLEMENT
950 }
951 
952 GHashTable *
_mcd_request_dup_immutable_properties(McdRequest * self)953 _mcd_request_dup_immutable_properties (McdRequest *self)
954 {
955   return tp_dbus_properties_mixin_make_properties_hash ((GObject *) self,
956       TP_IFACE_CHANNEL_REQUEST, "Account",
957       TP_IFACE_CHANNEL_REQUEST, "UserActionTime",
958       TP_IFACE_CHANNEL_REQUEST, "PreferredHandler",
959       TP_IFACE_CHANNEL_REQUEST, "Interfaces",
960       TP_IFACE_CHANNEL_REQUEST, "Requests",
961       TP_IFACE_CHANNEL_REQUEST, "Hints",
962       NULL);
963 }
964 
965 gboolean
_mcd_request_cancel(McdRequest * self,GError ** error)966 _mcd_request_cancel (McdRequest *self,
967     GError **error)
968 {
969   if (self->cancellable)
970     {
971       /* for the moment, McdChannel has to do the actual work, because its
972        * status/error track the failure state */
973       g_signal_emit (self, sig_id_cancelling, 0);
974       return TRUE;
975     }
976   else
977     {
978       g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
979           "ChannelRequest is no longer cancellable");
980       return FALSE;
981     }
982 }
983 
984 static TpClient *
guess_request_handler(McdRequest * self)985 guess_request_handler (McdRequest *self)
986 {
987   GList *sorted_handlers;
988   GVariant *properties;
989 
990   if (!tp_str_empty (self->preferred_handler))
991     {
992       McdClientProxy *client = _mcd_client_registry_lookup (
993           self->clients, self->preferred_handler);
994 
995         if (client != NULL)
996             return (TpClient *) client;
997     }
998 
999   properties = mcd_request_dup_properties (self);
1000   sorted_handlers = _mcd_client_registry_list_possible_handlers (
1001       self->clients, self->preferred_handler, properties,
1002       NULL, NULL);
1003   g_variant_unref (properties);
1004 
1005   if (sorted_handlers != NULL)
1006     {
1007       McdClientProxy *first = sorted_handlers->data;
1008 
1009       g_list_free (sorted_handlers);
1010       return (TpClient *) first;
1011     }
1012 
1013   return NULL;
1014 }
1015 
1016 void
_mcd_request_predict_handler(McdRequest * self)1017 _mcd_request_predict_handler (McdRequest *self)
1018 {
1019   GHashTable *properties;
1020   TpClient *predicted_handler;
1021 
1022   g_return_if_fail (!self->is_complete);
1023   g_return_if_fail (self->predicted_handler == NULL);
1024 
1025   predicted_handler = guess_request_handler (self);
1026 
1027   if (!predicted_handler)
1028     {
1029       /* No handler found. But it's possible that by the time that the
1030        * channel will be created some handler will have popped up, so we
1031        * must not destroy it. */
1032       DEBUG ("No known handler for request %s", self->object_path);
1033       return;
1034     }
1035 
1036   if (!tp_proxy_has_interface_by_id (predicted_handler,
1037       TP_IFACE_QUARK_CLIENT_INTERFACE_REQUESTS))
1038     {
1039       DEBUG ("Default handler %s for request %s doesn't want AddRequest",
1040              tp_proxy_get_bus_name (predicted_handler), self->object_path);
1041       return;
1042     }
1043 
1044   DEBUG ("Calling AddRequest on default handler %s for request %s",
1045       tp_proxy_get_bus_name (predicted_handler), self->object_path);
1046 
1047   properties = _mcd_request_dup_immutable_properties (self);
1048   tp_cli_client_interface_requests_call_add_request (predicted_handler, -1,
1049       self->object_path, properties,
1050       NULL, NULL, NULL, NULL);
1051   g_hash_table_unref (properties);
1052 
1053   /* Remember it so we can call RemoveRequest when appropriate */
1054   self->predicted_handler = g_object_ref (predicted_handler);
1055 }
1056