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