/* * simple-conn.c - a simple connection * * Copyright (C) 2007-2010 Collabora Ltd. * Copyright (C) 2007-2008 Nokia Corporation * * Copying and distribution of this file, with or without modification, * are permitted in any medium without royalty provided the copyright * notice and this notice are preserved. */ #include "config.h" #include "simple-conn.h" #include #include #include #include #include #include #include #include #include #include "textchan-null.h" #include "room-list-chan.h" #include "util.h" static void props_iface_init (TpSvcDBusPropertiesClass *); static void conn_iface_init (TpSvcConnectionClass *); G_DEFINE_TYPE_WITH_CODE (TpTestsSimpleConnection, tp_tests_simple_connection, TP_TYPE_BASE_CONNECTION, G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, props_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION, conn_iface_init)) /* type definition stuff */ enum { PROP_ACCOUNT = 1, PROP_BREAK_PROPS = 2, PROP_DBUS_STATUS = 3, N_PROPS }; enum { SIGNAL_GOT_SELF_HANDLE, SIGNAL_GOT_ALL, N_SIGNALS }; static guint signals[N_SIGNALS] = {0}; struct _TpTestsSimpleConnectionPrivate { gchar *account; guint connect_source; guint disconnect_source; gboolean break_fastpath_props; /* TpHandle => reffed TpTestsTextChannelNull */ GHashTable *text_channels; TpTestsRoomListChan *room_list_chan; GError *get_self_handle_error /* initially NULL */ ; }; static void tp_tests_simple_connection_init (TpTestsSimpleConnection *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TESTS_TYPE_SIMPLE_CONNECTION, TpTestsSimpleConnectionPrivate); self->priv->text_channels = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_object_unref); } static void get_property (GObject *object, guint property_id, GValue *value, GParamSpec *spec) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object); switch (property_id) { case PROP_ACCOUNT: g_value_set_string (value, self->priv->account); break; case PROP_BREAK_PROPS: g_value_set_boolean (value, self->priv->break_fastpath_props); break; case PROP_DBUS_STATUS: if (self->priv->break_fastpath_props) { g_debug ("returning broken value for Connection.Status"); g_value_set_uint (value, 0xdeadbeefU); } else { g_value_set_uint (value, tp_base_connection_get_status (TP_BASE_CONNECTION (self))); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); } } static void set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *spec) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object); switch (property_id) { case PROP_ACCOUNT: g_free (self->priv->account); self->priv->account = g_utf8_strdown (g_value_get_string (value), -1); break; case PROP_BREAK_PROPS: self->priv->break_fastpath_props = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); } } static void dispose (GObject *object) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object); g_hash_table_unref (self->priv->text_channels); g_clear_object (&self->priv->room_list_chan); G_OBJECT_CLASS (tp_tests_simple_connection_parent_class)->dispose (object); } static void finalize (GObject *object) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object); if (self->priv->connect_source != 0) { g_source_remove (self->priv->connect_source); } if (self->priv->disconnect_source != 0) { g_source_remove (self->priv->disconnect_source); } g_clear_error (&self->priv->get_self_handle_error); g_free (self->priv->account); G_OBJECT_CLASS (tp_tests_simple_connection_parent_class)->finalize (object); } static gchar * get_unique_connection_name (TpBaseConnection *conn) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (conn); return g_strdup (self->priv->account); } static gchar * tp_tests_simple_normalize_contact (TpHandleRepoIface *repo, const gchar *id, gpointer context, GError **error) { if (id[0] == '\0') { g_set_error (error, TP_ERROR, TP_ERROR_INVALID_HANDLE, "ID must not be empty"); return NULL; } if (strchr (id, ' ') != NULL) { g_set_error (error, TP_ERROR, TP_ERROR_INVALID_HANDLE, "ID must not contain spaces"); return NULL; } return g_utf8_strdown (id, -1); } static void create_handle_repos (TpBaseConnection *conn, TpHandleRepoIface *repos[TP_NUM_HANDLE_TYPES]) { repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_CONTACT, tp_tests_simple_normalize_contact, NULL); repos[TP_HANDLE_TYPE_ROOM] = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_ROOM, NULL, NULL); } static GPtrArray * create_channel_factories (TpBaseConnection *conn) { return g_ptr_array_sized_new (0); } void tp_tests_simple_connection_inject_disconnect (TpTestsSimpleConnection *self) { tp_base_connection_change_status ((TpBaseConnection *) self, TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED); } static gboolean pretend_connected (gpointer data) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (data); TpBaseConnection *conn = (TpBaseConnection *) self; TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); TpHandle self_handle; self_handle = tp_handle_ensure (contact_repo, self->priv->account, NULL, NULL); tp_base_connection_set_self_handle (conn, self_handle); if (tp_base_connection_get_status (conn) == TP_CONNECTION_STATUS_CONNECTING) { tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED); } self->priv->connect_source = 0; return FALSE; } static gboolean start_connecting (TpBaseConnection *conn, GError **error) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (conn); tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTING, TP_CONNECTION_STATUS_REASON_REQUESTED); /* In a real connection manager we'd ask the underlying implementation to * start connecting, then go to state CONNECTED when finished. Here there * isn't actually a connection, so we'll fake a connection process that * takes time. */ self->priv->connect_source = g_timeout_add (0, pretend_connected, self); return TRUE; } static gboolean pretend_disconnected (gpointer data) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (data); /* We are disconnected, all our channels are invalidated */ g_hash_table_remove_all (self->priv->text_channels); g_clear_object (&self->priv->room_list_chan); tp_base_connection_finish_shutdown (TP_BASE_CONNECTION (data)); self->priv->disconnect_source = 0; return FALSE; } static void shut_down (TpBaseConnection *conn) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (conn); /* In a real connection manager we'd ask the underlying implementation to * start shutting down, then call this function when finished. Here there * isn't actually a connection, so we'll fake a disconnection process that * takes time. */ self->priv->disconnect_source = g_timeout_add (0, pretend_disconnected, conn); } static GPtrArray * get_interfaces_always_present (TpBaseConnection *base) { GPtrArray *interfaces; interfaces = TP_BASE_CONNECTION_CLASS ( tp_tests_simple_connection_parent_class)->get_interfaces_always_present (base); g_ptr_array_add (interfaces, TP_IFACE_CONNECTION_INTERFACE_REQUESTS); return interfaces; } static void tp_tests_simple_connection_class_init (TpTestsSimpleConnectionClass *klass) { TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass; GObjectClass *object_class = (GObjectClass *) klass; GParamSpec *param_spec; object_class->get_property = get_property; object_class->set_property = set_property; object_class->dispose = dispose; object_class->finalize = finalize; g_type_class_add_private (klass, sizeof (TpTestsSimpleConnectionPrivate)); base_class->create_handle_repos = create_handle_repos; base_class->get_unique_connection_name = get_unique_connection_name; base_class->create_channel_factories = create_channel_factories; base_class->start_connecting = start_connecting; base_class->shut_down = shut_down; base_class->get_interfaces_always_present = get_interfaces_always_present; param_spec = g_param_spec_string ("account", "Account name", "The username of this user", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec); param_spec = g_param_spec_boolean ("break-0192-properties", "Break 0.19.2 properties", "Break Connection D-Bus properties introduced in spec 0.19.2", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_BREAK_PROPS, param_spec); param_spec = g_param_spec_uint ("dbus-status", "Connection.Status", "The connection status as visible on D-Bus (overridden so can break it)", TP_CONNECTION_STATUS_CONNECTED, G_MAXUINT, TP_CONNECTION_STATUS_DISCONNECTED, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_DBUS_STATUS, param_spec); signals[SIGNAL_GOT_SELF_HANDLE] = g_signal_new ("got-self-handle", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[SIGNAL_GOT_ALL] = g_signal_new ("got-all", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } void tp_tests_simple_connection_set_identifier (TpTestsSimpleConnection *self, const gchar *identifier) { TpBaseConnection *conn = (TpBaseConnection *) self; TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); TpHandle handle = tp_handle_ensure (contact_repo, identifier, NULL, NULL); /* if this fails then the identifier was bad - caller error */ g_return_if_fail (handle != 0); tp_base_connection_set_self_handle (conn, handle); } TpTestsSimpleConnection * tp_tests_simple_connection_new (const gchar *account, const gchar *protocol) { return TP_TESTS_SIMPLE_CONNECTION (g_object_new ( TP_TESTS_TYPE_SIMPLE_CONNECTION, "account", account, "protocol", protocol, NULL)); } gchar * tp_tests_simple_connection_ensure_text_chan (TpTestsSimpleConnection *self, const gchar *target_id, GHashTable **props) { TpTestsTextChannelNull *chan; gchar *chan_path; TpHandleRepoIface *contact_repo; TpHandle handle; static guint count = 0; TpBaseConnection *base_conn = (TpBaseConnection *) self; /* Get contact handle */ contact_repo = tp_base_connection_get_handles (base_conn, TP_HANDLE_TYPE_CONTACT); g_assert (contact_repo != NULL); handle = tp_handle_ensure (contact_repo, target_id, NULL, NULL); chan = g_hash_table_lookup (self->priv->text_channels, GUINT_TO_POINTER (handle)); if (chan != NULL) { /* Channel already exist, reuse it */ g_object_get (chan, "object-path", &chan_path, NULL); } else { chan_path = g_strdup_printf ("%s/Channel%u", tp_base_connection_get_object_path (base_conn), count++); chan = TP_TESTS_TEXT_CHANNEL_NULL ( tp_tests_object_new_static_class ( TP_TESTS_TYPE_TEXT_CHANNEL_NULL, "connection", self, "object-path", chan_path, "handle", handle, NULL)); g_hash_table_insert (self->priv->text_channels, GUINT_TO_POINTER (handle), chan); } if (props != NULL) *props = tp_tests_text_channel_get_props (chan); return chan_path; } static void room_list_chan_closed_cb (TpBaseChannel *channel, TpTestsSimpleConnection *self) { g_clear_object (&self->priv->room_list_chan); } gchar * tp_tests_simple_connection_ensure_room_list_chan (TpTestsSimpleConnection *self, const gchar *server, GHashTable **props) { gchar *chan_path; TpBaseConnection *base_conn = (TpBaseConnection *) self; if (self->priv->room_list_chan != NULL) { /* Channel already exist, reuse it */ g_object_get (self->priv->room_list_chan, "object-path", &chan_path, NULL); } else { chan_path = g_strdup_printf ("%s/RoomListChannel", tp_base_connection_get_object_path (base_conn)); self->priv->room_list_chan = TP_TESTS_ROOM_LIST_CHAN ( tp_tests_object_new_static_class ( TP_TESTS_TYPE_ROOM_LIST_CHAN, "connection", self, "object-path", chan_path, "server", server ? server : "", NULL)); g_signal_connect (self->priv->room_list_chan, "closed", G_CALLBACK (room_list_chan_closed_cb), self); } if (props != NULL) g_object_get (self->priv->room_list_chan, "channel-properties", props, NULL); return chan_path; } void tp_tests_simple_connection_set_get_self_handle_error ( TpTestsSimpleConnection *self, GQuark domain, gint code, const gchar *message) { self->priv->get_self_handle_error = g_error_new_literal (domain, code, message); } static void get_self_handle (TpSvcConnection *iface, DBusGMethodInvocation *context) { TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (iface); TpBaseConnection *base = TP_BASE_CONNECTION (iface); g_assert (TP_IS_BASE_CONNECTION (base)); TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); if (self->priv->get_self_handle_error != NULL) { dbus_g_method_return_error (context, self->priv->get_self_handle_error); return; } tp_svc_connection_return_from_get_self_handle (context, tp_base_connection_get_self_handle (base)); g_signal_emit (self, signals[SIGNAL_GOT_SELF_HANDLE], 0); } static void conn_iface_init (TpSvcConnectionClass *iface) { #define IMPLEMENT(prefix,x) \ tp_svc_connection_implement_##x (iface, prefix##x) IMPLEMENT(,get_self_handle); #undef IMPLEMENT } static void get_all (TpSvcDBusProperties *iface, const gchar *interface_name, DBusGMethodInvocation *context) { GHashTable *values = tp_dbus_properties_mixin_dup_all (G_OBJECT (iface), interface_name); tp_svc_dbus_properties_return_from_get_all (context, values); g_hash_table_unref (values); g_signal_emit (iface, signals[SIGNAL_GOT_ALL], g_quark_from_string (interface_name)); } static void props_iface_init (TpSvcDBusPropertiesClass *iface) { #define IMPLEMENT(x) \ tp_svc_dbus_properties_implement_##x (iface, x) IMPLEMENT (get_all); #undef IMPLEMENT }