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.
This commit is contained in:
Patrick Ohly 2012-11-29 20:53:01 +01:00
parent 970e4c5b5f
commit 8bf486427f
13 changed files with 845 additions and 1 deletions

View File

@ -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)

View File

@ -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
===============================

View File

@ -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

View File

@ -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

View File

@ -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;
};

View File

@ -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();
}

View File

@ -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.

View File

@ -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;

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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'))

View File

@ -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