From d012d14db5f5b8988ba6e9189b1b8ba207fa09e4 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Sun, 5 Jun 2011 18:11:33 +0200 Subject: [PATCH] Add TP_CONNECTION_FEATURE_CONTACT_GROUPS to prepare the list of groups on the connection --- docs/reference/telepathy-glib-sections.txt | 5 + telepathy-glib/connection-contact-list.c | 269 ++++++++++++++++++++++++++++ telepathy-glib/connection-contact-list.h | 8 + telepathy-glib/connection-internal.h | 10 + telepathy-glib/connection.c | 170 ++++++++++++++++++ telepathy-glib/signals-marshal.list | 1 + 6 files changed, 463 insertions(+), 0 deletions(-) diff --git a/docs/reference/telepathy-glib-sections.txt b/docs/reference/telepathy-glib-sections.txt index 1c1e4fa..8b5e944 100644 --- a/docs/reference/telepathy-glib-sections.txt +++ b/docs/reference/telepathy-glib-sections.txt @@ -3468,6 +3468,7 @@ TP_CONNECTION_FEATURE_AVATAR_REQUIREMENTS TP_CONNECTION_FEATURE_CONTACT_INFO TP_CONNECTION_FEATURE_BALANCE TP_CONNECTION_FEATURE_CONTACT_LIST +TP_CONNECTION_FEATURE_CONTACT_GROUPS tp_connection_run_until_ready TpConnectionWhenReadyCb tp_connection_call_when_ready @@ -3525,6 +3526,7 @@ tp_connection_get_feature_quark_avatar_requirements tp_connection_get_feature_quark_contact_info tp_connection_get_feature_quark_balance tp_connection_get_feature_quark_contact_list +tp_connection_get_feature_quark_contact_groups TP_TYPE_AVATAR_REQUIREMENTS TpAvatarRequirements @@ -3564,6 +3566,9 @@ tp_connection_unsubscribe_finish tp_connection_unpublish_async tp_connection_unpublish_finish +tp_connection_get_disjoint_groups +tp_connection_get_group_storage +tp_connection_get_contact_groups tp_connection_set_group_members_async tp_connection_set_group_members_finish tp_connection_add_to_group_async diff --git a/telepathy-glib/connection-contact-list.c b/telepathy-glib/connection-contact-list.c index 961cdc2..58aa398 100644 --- a/telepathy-glib/connection-contact-list.c +++ b/telepathy-glib/connection-contact-list.c @@ -408,6 +408,195 @@ void _tp_connection_prepare_contact_list_async (TpProxy *proxy, prepare_contact_list_cb, result, g_object_unref, NULL); } +static void +contact_groups_created_cb (TpConnection *self, + const gchar **names, + gpointer user_data, + GObject *weak_object) +{ + const gchar **iter; + + if (!self->priv->groups_fetched) + return; + + DEBUG ("Groups created:"); + + /* Remove the ending NULL */ + g_ptr_array_remove_index_fast (self->priv->contact_groups, + self->priv->contact_groups->len - 1); + + for (iter = names; *iter != NULL; iter++) + { + DEBUG (" %s", *iter); + g_ptr_array_add (self->priv->contact_groups, g_strdup (*iter)); + } + + /* Add back the ending NULL */ + g_ptr_array_add (self->priv->contact_groups, NULL); + + g_object_notify ((GObject *) self, "contact-groups"); + g_signal_emit_by_name (self, "groups-created", names); +} + +static void +contact_groups_removed_cb (TpConnection *self, + const gchar **names, + gpointer user_data, + GObject *weak_object) +{ + const gchar **iter; + + if (!self->priv->groups_fetched) + return; + + DEBUG ("Groups removed:"); + + /* Remove the ending NULL */ + g_ptr_array_remove_index_fast (self->priv->contact_groups, + self->priv->contact_groups->len - 1); + + for (iter = names; *iter != NULL; iter++) + { + guint i; + + for (i = 0; i < self->priv->contact_groups->len; i++) + { + const gchar *str = g_ptr_array_index (self->priv->contact_groups, i); + + if (!tp_strdiff (str, *iter)) + { + DEBUG (" %s", str); + g_ptr_array_remove_index_fast (self->priv->contact_groups, i); + break; + } + } + } + + /* Add back the ending NULL */ + g_ptr_array_add (self->priv->contact_groups, NULL); + + g_object_notify ((GObject *) self, "contact-groups"); + g_signal_emit_by_name (self, "groups-removed", names); +} + +static void +contact_group_renamed_cb (TpConnection *self, + const gchar *old_name, + const gchar *new_name, + gpointer user_data, + GObject *weak_object) +{ + guint i; + + if (!self->priv->groups_fetched) + return; + + DEBUG ("Group renamed: %s -> %s", old_name, new_name); + + /* Remove the ending NULL */ + g_ptr_array_remove_index_fast (self->priv->contact_groups, + self->priv->contact_groups->len - 1); + + for (i = 0; i < self->priv->contact_groups->len; i++) + { + const gchar *str = g_ptr_array_index (self->priv->contact_groups, i); + + if (!tp_strdiff (str, old_name)) + { + g_ptr_array_remove_index_fast (self->priv->contact_groups, i); + break; + } + } + g_ptr_array_add (self->priv->contact_groups, g_strdup (new_name)); + + /* Add back the ending NULL */ + g_ptr_array_add (self->priv->contact_groups, NULL); + + g_object_notify ((GObject *) self, "contact-groups"); + g_signal_emit_by_name (self, "group-renamed", old_name, new_name); +} + +static void +prepare_contact_groups_cb (TpProxy *proxy, + GHashTable *properties, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + TpConnection *self = (TpConnection *) proxy; + GSimpleAsyncResult *result = user_data; + GStrv groups; + gchar **iter; + gboolean valid; + + if (error != NULL) + { + g_simple_async_result_set_from_error (result, error); + goto OUT; + } + + self->priv->groups_fetched = TRUE; + + self->priv->disjoint_groups = tp_asv_get_boolean (properties, + "DisjointGroups", &valid); + if (!valid) + { + DEBUG ("Connection %s doesn't have DisjointGroups property", + tp_proxy_get_object_path (self)); + } + + self->priv->group_storage = tp_asv_get_uint32 (properties, + "GroupStorage", &valid); + if (!valid) + { + DEBUG ("Connection %s doesn't have GroupStorage property", + tp_proxy_get_object_path (self)); + } + + DEBUG ("Got contact list groups:"); + + /* Remove the ending NULL */ + g_ptr_array_remove_index_fast (self->priv->contact_groups, + self->priv->contact_groups->len - 1); + + groups = tp_asv_get_boxed (properties, "Groups", G_TYPE_STRV); + for (iter = groups; *iter != NULL; iter++) + { + DEBUG (" %s", *iter); + g_ptr_array_add (self->priv->contact_groups, g_strdup (*iter)); + } + + /* Add back the ending NULL */ + g_ptr_array_add (self->priv->contact_groups, NULL); + +OUT: + g_simple_async_result_complete (result); +} + +void +_tp_connection_prepare_contact_groups_async (TpProxy *proxy, + const TpProxyFeature *feature, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TpConnection *self = (TpConnection *) proxy; + GSimpleAsyncResult *result; + + tp_cli_connection_interface_contact_groups_connect_to_groups_created ( + self, contact_groups_created_cb, NULL, NULL, NULL, NULL); + tp_cli_connection_interface_contact_groups_connect_to_groups_removed ( + self, contact_groups_removed_cb, NULL, NULL, NULL, NULL); + tp_cli_connection_interface_contact_groups_connect_to_group_renamed ( + self, contact_group_renamed_cb, NULL, NULL, NULL, NULL); + + result = g_simple_async_result_new ((GObject *) self, callback, user_data, + _tp_connection_prepare_contact_groups_async); + + tp_cli_dbus_properties_call_get_all (self, -1, + TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS, + prepare_contact_groups_cb, result, g_object_unref, NULL); +} + /** * TP_CONNECTION_FEATURE_CONTACT_LIST: * @@ -814,6 +1003,86 @@ tp_connection_unpublish_finish (TpConnection *self, generic_finish (unpublish); } +/** + * TP_CONNECTION_FEATURE_CONTACT_GROUPS: + * + * Expands to a call to a function that returns a #GQuark representing the + * "contact-groups" feature. + * + * When this feature is prepared, the contact groups properties of the + * Connection has been retrieved. + * + * See #TpContact:contact-groups to get the list of groups a contact is member + * of. + * + * One can ask for a feature to be prepared using the + * tp_proxy_prepare_async() function, and waiting for it to callback. + * + * Since: 0.UNRELEASED + */ + +GQuark +tp_connection_get_feature_quark_contact_groups (void) +{ + return g_quark_from_static_string ("tp-connection-feature-contact-groups"); +} + +/** + * tp_connection_get_disjoint_groups: + * @self: a #TpConnection + * + * + * + * Returns: the value of #TpConnection:disjoint-groups + * + * Since: 0.UNRELEASED + */ +gboolean +tp_connection_get_disjoint_groups (TpConnection *self) +{ + g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE); + + return self->priv->disjoint_groups; +} + +/** + * tp_connection_get_group_storage: + * @self: a #TpConnection + * + * + * + * Returns: the value of #TpConnection:group-storage + * + * Since: 0.UNRELEASED + */ +TpContactMetadataStorageType +tp_connection_get_group_storage (TpConnection *self) +{ + g_return_val_if_fail (TP_IS_CONNECTION (self), + TP_CONTACT_METADATA_STORAGE_TYPE_NONE); + + return self->priv->group_storage; +} + +/** + * tp_connection_get_contact_groups: + * @self: a #TpConnection + * + * + * + * Returns: (array zero-terminated=1) (transfer none): the value of + * #TpConnection:contact-groups + * + * Since: 0.UNRELEASED + */ +const gchar * const * +tp_connection_get_contact_groups (TpConnection *self) +{ + g_return_val_if_fail (TP_IS_CONNECTION (self), NULL); + + return (const gchar * const *) self->priv->contact_groups->pdata; +} + #define contact_groups_generic_async(method) \ G_STMT_START { \ GSimpleAsyncResult *result; \ diff --git a/telepathy-glib/connection-contact-list.h b/telepathy-glib/connection-contact-list.h index 4af8f08..8f74cbd 100644 --- a/telepathy-glib/connection-contact-list.h +++ b/telepathy-glib/connection-contact-list.h @@ -83,6 +83,14 @@ gboolean tp_connection_unpublish_finish (TpConnection *self, GAsyncResult *result, GError **error); +#define TP_CONNECTION_FEATURE_CONTACT_GROUPS \ + (tp_connection_get_feature_quark_contact_groups ()) +GQuark tp_connection_get_feature_quark_contact_groups (void) G_GNUC_CONST; + +gboolean tp_connection_get_disjoint_groups (TpConnection *self); +TpContactMetadataStorageType tp_connection_get_group_storage (TpConnection *self); +const gchar * const *tp_connection_get_contact_groups (TpConnection *self); + void tp_connection_set_group_members_async (TpConnection *self, const gchar *group, guint n_contacts, diff --git a/telepathy-glib/connection-internal.h b/telepathy-glib/connection-internal.h index 4adf0aa..420037a 100644 --- a/telepathy-glib/connection-internal.h +++ b/telepathy-glib/connection-internal.h @@ -84,6 +84,12 @@ struct _TpConnectionPrivate { gboolean roster_fetched; gboolean contact_list_properties_fetched; + /* ContactGroups properties */ + gboolean disjoint_groups; + TpContactMetadataStorageType group_storage; + GPtrArray *contact_groups; + gboolean groups_fetched; + TpProxyPendingCall *introspection_call; unsigned ready:1; @@ -141,6 +147,10 @@ void _tp_connection_prepare_contact_list_async (TpProxy *proxy, GAsyncReadyCallback callback, gpointer user_data); void _tp_connection_contacts_changed_item_free (gpointer data); +void _tp_connection_prepare_contact_groups_async (TpProxy *proxy, + const TpProxyFeature *feature, + GAsyncReadyCallback callback, + gpointer user_data); G_END_DECLS diff --git a/telepathy-glib/connection.c b/telepathy-glib/connection.c index 78bf0c5..525edf5 100644 --- a/telepathy-glib/connection.c +++ b/telepathy-glib/connection.c @@ -278,12 +278,18 @@ enum PROP_CONTACT_LIST_PERSISTS, PROP_CAN_CHANGE_CONTACT_LIST, PROP_REQUEST_USES_MESSAGE, + PROP_DISJOINT_GROUPS, + PROP_GROUP_STORAGE, + PROP_CONTACT_GROUPS, N_PROPS }; enum { SIGNAL_BALANCE_CHANGED, SIGNAL_CONTACT_LIST_CHANGED, + SIGNAL_GROUPS_CREATED, + SIGNAL_GROUPS_REMOVED, + SIGNAL_GROUP_RENAMED, N_SIGNALS }; @@ -351,6 +357,15 @@ tp_connection_get_property (GObject *object, case PROP_REQUEST_USES_MESSAGE: g_value_set_boolean (value, self->priv->request_uses_message); break; + case PROP_DISJOINT_GROUPS: + g_value_set_boolean (value, self->priv->disjoint_groups); + break; + case PROP_GROUP_STORAGE: + g_value_set_uint (value, self->priv->group_storage); + break; + case PROP_CONTACT_GROUPS: + g_value_set_boxed (value, self->priv->contact_groups); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -1357,6 +1372,8 @@ tp_connection_init (TpConnection *self) self->priv->roster = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); self->priv->contacts_changed_queue = g_queue_new (); + self->priv->contact_groups = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (self->priv->contact_groups, NULL); } static void @@ -1440,6 +1457,8 @@ tp_connection_dispose (GObject *object) (GFunc) _tp_connection_contacts_changed_item_free, NULL); tp_clear_pointer (&self->priv->contacts_changed_queue, g_queue_free); + tp_clear_pointer (&self->priv->contact_groups, g_ptr_array_unref); + if (self->priv->contacts != NULL) { g_hash_table_foreach (self->priv->contacts, contact_notify_invalidated, @@ -1492,6 +1511,7 @@ enum { FEAT_CONTACT_INFO, FEAT_BALANCE, FEAT_CONTACT_LIST, + FEAT_CONTACT_GROUPS, N_FEAT }; @@ -1504,6 +1524,7 @@ tp_connection_list_features (TpProxyClass *cls G_GNUC_UNUSED) static GQuark need_contact_info[2] = {0, 0}; static GQuark need_balance[2] = {0, 0}; static GQuark need_contact_list[3] = {0, 0, 0}; + static GQuark need_contact_groups[2] = {0, 0}; if (G_LIKELY (features[0].name != 0)) return features; @@ -1542,6 +1563,11 @@ tp_connection_list_features (TpProxyClass *cls G_GNUC_UNUSED) need_contact_list[1] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS; features[FEAT_CONTACT_LIST].interfaces_needed = need_contact_list; + features[FEAT_CONTACT_GROUPS].name = TP_CONNECTION_FEATURE_CONTACT_GROUPS; + features[FEAT_CONTACT_GROUPS].prepare_async = _tp_connection_prepare_contact_groups_async; + need_contact_groups[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_GROUPS; + features[FEAT_CONTACT_GROUPS].interfaces_needed = need_contact_groups; + /* assert that the terminator at the end is there */ g_assert (features[N_FEAT].name == 0); @@ -1918,6 +1944,150 @@ tp_connection_class_init (TpConnectionClass *klass) NULL, NULL, _tp_marshal_VOID__BOXED_BOXED, G_TYPE_NONE, 2, G_TYPE_PTR_ARRAY, G_TYPE_PTR_ARRAY); + + /** + * TpConnection:disjoint-groups: + * + * True if each contact can be in at most one group; false if each contact + * can be in many groups. + * + * This property cannot change after the connection has moved to the + * %TP_CONNECTION_STATUS_CONNECTED state. Until then, its value is undefined, + * and it may change at any time, without notification. + * + * For this property to be valid, you must first call + * tp_proxy_prepare_async() with the feature + * %TP_CONNECTION_FEATURE_CONTACT_GROUPS. + * + * Since: 0.UNRELEASED + */ + param_spec = g_param_spec_boolean ("disjoint-groups", + "Disjoint Groups", "Whether groups are disjoint", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_DISJOINT_GROUPS, + param_spec); + + /** + * TpConnection:group-storage: + * + * Indicates the extent to which contacts' groups can be set and stored. + * + * This property cannot change after the connection has moved to the + * %TP_CONNECTION_STATUS_CONNECTED state. Until then, its value is undefined, + * and it may change at any time, without notification. + * + * For this property to be valid, you must first call + * tp_proxy_prepare_async() with the feature + * %TP_CONNECTION_FEATURE_CONTACT_GROUPS. + * + * Since: 0.UNRELEASED + */ + param_spec = g_param_spec_uint ("group-storage", + "Group Storage", "Group storage capabilities", + 0, G_MAXUINT, TP_CONTACT_METADATA_STORAGE_TYPE_NONE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_GROUP_STORAGE, + param_spec); + + /** + * TpConnection:contact-groups: + * + * The names of all groups that currently exist. This may be a larger set than + * the union of all #TpContact:contact-groups properties, if the connection + * allows groups to be empty. + * + * This property's value is not meaningful until the + * #TpConnection:contact-list-state property has become + * %TP_CONTACT_LIST_STATE_SUCCESS. + * + * For this property to be valid, you must first call + * tp_proxy_prepare_async() with the feature + * %TP_CONNECTION_FEATURE_CONTACT_GROUPS. + * + * Since: 0.UNRELEASED + */ + param_spec = g_param_spec_boxed ("contact-groups", + "Contact Groups", + "All existing contact groups", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONTACT_GROUPS, + param_spec); + + /** + * TpConnection::groups-created: + * @self: a #TpConnection + * @added: a #GStrv with the names of the new groups. + * + * Emitted when new, empty groups are created. This will often be followed by + * #TpContact::contact-groups-changed signals that add some members. When this + * signal is emitted, #TpConnection:contact-groups property is already + * updated. + * + * Since: 0.UNRELEASED + */ + signals[SIGNAL_GROUPS_CREATED] = g_signal_new ( + "groups-created", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _tp_marshal_VOID__BOXED, + G_TYPE_NONE, 1, G_TYPE_STRV); + + /** + * TpConnection::groups-removed: + * @self: A #TpConnection + * @added: A #GStrv with the names of the groups. + * + * Emitted when one or more groups are removed. If they had members at the + * time that they were removed, then immediately after this signal is emitted, + * #TpContact::contact-groups-changed signals that their members were removed. + * When this signal is emitted, #TpConnection:contact-groups property is + * already updated. + * + * Since: 0.UNRELEASED + */ + signals[SIGNAL_GROUPS_REMOVED] = g_signal_new ( + "groups-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _tp_marshal_VOID__BOXED, + G_TYPE_NONE, 1, G_TYPE_STRV); + + /** + * TpConnection::group-renamed: + * @self: a #TpConnection + * @old_name: the old name of the group. + * @new_name: the new name of the group. + * + * Emitted when a group is renamed, in protocols where this can be + * distinguished from group creation, removal and membership changes. + * + * Immediately after this signal is emitted, #TpConnection::groups-created + * signal the creation of a group with the new name, and + * #TpConnection::groups-removed signal the removal of a group with the old + * name. + * If the group was not empty, immediately after those signals are emitted, + * #TpContact::contact-groups-changed signal that the members of that group + * were removed from the old name and added to the new name. + * + * When this signal is emitted, #TpConnection:contact-groups property is + * already updated. + * + * Since: 0.UNRELEASED + */ + signals[SIGNAL_GROUP_RENAMED] = g_signal_new ( + "group-renamed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _tp_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); } /** diff --git a/telepathy-glib/signals-marshal.list b/telepathy-glib/signals-marshal.list index 1b31321..1e1ad0e 100644 --- a/telepathy-glib/signals-marshal.list +++ b/telepathy-glib/signals-marshal.list @@ -1,3 +1,4 @@ +VOID:BOXED VOID:BOXED,BOXED VOID:BOXED,UINT,INT,STRING VOID:INT,UINT,STRING -- 1.7.4.1