summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Ohly <patrick.ohly@intel.com>2012-11-29 20:53:01 +0100
committerPatrick Ohly <patrick.ohly@intel.com>2012-12-07 20:00:36 +0100
commit8bf486427fa8acfceef3b26d34fa303d6cfbc3e3 (patch)
tree03949e56f291c9e5607ec2d7dd91d05b2ce89b63
parent970e4c5b5fb4aea4eacfdb133718295a6e94c6bc (diff)
PIM: search for phone number in EDS directly during startup
Bypass folks while it still loads contacts and search for a phone number directly in EDS. This is necessary to ensure prompt responses for the caller ID lookup. Done with a StreamingView which translates EContacts into FolksIndividuals with the help of folks-eds = edsf. Combining these intermediate results and switching to the final results is done by a new MergeView class. A quiescence signal is emitted after receiving the EDS results and after folks is done. The first signal will be skipped when folks finishes first. The second signal will always be send, even if switching to folks did not change anything. Together with an artificial delay before folks is considered done, that approach make testing more reliable.
-rw-r--r--configure.ac2
-rw-r--r--src/dbus/server/pim/README28
-rw-r--r--src/dbus/server/pim/edsf-view.cpp118
-rw-r--r--src/dbus/server/pim/edsf-view.h74
-rw-r--r--src/dbus/server/pim/folks.h7
-rw-r--r--src/dbus/server/pim/full-view.cpp11
-rw-r--r--src/dbus/server/pim/full-view.h1
-rw-r--r--src/dbus/server/pim/locale-factory-boost.cpp16
-rw-r--r--src/dbus/server/pim/manager.cpp49
-rw-r--r--src/dbus/server/pim/merge-view.cpp207
-rw-r--r--src/dbus/server/pim/merge-view.h92
-rwxr-xr-xsrc/dbus/server/pim/testpim.py239
-rw-r--r--src/dbus/server/server.am2
13 files changed, 845 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index cb66617e..340cf7ce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -538,7 +538,7 @@ if test $enable_dbus_service = "yes"; then
if ! test -r "$srcdir/src/dbus/server/pim/locale-factory-$enable_dbus_pim.cpp"; then
AC_MSG_ERROR([invalid value '$enable_dbus_pim' for --enable-dbus-service-pim, $srcdir/src/dbus/server/pim/locale-factory-$enable_dbus_pim.cpp does not exist or is not readable])
fi
- PKG_CHECK_MODULES(FOLKS, [folks])
+ PKG_CHECK_MODULES(FOLKS, [folks folks-eds])
AC_DEFINE(ENABLE_DBUS_PIM, 1, [org._01.pim D-Bus API enabled])
DBUS_PIM_PLUGIN=$enable_dbus_pim
AC_SUBST(DBUS_PIM_PLUGIN)
diff --git a/src/dbus/server/pim/README b/src/dbus/server/pim/README
index 2ac82fec..7497e627 100644
--- a/src/dbus/server/pim/README
+++ b/src/dbus/server/pim/README
@@ -123,6 +123,34 @@ implementation and its testing. The limitation could be removed if
there is sufficient demand.
+Phone number lookup
+===================
+
+A "phone" search must return results quickly (<30ms with 10000
+contacts) under all circumstances, including the period of time where
+the unified address book is still getting assembled in memory. To
+achieve this, SyncEvolution searches directly in the active address
+books and presents these results until the ones from the unified
+address book are ready.
+
+A quiescence signal will be sent when:
+1. results from EDS are complete before the ones from the unified
+ address book
+2. results from the unified address book are complete.
+
+The first signal will be skipped and the EDS results discarded if EDS
+turns out to be slower than the unified address book.
+
+Results from different EDS address books are not unified, for the sake
+of simplicity. They get sorted according to the sort order that was
+active when starting the search. Changing the sort order while the
+search runs will only affect the final results from the unified
+address book.
+
+Refining such a search is not supported because refining a phone
+number lookup is not useful.
+
+
Configuration and data handling
===============================
diff --git a/src/dbus/server/pim/edsf-view.cpp b/src/dbus/server/pim/edsf-view.cpp
new file mode 100644
index 00000000..f573df9f
--- /dev/null
+++ b/src/dbus/server/pim/edsf-view.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) version 3.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include "edsf-view.h"
+#include <syncevo/BoostHelper.h>
+
+SE_GOBJECT_TYPE(EdsfPersona);
+
+SE_BEGIN_CXX
+
+EDSFView::EDSFView(const ESourceRegistryCXX &registry,
+ const std::string &uuid,
+ const std::string &query) :
+ m_registry(registry),
+ m_uuid(uuid),
+ m_query(query)
+{
+}
+
+void EDSFView::init(const boost::shared_ptr<EDSFView> &self)
+{
+ m_self = self;
+}
+
+boost::shared_ptr<EDSFView> EDSFView::create(const ESourceRegistryCXX &registry,
+ const std::string &uuid,
+ const std::string &query)
+{
+ boost::shared_ptr<EDSFView> view(new EDSFView(registry, uuid, query));
+ view->init(view);
+ return view;
+}
+
+void EDSFView::doStart()
+{
+ ESourceCXX source(e_source_registry_ref_source(m_registry, m_uuid.c_str()), false);
+ if (!source) {
+ SE_LOG_DEBUG(NULL, NULL, "edsf %s: address book not found", m_uuid.c_str());
+ return;
+ }
+ m_store = EdsfPersonaStoreCXX::steal(edsf_persona_store_new_with_source_registry(m_registry, source));
+ GErrorCXX gerror;
+ m_ebook = EBookClientCXX::steal(e_book_client_new(source.get(), gerror));
+ if (!m_ebook) {
+ SE_LOG_DEBUG(NULL, NULL, "edfs %s: no client for address book: %s", m_uuid.c_str(), gerror->message);
+ return;
+ }
+ SYNCEVO_GLIB_CALL_ASYNC(e_client_open,
+ boost::bind(&EDSFView::opened,
+ m_self,
+ _1,
+ _2),
+ E_CLIENT(m_ebook.get()),
+ false,
+ NULL);
+}
+
+void EDSFView::opened(gboolean success, const GError *gerror) throw()
+{
+ try {
+ if (!success) {
+ SE_LOG_DEBUG(NULL, NULL, "edfs %s: opening failed: %s", m_uuid.c_str(), gerror->message);
+ return;
+ }
+ SYNCEVO_GLIB_CALL_ASYNC(e_book_client_get_contacts,
+ boost::bind(&EDSFView::read,
+ m_self,
+ _1,
+ _2,
+ _3),
+ m_ebook.get(),
+ m_query.c_str(),
+ NULL);
+ } catch (...) {
+ Exception::handle(HANDLE_EXCEPTION_NO_ERROR);
+ }
+}
+
+void EDSFView::read(gboolean success, GSList *contactslist, const GError *gerror) throw()
+{
+ try {
+ GListCXX<EContact, GSList, GObjectDestructor> contacts(contactslist);
+ if (!success) {
+ SE_LOG_DEBUG(NULL, NULL, "edfs %s: reading failed: %s", m_uuid.c_str(), gerror->message);
+ return;
+ }
+
+ BOOST_FOREACH (EContact *contact, contacts) {
+ EdsfPersonaCXX persona(edsf_persona_new(m_store, contact), false);
+ GeeHashSetCXX personas(gee_hash_set_new(G_TYPE_OBJECT, g_object_ref, g_object_unref, NULL, NULL), false);
+ gee_collection_add(GEE_COLLECTION(personas.get()), persona.get());
+ FolksIndividualCXX individual(folks_individual_new(GEE_SET(personas.get())), false);
+ m_addedSignal(individual);
+ }
+ m_isQuiescent = true;
+ m_quiescenceSignal();
+ } catch (...) {
+ Exception::handle(HANDLE_EXCEPTION_NO_ERROR);
+ }
+}
+
+SE_END_CXX
diff --git a/src/dbus/server/pim/edsf-view.h b/src/dbus/server/pim/edsf-view.h
new file mode 100644
index 00000000..b5e04c65
--- /dev/null
+++ b/src/dbus/server/pim/edsf-view.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) version 3.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+/**
+ * Search in an EBook once. Uses folks-eds (= EDSF) to turn EContacts
+ * into FolksPersonas and from that into FolksIndividuals. Results
+ * will be sorted after the search is complete, then they will be
+ * advertised with the "added" signal.
+ */
+
+#ifndef INCL_SYNCEVO_DBUS_SERVER_PIM_EDSF_VIEW
+#define INCL_SYNCEVO_DBUS_SERVER_PIM_EDSF_VIEW
+
+#include "view.h"
+#include <folks/folks-eds.h>
+#include <syncevo/EDSClient.h>
+
+#include <boost/ptr_container/ptr_vector.hpp>
+
+SE_GOBJECT_TYPE(EBookClient)
+SE_GOBJECT_TYPE(EdsfPersonaStore);
+
+#include <syncevo/declarations.h>
+SE_BEGIN_CXX
+
+class EDSFView : public StreamingView
+{
+ boost::weak_ptr<EDSFView> m_self;
+ ESourceRegistryCXX m_registry;
+ std::string m_uuid;
+ std::string m_query;
+
+ EBookClientCXX m_ebook;
+ EdsfPersonaStoreCXX m_store;
+ Bool m_isQuiescent;
+
+ EDSFView(const ESourceRegistryCXX &registry,
+ const std::string &uuid,
+ const std::string &query);
+ void init(const boost::shared_ptr<EDSFView> &self);
+
+ void opened(gboolean success, const GError *gerror) throw();
+ void read(gboolean success, GSList *contactlist, const GError *gerror) throw();
+
+ public:
+ static boost::shared_ptr<EDSFView> create(const ESourceRegistryCXX &registry,
+ const std::string &uuid,
+ const std::string &query);
+
+ virtual bool isQuiescent() const { return m_isQuiescent; }
+
+ protected:
+ virtual void doStart();
+};
+
+SE_END_CXX
+
+#endif // INCL_SYNCEVO_DBUS_SERVER_PIM_EDSF_VIEW
diff --git a/src/dbus/server/pim/folks.h b/src/dbus/server/pim/folks.h
index 59cf6ace..375d4179 100644
--- a/src/dbus/server/pim/folks.h
+++ b/src/dbus/server/pim/folks.h
@@ -168,6 +168,13 @@ class IndividualFilter
*/
bool isIncluded(size_t index) const { return m_maxResults == -1 || index < (size_t)m_maxResults; }
+ /**
+ * The corresponding EBook query string
+ * (http://developer.gnome.org/libebook/stable/libebook-e-book-query.html#e-book-query-to-string)
+ * for the filter, if there is one. Empty if not.
+ */
+ virtual std::string getEBookFilter() const { return ""; }
+
/** true if the contact matches the filter */
virtual bool matches(const IndividualData &data) const = 0;
};
diff --git a/src/dbus/server/pim/full-view.cpp b/src/dbus/server/pim/full-view.cpp
index 212c267b..0bb3aa0f 100644
--- a/src/dbus/server/pim/full-view.cpp
+++ b/src/dbus/server/pim/full-view.cpp
@@ -156,6 +156,17 @@ void FullView::quiescenceChanged()
// once. See https://bugzilla.gnome.org/show_bug.cgi?id=684766
// "enter and leave quiescence state".
if (quiescent) {
+ int seconds = atoi(getEnv("SYNCEVOLUTION_PIM_DELAY_FOLKS", "0"));
+ if (seconds > 0) {
+ // Delay the quiescent state change as requested.
+ SE_LOG_DEBUG(NULL, NULL, "delay aggregrator quiescence by %d seconds", seconds);
+ m_quiescenceDelay.runOnce(seconds,
+ boost::bind(&FullView::quiescenceChanged,
+ this));
+ unsetenv("SYNCEVOLUTION_PIM_DELAY_FOLKS");
+ return;
+ }
+
m_isQuiescent = true;
m_quiescenceSignal();
}
diff --git a/src/dbus/server/pim/full-view.h b/src/dbus/server/pim/full-view.h
index 5dc7dd19..3035fa5f 100644
--- a/src/dbus/server/pim/full-view.h
+++ b/src/dbus/server/pim/full-view.h
@@ -37,6 +37,7 @@ class FullView : public IndividualView
boost::weak_ptr<FullView> m_self;
Timeout m_waitForIdle;
std::set<FolksIndividualCXX> m_pendingModifications;
+ Timeout m_quiescenceDelay;
/**
* Sorted vector. Sort order is maintained by this class.
diff --git a/src/dbus/server/pim/locale-factory-boost.cpp b/src/dbus/server/pim/locale-factory-boost.cpp
index 036b6bd4..c82f67e6 100644
--- a/src/dbus/server/pim/locale-factory-boost.cpp
+++ b/src/dbus/server/pim/locale-factory-boost.cpp
@@ -24,10 +24,14 @@
#include "locale-factory.h"
#include "folks.h"
+#include <libebook/libebook.h>
+
#include <phonenumbers/phonenumberutil.h>
#include <boost/locale.hpp>
#include <boost/lexical_cast.hpp>
+SE_GLIB_TYPE(EBookQuery, e_book_query)
+
SE_BEGIN_CXX
/**
@@ -330,6 +334,18 @@ public:
return false;
}
+ virtual std::string getEBookFilter() const
+ {
+ // A suffix match with a limited number of digits is most
+ // likely to find the right contacts.
+ size_t len = std::min((size_t)4, m_tel.size());
+ EBookQueryCXX query(e_book_query_field_test(E_CONTACT_TEL, E_BOOK_QUERY_ENDS_WITH,
+ m_tel.substr(m_tel.size() - len, len).c_str()),
+ false);
+ PlainGStr filter(e_book_query_to_string(query.get()));
+ return filter.get();
+ }
+
private:
const i18n::phonenumbers::PhoneNumberUtil &m_phoneNumberUtil;
std::string m_country;
diff --git a/src/dbus/server/pim/manager.cpp b/src/dbus/server/pim/manager.cpp
index 98a37fa9..0b3238fa 100644
--- a/src/dbus/server/pim/manager.cpp
+++ b/src/dbus/server/pim/manager.cpp
@@ -22,6 +22,8 @@
#include "persona-details.h"
#include "filtered-view.h"
#include "full-view.h"
+#include "merge-view.h"
+#include "edsf-view.h"
#include "../resource.h"
#include "../client.h"
#include "../session.h"
@@ -616,12 +618,59 @@ GDBusCXX::DBusObject_t Manager::search(const GDBusCXX::Caller_t &ID,
boost::shared_ptr<IndividualView> view;
view = m_folks->getMainView();
+ bool quiescent = view->isQuiescent();
+ std::string ebookFilter;
if (!filter.empty()) {
boost::shared_ptr<IndividualFilter> individualFilter = m_locale->createFilter(filter);
+ ebookFilter = individualFilter->getEBookFilter();
+ if (quiescent) {
+ // Don't search via EDS directly because the unified
+ // address book is ready.
+ ebookFilter.clear();
+ }
view = FilteredView::create(view, individualFilter);
view->setName(StringPrintf("filtered view%u", ViewResource::getNextViewNumber()));
}
+ SE_LOG_DEBUG(NULL, NULL, "preparing %s: EDS search term is '%s', active address books %s",
+ view->getName(),
+ ebookFilter.c_str(),
+ boost::join(m_enabledEBooks, " ").c_str());
+ if (!ebookFilter.empty() && !m_enabledEBooks.empty()) {
+ // Set up direct searching in all active address books.
+ // These searches are done once, so don't bother to deal
+ // with future changes to the active address books or
+ // the sort order.
+ MergeView::Searches searches;
+ searches.reserve(m_enabledEBooks.size());
+ boost::shared_ptr<IndividualCompare> compare =
+ m_sortOrder.empty() ?
+ IndividualCompare::defaultCompare() :
+ m_locale->createCompare(m_sortOrder);
+
+ // TODO: use global registry
+ ESourceRegistryCXX registry;
+ GErrorCXX gerror;
+ SYNCEVO_GLIB_CALL_SYNC(registry, gerror,
+ e_source_registry_new,
+ NULL);
+ if (!registry) {
+ gerror.throwError("unable to access databases registry");
+ }
+ BOOST_FOREACH (const std::string &uuid, m_enabledEBooks) {
+ searches.push_back(EDSFView::create(registry,
+ uuid,
+ ebookFilter));
+ searches.back()->setName(StringPrintf("eds view %s %s", uuid.c_str(), ebookFilter.c_str()));
+ }
+ boost::shared_ptr<MergeView> merge(MergeView::create(view,
+ searches,
+ m_locale,
+ compare));
+ merge->setName(StringPrintf("merge view%u", ViewResource::getNextViewNumber()));
+ view = merge;
+ }
+
boost::shared_ptr<ViewResource> viewResource(ViewResource::create(view,
m_locale,
client,
diff --git a/src/dbus/server/pim/merge-view.cpp b/src/dbus/server/pim/merge-view.cpp
new file mode 100644
index 00000000..5cd30ac6
--- /dev/null
+++ b/src/dbus/server/pim/merge-view.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2012 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) version 3.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include "merge-view.h"
+
+#include <syncevo/BoostHelper.h>
+
+SE_BEGIN_CXX
+
+MergeView::MergeView(const boost::shared_ptr<IndividualView> &view,
+ const Searches &searches,
+ const boost::shared_ptr<LocaleFactory> &locale,
+ const boost::shared_ptr<IndividualCompare> &compare) :
+ m_view(view),
+ m_searches(searches),
+ m_locale(locale),
+ m_compare(compare)
+{
+}
+
+void MergeView::init(const boost::shared_ptr<MergeView> &self)
+{
+ m_self = self;
+}
+
+boost::shared_ptr<MergeView> MergeView::create(const boost::shared_ptr<IndividualView> &view,
+ const Searches &searches,
+ const boost::shared_ptr<LocaleFactory> &locale,
+ const boost::shared_ptr<IndividualCompare> &compare)
+{
+ boost::shared_ptr<MergeView> merge(new MergeView(view, searches, locale, compare));
+ merge->init(merge);
+ return merge;
+}
+
+void MergeView::doStart()
+{
+ BOOST_FOREACH (const Searches::value_type &search, m_searches) {
+ search->m_quiescenceSignal.connect(boost::bind(&MergeView::edsDone,
+ m_self,
+ std::string(search->getName())));
+ search->m_addedSignal.connect(boost::bind(&MergeView::addEDSIndividual,
+ m_self,
+ _1));
+ search->start();
+ }
+ m_view->m_quiescenceSignal.connect(boost::bind(&MergeView::viewReady,
+ m_self));
+ m_view->start();
+ if (m_view->isQuiescent()) {
+ // Switch to view directly.
+ viewReady();
+ }
+}
+
+void MergeView::addEDSIndividual(const FolksIndividualCXX &individual) throw ()
+{
+ try {
+ Entries::auto_type data(new IndividualData);
+ data->init(m_compare.get(), m_locale.get(), individual);
+ // Binary search to find insertion point.
+ Entries::iterator it =
+ std::lower_bound(m_entries.begin(),
+ m_entries.end(),
+ *data,
+ IndividualDataCompare(m_compare));
+ size_t index = it - m_entries.begin();
+ it = m_entries.insert(it, data.release());
+ SE_LOG_DEBUG(NULL, NULL, "%s: added at #%ld/%ld", getName(), index, m_entries.size());
+ m_addedSignal(index, *it);
+ } catch (...) {
+ Exception::handle(HANDLE_EXCEPTION_NO_ERROR);
+ }
+}
+
+void MergeView::edsDone(const std::string &uuid) throw ()
+{
+ try {
+ SE_LOG_DEBUG(NULL, NULL, "%s: %s is done", getName(), uuid.c_str());
+ BOOST_FOREACH (const Searches::value_type &search, m_searches) {
+ if (!search->isQuiescent()) {
+ SE_LOG_DEBUG(NULL, NULL, "%s: still waiting for %s", getName(), search->getName());
+ return;
+ }
+ }
+ SE_LOG_DEBUG(NULL, NULL, "%s: all EDS searches done, %s", getName(), m_viewReady ? "folks also done" : "still waiting for folks, send quiescent now");
+ if (!m_viewReady) {
+ // folks is still busy, this may take a while. Therefore
+ // flush current status.
+ //
+ // TODO (?): it would be good to have a way to signal "done
+ // for now, better results coming" to the client. As
+ // things stand at the moment, it might conclude that
+ // incomplete resuls from EDS is all that there is to show
+ // to the user. Not much of a problem, though, if the
+ // quality of those results is good.
+ m_quiescenceSignal();
+ }
+ } catch (...) {
+ Exception::handle(HANDLE_EXCEPTION_NO_ERROR);
+ }
+}
+
+static void GetPersonaUIDs(FolksIndividual *individual, std::set<std::string> &uids)
+{
+ GeeSet *personas = folks_individual_get_personas(individual);
+ BOOST_FOREACH (FolksPersona *persona, GeeCollCXX<FolksPersona *>(personas)) {
+ // Includes backend, address book, and UID inside address book.
+ uids.insert(folks_persona_get_uid(persona));
+ }
+}
+
+static bool SamePersonas(FolksIndividual *a, FolksIndividual *b)
+{
+ std::set<std::string> a_uids, b_uids;
+ GetPersonaUIDs(a, a_uids);
+ GetPersonaUIDs(b, b_uids);
+ if (a_uids.size() == b_uids.size()) {
+ BOOST_FOREACH (const std::string &uid, a_uids) {
+ if (b_uids.find(uid) == b_uids.end()) {
+ break;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+void MergeView::viewReady() throw ()
+{
+ try {
+ if (!m_viewReady) {
+ m_viewReady = true;
+
+ SE_LOG_DEBUG(NULL, NULL, "%s: folks is ready: %d entries from EDS, %d from folks",
+ getName(),
+ (int)m_entries.size(),
+ (int)m_view->size());
+
+ // Change signals which transform the current view into the final one.
+ int index;
+ for (index = 0; index < m_view->size() && index < (int)m_entries.size(); index++) {
+ const IndividualData &oldData = m_entries[index];
+ const IndividualData *newData = m_view->getContact(index);
+ // Minimize changes if old and new data are
+ // identical. Instead of checking all data, assume
+ // that if the underlying contacts are identical, then
+ // so must be the data.
+ if (!SamePersonas(oldData.m_individual, newData->m_individual)) {
+ SE_LOG_DEBUG(NULL, NULL, "%s: entry #%d modified",
+ getName(),
+ index);
+ m_modifiedSignal(index, *newData);
+ }
+ }
+ for (; index < m_view->size(); index++) {
+ const IndividualData *newData = m_view->getContact(index);
+ SE_LOG_DEBUG(NULL, NULL, "%s: entry #%d added",
+ getName(),
+ index);
+ m_addedSignal(index, *newData);
+ }
+ // Index stays the same when removing, because the following
+ // entries get shifted.
+ int removeAt = index;
+ for (; index < (int)m_entries.size(); index++) {
+ const IndividualData &oldData = m_entries[index];
+ SE_LOG_DEBUG(NULL, NULL, "%s: entry #%d removed",
+ getName(),
+ index);
+ m_removedSignal(removeAt, oldData);
+ }
+
+ // Free resources which are no longer needed.
+ // The expectation is that this will abort loading
+ // from EDS.
+ try {
+ m_searches.clear();
+ m_entries.clear();
+ } catch (...) {
+ Exception::handle(HANDLE_EXCEPTION_NO_ERROR);
+ }
+ SE_LOG_DEBUG(NULL, NULL, "%s: switched to folks, quiescent", getName());
+ m_quiescenceSignal();
+ }
+ } catch (...) {
+ Exception::handle(HANDLE_EXCEPTION_NO_ERROR);
+ }
+}
+
+SE_END_CXX
diff --git a/src/dbus/server/pim/merge-view.h b/src/dbus/server/pim/merge-view.h
new file mode 100644
index 00000000..3e405561
--- /dev/null
+++ b/src/dbus/server/pim/merge-view.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2012 Intel Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) version 3.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+/**
+ * Combines results from multiple independent views ("unified address
+ * book light") until the main view is quiescent. Then this view
+ * switches over to mirroring the main view. When switching, it tries
+ * to minimize change signals.
+ *
+ * The independent views don't have to do their own sorting and don't
+ * need store individuals.
+ */
+
+#ifndef INCL_SYNCEVO_DBUS_SERVER_PIM_MERGE_VIEW
+#define INCL_SYNCEVO_DBUS_SERVER_PIM_MERGE_VIEW
+
+#include "view.h"
+
+#include <syncevo/declarations.h>
+SE_BEGIN_CXX
+
+class MergeView : public IndividualView
+{
+ public:
+ typedef std::vector< boost::shared_ptr<StreamingView> > Searches;
+
+ private:
+ boost::weak_ptr<MergeView> m_self;
+ boost::shared_ptr<IndividualView> m_view;
+ Searches m_searches;
+ boost::shared_ptr<LocaleFactory> m_locale;
+ boost::shared_ptr<IndividualCompare> m_compare;
+
+ /**
+ * As soon as this is true, m_entries becomes irrelevant and
+ * MergeView becomes a simple proxy for m_view.
+ */
+ Bool m_viewReady;
+
+ /**
+ * Sorted entries from the simple views.
+ */
+ typedef boost::ptr_vector<IndividualData> Entries;
+ Entries m_entries;
+
+ MergeView(const boost::shared_ptr<IndividualView> &view,
+ const Searches &searches,
+ const boost::shared_ptr<LocaleFactory> &locale,
+ const boost::shared_ptr<IndividualCompare> &compare);
+ void init(const boost::shared_ptr<MergeView> &self);
+
+ void addEDSIndividual(const FolksIndividualCXX &individual) throw ();
+ void edsDone(const std::string &uuid) throw ();
+ void viewReady() throw ();
+
+ public:
+ static boost::shared_ptr<MergeView> create(const boost::shared_ptr<IndividualView> &view,
+ const Searches &searches,
+ const boost::shared_ptr<LocaleFactory> &locale,
+ const boost::shared_ptr<IndividualCompare> &compare);
+
+ virtual bool isQuiescent() const { return m_view->isQuiescent(); }
+ virtual int size() const { return m_viewReady ? m_view->size() : m_entries.size(); }
+ virtual const IndividualData *getContact(int index) {
+ return m_viewReady ? m_view->getContact(index) :
+ (index >= 0 && (size_t)index < m_entries.size()) ? &m_entries[index] :
+ NULL;
+ }
+
+ protected:
+ virtual void doStart();
+};
+
+SE_END_CXX
+
+#endif // INCL_SYNCEVO_DBUS_SERVER_PIM_MERGE_VIEW
diff --git a/src/dbus/server/pim/testpim.py b/src/dbus/server/pim/testpim.py
index f7b87a25..7c41a2b9 100755
--- a/src/dbus/server/pim/testpim.py
+++ b/src/dbus/server/pim/testpim.py
@@ -2775,7 +2775,246 @@ END:VCARD''']):
# might still be loading the contacts, in which case looking up the
# contact would fail.
+ @timeout(60)
+ @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8 SYNCEVOLUTION_PIM_DELAY_FOLKS=5")
+ def testFilterStartup(self):
+ '''TestContacts.testFilterStartup - phone number lookup while folks still loads'''
+ self.setUpView(search=None)
+
+ # Override default sorting.
+ self.assertEqual("last/first", self.manager.GetSortOrder(timeout=self.timeout))
+ self.manager.SetSortOrder("first/last",
+ timeout=self.timeout)
+
+ # Insert new contacts.
+ #
+ # The names are chosen so that sorting by first name and sorting by last name needs to
+ # reverse the list.
+ for i, contact in enumerate([u'''BEGIN:VCARD
+VERSION:3.0
+N:Zoo;Abraham
+NICKNAME:Ace
+TEL:1234
+TEL:56/78
+TEL:+1-800-FOOBAR
+TEL:089/788899
+EMAIL:az@example.com
+END:VCARD''',
+
+u'''BEGIN:VCARD
+VERSION:3.0
+N:Yeah;Benjamin
+TEL:+49-89-788899
+END:VCARD''',
+
+u'''BEGIN:VCARD
+VERSION:3.0
+FN:Charly 'Chárleß' Xing
+N:Xing;Charly
+END:VCARD''']):
+ item = os.path.join(self.contacts, 'contact%d.vcf' % i)
+ output = codecs.open(item, "w", "utf-8")
+ output.write(contact)
+ output.close()
+ logging.log('inserting contacts')
+ out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local'])
+
+ # Now start with a phone number search which must look
+ # directly in EDS because the unified address book is not
+ # ready (delayed via env variable).
+ self.view.search([['phone', '089/788899']])
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.quiescentCount > 0)
+ self.assertEqual(2, len(self.view.contacts))
+ self.view.read(0, 2)
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.haveData(0, 2))
+ self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given'])
+ self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given'])
+
+ # Wait for final results from folks. The same in this case.
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.quiescentCount > 1)
+ self.assertEqual(2, len(self.view.contacts))
+ self.view.read(0, 2)
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.haveData(0, 2))
+ self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given'])
+ self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given'])
+
+ # Nothing changed when folks became active.
+ self.assertEqual([
+ ('added', 0, 2),
+ ('quiescent',),
+ ('quiescent',),
+ ],
+ self.view.events)
+
+ @timeout(60)
+ @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8 SYNCEVOLUTION_PIM_DELAY_FOLKS=5")
+ def testFilterStartupRefine(self):
+ '''TestContacts.testFilterStartupRefine - phone number lookup while folks still loads, with folks finding more contacts'''
+ self.setUpView(search=None)
+
+ # Override default sorting.
+ self.assertEqual("last/first", self.manager.GetSortOrder(timeout=self.timeout))
+ self.manager.SetSortOrder("first/last",
+ timeout=self.timeout)
+
+ # Insert new contacts.
+ #
+ # The names are chosen so that sorting by first name and sorting by last name needs to
+ # reverse the list.
+ for i, contact in enumerate([u'''BEGIN:VCARD
+VERSION:3.0
+N:Zoo;Abraham
+NICKNAME:Ace
+TEL:1234
+TEL:56/78
+TEL:+1-800-FOOBAR
+TEL:089/788899
+EMAIL:az@example.com
+END:VCARD''',
+
+# Extra space, breaks suffix match in EDS.
+# A more intelligent phone number search in EDS
+# will find this again.
+u'''BEGIN:VCARD
+VERSION:3.0
+N:Yeah;Benjamin
+TEL:+49-89-7888 99
+END:VCARD''',
+u'''BEGIN:VCARD
+VERSION:3.0
+FN:Charly 'Chárleß' Xing
+N:Xing;Charly
+END:VCARD''']):
+ item = os.path.join(self.contacts, 'contact%d.vcf' % i)
+ output = codecs.open(item, "w", "utf-8")
+ output.write(contact)
+ output.close()
+ logging.log('inserting contacts')
+ out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + self.uid, 'local'])
+
+ # Now start with a phone number search which must look
+ # directly in EDS because the unified address book is not
+ # ready (delayed via env variable).
+ self.view.search([['phone', '089/788899']])
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.quiescentCount > 0)
+ self.assertEqual(1, len(self.view.contacts))
+ self.view.read(0, 1)
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.haveData(0, 1))
+ self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given'])
+
+ # Wait for final results from folks. Also finds Benjamin.
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.quiescentCount > 1)
+ self.assertEqual(2, len(self.view.contacts))
+ self.view.read(0, 2)
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.haveData(0, 2))
+ self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given'])
+ self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given'])
+
+ # One contact added by folks.
+ self.assertEqual([
+ ('added', 0, 1),
+ ('quiescent',),
+ ('added', 1, 1),
+ ('quiescent',),
+ ],
+ self.view.events)
+
+ @timeout(60)
+ @property("ENV", "LC_TYPE=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 LANG=de_DE.UTF-8 SYNCEVOLUTION_PIM_DELAY_FOLKS=5")
+ def testFilterStartupMany(self):
+ '''TestContacts.testFilterStartupMany - phone number lookup in many address books'''
+ self.setUpView(search=None, peers=['0', '1', '2'])
+
+ # Override default sorting.
+ self.assertEqual("last/first", self.manager.GetSortOrder(timeout=self.timeout))
+ self.manager.SetSortOrder("first/last",
+ timeout=self.timeout)
+
+ # Insert new contacts.
+ #
+ # The names are chosen so that sorting by first name and sorting by last name needs to
+ # reverse the list.
+ for i, contact in enumerate([u'''BEGIN:VCARD
+VERSION:3.0
+N:Zoo;Abraham
+NICKNAME:Ace
+TEL:1234
+TEL:56/78
+TEL:+1-800-FOOBAR
+TEL:089/788899
+EMAIL:az@example.com
+END:VCARD''',
+
+u'''BEGIN:VCARD
+VERSION:3.0
+N:Yeah;Benjamin
+TEL:+49-89-788899
+END:VCARD''',
+
+u'''BEGIN:VCARD
+VERSION:3.0
+FN:Charly 'Chárleß' Xing
+N:Xing;Charly
+END:VCARD''']):
+ item = os.path.join(self.contacts, 'contact.vcf')
+ output = codecs.open(item, "w", "utf-8")
+ output.write(contact)
+ output.close()
+ logging.printf('inserting contact %d', i)
+ uid = self.uidPrefix + str(i)
+ out, err, returncode = self.runCmdline(['--import', self.contacts, '@' + self.managerPrefix + uid, 'local'])
+
+ # Now start with a phone number search which must look
+ # directly in EDS because the unified address book is not
+ # ready (delayed via env variable).
+ self.view.search([['phone', '089/788899']])
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.quiescentCount > 0)
+ self.assertEqual(2, len(self.view.contacts))
+ self.view.read(0, 2)
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.haveData(0, 2))
+ self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given'])
+ self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given'])
+
+ # Wait for final results from folks. The same in this case.
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.quiescentCount > 1)
+ self.assertEqual(2, len(self.view.contacts))
+ self.view.read(0, 2)
+ self.runUntil('phone results',
+ check=lambda: self.assertEqual([], self.view.errors),
+ until=lambda: self.view.haveData(0, 2))
+ self.assertEqual(u'Abraham', self.view.contacts[0]['structured-name']['given'])
+ self.assertEqual(u'Benjamin', self.view.contacts[1]['structured-name']['given'])
+
+ # Nothing changed when folks became active.
+ self.assertEqual([
+ ('added', 0, 2),
+ ('quiescent',),
+ ('quiescent',),
+ ],
+ self.view.events)
if __name__ == '__main__':
xdg = (os.path.join(os.path.abspath('.'), 'temp-testpim', 'config'),
os.path.join(os.path.abspath('.'), 'temp-testpim', 'local', 'cache'))
diff --git a/src/dbus/server/server.am b/src/dbus/server/server.am
index e54de914..39d39864 100644
--- a/src/dbus/server/server.am
+++ b/src/dbus/server/server.am
@@ -65,6 +65,8 @@ src_dbus_server_server_cpp_files += \
src/dbus/server/pim/view.cpp \
src/dbus/server/pim/full-view.cpp \
src/dbus/server/pim/filtered-view.cpp \
+ src/dbus/server/pim/edsf-view.cpp \
+ src/dbus/server/pim/merge-view.cpp \
src/dbus/server/pim/individual-traits.cpp \
src/dbus/server/pim/folks.cpp \
src/dbus/server/pim/manager.cpp