Skip to content
This repository has been archived by the owner on Sep 4, 2021. It is now read-only.

Commit

Permalink
[libcontacts] Allow multiple matches for a minimized phone number
Browse files Browse the repository at this point in the history
For resolution, find the contact possessing the number with the longest
match to the input number.

Also, correct the previous terminological misuse of 'normalization'
to mean 'minimization'.
  • Loading branch information
matthewvogt committed Oct 24, 2013
1 parent 6aa4886 commit 7b112bd
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 13 deletions.
133 changes: 121 additions & 12 deletions src/seasidecache.cpp
Expand Up @@ -254,7 +254,7 @@ typedef QPair<QString, QString> StringPair;

StringPair addressPair(const QContactPhoneNumber &phoneNumber)
{
return qMakePair(QString(), SeasideCache::normalizePhoneNumber(phoneNumber.number()));
return qMakePair(QString(), SeasideCache::minimizePhoneNumber(phoneNumber.number()));
}

StringPair addressPair(const QContactEmailAddress &emailAddress)
Expand Down Expand Up @@ -299,6 +299,73 @@ QList<quint32> internalIds(const QList<SeasideCache::ContactIdType> &ids)
return rv;
}

QString::const_iterator firstDtmfChar(QString::const_iterator it, QString::const_iterator end)
{
static const QString dtmfChars(QString::fromLatin1("pPwWxX#*"));

for ( ; it != end; ++it) {
if (dtmfChars.contains(*it))
return it;
}
return end;
}

int matchLength(const QString &lhs, const QString &rhs)
{
if (lhs.isEmpty() || rhs.isEmpty())
return 0;

QString::const_iterator lbegin = lhs.constBegin(), lend = lhs.constEnd();
QString::const_iterator rbegin = rhs.constBegin(), rend = rhs.constEnd();

// Do these numbers contain DTMF elements?
QString::const_iterator ldtmf = firstDtmfChar(lbegin, lend);
QString::const_iterator rdtmf = firstDtmfChar(rbegin, rend);

// Start match length calculation at the last non-DTMF digit
QString::const_iterator lit = ldtmf - 1;
QString::const_iterator rit = rdtmf - 1;

int matchLength = 0;
while (*lit == *rit) {
++matchLength;

--lit;
--rit;
if ((lit == lbegin) || (rit == rbegin)) {
if (*lit == *rit)
++matchLength;
break;
}
}

// Have we got a match?
if ((matchLength >= QtContactsSqliteExtensions::DefaultMaximumPhoneNumberCharacters) ||
((lit == lbegin) || (rit == rbegin))) {
// See if the match continues into the DTMF area
QString::const_iterator lit = ldtmf;
QString::const_iterator rit = rdtmf;
for ( ; (lit != lend) && (rit != rend); ++lit, ++rit) {
if ((*lit).toLower() != (*rit).toLower())
break;
++matchLength;
}
}

return matchLength;
}

int bestPhoneNumberMatchLength(const QContact &contact, const QString &match)
{
int bestMatchLength = 0;

foreach (const QContactPhoneNumber& phone, contact.details<QContactPhoneNumber>()) {
bestMatchLength = qMax(bestMatchLength, matchLength(SeasideCache::normalizePhoneNumber(phone.number()), match));
}

return bestMatchLength;
}

}

SeasideCache *SeasideCache::instancePtr = 0;
Expand Down Expand Up @@ -755,10 +822,43 @@ void SeasideCache::refreshContact(CacheItem *cacheItem)

SeasideCache::CacheItem *SeasideCache::itemByPhoneNumber(const QString &number, bool requireComplete)
{
QString normalizedNumber = normalizePhoneNumber(number);
QHash<QString, quint32>::const_iterator it = instancePtr->m_phoneNumberIds.find(normalizedNumber);
if (it != instancePtr->m_phoneNumberIds.end())
return itemById(*it, requireComplete);
QString minimizedNumber = minimizePhoneNumber(number);

QMultiHash<QString, quint32>::const_iterator it = instancePtr->m_phoneNumberIds.find(minimizedNumber);
QMultiHash<QString, quint32>::const_iterator end = instancePtr->m_phoneNumberIds.constEnd();
if (it != end) {
// How many matches are there for this number?
int matchCount = 1;
QMultiHash<QString, quint32>::const_iterator matchingIt = it + 1;
while ((matchingIt != end) && (matchingIt.key() == minimizedNumber)) {
++matchCount;
++matchingIt;
}
if (matchCount == 1)
return itemById(*it, requireComplete);

QString normalizedNumber = normalizePhoneNumber(number);

// Choose the best match from these contacts
int bestMatchLength = 0;
CacheItem *matchItem = 0;
for ( ; matchCount > 0; ++it, --matchCount) {
if (CacheItem *item = existingItem(*it)) {
int matchLength = bestPhoneNumberMatchLength(item->contact, normalizedNumber);
if (matchLength > bestMatchLength) {
bestMatchLength = matchLength;
matchItem = item;
}
}
}

if (matchItem != 0) {
if (requireComplete) {
ensureCompletion(matchItem);
}
return matchItem;
}
}

return 0;
}
Expand Down Expand Up @@ -1095,16 +1195,25 @@ QUrl SeasideCache::filteredAvatarUrl(const QContact &contact, const QStringList
}

