2 * Copyright (C) 2012 Intel Corporation
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.
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.
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
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>
28 #include <syncevo/BoostHelper.h>
29 #include <syncevo/LogRedirect.h>
31 #include <boost/ptr_container/ptr_vector.hpp>
33 #include <syncevo/declarations.h>
37 * Generic error callback. There really isn't much that can be done if
38 * libfolks fails, except for logging the problem.
40 static void logResult(const GError *gerror, const char *operation)
43 SE_LOG_ERROR(NULL, "%s: %s", operation, gerror->message);
45 SE_LOG_DEBUG(NULL, "%s: done", operation);
49 class CompareFormattedName : public IndividualCompare {
54 CompareFormattedName(bool reversed = false, bool firstLast = false) :
56 m_firstLast(firstLast)
60 virtual void createCriteria(FolksIndividual *individual, Criteria_t &criteria) const {
61 FolksStructuredName *fn =
62 folks_name_details_get_structured_name(FOLKS_NAME_DETAILS(individual));
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",
69 criteria.push_back(given ? given : "");
70 criteria.push_back(family ? family : "");
72 criteria.push_back(family ? family : "");
73 criteria.push_back(given ? given : "");
76 SE_LOG_DEBUG(NULL, "criteria: no formatted");
80 virtual bool compare(const Criteria_t &a, const Criteria_t &b) const {
82 IndividualCompare::compare(b, a) :
83 IndividualCompare::compare(a, b);
87 boost::shared_ptr<IndividualCompare> IndividualCompare::defaultCompare()
89 boost::shared_ptr<IndividualCompare> compare(new CompareFormattedName);
93 bool IndividualData::init(const IndividualCompare *compare,
94 const LocaleFactory *locale,
95 FolksIndividual *individual)
97 bool precomputedModified = false;
98 m_individual = FolksIndividualCXX(individual, ADD_REF);
101 compare->createCriteria(individual, m_criteria);
104 precomputedModified = locale->precompute(individual, m_precomputed);
106 return precomputedModified;
109 bool IndividualCompare::compare(const Criteria_t &a, const Criteria_t &b) const
111 Criteria_t::const_iterator ita = a.begin(),
114 while (itb != b.end()) {
115 if (ita == a.end()) {
119 int cmp = ita->compare(*itb);
121 // String comparison shows that a is less than b.
123 } else if (cmp > 0) {
124 // Is greater, so definitely not less => don't compare
125 // rest of the criteria.
128 // Equal, continue comparing.
138 IndividualAggregator::IndividualAggregator(const boost::shared_ptr<LocaleFactory> &locale) :
140 m_databases(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc) g_strdup, g_free, NULL, NULL, NULL, NULL, NULL, NULL), TRANSFER_REF)
144 void IndividualAggregator::init(boost::shared_ptr<IndividualAggregator> &self)
148 FolksBackendStoreCXX::steal(folks_backend_store_dup());
150 // Ignore some known harmless messages from folks.
151 LogRedirect::addIgnoreError("Error preparing Backend 'ofono'");
152 LogRedirect::addIgnoreError("Error preparing Backend 'telepathy'");
154 SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_prepare,
155 boost::bind(&IndividualAggregator::storePrepared,
160 FolksIndividualAggregatorCXX::steal(folks_individual_aggregator_new_with_backend_store(m_backendStore));
163 boost::shared_ptr<IndividualAggregator> IndividualAggregator::create(const boost::shared_ptr<LocaleFactory> &locale)
165 boost::shared_ptr<IndividualAggregator> aggregator(new IndividualAggregator(locale));
166 aggregator->init(aggregator);
170 std::string IndividualAggregator::dumpDatabases()
174 BOOST_FOREACH (const gchar *tmp, GeeStringCollection(GEE_COLLECTION(m_databases.get()), ADD_REF)) {
183 void IndividualAggregator::storePrepared()
185 SE_LOG_DEBUG(NULL, "backend store is prepared");
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");
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),
217 void IndividualAggregator::backendsLoaded()
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));
225 FolksBackendCXX::steal(folks_backend_store_dup_backend_by_name(m_backendStore, "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);
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()));
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"),
245 // Execute delayed work.
246 m_backendsLoadedSignal();
248 SE_LOG_ERROR(NULL, "EDS backend not active?!");
252 void IndividualAggregator::setDatabases(std::set<std::string> &databases)
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());
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()));
265 SE_LOG_DEBUG(NULL, "backends not loaded yet: setting EDS persona stores delayed: [%s]",
266 dumpDatabases().c_str());
270 void IndividualAggregator::setCompare(const boost::shared_ptr<IndividualCompare> &compare)
272 // Don't start main view. Instead rememeber the compare instance
276 m_view->setCompare(compare);
280 void IndividualAggregator::setLocale(const boost::shared_ptr<LocaleFactory> &locale)
285 m_view->setLocale(m_locale);
290 void IndividualAggregator::start()
293 m_view = FullView::create(m_folks, m_locale);
295 m_view->setCompare(m_compare);
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"),
307 bool IndividualAggregator::isRunning() const
312 boost::shared_ptr<FullView> IndividualAggregator::getMainView()
320 void IndividualAggregator::addContact(const Result<void (const std::string &)> &result,
321 const PersonaDetails &details)
323 // Called directly by D-Bus client. Need fully functional system address book.
324 runWithAddressBook(boost::bind(&IndividualAggregator::doAddContact,
328 result.getOnError());
331 void IndividualAggregator::doAddContact(const Result<void (const std::string &)> &result,
332 const PersonaDetails &details)
334 SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_add_persona_from_details,
335 boost::bind(&IndividualAggregator::addContactDone,
343 void IndividualAggregator::addContactDone(const GError *gerror,
344 FolksPersona *persona,
345 const Result<void (const std::string &)> &result) throw()
348 // Handle result of folks_persona_store_add_persona_from_details().
349 if (!persona || gerror) {
350 GErrorCXX::throwError(SE_HERE, "add contact", gerror);
353 const gchar *uid = folks_persona_get_uid(persona);
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);
360 SE_THROW("new persona has empty UID");
367 void IndividualAggregator::modifyContact(const Result<void ()> &result,
368 const std::string &localID,
369 const PersonaDetails &details)
371 runWithPersona(boost::bind(&IndividualAggregator::doModifyContact,
377 result.getOnError());
380 void IndividualAggregator::doModifyContact(const Result<void ()> &result,
381 FolksPersona *persona,
382 const PersonaDetails &details) throw()
385 // Asynchronously modify the persona. This will be turned into
386 // EDS updates by folks.
387 Details2Persona(result, details, persona);
393 void IndividualAggregator::removeContact(const Result<void ()> &result,
394 const std::string &localID)
396 runWithPersona(boost::bind(&IndividualAggregator::doRemoveContact,
401 result.getOnError());
404 void IndividualAggregator::doRemoveContact(const Result<void ()> &result,
405 FolksPersona *persona) throw()
408 SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_remove_persona,
409 boost::bind(&IndividualAggregator::removeContactDone,
420 void IndividualAggregator::removeContactDone(const GError *gerror,
421 const Result<void ()> &result) throw()
425 GErrorCXX::throwError(SE_HERE, "remove contact", gerror);
434 void IndividualAggregator::runWithAddressBook(const boost::function<void ()> &operation,
435 const ErrorCb_t &onError) throw()
439 runWithAddressBookHaveEDS(boost::signals2::connection(),
444 m_backendsLoadedSignal.connect_extended(boost::bind(&IndividualAggregator::runWithAddressBookHaveEDS,
455 void IndividualAggregator::runWithAddressBookHaveEDS(const boost::signals2::connection &conn,
456 const boost::function<void ()> &operation,
457 const ErrorCb_t &onError) throw()
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");
466 if (folks_persona_store_get_is_prepared(m_systemStore)) {
467 runWithAddressBookPrepared(NULL,
471 SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_prepare,
472 boost::bind(&IndividualAggregator::runWithAddressBookPrepared,
484 void IndividualAggregator::runWithAddressBookPrepared(const GError *gerror,
485 const boost::function<void ()> &operation,
486 const ErrorCb_t &onError) throw()
489 // Called after EDS system store is prepared, successfully or unsuccessfully.
491 GErrorCXX::throwError(SE_HERE, "prepare EDS system address book", gerror);
499 void IndividualAggregator::runWithPersona(const boost::function<void (FolksPersona *)> &operation,
500 const std::string &localID,
501 const ErrorCb_t &onError) throw()
504 runWithAddressBook(boost::bind(&IndividualAggregator::doRunWithPersona,
515 void IndividualAggregator::doRunWithPersona(const boost::function<void (FolksPersona *)> &operation,
516 const std::string &localID,
517 const ErrorCb_t &onError) throw()
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());
531 SE_THROW(StringPrintf("contact with local ID '%s' not found in system address book", localID.c_str()));
537 #ifdef ENABLE_UNIT_TESTS
539 class FolksTest : public CppUnit::TestFixture {
540 CPPUNIT_TEST_SUITE(FolksTest);
542 CPPUNIT_TEST(asyncError);
543 CPPUNIT_TEST(gvalue);
544 CPPUNIT_TEST_SUITE_END();
547 static void asyncCB(const GError *gerror, const char *func, bool &failed, bool &done) {
551 SE_LOG_ERROR(NULL, "%s: %s", func, gerror->message);
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)),
565 g_main_context_iteration(NULL, true);
567 CPPUNIT_ASSERT(!failed);
569 while (!folks_individual_aggregator_get_is_quiescent(aggregator)) {
570 g_main_context_iteration(NULL, true);
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));
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)),
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",
587 fullname.toString().c_str(),
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",
600 fullname.toString().c_str(),
604 Coll::const_iterator curr = coll.begin();
605 Coll::const_iterator end = coll.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);
612 SE_LOG_DEBUG(NULL, "first: id %s name %s = %s",
614 fullname.toString().c_str(),
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));
626 SE_LOG_DEBUG(NULL, "boost: id %s %s name %s",
628 fullname ? "has" : "has no",
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))));
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());
649 CPPUNIT_ASSERT_EQUAL(b.get(), (gboolean)!b2.get());
651 CPPUNIT_ASSERT_EQUAL(b.get(), b2.get());
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());
660 CPPUNIT_ASSERT(strcmp(str.get(), str2.get()));
661 CPPUNIT_ASSERT(str.get() != str2.get());
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);
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)),
682 g_main_context_iteration(NULL, true);
684 // Invalid parameters are not reported!
685 CPPUNIT_ASSERT(!failed);
687 // using simpler macro
689 SYNCEVO_GLIB_CALL_SYNC(NULL,
691 folks_individual_aggregator_remove_individual,
693 // Invalid parameters are not reported!
694 CPPUNIT_ASSERT(!gerror);
697 static void individualSignal(std::ostringstream &out,
700 const IndividualData &data) {
701 out << action << ": " << index << " " <<
702 folks_name_details_get_full_name(FOLKS_NAME_DETAILS(data.m_individual.get())) <<
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));
713 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(FolksTest);