1 /* * Copyright (C) 2007-2008 Collabora Ltd.
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2.1 of the License, or (at your option) any later version.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 * Authors: Xavier Claessens <xclaesse@gmail.com>
24 #include <glib/gi18n-lib.h>
26 #include <telepathy-glib/enums.h>
27 #include <telepathy-glib/connection.h>
28 #include <telepathy-glib/util.h>
29 #include <telepathy-glib/dbus.h>
30 #include <telepathy-glib/proxy-subclass.h>
31 #include <telepathy-glib/gtypes.h>
33 #include <libmissioncontrol/mission-control.h>
35 #include <extensions/extensions.h>
37 #include "empathy-dispatcher.h"
38 #include "empathy-utils.h"
39 #include "empathy-tube-handler.h"
40 #include "empathy-account-manager.h"
41 #include "empathy-tp-contact-factory.h"
42 #include "empathy-chatroom-manager.h"
43 #include "empathy-utils.h"
45 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
46 #include <libempathy/empathy-debug.h>
48 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyDispatcher)
51 EmpathyAccountManager *account_manager;
53 /* connection to connection data mapping */
54 GHashTable *connections;
55 GHashTable *outstanding_classes_requests;
59 /* channels which the dispatcher is listening "invalidated" */
62 GHashTable *request_channel_class_async_ids;
63 } EmpathyDispatcherPriv;
65 G_DEFINE_TYPE (EmpathyDispatcher, empathy_dispatcher, G_TYPE_OBJECT);
75 static guint signals[LAST_SIGNAL];
76 static EmpathyDispatcher *dispatcher = NULL;
78 static GList * empathy_dispatcher_find_channel_classes
79 (EmpathyDispatcher *dispatcher, TpConnection *connection,
80 const gchar *channel_type, guint handle_type, GArray *fixed_properties);
85 EmpathyDispatcher *dispatcher;
86 EmpathyDispatchOperation *operation;
87 TpConnection *connection;
91 EmpathyContact *contact;
93 /* Properties to pass to the channel when requesting it */
95 EmpathyDispatcherRequestCb *cb;
97 gpointer *request_data;
98 } DispatcherRequestData;
103 /* Channel type specific wrapper object */
104 GObject *channel_wrapper;
109 /* ObjectPath => DispatchData.. */
110 GHashTable *dispatched_channels;
111 /* ObjectPath -> EmpathyDispatchOperations */
112 GHashTable *dispatching_channels;
113 /* ObjectPath -> EmpathyDispatchOperations */
114 GHashTable *outstanding_channels;
115 /* List of DispatcherRequestData */
116 GList *outstanding_requests;
117 /* List of requestable channel classes */
118 GPtrArray *requestable_channels;
123 EmpathyDispatcher *dispatcher;
124 TpConnection *connection;
128 EmpathyDispatcherFindChannelClassCb *callback;
130 } FindChannelRequest;
132 static DispatchData *
133 new_dispatch_data (TpChannel *channel,
134 GObject *channel_wrapper)
136 DispatchData *d = g_slice_new0 (DispatchData);
137 d->channel = g_object_ref (channel);
138 if (channel_wrapper != NULL)
139 d->channel_wrapper = g_object_ref (channel_wrapper);
145 free_dispatch_data (DispatchData *data)
147 g_object_unref (data->channel);
148 if (data->channel_wrapper != NULL)
149 g_object_unref (data->channel_wrapper);
151 g_slice_free (DispatchData, data);
154 static DispatcherRequestData *
155 new_dispatcher_request_data (EmpathyDispatcher *dispatcher,
156 TpConnection *connection,
157 const gchar *channel_type,
161 EmpathyContact *contact,
162 EmpathyDispatcherRequestCb *cb,
165 DispatcherRequestData *result = g_slice_new0 (DispatcherRequestData);
167 result->dispatcher = g_object_ref (dispatcher);
168 result->connection = connection;
170 result->channel_type = g_strdup (channel_type);
171 result->handle_type = handle_type;
172 result->handle = handle;
173 result->request = request;
176 result->contact = g_object_ref (contact);
179 result->user_data = user_data;
185 free_dispatcher_request_data (DispatcherRequestData *r)
187 g_free (r->channel_type);
189 if (r->dispatcher != NULL)
190 g_object_unref (r->dispatcher);
192 if (r->contact != NULL)
193 g_object_unref (r->contact);
195 if (r->request != NULL)
196 g_hash_table_unref (r->request);
198 g_slice_free (DispatcherRequestData, r);
201 static ConnectionData *
202 new_connection_data (void)
204 ConnectionData *cd = g_slice_new0 (ConnectionData);
206 cd->dispatched_channels = g_hash_table_new_full (g_str_hash, g_str_equal,
207 g_free, (GDestroyNotify) free_dispatch_data);
209 cd->dispatching_channels = g_hash_table_new_full (g_str_hash, g_str_equal,
210 g_free, g_object_unref);
212 cd->outstanding_channels = g_hash_table_new_full (g_str_hash, g_str_equal,
219 free_connection_data (ConnectionData *cd)
223 g_hash_table_destroy (cd->dispatched_channels);
224 g_hash_table_destroy (cd->dispatching_channels);
227 for (l = cd->outstanding_requests ; l != NULL; l = g_list_delete_link (l,l))
229 free_dispatcher_request_data (l->data);
232 if (cd->requestable_channels != NULL)
234 for (i = 0 ; i < cd->requestable_channels->len ; i++)
236 g_ptr_array_index (cd->requestable_channels, i));
237 g_ptr_array_free (cd->requestable_channels, TRUE);
242 free_find_channel_request (FindChannelRequest *r)
247 g_object_unref (r->dispatcher);
248 g_free (r->channel_type);
250 if (r->properties != NULL)
252 for (idx = 0; idx < r->properties->len ; idx++)
254 str = g_array_index (r->properties, char *, idx);
258 g_array_free (r->properties, TRUE);
261 g_slice_free (FindChannelRequest, r);
265 dispatcher_connection_invalidated_cb (TpConnection *connection,
269 EmpathyDispatcher *dispatcher)
271 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
273 DEBUG ("Error: %s", message);
274 g_hash_table_remove (priv->connections, connection);
278 dispatcher_operation_can_start (EmpathyDispatcher *self,
279 EmpathyDispatchOperation *operation,
283 const gchar *channel_type =
284 empathy_dispatch_operation_get_channel_type (operation);
286 for (l = cd->outstanding_requests; l != NULL; l = g_list_next (l))
288 DispatcherRequestData *d = (DispatcherRequestData *) l->data;
290 if (d->operation == NULL && !tp_strdiff (d->channel_type, channel_type))
300 dispatch_operation_flush_requests (EmpathyDispatcher *dispatcher,
301 EmpathyDispatchOperation *operation,
307 l = cd->outstanding_requests;
310 DispatcherRequestData *d = (DispatcherRequestData *) l->data;
315 if (d->operation == operation)
320 d->cb (NULL, error, d->user_data);
322 d->cb (operation, NULL, d->user_data);
325 cd->outstanding_requests = g_list_delete_link
326 (cd->outstanding_requests, lt);
328 free_dispatcher_request_data (d);
334 dispatcher_channel_invalidated_cb (TpProxy *proxy,
338 EmpathyDispatcher *dispatcher)
340 /* Channel went away... */
341 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
342 TpConnection *connection;
343 EmpathyDispatchOperation *operation;
345 const gchar *object_path;
347 connection = tp_channel_borrow_connection (TP_CHANNEL (proxy));
349 cd = g_hash_table_lookup (priv->connections, connection);
350 /* Connection itself invalidated? */
354 object_path = tp_proxy_get_object_path (proxy);
356 DEBUG ("Channel %s invalidated", object_path);
358 g_hash_table_remove (cd->dispatched_channels, object_path);
359 g_hash_table_remove (cd->dispatching_channels, object_path);
361 priv->channels = g_list_remove (priv->channels, proxy);
363 operation = g_hash_table_lookup (cd->outstanding_channels, object_path);
364 if (operation != NULL)
366 GError error = { domain, code, message };
367 dispatch_operation_flush_requests (dispatcher, operation, &error, cd);
368 g_hash_table_remove (cd->outstanding_channels, object_path);
369 g_object_unref (operation);
374 dispatch_operation_approved_cb (EmpathyDispatchOperation *operation,
375 EmpathyDispatcher *dispatcher)
377 g_assert (empathy_dispatch_operation_is_incoming (operation));
378 DEBUG ("Send of for dispatching: %s",
379 empathy_dispatch_operation_get_object_path (operation));
380 g_signal_emit (dispatcher, signals[DISPATCH], 0, operation);
384 dispatch_operation_claimed_cb (EmpathyDispatchOperation *operation,
385 EmpathyDispatcher *dispatcher)
387 /* Our job is done, remove the dispatch operation and mark the channel as
389 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
390 TpConnection *connection;
392 const gchar *object_path;
394 connection = empathy_dispatch_operation_get_tp_connection (operation);
395 cd = g_hash_table_lookup (priv->connections, connection);
396 g_assert (cd != NULL);
398 object_path = empathy_dispatch_operation_get_object_path (operation);
400 if (g_hash_table_lookup (cd->dispatched_channels, object_path) == NULL)
403 d = new_dispatch_data (
404 empathy_dispatch_operation_get_channel (operation),
405 empathy_dispatch_operation_get_channel_wrapper (operation));
406 g_hash_table_insert (cd->dispatched_channels,
407 g_strdup (object_path), d);
409 g_hash_table_remove (cd->dispatching_channels, object_path);
411 DEBUG ("Channel claimed: %s", object_path);
415 dispatch_operation_ready_cb (EmpathyDispatchOperation *operation,
416 EmpathyDispatcher *dispatcher)
418 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
419 TpConnection *connection;
421 EmpathyDispatchOperationState status;
423 g_signal_connect (operation, "approved",
424 G_CALLBACK (dispatch_operation_approved_cb), dispatcher);
426 g_signal_connect (operation, "claimed",
427 G_CALLBACK (dispatch_operation_claimed_cb), dispatcher);
429 /* Signal the observers */
430 DEBUG ("Send to observers: %s",
431 empathy_dispatch_operation_get_object_path (operation));
432 g_signal_emit (dispatcher, signals[OBSERVE], 0, operation);
434 empathy_dispatch_operation_start (operation);
436 /* Signal potential requestors */
437 connection = empathy_dispatch_operation_get_tp_connection (operation);
438 cd = g_hash_table_lookup (priv->connections, connection);
439 g_assert (cd != NULL);
441 g_object_ref (operation);
442 g_object_ref (dispatcher);
444 dispatch_operation_flush_requests (dispatcher, operation, NULL, cd);
445 status = empathy_dispatch_operation_get_status (operation);
446 g_object_unref (operation);
448 if (status == EMPATHY_DISPATCHER_OPERATION_STATE_CLAIMED)
451 if (status == EMPATHY_DISPATCHER_OPERATION_STATE_APPROVING)
453 DEBUG ("Send to approvers: %s",
454 empathy_dispatch_operation_get_object_path (operation));
455 g_signal_emit (dispatcher, signals[APPROVE], 0, operation);
459 g_assert (status == EMPATHY_DISPATCHER_OPERATION_STATE_DISPATCHING);
460 DEBUG ("Send of for dispatching: %s",
461 empathy_dispatch_operation_get_object_path (operation));
462 g_signal_emit (dispatcher, signals[DISPATCH], 0, operation);
465 g_object_unref (dispatcher);
469 dispatcher_start_dispatching (EmpathyDispatcher *self,
470 EmpathyDispatchOperation *operation,
473 const gchar *object_path =
474 empathy_dispatch_operation_get_object_path (operation);
476 DEBUG ("Dispatching process started for %s", object_path);
478 if (g_hash_table_lookup (cd->dispatching_channels, object_path) == NULL)
480 g_assert (g_hash_table_lookup (cd->outstanding_channels,
481 object_path) == NULL);
483 g_hash_table_insert (cd->dispatching_channels,
484 g_strdup (object_path), operation);
486 switch (empathy_dispatch_operation_get_status (operation))
488 case EMPATHY_DISPATCHER_OPERATION_STATE_PREPARING:
489 g_signal_connect (operation, "ready",
490 G_CALLBACK (dispatch_operation_ready_cb), dispatcher);
492 case EMPATHY_DISPATCHER_OPERATION_STATE_PENDING:
493 dispatch_operation_ready_cb (operation, dispatcher);
496 g_assert_not_reached ();
500 else if (empathy_dispatch_operation_get_status (operation) >=
501 EMPATHY_DISPATCHER_OPERATION_STATE_PENDING)
503 /* Already dispatching and the operation is pending, thus the observers
504 * have seen it (if applicable), so we can flush the request right away.
506 dispatch_operation_flush_requests (self, operation, NULL, cd);
511 dispatcher_flush_outstanding_operations (EmpathyDispatcher *self,
517 g_hash_table_iter_init (&iter, cd->outstanding_channels);
518 while (g_hash_table_iter_next (&iter, NULL, &value))
520 EmpathyDispatchOperation *operation = EMPATHY_DISPATCH_OPERATION (value);
522 if (dispatcher_operation_can_start (self, operation, cd))
524 dispatcher_start_dispatching (dispatcher, operation, cd);
525 g_hash_table_iter_remove (&iter);
531 dispatcher_connection_new_channel (EmpathyDispatcher *dispatcher,
532 TpConnection *connection,
533 const gchar *object_path,
534 const gchar *channel_type,
537 GHashTable *properties,
540 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
543 EmpathyDispatchOperation *operation;
545 /* Channel types we never want to dispatch because they're either deprecated
546 * or can't sensibly be dispatch (e.g. channels that should always be
548 const char *blacklist[] = {
549 TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
550 TP_IFACE_CHANNEL_TYPE_TUBES,
551 TP_IFACE_CHANNEL_TYPE_ROOM_LIST,
555 cd = g_hash_table_lookup (priv->connections, connection);
557 /* Don't bother with channels we have already dispatched or are dispatching
558 * currently. This can happen when NewChannel(s) is fired after
559 * RequestChannel/CreateChannel/EnsureChannel */
560 if (g_hash_table_lookup (cd->dispatched_channels, object_path) != NULL)
563 if (g_hash_table_lookup (cd->dispatching_channels, object_path) != NULL)
566 /* Should never occur, but just in case a CM fires spurious NewChannel(s)
568 if (g_hash_table_lookup (cd->outstanding_channels, object_path) != NULL)
571 /* Only pick up non-requested text and file channels. For all other it
572 * doesn't make sense to handle it if we didn't request it. The same goes
573 * for channels we discovered by the Channels property or ListChannels */
574 if (!incoming && tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT)
575 && tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER))
577 DEBUG ("Ignoring incoming channel of type %s on %s",
578 channel_type, object_path);
582 for (i = 0 ; blacklist[i] != NULL; i++)
584 if (!tp_strdiff (channel_type, blacklist[i]))
586 DEBUG ("Ignoring blacklisted channel type %s on %s",
587 channel_type, object_path);
592 DEBUG ("New channel of type %s on %s", channel_type, object_path);
594 if (properties == NULL)
595 channel = tp_channel_new (connection, object_path, channel_type,
596 handle_type, handle, NULL);
598 channel = tp_channel_new_from_properties (connection, object_path,
601 g_signal_connect (channel, "invalidated",
602 G_CALLBACK (dispatcher_channel_invalidated_cb),
605 priv->channels = g_list_prepend (priv->channels, channel);
607 operation = empathy_dispatch_operation_new (connection, channel, NULL,
610 g_object_unref (channel);
614 /* Request could either be by us or by a remote party. If there are no
615 * outstanding requests for this channel type we can assume it's remote.
616 * Otherwise we wait untill they are all satisfied */
617 if (dispatcher_operation_can_start (dispatcher, operation, cd))
618 dispatcher_start_dispatching (dispatcher, operation, cd);
620 g_hash_table_insert (cd->outstanding_channels,
621 g_strdup (object_path), operation);
625 dispatcher_start_dispatching (dispatcher, operation, cd);
630 dispatcher_connection_new_channel_cb (TpConnection *connection,
631 const gchar *object_path,
632 const gchar *channel_type,
635 gboolean suppress_handler,
639 EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object);
641 /* Empathy heavily abuses surpress handler (don't try this at home), if
642 * surpress handler is true then it is an outgoing channel, which is
643 * requested either by us or some other party (like the megaphone applet).
644 * Otherwise it's an incoming channel */
645 dispatcher_connection_new_channel (dispatcher, connection,
646 object_path, channel_type, handle_type, handle, NULL, !suppress_handler);
650 dispatcher_connection_new_channel_with_properties (EmpathyDispatcher *dispatcher,
651 TpConnection *connection,
652 const gchar *object_path,
653 GHashTable *properties)
655 const gchar *channel_type;
662 channel_type = tp_asv_get_string (properties,
663 TP_IFACE_CHANNEL ".ChannelType");
664 if (channel_type == NULL)
666 g_message ("%s had an invalid ChannelType property", object_path);
670 handle_type = tp_asv_get_uint32 (properties,
671 TP_IFACE_CHANNEL ".TargetHandleType", &valid);
674 g_message ("%s had an invalid TargetHandleType property", object_path);
678 handle = tp_asv_get_uint32 (properties,
679 TP_IFACE_CHANNEL ".TargetHandle", &valid);
682 g_message ("%s had an invalid TargetHandle property", object_path);
686 /* We assume there is no channel dispather, so we're the only one dispatching
687 * it. Which means that a requested channel it is outgoing one */
688 requested = tp_asv_get_boolean (properties,
689 TP_IFACE_CHANNEL ".Requested", &valid);
692 g_message ("%s had an invalid Requested property", object_path);
696 dispatcher_connection_new_channel (dispatcher, connection,
697 object_path, channel_type, handle_type, handle, properties, !requested);
701 dispatcher_connection_new_channels_cb (TpConnection *connection,
702 const GPtrArray *channels,
706 EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object);
709 for (i = 0; i < channels->len ; i++)
711 GValueArray *arr = g_ptr_array_index (channels, i);
712 const gchar *object_path;
713 GHashTable *properties;
715 object_path = g_value_get_boxed (g_value_array_get_nth (arr, 0));
716 properties = g_value_get_boxed (g_value_array_get_nth (arr, 1));
718 dispatcher_connection_new_channel_with_properties (dispatcher,
719 connection, object_path, properties);
724 dispatcher_connection_got_all (TpProxy *proxy,
725 GHashTable *properties,
730 EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object);
731 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
733 GPtrArray *requestable_channels;
736 DEBUG ("Error: %s", error->message);
740 channels = tp_asv_get_boxed (properties, "Channels",
741 TP_ARRAY_TYPE_CHANNEL_DETAILS_LIST);
743 if (channels == NULL)
744 DEBUG ("No Channels property !?! on connection");
746 dispatcher_connection_new_channels_cb (TP_CONNECTION (proxy),
747 channels, NULL, object);
749 requestable_channels = tp_asv_get_boxed (properties,
750 "RequestableChannelClasses", TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST);
752 if (requestable_channels == NULL)
753 DEBUG ("No RequestableChannelClasses property !?! on connection");
758 FindChannelRequest *request;
761 cd = g_hash_table_lookup (priv->connections, proxy);
762 g_assert (cd != NULL);
764 cd->requestable_channels = g_boxed_copy (
765 TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, requestable_channels);
767 requests = g_hash_table_lookup (priv->outstanding_classes_requests,
770 for (l = requests; l != NULL; l = l->next)
774 retval = empathy_dispatcher_find_channel_classes (dispatcher,
775 TP_CONNECTION (proxy), request->channel_type,
776 request->handle_type, request->properties);
777 request->callback (retval, request->user_data);
779 free_find_channel_request (request);
780 g_list_free (retval);
783 g_list_free (requests);
785 g_hash_table_remove (priv->outstanding_classes_requests, proxy);
790 dispatcher_connection_list_channels_cb (TpConnection *connection,
791 const GPtrArray *channels,
800 DEBUG ("Error: %s", error->message);
804 for (i = 0; i < channels->len; i++)
808 values = g_ptr_array_index (channels, i);
809 /* We don't have any extra info, so assume already existing channels are
811 dispatcher_connection_new_channel (EMPATHY_DISPATCHER (dispatcher),
813 g_value_get_boxed (g_value_array_get_nth (values, 0)),
814 g_value_get_string (g_value_array_get_nth (values, 1)),
815 g_value_get_uint (g_value_array_get_nth (values, 2)),
816 g_value_get_uint (g_value_array_get_nth (values, 3)),
822 dispatcher_connection_advertise_capabilities_cb (TpConnection *connection,
823 const GPtrArray *capabilities,
829 DEBUG ("Error: %s", error->message);
833 dispatcher_new_connection_cb (EmpathyAccountManager *manager,
834 TpConnection *connection,
835 EmpathyDispatcher *dispatcher)
837 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
838 GPtrArray *capabilities;
841 const gchar *remove = NULL;
843 if (g_hash_table_lookup (priv->connections, connection) != NULL)
846 g_hash_table_insert (priv->connections, g_object_ref (connection),
847 new_connection_data ());
849 g_signal_connect (connection, "invalidated",
850 G_CALLBACK (dispatcher_connection_invalidated_cb), dispatcher);
852 if (tp_proxy_has_interface_by_id (TP_PROXY (connection),
853 TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS))
855 tp_cli_connection_interface_requests_connect_to_new_channels (connection,
856 dispatcher_connection_new_channels_cb,
857 NULL, NULL, G_OBJECT (dispatcher), NULL);
859 tp_cli_dbus_properties_call_get_all (connection, -1,
860 TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
861 dispatcher_connection_got_all,
862 NULL, NULL, G_OBJECT (dispatcher));
866 tp_cli_connection_connect_to_new_channel (connection,
867 dispatcher_connection_new_channel_cb,
868 NULL, NULL, G_OBJECT (dispatcher), NULL);
870 tp_cli_connection_call_list_channels (connection, -1,
871 dispatcher_connection_list_channels_cb, NULL, NULL,
872 G_OBJECT (dispatcher));
876 /* Advertise VoIP capabilities */
877 capabilities = g_ptr_array_sized_new (1);
878 cap_type = dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING,
879 G_TYPE_UINT, G_TYPE_INVALID);
880 g_value_init (&cap, cap_type);
881 g_value_take_boxed (&cap, dbus_g_type_specialized_construct (cap_type));
882 dbus_g_type_struct_set (&cap,
883 0, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
884 1, TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
885 TP_CHANNEL_MEDIA_CAPABILITY_VIDEO |
886 TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_STUN |
887 TP_CHANNEL_MEDIA_CAPABILITY_NAT_TRAVERSAL_GTALK_P2P, G_MAXUINT);
888 g_ptr_array_add (capabilities, g_value_get_boxed (&cap));
890 tp_cli_connection_interface_capabilities_call_advertise_capabilities (
891 connection, -1, capabilities, &remove,
892 dispatcher_connection_advertise_capabilities_cb,
893 NULL, NULL, G_OBJECT (dispatcher));
895 g_value_unset (&cap);
896 g_ptr_array_free (capabilities, TRUE);
900 remove_idle_handlers (gpointer key,
906 source_id = GPOINTER_TO_UINT (value);
907 g_source_remove (source_id);
911 dispatcher_constructor (GType type,
912 guint n_construct_params,
913 GObjectConstructParam *construct_params)
917 if (dispatcher == NULL)
919 retval = G_OBJECT_CLASS (empathy_dispatcher_parent_class)->constructor
920 (type, n_construct_params, construct_params);
922 dispatcher = EMPATHY_DISPATCHER (retval);
923 g_object_add_weak_pointer (retval, (gpointer) &dispatcher);
927 retval = g_object_ref (dispatcher);
934 dispatcher_finalize (GObject *object)
936 EmpathyDispatcherPriv *priv = GET_PRIV (object);
942 if (priv->request_channel_class_async_ids != NULL)
944 g_hash_table_foreach (priv->request_channel_class_async_ids,
945 remove_idle_handlers, NULL);
946 g_hash_table_destroy (priv->request_channel_class_async_ids);
949 g_signal_handlers_disconnect_by_func (priv->account_manager,
950 dispatcher_new_connection_cb, object);
952 for (l = priv->channels; l; l = l->next)
954 g_signal_handlers_disconnect_by_func (l->data,
955 dispatcher_channel_invalidated_cb, object);
958 g_list_free (priv->channels);
960 g_hash_table_iter_init (&iter, priv->connections);
961 while (g_hash_table_iter_next (&iter, &connection, NULL))
963 g_signal_handlers_disconnect_by_func (connection,
964 dispatcher_connection_invalidated_cb, object);
967 g_hash_table_iter_init (&iter, priv->outstanding_classes_requests);
968 while (g_hash_table_iter_next (&iter, &connection, (gpointer *) &list))
970 g_list_foreach (list, (GFunc) free_find_channel_request, NULL);
974 g_object_unref (priv->account_manager);
975 g_object_unref (priv->mc);
977 g_hash_table_destroy (priv->connections);
978 g_hash_table_destroy (priv->outstanding_classes_requests);
982 empathy_dispatcher_class_init (EmpathyDispatcherClass *klass)
984 GObjectClass *object_class = G_OBJECT_CLASS (klass);
986 object_class->finalize = dispatcher_finalize;
987 object_class->constructor = dispatcher_constructor;
990 g_signal_new ("observe",
991 G_TYPE_FROM_CLASS (klass),
995 g_cclosure_marshal_VOID__OBJECT,
997 1, EMPATHY_TYPE_DISPATCH_OPERATION);
1000 g_signal_new ("approve",
1001 G_TYPE_FROM_CLASS (klass),
1005 g_cclosure_marshal_VOID__OBJECT,
1007 1, EMPATHY_TYPE_DISPATCH_OPERATION);
1010 g_signal_new ("dispatch",
1011 G_TYPE_FROM_CLASS (klass),
1015 g_cclosure_marshal_VOID__OBJECT,
1017 1, EMPATHY_TYPE_DISPATCH_OPERATION);
1019 g_type_class_add_private (object_class, sizeof (EmpathyDispatcherPriv));
1024 empathy_dispatcher_init (EmpathyDispatcher *dispatcher)
1026 GList *connections, *l;
1027 EmpathyDispatcherPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (dispatcher,
1028 EMPATHY_TYPE_DISPATCHER, EmpathyDispatcherPriv);
1030 dispatcher->priv = priv;
1031 priv->mc = empathy_mission_control_dup_singleton ();
1032 priv->account_manager = empathy_account_manager_dup_singleton ();
1034 g_signal_connect (priv->account_manager, "new-connection",
1035 G_CALLBACK (dispatcher_new_connection_cb),
1038 priv->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
1039 g_object_unref, (GDestroyNotify) free_connection_data);
1041 priv->outstanding_classes_requests = g_hash_table_new_full (g_direct_hash,
1042 g_direct_equal, g_object_unref, NULL);
1044 priv->channels = NULL;
1046 connections = empathy_account_manager_dup_connections (priv->account_manager);
1047 for (l = connections; l; l = l->next)
1049 dispatcher_new_connection_cb (priv->account_manager, l->data, dispatcher);
1050 g_object_unref (l->data);
1052 g_list_free (connections);
1054 priv->request_channel_class_async_ids = g_hash_table_new (g_direct_hash,
1059 empathy_dispatcher_dup_singleton (void)
1061 return EMPATHY_DISPATCHER (g_object_new (EMPATHY_TYPE_DISPATCHER, NULL));
1065 dispatcher_request_failed (EmpathyDispatcher *dispatcher,
1066 DispatcherRequestData *request_data,
1067 const GError *error)
1069 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1070 ConnectionData *conn_data;
1072 conn_data = g_hash_table_lookup (priv->connections, request_data->connection);
1073 if (request_data->cb != NULL)
1074 request_data->cb (NULL, error, request_data->user_data);
1076 conn_data->outstanding_requests =
1077 g_list_remove (conn_data->outstanding_requests, request_data);
1078 free_dispatcher_request_data (request_data);
1082 dispatcher_connection_new_requested_channel (EmpathyDispatcher *dispatcher,
1083 DispatcherRequestData *request_data,
1084 const gchar *object_path,
1085 GHashTable *properties,
1086 const GError *error)
1088 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1089 EmpathyDispatchOperation *operation = NULL;
1090 ConnectionData *conn_data;
1092 conn_data = g_hash_table_lookup (priv->connections,
1093 request_data->connection);
1097 DEBUG ("Channel request failed: %s", error->message);
1099 dispatcher_request_failed (dispatcher, request_data, error);
1104 operation = g_hash_table_lookup (conn_data->outstanding_channels,
1107 if (operation != NULL)
1108 g_hash_table_remove (conn_data->outstanding_channels, object_path);
1110 operation = g_hash_table_lookup (conn_data->dispatching_channels,
1113 if (operation == NULL)
1115 DispatchData *data = g_hash_table_lookup (conn_data->dispatched_channels,
1120 operation = empathy_dispatch_operation_new_with_wrapper (
1121 request_data->connection,
1122 data->channel, request_data->contact, FALSE,
1123 data->channel_wrapper);
1129 if (properties != NULL)
1130 channel = tp_channel_new_from_properties (request_data->connection,
1131 object_path, properties, NULL);
1133 channel = tp_channel_new (request_data->connection, object_path,
1134 request_data->channel_type, request_data->handle_type,
1135 request_data->handle, NULL);
1137 g_signal_connect (channel, "invalidated",
1138 G_CALLBACK (dispatcher_channel_invalidated_cb),
1139 request_data->dispatcher);
1141 priv->channels = g_list_prepend (priv->channels, channel);
1143 operation = empathy_dispatch_operation_new (request_data->connection,
1144 channel, request_data->contact, FALSE);
1145 g_object_unref (channel);
1150 /* Already existed set potential extra information */
1151 g_object_set (G_OBJECT (operation),
1152 "contact", request_data->contact,
1156 request_data->operation = operation;
1158 /* (pre)-approve this right away as we requested it
1159 * This might cause the channel to be claimed, in which case the operation
1160 * will disappear. So ref it, and check the status before starting the
1163 g_object_ref (operation);
1164 empathy_dispatch_operation_approve (operation);
1166 if (empathy_dispatch_operation_get_status (operation) <
1167 EMPATHY_DISPATCHER_OPERATION_STATE_APPROVING)
1168 dispatcher_start_dispatching (request_data->dispatcher, operation,
1171 g_object_unref (operation);
1174 dispatcher_flush_outstanding_operations (request_data->dispatcher,
1179 dispatcher_request_channel_cb (TpConnection *connection,
1180 const gchar *object_path,
1181 const GError *error,
1183 GObject *weak_object)
1185 EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (weak_object);
1186 DispatcherRequestData *request_data = (DispatcherRequestData *) user_data;
1188 dispatcher_connection_new_requested_channel (dispatcher,
1189 request_data, object_path, NULL, error);
1193 dispatcher_request_channel (DispatcherRequestData *request_data)
1195 tp_cli_connection_call_request_channel (request_data->connection, -1,
1196 request_data->channel_type,
1197 request_data->handle_type,
1198 request_data->handle,
1199 TRUE, dispatcher_request_channel_cb,
1200 request_data, NULL, G_OBJECT (request_data->dispatcher));
1204 empathy_dispatcher_chat_with_contact (EmpathyContact *contact,
1205 EmpathyDispatcherRequestCb *callback,
1208 EmpathyDispatcher *dispatcher;
1209 EmpathyDispatcherPriv *priv;
1210 TpConnection *connection;
1211 ConnectionData *connection_data;
1212 DispatcherRequestData *request_data;
1214 g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1216 dispatcher = empathy_dispatcher_dup_singleton ();
1217 priv = GET_PRIV (dispatcher);
1219 connection = empathy_contact_get_connection (contact);
1220 connection_data = g_hash_table_lookup (priv->connections, connection);
1222 /* The contact handle might not be known yet */
1223 request_data = new_dispatcher_request_data (dispatcher, connection,
1224 TP_IFACE_CHANNEL_TYPE_TEXT, TP_HANDLE_TYPE_CONTACT,
1225 empathy_contact_get_handle (contact), NULL, contact, callback, user_data);
1227 connection_data->outstanding_requests = g_list_prepend
1228 (connection_data->outstanding_requests, request_data);
1230 dispatcher_request_channel (request_data);
1232 g_object_unref (dispatcher);
1237 EmpathyDispatcher *dispatcher;
1238 EmpathyDispatcherRequestCb *callback;
1240 } ChatWithContactIdData;
1243 dispatcher_chat_with_contact_id_cb (EmpathyTpContactFactory *factory,
1244 EmpathyContact *contact,
1245 const GError *error,
1247 GObject *weak_object)
1249 ChatWithContactIdData *data = user_data;
1253 /* FIXME: Should call data->callback with the error */
1254 DEBUG ("Error: %s", error->message);
1258 empathy_dispatcher_chat_with_contact (contact, data->callback, data->user_data);
1261 g_object_unref (data->dispatcher);
1262 g_slice_free (ChatWithContactIdData, data);
1266 empathy_dispatcher_chat_with_contact_id (TpConnection *connection,
1267 const gchar *contact_id,
1268 EmpathyDispatcherRequestCb *callback,
1271 EmpathyDispatcher *dispatcher;
1272 EmpathyTpContactFactory *factory;
1273 ChatWithContactIdData *data;
1275 g_return_if_fail (TP_IS_CONNECTION (connection));
1276 g_return_if_fail (!EMP_STR_EMPTY (contact_id));
1278 dispatcher = empathy_dispatcher_dup_singleton ();
1279 factory = empathy_tp_contact_factory_dup_singleton (connection);
1280 data = g_slice_new0 (ChatWithContactIdData);
1281 data->dispatcher = dispatcher;
1282 data->callback = callback;
1283 data->user_data = user_data;
1284 empathy_tp_contact_factory_get_from_id (factory, contact_id,
1285 dispatcher_chat_with_contact_id_cb, data, NULL, NULL);
1287 g_object_unref (factory);
1291 dispatcher_request_handles_cb (TpConnection *connection,
1292 const GArray *handles,
1293 const GError *error,
1297 DispatcherRequestData *request_data = (DispatcherRequestData *) user_data;
1301 EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (object);
1302 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1305 cd = g_hash_table_lookup (priv->connections, request_data->connection);
1307 if (request_data->cb)
1308 request_data->cb (NULL, error, request_data->user_data);
1310 cd->outstanding_requests = g_list_remove (cd->outstanding_requests,
1313 free_dispatcher_request_data (request_data);
1315 dispatcher_flush_outstanding_operations (dispatcher, cd);
1319 request_data->handle = g_array_index (handles, guint, 0);
1320 dispatcher_request_channel (request_data);
1324 empathy_dispatcher_join_muc (TpConnection *connection,
1325 const gchar *roomname,
1326 EmpathyDispatcherRequestCb *callback,
1329 EmpathyDispatcher *dispatcher;
1330 EmpathyDispatcherPriv *priv;
1331 DispatcherRequestData *request_data;
1332 ConnectionData *connection_data;
1333 const gchar *names[] = { roomname, NULL };
1335 g_return_if_fail (TP_IS_CONNECTION (connection));
1336 g_return_if_fail (!EMP_STR_EMPTY (roomname));
1338 dispatcher = empathy_dispatcher_dup_singleton ();
1339 priv = GET_PRIV (dispatcher);
1341 connection_data = g_hash_table_lookup (priv->connections, connection);
1343 /* Don't know the room handle yet */
1344 request_data = new_dispatcher_request_data (dispatcher, connection,
1345 TP_IFACE_CHANNEL_TYPE_TEXT, TP_HANDLE_TYPE_ROOM, 0, NULL,
1346 NULL, callback, user_data);
1348 connection_data->outstanding_requests = g_list_prepend
1349 (connection_data->outstanding_requests, request_data);
1351 tp_cli_connection_call_request_handles (connection, -1,
1352 TP_HANDLE_TYPE_ROOM, names,
1353 dispatcher_request_handles_cb, request_data, NULL,
1354 G_OBJECT (dispatcher));
1356 g_object_unref (dispatcher);
1360 dispatcher_create_channel_cb (TpConnection *connect,
1361 const gchar *object_path,
1362 GHashTable *properties,
1363 const GError *error,
1365 GObject *weak_object)
1367 EmpathyDispatcher *dispatcher = EMPATHY_DISPATCHER (weak_object);
1368 DispatcherRequestData *request_data = (DispatcherRequestData *) user_data;
1370 dispatcher_connection_new_requested_channel (dispatcher,
1371 request_data, object_path, properties, error);
1375 empathy_dispatcher_create_channel (EmpathyDispatcher *dispatcher,
1376 TpConnection *connection,
1377 GHashTable *request,
1378 EmpathyDispatcherRequestCb *callback,
1381 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1382 ConnectionData *connection_data;
1383 DispatcherRequestData *request_data;
1384 const gchar *channel_type;
1389 g_return_if_fail (EMPATHY_IS_DISPATCHER (dispatcher));
1390 g_return_if_fail (TP_IS_CONNECTION (connection));
1391 g_return_if_fail (request != NULL);
1393 connection_data = g_hash_table_lookup (priv->connections, connection);
1394 g_assert (connection_data != NULL);
1396 channel_type = tp_asv_get_string (request, TP_IFACE_CHANNEL ".ChannelType");
1398 handle_type = tp_asv_get_uint32 (request,
1399 TP_IFACE_CHANNEL ".TargetHandleType", &valid);
1401 handle_type = TP_UNKNOWN_HANDLE_TYPE;
1403 handle = tp_asv_get_uint32 (request, TP_IFACE_CHANNEL ".TargetHandle", NULL);
1405 request_data = new_dispatcher_request_data (dispatcher, connection,
1406 channel_type, handle_type, handle, request,
1407 NULL, callback, user_data);
1409 connection_data->outstanding_requests = g_list_prepend
1410 (connection_data->outstanding_requests, request_data);
1412 tp_cli_connection_interface_requests_call_create_channel (
1413 request_data->connection, -1,
1414 request_data->request, dispatcher_create_channel_cb, request_data, NULL,
1415 G_OBJECT (request_data->dispatcher));
1420 gboolean not_generic;
1421 const char *channel_namespace;
1423 } PropertiesMatcherData;
1426 match_with_properties (gpointer key,
1431 PropertiesMatcherData *data = user_data;
1436 /* discard generic properties, as we already checked them */
1437 if (!g_str_has_prefix ((char *) key, data->channel_namespace))
1440 data->not_generic = TRUE;
1442 for (idx = 0; idx < data->properties->len; idx++)
1444 /* if |key| exists in the properties, it's fine */
1445 if (!tp_strdiff ((char *) key, g_array_index (data->properties,
1452 empathy_dispatcher_find_channel_classes (EmpathyDispatcher *dispatcher,
1453 TpConnection *connection,
1454 const gchar *channel_type,
1456 GArray *fixed_properties)
1458 EmpathyDispatcherPriv *priv = GET_PRIV (dispatcher);
1460 GHashTable *fixed_props;
1463 GList *matching_classes;
1464 const gchar *c_type;
1466 gboolean valid, found;
1470 g_return_val_if_fail (channel_type != NULL, NULL);
1471 g_return_val_if_fail (handle_type != 0, NULL);
1473 cd = g_hash_table_lookup (priv->connections, connection);
1478 classes = cd->requestable_channels;
1479 if (classes == NULL)
1483 matching_classes = NULL;
1485 for (i = 0; i < classes->len; i++)
1487 class = g_ptr_array_index (classes, i);
1488 val = g_value_array_get_nth (class, 0);
1490 /* if the class doesn't match channel type discard it. */
1491 fixed_props = g_value_get_boxed (val);
1492 c_type = tp_asv_get_string (fixed_props,
1493 TP_IFACE_CHANNEL ".ChannelType");
1495 if (tp_strdiff (channel_type, c_type))
1498 /* we have the right channel type, see if the handle type matches */
1499 h_type = tp_asv_get_uint32 (fixed_props,
1500 TP_IFACE_CHANNEL ".TargetHandleType", &valid);
1502 if (!valid || handle_type != h_type)
1505 /* now we should ensure that the fixed props that we specified
1506 * are the only values inside the hash table.
1508 if (fixed_properties != NULL)
1510 PropertiesMatcherData *data;
1512 data = g_slice_new0 (PropertiesMatcherData);
1513 data->mismatch = FALSE;
1514 data->not_generic = FALSE;
1515 data->channel_namespace = channel_type;
1516 data->properties = fixed_properties;
1517 g_hash_table_foreach (fixed_props, match_with_properties, data);
1519 found = (!data->mismatch && data->not_generic);
1521 g_slice_free (PropertiesMatcherData, data);
1528 matching_classes = g_list_prepend (matching_classes, class);
1531 if (matching_classes != NULL)
1532 return g_list_reverse (matching_classes);
1538 find_channel_class_idle_cb (gpointer user_data)
1542 FindChannelRequest *request = user_data;
1544 gboolean is_ready = TRUE;
1545 EmpathyDispatcherPriv *priv = GET_PRIV (request->dispatcher);
1547 g_hash_table_remove (priv->request_channel_class_async_ids, request);
1549 cd = g_hash_table_lookup (priv->connections, request->connection);
1553 else if (cd->requestable_channels == NULL)
1558 retval = empathy_dispatcher_find_channel_classes (request->dispatcher,
1559 request->connection, request->channel_type, request->handle_type,
1560 request->properties);
1562 request->callback (retval, request->user_data);
1563 free_find_channel_request (request);
1564 g_list_free (retval);
1569 requests = g_hash_table_lookup (priv->outstanding_classes_requests,
1570 request->connection);
1571 requests = g_list_prepend (requests, request);
1573 g_hash_table_insert (priv->outstanding_classes_requests,
1574 request->connection, requests);
1580 setup_varargs (va_list var_args,
1581 const char *channel_namespace,
1582 const char *first_property_name)
1588 if (first_property_name == NULL)
1591 name = first_property_name;
1592 properties = g_array_new (TRUE, TRUE, sizeof (char *));
1594 while (name != NULL)
1596 name_full = g_strconcat (channel_namespace, ".", name, NULL);
1597 properties = g_array_append_val (properties, name_full);
1598 name = va_arg (var_args, char *);
1605 * empathy_dispatcher_find_requestable_channel_classes:
1606 * @dispatcher: an #EmpathyDispatcher
1607 * @connection: a #TpConnection
1608 * @channel_type: a string identifying the type of the channel to lookup
1609 * @handle_type: the handle type for the channel
1610 * @first_property_name: %NULL, or the name of the first fixed property,
1611 * followed optionally by more names, followed by %NULL.
1613 * Returns all the channel classes that a client can request for the connection
1614 * @connection, of the type identified by @channel_type, @handle_type and the
1615 * fixed properties list.
1616 * If @first_property_name is %NULL, no additional fixed properties will be
1617 * specified and the function will return all the requestable classes for
1618 * the specified channel type and handle type.
1619 * Note that this function may return %NULL without performing any lookup if
1620 * @connection is not ready. To ensure that @connection is always ready,
1621 * use the empathy_dispatcher_find_requestable_channel_classes_async() variant.
1623 * Return value: a #GList of #GValueArray objects, where the first element in
1624 * the array is a #GHashTable of the fixed properties, and the second is
1625 * a #GStrv of the allowed properties for the class.
1628 empathy_dispatcher_find_requestable_channel_classes
1629 (EmpathyDispatcher *dispatcher,
1630 TpConnection *connection,
1631 const gchar *channel_type,
1633 const char *first_property_name,
1638 EmpathyDispatcherPriv *priv;
1643 g_return_val_if_fail (EMPATHY_IS_DISPATCHER (dispatcher), NULL);
1644 g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL);
1645 g_return_val_if_fail (channel_type != NULL, NULL);
1646 g_return_val_if_fail (handle_type != 0, NULL);
1648 priv = GET_PRIV (dispatcher);
1650 va_start (var_args, first_property_name);
1652 properties = setup_varargs (var_args, channel_type, first_property_name);
1656 retval = empathy_dispatcher_find_channel_classes (dispatcher, connection,
1657 channel_type, handle_type, properties);
1659 /* free the properties array */
1660 for (idx = 0; idx < properties->len ; idx++)
1662 str = g_array_index (properties, char *, idx);
1666 g_array_free (properties, TRUE);
1672 * empathy_dispatcher_find_requestable_channel_classes_async:
1673 * @dispatcher: an #EmpathyDispatcher
1674 * @connection: a #TpConnection
1675 * @channel_type: a string identifying the type of the channel to lookup
1676 * @handle_type: the handle type for the channel
1677 * @callback: the callback to call when @connection is ready
1678 * @user_data: the user data to pass to @callback
1679 * @first_property_name: %NULL, or the name of the first fixed property,
1680 * followed optionally by more names, followed by %NULL.
1682 * Please see the documentation of
1683 * empathy_dispatcher_find_requestable_channel_classes() for a detailed
1684 * description of this function.
1687 empathy_dispatcher_find_requestable_channel_classes_async
1688 (EmpathyDispatcher *dispatcher,
1689 TpConnection *connection,
1690 const gchar *channel_type,
1692 EmpathyDispatcherFindChannelClassCb callback,
1694 const char *first_property_name,
1699 FindChannelRequest *request;
1700 EmpathyDispatcherPriv *priv;
1703 g_return_if_fail (EMPATHY_IS_DISPATCHER (dispatcher));
1704 g_return_if_fail (TP_IS_CONNECTION (connection));
1705 g_return_if_fail (channel_type != NULL);
1706 g_return_if_fail (handle_type != 0);
1708 priv = GET_PRIV (dispatcher);
1710 va_start (var_args, first_property_name);
1712 properties = setup_varargs (var_args, channel_type, first_property_name);
1716 /* append another request for this connection */
1717 request = g_slice_new0 (FindChannelRequest);
1718 request->dispatcher = dispatcher;
1719 request->channel_type = g_strdup (channel_type);
1720 request->handle_type = handle_type;
1721 request->connection = connection;
1722 request->callback = callback;
1723 request->user_data = user_data;
1724 request->properties = properties;
1726 source_id = g_idle_add (find_channel_class_idle_cb, request);
1728 g_hash_table_insert (priv->request_channel_class_async_ids,
1729 request, GUINT_TO_POINTER (source_id));