Imported Upstream version 1.4.99.2
[platform/upstream/syncevolution.git] / src / dbus / server / pim / folks.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 #include <config.h>
21 #include "folks.h"
22 #include "full-view.h"
23 #include "filtered-view.h"
24 #include "individual-traits.h"
25 #include "persona-details.h"
26 #include <boost/bind.hpp>
27 #include "test.h"
28 #include <syncevo/BoostHelper.h>
29 #include <syncevo/LogRedirect.h>
30
31 #include <boost/ptr_container/ptr_vector.hpp>
32
33 #include <syncevo/declarations.h>
34 SE_BEGIN_CXX
35
36 /**
37  * Generic error callback. There really isn't much that can be done if
38  * libfolks fails, except for logging the problem.
39  */
40 static void logResult(const GError *gerror, const char *operation)
41 {
42     if (gerror) {
43         SE_LOG_ERROR(NULL, "%s: %s", operation, gerror->message);
44     } else {
45         SE_LOG_DEBUG(NULL, "%s: done", operation);
46     }
47 }
48
49 class CompareFormattedName : public IndividualCompare {
50     bool m_reversed;
51     bool m_firstLast;
52
53 public:
54     CompareFormattedName(bool reversed = false, bool firstLast = false) :
55         m_reversed(reversed),
56         m_firstLast(firstLast)
57     {
58     }
59
60     virtual void createCriteria(FolksIndividual *individual, Criteria_t &criteria) const {
61         FolksStructuredName *fn =
62             folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual));
63         if (fn) {
64             const char *family = folks_structured_name_get_family_name(fn);
65             const char *given = folks_structured_name_get_given_name(fn);
66             SE_LOG_DEBUG(NULL, "criteria: formatted name: %s, %s",
67                          family, given);
68             if (m_firstLast) {
69                 criteria.push_back(given ? given : "");
70                 criteria.push_back(family ? family : "");
71             } else {
72                 criteria.push_back(family ? family : "");
73                 criteria.push_back(given ? given : "");
74             }
75         } else {
76             SE_LOG_DEBUG(NULL, "criteria: no formatted");
77         }
78     }
79
80     virtual bool compare(const Criteria_t &a, const Criteria_t &b) const {
81         return m_reversed ?
82             IndividualCompare::compare(b, a) :
83             IndividualCompare::compare(a, b);
84     }
85 };
86
87 boost::shared_ptr<IndividualCompare> IndividualCompare::defaultCompare()
88 {
89     boost::shared_ptr<IndividualCompare> compare(new CompareFormattedName);
90     return compare;
91 }
92
93 bool IndividualData::init(const IndividualCompare *compare,
94                           const LocaleFactory *locale,
95                           FolksIndividual *individual)
96 {
97     bool precomputedModified = false;
98     m_individual = FolksIndividualCXX(individual, ADD_REF);
99     if (compare) {
100         m_criteria.clear();
101         compare->createCriteria(individual, m_criteria);
102     }
103     if (locale) {
104         precomputedModified = locale->precompute(individual, m_precomputed);
105     }
106     return precomputedModified;
107 }
108
109 bool IndividualCompare::compare(const Criteria_t &a, const Criteria_t &b) const
110 {
111     Criteria_t::const_iterator ita = a.begin(),
112         itb = b.begin();
113
114     while (itb != b.end()) {
115         if (ita == a.end()) {
116             // a is shorter
117             return true;
118         }
119         int cmp = ita->compare(*itb);
120         if (cmp < 0) {
121             // String comparison shows that a is less than b.
122             return true;
123         } else if (cmp > 0) {
124             // Is greater, so definitely not less => don't compare
125             // rest of the criteria.
126             return false;
127         } else {
128             // Equal, continue comparing.
129             ++ita;
130             ++itb;
131         }
132     }
133
134     // a is not less b
135     return false;
136 }
137
138 IndividualAggregator::IndividualAggregator(const boost::shared_ptr<LocaleFactory> &locale) :
139     m_locale(locale),
140     m_databases(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc) g_strdup, g_free, NULL, NULL, NULL, NULL, NULL, NULL), TRANSFER_REF)
141 {
142 }
143
144 void IndividualAggregator::init(boost::shared_ptr<IndividualAggregator> &self)
145 {
146     m_self = self;
147     m_backendStore =
148         FolksBackendStoreCXX::steal(folks_backend_store_dup());
149
150     // Ignore some known harmless messages from folks.
151     LogRedirect::addIgnoreError("Error preparing Backend 'ofono'");
152     LogRedirect::addIgnoreError("Error preparing Backend 'telepathy'");
153
154     SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_prepare,
155                             boost::bind(&IndividualAggregator::storePrepared,
156                                         m_self),
157                             m_backendStore);
158
159     m_folks =
160         FolksIndividualAggregatorCXX::steal(folks_individual_aggregator_new_with_backend_store(m_backendStore));
161 }
162
163 boost::shared_ptr<IndividualAggregator> IndividualAggregator::create(const boost::shared_ptr<LocaleFactory> &locale)
164 {
165     boost::shared_ptr<IndividualAggregator> aggregator(new IndividualAggregator(locale));
166     aggregator->init(aggregator);
167     return aggregator;
168 }
169
170 std::string IndividualAggregator::dumpDatabases()
171 {
172     std::string res;
173
174     BOOST_FOREACH (const gchar *tmp, GeeStringCollection(GEE_COLLECTION(m_databases.get()), ADD_REF)) {
175         if (!res.empty()) {
176             res += ", ";
177         }
178         res += tmp;
179     }
180     return res;
181 }
182
183 void IndividualAggregator::storePrepared()
184 {
185     SE_LOG_DEBUG(NULL, "backend store is prepared");
186
187     // Have to hard-code the list of known backends that we don't want.
188     SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_disable_backend,
189                             boost::bind(logResult, (const GError *)NULL,
190                                         "folks_backend_store_disable_backend"),
191                             m_backendStore, "telepathy");
192     SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_disable_backend,
193                             boost::bind(logResult, (const GError *)NULL,
194                                         "folks_backend_store_disable_backend"),
195                             m_backendStore, "tracker");
196     SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_disable_backend,
197                             boost::bind(logResult, (const GError *)NULL,
198                                         "folks_backend_store_disable_backend"),
199                             m_backendStore, "key-file");
200     SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_disable_backend,
201                             boost::bind(logResult, (const GError *)NULL,
202                                         "folks_backend_store_disable_backend"),
203                             m_backendStore, "libsocialweb");
204     // Explicitly enable EDS, just to be sure.
205     SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_enable_backend,
206                             boost::bind(logResult, (const GError *)NULL,
207                                         "folks_backend_store_enable_backend"),
208                             m_backendStore, "eds");
209
210     // Start loading backends right away. Assumes that the
211     // asynchronous operations above will be done first.
212     SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_load_backends,
213                             boost::bind(&IndividualAggregator::backendsLoaded, m_self),
214                             m_backendStore);
215 }
216
217 void IndividualAggregator::backendsLoaded()
218 {
219     SE_LOG_DEBUG(NULL, "backend store has loaded backends");
220     GeeCollectionCXX coll(folks_backend_store_list_backends(m_backendStore), TRANSFER_REF);
221     BOOST_FOREACH (FolksBackend *backend, GeeCollCXX<FolksBackend *>(coll)) {
222         SE_LOG_DEBUG(NULL, "folks backend: %s", folks_backend_get_name(backend));
223     }
224     m_eds =
225         FolksBackendCXX::steal(folks_backend_store_dup_backend_by_name(m_backendStore, "eds"));
226     if (m_eds) {
227         // Remember system store, for writing contacts.
228         GeeMap *stores = folks_backend_get_persona_stores(m_eds);
229         FolksPersonaStore *systemStore = static_cast<FolksPersonaStore *>(gee_map_get(stores, "system-address-book"));
230         m_systemStore = FolksPersonaStoreCXX(systemStore, TRANSFER_REF);
231
232         // Tell the backend which databases we want.
233         SE_LOG_DEBUG(NULL, "backends loaded: setting EDS persona stores: [%s]",
234                      dumpDatabases().c_str());
235         folks_backend_set_persona_stores(m_eds, GEE_SET(m_databases.get()));
236
237         if (m_view) {
238             // We were started, prepare aggregator.
239             SYNCEVO_GLIB_CALL_ASYNC(folks_individual_aggregator_prepare,
240                                     boost::bind(logResult, _1,
241                                                 "folks_individual_aggregator_prepare"),
242                                     getFolks());
243         }
244
245         // Execute delayed work.
246         m_backendsLoadedSignal();
247     } else {
248         SE_LOG_ERROR(NULL, "EDS backend not active?!");
249     }
250 }
251
252 void IndividualAggregator::setDatabases(std::set<std::string> &databases)
253 {
254     gee_collection_clear(GEE_COLLECTION(m_databases.get()));
255     BOOST_FOREACH (const std::string &database, databases) {
256         gee_collection_add(GEE_COLLECTION(m_databases.get()), database.c_str());
257     }
258
259     if (m_eds) {
260         // Backend is loaded, tell it about the change.
261         SE_LOG_DEBUG(NULL, "backends already loaded: setting EDS persona stores directly: [%s]",
262                      dumpDatabases().c_str());
263         folks_backend_set_persona_stores(m_eds, GEE_SET(m_databases.get()));
264     } else {
265         SE_LOG_DEBUG(NULL, "backends not loaded yet: setting EDS persona stores delayed: [%s]",
266                      dumpDatabases().c_str());
267     }
268 }
269
270 void IndividualAggregator::setCompare(const boost::shared_ptr<IndividualCompare> &compare)
271 {
272     // Don't start main view. Instead rememeber the compare instance
273     // for start().
274     m_compare = compare;
275     if (m_view) {
276         m_view->setCompare(compare);
277     }
278 }
279
280 void IndividualAggregator::setLocale(const boost::shared_ptr<LocaleFactory> &locale)
281 {
282     m_locale = locale;
283
284     if (m_view) {
285         m_view->setLocale(m_locale);
286     }
287 }
288
289
290 void IndividualAggregator::start()
291 {
292     if (!m_view) {
293         m_view = FullView::create(m_folks, m_locale);
294         if (m_compare) {
295             m_view->setCompare(m_compare);
296         }
297         if (m_eds) {
298             // Backend was loaded and configured, we can prepare the aggregator.
299             SYNCEVO_GLIB_CALL_ASYNC(folks_individual_aggregator_prepare,
300                                     boost::bind(logResult, _1,
301                                                 "folks_individual_aggregator_prepare"),
302                                     getFolks());
303         }
304     }
305 }
306
307 bool IndividualAggregator::isRunning() const
308 {
309     return m_view;
310 }
311
312 boost::shared_ptr<FullView> IndividualAggregator::getMainView()
313 {
314     if (!m_view) {
315         start();
316     }
317     return m_view;
318 }
319
320 void IndividualAggregator::addContact(const Result<void (const std::string &)> &result,
321                                       const PersonaDetails &details)
322 {
323     // Called directly by D-Bus client. Need fully functional system address book.
324     runWithAddressBook(boost::bind(&IndividualAggregator::doAddContact,
325                                    this,
326                                    result,
327                                    details),
328                        result.getOnError());
329 }
330
331 void IndividualAggregator::doAddContact(const Result<void (const std::string &)> &result,
332                                         const PersonaDetails &details)
333 {
334     SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_add_persona_from_details,
335                             boost::bind(&IndividualAggregator::addContactDone,
336                                         this,
337                                         _2, _1,
338                                         result),
339                             m_systemStore,
340                             details.get());
341 }
342
343 void IndividualAggregator::addContactDone(const GError *gerror,
344                                           FolksPersona *persona,
345                                           const Result<void (const std::string &)> &result) throw()
346 {
347     try {
348         // Handle result of folks_persona_store_add_persona_from_details().
349         if (!persona || gerror) {
350             GErrorCXX::throwError(SE_HERE, "add contact", gerror);
351         }
352
353         const gchar *uid = folks_persona_get_uid(persona);
354         if (uid) {
355             gchar *backend, *storeID, *personaID;
356             folks_persona_split_uid(uid, &backend, &storeID, &personaID);
357             PlainGStr tmp1(backend), tmp2(storeID), tmp3(personaID);
358             result.done(personaID);
359         } else {
360             SE_THROW("new persona has empty UID");
361         }
362     } catch (...) {
363         result.failed();
364     }
365 }
366
367 void IndividualAggregator::modifyContact(const Result<void ()> &result,
368                                          const std::string &localID,
369                                          const PersonaDetails &details)
370 {
371     runWithPersona(boost::bind(&IndividualAggregator::doModifyContact,
372                                this,
373                                result,
374                                _1,
375                                details),
376                    localID,
377                    result.getOnError());
378 }
379
380 void IndividualAggregator::doModifyContact(const Result<void ()> &result,
381                                            FolksPersona *persona,
382                                            const PersonaDetails &details) throw()
383 {
384     try {
385         // Asynchronously modify the persona. This will be turned into
386         // EDS updates by folks.
387         Details2Persona(result, details, persona);
388     } catch (...) {
389         result.failed();
390     }
391 }
392
393 void IndividualAggregator::removeContact(const Result<void ()> &result,
394                                          const std::string &localID)
395 {
396     runWithPersona(boost::bind(&IndividualAggregator::doRemoveContact,
397                                this,
398                                result,
399                                _1),
400                    localID,
401                    result.getOnError());
402 }
403
404 void IndividualAggregator::doRemoveContact(const Result<void ()> &result,
405                                            FolksPersona *persona) throw()
406 {
407     try {
408         SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_remove_persona,
409                                 boost::bind(&IndividualAggregator::removeContactDone,
410                                             this,
411                                             _1,
412                                             result),
413                                 m_systemStore,
414                                 persona);
415     } catch (...) {
416         result.failed();
417     }
418 }
419
420 void IndividualAggregator::removeContactDone(const GError *gerror,
421                                              const Result<void ()> &result) throw()
422 {
423     try {
424         if (gerror) {
425             GErrorCXX::throwError(SE_HERE, "remove contact", gerror);
426         }
427         result.done();
428     } catch (...) {
429         result.failed();
430     }
431 }
432
433
434 void IndividualAggregator::runWithAddressBook(const boost::function<void ()> &operation,
435                                               const ErrorCb_t &onError) throw()
436 {
437     try {
438         if (m_eds) {
439             runWithAddressBookHaveEDS(boost::signals2::connection(),
440                                       operation,
441                                       onError);
442         } else {
443             // Do it later.
444             m_backendsLoadedSignal.connect_extended(boost::bind(&IndividualAggregator::runWithAddressBookHaveEDS,
445                                                                 this,
446                                                                 _1,
447                                                                 operation,
448                                                                 onError));
449         }
450     } catch (...) {
451         onError();
452     }
453 }
454
455 void IndividualAggregator::runWithAddressBookHaveEDS(const boost::signals2::connection &conn,
456                                                      const boost::function<void ()> &operation,
457                                                      const ErrorCb_t &onError) throw()
458 {
459     try {
460         // Called after we obtained EDS backend. Need system store
461         // which is prepared.
462         m_backendsLoadedSignal.disconnect(conn);
463         if (!m_systemStore) {
464             SE_THROW("system address book not found");
465         }
466         if (folks_persona_store_get_is_prepared(m_systemStore)) {
467             runWithAddressBookPrepared(NULL,
468                                        operation,
469                                        onError);
470         } else {
471             SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_prepare,
472                                     boost::bind(&IndividualAggregator::runWithAddressBookPrepared,
473                                                 this,
474                                                 _1,
475                                                 operation,
476                                                 onError),
477                                     m_systemStore);
478         }
479     } catch (...) {
480         onError();
481     }
482 }
483
484 void IndividualAggregator::runWithAddressBookPrepared(const GError *gerror,
485                                                       const boost::function<void ()> &operation,
486                                                       const ErrorCb_t &onError) throw()
487 {
488     try {
489         // Called after EDS system store is prepared, successfully or unsuccessfully.
490         if (gerror) {
491             GErrorCXX::throwError(SE_HERE, "prepare EDS system address book", gerror);
492         }
493         operation();
494     } catch (...) {
495         onError();
496     }
497 }
498
499 void IndividualAggregator::runWithPersona(const boost::function<void (FolksPersona *)> &operation,
500                                           const std::string &localID,
501                                           const ErrorCb_t &onError) throw()
502 {
503     try {
504         runWithAddressBook(boost::bind(&IndividualAggregator::doRunWithPersona,
505                                        this,
506                                        operation,
507                                        localID,
508                                        onError),
509                            onError);
510     } catch (...) {
511         onError();
512     }
513 }
514
515 void IndividualAggregator::doRunWithPersona(const boost::function<void (FolksPersona *)> &operation,
516                                             const std::string &localID,
517                                             const ErrorCb_t &onError) throw()
518 {
519     try {
520         typedef GeeCollCXX< GeeMapEntryWrapper<const gchar *, FolksPersona *> > Coll;
521         Coll personas(folks_persona_store_get_personas(m_systemStore), ADD_REF);
522         BOOST_FOREACH (const Coll::value_type &entry, personas) {
523             // key seems to be <store id>:<persona ID>
524             const gchar *key = entry.key();
525             const gchar *colon = strchr(key, ':');
526             if (colon && localID == colon + 1) {
527                 operation(entry.value());
528                 return;
529             }
530         }
531         SE_THROW(StringPrintf("contact with local ID '%s' not found in system address book", localID.c_str()));
532     } catch (...) {
533         onError();
534     }
535 }
536
537 #ifdef ENABLE_UNIT_TESTS
538
539 class FolksTest : public CppUnit::TestFixture {
540     CPPUNIT_TEST_SUITE(FolksTest);
541     CPPUNIT_TEST(open);
542     CPPUNIT_TEST(asyncError);
543     CPPUNIT_TEST(gvalue);
544     CPPUNIT_TEST_SUITE_END();
545
546 private:
547     static void asyncCB(const GError *gerror, const char *func, bool &failed, bool &done) {
548         done = true;
549         if (gerror) {
550             failed = true;
551             SE_LOG_ERROR(NULL, "%s: %s", func, gerror->message);
552         }
553     }
554
555     void open() {
556         FolksIndividualAggregatorCXX aggregator(folks_individual_aggregator_new(), TRANSFER_REF);
557         bool done = false, failed = false;
558         SYNCEVO_GLIB_CALL_ASYNC(folks_individual_aggregator_prepare,
559                                 boost::bind(asyncCB, _1,
560                                             "folks_individual_aggregator_prepare",
561                                             boost::ref(failed), boost::ref(done)),
562                                 aggregator);
563
564         while (!done) {
565             g_main_context_iteration(NULL, true);
566         }
567         CPPUNIT_ASSERT(!failed);
568
569         while (!folks_individual_aggregator_get_is_quiescent(aggregator)) {
570             g_main_context_iteration(NULL, true);
571         }
572
573         GeeMap *individuals = folks_individual_aggregator_get_individuals(aggregator);
574         typedef GeeCollCXX< GeeMapEntryWrapper<const gchar *, FolksIndividual *> > Coll;
575         Coll coll(individuals, ADD_REF);
576         SE_LOG_DEBUG(NULL, "%d individuals", gee_map_get_size(individuals));
577
578         GeeMapIteratorCXX it(gee_map_map_iterator(individuals), TRANSFER_REF);
579         while (gee_map_iterator_next(it)) {
580             PlainGStr id(reinterpret_cast<gchar *>(gee_map_iterator_get_key(it)));
581             FolksIndividualCXX individual(reinterpret_cast<FolksIndividual *>(gee_map_iterator_get_value(it)),
582                                           TRANSFER_REF);
583             GValueStringCXX fullname;
584             g_object_get_property(G_OBJECT(individual.get()), "full-name", &fullname);
585             SE_LOG_DEBUG(NULL, "map: id %s name %s = %s",
586                          id.get(),
587                          fullname.toString().c_str(),
588                          fullname.get());
589         }
590
591         GeeIteratorCXX it2(gee_iterable_iterator(GEE_ITERABLE(individuals)), TRANSFER_REF);
592         while (gee_iterator_next(it2)) {
593             GeeMapEntryCXX entry(reinterpret_cast<GeeMapEntry *>(gee_iterator_get(it2)), TRANSFER_REF);
594             gchar *id(reinterpret_cast<gchar *>(const_cast<gpointer>(gee_map_entry_get_key(entry))));
595             FolksIndividual *individual(reinterpret_cast<FolksIndividual *>(const_cast<gpointer>(gee_map_entry_get_value(entry))));
596             GValueStringCXX fullname;
597             g_object_get_property(G_OBJECT(individual), "full-name", &fullname);
598             SE_LOG_DEBUG(NULL, "iterable: id %s name %s = %s",
599                          id,
600                          fullname.toString().c_str(),
601                          fullname.get());
602         }
603
604         Coll::const_iterator curr = coll.begin();
605         Coll::const_iterator end = coll.end();
606         if (curr != end) {
607             const gchar *id = (*curr).key();
608             FolksIndividual *individual((*curr).value());
609             GValueStringCXX fullname;
610             g_object_get_property(G_OBJECT(individual), "full-name", &fullname);
611
612             SE_LOG_DEBUG(NULL, "first: id %s name %s = %s",
613                          id,
614                          fullname.toString().c_str(),
615                          fullname.get());
616             ++curr;
617         }
618
619         BOOST_FOREACH (Coll::value_type &entry, coll) {
620             const gchar *id = entry.key();
621             FolksIndividual *individual(entry.value());
622             // GValueStringCXX fullname;
623             // g_object_get_property(G_OBJECT(individual), "full-name", &fullname);
624             const gchar *fullname = folks_name_details_get_full_name(FOLKS_NAME_DETAILS(individual));
625
626             SE_LOG_DEBUG(NULL, "boost: id %s %s name %s",
627                          id,
628                          fullname ? "has" : "has no",
629                          fullname);
630
631             typedef GeeCollCXX<FolksEmailFieldDetails *> EmailColl;
632             EmailColl emails(folks_email_details_get_email_addresses(FOLKS_EMAIL_DETAILS(individual)), ADD_REF);
633             SE_LOG_DEBUG(NULL, "     %d emails", gee_collection_get_size(GEE_COLLECTION(emails.get())));
634             BOOST_FOREACH (FolksEmailFieldDetails *email, emails) {
635                 SE_LOG_DEBUG(NULL, "     %s",
636                              reinterpret_cast<const gchar *>(folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(email))));
637             }
638         }
639
640         aggregator.reset();
641     }
642
643     void gvalue() {
644         GValueBooleanCXX b(true);
645         SE_LOG_DEBUG(NULL, "GValueBooleanCXX(true) = %s", b.toString().c_str());
646         GValueBooleanCXX b2(b);
647         CPPUNIT_ASSERT_EQUAL(b.get(), b2.get());
648         b2.set(false);
649         CPPUNIT_ASSERT_EQUAL(b.get(), (gboolean)!b2.get());
650         b2 = b;
651         CPPUNIT_ASSERT_EQUAL(b.get(), b2.get());
652
653         GValueStringCXX str("foo bar");
654         SE_LOG_DEBUG(NULL, "GValueStringCXX(\"foo bar\") = %s", str.toString().c_str());
655         CPPUNIT_ASSERT(!strcmp(str.get(), "foo bar"));
656         GValueStringCXX str2(str);
657         CPPUNIT_ASSERT(!strcmp(str.get(), str2.get()));
658         CPPUNIT_ASSERT(str.get() != str2.get());
659         str2.set("foo");
660         CPPUNIT_ASSERT(strcmp(str.get(), str2.get()));
661         CPPUNIT_ASSERT(str.get() != str2.get());
662         str2 = str;
663         CPPUNIT_ASSERT(!strcmp(str.get(), str2.get()));
664         CPPUNIT_ASSERT(str.get() != str2.get());
665         str2.take(g_strdup("bar"));
666         CPPUNIT_ASSERT(strcmp(str.get(), str2.get()));
667         CPPUNIT_ASSERT(str.get() != str2.get());
668         const char *fixed = "fixed";
669         str2.setStatic(fixed);
670         CPPUNIT_ASSERT(!strcmp(str2.get(), fixed));
671         CPPUNIT_ASSERT(str2.get() == fixed);
672     }
673
674     void asyncError() {
675         bool done = false, failed = false;
676         SYNCEVO_GLIB_CALL_ASYNC(folks_individual_aggregator_remove_individual,
677                                 boost::bind(asyncCB, _1,
678                                             "folks_individual_aggregator_remove_individual",
679                                             boost::ref(failed), boost::ref(done)),
680                                 NULL, NULL);
681         while (!done) {
682             g_main_context_iteration(NULL, true);
683         }
684         // Invalid parameters are not reported!
685         CPPUNIT_ASSERT(!failed);
686
687         // using simpler macro
688         GErrorCXX gerror;
689         SYNCEVO_GLIB_CALL_SYNC(NULL,
690                                gerror,
691                                folks_individual_aggregator_remove_individual,
692                                NULL, NULL);
693         // Invalid parameters are not reported!
694         CPPUNIT_ASSERT(!gerror);
695     }
696
697     static void individualSignal(std::ostringstream &out,
698                                  const char *action,
699                                  int index,
700                                  const IndividualData &data) {
701         out << action << ": " << index << " " <<
702             folks_name_details_get_full_name(FOLKS_NAME_DETAILS(data.m_individual.get())) <<
703             std::endl;
704     }
705
706     static void monitorView(IndividualView &view, std::ostringstream &out) {
707         view.m_addedSignal.connect(boost::bind(individualSignal, boost::ref(out), "added", _1, _2));
708         view.m_removedSignal.connect(boost::bind(individualSignal, boost::ref(out), "removed", _1, _2));
709         view.m_modifiedSignal.connect(boost::bind(individualSignal, boost::ref(out), "modified", _1, _2));
710     }
711 };
712
713 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(FolksTest);
714
715 #endif
716
717 SE_END_CXX
718