Commit 787f62ee authored by chriadam's avatar chriadam

[qtcontacts-sqlite] Automatically extend known display label groups at...

[qtcontacts-sqlite] Automatically extend known display label groups at runtime. Contributes to JB#44742
parent 905f6b7b
......@@ -1898,7 +1898,7 @@ static bool executeDisplayLabelGroupLocalizationStatements(QSqlDatabase &databas
const QString dlg = cdb->determineDisplayLabelGroup(c);
displayLabelGroups.append(dlg);
displayLabelGroupSortOrders.append(cdb->possibleDisplayLabelGroups().indexOf(dlg) + 1);
displayLabelGroupSortOrders.append(cdb->displayLabelGroupSortValue(dlg));
}
selectQuery.finish();
}
......@@ -2660,6 +2660,60 @@ static QVector<QtContactsSqliteExtensions::DisplayLabelGroupGenerator*> initiali
}
static QVector<QtContactsSqliteExtensions::DisplayLabelGroupGenerator*> s_dlgGenerators = initializeDisplayLabelGroupGenerators();
static qint32 displayLabelGroupSortValue(const QString &group, const QMap<QString, int> &knownDisplayLabelGroups)
{
static const int maxUnicodeCodePointValue = 1114111; // 0x10FFFF
static const int numberGroupSortValue = maxUnicodeCodePointValue + 1;
qint32 retn = -1;
if (!group.isEmpty()) {
retn = group == QStringLiteral("#") ? numberGroupSortValue : knownDisplayLabelGroups.value(group, -1);
if (retn < 0) {
// the group is not a previously-known display label group.
// convert the group to a utf32 code point value.
const QChar first = group.at(0);
if (first.isSurrogate()) {
if (group.size() >= 2) {
const QChar second = group.at(1);
retn = ((first.isHighSurrogate() ? first.unicode() : second.unicode() - 0xD800) * 0x400)
+ (second.isLowSurrogate() ? second.unicode() : first.unicode() - 0xDC00) + 0x10000;
} else {
// cannot calculate the true codepoint without the second character in the surrogate pair.
// assume that it's the very last possible codepoint.
retn = maxUnicodeCodePointValue;
}
} else {
// use the unicode code point value as the sort value.
retn = first.unicode();
// resolve overlap issue by compressing overlapping groups
// into a single subsequent group.
// e.g. in Chinese locale, there may be more than
// 65 default display label groups, and thus the
// letter 'A' (whose unicode value is 65) would overlap.
int lastContiguousSortValue = -1;
for (int sortValue : knownDisplayLabelGroups) {
if (sortValue != (lastContiguousSortValue + 1)) {
break;
}
lastContiguousSortValue = sortValue;
}
// instead of placing into LCSV+1, we place into LCSV+2
// to ensure that ALL overlapping groups are compressed
// into the same group, in order to avoid "first seen
// will sort first" issues (e.g. B < A).
const int compressedSortValue = lastContiguousSortValue + 2;
if (retn < compressedSortValue) {
retn = compressedSortValue;
}
}
}
}
return retn;
}
// Adapted from the inter-process mutex in QMF
// The first user creates the semaphore that all subsequent instances
// attach to. We rely on undo semantics to release locked semaphores
......@@ -2721,8 +2775,9 @@ void ContactsDatabase::Query::reportError(const char *text) const
reportError(QString::fromLatin1(text));
}
ContactsDatabase::ContactsDatabase()
: m_mutex(QMutex::Recursive)
ContactsDatabase::ContactsDatabase(ContactsEngine *engine)
: m_engine(engine)
, m_mutex(QMutex::Recursive)
, m_nonprivileged(false)
, m_localeName(QLocale().name())
, m_defaultGenerator(new DefaultDlgGenerator)
......@@ -2768,21 +2823,38 @@ bool ContactsDatabase::open(const QString &connectionName, bool nonprivileged, b
}
m_dlgGenerators.append(m_defaultGenerator.data());
// and build a "superlist" of possible display label groups
// which defines a total sort ordering for display label groups.
// and build a "superlist" of known display label groups.
const QLocale locale;
QStringList knownDisplayLabelGroups;
for (auto generator : m_dlgGenerators) {
if (generator->validForLocale(locale)) {
const QStringList groups = generator->displayLabelGroups();
for (const QString &group : groups) {
if (!m_possibleDisplayLabelGroups.contains(group)) {
m_possibleDisplayLabelGroups.append(group);
if (!knownDisplayLabelGroups.contains(group)) {
knownDisplayLabelGroups.append(group);
}
}
}
}
m_possibleDisplayLabelGroups.removeAll(QStringLiteral("#"));
m_possibleDisplayLabelGroups.append(QStringLiteral("#"));
knownDisplayLabelGroups.removeAll(QStringLiteral("#"));
knownDisplayLabelGroups.append(QStringLiteral("#"));
// from that list, build a mapping from group to sort priority value,
// based upon the position of each group in the list,
// which defines a total sort ordering for known display label groups.
for (int i = 0; i < knownDisplayLabelGroups.size(); ++i) {
const QString &group(knownDisplayLabelGroups.at(i));
m_knownDisplayLabelGroupsSortValues.insert(
group,
group == QStringLiteral("#")
? ::displayLabelGroupSortValue(group, m_knownDisplayLabelGroupsSortValues)
: i);
}
// XXX TODO: do we need to add groups which currently exist in the database,
// but which aren't currently included in the m_knownDisplayLabelGroupsSortValues?
// I don't think we do, since it only matters on write, and we will update
// the m_knownDisplayLabelGroupsSortValues in determineDisplayLabelGroup() during write...
}
if (m_database.isOpen()) {
......@@ -3263,11 +3335,12 @@ QDateTime ContactsDatabase::fromDateTimeString(const QString &s)
return QDateTime(datepart, timepart, Qt::UTC);
}
QString ContactsDatabase::determineDisplayLabelGroup(const QContact &c) const
QString ContactsDatabase::determineDisplayLabelGroup(const QContact &c)
{
// TODO: read the preferred detail and field from system settings!
// XXXXXXXX TODO: read the preferred detail and field from system settings!
const int preferredDetail = QContactName::Type;
const int preferredField = QContactName::FieldLastName;
QString data;
if (preferredDetail == QContactName::Type) {
// try to use the preferred field data.
......@@ -3315,6 +3388,21 @@ QString ContactsDatabase::determineDisplayLabelGroup(const QContact &c) const
}
}
if (!group.isEmpty() && !m_knownDisplayLabelGroupsSortValues.contains(group)) {
// We are about to write a contact to the database which has a
// display label group which previously was not known / observed.
// Calculate the sort value for the display label group,
// and add it to our map of displayLabelGroup->sortValue.
// Note: this should be thread-safe since we only call this method within writes.
m_knownDisplayLabelGroupsSortValues.insert(
group, ::displayLabelGroupSortValue(
group,
m_knownDisplayLabelGroupsSortValues));
// and invoke engine->_q_displayLabelGroupsChanged() asynchronously.
QMetaObject::invokeMethod(m_engine, "_q_displayLabelGroupsChanged", Qt::QueuedConnection);
}
return group;
}
......@@ -3337,7 +3425,7 @@ QStringList ContactsDatabase::displayLabelGroups() const
QMutexLocker locker(accessMutex());
QSqlQuery selectQuery(m_database);
selectQuery.setForwardOnly(true);
const QString statement = QStringLiteral("SELECT DISTINCT DisplayLabelGroup FROM Contacts");
const QString statement = QStringLiteral("SELECT DISTINCT DisplayLabelGroup FROM Contacts ORDER BY DisplayLabelGroupSortOrder ASC");
if (!selectQuery.prepare(statement)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare distinct display label group selection query: %1\n%2")
.arg(selectQuery.lastError().text())
......@@ -3363,9 +3451,11 @@ QStringList ContactsDatabase::displayLabelGroups() const
return groups;
}
QStringList ContactsDatabase::possibleDisplayLabelGroups() const
int ContactsDatabase::displayLabelGroupSortValue(const QString &group) const
{
return m_possibleDisplayLabelGroups;
static const int maxUnicodeCodePointValue = 1114111; // 0x10FFFF
static const int nullGroupSortValue = maxUnicodeCodePointValue + 1;
return m_knownDisplayLabelGroupsSortValues.value(group, nullGroupSortValue);
}
#include "../extensions/qcontactdeactivated_impl.h"
......
......@@ -47,6 +47,7 @@
#include <QContact>
class ContactsEngine;
class ContactsDatabase
{
public:
......@@ -105,7 +106,7 @@ public:
void reportError(const char *text) const;
};
ContactsDatabase();
ContactsDatabase(ContactsEngine *engine);
~ContactsDatabase();
QMutex *accessMutex() const;
......@@ -152,9 +153,9 @@ public:
bool removeTransientDetails(quint32 contactId);
bool removeTransientDetails(const QList<quint32> &contactIds);
QString determineDisplayLabelGroup(const QContact &c) const;
QString determineDisplayLabelGroup(const QContact &c);
QStringList displayLabelGroups() const;
QStringList possibleDisplayLabelGroups() const;
int displayLabelGroupSortValue(const QString &group) const;
static bool execute(QSqlQuery &query);
static bool executeBatch(QSqlQuery &query, QSqlQuery::BatchExecutionMode mode = QSqlQuery::ValuesAsRows);
......@@ -171,6 +172,7 @@ public:
static QDateTime fromDateTimeString(const QString &s);
private:
ContactsEngine *m_engine;
QSqlDatabase m_database;
ContactsTransientStore m_transientStore;
QMutex m_mutex;
......@@ -180,7 +182,7 @@ private:
QHash<QString, QSqlQuery> m_preparedQueries;
QVector<QtContactsSqliteExtensions::DisplayLabelGroupGenerator*> m_dlgGenerators;
QScopedPointer<QtContactsSqliteExtensions::DisplayLabelGroupGenerator> m_defaultGenerator;
QStringList m_possibleDisplayLabelGroups;
QMap<QString, int> m_knownDisplayLabelGroupsSortValues;
};
#endif
......@@ -69,13 +69,13 @@ class Job
{
public:
struct WriterProxy {
const ContactsEngine &engine;
ContactsEngine &engine;
ContactsDatabase &database;
ContactNotifier &notifier;
ContactReader &reader;
mutable ContactWriter *writer;
WriterProxy(const ContactsEngine &e, ContactsDatabase &db, ContactNotifier &n, ContactReader &r)
WriterProxy(ContactsEngine &e, ContactsDatabase &db, ContactNotifier &n, ContactReader &r)
: engine(e), database(db), notifier(n), reader(r), writer(0)
{
}
......@@ -518,6 +518,7 @@ public:
JobThread(ContactsEngine *engine, const QString &databaseUuid, bool nonprivileged, bool autoTest)
: m_currentJob(0)
, m_engine(engine)
, m_database(engine)
, m_databaseUuid(databaseUuid)
, m_updatePending(false)
, m_running(false)
......@@ -1174,7 +1175,7 @@ QList<QContactType::TypeValues> ContactsEngine::supportedContactTypes() const
return QList<QContactType::TypeValues>() << QContactType::TypeContact;
}
void ContactsEngine::regenerateDisplayLabel(QContact &contact) const
void ContactsEngine::regenerateDisplayLabel(QContact &contact)
{
QContactManager::Error displayLabelError = QContactManager::NoError;
const QString label = synthesizedDisplayLabel(contact, &displayLabelError);
......@@ -1412,9 +1413,9 @@ void ContactsEngine::_q_syncContactsChanged(const QStringList &syncTargets)
emit syncContactsChanged(syncTargets);
}
void ContactsEngine::_q_displayLabelGroupsChanged(const QStringList &groups)
void ContactsEngine::_q_displayLabelGroupsChanged()
{
emit displayLabelGroupsChanged(groups);
emit displayLabelGroupsChanged(displayLabelGroups());
}
void ContactsEngine::_q_contactsRemoved(const QVector<quint32> &contactIds)
......@@ -1443,7 +1444,7 @@ ContactsDatabase &ContactsEngine::database()
QString dbId(QStringLiteral("qtcontacts-sqlite%1-%2"));
dbId = dbId.arg(m_autoTest ? QStringLiteral("-test") : QString()).arg(databaseUuid());
m_database.reset(new ContactsDatabase);
m_database.reset(new ContactsDatabase(this));
if (!m_database->open(dbId, m_nonprivileged, m_autoTest, true)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Unable to open synchronous engine database connection"));
}
......
......@@ -132,7 +132,7 @@ public:
bool isRelationshipTypeSupported(const QString &relationshipType, QContactType::TypeValues contactType) const override;
QList<QContactType::TypeValues> supportedContactTypes() const override;
void regenerateDisplayLabel(QContact &contact) const;
void regenerateDisplayLabel(QContact &contact);
bool fetchSyncContacts(const QString &syncTarget, const QDateTime &lastSync, const QList<QContactId> &exportedIds,
QList<QContact> *syncContacts, QList<QContact> *addedContacts, QList<QContactId> *deletedContactIds,
......@@ -176,7 +176,7 @@ private slots:
void _q_selfContactIdChanged(quint32,quint32);
void _q_relationshipsAdded(const QVector<quint32> &contactIds);
void _q_relationshipsRemoved(const QVector<quint32> &contactIds);
void _q_displayLabelGroupsChanged(const QStringList &groups);
void _q_displayLabelGroupsChanged();
private:
QString databaseUuid();
......
......@@ -139,7 +139,7 @@ static const QString matchEmailAddressesTable(QString::fromLatin1("matchEmailAdd
static const QString matchPhoneNumbersTable(QString::fromLatin1("matchPhoneNumbers"));
static const QString matchOnlineAccountsTable(QString::fromLatin1("matchOnlineAccounts"));
ContactWriter::ContactWriter(const ContactsEngine &engine, ContactsDatabase &database, ContactNotifier *notifier, ContactReader *reader)
ContactWriter::ContactWriter(ContactsEngine &engine, ContactsDatabase &database, ContactNotifier *notifier, ContactReader *reader)
: m_engine(engine)
, m_database(database)
, m_notifier(notifier)
......@@ -5625,7 +5625,7 @@ ContactsDatabase::Query ContactWriter::bindContactDetails(const QContact &contac
query.bindValue(0, displayLabel);
const QString displayLabelGroup = m_database.determineDisplayLabelGroup(contact);
query.bindValue(1, displayLabelGroup);
const int displayLabelGroupSortOrder = m_database.possibleDisplayLabelGroups().indexOf(displayLabelGroup) + 1;
const int displayLabelGroupSortOrder = m_database.displayLabelGroupSortValue(displayLabelGroup);
query.bindValue(2, displayLabelGroupSortOrder);
query.bindValue(3, firstName);
......
......@@ -72,7 +72,7 @@ class ContactWriter
public:
typedef QList<QContactDetail::DetailType> DetailList;
ContactWriter(const ContactsEngine &engine, ContactsDatabase &database, ContactNotifier *notifier, ContactReader *reader);
ContactWriter(ContactsEngine &engine, ContactsDatabase &database, ContactNotifier *notifier, ContactReader *reader);
~ContactWriter();
QContactManager::Error save(
......@@ -167,7 +167,7 @@ private:
template <typename T> bool removeCommonDetails(quint32 contactId, QContactManager::Error *error);
const ContactsEngine &m_engine;
ContactsEngine &m_engine;
ContactsDatabase &m_database;
ContactNotifier *m_notifier;
ContactReader *m_reader;
......
TARGET = tst_displaylabelgroups
include (../../../common.pri)
# We need access to QtContacts private headers
QT += contacts-private
# And we need access to the ContactManagerEngine header and moc output
INCLUDEPATH += ../../../../src/extensions/
HEADERS += ../../../../src/extensions/contactmanagerengine.h
SOURCES += tst_displaylabelgroups.cpp
......@@ -40,6 +40,9 @@
#include <QContactPhoneNumber>
#include <QContactHobby>
#include <private/qcontactmanager_p.h>
#include "contactmanagerengine.h"
#include "qtcontacts-extensions.h"
QTCONTACTS_USE_NAMESPACE
......@@ -121,6 +124,19 @@ void tst_DisplayLabelGroups::cleanup()
}
}
#define DETERMINE_ACTUAL_ORDER_AND_GROUPS \
do { \
actualOrder.clear(); \
actualGroups.clear(); \
for (const QContact &c : sorted) { \
actualOrder += c.detail<QContactPhoneNumber>().number(); \
if (c.detail<QContactPhoneNumber>().number().isEmpty()) { \
actualOrder += c.detail<QContactHobby>().hobby(); \
} \
actualGroups += c.detail<QContactDisplayLabel>().value(QContactDisplayLabel__FieldLabelGroup).toString(); \
} \
} while (0)
void tst_DisplayLabelGroups::testDisplayLabelGroups()
{
// this test relies on the display label grouping
......@@ -228,13 +244,7 @@ void tst_DisplayLabelGroups::testDisplayLabelGroups()
displayLabelGroupSort.setDetailType(QContactDisplayLabel::Type, QContactDisplayLabel__FieldLabelGroup);
QList<QContact> sorted = m_cm->contacts(displayLabelGroupSort);
QString actualOrder, actualGroups;
for (const QContact &c : sorted) {
actualOrder += c.detail<QContactPhoneNumber>().number();
if (c.detail<QContactPhoneNumber>().number().isEmpty()) {
actualOrder += c.detail<QContactHobby>().hobby();
}
actualGroups += c.detail<QContactDisplayLabel>().value(QContactDisplayLabel__FieldLabelGroup).toString();
}
DETERMINE_ACTUAL_ORDER_AND_GROUPS;
// fixup for potential ambiguity in sort order. 3, 7 and 9 all sort equally.
actualOrder.replace(QChar('7'), QChar('3'));
actualOrder.replace(QChar('9'), QChar('3'));
......@@ -248,15 +258,7 @@ void tst_DisplayLabelGroups::testDisplayLabelGroups()
QContactSortOrder lastNameSort;
lastNameSort.setDetailType(QContactName::Type, QContactName::FieldLastName);
sorted = m_cm->contacts(QList<QContactSortOrder>() << displayLabelGroupSort << lastNameSort);
actualOrder.clear();
actualGroups.clear();
for (const QContact &c : sorted) {
actualOrder += c.detail<QContactPhoneNumber>().number();
if (c.detail<QContactPhoneNumber>().number().isEmpty()) {
actualOrder += c.detail<QContactHobby>().hobby();
}
actualGroups += c.detail<QContactDisplayLabel>().value(QContactDisplayLabel__FieldLabelGroup).toString();
}
DETERMINE_ACTUAL_ORDER_AND_GROUPS;
// fixup for potential ambiguity in sort order. 3 and 9 sort equally.
actualOrder.replace(QChar('9'), QChar('3'));
QCOMPARE(actualOrder, QStringLiteral("615824337"));
......@@ -269,15 +271,7 @@ void tst_DisplayLabelGroups::testDisplayLabelGroups()
QContactSortOrder firstNameSort;
firstNameSort.setDetailType(QContactName::Type, QContactName::FieldFirstName);
sorted = m_cm->contacts(QList<QContactSortOrder>() << displayLabelGroupSort << firstNameSort);
actualOrder.clear();
actualGroups.clear();
for (const QContact &c : sorted) {
actualOrder += c.detail<QContactPhoneNumber>().number();
if (c.detail<QContactPhoneNumber>().number().isEmpty()) {
actualOrder += c.detail<QContactHobby>().hobby();
}
actualGroups += c.detail<QContactDisplayLabel>().value(QContactDisplayLabel__FieldLabelGroup).toString();
}
DETERMINE_ACTUAL_ORDER_AND_GROUPS;
QCOMPARE(actualOrder, QStringLiteral("615824793"));
QCOMPARE(actualGroups, QStringLiteral("Z1345OEEE"));
......@@ -286,17 +280,56 @@ void tst_DisplayLabelGroups::testDisplayLabelGroups()
// except that contact 9's last name causes it to be sorted before contact 7,
// and contact 9 should sort before contact 3 due to the first name.
sorted = m_cm->contacts(QList<QContactSortOrder>() << displayLabelGroupSort << lastNameSort << firstNameSort);
actualOrder.clear();
actualGroups.clear();
for (const QContact &c : sorted) {
actualOrder += c.detail<QContactPhoneNumber>().number();
if (c.detail<QContactPhoneNumber>().number().isEmpty()) {
actualOrder += c.detail<QContactHobby>().hobby();
}
actualGroups += c.detail<QContactDisplayLabel>().value(QContactDisplayLabel__FieldLabelGroup).toString();
}
DETERMINE_ACTUAL_ORDER_AND_GROUPS;
QCOMPARE(actualOrder, QStringLiteral("615824937"));
QCOMPARE(actualGroups, QStringLiteral("Z1345OEEE"));
// Now add a contact which has a special name such that the test
// display label group generator plugin will generate a group
// for it which was previously "unknown".
// We expect that group to be added before '#' but after other groups.
typedef QtContactsSqliteExtensions::ContactManagerEngine EngineType;
EngineType *cme = dynamic_cast<EngineType *>(QContactManagerData::managerData(m_cm)->m_engine);
const QStringList oldContactDisplayLabelGroups = cme->displayLabelGroups();
QSignalSpy dlgcSpy(cme, SIGNAL(displayLabelGroupsChanged(QStringList)));
QContact c10, c11;
QContactName n10, n11;
QContactDisplayLabel d10, d11;
QContactPhoneNumber p10, p11;
n10.setLastName("10ten"); // first letter is digit, should be in #.
n10.setFirstName("Ten");
d10.setLabel("Test J Contact");
p10.setNumber("J");
c10.saveDetail(&n10);
c10.saveDetail(&d10);
c10.saveDetail(&p10);
n11.setLastName("tst_displaylabelgroups_unknown_dlg"); // special case, group &.
n11.setFirstName("Eleven");
d11.setLabel("Test K Contact");
p11.setNumber("K");
c11.saveDetail(&n11);
c11.saveDetail(&d11);
c11.saveDetail(&p11);
QVERIFY(m_cm->saveContact(&c10));
QVERIFY(m_cm->saveContact(&c11));
// ensure that the resultant sort order is expected
sorted = m_cm->contacts(QList<QContactSortOrder>() << displayLabelGroupSort << lastNameSort << firstNameSort);
DETERMINE_ACTUAL_ORDER_AND_GROUPS;
QCOMPARE(actualOrder, QStringLiteral("615824937KJ"));
QCOMPARE(actualGroups, QStringLiteral("Z1345OEEE&#"));
// should have received signal that display label groups have changed.
QTest::qWait(250);
QCOMPARE(dlgcSpy.count(), 1);
QStringList expected(oldContactDisplayLabelGroups);
expected.insert(expected.size() - 1, QStringLiteral("&")); // & group should have been inserted before #.
QList<QVariant> data = dlgcSpy.takeFirst();
QCOMPARE(data.first().value<QStringList>(), expected);
}
QTEST_MAIN(tst_DisplayLabelGroups)
......
......@@ -79,12 +79,23 @@ QStringList TestDlgg::displayLabelGroups() const
QStringLiteral("5"),
QStringLiteral("O"), // sort O before E to test DisplayLabelGroupSortOrder semantics
QStringLiteral("E"),
QStringLiteral("#"),
};
return allGroups;
}
QString TestDlgg::displayLabelGroup(const QString &data) const
{
if (data.size() && data.at(0).isDigit()) {
return QStringLiteral("#"); // default # group for numeric names
}
if (data == QStringLiteral("tst_displaylabelgroups_unknown_dlg")) {
// special case: return a group which is NOT included in the "all groups" above.
// this allows us to test that dynamic group adding works as expected.
return QStringLiteral("&"); // should be sorted before '#' but after every other group.
}
if (data.size() > 5) {
return (data.size() % 2 == 0) ? QStringLiteral("E") : QStringLiteral("O");
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment