/* * Copyright (C) 2012 Intel 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) version 3. * * 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 Street, Fifth Floor, Boston, MA * 02110-1301 USA */ /** * The D-Bus IPC binding for folks.h. Maps FolksIndividual to and * from the D-Bus dict described in pim-manager-api.txt. */ #include "individual-traits.h" #include "persona-details.h" #include "folks.h" SE_GLIB_TYPE(GDateTime, g_date_time) SE_GOBJECT_TYPE(GTimeZone) SE_GOBJECT_TYPE(GObject) SE_BEGIN_CXX typedef GValueDynTypedCXX GValueDateTimeCXX; static const char * const CONTACT_HASH_FULL_NAME = "full-name"; static const char * const CONTACT_HASH_NICKNAME = "nickname"; static const char * const CONTACT_HASH_STRUCTURED_NAME = "structured-name"; static const char * const CONTACT_HASH_STRUCTURED_NAME_FAMILY = "family"; static const char * const CONTACT_HASH_STRUCTURED_NAME_GIVEN = "given"; static const char * const CONTACT_HASH_STRUCTURED_NAME_ADDITIONAL = "additional"; static const char * const CONTACT_HASH_STRUCTURED_NAME_PREFIXES = "prefixes"; static const char * const CONTACT_HASH_STRUCTURED_NAME_SUFFIXES = "suffixes"; static const char * const CONTACT_HASH_ALIAS = "alias"; static const char * const CONTACT_HASH_PHOTO = "photo"; static const char * const CONTACT_HASH_BIRTHDAY = "birthday"; static const char * const CONTACT_HASH_LOCATION = "location"; static const char * const CONTACT_HASH_EMAILS = "emails"; static const char * const CONTACT_HASH_PHONES = "phones"; static const char * const CONTACT_HASH_URLS = "urls"; static const char * const CONTACT_HASH_NOTES = "notes"; static const char * const CONTACT_HASH_ADDRESSES = "addresses"; static const char * const CONTACT_HASH_ADDRESSES_PO_BOX = "po-box"; static const char * const CONTACT_HASH_ADDRESSES_EXTENSION = "extension"; static const char * const CONTACT_HASH_ADDRESSES_STREET = "street"; static const char * const CONTACT_HASH_ADDRESSES_LOCALITY = "locality"; static const char * const CONTACT_HASH_ADDRESSES_REGION = "region"; static const char * const CONTACT_HASH_ADDRESSES_POSTAL_CODE = "postal-code"; static const char * const CONTACT_HASH_ADDRESSES_COUNTRY = "country"; static const char * const CONTACT_HASH_ROLES = "roles"; static const char * const CONTACT_HASH_ROLES_ORGANISATION = "organisation"; static const char * const CONTACT_HASH_ROLES_TITLE = "title"; static const char * const CONTACT_HASH_ROLES_ROLE = "role"; static const char * const CONTACT_HASH_GROUPS = "groups"; static const char * const CONTACT_HASH_SOURCE = "source"; static const char * const CONTACT_HASH_ID = "id"; static const char * const INDIVIDUAL_DICT = "a{sv}"; static const char * const INDIVIDUAL_DICT_ENTRY = "{sv}"; /** * Checks whether a certain value is the default value and thus * can be skipped when converting to D-Bus. * * class B provides additional type information, which is necessary * to check a GeeSet containing FolksRoleFieldDetails differently * than other GeeSets. */ template struct IsNonDefault { template static bool check(V value) { // Default version uses normal C/C++ rules, for example pointer non-NULL. // For bool and integer, the value will only be sent if true or non-zero. return value; } static bool check(const gchar *value) { // Don't send empty strings. return value && value[0]; } static bool check(GeeSet *value) { // Don't send empty sets. return value && gee_collection_get_size(GEE_COLLECTION(value)); } }; template <> struct IsNonDefault< GeeCollCXX > { static bool check(GeeSet *value) { // Don't send empty set and set which contains only empty roles. if (value) { BOOST_FOREACH (FolksRoleFieldDetails *value, GeeCollCXX(value)) { FolksRole *role = static_cast(const_cast((folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(value))))); if (IsNonDefault::check(folks_role_get_organisation_name(role)) || IsNonDefault::check(folks_role_get_title(role)) || IsNonDefault::check(folks_role_get_role(role))) { return true; } } } return false; } }; /** * Adds a dict entry to the builder, with 'key' as string key and the * result of 'get()' as value. */ template void SerializeFolks(GDBusCXX::builder_type &builder, O *obj, V (*get)(O *), const char *key) { if (!obj) { SE_THROW("casting to base class failed"); } V value = get(obj); if (IsNonDefault::check(value)) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT_ENTRY)); // dict entry GDBusCXX::dbus_traits::append(builder, key); g_variant_builder_open(&builder, G_VARIANT_TYPE("v")); // variant GDBusCXX::dbus_traits::append(builder, value); g_variant_builder_close(&builder); // variant g_variant_builder_close(&builder); // dict entry } } template void SerializeFolks(GDBusCXX::builder_type &builder, O *obj, V (*get)(O *), B *, // dummy parameter, determines base type const char *key) { if (!obj) { SE_THROW("casting to base class failed"); } V value = get(obj); if (IsNonDefault::check(value)) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT_ENTRY)); // dict entry GDBusCXX::dbus_traits::append(builder, key); g_variant_builder_open(&builder, G_VARIANT_TYPE("v")); // variant GDBusCXX::dbus_traits::append(builder, value); g_variant_builder_close(&builder); // variant g_variant_builder_close(&builder); // dict entry } } SE_END_CXX namespace GDBusCXX { template <> struct dbus_traits { static void append(builder_type &builder, FolksStructuredName *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_family_name, CONTACT_HASH_STRUCTURED_NAME_FAMILY); SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_given_name, CONTACT_HASH_STRUCTURED_NAME_GIVEN); SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_additional_names, CONTACT_HASH_STRUCTURED_NAME_ADDITIONAL); SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_prefixes, CONTACT_HASH_STRUCTURED_NAME_PREFIXES); SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_suffixes, CONTACT_HASH_STRUCTURED_NAME_SUFFIXES); g_variant_builder_close(&builder); // dict } }; /** Path to icon (the expected case) or uninitialized string (not set or not a file). */ static InitStateString ExtractFilePath(GLoadableIcon *value) { if (value && G_IS_FILE_ICON(value)) { GFileIcon *fileIcon = G_FILE_ICON(value); GFile *file = g_file_icon_get_file(fileIcon); if (file) { PlainGStr uri(g_file_get_uri(file)); // Have a path. return InitStateString(uri.get(), true); } } // No path. return InitStateString(); } template <> struct dbus_traits { static void append(builder_type &builder, GLoadableIcon *value) { InitStateString path = ExtractFilePath(value); // EDS is expected to only work with URIs for the PHOTO // property, therefore we shouldn't get here without a valid // path. Either way, we need to store something. GDBusCXX::dbus_traits::append(builder, path.c_str()); } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksPostalAddress *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_po_box, CONTACT_HASH_ADDRESSES_PO_BOX); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_extension, CONTACT_HASH_ADDRESSES_EXTENSION); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_street, CONTACT_HASH_ADDRESSES_STREET); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_locality, CONTACT_HASH_ADDRESSES_LOCALITY); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_region, CONTACT_HASH_ADDRESSES_REGION); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_postal_code, CONTACT_HASH_ADDRESSES_POSTAL_CODE); SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_country, CONTACT_HASH_ADDRESSES_COUNTRY); // Not used by EDS. The tracker backend in folks was able to provide such a uid. // SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_address_format, CONTACT_HASH_ADDRESSES_FORMAT); // SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_uid, "uid"); g_variant_builder_close(&builder); // dict } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksRole *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict // Other parts of ORG are not currently supported by libfolks. SyncEvo::SerializeFolks(builder, value, folks_role_get_organisation_name, CONTACT_HASH_ROLES_ORGANISATION); SyncEvo::SerializeFolks(builder, value, folks_role_get_title, CONTACT_HASH_ROLES_TITLE); SyncEvo::SerializeFolks(builder, value, folks_role_get_role, CONTACT_HASH_ROLES_ROLE); // Not used: // SyncEvo::SerializeFolks(builder, value, folks_role_get_uid, "uid"); g_variant_builder_close(&builder); // dict } }; // TODO: put into global header file static const char * const MANAGER_PREFIX = "pim-manager-"; template <> struct dbus_traits { static void append(builder_type &builder, FolksPersona *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE("(ss)")); // pair of peer ID and local ID const gchar *uid = folks_persona_get_uid(value); if (uid) { gchar *backend, *storeID, *personaID; folks_persona_split_uid(uid, &backend, &storeID, &personaID); PlainGStr tmp1(backend), tmp2(storeID), tmp3(personaID); if (boost::starts_with(storeID, MANAGER_PREFIX)) { dbus_traits::append(builder, storeID + strlen(MANAGER_PREFIX)); } else { // Must be the system address book. dbus_traits::append(builder, ""); } dbus_traits::append(builder, personaID); } else { dbus_traits::append(builder, ""); dbus_traits::append(builder, ""); } g_variant_builder_close(&builder); // pair } }; // Only use this with FolksAbstractFieldDetails instances where // the value is a string. template <> struct dbus_traits { static void append(builder_type &builder, FolksAbstractFieldDetails *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE("(sas)")); // pair of string and string list gconstpointer v = folks_abstract_field_details_get_value(value); dbus_traits::append(builder, v ? (const char *)v : ""); g_variant_builder_open(&builder, G_VARIANT_TYPE("as")); GeeMultiMap *map = folks_abstract_field_details_get_parameters(value); if (map) { BOOST_FOREACH (const char *type, GeeCollCXX(gee_multi_map_get(map, FOLKS_ABSTRACT_FIELD_DETAILS_PARAM_TYPE))) { dbus_traits::append(builder, type); } } g_variant_builder_close(&builder); // string list g_variant_builder_close(&builder); // pair } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksPostalAddressFieldDetails *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE(StringPrintf("(%sas)", INDIVIDUAL_DICT).c_str())); // pair of dict and string list FolksAbstractFieldDetails *fieldDetails = FOLKS_ABSTRACT_FIELD_DETAILS(value); gconstpointer v = folks_abstract_field_details_get_value(fieldDetails); dbus_traits::append(builder, static_cast(const_cast(v))); g_variant_builder_open(&builder, G_VARIANT_TYPE("as")); GeeMultiMap *map = folks_abstract_field_details_get_parameters(fieldDetails); if (map) { BOOST_FOREACH (const char *type, GeeCollCXX(gee_multi_map_get(map, "type"))) { dbus_traits::append(builder, type); } } g_variant_builder_close(&builder); // string list g_variant_builder_close(&builder); // pair } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksNoteFieldDetails *value) { FolksAbstractFieldDetails *fieldDetails = FOLKS_ABSTRACT_FIELD_DETAILS(value); gconstpointer v = folks_abstract_field_details_get_value(fieldDetails); dbus_traits::append(builder, static_cast(v)); // plain string // Ignore parameters. LANGUAGE is hardly ever set. } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksRoleFieldDetails *value) { FolksAbstractFieldDetails *fieldDetails = FOLKS_ABSTRACT_FIELD_DETAILS(value); gconstpointer v = folks_abstract_field_details_get_value(fieldDetails); dbus_traits::append(builder, static_cast(const_cast((v)))); // Ignore parameters. LANGUAGE is hardly ever set. } }; // Used for types like V = FolksEmailFieldDetails * template struct dbus_traits< GeeCollCXX > { static void append(builder_type &builder, const GeeCollCXX &collection) { g_variant_builder_open(&builder, G_VARIANT_TYPE("av")); // array of variants BOOST_FOREACH (V value, collection) { g_variant_builder_open(&builder, G_VARIANT_TYPE("v")); // variant dbus_traits::append(builder, value); g_variant_builder_close(&builder); // variant } g_variant_builder_close(&builder); // array of variants } }; template <> struct dbus_traits { static void append(builder_type &builder, GDateTime *value) { // Extract local date from UTC date + time + UTC offset. // // The libfolks EDS backend does date + 00:00 in local time, // then converts to UTC. We need to hard-code the stripping // of the time. Folks should make it easier to extract the date, see // https://bugzilla.gnome.org/show_bug.cgi?id=684905 // // TODO: if folks doesn't get fixed, then we should cache the // GTimeZone local = g_time_zone_new_local() // and use that throughout the runtime of the process, like // folks-eds does. GDateTimeCXX local(g_date_time_to_local(value), TRANSFER_REF); gint year, month, day; g_date_time_get_ymd(local.get(), &year, &month, &day); g_variant_builder_open(&builder, G_VARIANT_TYPE("(iii)")); // tuple with year, month, day GDBusCXX::dbus_traits::append(builder, year); GDBusCXX::dbus_traits::append(builder, month); GDBusCXX::dbus_traits::append(builder, day); g_variant_builder_close(&builder); // tuple } }; template <> struct dbus_traits { static void append(builder_type &builder, FolksLocation *value) { g_variant_builder_open(&builder, G_VARIANT_TYPE("(dd)")); // tuple with latitude + longitude GDBusCXX::dbus_traits::append(builder, value->latitude); GDBusCXX::dbus_traits::append(builder, value->longitude); g_variant_builder_close(&builder); // tuple } }; } // namespace GDBusCXX SE_BEGIN_CXX static guint FolksAbstractFieldDetailsHash(gconstpointer v, void *d) { return folks_abstract_field_details_hash((FolksAbstractFieldDetails *)v); } static gboolean FolksAbstractFieldDetailsEqual(gconstpointer a, gconstpointer b, void *d) { return folks_abstract_field_details_equal((FolksAbstractFieldDetails *)a, (FolksAbstractFieldDetails *)b); } /** * Copy from D-Bus into a type derived from FolksAbstractFieldDetails, * including type flags. */ static void DBus2AbstractField(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details, GType detailType, FolksPersonaDetail detail, FolksAbstractFieldDetails *(*fieldNew)(const gchar *, GeeMultiMap *)) { // type of emails, urls, etc. typedef std::vector< std::pair< std::string, std::vector > > Details_t; Details_t value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(detailType, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const Details_t::value_type &entry, value) { const Details_t::value_type::first_type &val = entry.first; const Details_t::value_type::second_type &flags = entry.second; FolksAbstractFieldDetailsCXX field(fieldNew(val.c_str(), NULL), TRANSFER_REF); BOOST_FOREACH (const std::string &type, flags) { folks_abstract_field_details_add_parameter(field.get(), FOLKS_ABSTRACT_FIELD_DETAILS_PARAM_TYPE, type.c_str()); } gee_collection_add(GEE_COLLECTION(set.get()), field.get()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(detail)), new GValueObjectCXX(set.get())); } /** * Copy from D-Bus into a type derived from FolksAbstractFieldDetails, * excluding type flags. */ static void DBus2SimpleAbstractField(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details, GType detailType, FolksPersonaDetail detail, FolksAbstractFieldDetails *(*fieldNew)(const gchar *, GeeMultiMap *)) { // type of notes typedef std::vector Details_t; Details_t value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(detailType, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const std::string &val, value) { FolksAbstractFieldDetailsCXX field(fieldNew(val.c_str(), NULL), TRANSFER_REF); gee_collection_add(GEE_COLLECTION(set.get()), field.get()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(detail)), new GValueObjectCXX(set.get())); } /** * Copy from D-Bus into FolksRoleFieldDetails. */ static void DBus2Role(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details) { // type of role typedef std::vector Details_t; Details_t value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(FOLKS_TYPE_ROLE_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const StringMap &entry, value) { FolksRoleCXX role(folks_role_new(NULL, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const StringPair &aspect, entry) { const std::string &k = aspect.first; const std::string &v = aspect.second; if (k == CONTACT_HASH_ROLES_ORGANISATION) { folks_role_set_organisation_name(role, v.c_str()); } else if (k == CONTACT_HASH_ROLES_TITLE) { folks_role_set_title(role, v.c_str()); } else if (k == CONTACT_HASH_ROLES_ROLE) { folks_role_set_role(role, v.c_str()); } } FolksRoleFieldDetailsCXX field(folks_role_field_details_new(role.get(), NULL), TRANSFER_REF); gee_collection_add(GEE_COLLECTION(set.get()), field.get()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_ROLES)), new GValueObjectCXX(set.get())); } /** * Copy from D-Bus into a set of strings. */ static void DBus2Groups(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details) { std::list value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc)g_strdup, g_free, NULL, NULL, NULL, NULL, NULL, NULL), TRANSFER_REF); BOOST_FOREACH(const std::string &entry, value) { gee_collection_add(GEE_COLLECTION(set.get()), entry.c_str()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_GROUPS)), new GValueObjectCXX(set.get())); } /** * Copy from D-Bus into a FolksAddressFieldDetails. */ static void DBus2Addr(GDBusCXX::ExtractArgs &context, GVariantIter &valueIter, PersonaDetails &details) { // type of CONTACT_HASH_ADDRESSES typedef std::vector< std::pair< StringMap, std::vector > > Details_t; Details_t value; GDBusCXX::dbus_traits::get(context, valueIter, value); GeeHashSetCXX set(gee_hash_set_new(FOLKS_TYPE_POSTAL_ADDRESS_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), TRANSFER_REF); BOOST_FOREACH (const Details_t::value_type &entry, value) { const StringMap &fields = entry.first; const std::vector &flags = entry.second; FolksPostalAddressCXX address(folks_postal_address_new(GetWithDef(fields, CONTACT_HASH_ADDRESSES_PO_BOX).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_EXTENSION).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_STREET).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_LOCALITY).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_REGION).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_POSTAL_CODE).c_str(), GetWithDef(fields, CONTACT_HASH_ADDRESSES_COUNTRY).c_str(), NULL /* address format */, NULL /* uid */), TRANSFER_REF); FolksAbstractFieldDetailsCXX field(FOLKS_ABSTRACT_FIELD_DETAILS(folks_postal_address_field_details_new(address.get(), NULL)), TRANSFER_REF); BOOST_FOREACH (const std::string &type, flags) { folks_abstract_field_details_add_parameter(field.get(), FOLKS_ABSTRACT_FIELD_DETAILS_PARAM_TYPE, type.c_str()); } gee_collection_add(GEE_COLLECTION(set.get()), field.get()); } g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_POSTAL_ADDRESSES)), new GValueObjectCXX(set.get())); } void DBus2PersonaDetails(GDBusCXX::ExtractArgs &context, GDBusCXX::reader_type &iter, PersonaDetails &details) { GDBusCXX::GVariantCXX var(g_variant_iter_next_value(&iter)); if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_ARRAY)) { SE_THROW("D-Bus contact hash: unexpected content"); } GVariantIter contIter; GDBusCXX::GVariantCXX child; g_variant_iter_init(&contIter, var); // array while((child = g_variant_iter_next_value(&contIter)) != NULL) { GVariantIter childIter; g_variant_iter_init(&childIter, child); // dict entry std::string key; GDBusCXX::dbus_traits::get(context, childIter, key); GDBusCXX::GVariantCXX valueVarient(g_variant_iter_next_value(&childIter)); if (valueVarient == NULL || !g_variant_type_equal(g_variant_get_type(valueVarient), G_VARIANT_TYPE_VARIANT)) { SE_THROW("D-Bus contact hash entry: value must be variant"); } GVariantIter valueIter; g_variant_iter_init(&valueIter, valueVarient); if (key == CONTACT_HASH_FULL_NAME) { std::string value; GDBusCXX::dbus_traits::get(context, valueIter, value); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_FULL_NAME)), new GValueStringCXX(value.c_str())); } else if (key == CONTACT_HASH_STRUCTURED_NAME) { StringMap value; GDBusCXX::dbus_traits::get(context, valueIter, value); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_STRUCTURED_NAME)), new GValueObjectCXX(folks_structured_name_new(GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_FAMILY).c_str(), GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_GIVEN).c_str(), GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_ADDITIONAL).c_str(), GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_PREFIXES).c_str(), GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_SUFFIXES).c_str()), TRANSFER_REF)); } else if (key == CONTACT_HASH_PHOTO) { std::string value; GDBusCXX::dbus_traits::get(context, valueIter, value); GFileCXX file(g_file_new_for_uri(value.c_str()), TRANSFER_REF); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_AVATAR)), new GValueObjectCXX(g_file_icon_new(file.get()), TRANSFER_REF)); } else if (key == CONTACT_HASH_BIRTHDAY) { boost::tuple value; GDBusCXX::dbus_traits::get(context, valueIter, value); GDateTimeCXX local(g_date_time_new_local(value.get<0>(), value.get<1>(), value.get<2>(), 0, 0, 0), TRANSFER_REF); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_BIRTHDAY)), new GValueDateTimeCXX(g_date_time_to_utc(local.get()), TRANSFER_REF)); } else if (key == CONTACT_HASH_LOCATION) { boost::tuple value; GDBusCXX::dbus_traits::get(context, valueIter, value); FolksLocationCXX location(folks_location_new(value.get<0>(), value.get<1>()), TRANSFER_REF); g_hash_table_insert(details.get(), const_cast(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_LOCATION)), new GValueObjectCXX(location.get())); } else if (key == CONTACT_HASH_EMAILS) { DBus2AbstractField(context, valueIter, details, FOLKS_TYPE_EMAIL_FIELD_DETAILS, FOLKS_PERSONA_DETAIL_EMAIL_ADDRESSES, reinterpret_cast(folks_email_field_details_new)); } else if (key == CONTACT_HASH_PHONES) { DBus2AbstractField(context, valueIter, details, FOLKS_TYPE_PHONE_FIELD_DETAILS, FOLKS_PERSONA_DETAIL_PHONE_NUMBERS, reinterpret_cast(folks_phone_field_details_new)); } else if (key == CONTACT_HASH_URLS) { DBus2AbstractField(context, valueIter, details, FOLKS_TYPE_URL_FIELD_DETAILS, FOLKS_PERSONA_DETAIL_URLS, reinterpret_cast(folks_url_field_details_new)); } else if (key == CONTACT_HASH_NOTES) { DBus2SimpleAbstractField(context, valueIter, details, FOLKS_TYPE_NOTE_FIELD_DETAILS, FOLKS_PERSONA_DETAIL_NOTES, reinterpret_cast(folks_note_field_details_new)); } else if (key == CONTACT_HASH_ROLES) { DBus2Role(context, valueIter, details); } else if (key == CONTACT_HASH_GROUPS) { DBus2Groups(context, valueIter, details); } else if (key == CONTACT_HASH_ADDRESSES) { DBus2Addr(context, valueIter, details); } } } struct Pending { Pending(const Result &result, FolksPersona *persona) : m_result(result), m_persona(persona, ADD_REF), m_current(0) {} Result m_result; FolksPersonaCXX m_persona; typedef boost::function AsyncDone; typedef boost::function Prepare; typedef boost::tuple > Change; typedef std::vector Changes; Changes m_changes; size_t m_current; }; static bool GeeCollectionEqual(GeeCollection *a, GeeCollection *b) { return gee_collection_get_size(a) == gee_collection_get_size(b) && gee_collection_contains_all(a, b); } /** * Gets called by event loop. All errors must be reported back to the caller. */ static void Details2PersonaStep(const GError *gerror, const boost::shared_ptr &pending) throw () { try { if (gerror) { GErrorCXX::throwError("modifying property", gerror); } size_t current = pending->m_current++; if (current < pending->m_changes.size()) { // send next change, as determined earlier Pending::Change &change = pending->m_changes[current]; SE_LOG_DEBUG(NULL, "modification step %d/%d: %s", (int)current, (int)pending->m_changes.size(), boost::get<1>(change)); boost::get<0>(change)(new Pending::AsyncDone(boost::bind(Details2PersonaStep, _1, pending))); } else { pending->m_result.done(); } } catch (...) { // Tell caller about specific error. pending->m_result.failed(); } } void Details2Persona(const Result &result, const PersonaDetails &details, FolksPersona *persona) { boost::shared_ptr pending(new Pending(result, persona)); #define PUSH_CHANGE(_prepare) \ SE_LOG_DEBUG(NULL, "queueing new change: %s", #_prepare); \ pending->m_changes.push_back(Pending::Change(boost::bind(_prepare, \ details, \ value, \ SYNCEVO_GLIB_CALL_ASYNC_CXX(_prepare)::handleGLibResult, \ _1), \ #_prepare, \ tracker)) const GValue *gvalue; boost::shared_ptr tracker; gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_FULL_NAME))); { const gchar *value; if (gvalue) { value = g_value_get_string(gvalue); tracker = boost::shared_ptr(g_strdup(value), g_free); } else { value = NULL; } FolksNameDetails *details = FOLKS_NAME_DETAILS(persona); if (g_strcmp0(value, folks_name_details_get_full_name(details))) { PUSH_CHANGE(folks_name_details_change_full_name); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_STRUCTURED_NAME))); { FolksStructuredName *value; if (gvalue) { value = FOLKS_STRUCTURED_NAME(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(folks_structured_name_new("", "", "", "", ""), g_object_unref); value = static_cast(tracker.get()); } FolksNameDetails *details = FOLKS_NAME_DETAILS(persona); if (!folks_structured_name_equal(value, folks_name_details_get_structured_name(details))) { PUSH_CHANGE(folks_name_details_change_structured_name); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_AVATAR))); { FolksAvatarDetails *details = FOLKS_AVATAR_DETAILS(persona); GLoadableIcon *value; if (gvalue) { value = G_LOADABLE_ICON(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(); value = NULL; } InitStateString newPath = GDBusCXX::ExtractFilePath(value); InitStateString oldPath = GDBusCXX::ExtractFilePath(folks_avatar_details_get_avatar(details)); if (newPath.wasSet() != oldPath.wasSet() || newPath != oldPath) { PUSH_CHANGE(folks_avatar_details_change_avatar); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_BIRTHDAY))); { GDateTime *value; if (gvalue) { value = static_cast(g_value_get_boxed(gvalue)); tracker = boost::shared_ptr(g_date_time_ref(value), g_date_time_unref); } else { tracker = boost::shared_ptr(); value = NULL; } FolksBirthdayDetails *details = FOLKS_BIRTHDAY_DETAILS(persona); GDateTime *old = folks_birthday_details_get_birthday(details); if ((value == NULL && old != NULL) || (value != NULL && old == NULL) || !g_date_time_equal(value, old)) { PUSH_CHANGE(folks_birthday_details_change_birthday); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_LOCATION))); { FolksLocation *value; if (gvalue) { value = static_cast(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(); value = NULL; } FolksLocationDetails *details = FOLKS_LOCATION_DETAILS(persona); FolksLocation *old = folks_location_details_get_location(details); if ((value == NULL && old != NULL) || (value != NULL && old == NULL) || (value && old && (value->latitude != old->latitude || value->longitude != old->longitude))) { PUSH_CHANGE(folks_location_details_change_location); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_EMAIL_ADDRESSES))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_EMAIL_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksEmailDetails *details = FOLKS_EMAIL_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_email_details_get_email_addresses(details)))) { PUSH_CHANGE(folks_email_details_change_email_addresses); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_PHONE_NUMBERS))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_PHONE_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksPhoneDetails *details = FOLKS_PHONE_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_phone_details_get_phone_numbers(details)))) { PUSH_CHANGE(folks_phone_details_change_phone_numbers); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_URLS))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_URL_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksUrlDetails *details = FOLKS_URL_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_url_details_get_urls(details)))) { PUSH_CHANGE(folks_url_details_change_urls); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_NOTES))); { // At the moment the comparison fails and tiggers a write on each update?! GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_NOTE_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksNoteDetails *details = FOLKS_NOTE_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_note_details_get_notes(details)))) { PUSH_CHANGE(folks_note_details_change_notes); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_ROLES))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_ROLE_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksRoleDetails *details = FOLKS_ROLE_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_role_details_get_roles(details)))) { PUSH_CHANGE(folks_role_details_change_roles); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_GROUPS))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc)g_strdup, g_free, NULL, NULL, NULL, NULL, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksGroupDetails *details = FOLKS_GROUP_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_group_details_get_groups(details)))) { PUSH_CHANGE(folks_group_details_change_groups); } } gvalue = static_cast(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_POSTAL_ADDRESSES))); { GeeSet *value; if (gvalue) { value = GEE_SET(g_value_get_object(gvalue)); tracker = boost::shared_ptr(g_object_ref(value), g_object_unref); } else { tracker = boost::shared_ptr(gee_hash_set_new(FOLKS_TYPE_POSTAL_ADDRESS_FIELD_DETAILS, g_object_ref, g_object_unref, FolksAbstractFieldDetailsHash, NULL, NULL, FolksAbstractFieldDetailsEqual, NULL, NULL), g_object_unref); value = static_cast(tracker.get()); } FolksPostalAddressDetails *details = FOLKS_POSTAL_ADDRESS_DETAILS(persona); if (!GeeCollectionEqual(GEE_COLLECTION(value), GEE_COLLECTION(folks_postal_address_details_get_postal_addresses(details)))) { PUSH_CHANGE(folks_postal_address_details_change_postal_addresses); } } Details2PersonaStep(NULL, pending); } void FolksIndividual2DBus(const FolksIndividualCXX &individual, GDBusCXX::builder_type &builder) { g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict FolksNameDetails *name = FOLKS_NAME_DETAILS(individual.get()); SerializeFolks(builder, name, folks_name_details_get_full_name, CONTACT_HASH_FULL_NAME); SerializeFolks(builder, name, folks_name_details_get_nickname, CONTACT_HASH_NICKNAME); SerializeFolks(builder, name, folks_name_details_get_structured_name, CONTACT_HASH_STRUCTURED_NAME); // gconstpointer folks_abstract_field_details_get_value (FolksAbstractFieldDetails* self); FolksAliasDetails *alias = FOLKS_ALIAS_DETAILS(individual.get()); SerializeFolks(builder, alias, folks_alias_details_get_alias, CONTACT_HASH_ALIAS); FolksAvatarDetails *avatar = FOLKS_AVATAR_DETAILS(individual.get()); SerializeFolks(builder, avatar, folks_avatar_details_get_avatar, CONTACT_HASH_PHOTO); FolksBirthdayDetails *birthday = FOLKS_BIRTHDAY_DETAILS(individual.get()); SerializeFolks(builder, birthday, folks_birthday_details_get_birthday, CONTACT_HASH_BIRTHDAY); // const gchar* folks_birthday_details_get_calendar_event_id (FolksBirthdayDetails* self); FolksLocationDetails *location = FOLKS_LOCATION_DETAILS(individual.get()); SerializeFolks(builder, location, folks_location_details_get_location, CONTACT_HASH_LOCATION); FolksEmailDetails *emails = FOLKS_EMAIL_DETAILS(individual.get()); SerializeFolks(builder, emails, folks_email_details_get_email_addresses, (GeeCollCXX*)NULL, CONTACT_HASH_EMAILS); FolksPhoneDetails *phones = FOLKS_PHONE_DETAILS(individual.get()); SerializeFolks(builder, phones, folks_phone_details_get_phone_numbers, (GeeCollCXX*)NULL, CONTACT_HASH_PHONES); FolksUrlDetails *urls = FOLKS_URL_DETAILS(individual.get()); SerializeFolks(builder, urls, folks_url_details_get_urls, (GeeCollCXX*)NULL, CONTACT_HASH_URLS); // Doesn't work like this, folks_im_details_get_im_addresses returns // a GeeMultiMap, not a GeeList. Not required anyway. // FolksImDetails *im = FOLKS_IM_DETAILS(individual.get()); // SerializeFolks(builder, im, folks_im_details_get_im_addresses, // (GeeCollCXX*)NULL, "im"); FolksNoteDetails *notes = FOLKS_NOTE_DETAILS(individual.get()); SerializeFolks(builder, notes, folks_note_details_get_notes, (GeeCollCXX*)NULL, CONTACT_HASH_NOTES); FolksPostalAddressDetails *postal = FOLKS_POSTAL_ADDRESS_DETAILS(individual.get()); SerializeFolks(builder, postal, folks_postal_address_details_get_postal_addresses, (GeeCollCXX*)NULL, CONTACT_HASH_ADDRESSES); FolksRoleDetails *roles = FOLKS_ROLE_DETAILS(individual.get()); SerializeFolks(builder, roles, folks_role_details_get_roles, (GeeCollCXX*)NULL, CONTACT_HASH_ROLES); FolksGroupDetails *groups = FOLKS_GROUP_DETAILS(individual.get()); SerializeFolks(builder, groups, folks_group_details_get_groups, (GeeStringCollection*)NULL, CONTACT_HASH_GROUPS); SerializeFolks(builder, individual.get(), folks_individual_get_personas, (GeeCollCXX*)NULL, CONTACT_HASH_SOURCE); SerializeFolks(builder, individual.get(), folks_individual_get_id, CONTACT_HASH_ID); #if 0 // Not exposed via D-Bus. FolksGender folks_gender_details_get_gender (FolksGenderDetails* self); GeeMultiMap* folks_web_service_details_get_web_service_addresses (FolksWebServiceDetails* self); guint folks_interaction_details_get_im_interaction_count (FolksInteractionDetails* self); GDateTime* folks_interaction_details_get_last_im_interaction_datetime (FolksInteractionDetails* self); guint folks_interaction_details_get_call_interaction_count (FolksInteractionDetails* self); GDateTime* folks_interaction_details_get_last_call_interaction_datetime (FolksInteractionDetails* self); GeeSet* folks_local_id_details_get_local_ids (FolksLocalIdDetails* self); const gchar* folks_presence_details_get_default_message_from_type (FolksPresenceType type); FolksPresenceType folks_presence_details_get_presence_type (FolksPresenceDetails* self); const gchar* folks_presence_details_get_presence_message (FolksPresenceDetails* self); const gchar* folks_presence_details_get_presence_status (FolksPresenceDetails* self); #endif g_variant_builder_close(&builder); // dict } SE_END_CXX