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, NULL, "%s: %s", operation, gerror->message);
45 SE_LOG_DEBUG(NULL, 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, 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, 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 void IndividualData::init(const IndividualCompare *compare,
94 const LocaleFactory *locale,
95 FolksIndividual *individual)
97 m_individual = individual;
100 compare->createCriteria(individual, m_criteria);
103 locale->precompute(individual, m_precomputed);
107 bool IndividualCompare::compare(const Criteria_t &a, const Criteria_t &b) const
109 Criteria_t::const_iterator ita = a.begin(),
112 while (itb != b.end()) {
113 if (ita == a.end()) {
117 int cmp = ita->compare(*itb);
119 // String comparison shows that a is less than b.
121 } else if (cmp > 0) {
122 // Is greater, so definitely not less => don't compare
123 // rest of the criteria.
126 // Equal, continue comparing.
136 IndividualAggregator::IndividualAggregator(const boost::shared_ptr<LocaleFactory> &locale) :
138 m_databases(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc) g_strdup, g_free, NULL, NULL, NULL, NULL, NULL, NULL), false)
142 void IndividualAggregator::init(boost::shared_ptr<IndividualAggregator> &self)
146 FolksBackendStoreCXX::steal(folks_backend_store_dup());
148 // Ignore some known harmless messages from folks.
149 LogRedirect::addIgnoreError("Error preparing Backend 'ofono'");
150 LogRedirect::addIgnoreError("Error preparing Backend 'telepathy'");
152 SYNCEVO_GLIB_CALL_ASYNC(folks_backend_store_prepare,
153 boost::bind(&IndividualAggregator::storePrepared,
158 FolksIndividualAggregatorCXX::steal(folks_individual_aggregator_new_with_backend_store(m_backendStore));
161 boost::shared_ptr<IndividualAggregator> IndividualAggregator::create(const boost::shared_ptr<LocaleFactory> &locale)
163 boost::shared_ptr<IndividualAggregator> aggregator(new IndividualAggregator(locale));
164 aggregator->init(aggregator);
168 std::string IndividualAggregator::dumpDatabases()
172 BOOST_FOREACH (const gchar *tmp, GeeStringCollection(GEE_COLLECTION(m_databases.get()))) {
181 void IndividualAggregator::storePrepared()
183 SE_LOG_DEBUG(NULL, NULL, "backend store is prepared");
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");
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),
215 void IndividualAggregator::backendsLoaded()
217 SE_LOG_DEBUG(NULL, NULL, "backend store has loaded backends");
218 GeeCollectionCXX coll(folks_backend_store_list_backends(m_backendStore));
219 BOOST_FOREACH (FolksBackend *backend, GeeCollCXX<FolksBackend *>(coll.get())) {
220 SE_LOG_DEBUG(NULL, NULL, "folks backend: %s", folks_backend_get_name(backend));
223 FolksBackendCXX::steal(folks_backend_store_dup_backend_by_name(m_backendStore, "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 = systemStore;
230 // Tell the backend which databases we want.
231 SE_LOG_DEBUG(NULL, 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()));
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"),
243 // Execute delayed work.
244 m_backendsLoadedSignal();
246 SE_LOG_ERROR(NULL, NULL, "EDS backend not active?!");
250 void IndividualAggregator::setDatabases(std::set<std::string> &databases)
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());
258 // Backend is loaded, tell it about the change.
259 SE_LOG_DEBUG(NULL, 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()));
263 SE_LOG_DEBUG(NULL, NULL, "backends not loaded yet: setting EDS persona stores delayed: [%s]",
264 dumpDatabases().c_str());
268 void IndividualAggregator::setCompare(const boost::shared_ptr<IndividualCompare> &compare)
270 // Don't start main view. Instead rememeber the compare instance
274 m_view->setCompare(compare);
278 void IndividualAggregator::start()
281 m_view = FullView::create(m_folks, m_locale);
283 m_view->setCompare(m_compare);
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"),
295 bool IndividualAggregator::isRunning() const
300 boost::shared_ptr<FullView> IndividualAggregator::getMainView()
308 void IndividualAggregator::addContact(const Result<void (const std::string &)> &result,
309 const PersonaDetails &details)
311 // Called directly by D-Bus client. Need fully functional system address book.
312 runWithAddressBook(boost::bind(&IndividualAggregator::doAddContact,
316 result.getOnError());
319 void IndividualAggregator::doAddContact(const Result<void (const std::string &)> &result,
320 const PersonaDetails &details)
322 SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_add_persona_from_details,
323 boost::bind(&IndividualAggregator::addContactDone,
331 void IndividualAggregator::addContactDone(const GError *gerror,
332 FolksPersona *persona,
333 const Result<void (const std::string &)> &result) throw()
336 // Handle result of folks_persona_store_add_persona_from_details().
337 if (!persona || gerror) {
338 GErrorCXX::throwError("add contact", gerror);
341 const gchar *uid = folks_persona_get_uid(persona);
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);
348 SE_THROW("new persona has empty UID");
355 void IndividualAggregator::modifyContact(const Result<void ()> &result,
356 const std::string &localID,
357 const PersonaDetails &details)
359 runWithPersona(boost::bind(&IndividualAggregator::doModifyContact,
365 result.getOnError());
368 void IndividualAggregator::doModifyContact(const Result<void ()> &result,
369 FolksPersona *persona,
370 const PersonaDetails &details) throw()
373 // Asynchronously modify the persona. This will be turned into
374 // EDS updates by folks.
375 Details2Persona(result, details, persona);
381 void IndividualAggregator::removeContact(const Result<void ()> &result,
382 const std::string &localID)
384 runWithPersona(boost::bind(&IndividualAggregator::doRemoveContact,
389 result.getOnError());
392 void IndividualAggregator::doRemoveContact(const Result<void ()> &result,
393 FolksPersona *persona) throw()
396 SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_remove_persona,
397 boost::bind(&IndividualAggregator::removeContactDone,
408 void IndividualAggregator::removeContactDone(const GError *gerror,
409 const Result<void ()> &result) throw()
413 GErrorCXX::throwError("remove contact", gerror);
422 void IndividualAggregator::runWithAddressBook(const boost::function<void ()> &operation,
423 const ErrorCb_t &onError) throw()
427 runWithAddressBookHaveEDS(boost::signals2::connection(),
432 m_backendsLoadedSignal.connect_extended(boost::bind(&IndividualAggregator::runWithAddressBookHaveEDS,
443 void IndividualAggregator::runWithAddressBookHaveEDS(const boost::signals2::connection &conn,
444 const boost::function<void ()> &operation,
445 const ErrorCb_t &onError) throw()
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");
454 if (folks_persona_store_get_is_prepared(m_systemStore)) {
455 runWithAddressBookPrepared(NULL,
459 SYNCEVO_GLIB_CALL_ASYNC(folks_persona_store_prepare,
460 boost::bind(&IndividualAggregator::runWithAddressBookPrepared,
472 void IndividualAggregator::runWithAddressBookPrepared(const GError *gerror,
473 const boost::function<void ()> &operation,
474 const ErrorCb_t &onError) throw()
477 // Called after EDS system store is prepared, successfully or unsuccessfully.
479 GErrorCXX::throwError("prepare EDS system address book", gerror);
487 void IndividualAggregator::runWithPersona(const boost::function<void (FolksPersona *)> &operation,
488 const std::string &localID,
489 const ErrorCb_t &onError) throw()
492 runWithAddressBook(boost::bind(&IndividualAggregator::doRunWithPersona,
503 void IndividualAggregator::doRunWithPersona(const boost::function<void (FolksPersona *)> &operation,
504 const std::string &localID,
505 const ErrorCb_t &onError) throw()
508 GeeMap *personas = folks_persona_store_get_personas(m_systemStore);
509 typedef GeeCollCXX< GeeMapEntryWrapper<const gchar *, FolksPersona *> > Coll;
510 BOOST_FOREACH (const Coll::value_type &entry, Coll(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());
519 SE_THROW(StringPrintf("contact with local ID '%s' not found in system address book", localID.c_str()));
525 #ifdef ENABLE_UNIT_TESTS
527 class FolksTest : public CppUnit::TestFixture {
528 CPPUNIT_TEST_SUITE(FolksTest);
530 CPPUNIT_TEST(asyncError);
531 CPPUNIT_TEST(gvalue);
532 CPPUNIT_TEST_SUITE_END();
535 static void asyncCB(const GError *gerror, const char *func, bool &failed, bool &done) {
539 SE_LOG_ERROR(NULL, NULL, "%s: %s", func, gerror->message);
544 FolksIndividualAggregatorCXX aggregator(folks_individual_aggregator_new(), false);
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)),
553 g_main_context_iteration(NULL, true);
555 CPPUNIT_ASSERT(!failed);
557 while (!folks_individual_aggregator_get_is_quiescent(aggregator)) {
558 g_main_context_iteration(NULL, true);
561 GeeMap *individuals = folks_individual_aggregator_get_individuals(aggregator);
562 SE_LOG_DEBUG(NULL, NULL, "%d individuals", gee_map_get_size(individuals));
564 GeeMapIteratorCXX it(gee_map_map_iterator(individuals), false);
565 while (gee_map_iterator_next(it)) {
566 PlainGStr id(reinterpret_cast<gchar *>(gee_map_iterator_get_key(it)));
567 FolksIndividualCXX individual(reinterpret_cast<FolksIndividual *>(gee_map_iterator_get_value(it)),
569 GValueStringCXX fullname;
570 g_object_get_property(G_OBJECT(individual.get()), "full-name", &fullname);
571 SE_LOG_DEBUG(NULL, NULL, "map: id %s name %s = %s",
573 fullname.toString().c_str(),
577 GeeIteratorCXX it2(gee_iterable_iterator(GEE_ITERABLE(individuals)), false);
578 while (gee_iterator_next(it2)) {
579 GeeMapEntryCXX entry(reinterpret_cast<GeeMapEntry *>(gee_iterator_get(it2)), false);
580 gchar *id(reinterpret_cast<gchar *>(const_cast<gpointer>(gee_map_entry_get_key(entry))));
581 FolksIndividual *individual(reinterpret_cast<FolksIndividual *>(const_cast<gpointer>(gee_map_entry_get_value(entry))));
582 GValueStringCXX fullname;
583 g_object_get_property(G_OBJECT(individual), "full-name", &fullname);
584 SE_LOG_DEBUG(NULL, NULL, "iterable: id %s name %s = %s",
586 fullname.toString().c_str(),
590 typedef GeeCollCXX< GeeMapEntryWrapper<const gchar *, FolksIndividual *> > Coll;
591 Coll coll(individuals);
592 Coll::const_iterator curr = coll.begin();
593 Coll::const_iterator end = coll.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);
600 SE_LOG_DEBUG(NULL, NULL, "first: id %s name %s = %s",
602 fullname.toString().c_str(),
607 BOOST_FOREACH (Coll::value_type &entry, Coll(individuals)) {
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));
614 SE_LOG_DEBUG(NULL, NULL, "boost: id %s %s name %s",
616 fullname ? "has" : "has no",
619 GeeSet *emails = folks_email_details_get_email_addresses(FOLKS_EMAIL_DETAILS(individual));
620 SE_LOG_DEBUG(NULL, NULL, " %d emails", gee_collection_get_size(GEE_COLLECTION(emails)));
621 typedef GeeCollCXX<FolksEmailFieldDetails *> EmailColl;
622 BOOST_FOREACH (FolksEmailFieldDetails *email, EmailColl(emails)) {
623 SE_LOG_DEBUG(NULL, NULL, " %s",
624 reinterpret_cast<const gchar *>(folks_abstract_field_details_get_value(FOLKS_ABSTRACT_FIELD_DETAILS(email))));
632 GValueBooleanCXX b(true);
633 SE_LOG_DEBUG(NULL, NULL, "GValueBooleanCXX(true) = %s", b.toString().c_str());
634 GValueBooleanCXX b2(b);
635 CPPUNIT_ASSERT_EQUAL(b.get(), b2.get());
637 CPPUNIT_ASSERT_EQUAL(b.get(), (gboolean)!b2.get());
639 CPPUNIT_ASSERT_EQUAL(b.get(), b2.get());
641 GValueStringCXX str("foo bar");
642 SE_LOG_DEBUG(NULL, 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());
648 CPPUNIT_ASSERT(strcmp(str.get(), str2.get()));
649 CPPUNIT_ASSERT(str.get() != str2.get());
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);
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)),
670 g_main_context_iteration(NULL, true);
672 // Invalid parameters are not reported!
673 CPPUNIT_ASSERT(!failed);
675 // using simpler macro
677 SYNCEVO_GLIB_CALL_SYNC(NULL,
679 folks_individual_aggregator_remove_individual,
681 // Invalid parameters are not reported!
682 CPPUNIT_ASSERT(!gerror);
685 static void individualSignal(std::ostringstream &out,
688 const IndividualData &data) {
689 out << action << ": " << index << " " <<
690 folks_name_details_get_full_name(FOLKS_NAME_DETAILS(data.m_individual.get())) <<
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));
701 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(FolksTest);