PIM: pre-compute normalized telephone numbers

Looking up by phone number spends most of its cycles in normalizing of
the phone numbers in the unified address book. Instead of doing that
work over and over again during the search, do it once while loading.

This has implications for several classes:
- IndividualData must be extended to hold the pre-computed values;
  what kind of data can be stored there is currently hard-coded
  in the LocaleFactory base class, to keep it simple and minimize
  memory fragmentation.
- Internal contact changes and access must be based on IndividualData,
  not FolksIndividual.
- FullView must know which LocaleFactory will pre-compute the data.
- Changing contacts may have no effect on sorting, but may lead
  to different pre-computed data.

Looking up a phone number only once does not gain from this change, it
even gets slower (more memory intensive, less cache locality). Only
searching multiple times becomes faster.

Ultimately it would be best to store the normalized strings together
with the telephone number inside EDS when the contact gets
created.
This commit is contained in:
Patrick Ohly 2012-11-26 14:36:45 +01:00
parent d92b237e37
commit d77e5053cc
5 changed files with 148 additions and 103 deletions

View File

@ -82,12 +82,18 @@ public:
}
};
void IndividualData::init(const boost::shared_ptr<IndividualCompare> &compare,
void IndividualData::init(const IndividualCompare *compare,
const LocaleFactory *locale,
FolksIndividual *individual)
{
m_individual = individual;
m_criteria.clear();
compare->createCriteria(individual, m_criteria);
if (compare) {
m_criteria.clear();
compare->createCriteria(individual, m_criteria);
}
if (locale) {
locale->precompute(individual, m_precomputed);
}
}
void IndividualView::start()
@ -109,7 +115,7 @@ void IndividualView::findContact(const std::string &id, int hint, int &index, Fo
int count = size();
// Start searching at the hint.
for (i = hint; i < count; i++) {
individual = getContact(i);
individual = getContact(i)->m_individual;
if (id == folks_individual_get_id(individual.get())) {
index = i;
return;
@ -117,7 +123,7 @@ void IndividualView::findContact(const std::string &id, int hint, int &index, Fo
}
// Finish search before the hint.
for (i = 0; i < hint; i++) {
individual = getContact(i);
individual = getContact(i)->m_individual;
if (id == folks_individual_get_id(individual.get())) {
index = i;
return;
@ -182,8 +188,10 @@ bool IndividualCompare::compare(const Criteria_t &a, const Criteria_t &b) const
return false;
}
FullView::FullView(const FolksIndividualAggregatorCXX &folks) :
FullView::FullView(const FolksIndividualAggregatorCXX &folks,
const boost::shared_ptr<LocaleFactory> &locale) :
m_folks(folks),
m_locale(locale),
m_isQuiescent(false),
// Ensure that there is a sort criteria.
m_compare(new CompareFormattedName())
@ -214,7 +222,8 @@ void FullView::doStart()
individuals.reserve(size);
SE_LOG_DEBUG(NULL, NULL, "starting with %u individuals", size);
BOOST_FOREACH (const Coll::value_type &entry, coll) {
data.init(m_compare, entry.value());
FolksIndividual *individual = entry.value();
data.init(m_compare.get(), m_locale.get(), individual);
individuals.push_back(new IndividualData(data));
}
individuals.sort(IndividualDataCompare(m_compare));
@ -224,7 +233,7 @@ void FullView::doStart()
// Avoid loop if no-one is listening.
if (!m_addedSignal.empty()) {
for (size_t index = 0; index < m_entries.size(); index++) {
m_addedSignal(index, m_entries[index].m_individual);
m_addedSignal(index, m_entries[index]);
}
}
@ -250,9 +259,10 @@ void FullView::doStart()
m_self));
}
boost::shared_ptr<FullView> FullView::create(const FolksIndividualAggregatorCXX &folks)
boost::shared_ptr<FullView> FullView::create(const FolksIndividualAggregatorCXX &folks,
const boost::shared_ptr<LocaleFactory> &locale)
{
boost::shared_ptr<FullView> view(new FullView(folks));
boost::shared_ptr<FullView> view(new FullView(folks, locale));
view->init(view);
return view;
}
@ -314,7 +324,7 @@ void FullView::quiescenceChanged()
}
}
void FullView::doAddIndividual(std::auto_ptr<IndividualData> &data)
void FullView::doAddIndividual(Entries_t::auto_type &data)
{
// Binary search to find insertion point.
Entries_t::iterator it =
@ -323,9 +333,9 @@ void FullView::doAddIndividual(std::auto_ptr<IndividualData> &data)
*data,
IndividualDataCompare(m_compare));
size_t index = it - m_entries.begin();
it = m_entries.insert(it, data);
it = m_entries.insert(it, data.release());
SE_LOG_DEBUG(NULL, NULL, "full view: added at #%ld/%ld", index, m_entries.size());
m_addedSignal(index, it->m_individual);
m_addedSignal(index, *it);
waitForIdle();
// Monitor individual for changes.
@ -338,8 +348,8 @@ void FullView::doAddIndividual(std::auto_ptr<IndividualData> &data)
void FullView::addIndividual(FolksIndividual *individual)
{
std::auto_ptr<IndividualData> data(new IndividualData);
data->init(m_compare, individual);
Entries_t::auto_type data(new IndividualData);
data->init(m_compare.get(), m_locale.get(), individual);
doAddIndividual(data);
}
@ -354,8 +364,8 @@ void FullView::modifyIndividual(FolksIndividual *individual)
if (it->m_individual.get() == individual) {
size_t index = it - m_entries.begin();
std::auto_ptr<IndividualData> data(new IndividualData);
data->init(m_compare, individual);
Entries_t::auto_type data(new IndividualData);
data->init(m_compare.get(), m_locale.get(), individual);
if (data->m_criteria != it->m_criteria &&
((it != m_entries.begin() && !m_compare->compare((it - 1)->m_criteria, data->m_criteria)) ||
(it + 1 != m_entries.end() && !m_compare->compare(data->m_criteria, (it + 1)->m_criteria)))) {
@ -364,12 +374,14 @@ void FullView::modifyIndividual(FolksIndividual *individual)
// as simple as possible, because this is not expected
// to happen often.
SE_LOG_DEBUG(NULL, NULL, "full view: temporarily removed at #%ld/%ld", index, m_entries.size());
m_entries.erase(it);
m_removedSignal(index, individual);
Entries_t::auto_type old = m_entries.release(it);
m_removedSignal(index, *old);
doAddIndividual(data);
} else {
SE_LOG_DEBUG(NULL, NULL, "full view: modified at #%ld/%ld", index, m_entries.size());
m_modifiedSignal(index, individual);
// Use potentially modified pre-computed data.
m_entries.replace(it, data.release());
m_modifiedSignal(index, *it);
waitForIdle();
}
return;
@ -388,8 +400,8 @@ void FullView::removeIndividual(FolksIndividual *individual)
if (it->m_individual.get() == individual) {
size_t index = it - m_entries.begin();
SE_LOG_DEBUG(NULL, NULL, "full view: removed at #%ld/%ld", index, m_entries.size());
m_entries.erase(it);
m_removedSignal(index, individual);
Entries_t::auto_type data = m_entries.release(it);
m_removedSignal(index, *data);
waitForIdle();
return;
}
@ -440,7 +452,7 @@ void FullView::setCompare(const boost::shared_ptr<IndividualCompare> &compare)
// Change sort criteria and sort.
BOOST_FOREACH (IndividualData &data, m_entries) {
data.init(m_compare, data.m_individual);
data.init(m_compare.get(), NULL, data.m_individual);
}
m_entries.sort(IndividualDataCompare(m_compare));
@ -453,7 +465,7 @@ void FullView::setCompare(const boost::shared_ptr<IndividualCompare> &compare)
// where it came from now. The effect is that temporarily
// the same contact might be shown at two different
// indices.
m_modifiedSignal(index, current.m_individual);
m_modifiedSignal(index, current);
}
}
@ -492,7 +504,7 @@ void FilteredView::doStart()
// cause changes to the parent view, otherwise the result will not
// be inconsistent.
for (int index = 0; index < m_parent->size(); index++) {
addIndividual(index, m_parent->getContact(index));
addIndividual(index, *m_parent->getContact(index));
}
// Start listening to signals.
@ -505,13 +517,13 @@ void FilteredView::refineFilter(const boost::shared_ptr<IndividualFilter> &indiv
{
size_t index = 0;
while (index < m_local2parent.size()) {
FolksIndividualCXX individual = m_parent->getContact(index);
if (individualFilter->matches(individual)) {
const IndividualData *data = m_parent->getContact(index);
if (individualFilter->matches(*data)) {
// Still matched, just skip it.
++index;
} else {
// No longer matched, remove it.
m_removedSignal(index, individual);
m_removedSignal(index, *data);
m_local2parent.erase(m_local2parent.begin() + index);
}
}
@ -523,14 +535,9 @@ void FilteredView::refineFilter(const boost::shared_ptr<IndividualFilter> &indiv
}
}
void FilteredView::addIndividual(int parentIndex, FolksIndividual *individual)
void FilteredView::addIndividual(int parentIndex, const IndividualData &data)
{
if (!individual) {
// sanity check
return;
}
if (m_filter->matches(individual)) {
if (m_filter->matches(data)) {
// We can use binary search to find the insertion point.
// Check last entry first, because that is going to be
// very common when adding via doStart().
@ -554,17 +561,12 @@ void FilteredView::addIndividual(int parentIndex, FolksIndividual *individual)
}
SE_LOG_DEBUG(NULL, NULL, "filtered view: added at #%ld/%ld", index, m_local2parent.size());
m_addedSignal(index, individual);
m_addedSignal(index, data);
}
}
void FilteredView::removeIndividual(int parentIndex, FolksIndividual *individual)
void FilteredView::removeIndividual(int parentIndex, const IndividualData &data)
{
if (!individual) {
// sanity check
return;
}
// The entries are sorted. Therefore we can use a binary search
// to find the parentIndex or the first entry after it.
Entries_t::iterator it =
@ -575,7 +577,7 @@ void FilteredView::removeIndividual(int parentIndex, FolksIndividual *individual
size_t index = it - m_local2parent.begin();
SE_LOG_DEBUG(NULL, NULL, "filtered view: removed at #%ld/%ld", index, m_local2parent.size());
it = m_local2parent.erase(it);
m_removedSignal(index, individual);
m_removedSignal(index, data);
}
// Now reduce the index in our mapping for all following entries.
@ -587,43 +589,39 @@ void FilteredView::removeIndividual(int parentIndex, FolksIndividual *individual
}
}
void FilteredView::modifyIndividual(int parentIndex, FolksIndividual *individual)
void FilteredView::modifyIndividual(int parentIndex, const IndividualData &data)
{
if (!individual) {
// sanity check
return;
}
Entries_t::iterator it =
std::lower_bound(m_local2parent.begin(),
m_local2parent.end(),
parentIndex);
bool matches = m_filter->matches(individual);
bool matches = m_filter->matches(data);
if (it != m_local2parent.end() && *it == parentIndex) {
// Was matched before the change.
size_t index = it - m_local2parent.begin();
if (matches) {
// Still matched, merely pass on modification signal.
SE_LOG_DEBUG(NULL, NULL, "filtered view: modified at #%ld/%ld", index, m_local2parent.size());
m_modifiedSignal(index, individual);
m_modifiedSignal(index, data);
} else {
// Removed.
SE_LOG_DEBUG(NULL, NULL, "filtered view: removed at #%ld/%ld due to modification", index, m_local2parent.size());
m_local2parent.erase(it);
m_removedSignal(index, individual);
m_removedSignal(index, data);
}
} else if (matches) {
// Was not matched before and is matched now => add it.
size_t index = it - m_local2parent.begin();
m_local2parent.insert(it, parentIndex);
SE_LOG_DEBUG(NULL, NULL, "filtered view: added at #%ld/%ld due to modification", index, m_local2parent.size());
m_addedSignal(index, individual);
m_addedSignal(index, data);
} else {
// Neither matched before nor now => nothing changed.
}
}
IndividualAggregator::IndividualAggregator() :
IndividualAggregator::IndividualAggregator(const boost::shared_ptr<LocaleFactory> &locale) :
m_locale(locale),
m_databases(gee_hash_set_new(G_TYPE_STRING, (GBoxedCopyFunc) g_strdup, g_free, NULL, NULL), false)
{
}
@ -670,9 +668,9 @@ void IndividualAggregator::init(boost::shared_ptr<IndividualAggregator> &self)
FolksIndividualAggregatorCXX::steal(folks_individual_aggregator_new_with_backend_store(m_backendStore));
}
boost::shared_ptr<IndividualAggregator> IndividualAggregator::create()
boost::shared_ptr<IndividualAggregator> IndividualAggregator::create(const boost::shared_ptr<LocaleFactory> &locale)
{
boost::shared_ptr<IndividualAggregator> aggregator(new IndividualAggregator);
boost::shared_ptr<IndividualAggregator> aggregator(new IndividualAggregator(locale));
aggregator->init(aggregator);
return aggregator;
}
@ -756,7 +754,7 @@ void IndividualAggregator::setCompare(const boost::shared_ptr<IndividualCompare>
void IndividualAggregator::start()
{
if (!m_view) {
m_view = FullView::create(m_folks);
m_view = FullView::create(m_folks, m_locale);
if (m_compare) {
m_view->setCompare(m_compare);
}
@ -1163,9 +1161,9 @@ private:
static void individualSignal(std::ostringstream &out,
const char *action,
int index,
FolksIndividual *individual) {
const IndividualData &data) {
out << action << ": " << index << " " <<
folks_name_details_get_full_name(FOLKS_NAME_DETAILS(individual)) <<
folks_name_details_get_full_name(FOLKS_NAME_DETAILS(data.m_individual.get())) <<
std::endl;
}

View File

@ -30,6 +30,7 @@
#include <folks/folks.h>
#include "locale-factory.h"
#include "../dbus-callbacks.h"
#include "../timeout.h"
@ -105,19 +106,22 @@ class IndividualCompare
};
/**
* A FolksIndividual plus its sort criteria.
* A FolksIndividual plus its sort criteria and search cache.
*/
struct IndividualData
{
/**
* Sets all members to match the given individual, using the
* compare instance to compute values.
* compare instance to compute values. Both compare and locale may
* be NULL.
*/
void init(const boost::shared_ptr<IndividualCompare> &compare,
void init(const IndividualCompare *compare,
const LocaleFactory *locale,
FolksIndividual *individual);
FolksIndividualCXX m_individual;
IndividualCompare::Criteria_t m_criteria;
LocaleFactory::Precomputed m_precomputed;
};
/**
@ -151,7 +155,7 @@ class IndividualFilter
virtual ~IndividualFilter() {}
/** true if the contact matches the filter */
virtual bool matches(FolksIndividual *individual) const = 0;
virtual bool matches(const IndividualData &data) const = 0;
};
class IndividualAggregator;
@ -167,7 +171,7 @@ class IndividualView
Bool m_started;
public:
typedef boost::signals2::signal<void (int, FolksIndividual *)> ChangeSignal_t;
typedef boost::signals2::signal<void (int, const IndividualData &)> ChangeSignal_t;
typedef boost::signals2::signal<void (void)> QuiescenceSignal_t;
/**
@ -230,7 +234,7 @@ class IndividualView
virtual void readContacts(const std::vector<std::string> &ids, Contacts &contacts);
/** returns access to one individual or an empty pointer if outside of the current range */
virtual FolksIndividualCXX getContact(int index) = 0;
virtual const IndividualData *getContact(int index) = 0;
protected:
/**
@ -248,6 +252,7 @@ class IndividualView
class FullView : public IndividualView
{
FolksIndividualAggregatorCXX m_folks;
boost::shared_ptr<LocaleFactory> m_locale;
bool m_isQuiescent;
boost::weak_ptr<FullView> m_self;
Timeout m_waitForIdle;
@ -264,7 +269,8 @@ class FullView : public IndividualView
*/
boost::shared_ptr<IndividualCompare> m_compare;
FullView(const FolksIndividualAggregatorCXX &folks);
FullView(const FolksIndividualAggregatorCXX &folks,
const boost::shared_ptr<LocaleFactory> &locale);
void init(const boost::shared_ptr<FullView> &self);
/**
@ -282,14 +288,14 @@ class FullView : public IndividualView
* Adds the new individual to m_entries, transfers ownership
* (data == NULL afterwards).
*/
void doAddIndividual(std::auto_ptr<IndividualData> &data);
void doAddIndividual(Entries_t::auto_type &data);
public:
/**
* @param folks the aggregator to use, may be empty for testing
* @param folks the aggregator to use
*/
static boost::shared_ptr<FullView> create(const FolksIndividualAggregatorCXX &folks =
FolksIndividualAggregatorCXX());
static boost::shared_ptr<FullView> create(const FolksIndividualAggregatorCXX &folks,
const boost::shared_ptr<LocaleFactory> &locale);
/** FolksIndividualAggregator "individuals-changed" slot */
void individualsChanged(GeeSet *added,
@ -349,7 +355,7 @@ class FullView : public IndividualView
// from IndividualView
virtual void doStart();
virtual int size() const { return (int)m_entries.size(); }
virtual FolksIndividualCXX getContact(int index) { return (index >= 0 && (unsigned)index < m_entries.size()) ? m_entries[index].m_individual : FolksIndividualCXX(); }
virtual const IndividualData *getContact(int index) { return (index >= 0 && (unsigned)index < m_entries.size()) ? &m_entries[index] : NULL; }
};
/**
@ -392,23 +398,23 @@ class FilteredView : public IndividualView
* Add a FolksIndividual if it matches the filter. Tracking of
* changes to individuals is done in parent view.
*/
void addIndividual(int parentIndex, FolksIndividual *individual);
void addIndividual(int parentIndex, const IndividualData &data);
/**
* Removes a FolksIndividual. Might not have been added at all.
*/
void removeIndividual(int parentIndex, FolksIndividual *individual);
void removeIndividual(int parentIndex, const IndividualData &data);
/**
* Check whether a changed individual still belongs into the view.
*/
void modifyIndividual(int parentIndex, FolksIndividual *individual);
void modifyIndividual(int parentIndex, const IndividualData &data);
// from IndividualView
virtual void doStart();
virtual void refineFilter(const boost::shared_ptr<IndividualFilter> &individualFilter);
virtual int size() const { return (int)m_local2parent.size(); }
virtual FolksIndividualCXX getContact(int index) { return (index >= 0 && (unsigned)index < m_local2parent.size()) ? m_parent->getContact(m_local2parent[index]) : FolksIndividualCXX(); }
virtual const IndividualData *getContact(int index) { return (index >= 0 && (unsigned)index < m_local2parent.size()) ? m_parent->getContact(m_local2parent[index]) : NULL; }
};
/**
@ -420,6 +426,7 @@ class IndividualAggregator
/** empty when not started yet */
boost::shared_ptr<FullView> m_view;
boost::shared_ptr<IndividualCompare> m_compare;
boost::shared_ptr<LocaleFactory> m_locale;
boost::weak_ptr<IndividualAggregator> m_self;
FolksIndividualAggregatorCXX m_folks;
FolksBackendStoreCXX m_backendStore;
@ -446,7 +453,7 @@ class IndividualAggregator
/** string representation for debugging */
std::string dumpDatabases();
IndividualAggregator();
IndividualAggregator(const boost::shared_ptr<LocaleFactory> &locale);
void init(boost::shared_ptr<IndividualAggregator> &self);
/**
@ -505,7 +512,7 @@ class IndividualAggregator
* Creates an idle IndividualAggregator. Configure it and
* subscribe to signals, then call start().
*/
static boost::shared_ptr<IndividualAggregator> create();
static boost::shared_ptr<IndividualAggregator> create(const boost::shared_ptr<LocaleFactory> &locale);
/**
* Access to FolksIndividualAggregator which is owned by

View File

@ -216,8 +216,9 @@ public:
return boost::contains(tel, m_searchValueTel);
}
virtual bool matches(FolksIndividual *individual) const
virtual bool matches(const IndividualData &data) const
{
FolksIndividual *individual = data.m_individual.get();
FolksNameDetails *name = FOLKS_NAME_DETAILS(individual);
const char *fullname = folks_name_details_get_full_name(name);
if (containsSearchText(fullname)) {
@ -318,26 +319,11 @@ public:
m_phoneNumberUtil.Format(number, i18n::phonenumbers::PhoneNumberUtil::E164, &m_tel);
}
virtual bool matches(FolksIndividual *individual) const
virtual bool matches(const IndividualData &data) const
{
FolksPhoneDetails *phoneDetails = FOLKS_PHONE_DETAILS(individual);
GeeSet *phones = folks_phone_details_get_phone_numbers(phoneDetails);
BOOST_FOREACH (FolksAbstractFieldDetails *phone, GeeCollCXX<FolksAbstractFieldDetails *>(phones)) {
const gchar *value =
reinterpret_cast<const gchar *>(folks_abstract_field_details_get_value(phone));
if (value) {
i18n::phonenumbers::PhoneNumber number;
i18n::phonenumbers::PhoneNumberUtil::ErrorType error =
m_phoneNumberUtil.Parse(value, m_country, &number);
if (error == i18n::phonenumbers::PhoneNumberUtil::NO_PARSING_ERROR) {
std::string tel;
m_phoneNumberUtil.Format(number, i18n::phonenumbers::PhoneNumberUtil::E164, &tel);
// TODO (?): cache the normalized phone number, perhaps even maintain
// a lookup data structure.
if (boost::starts_with(tel, m_tel)) {
return true;
}
}
BOOST_FOREACH(const std::string &tel, data.m_precomputed.m_phoneNumbers) {
if (boost::starts_with(tel, m_tel)) {
return true;
}
}
return false;
@ -351,11 +337,15 @@ private:
class LocaleFactoryBoost : public LocaleFactory
{
const i18n::phonenumbers::PhoneNumberUtil &m_phoneNumberUtil;
std::locale m_locale;
std::string m_country;
public:
LocaleFactoryBoost() :
m_locale(genLocale())
m_phoneNumberUtil(*i18n::phonenumbers::PhoneNumberUtil::GetInstance()),
m_locale(genLocale()),
m_country(std::use_facet<boost::locale::info>(m_locale).country())
{}
static std::locale genLocale()
@ -428,6 +418,30 @@ public:
return res;
}
virtual void precompute(FolksIndividual *individual, Precomputed &precomputed) const
{
precomputed.m_phoneNumbers.clear();
FolksPhoneDetails *phoneDetails = FOLKS_PHONE_DETAILS(individual);
GeeSet *phones = folks_phone_details_get_phone_numbers(phoneDetails);
precomputed.m_phoneNumbers.reserve(gee_collection_get_size(GEE_COLLECTION(phones)));
BOOST_FOREACH (FolksAbstractFieldDetails *phone, GeeCollCXX<FolksAbstractFieldDetails *>(phones)) {
const gchar *value =
reinterpret_cast<const gchar *>(folks_abstract_field_details_get_value(phone));
if (value) {
i18n::phonenumbers::PhoneNumber number;
// TODO
i18n::phonenumbers::PhoneNumberUtil::ErrorType error =
m_phoneNumberUtil.Parse(value, m_country, &number);
if (error == i18n::phonenumbers::PhoneNumberUtil::NO_PARSING_ERROR) {
std::string tel;
m_phoneNumberUtil.Format(number, i18n::phonenumbers::PhoneNumberUtil::E164, &tel);
precomputed.m_phoneNumbers.push_back(tel);
}
}
}
}
};
boost::shared_ptr<LocaleFactory> LocaleFactory::createFactory()

View File

@ -28,6 +28,8 @@
#include <boost/shared_ptr.hpp>
#include <folks/folks.h>
#include <syncevo/util.h>
#include <syncevo/declarations.h>
@ -36,6 +38,7 @@ SE_BEGIN_CXX
class IndividualCompare;
class IndividualFilter;
/**
* Factory for everything related to the current locale: sorting and
* searching.
@ -75,6 +78,28 @@ class LocaleFactory
* @return a valid instance, must not be NULL
*/
virtual boost::shared_ptr<IndividualFilter> createFilter(const Filter_t &filter) = 0;
/**
* Pre-computed data for a single FolksIndividual which will be needed
* for searching. Strictly speaking, this should be an opaque pointer
* whose content is entirely owned by the implementation of LocaleFactory.
* For the sake of performance and simplicity, we define a struct instead
* which can be embedded inside IndividualData. Leads to better memory
* locality and reduces overall memory consumption/usage.
*/
struct Precomputed
{
/**
* Normalized phone numbers (E164). Contains only + and digits.
* TODO (?): store in more compact format.
*/
std::vector<std::string> m_phoneNumbers;
};
/**
* (Re)set pre-computed data for an individual.
*/
virtual void precompute(FolksIndividual *individual, Precomputed &precomputed) const = 0;
};
SE_END_CXX

View File

@ -123,7 +123,7 @@ void Manager::init()
void Manager::initFolks()
{
m_folks = IndividualAggregator::create();
m_folks = IndividualAggregator::create(m_locale);
}
void Manager::initSorting(const std::string &order)
@ -267,8 +267,9 @@ class ViewResource : public Resource, public GDBusCXX::DBusObjectHelper
* an up-to-date view when the requested data arrives.
*/
void handleChange(const GDBusCXX::DBusClientCall0 &call,
int start, FolksIndividual *individual)
int start, const IndividualData &data)
{
FolksIndividual *individual = data.m_individual.get();
const char *id = folks_individual_get_id(individual);
SE_LOG_DEBUG(NULL, NULL, "handle change %s: %s, #%d, %s = %s",
getPath(),
@ -495,10 +496,10 @@ class ViewResource : public Resource, public GDBusCXX::DBusObjectHelper
if (size) {
std::vector<std::string> ids;
ids.reserve(size);
FolksIndividualCXX individual;
const IndividualData *data;
for (int i = 0; i < size; i++) {
individual = m_view->getContact(i);
ids.push_back(folks_individual_get_id(individual.get()));
data = m_view->getContact(i);
ids.push_back(folks_individual_get_id(data->m_individual.get()));
}
sendChange(m_contactsAdded, 0, ids);
}