157d03291f0ae0a4a78d15f01f1bd0cb0109fe99
[platform/upstream/syncevolution.git] / src / dbus / server / pim / individual-traits.cpp
1 /*
2  * Copyright (C) 2012 Intel Corporation
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) version 3.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301  USA
18  */
19
20 /**
21  * The D-Bus IPC binding for folks.h. Maps FolksIndividual to and
22  * from the D-Bus dict described in pim-manager-api.txt.
23  */
24
25 #include "individual-traits.h"
26 #include "persona-details.h"
27 #include "folks.h"
28
29 SE_GLIB_TYPE(GDateTime, g_date_time)
30 SE_GOBJECT_TYPE(GTimeZone)
31 SE_GOBJECT_TYPE(GObject)
32
33 SE_BEGIN_CXX
34
35 typedef GValueDynTypedCXX<GDateTime *, g_date_time_get_type> GValueDateTimeCXX;
36
37 static const char * const CONTACT_HASH_FULL_NAME = "full-name";
38 static const char * const CONTACT_HASH_NICKNAME = "nickname";
39 static const char * const CONTACT_HASH_STRUCTURED_NAME = "structured-name";
40 static const char * const CONTACT_HASH_STRUCTURED_NAME_FAMILY = "family";
41 static const char * const CONTACT_HASH_STRUCTURED_NAME_GIVEN = "given";
42 static const char * const CONTACT_HASH_STRUCTURED_NAME_ADDITIONAL = "additional";
43 static const char * const CONTACT_HASH_STRUCTURED_NAME_PREFIXES = "prefixes";
44 static const char * const CONTACT_HASH_STRUCTURED_NAME_SUFFIXES = "suffixes";
45 static const char * const CONTACT_HASH_ALIAS = "alias";
46 static const char * const CONTACT_HASH_PHOTO = "photo";
47 static const char * const CONTACT_HASH_BIRTHDAY = "birthday";
48 static const char * const CONTACT_HASH_LOCATION = "location";
49 static const char * const CONTACT_HASH_EMAILS = "emails";
50 static const char * const CONTACT_HASH_PHONES = "phones";
51 static const char * const CONTACT_HASH_URLS = "urls";
52 static const char * const CONTACT_HASH_NOTES = "notes";
53 static const char * const CONTACT_HASH_ADDRESSES = "addresses";
54 static const char * const CONTACT_HASH_ADDRESSES_PO_BOX = "po-box";
55 static const char * const CONTACT_HASH_ADDRESSES_EXTENSION = "extension";
56 static const char * const CONTACT_HASH_ADDRESSES_STREET = "street";
57 static const char * const CONTACT_HASH_ADDRESSES_LOCALITY = "locality";
58 static const char * const CONTACT_HASH_ADDRESSES_REGION = "region";
59 static const char * const CONTACT_HASH_ADDRESSES_POSTAL_CODE = "postal-code";
60 static const char * const CONTACT_HASH_ADDRESSES_COUNTRY = "country";
61 static const char * const CONTACT_HASH_ROLES = "roles";
62 static const char * const CONTACT_HASH_ROLES_ORGANISATION = "organisation";
63 static const char * const CONTACT_HASH_ROLES_TITLE = "title";
64 static const char * const CONTACT_HASH_ROLES_ROLE = "role";
65 static const char * const CONTACT_HASH_GROUPS = "groups";
66 static const char * const CONTACT_HASH_SOURCE = "source";
67 static const char * const CONTACT_HASH_ID = "id";
68
69 static const char * const INDIVIDUAL_DICT = "a{sv}";
70 static const char * const INDIVIDUAL_DICT_ENTRY = "{sv}";
71
72 /**
73  * Checks whether a certain value is the default value and thus
74  * can be skipped when converting to D-Bus.
75  *
76  * class B provides additional type information, which is necessary
77  * to check a GeeSet containing FolksRoleFieldDetails differently
78  * than other GeeSets.
79  */
80 template <class B> struct IsNonDefault
81 {
82     template<class V> static bool check(V value)
83     {
84         // Default version uses normal C/C++ rules, for example pointer non-NULL.
85         // For bool and integer, the value will only be sent if true or non-zero.
86         return value;
87     }
88
89     static bool check(const gchar *value)
90     {
91         // Don't send empty strings.
92         return value && value[0];
93     }
94
95     static bool check(GeeSet *value)
96     {
97         // Don't send empty sets.
98         return value && gee_collection_get_size(GEE_COLLECTION(value));
99     }
100 };
101
102 template <> struct IsNonDefault< GeeCollCXX<FolksRoleFieldDetails *> >
103 {
104     static bool check(GeeSet *value)
105     {
106         // Don't send empty set and set which contains only empty roles.
107         if (value) {
108             BOOST_FOREACH (FolksRoleFieldDetails *value, GeeCollCXX<FolksRoleFieldDetails *>(value)) {
109                 FolksRole *role = static_cast<FolksRole *>(const_cast<gpointer>((folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(value)))));
110                 if (IsNonDefault<const gchar *>::check(folks_role_get_organisation_name(role)) ||
111                     IsNonDefault<const gchar *>::check(folks_role_get_title(role)) ||
112                     IsNonDefault<const gchar *>::check(folks_role_get_role(role))) {
113                     return true;
114                 }
115             }
116         }
117         return false;
118     }
119 };
120
121 /**
122  * Adds a dict entry to the builder, with 'key' as string key and the
123  * result of 'get()' as value.
124  */
125 template <class O, class V> void SerializeFolks(GDBusCXX::builder_type &builder,
126                                                 O *obj,
127                                                 V (*get)(O *),
128                                                 const char *key)
129 {
130     if (!obj) {
131         SE_THROW("casting to base class failed");
132     }
133     V value = get(obj);
134
135     if (IsNonDefault<V>::check(value)) {
136         g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT_ENTRY)); // dict entry
137         GDBusCXX::dbus_traits<std::string>::append(builder, key);
138         g_variant_builder_open(&builder, G_VARIANT_TYPE("v")); // variant
139         GDBusCXX::dbus_traits<V>::append(builder, value);
140         g_variant_builder_close(&builder); // variant
141         g_variant_builder_close(&builder); // dict entry
142     }
143 }
144
145 template <class O, class V, class B> void SerializeFolks(GDBusCXX::builder_type &builder,
146                                                          O *obj,
147                                                          V (*get)(O *),
148                                                          B *, // dummy parameter, determines base type
149                                                          const char *key)
150 {
151     if (!obj) {
152         SE_THROW("casting to base class failed");
153     }
154     V value = get(obj);
155
156     if (IsNonDefault<B>::check(value)) {
157         g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT_ENTRY)); // dict entry
158         GDBusCXX::dbus_traits<std::string>::append(builder, key);
159         g_variant_builder_open(&builder, G_VARIANT_TYPE("v")); // variant
160         GDBusCXX::dbus_traits<B>::append(builder, value);
161         g_variant_builder_close(&builder); // variant
162         g_variant_builder_close(&builder); // dict entry
163     }
164 }
165
166 SE_END_CXX
167
168 namespace GDBusCXX {
169
170 template <> struct dbus_traits<FolksStructuredName *> {
171     static void append(builder_type &builder, FolksStructuredName *value) {
172         g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict
173         SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_family_name, CONTACT_HASH_STRUCTURED_NAME_FAMILY);
174         SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_given_name, CONTACT_HASH_STRUCTURED_NAME_GIVEN);
175         SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_additional_names, CONTACT_HASH_STRUCTURED_NAME_ADDITIONAL);
176         SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_prefixes, CONTACT_HASH_STRUCTURED_NAME_PREFIXES);
177         SyncEvo::SerializeFolks(builder, value, folks_structured_name_get_suffixes, CONTACT_HASH_STRUCTURED_NAME_SUFFIXES);
178         g_variant_builder_close(&builder); // dict
179     }
180 };
181
182 /** Path to icon (the expected case) or uninitialized string (not set or not a file). */
183 static InitStateString ExtractFilePath(GLoadableIcon *value)
184 {
185     if (value &&
186         G_IS_FILE_ICON(value)) {
187         GFileIcon *fileIcon = G_FILE_ICON(value);
188         GFile *file = g_file_icon_get_file(fileIcon);
189         if (file) {
190             PlainGStr uri(g_file_get_uri(file));
191             // Have a path.
192             return InitStateString(uri.get(), true);
193         }
194     }
195     // No path.
196     return InitStateString();
197 }
198
199 template <> struct dbus_traits<GLoadableIcon *> {
200     static void append(builder_type &builder, GLoadableIcon *value) {
201         InitStateString path = ExtractFilePath(value);
202         // EDS is expected to only work with URIs for the PHOTO
203         // property, therefore we shouldn't get here without a valid
204         // path. Either way, we need to store something.
205         GDBusCXX::dbus_traits<const char *>::append(builder, path.c_str());
206     }
207 };
208
209 template <> struct dbus_traits<FolksPostalAddress *> {
210     static void append(builder_type &builder, FolksPostalAddress *value) {
211         g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict
212         SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_po_box, CONTACT_HASH_ADDRESSES_PO_BOX);
213         SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_extension, CONTACT_HASH_ADDRESSES_EXTENSION);
214         SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_street, CONTACT_HASH_ADDRESSES_STREET);
215         SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_locality, CONTACT_HASH_ADDRESSES_LOCALITY);
216         SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_region, CONTACT_HASH_ADDRESSES_REGION);
217         SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_postal_code, CONTACT_HASH_ADDRESSES_POSTAL_CODE);
218         SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_country, CONTACT_HASH_ADDRESSES_COUNTRY);
219
220         // Not used by EDS. The tracker backend in folks was able to provide such a uid.
221         // SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_address_format, CONTACT_HASH_ADDRESSES_FORMAT);
222         // SyncEvo::SerializeFolks(builder, value, folks_postal_address_get_uid, "uid");
223         g_variant_builder_close(&builder); // dict
224     }
225 };
226
227 template <> struct dbus_traits<FolksRole *> {
228     static void append(builder_type &builder, FolksRole *value) {
229         g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict
230         // Other parts of ORG are not currently supported by libfolks.
231         SyncEvo::SerializeFolks(builder, value, folks_role_get_organisation_name, CONTACT_HASH_ROLES_ORGANISATION);
232         SyncEvo::SerializeFolks(builder, value, folks_role_get_title, CONTACT_HASH_ROLES_TITLE);
233         SyncEvo::SerializeFolks(builder, value, folks_role_get_role, CONTACT_HASH_ROLES_ROLE);
234         // Not used:
235         // SyncEvo::SerializeFolks(builder, value, folks_role_get_uid, "uid");
236         g_variant_builder_close(&builder); // dict
237     }
238 };
239
240 // TODO: put into global header file
241 static const char * const MANAGER_PREFIX = "pim-manager-";
242
243 template <> struct dbus_traits<FolksPersona *> {
244     static void append(builder_type &builder, FolksPersona *value) {
245         g_variant_builder_open(&builder, G_VARIANT_TYPE("(ss)")); // pair of peer ID and local ID
246         const gchar *uid = folks_persona_get_uid(value);
247         if (uid) {
248             gchar *backend, *storeID, *personaID;
249             folks_persona_split_uid(uid, &backend, &storeID, &personaID);
250             PlainGStr tmp1(backend), tmp2(storeID), tmp3(personaID);
251             if (boost::starts_with(storeID, MANAGER_PREFIX)) {
252                 dbus_traits<const char *>::append(builder, storeID + strlen(MANAGER_PREFIX));
253             } else {
254                 // Must be the system address book.
255                 dbus_traits<const char *>::append(builder, "");
256             }
257             dbus_traits<const char *>::append(builder, personaID);
258         } else {
259             dbus_traits<const char *>::append(builder, "");
260             dbus_traits<const char *>::append(builder, "");
261         }
262         g_variant_builder_close(&builder); // pair
263     }
264 };
265
266 // Only use this with FolksAbstractFieldDetails instances where
267 // the value is a string.
268 template <> struct dbus_traits<FolksAbstractFieldDetails *> {
269     static void append(builder_type &builder, FolksAbstractFieldDetails *value) {
270         g_variant_builder_open(&builder, G_VARIANT_TYPE("(sas)")); // pair of string and string list
271         gconstpointer v = folks_abstract_field_details_get_value(value);
272         dbus_traits<const char *>::append(builder, v ? (const char *)v : "");
273         g_variant_builder_open(&builder, G_VARIANT_TYPE("as"));
274         GeeMultiMap *map = folks_abstract_field_details_get_parameters(value);
275         if (map) {
276             BOOST_FOREACH (const char *type, GeeCollCXX<const char *>(gee_multi_map_get(map, FOLKS_ABSTRACT_FIELD_DETAILS_PARAM_TYPE))) {
277                 dbus_traits<const char *>::append(builder, type);
278             }
279         }
280         g_variant_builder_close(&builder); // string list
281         g_variant_builder_close(&builder); // pair
282     }
283 };
284
285 template <> struct dbus_traits<FolksPostalAddressFieldDetails *> {
286     static void append(builder_type &builder, FolksPostalAddressFieldDetails *value) {
287         g_variant_builder_open(&builder, G_VARIANT_TYPE(StringPrintf("(%sas)", INDIVIDUAL_DICT).c_str())); // pair of dict and string list
288         FolksAbstractFieldDetails *fieldDetails = FOLKS_ABSTRACT_FIELD_DETAILS(value);
289         gconstpointer v = folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(fieldDetails));
290         dbus_traits<FolksPostalAddress *>::append(builder, static_cast<FolksPostalAddress *>(const_cast<gpointer>(v)));
291         g_variant_builder_open(&builder, G_VARIANT_TYPE("as"));
292         GeeMultiMap *map = folks_abstract_field_details_get_parameters(fieldDetails);
293         if (map) {
294             BOOST_FOREACH (const char *type, GeeCollCXX<const char *>(gee_multi_map_get(map, "type"))) {
295                 dbus_traits<const char *>::append(builder, type);
296             }
297         }
298         g_variant_builder_close(&builder); // string list
299         g_variant_builder_close(&builder); // pair
300     }
301 };
302
303 template <> struct dbus_traits<FolksNoteFieldDetails *> {
304     static void append(builder_type &builder, FolksNoteFieldDetails *value) {
305         FolksAbstractFieldDetails *fieldDetails = FOLKS_ABSTRACT_FIELD_DETAILS(value);
306         gconstpointer v = folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(fieldDetails));
307         dbus_traits<const char *>::append(builder, static_cast<const char *>(v)); // plain string
308         // Ignore parameters. LANGUAGE is hardly ever set.
309     }
310 };
311
312 template <> struct dbus_traits<FolksRoleFieldDetails *> {
313     static void append(builder_type &builder, FolksRoleFieldDetails *value) {
314         FolksAbstractFieldDetails *fieldDetails = FOLKS_ABSTRACT_FIELD_DETAILS(value);
315         gconstpointer v = folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(fieldDetails));
316         dbus_traits<FolksRole *>::append(builder, static_cast<FolksRole *>(const_cast<gpointer>((v))));
317         // Ignore parameters. LANGUAGE is hardly ever set.
318     }
319 };
320
321 // Used for types like V = FolksEmailFieldDetails *
322 template <class V> struct dbus_traits< GeeCollCXX<V> > {
323     static void append(builder_type &builder, const GeeCollCXX<V> &collection) {
324         g_variant_builder_open(&builder, G_VARIANT_TYPE("av")); // array of variants
325         BOOST_FOREACH (V value, collection) {
326             g_variant_builder_open(&builder, G_VARIANT_TYPE("v")); // variant
327             dbus_traits<V>::append(builder, value);
328             g_variant_builder_close(&builder); // variant
329         }
330         g_variant_builder_close(&builder); // array of variants
331     }
332 };
333
334 template <> struct dbus_traits<GDateTime *> {
335     static void append(builder_type &builder, GDateTime *value) {
336         // Extract local date from UTC date + time + UTC offset.
337         //
338         // The libfolks EDS backend does date + 00:00 in local time,
339         // then converts to UTC. We need to hard-code the stripping
340         // of the time. Folks should make it easier to extract the date, see
341         // https://bugzilla.gnome.org/show_bug.cgi?id=684905
342         //
343         // TODO: if folks doesn't get fixed, then we should cache the
344         // GTimeZone local = g_time_zone_new_local()
345         // and use that throughout the runtime of the process, like
346         // folks-eds does.
347         GDateTimeCXX local(g_date_time_to_local(value), false);
348         gint year, month, day;
349         g_date_time_get_ymd(local.get(), &year, &month, &day);
350         g_variant_builder_open(&builder, G_VARIANT_TYPE("(iii)")); // tuple with year, month, day
351         GDBusCXX::dbus_traits<int>::append(builder, year);
352         GDBusCXX::dbus_traits<int>::append(builder, month);
353         GDBusCXX::dbus_traits<int>::append(builder, day);
354         g_variant_builder_close(&builder); // tuple
355     }
356 };
357
358 template <> struct dbus_traits<FolksLocation *> {
359     static void append(builder_type &builder, FolksLocation *value) {
360         g_variant_builder_open(&builder, G_VARIANT_TYPE("(dd)")); // tuple with latitude + longitude
361         GDBusCXX::dbus_traits<double>::append(builder, value->latitude);
362         GDBusCXX::dbus_traits<double>::append(builder, value->longitude);
363         g_variant_builder_close(&builder); // tuple
364     }
365 };
366
367 } // namespace GDBusCXX
368
369 SE_BEGIN_CXX
370
371 static guint FolksAbstractFieldDetailsHash(gconstpointer v, void *d)
372 {
373     return folks_abstract_field_details_hash((FolksAbstractFieldDetails *)v);
374 }
375 static gboolean FolksAbstractFieldDetailsEqual(gconstpointer a, gconstpointer b, void *d)
376 {
377     return folks_abstract_field_details_equal((FolksAbstractFieldDetails *)a,
378                                               (FolksAbstractFieldDetails *)b);
379 }
380
381 /**
382  * Copy from D-Bus into a type derived from FolksAbstractFieldDetails,
383  * including type flags.
384  */
385 static void DBus2AbstractField(GDBusCXX::ExtractArgs &context,
386                                GVariantIter &valueIter,
387                                PersonaDetails &details,
388                                GType detailType,
389                                FolksPersonaDetail detail,
390                                FolksAbstractFieldDetails *(*fieldNew)(const gchar *, GeeMultiMap *))
391 {
392     // type of emails, urls, etc.
393     typedef std::vector< std::pair< std::string, std::vector<std::string> > > Details_t;
394
395     Details_t value;
396     GDBusCXX::dbus_traits<typeof(value)>::get(context, valueIter, value);
397     GeeHashSetCXX set(gee_hash_set_new(detailType,
398                                        g_object_ref,
399                                        g_object_unref,
400                                        FolksAbstractFieldDetailsHash, NULL, NULL,
401                                        FolksAbstractFieldDetailsEqual, NULL, NULL),
402                       false);
403     BOOST_FOREACH (const Details_t::value_type &entry, value) {
404         const Details_t::value_type::first_type &val = entry.first;
405         const Details_t::value_type::second_type &flags = entry.second;
406         FolksAbstractFieldDetailsCXX field(fieldNew(val.c_str(), NULL), false);
407         BOOST_FOREACH (const std::string &type, flags) {
408             folks_abstract_field_details_add_parameter(field.get(),
409                                                        FOLKS_ABSTRACT_FIELD_DETAILS_PARAM_TYPE,
410                                                        type.c_str());
411         }
412         gee_collection_add(GEE_COLLECTION(set.get()),
413                            field.get());
414     }
415     g_hash_table_insert(details.get(),
416                         const_cast<gchar *>(folks_persona_store_detail_key(detail)),
417                         new GValueObjectCXX(set.get()));
418 }
419
420 /**
421  * Copy from D-Bus into a type derived from FolksAbstractFieldDetails,
422  * excluding type flags.
423  */
424 static void DBus2SimpleAbstractField(GDBusCXX::ExtractArgs &context,
425                                      GVariantIter &valueIter,
426                                      PersonaDetails &details,
427                                      GType detailType,
428                                      FolksPersonaDetail detail,
429                                      FolksAbstractFieldDetails *(*fieldNew)(const gchar *, GeeMultiMap *))
430 {
431     // type of notes
432     typedef std::vector<std::string> Details_t;
433
434     Details_t value;
435     GDBusCXX::dbus_traits<typeof(value)>::get(context, valueIter, value);
436     GeeHashSetCXX set(gee_hash_set_new(detailType,
437                                        g_object_ref,
438                                        g_object_unref,
439                                        FolksAbstractFieldDetailsHash, NULL, NULL,
440                                        FolksAbstractFieldDetailsEqual, NULL, NULL),
441                       false);
442     BOOST_FOREACH (const std::string &val, value) {
443         FolksAbstractFieldDetailsCXX field(fieldNew(val.c_str(), NULL), false);
444         gee_collection_add(GEE_COLLECTION(set.get()),
445                            field.get());
446     }
447     g_hash_table_insert(details.get(),
448                         const_cast<gchar *>(folks_persona_store_detail_key(detail)),
449                         new GValueObjectCXX(set.get()));
450 }
451
452 /**
453  * Copy from D-Bus into FolksRoleFieldDetails.
454  */
455 static void DBus2Role(GDBusCXX::ExtractArgs &context,
456                       GVariantIter &valueIter,
457                       PersonaDetails &details)
458 {
459     // type of role
460     typedef std::vector<StringMap> Details_t;
461
462     Details_t value;
463     GDBusCXX::dbus_traits<typeof(value)>::get(context, valueIter, value);
464     GeeHashSetCXX set(gee_hash_set_new(FOLKS_TYPE_ROLE_FIELD_DETAILS,
465                                        g_object_ref,
466                                        g_object_unref,
467                                        FolksAbstractFieldDetailsHash, NULL, NULL,
468                                        FolksAbstractFieldDetailsEqual, NULL, NULL),
469                       false);
470     BOOST_FOREACH (const StringMap &entry, value) {
471         FolksRoleCXX role(folks_role_new(NULL, NULL, NULL),
472                           false);
473         BOOST_FOREACH (const StringPair &aspect, entry) {
474             const std::string &k = aspect.first;
475             const std::string &v = aspect.second;
476             if (k == CONTACT_HASH_ROLES_ORGANISATION) {
477                 folks_role_set_organisation_name(role, v.c_str());
478             } else if (k == CONTACT_HASH_ROLES_TITLE) {
479                 folks_role_set_title(role, v.c_str());
480             } else if (k == CONTACT_HASH_ROLES_ROLE) {
481                 folks_role_set_role(role, v.c_str());
482             }
483         }
484         FolksRoleFieldDetailsCXX field(folks_role_field_details_new(role.get(), NULL),
485                                        false);
486         gee_collection_add(GEE_COLLECTION(set.get()),
487                            field.get());
488     }
489     g_hash_table_insert(details.get(),
490                         const_cast<gchar *>(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_ROLES)),
491                         new GValueObjectCXX(set.get()));
492 }
493
494 /**
495  * Copy from D-Bus into a set of strings.
496  */
497 static void DBus2Groups(GDBusCXX::ExtractArgs &context,
498                         GVariantIter &valueIter,
499                         PersonaDetails &details)
500 {
501     std::list<std::string> value;
502     GDBusCXX::dbus_traits<typeof(value)>::get(context, valueIter, value);
503     GeeHashSetCXX set(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc)g_strdup, g_free, NULL, NULL, NULL, NULL, NULL, NULL), false);
504     BOOST_FOREACH(const std::string &entry, value) {
505         gee_collection_add(GEE_COLLECTION(set.get()),
506                            entry.c_str());
507     }
508     g_hash_table_insert(details.get(),
509                         const_cast<gchar *>(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_GROUPS)),
510                         new GValueObjectCXX(set.get()));
511 }
512
513 /**
514  * Copy from D-Bus into a FolksAddressFieldDetails.
515  */
516 static void DBus2Addr(GDBusCXX::ExtractArgs &context,
517                       GVariantIter &valueIter,
518                       PersonaDetails &details)
519 {
520     // type of CONTACT_HASH_ADDRESSES
521     typedef std::vector< std::pair< StringMap, std::vector<std::string> > > Details_t;
522
523     Details_t value;
524     GDBusCXX::dbus_traits<typeof(value)>::get(context, valueIter, value);
525     GeeHashSetCXX set(gee_hash_set_new(FOLKS_TYPE_POSTAL_ADDRESS_FIELD_DETAILS,
526                                        g_object_ref,
527                                        g_object_unref,
528                                        FolksAbstractFieldDetailsHash, NULL, NULL,
529                                        FolksAbstractFieldDetailsEqual, NULL, NULL),
530                       false);
531     BOOST_FOREACH (const Details_t::value_type &entry, value) {
532         const StringMap &fields = entry.first;
533         const std::vector<std::string> &flags = entry.second;
534         FolksPostalAddressCXX address(folks_postal_address_new(GetWithDef(fields, CONTACT_HASH_ADDRESSES_PO_BOX).c_str(),
535                                                                GetWithDef(fields, CONTACT_HASH_ADDRESSES_EXTENSION).c_str(),
536                                                                GetWithDef(fields, CONTACT_HASH_ADDRESSES_STREET).c_str(),
537                                                                GetWithDef(fields, CONTACT_HASH_ADDRESSES_LOCALITY).c_str(),
538                                                                GetWithDef(fields, CONTACT_HASH_ADDRESSES_REGION).c_str(),
539                                                                GetWithDef(fields, CONTACT_HASH_ADDRESSES_POSTAL_CODE).c_str(),
540                                                                GetWithDef(fields, CONTACT_HASH_ADDRESSES_COUNTRY).c_str(),
541                                                                NULL /* address format */,
542                                                                NULL /* uid */),
543                                       false);
544         FolksAbstractFieldDetailsCXX field(FOLKS_ABSTRACT_FIELD_DETAILS(folks_postal_address_field_details_new(address.get(), NULL)),
545                                            false);
546         BOOST_FOREACH (const std::string &type, flags) {
547             folks_abstract_field_details_add_parameter(field.get(),
548                                                        FOLKS_ABSTRACT_FIELD_DETAILS_PARAM_TYPE,
549                                                        type.c_str());
550         }
551         gee_collection_add(GEE_COLLECTION(set.get()),
552                            field.get());
553     }
554     g_hash_table_insert(details.get(),
555                         const_cast<gchar *>(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_POSTAL_ADDRESSES)),
556                         new GValueObjectCXX(set.get()));
557 }
558
559 void DBus2PersonaDetails(GDBusCXX::ExtractArgs &context,
560                          GDBusCXX::reader_type &iter,
561                          PersonaDetails &details)
562 {
563     GDBusCXX::GVariantCXX var(g_variant_iter_next_value(&iter));
564     if (var == NULL || !g_variant_type_is_subtype_of(g_variant_get_type(var), G_VARIANT_TYPE_ARRAY)) {
565         SE_THROW("D-Bus contact hash: unexpected content");
566     }
567
568     GVariantIter contIter;
569     GDBusCXX::GVariantCXX child;
570     g_variant_iter_init(&contIter, var); // array
571     while((child = g_variant_iter_next_value(&contIter)) != NULL) {
572         GVariantIter childIter;
573         g_variant_iter_init(&childIter, child); // dict entry
574         std::string key;
575         GDBusCXX::dbus_traits<std::string>::get(context, childIter, key);
576         GDBusCXX::GVariantCXX valueVarient(g_variant_iter_next_value(&childIter));
577         if (valueVarient == NULL || !g_variant_type_equal(g_variant_get_type(valueVarient), G_VARIANT_TYPE_VARIANT)) {
578             SE_THROW("D-Bus contact hash entry: value must be variant");
579         }
580
581         GVariantIter valueIter;
582         g_variant_iter_init(&valueIter, valueVarient);
583         if (key == CONTACT_HASH_FULL_NAME) {
584             std::string value;
585             GDBusCXX::dbus_traits<std::string>::get(context, valueIter, value);
586             g_hash_table_insert(details.get(),
587                                 const_cast<gchar *>(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_FULL_NAME)),
588                                 new GValueStringCXX(value.c_str()));
589         } else if (key == CONTACT_HASH_STRUCTURED_NAME) {
590             StringMap value;
591             GDBusCXX::dbus_traits<StringMap>::get(context, valueIter, value);
592             g_hash_table_insert(details.get(),
593                                 const_cast<gchar *>(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_STRUCTURED_NAME)),
594                                 new GValueObjectCXX(folks_structured_name_new(GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_FAMILY).c_str(),
595                                                                               GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_GIVEN).c_str(),
596                                                                               GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_ADDITIONAL).c_str(),
597                                                                               GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_PREFIXES).c_str(),
598                                                                               GetWithDef(value, CONTACT_HASH_STRUCTURED_NAME_SUFFIXES).c_str()),
599                                                     false));
600         } else if (key == CONTACT_HASH_PHOTO) {
601             std::string value;
602             GDBusCXX::dbus_traits<std::string>::get(context, valueIter, value);
603             GFileCXX file(g_file_new_for_uri(value.c_str()),
604                           false);
605             g_hash_table_insert(details.get(),
606                                 const_cast<gchar *>(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_AVATAR)),
607                                 new GValueObjectCXX(g_file_icon_new(file.get()), false));
608         } else if (key == CONTACT_HASH_BIRTHDAY) {
609             boost::tuple<int, int, int> value;
610             GDBusCXX::dbus_traits<typeof(value)>::get(context, valueIter, value);
611             GDateTimeCXX local(g_date_time_new_local(value.get<0>(),
612                                                      value.get<1>(),
613                                                      value.get<2>(),
614                                                      0, 0, 0),
615                                false);
616             g_hash_table_insert(details.get(),
617                                 const_cast<gchar *>(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_BIRTHDAY)),
618                                 new GValueDateTimeCXX(g_date_time_to_utc(local.get()), false));
619         } else if (key == CONTACT_HASH_LOCATION) {
620             boost::tuple<double, double> value;
621             GDBusCXX::dbus_traits<typeof(value)>::get(context, valueIter, value);
622             FolksLocationCXX location(folks_location_new(value.get<0>(),
623                                                          value.get<1>()),
624                                       false);
625             g_hash_table_insert(details.get(),
626                                 const_cast<gchar *>(folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_LOCATION)),
627                                 new GValueObjectCXX(location.get()));
628         } else if (key == CONTACT_HASH_EMAILS) {
629             DBus2AbstractField(context, valueIter, details,
630                                FOLKS_TYPE_EMAIL_FIELD_DETAILS,
631                                FOLKS_PERSONA_DETAIL_EMAIL_ADDRESSES,
632                                reinterpret_cast<FolksAbstractFieldDetails *(*)(const gchar *, GeeMultiMap *)>(folks_email_field_details_new));
633         } else if (key == CONTACT_HASH_PHONES) {
634             DBus2AbstractField(context, valueIter, details,
635                                FOLKS_TYPE_PHONE_FIELD_DETAILS,
636                                FOLKS_PERSONA_DETAIL_PHONE_NUMBERS,
637                                reinterpret_cast<FolksAbstractFieldDetails *(*)(const gchar *, GeeMultiMap *)>(folks_phone_field_details_new));
638         } else if (key == CONTACT_HASH_URLS) {
639             DBus2AbstractField(context, valueIter, details,
640                                FOLKS_TYPE_URL_FIELD_DETAILS,
641                                FOLKS_PERSONA_DETAIL_URLS,
642                                reinterpret_cast<FolksAbstractFieldDetails *(*)(const gchar *, GeeMultiMap *)>(folks_url_field_details_new));
643         } else if (key == CONTACT_HASH_NOTES) {
644             DBus2SimpleAbstractField(context, valueIter, details,
645                                      FOLKS_TYPE_NOTE_FIELD_DETAILS,
646                                      FOLKS_PERSONA_DETAIL_NOTES,
647                                      reinterpret_cast<FolksAbstractFieldDetails *(*)(const gchar *, GeeMultiMap *)>(folks_note_field_details_new));
648         } else if (key == CONTACT_HASH_ROLES) {
649             DBus2Role(context, valueIter, details);
650         } else if (key == CONTACT_HASH_GROUPS) {
651             DBus2Groups(context, valueIter, details);
652         } else if (key == CONTACT_HASH_ADDRESSES) {
653             DBus2Addr(context, valueIter, details);
654         }
655     }
656 }
657
658 struct Pending
659 {
660     Pending(const Result<void ()> &result,
661             FolksPersona *persona) :
662         m_result(result),
663         m_persona(persona),
664         m_current(0)
665     {}
666
667     Result<void ()> m_result;
668     FolksPersonaCXX m_persona;
669     typedef boost::function<void (const GError *)> AsyncDone;
670     typedef boost::function<void (AsyncDone *)> Prepare;
671     typedef boost::tuple<Prepare,
672                          const char *,
673                          boost::shared_ptr<void> > Change;
674     typedef std::vector<Change> Changes;
675     Changes m_changes;
676     size_t m_current;
677 };
678
679 static bool GeeCollectionEqual(GeeCollection *a, GeeCollection *b)
680 {
681     return gee_collection_get_size(a) == gee_collection_get_size(b) &&
682         gee_collection_contains_all(a, b);
683 }
684
685 /**
686  * Gets called by event loop. All errors must be reported back to the caller.
687  */
688 static void Details2PersonaStep(const GError *gerror, const boost::shared_ptr<Pending> &pending) throw ()
689 {
690     try {
691         if (gerror) {
692             GErrorCXX::throwError("modifying property", gerror);
693         }
694         size_t current = pending->m_current++;
695         if (current < pending->m_changes.size()) {
696             // send next change, as determined earlier
697             Pending::Change &change = pending->m_changes[current];
698             SE_LOG_DEBUG(NULL, NULL, "modification step %d/%d: %s",
699                          (int)current,
700                          (int)pending->m_changes.size(),
701                          boost::get<1>(change));
702             boost::get<0>(change)(new Pending::AsyncDone(boost::bind(Details2PersonaStep, _1, pending)));
703         } else {
704             pending->m_result.done();
705         }
706     } catch (...) {
707         // Tell caller about specific error.
708         pending->m_result.failed();
709     }
710 }
711
712 void Details2Persona(const Result<void ()> &result, const PersonaDetails &details, FolksPersona *persona)
713 {
714     boost::shared_ptr<Pending> pending(new Pending(result, persona));
715
716 #define PUSH_CHANGE(_prepare) \
717         SE_LOG_DEBUG(NULL, NULL, "queueing new change: %s", #_prepare); \
718         pending->m_changes.push_back(Pending::Change(boost::bind(_prepare, \
719                                                                  details, \
720                                                                  value, \
721                                                                  SYNCEVO_GLIB_CALL_ASYNC_CXX(_prepare)::handleGLibResult, \
722                                                                  _1), \
723                                                      #_prepare, \
724                                                      tracker))
725
726     const GValue *gvalue;
727     boost::shared_ptr<void> tracker;
728     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(),
729                                                              folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_FULL_NAME)));
730     {
731         const gchar *value;
732         if (gvalue) {
733             value = g_value_get_string(gvalue);
734             tracker = boost::shared_ptr<void>(g_strdup(value), g_free);
735         } else {
736             value = NULL;
737         }
738         FolksNameDetails *details = FOLKS_NAME_DETAILS(persona);
739         if (g_strcmp0(value, folks_name_details_get_full_name(details))) {
740             PUSH_CHANGE(folks_name_details_change_full_name);
741         }
742     }
743
744
745     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_STRUCTURED_NAME)));
746     {
747         FolksStructuredName *value;
748         if (gvalue) {
749             value = FOLKS_STRUCTURED_NAME(g_value_get_object(gvalue));
750             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
751         } else {
752             tracker = boost::shared_ptr<void>(folks_structured_name_new("", "", "", "", ""), g_object_unref);
753             value = static_cast<FolksStructuredName *>(tracker.get());
754         }
755         FolksNameDetails *details = FOLKS_NAME_DETAILS(persona);
756         if (!folks_structured_name_equal(value, folks_name_details_get_structured_name(details))) {
757             PUSH_CHANGE(folks_name_details_change_structured_name);
758         }
759     }
760
761     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_AVATAR)));
762     {
763         FolksAvatarDetails *details = FOLKS_AVATAR_DETAILS(persona);
764         GLoadableIcon *value;
765         if (gvalue) {
766             value = G_LOADABLE_ICON(g_value_get_object(gvalue));
767             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
768         } else {
769             tracker = boost::shared_ptr<void>();
770             value = NULL;
771         }
772         InitStateString newPath = GDBusCXX::ExtractFilePath(value);
773         InitStateString oldPath = GDBusCXX::ExtractFilePath(folks_avatar_details_get_avatar(details));
774         if (newPath.wasSet() != oldPath.wasSet() ||
775             newPath != oldPath) {
776             PUSH_CHANGE(folks_avatar_details_change_avatar);
777         }
778     }
779
780     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_BIRTHDAY)));
781     {
782         GDateTime *value;
783         if (gvalue) {
784             value = static_cast<GDateTime *>(g_value_get_boxed(gvalue));
785             tracker = boost::shared_ptr<void>(g_date_time_ref(value), g_date_time_unref);
786         } else {
787             tracker = boost::shared_ptr<void>();
788             value = NULL;
789         }
790         FolksBirthdayDetails *details = FOLKS_BIRTHDAY_DETAILS(persona);
791         GDateTime *old = folks_birthday_details_get_birthday(details);
792         if ((value == NULL && old != NULL) ||
793             (value != NULL && old == NULL) ||
794             !g_date_time_equal(value, old)) {
795             PUSH_CHANGE(folks_birthday_details_change_birthday);
796         }
797     }
798
799     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_LOCATION)));
800     {
801         FolksLocation *value;
802         if (gvalue) {
803             value = static_cast<FolksLocation *>(g_value_get_object(gvalue));
804             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
805         } else {
806             tracker = boost::shared_ptr<void>();
807             value = NULL;
808         }
809         FolksLocationDetails *details = FOLKS_LOCATION_DETAILS(persona);
810         FolksLocation *old = folks_location_details_get_location(details);
811         if ((value == NULL && old != NULL) ||
812             (value != NULL && old == NULL) ||
813             (value && old &&
814              (value->latitude != old->latitude ||
815               value->longitude != old->longitude))) {
816             PUSH_CHANGE(folks_location_details_change_location);
817         }
818     }
819
820     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_EMAIL_ADDRESSES)));
821     {
822         GeeSet *value;
823         if (gvalue) {
824             value = GEE_SET(g_value_get_object(gvalue));
825             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
826         } else {
827             tracker = boost::shared_ptr<void>(gee_hash_set_new(FOLKS_TYPE_EMAIL_FIELD_DETAILS,
828                                                                g_object_ref,
829                                                                g_object_unref,
830                                                                FolksAbstractFieldDetailsHash, NULL, NULL,
831                                                                FolksAbstractFieldDetailsEqual, NULL, NULL),
832                                               g_object_unref);
833             value = static_cast<GeeSet *>(tracker.get());
834         }
835         FolksEmailDetails *details = FOLKS_EMAIL_DETAILS(persona);
836         if (!GeeCollectionEqual(GEE_COLLECTION(value),
837                                 GEE_COLLECTION(folks_email_details_get_email_addresses(details)))) {
838             PUSH_CHANGE(folks_email_details_change_email_addresses);
839         }
840     }
841
842     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_PHONE_NUMBERS)));
843     {
844         GeeSet *value;
845         if (gvalue) {
846             value = GEE_SET(g_value_get_object(gvalue));
847             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
848         } else {
849             tracker = boost::shared_ptr<void>(gee_hash_set_new(FOLKS_TYPE_PHONE_FIELD_DETAILS,
850                                                                g_object_ref,
851                                                                g_object_unref,
852                                                                FolksAbstractFieldDetailsHash, NULL, NULL,
853                                                                FolksAbstractFieldDetailsEqual, NULL, NULL),
854                                               g_object_unref);
855             value = static_cast<GeeSet *>(tracker.get());
856         }
857         FolksPhoneDetails *details = FOLKS_PHONE_DETAILS(persona);
858         if (!GeeCollectionEqual(GEE_COLLECTION(value),
859                                 GEE_COLLECTION(folks_phone_details_get_phone_numbers(details)))) {
860             PUSH_CHANGE(folks_phone_details_change_phone_numbers);
861         }
862     }
863
864     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_URLS)));
865     {
866         GeeSet *value;
867         if (gvalue) {
868             value = GEE_SET(g_value_get_object(gvalue));
869             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
870         } else {
871             tracker = boost::shared_ptr<void>(gee_hash_set_new(FOLKS_TYPE_URL_FIELD_DETAILS,
872                                                                g_object_ref,
873                                                                g_object_unref,
874                                                                FolksAbstractFieldDetailsHash, NULL, NULL,
875                                                                FolksAbstractFieldDetailsEqual, NULL, NULL),
876                                               g_object_unref);
877             value = static_cast<GeeSet *>(tracker.get());
878         }
879         FolksUrlDetails *details = FOLKS_URL_DETAILS(persona);
880         if (!GeeCollectionEqual(GEE_COLLECTION(value),
881                                 GEE_COLLECTION(folks_url_details_get_urls(details)))) {
882             PUSH_CHANGE(folks_url_details_change_urls);
883         }
884     }
885
886     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_NOTES)));
887     {
888         // At the moment the comparison fails and tiggers a write on each update?!
889         GeeSet *value;
890         if (gvalue) {
891             value = GEE_SET(g_value_get_object(gvalue));
892             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
893         } else {
894             tracker = boost::shared_ptr<void>(gee_hash_set_new(FOLKS_TYPE_NOTE_FIELD_DETAILS,
895                                                                g_object_ref,
896                                                                g_object_unref,
897                                                                FolksAbstractFieldDetailsHash, NULL, NULL,
898                                                                FolksAbstractFieldDetailsEqual, NULL, NULL),
899                                               g_object_unref);
900             value = static_cast<GeeSet *>(tracker.get());
901         }
902         FolksNoteDetails *details = FOLKS_NOTE_DETAILS(persona);
903         if (!GeeCollectionEqual(GEE_COLLECTION(value),
904                                 GEE_COLLECTION(folks_note_details_get_notes(details)))) {
905             PUSH_CHANGE(folks_note_details_change_notes);
906         }
907     }
908
909     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_ROLES)));
910     {
911         GeeSet *value;
912         if (gvalue) {
913             value = GEE_SET(g_value_get_object(gvalue));
914             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
915         } else {
916             tracker = boost::shared_ptr<void>(gee_hash_set_new(FOLKS_TYPE_ROLE_FIELD_DETAILS,
917                                                                g_object_ref,
918                                                                g_object_unref,
919                                                                FolksAbstractFieldDetailsHash, NULL, NULL,
920                                                                FolksAbstractFieldDetailsEqual, NULL, NULL),
921                                               g_object_unref);
922             value = static_cast<GeeSet *>(tracker.get());
923         }
924         FolksRoleDetails *details = FOLKS_ROLE_DETAILS(persona);
925         if (!GeeCollectionEqual(GEE_COLLECTION(value),
926                                 GEE_COLLECTION(folks_role_details_get_roles(details)))) {
927             PUSH_CHANGE(folks_role_details_change_roles);
928         }
929     }
930
931     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_GROUPS)));
932     {
933         GeeSet *value;
934         if (gvalue) {
935             value = GEE_SET(g_value_get_object(gvalue));
936             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
937         } else {
938             tracker = boost::shared_ptr<void>(gee_hash_set_new(G_TYPE_STRING,
939                                                                (GBoxedCopyFunc)g_strdup,
940                                                                g_free,
941                                                                NULL,
942                                                                NULL,
943                                                                NULL,
944                                                                NULL,
945                                                                NULL,
946                                                                NULL),
947                                               g_object_unref);
948             value = static_cast<GeeSet *>(tracker.get());
949         }
950         FolksGroupDetails *details = FOLKS_GROUP_DETAILS(persona);
951         if (!GeeCollectionEqual(GEE_COLLECTION(value),
952                                 GEE_COLLECTION(folks_group_details_get_groups(details)))) {
953             PUSH_CHANGE(folks_group_details_change_groups);
954         }
955     }
956
957     gvalue = static_cast<const GValue *>(g_hash_table_lookup(details.get(), folks_persona_store_detail_key(FOLKS_PERSONA_DETAIL_POSTAL_ADDRESSES)));
958     {
959         GeeSet *value;
960         if (gvalue) {
961             value = GEE_SET(g_value_get_object(gvalue));
962             tracker = boost::shared_ptr<void>(g_object_ref(value), g_object_unref);
963         } else {
964             tracker = boost::shared_ptr<void>(gee_hash_set_new(FOLKS_TYPE_POSTAL_ADDRESS_FIELD_DETAILS,
965                                                                g_object_ref,
966                                                                g_object_unref,
967                                                                FolksAbstractFieldDetailsHash, NULL, NULL,
968                                                                FolksAbstractFieldDetailsEqual, NULL, NULL),
969                                               g_object_unref);
970             value = static_cast<GeeSet *>(tracker.get());
971         }
972         FolksPostalAddressDetails *details = FOLKS_POSTAL_ADDRESS_DETAILS(persona);
973         if (!GeeCollectionEqual(GEE_COLLECTION(value),
974                                 GEE_COLLECTION(folks_postal_address_details_get_postal_addresses(details)))) {
975             PUSH_CHANGE(folks_postal_address_details_change_postal_addresses);
976         }
977     }
978
979     Details2PersonaStep(NULL, pending);
980 }
981
982 void FolksIndividual2DBus(const FolksIndividualCXX &individual, GDBusCXX::builder_type &builder)
983 {
984     g_variant_builder_open(&builder, G_VARIANT_TYPE(INDIVIDUAL_DICT)); // dict
985
986     FolksNameDetails *name = FOLKS_NAME_DETAILS(individual.get());
987     SerializeFolks(builder, name, folks_name_details_get_full_name,
988                    CONTACT_HASH_FULL_NAME);
989     SerializeFolks(builder, name, folks_name_details_get_nickname,
990                    CONTACT_HASH_NICKNAME);
991     SerializeFolks(builder, name, folks_name_details_get_structured_name,
992                    CONTACT_HASH_STRUCTURED_NAME);
993
994     // gconstpointer folks_abstract_field_details_get_value (FolksAbstractFieldDetails* self);
995
996     FolksAliasDetails *alias = FOLKS_ALIAS_DETAILS(individual.get());
997     SerializeFolks(builder, alias, folks_alias_details_get_alias,
998                    CONTACT_HASH_ALIAS);
999
1000     FolksAvatarDetails *avatar = FOLKS_AVATAR_DETAILS(individual.get());
1001     SerializeFolks(builder, avatar, folks_avatar_details_get_avatar,
1002                    CONTACT_HASH_PHOTO);
1003
1004     FolksBirthdayDetails *birthday = FOLKS_BIRTHDAY_DETAILS(individual.get());
1005     SerializeFolks(builder, birthday, folks_birthday_details_get_birthday,
1006                    CONTACT_HASH_BIRTHDAY);
1007     // const gchar* folks_birthday_details_get_calendar_event_id (FolksBirthdayDetails* self);
1008
1009     FolksLocationDetails *location = FOLKS_LOCATION_DETAILS(individual.get());
1010     SerializeFolks(builder, location, folks_location_details_get_location,
1011                    CONTACT_HASH_LOCATION);
1012
1013     FolksEmailDetails *emails = FOLKS_EMAIL_DETAILS(individual.get());
1014     SerializeFolks(builder, emails, folks_email_details_get_email_addresses,
1015                    (GeeCollCXX<FolksAbstractFieldDetails *>*)NULL,
1016                    CONTACT_HASH_EMAILS);
1017
1018     FolksPhoneDetails *phones = FOLKS_PHONE_DETAILS(individual.get());
1019     SerializeFolks(builder, phones, folks_phone_details_get_phone_numbers,
1020                    (GeeCollCXX<FolksAbstractFieldDetails *>*)NULL,
1021                    CONTACT_HASH_PHONES);
1022
1023     FolksUrlDetails *urls = FOLKS_URL_DETAILS(individual.get());
1024     SerializeFolks(builder, urls, folks_url_details_get_urls,
1025                    (GeeCollCXX<FolksAbstractFieldDetails *>*)NULL,
1026                    CONTACT_HASH_URLS);
1027
1028     // Doesn't work like this, folks_im_details_get_im_addresses returns
1029     // a GeeMultiMap, not a GeeList. Not required anyway.
1030     // FolksImDetails *im = FOLKS_IM_DETAILS(individual.get());
1031     // SerializeFolks(builder, im, folks_im_details_get_im_addresses,
1032     //                (GeeCollCXX<FolksAbstractFieldDetails *>*)NULL, "im");
1033
1034     FolksNoteDetails *notes = FOLKS_NOTE_DETAILS(individual.get());
1035     SerializeFolks(builder, notes, folks_note_details_get_notes,
1036                    (GeeCollCXX<FolksNoteFieldDetails *>*)NULL,
1037                    CONTACT_HASH_NOTES);
1038
1039     FolksPostalAddressDetails *postal = FOLKS_POSTAL_ADDRESS_DETAILS(individual.get());
1040     SerializeFolks(builder, postal, folks_postal_address_details_get_postal_addresses,
1041                    (GeeCollCXX<FolksPostalAddressFieldDetails *>*)NULL,
1042                    CONTACT_HASH_ADDRESSES);
1043
1044     FolksRoleDetails *roles = FOLKS_ROLE_DETAILS(individual.get());
1045     SerializeFolks(builder, roles, folks_role_details_get_roles,
1046                    (GeeCollCXX<FolksRoleFieldDetails *>*)NULL,
1047                    CONTACT_HASH_ROLES);
1048
1049     FolksGroupDetails *groups = FOLKS_GROUP_DETAILS(individual.get());
1050     SerializeFolks(builder, groups, folks_group_details_get_groups,
1051                    (GeeStringCollection*)NULL,
1052                    CONTACT_HASH_GROUPS);
1053
1054     SerializeFolks(builder, individual.get(), folks_individual_get_personas,
1055                    (GeeCollCXX<FolksPersona *>*)NULL,
1056                    CONTACT_HASH_SOURCE);
1057
1058     SerializeFolks(builder, individual.get(), folks_individual_get_id,
1059                    CONTACT_HASH_ID);
1060
1061 #if 0
1062     // Not exposed via D-Bus.
1063 FolksGender folks_gender_details_get_gender (FolksGenderDetails* self);
1064 GeeMultiMap* folks_web_service_details_get_web_service_addresses (FolksWebServiceDetails* self);
1065 guint folks_interaction_details_get_im_interaction_count (FolksInteractionDetails* self);
1066 GDateTime* folks_interaction_details_get_last_im_interaction_datetime (FolksInteractionDetails* self);
1067 guint folks_interaction_details_get_call_interaction_count (FolksInteractionDetails* self);
1068 GDateTime* folks_interaction_details_get_last_call_interaction_datetime (FolksInteractionDetails* self);
1069 GeeSet* folks_local_id_details_get_local_ids (FolksLocalIdDetails* self);
1070 const gchar* folks_presence_details_get_default_message_from_type (FolksPresenceType type);
1071 FolksPresenceType folks_presence_details_get_presence_type (FolksPresenceDetails* self);
1072 const gchar* folks_presence_details_get_presence_message (FolksPresenceDetails* self);
1073 const gchar* folks_presence_details_get_presence_status (FolksPresenceDetails* self);
1074 #endif
1075
1076     g_variant_builder_close(&builder); // dict
1077 }
1078
1079 SE_END_CXX