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