/* * conn-contact-info.c - Gabble connection ContactInfo interface * Copyright (C) 2009-2010 Collabora Ltd. * Copyright (C) 2009-2010 Nokia Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "conn-contact-info.h" #include #include #include #include #include "vcard-manager.h" #define DEBUG_FLAG GABBLE_DEBUG_CONNECTION #include "debug.h" #include "util.h" /* Arbitrary lengths for supported fields' types, increase as necessary when * adding new fields */ #define MAX_TYPES 14 #define MAX_ELEMENTS 8 #define MAX_TYPE_PARAM_LEN 8 /* strlen ("internet") in "type=internet" */ typedef enum { /* in Telepathy: one value per field; in XMPP: one value per field */ FIELD_SIMPLE, /* same as FIELD_SIMPLE but may not be repeated */ FIELD_SIMPLE_ONCE, /* in Telepathy: exactly n_elements values; in XMPP: a child element for * each entry in elements, in that order */ FIELD_STRUCTURED, /* same as FIELD_STRUCTURED but may not be repeated */ FIELD_STRUCTURED_ONCE, /* Special cases: */ /* in Telepathy, one multi-line value; in XMPP, a sequence of s */ FIELD_LABEL, /* same as FIELD_STRUCTURED except the last element may repeat n times */ FIELD_ORG, /* a field we intentionally ignore */ FIELD_IGNORED } FieldBehaviour; typedef struct { /* Name in XEP-0054, vcard-temp (upper-case as per the DTD) */ const gchar *xmpp_name; /* Name in Telepathy's vCard representation (lower-case), or NULL * to lower-case the XEP-0054 name automatically */ const gchar *vcard_name; /* General type of field */ FieldBehaviour behaviour; /* Telepathy flags for this field (none are applicable to XMPP yet) */ TpContactInfoFieldFlags tp_flags; /* Valid values for the TYPE type-parameter, in upper case */ const gchar * const types[MAX_TYPES]; /* Child elements for structured/repeating fields, in upper case */ const gchar * const elements[MAX_ELEMENTS]; } VCardField; static VCardField known_fields[] = { /* Simple fields */ { "FN", NULL, FIELD_SIMPLE_ONCE, 0, { NULL }, { NULL } }, { "BDAY", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "MAILER", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "TZ", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "TITLE", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "ROLE", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "NOTE", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "PRODID", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "REV", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "SORT-STRING", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "UID", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "URL", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "NICKNAME", NULL, FIELD_SIMPLE, 0, { NULL }, { NULL } }, /* Simple fields which are Jabber-specific */ { "JABBERID", "x-jabber", FIELD_SIMPLE, 0, { NULL }, { NULL } }, { "DESC", "x-desc", FIELD_SIMPLE, 0, { NULL }, { NULL } }, /* Structured fields */ { "N", NULL, FIELD_STRUCTURED_ONCE, 0, { NULL }, { "FAMILY", "GIVEN", "MIDDLE", "PREFIX", "SUFFIX", NULL } }, { "ADR", NULL, FIELD_STRUCTURED, 0, { "type=home", "type=work", "type=postal", "type=parcel", "type=dom", "type=intl", "type=pref", NULL }, { "POBOX", "EXTADD", "STREET", "LOCALITY", "REGION", "PCODE", "CTRY", NULL } }, { "GEO", NULL, FIELD_STRUCTURED_ONCE, 0, { NULL }, { "LAT", "LON", NULL } }, /* TEL and EMAIL are like structured fields: they have exactly one child * per occurrence */ { "TEL", NULL, FIELD_STRUCTURED, 0, { "type=home", "type=work", "type=voice", "type=fax", "type=pager", "type=msg", "type=cell", "type=video", "type=bbs", "type=modem", "type=isdn", "type=pcs", "type=pref", NULL }, { "NUMBER", NULL } }, { "EMAIL", NULL, FIELD_STRUCTURED, 0, { "type=home", "type=work", "type=internet", "type=pref", "type=x400", NULL }, { "USERID", NULL } }, /* Special cases with their own semantics */ { "LABEL", NULL, FIELD_LABEL, 0, { "type=home", "type=work", "type=postal", "type=parcel", "type=dom", "type=intl", "type=pref", NULL }, { NULL } }, { "ORG", NULL, FIELD_ORG, 0, { NULL }, { NULL } }, /* Things we don't handle: */ /* PHOTO is handled by the Avatar code */ { "PHOTO", NULL, FIELD_IGNORED }, /* KEY: is Base64 (perhaps? hard to tell from the XEP) */ /* LOGO: can be base64 or a URL */ /* SOUND: can be base64, URL, or phonetic (!) */ /* AGENT: is an embedded vCard (!) */ /* CATEGORIES: same vCard encoding as NICKNAME, but split into KEYWORDs * in XMPP; nobody is likely to use it on XMPP */ /* CLASS: if you're putting non-PUBLIC vCards on your XMPP account, * you're probably Doing It Wrong */ { NULL } }; /* static XML element name => static VCardField */ static GHashTable *known_fields_xmpp = NULL; /* g_strdup'd Telepathy pseudo-vCard element name => static VCardField */ static GHashTable *known_fields_vcard = NULL; /* * _insert_contact_field: * @contact_info: an array of Contact_Info_Field structures * @field_name: a vCard field name in any case combination * @field_params: a list of vCard type-parameters, typically of the form * type=xxx; must be in lower-case if case-insensitive * @field_values: for unstructured fields, an array containing one element; * for structured fields, the elements of the field in order */ static void _insert_contact_field (GPtrArray *contact_info, const gchar *field_name, const gchar * const *field_params, const gchar * const *field_values) { gchar *field_name_down = g_ascii_strdown (field_name, -1); g_ptr_array_add (contact_info, tp_value_array_build (3, G_TYPE_STRING, field_name_down, G_TYPE_STRV, field_params, G_TYPE_STRV, field_values, G_TYPE_INVALID)); g_free (field_name_down); } static void _create_contact_field_extended (GPtrArray *contact_info, WockyNode *node, const gchar * const *supported_types, const gchar * const *mandatory_fields) { guint i; WockyNode *child_node; GPtrArray *field_params = NULL; gchar **field_values = NULL; guint supported_types_size = 0; guint mandatory_fields_size = 0; if (supported_types != NULL) supported_types_size = g_strv_length ((gchar **) supported_types); field_params = g_ptr_array_new (); /* we can simply omit a type if not found */ for (i = 0; i < supported_types_size; ++i) { guint j; gchar child_name[MAX_TYPE_PARAM_LEN + 1] = { '\0' }; /* the +5 is to skip over "type=" - all type-parameters we support have * type=, which is verified in conn_contact_info_build_supported_fields */ for (j = 0; j < MAX_TYPE_PARAM_LEN && supported_types[i][j + 5] != '\0'; j++) { child_name[j] = g_ascii_toupper (supported_types[i][j + 5]); } child_node = wocky_node_get_child (node, child_name); if (child_node != NULL) g_ptr_array_add (field_params, (gchar *) supported_types[i]); } g_ptr_array_add (field_params, NULL); if (mandatory_fields != NULL) { mandatory_fields_size = g_strv_length ((gchar **) mandatory_fields); /* the mandatory field values need to be ordered properly */ field_values = g_new0 (gchar *, mandatory_fields_size + 1); for (i = 0; i < mandatory_fields_size; ++i) { child_node = wocky_node_get_child (node, mandatory_fields[i]); if (child_node != NULL) field_values[i] = child_node->content; else field_values[i] = ""; } } _insert_contact_field (contact_info, node->name, (const gchar * const *) field_params->pdata, (const gchar * const *) field_values); /* The strings in both arrays are borrowed, so we just need to free the * arrays themselves. */ g_ptr_array_free (field_params, TRUE); g_free (field_values); } static GPtrArray * _parse_vcard (WockyNode *vcard_node, GError **error) { GPtrArray *contact_info = dbus_g_type_specialized_construct ( TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST); NodeIter i; for (i = node_iter (vcard_node); i; i = node_iter_next (i)) { WockyNode *node = node_iter_data (i); const VCardField *field; if (node->name == NULL || !tp_strdiff (node->name, "")) continue; field = g_hash_table_lookup (known_fields_xmpp, node->name); if (field == NULL) { DEBUG ("unknown vCard node in XML: %s", node->name); continue; } switch (field->behaviour) { case FIELD_SIMPLE: case FIELD_SIMPLE_ONCE: { const gchar * const field_values[2] = { node->content, NULL }; _insert_contact_field (contact_info, node->name, NULL, field_values); } break; case FIELD_STRUCTURED: case FIELD_STRUCTURED_ONCE: { _create_contact_field_extended (contact_info, node, field->types, field->elements); } break; case FIELD_ORG: { WockyNode *orgname = wocky_node_get_child (node, "ORGNAME"); NodeIter orgunit_iter; GPtrArray *field_values; const gchar *value; if (orgname == NULL) { DEBUG ("ignoring with no "); break; } field_values = g_ptr_array_new (); value = orgname->content; if (value == NULL) value = ""; g_ptr_array_add (field_values, (gpointer) value); for (orgunit_iter = node_iter (node); orgunit_iter != NULL; orgunit_iter = node_iter_next (orgunit_iter)) { WockyNode *orgunit = node_iter_data (orgunit_iter); if (tp_strdiff (orgunit->name, "ORGUNIT")) continue; value = orgunit->content; if (value == NULL) value = ""; g_ptr_array_add (field_values, (gpointer) value); } g_ptr_array_add (field_values, NULL); _insert_contact_field (contact_info, "org", NULL, (const gchar * const *) field_values->pdata); g_ptr_array_free (field_values, TRUE); } break; case FIELD_LABEL: { NodeIter line_iter; gchar *field_values[2] = { NULL, NULL }; GString *text = g_string_new (""); for (line_iter = node_iter (node); line_iter != NULL; line_iter = node_iter_next (line_iter)) { const gchar *line; WockyNode *line_node = node_iter_data (line_iter); if (tp_strdiff (line_node->name, "LINE")) continue; line = line_node->content; if (line != NULL) { g_string_append (text, line); } if (line == NULL || ! g_str_has_suffix (line, "\n")) { g_string_append_c (text, '\n'); } } field_values[0] = g_string_free (text, FALSE); _insert_contact_field (contact_info, "label", NULL, (const gchar * const *) field_values); g_free (field_values[0]); } break; case FIELD_IGNORED: break; default: g_assert_not_reached (); } } return contact_info; } static void _emit_contact_info_changed (TpSvcConnectionInterfaceContactInfo *iface, TpHandle contact, WockyNode *vcard_node) { GPtrArray *contact_info; contact_info = _parse_vcard (vcard_node, NULL); if (contact_info == NULL) return; tp_svc_connection_interface_contact_info_emit_contact_info_changed ( iface, contact, contact_info); g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info); } static void _request_vcards_cb (GabbleVCardManager *manager, GabbleVCardManagerRequest *request, TpHandle handle, WockyNode *vcard_node, GError *vcard_error, gpointer user_data) { GabbleConnection *conn = GABBLE_CONNECTION (user_data); g_assert (g_hash_table_lookup (conn->vcard_requests, GUINT_TO_POINTER (handle))); g_hash_table_remove (conn->vcard_requests, GUINT_TO_POINTER (handle)); /* No need to signal ContactInfoChanged here because it'll get done * in vcard_updated. */ } /** * gabble_connection_get_contact_info * * Implements D-Bus method GetContactInfo * on interface org.freedesktop.Telepathy.Connection.Interface.ContactInfo * * @context: The D-Bus invocation context to use to return values * or throw an error. */ static void gabble_connection_get_contact_info ( TpSvcConnectionInterfaceContactInfo *iface, const GArray *contacts, DBusGMethodInvocation *context) { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; TpHandleRepoIface *contacts_repo = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); GError *error = NULL; guint i; GHashTable *ret; TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (TP_BASE_CONNECTION (iface), context); if (!tp_handles_are_valid (contacts_repo, contacts, FALSE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } ret = dbus_g_type_specialized_construct (TP_HASH_TYPE_CONTACT_INFO_MAP); for (i = 0; i < contacts->len; i++) { WockyNode *vcard_node; TpHandle contact = g_array_index (contacts, TpHandle, i); if (gabble_vcard_manager_get_cached (self->vcard_manager, contact, &vcard_node)) { GPtrArray *contact_info = _parse_vcard (vcard_node, NULL); /* we have the cached vcard but it cannot be parsed, skipping */ if (contact_info == NULL) { DEBUG ("contact %d vcard is cached but cannot be parsed, " "skipping.", contact); continue; } g_hash_table_insert (ret, GUINT_TO_POINTER (contact), contact_info); } } tp_svc_connection_interface_contact_info_return_from_get_contact_info ( context, ret); g_boxed_free (TP_HASH_TYPE_CONTACT_INFO_MAP, ret); } static void _return_from_request_contact_info (WockyNode *vcard_node, GError *vcard_error, DBusGMethodInvocation *context) { GError *error = NULL; GPtrArray *contact_info; if (NULL == vcard_node) { GError tp_error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, vcard_error->message }; if (vcard_error->domain == GABBLE_XMPP_ERROR) { switch (vcard_error->code) { case XMPP_ERROR_NOT_AUTHORIZED: case XMPP_ERROR_FORBIDDEN: tp_error.code = TP_ERROR_PERMISSION_DENIED; break; case XMPP_ERROR_ITEM_NOT_FOUND: tp_error.code = TP_ERROR_DOES_NOT_EXIST; break; } /* what other mappings make sense here? */ } dbus_g_method_return_error (context, &tp_error); return; } contact_info = _parse_vcard (vcard_node, &error); if (contact_info == NULL) { dbus_g_method_return_error (context, error); g_error_free (error); return; } tp_svc_connection_interface_contact_info_return_from_request_contact_info ( context, contact_info); g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info); } static void _request_vcard_cb (GabbleVCardManager *self, GabbleVCardManagerRequest *request, TpHandle handle, WockyNode *vcard_node, GError *vcard_error, gpointer user_data) { DBusGMethodInvocation *context = user_data; _return_from_request_contact_info (vcard_node, vcard_error, context); } /** * gabble_connection_refresh_contact_info * * Implements D-Bus method RefreshContactInfo * on interface org.freedesktop.Telepathy.Connection.Interface.ContactInfo * * @context: The D-Bus invocation context to use to return values * or throw an error. */ static void gabble_connection_refresh_contact_info (TpSvcConnectionInterfaceContactInfo *iface, const GArray *contacts, DBusGMethodInvocation *context) { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; TpHandleRepoIface *contacts_repo = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); GError *error = NULL; guint i; TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (TP_BASE_CONNECTION (iface), context); if (!tp_handles_are_valid (contacts_repo, contacts, FALSE, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } for (i = 0; i < contacts->len; i++) { TpHandle contact = g_array_index (contacts, TpHandle, i); if (g_hash_table_lookup (self->vcard_requests, GUINT_TO_POINTER (contact)) == NULL) { GabbleVCardManagerRequest *request; gabble_vcard_manager_invalidate_cache (self->vcard_manager, contact); request = gabble_vcard_manager_request (self->vcard_manager, contact, 0, _request_vcards_cb, self, NULL); g_hash_table_insert (self->vcard_requests, GUINT_TO_POINTER (contact), request); } } tp_svc_connection_interface_contact_info_return_from_refresh_contact_info ( context); } /** * gabble_connection_request_contact_info * * Implements D-Bus method RequestContactInfo * on interface org.freedesktop.Telepathy.Connection.Interface.ContactInfo * * @context: The D-Bus invocation context to use to return values * or throw an error. */ static void gabble_connection_request_contact_info (TpSvcConnectionInterfaceContactInfo *iface, guint contact, DBusGMethodInvocation *context) { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT); GError *err = NULL; WockyNode *vcard_node; TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); if (!tp_handle_is_valid (contact_handles, contact, &err)) { dbus_g_method_return_error (context, err); g_error_free (err); return; } if (gabble_vcard_manager_get_cached (self->vcard_manager, contact, &vcard_node)) _return_from_request_contact_info (vcard_node, NULL, context); else gabble_vcard_manager_request (self->vcard_manager, contact, 0, _request_vcard_cb, context, NULL); } static GabbleVCardManagerEditInfo * conn_contact_info_new_edit (const VCardField *field, const gchar *value, const gchar * const *field_params, GError **error) { GabbleVCardManagerEditInfo *edit_info; GabbleVCardEditType edit_type = GABBLE_VCARD_EDIT_APPEND; const gchar * const *p; if (field->behaviour == FIELD_STRUCTURED_ONCE || field->behaviour == FIELD_SIMPLE_ONCE) edit_type = GABBLE_VCARD_EDIT_REPLACE; edit_info = gabble_vcard_manager_edit_info_new (field->xmpp_name, value, edit_type, NULL); if (field_params == NULL) return edit_info; if (field->types[0] == NULL && field_params[0] != NULL) { g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "%s vCard field expects no type-parameters", field->xmpp_name); gabble_vcard_manager_edit_info_free (edit_info); return NULL; } for (p = field_params; *p != NULL; ++p) { guint i; gboolean used = FALSE; for (i = 0; field->types[i] != NULL; i++) { if (!tp_strdiff (field->types[i], *p)) { /* the +5 is to skip over "type=" - all type-parameters we * support have type=, which is verified in * conn_contact_info_build_supported_fields */ gchar *tmp = g_ascii_strup (field->types[i] + 5, -1); gabble_vcard_manager_edit_info_add_child (edit_info, tmp, NULL); g_free (tmp); used = TRUE; break; } } if (!used) { g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "%s vCard field does not support type-parameter %s", field->xmpp_name, *p); gabble_vcard_manager_edit_info_free (edit_info); return NULL; } } return edit_info; } static void _set_contact_info_cb (GabbleVCardManager *vcard_manager, GabbleVCardManagerEditRequest *request, WockyNode *vcard_node, GError *vcard_error, gpointer user_data) { DBusGMethodInvocation *context = user_data; if (vcard_node == NULL) { GError tp_error = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, vcard_error->message }; if (vcard_error->domain == GABBLE_XMPP_ERROR) if (vcard_error->code == XMPP_ERROR_BAD_REQUEST || vcard_error->code == XMPP_ERROR_NOT_ACCEPTABLE) tp_error.code = TP_ERROR_INVALID_ARGUMENT; dbus_g_method_return_error (context, &tp_error); } else { tp_svc_connection_interface_contact_info_return_from_set_contact_info ( context); } } /** * gabble_connection_set_contact_info * * Implements D-Bus method SetContactInfo * on interface org.freedesktop.Telepathy.Connection.Interface.ContactInfo * * @context: The D-Bus invocation context to use to return values * or throw an error. */ static void gabble_connection_set_contact_info (TpSvcConnectionInterfaceContactInfo *iface, const GPtrArray *contact_info, DBusGMethodInvocation *context) { GabbleConnection *self = GABBLE_CONNECTION (iface); TpBaseConnection *base = (TpBaseConnection *) self; GList *edits = NULL; guint i; GError *error = NULL; TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); for (i = 0; i < contact_info->len; i++) { GValueArray *structure = g_ptr_array_index (contact_info, i); guint n_field_values = 0; VCardField *field; const gchar *field_name; const gchar * const *field_params; const gchar * const *field_values; GabbleVCardManagerEditInfo *edit_info; field_name = g_value_get_string (structure->values + 0); field_params = g_value_get_boxed (structure->values + 1); field_values = g_value_get_boxed (structure->values + 2); if (field_values != NULL) n_field_values = g_strv_length ((gchar **) field_values); field = g_hash_table_lookup (known_fields_vcard, field_name); if (field == NULL) { g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "unknown vCard field from D-Bus: %s", field_name); goto finally; } if (!gabble_vcard_manager_can_use_vcard_field (self->vcard_manager, field->xmpp_name)) { g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "%s vCard field is not supported by this server", field->xmpp_name); goto finally; } switch (field->behaviour) { case FIELD_SIMPLE: case FIELD_SIMPLE_ONCE: { if (n_field_values != 1) { g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "%s vCard field expects one value but got %u", field->xmpp_name, n_field_values); goto finally; } edit_info = conn_contact_info_new_edit (field, field_values[0], field_params, &error); if (edit_info == NULL) { goto finally; } } break; case FIELD_STRUCTURED: case FIELD_STRUCTURED_ONCE: { guint n_elements = g_strv_length ((gchar **) field->elements); guint j; if (n_field_values != n_elements) { g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "%s vCard field expects %u values but got %u", field->xmpp_name, n_elements, n_field_values); goto finally; } edit_info = conn_contact_info_new_edit (field, NULL, field_params, &error); if (edit_info == NULL) { goto finally; } for (j = 0; j < n_elements; ++j) gabble_vcard_manager_edit_info_add_child (edit_info, field->elements[j], field_values[j]); } break; case FIELD_ORG: { guint j; if (n_field_values == 0) { g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "ORG vCard field expects at least one value but got 0"); goto finally; } edit_info = conn_contact_info_new_edit (field, NULL, field_params, &error); if (edit_info == NULL) { goto finally; } gabble_vcard_manager_edit_info_add_child (edit_info, "ORGNAME", field_values[0]); for (j = 1; field_values[j] != NULL; j++) { gabble_vcard_manager_edit_info_add_child (edit_info, "ORGUNIT", field_values[j]); } } break; case FIELD_LABEL: { gchar **lines; guint j; if (n_field_values != 1) { g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "%s vCard field expects one value but got %u", field->xmpp_name, n_field_values); goto finally; } edit_info = conn_contact_info_new_edit (field, NULL, field_params, &error); if (edit_info == NULL) { goto finally; } lines = g_strsplit (field_values[0], "\n", 0); for (j = 0; lines[j] != NULL; j++) { /* don't emit a trailing empty line if the label ended * with \n */ if (lines[j][0] == '\0' && lines[j + 1] == NULL) continue; gabble_vcard_manager_edit_info_add_child (edit_info, "LINE", lines[j]); } g_strfreev (lines); } break; default: g_assert_not_reached (); } g_assert (edit_info != NULL); edits = g_list_append (edits, edit_info); } finally: if (error != NULL) { DEBUG ("%s", error->message); g_list_foreach (edits, (GFunc) gabble_vcard_manager_edit_info_free, NULL); dbus_g_method_return_error (context, error); g_error_free (error); } else { edits = g_list_prepend (edits, gabble_vcard_manager_edit_info_new (NULL, NULL, GABBLE_VCARD_EDIT_CLEAR, NULL)); /* fix the alias (if missing) afterwards */ edits = g_list_append (edits, gabble_vcard_manager_edit_info_new (NULL, NULL, GABBLE_VCARD_EDIT_SET_ALIAS, NULL)); gabble_vcard_manager_edit (self->vcard_manager, 0, _set_contact_info_cb, context, G_OBJECT (self), edits); } } static void _vcard_updated (GObject *object, TpHandle contact, gpointer user_data) { GabbleConnection *conn = GABBLE_CONNECTION (user_data); WockyNode *vcard_node; if (conn->vcard_manager != NULL && gabble_vcard_manager_get_cached (conn->vcard_manager, contact, &vcard_node)) { _emit_contact_info_changed ( TP_SVC_CONNECTION_INTERFACE_CONTACT_INFO (conn), contact, vcard_node); } } static GPtrArray * conn_contact_info_build_supported_fields (GabbleConnection *conn, GabbleVCardManager *vcard_manager) { GPtrArray *fields = dbus_g_type_specialized_construct ( TP_ARRAY_TYPE_FIELD_SPECS); VCardField *field; for (field = known_fields; field->xmpp_name != NULL; field++) { GValueArray *va; gchar *vcard_name; guint max_times; guint i; TpContactInfoFieldFlags tp_flags = field->tp_flags; if (field->behaviour == FIELD_IGNORED) continue; /* Shorthand to avoid having to put it in the struct initialization: * on XMPP, there is no field that supports arbitrary type-parameters. * Setting Parameters_Mandatory eliminates the special case that an * empty list means arbitrary parameters. */ if (field->types[0] == NULL) { tp_flags |= TP_CONTACT_INFO_FIELD_FLAG_PARAMETERS_EXACT; } #ifndef G_DISABLE_ASSERT for (i = 0; field->types[i] != NULL; i++) { /* All type-parameters XMPP currently supports are of the form type=, * which is assumed in _create_contact_field_extended and * conn_contact_info_edit_add_type_params */ g_assert (g_str_has_prefix (field->types[i], "type=")); g_assert_cmpuint ((guint) strlen (field->types[i]), <=, MAX_TYPE_PARAM_LEN + 5); } #endif if (!gabble_vcard_manager_can_use_vcard_field (vcard_manager, field->xmpp_name)) { continue; } if (field->vcard_name != NULL) vcard_name = g_strdup (field->vcard_name); else vcard_name = g_ascii_strdown (field->xmpp_name, -1); /* On Google, your full name in your VCard *is* your alias, so * SetAliases and setting FN will do exactly the same thing. As * a result, we should set the Overwritten_By_Nickname flag on * the FN VCard field if we're on Google. * * If we're not on Google, your nickname in your VCard is your * alias and the same stuff applies, so we should set the same * flag. * * Doing this means that UIs can omit fields from ContactInfo * dialogs with this flag set so they can just display one * widget to set the alias. It's confusing to have an entry * widget which gets overridden and can screw up your alias * setting anyway. */ if (conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER) { if (!tp_strdiff (vcard_name, "fn")) tp_flags |= TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME; } else { if (!tp_strdiff (vcard_name, "nickname")) tp_flags |= TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME; } switch (field->behaviour) { case FIELD_SIMPLE_ONCE: case FIELD_STRUCTURED_ONCE: max_times = 1; break; default: max_times = G_MAXUINT32; } va = tp_value_array_build (4, G_TYPE_STRING, vcard_name, G_TYPE_STRV, field->types, G_TYPE_UINT, tp_flags, G_TYPE_UINT, max_times, G_TYPE_INVALID); g_free (vcard_name); g_ptr_array_add (fields, va); } return fields; } void conn_contact_info_class_init (GabbleConnectionClass *klass) { VCardField *field; /* These are never freed; they're only allocated once per run of Gabble. * The destructor in the latter is only set for completeness */ known_fields_xmpp = g_hash_table_new (g_str_hash, g_str_equal); known_fields_vcard = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (field = known_fields; field->xmpp_name != NULL; field++) { gchar *vcard_name; if (field->vcard_name != NULL) vcard_name = g_strdup (field->vcard_name); else vcard_name = g_ascii_strdown (field->xmpp_name, -1); g_hash_table_insert (known_fields_xmpp, (gchar *) field->xmpp_name, field); g_hash_table_insert (known_fields_vcard, vcard_name, field); } } static void conn_contact_info_status_changed_cb (GabbleConnection *conn, guint status, guint reason, gpointer user_data G_GNUC_UNUSED) { if (status != TP_CONNECTION_STATUS_CONNECTED) return; if (gabble_vcard_manager_has_limited_vcard_fields (conn->vcard_manager)) { g_boxed_free (TP_ARRAY_TYPE_FIELD_SPECS, conn->contact_info_fields); conn->contact_info_fields = conn_contact_info_build_supported_fields ( conn, conn->vcard_manager); } } static void conn_contact_info_fill_contact_attributes (GObject *obj, const GArray *contacts, GHashTable *attributes_hash) { guint i; GabbleConnection *self = GABBLE_CONNECTION (obj); g_assert (self->vcard_manager != NULL); for (i = 0; i < contacts->len; i++) { TpHandle contact = g_array_index (contacts, TpHandle, i); WockyNode *vcard_node; if (gabble_vcard_manager_get_cached (self->vcard_manager, contact, &vcard_node)) { GPtrArray *contact_info = _parse_vcard (vcard_node, NULL); if (contact_info != NULL) { GValue *val = tp_g_value_slice_new_take_boxed ( TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, contact_info); tp_contacts_mixin_set_contact_attribute (attributes_hash, contact, TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO"/info", val); } } } } void conn_contact_info_init (GabbleConnection *conn) { g_assert (conn->vcard_manager != NULL); tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (conn), TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, conn_contact_info_fill_contact_attributes); conn->contact_info_fields = conn_contact_info_build_supported_fields (conn, conn->vcard_manager); g_signal_connect (conn->vcard_manager, "vcard-update", G_CALLBACK (_vcard_updated), conn); g_signal_connect (conn, "status-changed", G_CALLBACK (conn_contact_info_status_changed_cb), NULL); } void conn_contact_info_finalize (GabbleConnection *conn) { g_boxed_free (TP_ARRAY_TYPE_FIELD_SPECS, conn->contact_info_fields); conn->contact_info_fields = NULL; } void conn_contact_info_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcConnectionInterfaceContactInfoClass *klass = g_iface; #define IMPLEMENT(x) tp_svc_connection_interface_contact_info_implement_##x (\ klass, gabble_connection_##x) IMPLEMENT(get_contact_info); IMPLEMENT(refresh_contact_info); IMPLEMENT(request_contact_info); IMPLEMENT(set_contact_info); #undef IMPLEMENT } static TpDBusPropertiesMixinPropImpl props[] = { { "ContactInfoFlags", GUINT_TO_POINTER (TP_CONTACT_INFO_FLAG_CAN_SET), NULL }, { "SupportedFields", NULL, NULL }, { NULL } }; TpDBusPropertiesMixinPropImpl *conn_contact_info_properties = props; void conn_contact_info_properties_getter (GObject *object, GQuark interface, GQuark name, GValue *value, gpointer getter_data) { GabbleConnection *conn = GABBLE_CONNECTION (object); GQuark q_supported_fields = g_quark_from_static_string ( "SupportedFields"); if (name == q_supported_fields) { g_value_set_boxed (value, conn->contact_info_fields); } else { g_value_set_uint (value, GPOINTER_TO_UINT (getter_data)); } }