1 /* * Copyright (C) 2007-2009 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>
18 * Sjoerd Simons <sjoerd.simons@collabora.co.uk>
19 * Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
22 #define DISPATCHER_BUS_NAME TP_CLIENT_BUS_NAME_BASE "Empathy"
23 #define DISPATCHER_OBJECT_PATH TP_CLIENT_OBJECT_PATH_BASE "Empathy"
29 #include <glib/gi18n-lib.h>
31 #include <telepathy-glib/account-manager.h>
32 #include <telepathy-glib/enums.h>
33 #include <telepathy-glib/connection.h>
34 #include <telepathy-glib/util.h>
35 #include <telepathy-glib/dbus.h>
36 #include <telepathy-glib/proxy-subclass.h>
37 #include <telepathy-glib/gtypes.h>
38 #include <telepathy-glib/defs.h>
39 #include <telepathy-glib/interfaces.h>
41 #include <extensions/extensions.h>
43 #include "empathy-dispatcher.h"
44 #include "empathy-utils.h"
45 #include "empathy-tp-contact-factory.h"
46 #include "empathy-chatroom-manager.h"
47 #include "empathy-utils.h"
49 #define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER
50 #include <libempathy/empathy-debug.h>
52 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyDispatcher)
55 gboolean dispose_has_run;
57 TpAccountManager *account_manager;
58 /* connection to connection data mapping */
59 GHashTable *connections;
60 GHashTable *outstanding_classes_requests;
62 /* reffed (TpAccount *) => gulong
63 * Signal handler ID of the "status-changed" signal */
64 GHashTable *status_changed_handlers;
65 } EmpathyDispatcherPriv;
67 G_DEFINE_TYPE (EmpathyDispatcher, empathy_dispatcher, G_TYPE_OBJECT);
69 static EmpathyDispatcher *dispatcher = NULL;
71 static void dispatcher_init_connection_if_needed (
72 EmpathyDispatcher *dispatcher,
73 TpConnection *connection);
75 static GList * empathy_dispatcher_find_channel_classes
76 (EmpathyDispatcher *dispatcher, TpConnection *connection,
77 const gchar *channel_type, guint handle_type, GArray *fixed_properties);
82 /* Channel type specific wrapper object */
83 GObject *channel_wrapper;
88 /* List of requestable channel classes */
89 GPtrArray *requestable_channels;
94 EmpathyDispatcher *dispatcher;
95 TpConnection *connection;
99 EmpathyDispatcherFindChannelClassCb *callback;
101 } FindChannelRequest;
103 static ConnectionData *
104 new_connection_data (void)
106 return g_slice_new0 (ConnectionData);
110 free_connection_data (ConnectionData *cd)
114 if (cd->requestable_channels != NULL)
116 for (i = 0 ; i < cd->requestable_channels->len ; i++)
118 g_ptr_array_index (cd->requestable_channels, i));
119 g_ptr_array_free (cd->requestable_channels, TRUE);
124 free_find_channel_request (FindChannelRequest *r)
129 g_object_unref (r->dispatcher);
130 g_free (r->channel_type);
132 if (r->properties != NULL)
134 for (idx = 0; idx < r->properties->len ; idx++)
136 str = g_array_index (r->properties, char *, idx);
140 g_array_free (r->properties, TRUE);
143 g_slice_free (FindChannelRequest, r);
147 dispatcher_connection_invalidated_cb (TpConnection *connection,
151 EmpathyDispatcher *self)
153 EmpathyDispatcherPriv *priv = GET_PRIV (self);
155 DEBUG ("Error: %s", message);
157 g_hash_table_remove (priv->connections, connection);
161 got_connection_rcc (EmpathyDispatcher *self,
162 TpConnection *connection)
164 EmpathyDispatcherPriv *priv = GET_PRIV (self);
165 TpCapabilities *caps;
168 FindChannelRequest *request;
171 caps = tp_connection_get_capabilities (connection);
172 g_assert (caps != NULL);
174 cd = g_hash_table_lookup (priv->connections, connection);
175 g_assert (cd != NULL);
177 cd->requestable_channels = g_boxed_copy (
178 TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST,
179 tp_capabilities_get_channel_classes (caps));
181 requests = g_hash_table_lookup (priv->outstanding_classes_requests,
184 for (l = requests; l != NULL; l = l->next)
188 retval = empathy_dispatcher_find_channel_classes (self,
189 connection, request->channel_type,
190 request->handle_type, request->properties);
191 request->callback (retval, request->user_data);
193 free_find_channel_request (request);
194 g_list_free (retval);
197 g_list_free (requests);
199 g_hash_table_remove (priv->outstanding_classes_requests, connection);
203 connection_prepare_cb (GObject *source,
204 GAsyncResult *result,
207 EmpathyDispatcher *self = EMPATHY_DISPATCHER (user_data);
208 GError *error = NULL;
209 TpConnection *connection = (TpConnection *) source;
211 if (!tp_proxy_prepare_finish (source, result, &error))
213 DEBUG ("Error: %s", error->message);
215 g_error_free (error);
219 got_connection_rcc (self, connection);
222 g_object_unref (self);
226 dispatcher_init_connection_if_needed (EmpathyDispatcher *self,
227 TpConnection *connection)
229 EmpathyDispatcherPriv *priv = GET_PRIV (self);
230 GQuark features[] = { TP_CONNECTION_FEATURE_CORE,
231 TP_CONNECTION_FEATURE_CAPABILITIES,
234 if (g_hash_table_lookup (priv->connections, connection) != NULL)
237 g_hash_table_insert (priv->connections, g_object_ref (connection),
238 new_connection_data ());
240 g_signal_connect (connection, "invalidated",
241 G_CALLBACK (dispatcher_connection_invalidated_cb), self);
243 /* Ensure to keep the self object alive while preparing the connection */
246 tp_proxy_prepare_async (connection, features, connection_prepare_cb, self);
250 dispatcher_status_changed_cb (TpAccount *account,
254 gchar *dbus_error_name,
256 EmpathyDispatcher *self)
258 TpConnection *conn = tp_account_get_connection (account);
261 dispatcher_init_connection_if_needed (self, conn);
265 dispatcher_constructor (GType type,
266 guint n_construct_params,
267 GObjectConstructParam *construct_params)
270 EmpathyDispatcherPriv *priv;
272 if (dispatcher != NULL)
273 return g_object_ref (dispatcher);
275 retval = G_OBJECT_CLASS (empathy_dispatcher_parent_class)->constructor
276 (type, n_construct_params, construct_params);
278 dispatcher = EMPATHY_DISPATCHER (retval);
279 g_object_add_weak_pointer (retval, (gpointer) &dispatcher);
281 priv = GET_PRIV (dispatcher);
287 dispatcher_dispose (GObject *object)
289 EmpathyDispatcherPriv *priv = GET_PRIV (object);
293 if (priv->dispose_has_run)
296 priv->dispose_has_run = TRUE;
298 g_hash_table_iter_init (&iter, priv->connections);
299 while (g_hash_table_iter_next (&iter, &connection, NULL))
301 g_signal_handlers_disconnect_by_func (connection,
302 dispatcher_connection_invalidated_cb, object);
305 g_hash_table_destroy (priv->connections);
306 priv->connections = NULL;
308 G_OBJECT_CLASS (empathy_dispatcher_parent_class)->dispose (object);
312 dispatcher_finalize (GObject *object)
314 EmpathyDispatcherPriv *priv = GET_PRIV (object);
318 gpointer account, id;
320 g_hash_table_iter_init (&iter, priv->outstanding_classes_requests);
321 while (g_hash_table_iter_next (&iter, &connection, (gpointer *) &list))
323 g_list_foreach (list, (GFunc) free_find_channel_request, NULL);
327 g_hash_table_iter_init (&iter, priv->status_changed_handlers);
328 while (g_hash_table_iter_next (&iter, &account, &id))
330 g_signal_handler_disconnect (account, GPOINTER_TO_UINT (id));
332 g_hash_table_destroy (priv->status_changed_handlers);
334 g_object_unref (priv->account_manager);
336 g_hash_table_destroy (priv->outstanding_classes_requests);
340 empathy_dispatcher_class_init (EmpathyDispatcherClass *klass)
342 GObjectClass *object_class = G_OBJECT_CLASS (klass);
344 object_class->dispose = dispatcher_dispose;
345 object_class->finalize = dispatcher_finalize;
346 object_class->constructor = dispatcher_constructor;
348 g_type_class_add_private (object_class, sizeof (EmpathyDispatcherPriv));
352 connect_account (EmpathyDispatcher *self,
355 EmpathyDispatcherPriv *priv = GET_PRIV (self);
356 TpConnection *conn = tp_account_get_connection (account);
359 id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->status_changed_handlers,
366 dispatcher_status_changed_cb (account, 0, 0, 0, NULL, NULL, self);
368 id = g_signal_connect (account, "status-changed",
369 G_CALLBACK (dispatcher_status_changed_cb), self);
371 g_hash_table_insert (priv->status_changed_handlers, g_object_ref (account),
372 GUINT_TO_POINTER (id));
376 account_manager_prepared_cb (GObject *source_object,
377 GAsyncResult *result,
381 EmpathyDispatcher *self = user_data;
382 TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
383 GError *error = NULL;
385 if (!tp_account_manager_prepare_finish (account_manager, result, &error))
387 DEBUG ("Failed to prepare account manager: %s", error->message);
388 g_error_free (error);
392 accounts = tp_account_manager_get_valid_accounts (account_manager);
393 for (l = accounts; l; l = l->next)
395 TpAccount *a = l->data;
397 connect_account (self, a);
400 g_list_foreach (accounts, (GFunc) g_object_ref, NULL);
401 g_list_free (accounts);
405 account_prepare_cb (GObject *source_object,
406 GAsyncResult *result,
409 EmpathyDispatcher *self = user_data;
410 TpAccount *account = TP_ACCOUNT (source_object);
411 GError *error = NULL;
413 if (!tp_account_prepare_finish (account, result, &error))
415 DEBUG ("Failed to prepare account: %s", error->message);
416 g_error_free (error);
420 connect_account (self, account);
424 account_validity_changed_cb (TpAccountManager *manager,
432 tp_account_prepare_async (account, NULL, account_prepare_cb, user_data);
436 empathy_dispatcher_init (EmpathyDispatcher *self)
438 EmpathyDispatcherPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
439 EMPATHY_TYPE_DISPATCHER, EmpathyDispatcherPriv);
442 priv->account_manager = tp_account_manager_dup ();
444 priv->connections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
445 g_object_unref, (GDestroyNotify) free_connection_data);
447 priv->outstanding_classes_requests = g_hash_table_new_full (g_direct_hash,
448 g_direct_equal, g_object_unref, NULL);
450 tp_account_manager_prepare_async (priv->account_manager, NULL,
451 account_manager_prepared_cb, self);
453 tp_g_signal_connect_object (priv->account_manager,
454 "account-validity-changed", G_CALLBACK (account_validity_changed_cb),
457 priv->status_changed_handlers = g_hash_table_new_full (NULL, NULL,
458 (GDestroyNotify) g_object_unref, NULL);
462 empathy_dispatcher_dup_singleton (void)
464 return EMPATHY_DISPATCHER (g_object_new (EMPATHY_TYPE_DISPATCHER, NULL));
468 empathy_dispatcher_chat_with_contact (EmpathyContact *contact,
471 empathy_dispatcher_chat_with_contact_id (
472 empathy_contact_get_account (contact), empathy_contact_get_id (contact),
477 ensure_text_channel_cb (GObject *source,
478 GAsyncResult *result,
481 GError *error = NULL;
483 if (!tp_account_channel_request_ensure_channel_finish (
484 TP_ACCOUNT_CHANNEL_REQUEST (source), result, &error))
486 DEBUG ("Failed to ensure text channel: %s", error->message);
487 g_error_free (error);
492 empathy_dispatcher_chat_with_contact_id (TpAccount *account,
493 const gchar *contact_id,
497 TpAccountChannelRequest *req;
499 request = tp_asv_new (
500 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
501 TP_IFACE_CHANNEL_TYPE_TEXT,
502 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
503 TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, contact_id,
506 req = tp_account_channel_request_new (account, request, timestamp);
508 tp_account_channel_request_ensure_channel_async (req, NULL, NULL,
509 ensure_text_channel_cb, NULL);
511 g_hash_table_unref (request);
512 g_object_unref (req);
516 empathy_dispatcher_join_muc (TpAccount *account,
517 const gchar *room_name,
521 TpAccountChannelRequest *req;
523 request = tp_asv_new (
524 TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
525 TP_IFACE_CHANNEL_TYPE_TEXT,
526 TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_ROOM,
527 TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, room_name,
530 req = tp_account_channel_request_new (account, request, timestamp);
532 tp_account_channel_request_ensure_channel_async (req, NULL, NULL,
533 ensure_text_channel_cb, NULL);
535 g_hash_table_unref (request);
536 g_object_unref (req);
540 channel_class_matches (GValueArray *class,
541 const char *channel_type,
543 GArray *fixed_properties)
551 v = g_value_array_get_nth (class, 0);
553 /* if the class doesn't match channel type discard it. */
554 fprops = g_value_get_boxed (v);
555 c_type = tp_asv_get_string (fprops, TP_IFACE_CHANNEL ".ChannelType");
557 if (tp_strdiff (channel_type, c_type))
560 /* we have the right channel type, see if the handle type matches */
561 h_type = tp_asv_get_uint32 (fprops,
562 TP_IFACE_CHANNEL ".TargetHandleType", &valid);
564 if (!valid || (handle_type != h_type && handle_type != TP_UNKNOWN_HANDLE_TYPE))
567 if (fixed_properties != NULL)
569 gpointer h_key, h_val;
574 g_hash_table_iter_init (&iter, fprops);
576 while (g_hash_table_iter_next (&iter, &h_key, &h_val))
578 /* discard ChannelType and TargetHandleType, as we already
581 if (!tp_strdiff ((char *) h_key, TP_IFACE_CHANNEL ".ChannelType") ||
583 ((char *) h_key, TP_IFACE_CHANNEL ".TargetHandleType"))
588 for (idx = 0; idx < fixed_properties->len; idx++)
590 /* if |key| doesn't exist in |fixed_properties|, discard
595 g_array_index (fixed_properties, char *, idx)))
598 /* exit the for() loop */
609 /* if no fixed_properties are specified, discard the classes
610 * with some fixed properties other than the two we already
613 if (g_hash_table_size (fprops) > 2)
621 empathy_dispatcher_find_channel_classes (EmpathyDispatcher *self,
622 TpConnection *connection,
623 const gchar *channel_type,
625 GArray *fixed_properties)
627 EmpathyDispatcherPriv *priv = GET_PRIV (self);
630 GList *matching_classes;
634 g_return_val_if_fail (channel_type != NULL, NULL);
636 cd = g_hash_table_lookup (priv->connections, connection);
641 classes = cd->requestable_channels;
645 matching_classes = NULL;
647 for (i = 0; i < classes->len; i++)
649 class = g_ptr_array_index (classes, i);
651 if (!channel_class_matches
652 (class, channel_type, handle_type, fixed_properties))
655 matching_classes = g_list_prepend (matching_classes, class);
658 return matching_classes;
662 setup_varargs (va_list var_args,
663 const char *channel_namespace,
664 const char *first_property_name)
670 if (first_property_name == NULL)
673 name = first_property_name;
674 properties = g_array_new (TRUE, TRUE, sizeof (char *));
678 name_full = g_strdup (name);
679 properties = g_array_append_val (properties, name_full);
680 name = va_arg (var_args, char *);
687 * empathy_dispatcher_find_requestable_channel_classes:
688 * @dispatcher: an #EmpathyDispatcher
689 * @connection: a #TpConnection
690 * @channel_type: a string identifying the type of the channel to lookup
691 * @handle_type: the handle type for the channel, or %TP_UNKNOWN_HANDLE_TYPE
692 * if you don't care about the channel's target handle type
693 * @first_property_name: %NULL, or the name of the first fixed property,
694 * followed optionally by more names, followed by %NULL.
696 * Returns all the channel classes that a client can request for the connection
697 * @connection, of the type identified by @channel_type, @handle_type and the
698 * fixed properties list.
699 * The classes which are compatible with a fixed properties list (i.e. those
700 * that will be returned by this function) are intended as those that do not
701 * contain any fixed property other than those in the list; note that this
702 * doesn't guarantee that all the classes compatible with the list will contain
703 * all the requested fixed properties, so the clients will have to filter
704 * the returned list themselves.
705 * If @first_property_name is %NULL, only the classes with no other fixed
706 * properties than ChannelType and TargetHandleType will be returned.
707 * Note that this function may return %NULL without performing any lookup if
708 * @connection is not ready. To ensure that @connection is always ready,
709 * use the empathy_dispatcher_find_requestable_channel_classes_async() variant.
711 * Return value: a #GList of #GValueArray objects, where the first element in
712 * the array is a #GHashTable of the fixed properties, and the second is
713 * a #GStrv of the allowed properties for the class. The list should be free'd
714 * with g_list_free() when done, but the objects inside the list are owned
715 * by the #EmpathyDispatcher and must not be modified.
718 empathy_dispatcher_find_requestable_channel_classes
719 (EmpathyDispatcher *self,
720 TpConnection *connection,
721 const gchar *channel_type,
723 const char *first_property_name,
728 EmpathyDispatcherPriv *priv;
733 g_return_val_if_fail (EMPATHY_IS_DISPATCHER (self), NULL);
734 g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL);
735 g_return_val_if_fail (channel_type != NULL, NULL);
737 priv = GET_PRIV (self);
739 va_start (var_args, first_property_name);
741 properties = setup_varargs (var_args, channel_type, first_property_name);
745 retval = empathy_dispatcher_find_channel_classes (self, connection,
746 channel_type, handle_type, properties);
748 if (properties != NULL)
750 /* free the properties array */
751 for (idx = 0; idx < properties->len ; idx++)
753 str = g_array_index (properties, char *, idx);
757 g_array_free (properties, TRUE);