diff options
Diffstat (limited to 'src/connection.c')
-rw-r--r-- | src/connection.c | 1702 |
1 files changed, 1652 insertions, 50 deletions
diff --git a/src/connection.c b/src/connection.c index 2ab791a0..a0e19e4d 100644 --- a/src/connection.c +++ b/src/connection.c @@ -61,11 +61,31 @@ #include "plugin-loader.h" +#ifdef ENABLE_OLPC +#include "olpc-activity-manager.h" +#endif + #include <extensions/extensions.h> #define DEBUG_FLAG DEBUG_CONNECTION #include "debug.h" +#ifdef ENABLE_OLPC + +#define ACTIVITY_PAIR_TYPE \ + (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, \ + G_TYPE_INVALID)) + +static void +salut_connection_olpc_buddy_info_iface_init (gpointer g_iface, + gpointer iface_data); + +static void +salut_connection_olpc_activity_properties_iface_init (gpointer g_iface, + gpointer iface_data); + +#endif + static void salut_connection_aliasing_service_iface_init (gpointer g_iface, gpointer iface_data); @@ -96,8 +116,8 @@ G_DEFINE_TYPE_WITH_CODE(SalutConnection, tp_contacts_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST, tp_base_contact_list_mixin_list_iface_init); - G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE, - tp_presence_mixin_iface_init); + G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE, + tp_presence_mixin_simple_presence_iface_init); G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_AVATARS, salut_connection_avatar_service_iface_init); G_IMPLEMENT_INTERFACE @@ -109,8 +129,19 @@ G_DEFINE_TYPE_WITH_CODE(SalutConnection, salut_conn_future_iface_init); G_IMPLEMENT_INTERFACE (SALUT_TYPE_PLUGIN_CONNECTION, salut_plugin_connection_iface_init); +#ifdef ENABLE_OLPC + G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_OLPC_BUDDY_INFO, + salut_connection_olpc_buddy_info_iface_init); + G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_OLPC_ACTIVITY_PROPERTIES, + salut_connection_olpc_activity_properties_iface_init); +#endif ) +#ifdef ENABLE_OLPC +static gboolean uninvite_stanza_callback (WockyPorter *porter, + WockyStanza *stanza, gpointer user_data); +#endif + /* properties */ enum { PROP_NICKNAME = 1, @@ -127,6 +158,9 @@ enum { PROP_SELF, PROP_XCM, PROP_SI_BYTESTREAM_MANAGER, +#ifdef ENABLE_OLPC + PROP_OLPC_ACTIVITY_MANAGER, +#endif PROP_BACKEND, PROP_DNSSD_NAME, LAST_PROP @@ -143,6 +177,10 @@ struct _SalutConnectionPrivate gchar *last_name; gchar *jid; gchar *email; +#ifdef ENABLE_OLPC + gchar *olpc_color; + GArray *olpc_key; +#endif /* Discovery client for browsing and resolving */ SalutDiscoveryClient *discovery_client; @@ -185,6 +223,11 @@ struct _SalutConnectionPrivate /* gchar *interface → GList<DBusGMethodInvocation> */ GHashTable *pending_sidecars; +#ifdef ENABLE_OLPC + SalutOlpcActivityManager *olpc_activity_manager; + guint uninvite_handler_id; +#endif + /* timer used when trying to properly disconnect */ guint disconnect_timer; @@ -213,6 +256,9 @@ salut_connection_create_handle_repos (TpBaseConnection *self, TpHandleRepoIface *repos[TP_NUM_HANDLE_TYPES]); static GPtrArray * +salut_connection_create_channel_factories (TpBaseConnection *self); + +static GPtrArray * salut_connection_create_channel_managers (TpBaseConnection *self); static gchar * @@ -261,21 +307,6 @@ static TpDBusPropertiesMixinPropImpl conn_avatars_properties[] = { { NULL } }; -static TpDBusPropertiesMixinPropImpl conn_aliasing_properties[] = { - { "AliasFlags", GUINT_TO_POINTER (0), NULL }, - { NULL } -}; - -static void -conn_aliasing_properties_getter (GObject *object, - GQuark interface, - GQuark name, - GValue *value, - gpointer getter_data) -{ - g_value_set_uint (value, GPOINTER_TO_UINT (getter_data)); -} - static void salut_connection_init (SalutConnection *obj) { @@ -303,6 +334,10 @@ salut_connection_init (SalutConnection *obj) priv->last_name = NULL; priv->jid = NULL; priv->email = NULL; +#ifdef ENABLE_OLPC + priv->olpc_color = NULL; + priv->olpc_key = NULL; +#endif priv->discovery_client = NULL; priv->self = NULL; @@ -319,9 +354,6 @@ static void sidecars_conn_status_changed_cb (SalutConnection *conn, guint status, guint reason, gpointer unused); -static void _contact_manager_contact_change_cb (SalutContactManager *mgr, - SalutContact *contact, int changes, gpointer data); - static void salut_connection_constructed (GObject *obj) { @@ -337,9 +369,8 @@ salut_connection_constructed (GObject *obj) G_STRUCT_OFFSET (SalutConnection, contacts_mixin)); tp_base_connection_register_with_contacts_mixin (base); - tp_presence_mixin_register_with_contacts_mixin (obj); - tp_base_contact_list_mixin_register_with_contacts_mixin ( - TP_BASE_CONTACT_LIST (self->priv->contact_manager), base); + tp_presence_mixin_simple_presence_register_with_contacts_mixin (obj); + tp_base_contact_list_mixin_register_with_contacts_mixin (base); tp_contacts_mixin_add_contact_attributes_iface (obj, TP_IFACE_CONNECTION_INTERFACE_AVATARS, @@ -421,6 +452,11 @@ salut_connection_get_property (GObject *object, g_value_set_object (value, priv->si_bytestream_manager); break; #endif +#ifdef ENABLE_OLPC + case PROP_OLPC_ACTIVITY_MANAGER: + g_value_set_object (value, priv->olpc_activity_manager); + break; +#endif case PROP_BACKEND: g_value_set_gtype (value, priv->backend_type); break; @@ -547,15 +583,23 @@ make_presence_opt_args (SalutPresenceId presence, const gchar *message) static GHashTable * get_contact_statuses (GObject *obj, - const GArray *handles) + const GArray *handles, + GError **error) { SalutConnection *self = SALUT_CONNECTION (obj); SalutConnectionPrivate *priv = self->priv; TpBaseConnection *base = (TpBaseConnection *) self; TpHandle self_handle = tp_base_connection_get_self_handle (base); + TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); GHashTable *ret; guint i; + if (!tp_handles_are_valid (handle_repo, handles, FALSE, error)) + { + return NULL; + } + ret = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) tp_presence_status_free); @@ -672,12 +716,16 @@ static const gchar *interfaces [] = { TP_IFACE_CONNECTION_INTERFACE_ALIASING, TP_IFACE_CONNECTION_INTERFACE_AVATARS, TP_IFACE_CONNECTION_INTERFACE_CONTACTS, - TP_IFACE_CONNECTION_INTERFACE_PRESENCE, + TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, TP_IFACE_CONNECTION_INTERFACE_REQUESTS, TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES, TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST, SALUT_IFACE_CONNECTION_FUTURE, +#ifdef ENABLE_OLPC + SALUT_IFACE_OLPC_BUDDY_INFO, + SALUT_IFACE_OLPC_ACTIVITY_PROPERTIES, +#endif NULL }; static GPtrArray * @@ -714,11 +762,6 @@ salut_connection_class_init (SalutConnectionClass *salut_connection_class) NULL, conn_avatars_properties, }, - { TP_IFACE_CONNECTION_INTERFACE_ALIASING, - conn_aliasing_properties_getter, - NULL, - conn_aliasing_properties, - }, { NULL } }; @@ -735,6 +778,8 @@ salut_connection_class_init (SalutConnectionClass *salut_connection_class) tp_connection_class->create_handle_repos = salut_connection_create_handle_repos; + tp_connection_class->create_channel_factories = + salut_connection_create_channel_factories; tp_connection_class->create_channel_managers = salut_connection_create_channel_managers; tp_connection_class->get_unique_connection_name = @@ -754,7 +799,7 @@ salut_connection_class_init (SalutConnectionClass *salut_connection_class) is_presence_status_available, get_contact_statuses, set_own_status, presence_statuses); - tp_presence_mixin_init_dbus_properties (object_class); + tp_presence_mixin_simple_presence_init_dbus_properties (object_class); tp_contacts_mixin_class_init (object_class, G_STRUCT_OFFSET (SalutConnectionClass, contacts_mixin)); @@ -857,6 +902,17 @@ salut_connection_class_init (SalutConnectionClass *salut_connection_class) g_object_class_install_property (object_class, PROP_SI_BYTESTREAM_MANAGER, param_spec); +#ifdef ENABLE_OLPC + param_spec = g_param_spec_object ( + "olpc-activity-manager", + "SalutOlpcActivityManager object", + "The OLPC activity Manager associated with this Salut Connection", + SALUT_TYPE_OLPC_ACTIVITY_MANAGER, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_OLPC_ACTIVITY_MANAGER, + param_spec); +#endif + param_spec = g_param_spec_gtype ( "backend-type", "backend type", @@ -921,6 +977,19 @@ salut_connection_dispose (GObject *object) priv->self = NULL; } +#ifdef ENABLE_OLPC + { + wocky_porter_unregister_handler (self->porter, priv->uninvite_handler_id); + priv->uninvite_handler_id = 0; + } + + if (priv->olpc_activity_manager != NULL) + { + g_object_unref (priv->olpc_activity_manager); + priv->olpc_activity_manager = NULL; + } +#endif + if (self->session != NULL) { g_object_unref (self->session); @@ -969,6 +1038,11 @@ salut_connection_finalize (GObject *object) g_free (priv->last_name); g_free (priv->email); g_free (priv->jid); +#ifdef ENABLE_OLPC + if (priv->olpc_key != NULL) + g_array_unref (priv->olpc_key); + g_free (priv->olpc_color); +#endif g_free (priv->dnssd_name); tp_contacts_mixin_finalize (G_OBJECT(self)); @@ -1074,6 +1148,19 @@ _self_established_cb (SalutSelf *s, gpointer data) } #endif +#ifdef ENABLE_OLPC + if (!salut_olpc_activity_manager_start (priv->olpc_activity_manager, &error)) + { + DEBUG ("failed to start olpc activity manager: %s", error->message); + g_clear_error (&error); + + tp_base_connection_change_status ( TP_BASE_CONNECTION (base), + TP_CONNECTION_STATUS_DISCONNECTED, + TP_CONNECTION_STATUS_REASON_NETWORK_ERROR); + return; + } +#endif + tp_base_connection_change_status (base, TP_CONNECTION_STATUS_CONNECTED, TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED); } @@ -1099,7 +1186,13 @@ discovery_client_running (SalutConnection *self) priv->self = salut_discovery_client_create_self (priv->discovery_client, self, priv->nickname, priv->first_name, priv->last_name, priv->jid, - priv->email, priv->published_name); + priv->email, priv->published_name, +#ifdef ENABLE_OLPC + priv->olpc_key, priv->olpc_color +#else + NULL, NULL +#endif + ); if (priv->pre_connect_caps != NULL) { @@ -1196,6 +1289,23 @@ _salut_connection_disconnect (SalutConnection *self) /* Aliasing interface */ +/** + * salut_connection_get_alias_flags + * + * Implements D-Bus method GetAliasFlags + * on interface org.freedesktop.Telepathy.Connection.Interface.Aliasing + * + */ +static void +salut_connection_get_alias_flags (TpSvcConnectionInterfaceAliasing *self, + DBusGMethodInvocation *context) +{ + /* Aliases are set by the contacts + * Actually we concat the first and lastname property */ + + tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context, + 0); +} static const gchar * salut_connection_get_alias (SalutConnection *self, TpHandle handle) @@ -1234,7 +1344,7 @@ salut_connection_get_alias (SalutConnection *self, TpHandle handle) * salut_connection_request_aliases * * Implements D-Bus method RequestAliases - * on interface im.telepathy1.Connection.Interface.Aliasing + * on interface org.freedesktop.Telepathy.Connection.Interface.Aliasing * */ static void @@ -1276,6 +1386,40 @@ salut_connection_request_aliases (TpSvcConnectionInterfaceAliasing *iface, } static void +salut_connection_get_aliases (TpSvcConnectionInterfaceAliasing *iface, + const GArray *contacts, DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + TpBaseConnection *base = TP_BASE_CONNECTION (self); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + guint i; + GError *error = NULL; + GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, NULL); + + if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + for (i = 0; i < contacts->len; i++) + { + TpHandle handle = g_array_index (contacts, TpHandle, i); + + g_hash_table_insert (result, GUINT_TO_POINTER (handle), + (gchar *) salut_connection_get_alias (self, handle)); + } + + tp_svc_connection_interface_aliasing_return_from_get_aliases (context, + result); + + g_hash_table_unref (result); +} + +static void salut_connection_aliasing_fill_contact_attributes (GObject *obj, const GArray *contacts, GHashTable *attributes_hash) { @@ -1415,18 +1559,24 @@ static void _contact_manager_contact_alias_changed (SalutConnection *self, SalutContact *contact, TpHandle handle) { - GHashTable *aliases; + GPtrArray *aliases; + GValue entry = {0, }; - DEBUG("Emitting AliasesChanged"); + g_value_init (&entry, TP_STRUCT_TYPE_ALIAS_PAIR); + g_value_take_boxed (&entry, + dbus_g_type_specialized_construct (TP_STRUCT_TYPE_ALIAS_PAIR)); + + dbus_g_type_struct_set (&entry, + 0, handle, 1, salut_contact_get_alias (contact), G_MAXUINT); + aliases = g_ptr_array_sized_new (1); + g_ptr_array_add (aliases, g_value_get_boxed (&entry)); - aliases = g_hash_table_new (NULL, NULL); - g_hash_table_insert (aliases, - GUINT_TO_POINTER (handle), - (gchar *) salut_contact_get_alias (contact)); + DEBUG("Emitting AliasesChanged"); tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases); - g_hash_table_unref (aliases); + g_value_unset (&entry); + g_ptr_array_unref (aliases); } static void @@ -1438,7 +1588,9 @@ salut_connection_aliasing_service_iface_init (gpointer g_iface, #define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x \ (klass, salut_connection_##x) + IMPLEMENT (get_alias_flags); IMPLEMENT (request_aliases); + IMPLEMENT (get_aliases); IMPLEMENT (set_aliases); #undef IMPLEMENT } @@ -1501,6 +1653,62 @@ salut_connection_set_avatar (TpSvcConnectionInterfaceAvatars *iface, static void +salut_connection_get_avatar_tokens (TpSvcConnectionInterfaceAvatars *iface, + const GArray *contacts, DBusGMethodInvocation *context) +{ + guint i; + gchar **ret; + GError *err = NULL; + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = TP_BASE_CONNECTION (self); + TpHandle self_handle = tp_base_connection_get_self_handle (base); + TpHandleRepoIface *handle_repo; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + handle_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + + if (!tp_handles_are_valid (handle_repo, contacts, FALSE, &err)) + { + dbus_g_method_return_error (context, err); + g_error_free (err); + return; + } + + ret = g_new0(gchar *, contacts->len + 1); + + for (i = 0; i < contacts->len ; i++) + { + TpHandle handle = g_array_index (contacts, TpHandle, i); + if (self_handle == handle) + { + ret[i] = priv->self->avatar_token; + } + else + { + SalutContact *contact; + + contact = salut_contact_manager_get_contact (priv->contact_manager, + handle); + if (contact != NULL) + { + ret[i] = contact->avatar_token; + g_object_unref (contact); + } + } + if (ret[i] == NULL) + ret[i] = ""; + } + + tp_svc_connection_interface_avatars_return_from_get_avatar_tokens (context, + (const gchar **)ret); + + g_free (ret); +} + +static void salut_connection_get_known_avatar_tokens ( TpSvcConnectionInterfaceAvatars *iface, const GArray *contacts, DBusGMethodInvocation *context) @@ -1694,6 +1902,78 @@ salut_connection_request_avatars ( } static void +_request_avatar_cb (SalutContact *contact, guint8 *avatar, gsize size, + gpointer user_data) +{ + DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data; + + GError *err = NULL; + GArray *arr; + + if (size == 0) + { + err = g_error_new (TP_ERROR, TP_ERROR_NOT_AVAILABLE, + "Unable to get avatar"); + dbus_g_method_return_error (context, err); + g_error_free (err); + return; + } + + arr = g_array_sized_new (FALSE, FALSE, sizeof (guint8), size); + arr = g_array_append_vals (arr, avatar, size); + tp_svc_connection_interface_avatars_return_from_request_avatar (context, + arr, ""); + g_array_unref (arr); +} + +static void +salut_connection_request_avatar (TpSvcConnectionInterfaceAvatars *iface, + guint handle, DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = TP_BASE_CONNECTION (self); + TpHandle self_handle = tp_base_connection_get_self_handle (base); + SalutContact *contact; + GError *err = NULL; + TpHandleRepoIface *handle_repo; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + handle_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + + if (!tp_handle_is_valid (handle_repo, handle, &err)) + { + dbus_g_method_return_error (context, err); + g_error_free (err); + return; + } + + if (handle == self_handle) + { + _request_avatar_cb (NULL, priv->self->avatar, priv->self->avatar_size, + context); + return; + } + + contact = salut_contact_manager_get_contact (priv->contact_manager, handle); + if (contact == NULL || contact->avatar_token == NULL) + { + err = g_error_new (TP_ERROR, TP_ERROR_NOT_AVAILABLE, "No known avatar"); + dbus_g_method_return_error (context, err); + g_error_free (err); + if (contact != NULL) + { + g_object_unref (contact); + } + return; + } + salut_contact_get_avatar (contact, _request_avatar_cb, context); + g_object_unref (contact); +} + +static void conn_avatars_properties_getter (GObject *object, GQuark interface, GQuark name, @@ -1714,6 +1994,15 @@ conn_avatars_properties_getter (GObject *object, } static void +salut_connection_get_avatar_requirements ( + TpSvcConnectionInterfaceAvatars *iface, DBusGMethodInvocation *context) +{ + tp_svc_connection_interface_avatars_return_from_get_avatar_requirements ( + context, mimetypes, AVATAR_MIN_PX, AVATAR_MIN_PX, AVATAR_MAX_PX, + AVATAR_MAX_PX, AVATAR_MAX_BYTES); +} + +static void salut_connection_avatar_service_iface_init (gpointer g_iface, gpointer iface_data) { @@ -1722,7 +2011,10 @@ salut_connection_avatar_service_iface_init (gpointer g_iface, #define IMPLEMENT(x) tp_svc_connection_interface_avatars_implement_##x \ (klass, salut_connection_##x) + IMPLEMENT (get_avatar_requirements); + IMPLEMENT (get_avatar_tokens); IMPLEMENT (get_known_avatar_tokens); + IMPLEMENT (request_avatar); IMPLEMENT (request_avatars); IMPLEMENT (set_avatar); IMPLEMENT (clear_avatar); @@ -1746,6 +2038,56 @@ salut_free_enhanced_contact_capabilities (GPtrArray *caps) g_ptr_array_unref (caps); } +/** + * salut_connection_get_contact_capabilities + * + * Implements D-Bus method GetContactCapabilities + * on interface + * org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities + */ +static void +salut_connection_get_contact_capabilities ( + TpSvcConnectionInterfaceContactCapabilities *iface, + const GArray *handles, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + TpBaseConnection *base = (TpBaseConnection *) self; + TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_CONTACT); + guint i; + GHashTable *ret; + GError *error = NULL; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + if (!tp_handles_are_valid (contact_handles, handles, FALSE, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + ret = g_hash_table_new_full (NULL, NULL, NULL, + (GDestroyNotify) salut_free_enhanced_contact_capabilities); + + for (i = 0; i < handles->len; i++) + { + GPtrArray *arr = g_ptr_array_new (); + TpHandle handle = g_array_index (handles, TpHandle, i); + + salut_connection_get_handle_contact_capabilities (self, handle, arr); + + g_hash_table_insert (ret, GINT_TO_POINTER (handle), arr); + } + + tp_svc_connection_interface_contact_capabilities_return_from_get_contact_capabilities + (context, ret); + + g_hash_table_unref (ret); +} + + static void _emit_contact_capabilities_changed (SalutConnection *conn, TpHandle handle) @@ -1827,7 +2169,7 @@ data_forms_equal (GPtrArray *one, * * Implements D-Bus method UpdateCapabilities * on interface - * im.telepathy1.Connection.Interface.ContactCapabilities + * org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities */ static void salut_connection_update_capabilities ( @@ -1959,10 +2301,1041 @@ salut_conn_contact_caps_iface_init (gpointer g_iface, gpointer iface_data) #define IMPLEMENT(x) \ tp_svc_connection_interface_contact_capabilities_implement_##x (\ klass, salut_connection_##x) + IMPLEMENT(get_contact_capabilities); IMPLEMENT(update_capabilities); #undef IMPLEMENT } + +#ifdef ENABLE_OLPC +static GValue * +new_gvalue (GType type) +{ + GValue *result = g_slice_new0 (GValue); + g_value_init (result, type); + return result; +} + +static GHashTable * +get_properties_hash (const GArray *key, const gchar *color, const gchar *jid, + const gchar *ip4, const gchar *ip6) +{ + GHashTable *properties; + GValue *gvalue; + + properties = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) tp_g_value_slice_free); + if (key != NULL) + { + gvalue = new_gvalue (DBUS_TYPE_G_UCHAR_ARRAY); + g_value_set_boxed (gvalue, key); + g_hash_table_insert (properties, "key", gvalue); + } + + if (color != NULL) + { + gvalue = new_gvalue (G_TYPE_STRING); + g_value_set_string (gvalue, color); + g_hash_table_insert (properties, "color", gvalue); + } + + if (jid != NULL) + { + gvalue = new_gvalue (G_TYPE_STRING); + g_value_set_string (gvalue, jid); + g_hash_table_insert (properties, "jid", gvalue); + } + + if (ip4 != NULL) + { + gvalue = new_gvalue (G_TYPE_STRING); + g_value_set_string (gvalue, ip4); + g_hash_table_insert (properties, "ip4-address", gvalue); + } + + if (ip6 != NULL) + { + gvalue = new_gvalue (G_TYPE_STRING); + g_value_set_string (gvalue, ip6); + g_hash_table_insert (properties, "ip6-address", gvalue); + } + + return properties; +} + +static void +emit_properties_changed (SalutConnection *connection, + TpHandle handle, + const GArray *key, + const gchar *color, + const gchar *jid, + const gchar *ip4, + const gchar *ip6) +{ + GHashTable *properties; + properties = get_properties_hash (key, color, jid, ip4, ip6); + + salut_svc_olpc_buddy_info_emit_properties_changed (connection, + handle, properties); + + g_hash_table_unref (properties); +} + +static void +append_activity (SalutOlpcActivity *activity, + gpointer user_data) +{ + GPtrArray *arr = user_data; + GType type = ACTIVITY_PAIR_TYPE; + GValue gvalue = {0}; + + g_value_init (&gvalue, type); + g_value_take_boxed (&gvalue, + dbus_g_type_specialized_construct (type)); + + dbus_g_type_struct_set (&gvalue, + 0, activity->id, + 1, activity->room, + G_MAXUINT); + g_ptr_array_add (arr, g_value_get_boxed (&gvalue)); +} + +static void +free_olpc_activities (GPtrArray *arr) +{ + GType type = ACTIVITY_PAIR_TYPE; + guint i; + + for (i = 0; i < arr->len; i++) + g_boxed_free (type, arr->pdata[i]); + + g_ptr_array_unref (arr); +} + +static void +_contact_manager_contact_olpc_activities_changed (SalutConnection *self, + SalutContact *contact, + TpHandle handle) +{ + GPtrArray *activities = g_ptr_array_new (); + + DEBUG ("called for %u", handle); + + salut_contact_foreach_olpc_activity (contact, append_activity, activities); + salut_svc_olpc_buddy_info_emit_activities_changed (self, + handle, activities); + free_olpc_activities (activities); +} + +static void +_contact_manager_contact_olpc_properties_changed (SalutConnection *self, + SalutContact *contact, + TpHandle handle) +{ + emit_properties_changed (self, handle, contact->olpc_key, + contact->olpc_color, contact->jid, contact->olpc_ip4, contact->olpc_ip6); +} + +static gboolean +check_handle (TpHandleRepoIface *handle_repo, + TpHandle handle, + DBusGMethodInvocation *context) +{ + GError *error = NULL; + + if (!tp_handle_is_valid (handle_repo, handle, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return FALSE; + } + + return TRUE; +} + +static gboolean +check_contact (TpBaseConnection *base, + TpHandle contact, + DBusGMethodInvocation *context) +{ + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + base, TP_HANDLE_TYPE_CONTACT); + + return check_handle (contact_repo, contact, context); +} + +static gboolean +check_room (TpBaseConnection *base, + TpHandle contact, + DBusGMethodInvocation *context) +{ + TpHandleRepoIface *room_repo = tp_base_connection_get_handles ( + base, TP_HANDLE_TYPE_ROOM); + + return check_handle (room_repo, contact, context); +} + +static void +salut_connection_olpc_get_properties (SalutSvcOLPCBuddyInfo *iface, + TpHandle handle, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = TP_BASE_CONNECTION (self); + TpHandle self_handle = tp_base_connection_get_self_handle (base); + GHashTable *properties = NULL; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + if (!check_contact (base, handle, context)) + return; + + if (handle == self_handle) + { + properties = get_properties_hash (priv->self->olpc_key, + priv->self->olpc_color, priv->self->jid, NULL, NULL); + } + else + { + SalutContact *contact; + contact = salut_contact_manager_get_contact (priv->contact_manager, + handle); + if (contact == NULL) + { + /* FIXME: should this be InvalidHandle? */ + GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE, "Unknown contact" }; + dbus_g_method_return_error (context, &e); + return; + } + properties = get_properties_hash (contact->olpc_key, contact->olpc_color, + contact->jid, contact->olpc_ip4, contact->olpc_ip6); + g_object_unref (contact); + } + + salut_svc_olpc_buddy_info_return_from_get_properties (context, properties); + g_hash_table_unref (properties); +} + + +static gboolean +find_unknown_properties (gpointer key, + gpointer value, + gpointer user_data) +{ + gchar **valid_props = (gchar **) user_data; + int i; + for (i = 0; valid_props[i] != NULL; i++) + { + if (!tp_strdiff (key, valid_props[i])) + return FALSE; + } + return TRUE; +} + +static void +salut_connection_olpc_set_properties (SalutSvcOLPCBuddyInfo *iface, + GHashTable *properties, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + + GError *error = NULL; + /* Only a few known properties, so handle it quite naively */ + const gchar *known_properties[] = { "color", "key", "jid", "ip4-address", + "ip6-address", NULL }; + const gchar *color = NULL; + const GArray *key = NULL; + const gchar *jid = NULL; + const GValue *val; + + /* this function explicitly supports being called when DISCONNECTED + * or CONNECTING */ + + if (g_hash_table_find (properties, find_unknown_properties, known_properties) + != NULL) + { + error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Unknown property given"); + goto error; + } + + val = (const GValue *) g_hash_table_lookup (properties, "color"); + if (val != NULL) + { + if (G_VALUE_TYPE (val) != G_TYPE_STRING) + { + error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Color value should be of type s"); + goto error; + } + else + { + int len; + gboolean correct = TRUE; + + color = g_value_get_string (val); + + /* be very anal about the color format */ + len = strlen (color); + if (len != 15) + { + correct = FALSE; + } + else + { + int i; + for (i = 0 ; i < len ; i++) + { + switch (i) + { + case 0: + case 8: + correct = (color[i] == '#'); + break; + case 7: + correct = (color[i] == ','); + break; + default: + correct = isxdigit (color[i]); + break; + } + } + } + + if (!correct) + { + error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Color value has an incorrect format"); + goto error; + } + } + } + + if ((val = (const GValue *) g_hash_table_lookup (properties, "key")) != NULL) + { + if (G_VALUE_TYPE (val) != DBUS_TYPE_G_UCHAR_ARRAY) + { + error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Key value should be of type ay"); + goto error; + } + else + { + key = g_value_get_boxed (val); + if (key->len == 0) + { + error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Key value of length 0 not allowed"); + goto error; + } + } + } + + val = g_hash_table_lookup (properties, "jid"); + if (val != NULL) + { + if (G_VALUE_TYPE (val) != G_TYPE_STRING) + { + error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "JID value should be of type s"); + goto error; + } + + jid = g_value_get_string (val); + + if (strchr (jid, '@') == NULL) + { + error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "JID value has an incorrect format"); + goto error; + } + } + + if (priv->self) + { + if (!salut_self_set_olpc_properties (priv->self, key, color, jid, + &error)) + goto error; + } + else + { + /* queue it up for later */ + if (key) + { + if (priv->olpc_key == NULL) + { + priv->olpc_key = g_array_sized_new (FALSE, FALSE, sizeof (guint8), + key->len); + } + else + { + g_array_remove_range (priv->olpc_key, 0, priv->olpc_key->len); + } + g_array_append_vals (priv->olpc_key, key->data, key->len); + } + if (color) + { + g_free (priv->olpc_color); + priv->olpc_color = g_strdup (color); + } + if (jid) + { + g_free (priv->jid); + priv->jid = g_strdup (jid); + } + } + + salut_svc_olpc_buddy_info_return_from_set_properties (context); + return; + +error: + dbus_g_method_return_error (context, error); + g_error_free (error); +} + +static void +salut_connection_olpc_get_current_activity (SalutSvcOLPCBuddyInfo *iface, + TpHandle handle, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + TpBaseConnection *base = (TpBaseConnection *) self; + TpHandle self_handle = tp_base_connection_get_self_handle (base); + SalutConnectionPrivate *priv = self->priv; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + DEBUG ("called for %u", handle); + + if (!check_contact (base, handle, context)) + return; + + if (handle == self_handle) + { + DEBUG ("Returning my own cur.act.: %s -> %u", + priv->self->olpc_cur_act ? priv->self->olpc_cur_act : "", + priv->self->olpc_cur_act_room); + salut_svc_olpc_buddy_info_return_from_get_current_activity (context, + priv->self->olpc_cur_act ? priv->self->olpc_cur_act : "", + priv->self->olpc_cur_act_room); + } + else + { + SalutContact *contact = salut_contact_manager_get_contact + (priv->contact_manager, handle); + + if (contact == NULL) + { + /* FIXME: should this be InvalidHandle? */ + GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE, "Unknown contact" }; + DEBUG ("Returning error: unknown contact"); + dbus_g_method_return_error (context, &e); + return; + } + + DEBUG ("Returning buddy %u cur.act.: %s -> %u", handle, + contact->olpc_cur_act ? contact->olpc_cur_act : "", + contact->olpc_cur_act_room); + salut_svc_olpc_buddy_info_return_from_get_current_activity (context, + contact->olpc_cur_act ? contact->olpc_cur_act : "", + contact->olpc_cur_act_room); + g_object_unref (contact); + } +} + +static void +salut_connection_olpc_set_current_activity (SalutSvcOLPCBuddyInfo *iface, + const gchar *activity_id, + TpHandle room_handle, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = (TpBaseConnection *) self; + GError *error = NULL; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + DEBUG ("called"); + + if (activity_id[0] == '\0') + { + if (room_handle != 0) + { + GError e = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "If activity ID is empty, room handle must be 0" }; + + dbus_g_method_return_error (context, &e); + return; + } + } + else + { + if (!check_room (base, room_handle, context)) + return; + } + + if (!salut_self_set_olpc_current_activity (priv->self, activity_id, + room_handle, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + salut_svc_olpc_buddy_info_return_from_set_current_activity (context); +} + +static void +salut_connection_olpc_get_activities (SalutSvcOLPCBuddyInfo *iface, + TpHandle handle, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = (TpBaseConnection *) self; + TpHandle self_handle = tp_base_connection_get_self_handle (base); + GPtrArray *arr; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + DEBUG ("called for %u", handle); + + if (!check_contact (base, handle, context)) + return; + + if (handle == self_handle) + { + arr = g_ptr_array_new (); + salut_self_foreach_olpc_activity (priv->self, append_activity, arr); + } + else + { + SalutContact *contact = salut_contact_manager_get_contact + (priv->contact_manager, handle); + + if (contact == NULL) + { + /* FIXME: should this be InvalidHandle? */ + GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE, "Unknown contact" }; + DEBUG ("Returning error: unknown contact"); + dbus_g_method_return_error (context, &e); + return; + } + + arr = g_ptr_array_new (); + salut_contact_foreach_olpc_activity (contact, append_activity, arr); + g_object_unref (contact); + } + + salut_svc_olpc_buddy_info_return_from_get_activities (context, arr); + free_olpc_activities (arr); +} + +static void +salut_connection_olpc_set_activities (SalutSvcOLPCBuddyInfo *iface, + const GPtrArray *activities, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = (TpBaseConnection *) self; + TpHandleRepoIface *room_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_ROOM); + GHashTable *room_to_act_id = g_hash_table_new_full (g_direct_hash, + g_direct_equal, NULL, (GDestroyNotify) g_free); + GError *error = NULL; + guint i; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + for (i = 0; i < activities->len; i++) + { + GValue pair = {0}; + gchar *activity; + guint room_handle; + + g_value_init (&pair, ACTIVITY_PAIR_TYPE); + g_value_set_static_boxed (&pair, g_ptr_array_index (activities, i)); + dbus_g_type_struct_get (&pair, + 0, &activity, + 1, &room_handle, + G_MAXUINT); + + if (activity[0] == '\0') + { + GError e = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Invalid empty activity ID" }; + + DEBUG ("%s", e.message); + dbus_g_method_return_error (context, &e); + g_free (activity); + goto finally; + } + + if (!tp_handle_is_valid (room_repo, room_handle, &error)) + { + DEBUG ("Invalid room handle %u: %s", room_handle, error->message); + dbus_g_method_return_error (context, error); + g_error_free (error); + g_free (activity); + goto finally; + } + + g_hash_table_insert (room_to_act_id, GUINT_TO_POINTER (room_handle), + activity); + } + + if (!salut_self_set_olpc_activities (priv->self, room_to_act_id, &error)) + { + dbus_g_method_return_error (context, error); + } + else + { + salut_svc_olpc_buddy_info_return_from_set_activities (context); + } + +finally: + g_hash_table_unref (room_to_act_id); +} + +static void +salut_connection_olpc_add_activity (SalutSvcOLPCBuddyInfo *iface, + const gchar *id, + TpHandle handle, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = (TpBaseConnection *) self; + GError *error = NULL; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + if (!salut_self_add_olpc_activity (priv->self, id, handle, &error)) + { + dbus_g_method_return_error (context, error); + } + else + { + salut_svc_olpc_buddy_info_return_from_set_activities (context); + } +} + +static void +salut_connection_olpc_buddy_info_iface_init (gpointer g_iface, + gpointer iface_data) +{ + SalutSvcOLPCBuddyInfoClass *klass = + (SalutSvcOLPCBuddyInfoClass *) g_iface; +#define IMPLEMENT(x) salut_svc_olpc_buddy_info_implement_##x (klass, \ + salut_connection_olpc_##x) + IMPLEMENT(set_properties); + IMPLEMENT(get_properties); + IMPLEMENT(set_activities); + IMPLEMENT(add_activity); + IMPLEMENT(get_activities); + IMPLEMENT(set_current_activity); + IMPLEMENT(get_current_activity); +#undef IMPLEMENT +} + +static void +salut_connection_act_get_properties (SalutSvcOLPCActivityProperties *iface, + TpHandle handle, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = (TpBaseConnection *) self; + TpHandleRepoIface *room_repo = tp_base_connection_get_handles (base, + TP_HANDLE_TYPE_ROOM); + GHashTable *properties = NULL; + GError *error = NULL; + SalutOlpcActivity *activity; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + if (!tp_handle_is_valid (room_repo, handle, &error)) + goto error; + + activity = salut_olpc_activity_manager_get_activity_by_room ( + priv->olpc_activity_manager, handle); + if (activity == NULL) + { + g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, + "Activity unknown: %u", handle); + goto error; + } + + properties = salut_olpc_activity_create_properties_table (activity); + + salut_svc_olpc_buddy_info_return_from_get_properties (context, properties); + g_hash_table_unref (properties); + + return; + +error: + dbus_g_method_return_error (context, error); + g_error_free (error); +} + +static gboolean +check_color (const gchar *color) +{ + int len, i; + + /* be very anal about the color format */ + len = strlen (color); + if (len != 15) + return FALSE; + + for (i = 0 ; i < len ; i++) + { + switch (i) + { + case 0: + case 8: + if (color[i] != '#') + return FALSE; + break; + case 7: + if (color[i] != ',') + return FALSE; + break; + default: + if (!isxdigit (color[i])) + return FALSE; + break; + } + } + + return TRUE; +} + +/* returned strings are only valid as long as the hash table isn't modified */ +static gboolean +extract_properties_from_hash (GHashTable *properties, + const gchar **id, + const gchar **color, + const gchar **name, + const gchar **type, + const gchar **tags, + gboolean *is_private, + GError **error) +{ + GValue *activity_id_val, *color_val, *activity_name_val, *activity_type_val, + *tags_val, *is_private_val; + + /* activity ID */ + activity_id_val = g_hash_table_lookup (properties, "id"); + if (activity_id_val != NULL) + { + if (G_VALUE_TYPE (activity_id_val) != G_TYPE_STRING) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Activity ID value should be of type s"); + return FALSE; + } + + if (id != NULL) + *id = g_value_get_string (activity_id_val); + } + + /* color */ + color_val = g_hash_table_lookup (properties, "color"); + if (color_val != NULL) + { + if (G_VALUE_TYPE (color_val) != G_TYPE_STRING) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Color value should be of type s"); + return FALSE; + } + + if (color != NULL) + { + *color = g_value_get_string (color_val); + + if (!check_color (*color)) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Color value has an incorrect format"); + return FALSE; + } + } + } + + /* name */ + activity_name_val = g_hash_table_lookup (properties, "name"); + if (activity_name_val != NULL) + { + if (G_VALUE_TYPE (activity_name_val) != G_TYPE_STRING) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "name value should be of type s"); + return FALSE; + } + + if (name != NULL) + *name = g_value_get_string (activity_name_val); + } + + /* type */ + activity_type_val = g_hash_table_lookup (properties, "type"); + if (activity_type_val != NULL) + { + if (G_VALUE_TYPE (activity_type_val) != G_TYPE_STRING) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "type value should be of type s"); + return FALSE; + } + + if (type != NULL) + { + *type = g_value_get_string (activity_type_val); + + if (*type[0] == '\0') + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "type value must be non-empty"); + return FALSE; + } + } + } + + /* tags */ + tags_val = g_hash_table_lookup (properties, "tags"); + if (tags_val != NULL) + { + if (G_VALUE_TYPE (activity_type_val) != G_TYPE_STRING) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "tags value should be of type s"); + return FALSE; + } + + if (type != NULL) + *tags = g_value_get_string (tags_val); + } + + /* is_private */ + is_private_val = g_hash_table_lookup (properties, "private"); + if (is_private_val != NULL) + { + if (G_VALUE_TYPE (is_private_val) != G_TYPE_BOOLEAN) + { + g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "private value should be of type b"); + return FALSE; + } + + if (is_private != NULL) + *is_private = g_value_get_boolean (is_private_val); + } + + return TRUE; +} + +static void +salut_connection_act_set_properties (SalutSvcOLPCActivityProperties *iface, + TpHandle handle, + GHashTable *properties, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = (TpBaseConnection *) self; + GError *error = NULL; + const gchar *known_properties[] = { "color", "name", "type", "private", + "tags", NULL }; + const gchar *color = NULL, *name = NULL, *type = NULL, *tags = NULL; + gboolean is_private = TRUE; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + if (!check_room (base, handle, context)) + return; + + if (g_hash_table_find (properties, find_unknown_properties, known_properties) + != NULL) + { + error = g_error_new (TP_ERROR, TP_ERROR_INVALID_ARGUMENT, + "Unknown property given"); + goto error; + } + + if (!extract_properties_from_hash (properties, NULL, &color, &name, &type, + &tags, &is_private, &error)) + goto error; + + if (!salut_self_set_olpc_activity_properties (priv->self, handle, color, + name, type, tags, is_private, &error)) + goto error; + + salut_svc_olpc_activity_properties_return_from_set_properties (context); + return; + +error: + dbus_g_method_return_error (context, error); + g_error_free (error); +} + +typedef struct +{ + SalutContact *inviter; + SalutOlpcActivity *activity; +} muc_ready_ctx; + +static muc_ready_ctx * +muc_ready_ctx_new (SalutContact *inviter, + SalutOlpcActivity *activity) +{ + muc_ready_ctx *ctx = g_slice_new (muc_ready_ctx); + ctx->inviter = inviter; + g_object_ref (inviter); + ctx->activity = activity; + g_object_ref (activity); + return ctx; +} + +static void +muc_ready_ctx_free (muc_ready_ctx *ctx) +{ + if (ctx == NULL) + return; + + g_object_unref (ctx->inviter); + g_object_unref (ctx->activity); + g_slice_free (muc_ready_ctx, ctx); +} + +static void +muc_ready_cb (SalutMucChannel *muc, + muc_ready_ctx *ctx) +{ + /* We joined the muc so have to forget about invites */ + salut_contact_left_activity (ctx->inviter, ctx->activity); + + DEBUG ("forget invite received from %s", ctx->inviter->name); + g_signal_handlers_disconnect_matched (muc, G_SIGNAL_MATCH_DATA, 0, 0, NULL, + NULL, ctx); + muc_ready_ctx_free (ctx); +} + +static void +muc_closed_cb (SalutMucChannel *muc, + muc_ready_ctx *ctx) +{ + /* FIXME: should we call left_private_activity here too ? */ + + g_signal_handlers_disconnect_matched (muc, G_SIGNAL_MATCH_DATA, 0, 0, NULL, + NULL, ctx); + muc_ready_ctx_free (ctx); +} + +void +salut_connection_olpc_observe_invitation (SalutConnection *self, + TpHandle room, + TpHandle inviter_handle, + WockyNode *invite_node) +{ + SalutConnectionPrivate *priv = self->priv; + WockyNode *props_node; + GHashTable *properties; + const gchar *activity_id, *color = NULL, *activity_name = NULL, + *activity_type = NULL, *tags = NULL; + SalutContact *inviter; + SalutOlpcActivity *activity; + SalutMucChannel *muc; + muc_ready_ctx *ctx; + + props_node = wocky_node_get_child_ns (invite_node, "properties", + NS_OLPC_ACTIVITY_PROPS); + + if (props_node == NULL) + return; + + inviter = salut_contact_manager_get_contact (priv->contact_manager, + inviter_handle); + if (inviter == NULL) + return; + + properties = salut_wocky_node_extract_properties (props_node, + "property"); + + if (!extract_properties_from_hash (properties, &activity_id, &color, + &activity_name, &activity_type, &tags, NULL, NULL)) + return; + + activity = salut_olpc_activity_manager_got_invitation ( + priv->olpc_activity_manager, + room, inviter, activity_id, activity_name, activity_type, + color, tags); +#ifndef USE_BACKEND_BONJOUR + muc = salut_muc_manager_get_text_channel (priv->muc_manager, room); + g_assert (muc != NULL); + + ctx = muc_ready_ctx_new (inviter, activity); + g_signal_connect (muc, "ready", G_CALLBACK (muc_ready_cb), ctx); + g_signal_connect (muc, "closed", G_CALLBACK (muc_closed_cb), ctx); + + g_object_unref (muc); +#endif + g_hash_table_unref (properties); + g_object_unref (inviter); +} + +static void +salut_connection_act_get_activity (SalutSvcOLPCActivityProperties *iface, + const gchar *activity_id, + DBusGMethodInvocation *context) +{ + SalutConnection *self = SALUT_CONNECTION (iface); + SalutConnectionPrivate *priv = self->priv; + TpBaseConnection *base = (TpBaseConnection *) self; + GError *error = NULL; + SalutOlpcActivity *activity; + + TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); + + activity = salut_olpc_activity_manager_get_activity_by_id ( + priv->olpc_activity_manager, activity_id); + if (activity == NULL) + { + g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, + "Activity unknown: %s", activity_id); + goto error; + } + + salut_svc_olpc_activity_properties_return_from_get_activity (context, + activity->room); + + return; + +error: + dbus_g_method_return_error (context, error); + g_error_free (error); +} + +static void +salut_connection_olpc_activity_properties_iface_init (gpointer g_iface, + gpointer iface_data) +{ + SalutSvcOLPCActivityPropertiesClass *klass = + (SalutSvcOLPCActivityPropertiesClass *) g_iface; +#define IMPLEMENT(x) salut_svc_olpc_activity_properties_implement_##x \ + (klass, salut_connection_act_##x) + IMPLEMENT(set_properties); + IMPLEMENT(get_properties); + IMPLEMENT(get_activity); +#undef IMPLEMENT +} +#endif + gchar * salut_normalize_non_empty (const gchar *id, GError **error) @@ -2033,7 +3406,234 @@ _contact_manager_contact_change_cb (SalutContactManager *mgr, { salut_conn_contact_info_changed (self, contact, handle); } + +#ifdef ENABLE_OLPC + if (changes & SALUT_CONTACT_OLPC_PROPERTIES) + _contact_manager_contact_olpc_properties_changed (self, contact, handle); + + if (changes & SALUT_CONTACT_OLPC_CURRENT_ACTIVITY) + salut_svc_olpc_buddy_info_emit_current_activity_changed (self, + handle, contact->olpc_cur_act, contact->olpc_cur_act_room); + + if (changes & SALUT_CONTACT_OLPC_ACTIVITIES) + _contact_manager_contact_olpc_activities_changed (self, contact, handle); +#endif +} + +#ifdef ENABLE_OLPC +static void +_olpc_activity_manager_activity_modified_cb (SalutOlpcActivityManager *mgr, + SalutOlpcActivity *activity, SalutConnection *self) +{ + GHashTable *properties; + + properties = salut_olpc_activity_create_properties_table (activity); + salut_svc_olpc_activity_properties_emit_activity_properties_changed ( + self, activity->room, properties); + + g_hash_table_unref (properties); +} + +gboolean +salut_connection_olpc_observe_muc_stanza (SalutConnection *self, + TpHandle room, TpHandle sender, WockyStanza *stanza) +{ + WockyNode *node = wocky_stanza_get_top_node (stanza); + SalutConnectionPrivate *priv = self->priv; + WockyNode *props_node; + GHashTable *properties; + const gchar *activity_id, *color = NULL, *activity_name = NULL, + *activity_type = NULL, *tags = NULL; + gboolean is_private = FALSE; + SalutOlpcActivity *activity; + + props_node = wocky_node_get_child_ns (node, "properties", + NS_OLPC_ACTIVITY_PROPS); + + if (props_node == NULL) + return FALSE; + + activity = salut_olpc_activity_manager_get_activity_by_room ( + priv->olpc_activity_manager, room); + + if (activity == NULL) + { + DEBUG ("no activity in room %d", room); + return FALSE; + } + + properties = salut_wocky_node_extract_properties (props_node, + "property"); + + if (!extract_properties_from_hash (properties, &activity_id, &color, + &activity_name, &activity_type, &tags, &is_private, NULL)) + return TRUE; + + salut_olpc_activity_update (activity, room, activity_id, activity_name, + activity_type, color, tags, is_private); + + g_hash_table_unref (properties); + + return TRUE; +} + +static gboolean +uninvite_stanza_callback (WockyPorter *porter, + WockyStanza *stanza, + gpointer user_data) +{ + SalutConnection *self = SALUT_CONNECTION (user_data); + SalutConnectionPrivate *priv = self->priv; + TpHandleRepoIface *room_repo = tp_base_connection_get_handles ( + (TpBaseConnection *) self, TP_HANDLE_TYPE_ROOM); + WockyNode *node; + TpHandle room_handle; + const gchar *room, *activity_id; + SalutOlpcActivity *activity; + WockyNode *top_node = wocky_stanza_get_top_node (stanza); + SalutContact *contact = SALUT_CONTACT (wocky_stanza_get_from_contact (stanza)); + + node = wocky_node_get_child_ns (top_node, "uninvite", + NS_OLPC_ACTIVITY_PROPS); + g_assert (node != NULL); + + room = wocky_node_get_attribute (node, "room"); + if (room == NULL) + { + DEBUG ("No room attribute"); + return FALSE; + } + + room_handle = tp_handle_lookup (room_repo, room, NULL, NULL); + if (room_handle == 0) + { + DEBUG ("room %s unknown", room); + return FALSE; + } + + activity_id = wocky_node_get_attribute (node, "id"); + if (activity_id == NULL) + { + DEBUG ("No id attribute"); + return FALSE; + } + + DEBUG ("received uninvite from %s", contact->name); + + activity = salut_olpc_activity_manager_get_activity_by_room ( + priv->olpc_activity_manager, room_handle); + + if (activity == NULL) + return FALSE; + + salut_contact_left_activity (contact, activity); + + return TRUE; +} + +#endif + +static GPtrArray * +salut_connection_create_channel_factories (TpBaseConnection *base) +{ + SalutConnection *self = SALUT_CONNECTION (base); + SalutConnectionPrivate *priv = self->priv; + GPtrArray *factories = g_ptr_array_sized_new (4); + + /* Create the contact manager */ + priv->contact_manager = salut_discovery_client_create_contact_manager ( + priv->discovery_client, self); + g_signal_connect (priv->contact_manager, "contact-change", + G_CALLBACK (_contact_manager_contact_change_cb), self); + +#ifdef ENABLE_OLPC + priv->uninvite_handler_id = wocky_porter_register_handler_from_anyone ( + self->porter, + WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE, + WOCKY_PORTER_HANDLER_PRIORITY_NORMAL, + uninvite_stanza_callback, self, + '(', "uninvite", + ':', NS_OLPC_ACTIVITY_PROPS, + ')', NULL); + + /* create the OLPC activity manager */ + priv->olpc_activity_manager = + salut_discovery_client_create_olpc_activity_manager ( + priv->discovery_client, self); + g_signal_connect (priv->olpc_activity_manager, "activity-modified", + G_CALLBACK (_olpc_activity_manager_activity_modified_cb), self); +#endif + + return factories; +} + +#ifdef ENABLE_OLPC +static void +muc_channel_closed_cb (SalutMucChannel *chan, + SalutOlpcActivity *activity) +{ + SalutConnection *conn; + SalutConnectionPrivate *priv; + TpBaseConnection *base; + TpHandle self_handle; + GPtrArray *activities = g_ptr_array_new (); + + g_signal_handlers_disconnect_by_func (chan, + G_CALLBACK (muc_channel_closed_cb), activity); + + g_object_get (activity, + "connection", &conn, + NULL); + + priv = conn->priv; + base = (TpBaseConnection *) conn; + self_handle = tp_base_connection_get_self_handle (base); + + salut_self_remove_olpc_activity (priv->self, activity); + + salut_self_foreach_olpc_activity (priv->self, append_activity, activities); + salut_svc_olpc_buddy_info_emit_activities_changed (conn, self_handle, + activities); + free_olpc_activities (activities); + + /* we were holding a ref since the channel was opened */ + g_object_unref (activity); + + g_object_unref (conn); +} + +static void +muc_manager_new_channels_cb (TpChannelManager *channel_manager, + GHashTable *channels, + SalutConnection *conn) +{ + SalutConnectionPrivate *priv = conn->priv; + GHashTableIter iter; + gpointer chan; + + g_hash_table_iter_init (&iter, channels); + while (g_hash_table_iter_next (&iter, &chan, NULL)) + { + SalutOlpcActivity *activity; + TpHandle room_handle; + + if (!SALUT_IS_MUC_CHANNEL (chan)) + return; + + g_object_get (chan, + "handle", &room_handle, + NULL); + + /* ref the activity as long as we have a channel open */ + activity = salut_olpc_activity_manager_ensure_activity_by_room ( + priv->olpc_activity_manager, + room_handle); + + g_signal_connect (chan, "closed", G_CALLBACK (muc_channel_closed_cb), + activity); + } } +#endif static void add_to_array (gpointer data, @@ -2052,16 +3652,12 @@ salut_connection_create_channel_managers (TpBaseConnection *base) GPtrArray *tmp; SalutPluginLoader *loader; - /* Create the contact manager. This is not a channel manager anymore, - * but still needs to be created from here because others channel managers use - * it and TpBaseConnection calls ::create_channel_managers() before - * ::constructed() */ - self->priv->contact_manager = salut_discovery_client_create_contact_manager ( - self->priv->discovery_client, self); - g_signal_connect (self->priv->contact_manager, "contact-change", - G_CALLBACK (_contact_manager_contact_change_cb), self); - + /* FIXME: The second and third arguments depend on create_channel_factories + * being called before this; should telepathy-glib guarantee that or + * should we be defensive? + */ priv->im_manager = salut_im_manager_new (self, priv->contact_manager); + priv->ft_manager = salut_ft_manager_new (self, priv->contact_manager); #ifndef USE_BACKEND_BONJOUR @@ -2077,6 +3673,7 @@ salut_connection_create_channel_managers (TpBaseConnection *base) #endif g_ptr_array_add (managers, priv->im_manager); + g_ptr_array_add (managers, priv->contact_manager); g_ptr_array_add (managers, priv->ft_manager); #ifndef USE_BACKEND_BONJOUR g_ptr_array_add (managers, priv->muc_manager); @@ -2087,6 +3684,11 @@ salut_connection_create_channel_managers (TpBaseConnection *base) g_ptr_array_add (managers, priv->tubes_manager); #endif +#ifdef ENABLE_OLPC + g_signal_connect (TP_CHANNEL_MANAGER (priv->muc_manager), "new-channels", + G_CALLBACK (muc_manager_new_channels_cb), self); +#endif + /* plugin channel managers */ loader = salut_plugin_loader_dup (); tmp = salut_plugin_loader_create_channel_managers (loader, plugin_connection); |