QString SeasideCache::normalizePhoneNumber(const QString &input)
{
const QtContactsSqliteExtensions::NormalizePhoneNumberFlags normalizeFlags(QtContactsSqliteExtensions::KeepPhoneNumberDialString |
QtContactsSqliteExtensions::ValidatePhoneNumber);

// If the number if not valid, return null
return QtContactsSqliteExtensions::normalizePhoneNumber(input, normalizeFlags);
}

QString SeasideCache::minimizePhoneNumber(const QString &input)
{
// TODO: use a configuration variable to make this configurable
static const int maxCharacters = QtContactsSqliteExtensions::DefaultMaximumPhoneNumberCharacters;
const int maxCharacters = QtContactsSqliteExtensions::DefaultMaximumPhoneNumberCharacters;

// If the number if not valid, return null
QString validated(QtContactsSqliteExtensions::normalizePhoneNumber(input, QtContactsSqliteExtensions::ValidatePhoneNumber));
QString validated(normalizePhoneNumber(input));
if (validated.isNull())
return validated;

return QtContactsSqliteExtensions::minimizePhoneNumber(input, maxCharacters);
return QtContactsSqliteExtensions::minimizePhoneNumber(validated, maxCharacters);
}

static QContactFilter filterForMergeCandidates(const QContact &contact)
Expand Down Expand Up @@ -1665,14 +1774,14 @@ bool SeasideCache::updateContactIndexing(const QContact &oldContact, const QCont
resolveUnknownAddresses(address.first, address.second, item);
}

m_phoneNumberIds[address.second] = iid;
m_phoneNumberIds.insert(address.second, iid);
}

// Remove any addresses no longer available for this contact
if (!oldAddresses.isEmpty()) {
modified = true;
foreach (const StringPair &address, oldAddresses) {
m_phoneNumberIds.remove(address.second);
m_phoneNumberIds.remove(address.second, iid);
}
oldAddresses.clear();
}
Expand Down Expand Up @@ -2216,8 +2325,8 @@ void SeasideCache::requestStateChanged(QContactAbstractRequest::State state)
// This address is unknown - keep it for later resolution
ResolveData data(*m_activeResolve);
if (data.first == QString()) {
// Compare this phone number in normalized form
data.compare = normalizePhoneNumber(data.second);
// Compare this phone number in minimized form
data.compare = minimizePhoneNumber(data.second);
} else if (data.second == QString()) {
// Compare this email address in lowercased form
data.compare = data.first.toLower();
Expand Down
3 changes: 2 additions & 1 deletion src/seasidecache.h
Expand Up @@ -342,6 +342,7 @@ class CONTACTCACHE_EXPORT SeasideCache : public QObject
static QUrl filteredAvatarUrl(const QContact &contact, const QStringList &metadataFragments = QStringList());

static QString normalizePhoneNumber(const QString &input);
static QString minimizePhoneNumber(const QString &input);

bool event(QEvent *event);

Expand Down Expand Up @@ -426,7 +427,7 @@ private slots:
QBasicTimer m_expiryTimer;
QBasicTimer m_fetchTimer;
QHash<quint32, CacheItem> m_people;
QHash<QString, quint32> m_phoneNumberIds;
QMultiHash<QString, quint32> m_phoneNumberIds;
QHash<QString, quint32> m_emailAddressIds;
QHash<QPair<QString, QString>, quint32> m_onlineAccountIds;
QHash<ContactIdType, QContact> m_contactsToSave;
Expand Down

0 comments on commit 7b112bd

Please sign in to comment.