Skip to content

Commit

Permalink
[qtcontacts-sqlite] Store display label group to database. Contribute…
Browse files Browse the repository at this point in the history
…s to JB#44742
  • Loading branch information
Chris Adams committed Apr 15, 2019
1 parent 2f621de commit 1db5b4a
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 65 deletions.
68 changes: 46 additions & 22 deletions src/engine/contactreader.cpp
Expand Up @@ -142,7 +142,8 @@ static QVariant dateValue(const QVariant &columnValue)

static const FieldInfo displayLabelFields[] =
{
{ QContactDisplayLabel::FieldLabel, "displayLabel", LocalizedField }
{ QContactDisplayLabel::FieldLabel, "displayLabel", LocalizedField },
{ QContactDisplayLabel__FieldLabelGroup, "displayLabelGroup", LocalizedField }
};

static const FieldInfo nameFields[] =
Expand Down Expand Up @@ -1935,7 +1936,30 @@ QContactManager::Error ContactReader::queryContacts(
QContactManager::Error err = QContactManager::NoError;

const QString idsQueryStatement(QString::fromLatin1(
"SELECT Contacts.* "
"SELECT " // Contacts.*, but order can change due to schema upgrades, so list manually.
"Contacts.contactId, "
"Contacts.displayLabel, "
"Contacts.displayLabelGroup, "
"Contacts.firstName, "
"Contacts.lowerFirstName, "
"Contacts.lastName, "
"Contacts.lowerLastName, "
"Contacts.middleName, "
"Contacts.prefix, "
"Contacts.suffix, "
"Contacts.customLabel, "
"Contacts.syncTarget, "
"Contacts.created, "
"Contacts.modified, "
"Contacts.gender, "
"Contacts.isFavorite, "
"Contacts.hasPhoneNumber, "
"Contacts.hasEmailAddress, "
"Contacts.hasOnlineAccount, "
"Contacts.isOnline, "
"Contacts.isDeactivated, "
"Contacts.isIncidental, "
"Contacts.type "
"FROM temp.%1 "
"CROSS JOIN Contacts ON temp.%1.contactId = Contacts.contactId " // Cross join ensures we scan the temp table first
"ORDER BY temp.%1.rowId ASC").arg(tableName));
Expand Down Expand Up @@ -2117,50 +2141,50 @@ QContactManager::Error ContactReader::queryContacts(
contact.setId(id);

QString persistedDL = contactQuery.value(1).toString();
if (!persistedDL.isEmpty())
ContactsEngine::setContactDisplayLabel(&contact, persistedDL);
QString displayLabelGroup = contactQuery.value(2).toString();
ContactsEngine::setContactDisplayLabel(&contact, persistedDL, displayLabelGroup);

QContactName name;
setValue(&name, QContactName::FieldFirstName , contactQuery.value(2));
setValue(&name, QContactName::FieldFirstName , contactQuery.value(3));
// ignore lowerFirstName
setValue(&name, QContactName::FieldLastName , contactQuery.value(4));
setValue(&name, QContactName::FieldLastName , contactQuery.value(5));
// ignore lowerLastName
setValue(&name, QContactName::FieldMiddleName , contactQuery.value(6));
setValue(&name, QContactName::FieldPrefix , contactQuery.value(7));
setValue(&name, QContactName::FieldSuffix , contactQuery.value(8));
setValue(&name, QContactName__FieldCustomLabel, contactQuery.value(9));
setValue(&name, QContactName::FieldMiddleName , contactQuery.value(7));
setValue(&name, QContactName::FieldPrefix , contactQuery.value(8));
setValue(&name, QContactName::FieldSuffix , contactQuery.value(9));
setValue(&name, QContactName__FieldCustomLabel, contactQuery.value(10));
if (!name.isEmpty())
contact.saveDetail(&name);

const QString syncTarget(contactQuery.value(10).toString());
const QString syncTarget(contactQuery.value(11).toString());

QContactSyncTarget starget;
setValue(&starget, QContactSyncTarget::FieldSyncTarget, syncTarget);
if (!starget.isEmpty())
contact.saveDetail(&starget);

QContactTimestamp timestamp;
setValue(&timestamp, QContactTimestamp::FieldCreationTimestamp , ContactsDatabase::fromDateTimeString(contactQuery.value(11).toString()));
setValue(&timestamp, QContactTimestamp::FieldModificationTimestamp, ContactsDatabase::fromDateTimeString(contactQuery.value(12).toString()));
setValue(&timestamp, QContactTimestamp::FieldCreationTimestamp , ContactsDatabase::fromDateTimeString(contactQuery.value(12).toString()));
setValue(&timestamp, QContactTimestamp::FieldModificationTimestamp, ContactsDatabase::fromDateTimeString(contactQuery.value(13).toString()));

QContactGender gender;
// Gender is an enum in qtpim
QString genderText = contactQuery.value(13).toString();
QString genderText = contactQuery.value(14).toString();
gender.setGender(static_cast<QContactGender::GenderField>(genderText.toInt()));
contact.saveDetail(&gender);

QContactFavorite favorite;
setValue(&favorite, QContactFavorite::FieldFavorite, contactQuery.value(14).toBool());
setValue(&favorite, QContactFavorite::FieldFavorite, contactQuery.value(15).toBool());
if (!favorite.isEmpty())
contact.saveDetail(&favorite);

QContactStatusFlags flags;
flags.setFlag(QContactStatusFlags::HasPhoneNumber, contactQuery.value(15).toBool());
flags.setFlag(QContactStatusFlags::HasEmailAddress, contactQuery.value(16).toBool());
flags.setFlag(QContactStatusFlags::HasOnlineAccount, contactQuery.value(17).toBool());
flags.setFlag(QContactStatusFlags::IsOnline, contactQuery.value(18).toBool());
flags.setFlag(QContactStatusFlags::IsDeactivated, contactQuery.value(19).toBool());
flags.setFlag(QContactStatusFlags::IsIncidental, contactQuery.value(20).toBool());
flags.setFlag(QContactStatusFlags::HasPhoneNumber, contactQuery.value(16).toBool());
flags.setFlag(QContactStatusFlags::HasEmailAddress, contactQuery.value(17).toBool());
flags.setFlag(QContactStatusFlags::HasOnlineAccount, contactQuery.value(18).toBool());
flags.setFlag(QContactStatusFlags::IsOnline, contactQuery.value(19).toBool());
flags.setFlag(QContactStatusFlags::IsDeactivated, contactQuery.value(20).toBool());
flags.setFlag(QContactStatusFlags::IsIncidental, contactQuery.value(21).toBool());

if (flags.testFlag(QContactStatusFlags::IsDeactivated)) {
QContactDeactivated deactivated;
Expand All @@ -2171,7 +2195,7 @@ QContactManager::Error ContactReader::queryContacts(
contact.saveDetail(&incidental);
}

int contactType = contactQuery.value(21).toInt();
int contactType = contactQuery.value(22).toInt();
QContactType typeDetail = contact.detail<QContactType>();
typeDetail.setType(static_cast<QContactType::TypeValues>(contactType));
contact.saveDetail(&typeDetail);
Expand Down
107 changes: 106 additions & 1 deletion src/engine/contactsdatabase.cpp
Expand Up @@ -63,6 +63,7 @@ static const char *createContactsTable =
"\n CREATE TABLE Contacts ("
"\n contactId INTEGER PRIMARY KEY ASC AUTOINCREMENT,"
"\n displayLabel TEXT,"
"\n displayLabelGroup TEXT," // Don't specify `COLLATE localeCollation` as a change in locale would be equivalent to a database corruption
"\n firstName TEXT,"
"\n lowerFirstName TEXT,"
"\n lastName TEXT,"
Expand Down Expand Up @@ -1286,6 +1287,10 @@ static const char *upgradeVersion16[] = {
"PRAGMA user_version=17",
0 // NULL-terminated
};
static const char *upgradeVersion17[] = {
"PRAGMA user_version=18",
0 // NULL-terminated
};

typedef bool (*UpgradeFunction)(QSqlDatabase &database);

Expand Down Expand Up @@ -1679,6 +1684,81 @@ static bool updateStorageTypes(QSqlDatabase &database)
return true;
}

static bool addDisplayLabelGroup(QSqlDatabase &database)
{
// add the display label group (e.g. ribbon group / name bucket) column
{
QSqlQuery alterQuery(database);
const QString statement = QStringLiteral("ALTER TABLE Contacts ADD COLUMN displayLabelGroup TEXT");
if (!alterQuery.prepare(statement)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare add display label group column query: %1\n%2")
.arg(alterQuery.lastError().text())
.arg(statement));
return false;
}
if (!alterQuery.exec()) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to add display label group column: %1\n%2")
.arg(alterQuery.lastError().text())
.arg(statement));
return false;
}
alterQuery.finish();
}

// for every single contact in our database, read the data required to generate the display label group data.
QVariantList contactIds;
QVariantList displayLabelGroups;
{
QSqlQuery selectQuery(database);
selectQuery.setForwardOnly(true);
const QString statement = QStringLiteral("SELECT contactId, firstName, lastName, displayLabel FROM Contacts");
if (!selectQuery.prepare(statement)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare display label groups data selection query: %1\n%2")
.arg(selectQuery.lastError().text())
.arg(statement));
return false;
}
if (!selectQuery.exec()) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to select display label groups data: %1\n%2")
.arg(selectQuery.lastError().text())
.arg(statement));
return false;
}
while (selectQuery.next()) {
const quint32 dbId = selectQuery.value(0).toUInt();
const QString firstName = selectQuery.value(1).toString();
const QString lastName = selectQuery.value(2).toString();
const QString displayLabel = selectQuery.value(3).toString();
contactIds.append(dbId);
displayLabelGroups.append(ContactsDatabase::determineDisplayLabelGroup(firstName, lastName, displayLabel));
}
selectQuery.finish();
}

// now write the generated data back to the database.
{
QSqlQuery updateQuery(database);
const QString statement = QStringLiteral("UPDATE Contacts SET displayLabelGroup = ? WHERE contactId = ?");
if (!updateQuery.prepare(statement)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare update display label groups query: %1\n%2")
.arg(updateQuery.lastError().text())
.arg(statement));
return false;
}
updateQuery.addBindValue(displayLabelGroups);
updateQuery.addBindValue(contactIds);
if (!updateQuery.execBatch()) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to update display label groups: %1\n%2")
.arg(updateQuery.lastError().text())
.arg(statement));
return false;
}
updateQuery.finish();
}

return true;
}

struct UpgradeOperation {
UpgradeFunction fn;
const char **statements;
Expand All @@ -1702,9 +1782,10 @@ static UpgradeOperation upgradeVersions[] = {
{ 0, upgradeVersion14 },
{ 0, upgradeVersion15 },
{ updateStorageTypes, upgradeVersion16 },
{ addDisplayLabelGroup, upgradeVersion17 },
};

static const int currentSchemaVersion = 17;
static const int currentSchemaVersion = 18;

static bool execute(QSqlDatabase &database, const QString &statement)
{
Expand Down Expand Up @@ -3017,6 +3098,30 @@ QDateTime ContactsDatabase::fromDateTimeString(const QString &s)
return QDateTime(datepart, timepart, Qt::UTC);
}

QString ContactsDatabase::determineDisplayLabelGroup(const QString &firstName, const QString &lastName, const QString &displayLabel)
{
// XXX TODO!
// This is a hacky example only.
// In reality, we need to load plugins which do this properly for e.g. Chinese etc also.
// And we need to check the system settings to determine if first or last name should be preferred.

const QString preferred =
!lastName.trimmed().isEmpty() ? lastName :
!firstName.trimmed().isEmpty() ? firstName : displayLabel;

const QChar firstChar = preferred.trimmed().at(0).toUpper();

if (firstChar >= QChar('A') && firstChar <= QChar('Z')) {
return QString(firstChar);
}

if (firstChar.isDigit()) {
return QString(QChar('#'));
}

return QString(QChar('!'));
}

#include "../extensions/qcontactdeactivated_impl.h"
#include "../extensions/qcontactincidental_impl.h"
#include "../extensions/qcontactoriginmetadata_impl.h"
Expand Down
2 changes: 2 additions & 0 deletions src/engine/contactsdatabase.h
Expand Up @@ -162,6 +162,8 @@ class ContactsDatabase
// Output is UTC
static QDateTime fromDateTimeString(const QString &s);

static QString determineDisplayLabelGroup(const QString &firstName, const QString &lastName, const QString &displayLabel);

private:
QSqlDatabase m_database;
ContactsTransientStore m_transientStore;
Expand Down
26 changes: 21 additions & 5 deletions src/engine/contactsengine.cpp
Expand Up @@ -1176,13 +1176,16 @@ QList<QContactType::TypeValues> ContactsEngine::supportedContactTypes() const
void ContactsEngine::regenerateDisplayLabel(QContact &contact) const
{
QContactManager::Error displayLabelError = QContactManager::NoError;
QString label = synthesizedDisplayLabel(contact, &displayLabelError);
const QString label = synthesizedDisplayLabel(contact, &displayLabelError);
if (displayLabelError != QContactManager::NoError) {
QTCONTACTS_SQLITE_DEBUG(QString::fromLatin1("Unable to regenerate displayLabel for contact: %1").arg(ContactId::toString(contact)));
return;
}

setContactDisplayLabel(&contact, label);
const QString group = ContactsDatabase::determineDisplayLabelGroup(contact.detail<QContactName>().firstName(),
contact.detail<QContactName>().lastName(),
label);
setContactDisplayLabel(&contact, label, group);
}

bool ContactsEngine::fetchSyncContacts(const QString &syncTarget, const QDateTime &lastSync, const QList<QContactId> &exportedIds,
Expand Down Expand Up @@ -1282,11 +1285,24 @@ bool ContactsEngine::removeOOB(const QString &scope)
return writer()->removeOOB(scope, QStringList());
}

bool ContactsEngine::setContactDisplayLabel(QContact *contact, const QString &label)
bool ContactsEngine::setContactDisplayLabel(QContact *contact, const QString &label, const QString &group)
{
QContactDisplayLabel detail(contact->detail<QContactDisplayLabel>());
detail.setLabel(label);
return contact->saveDetail(&detail);
bool needSave = false;
if (!label.trimmed().isEmpty()) {
detail.setLabel(label);
needSave = true;
}
if (!group.trimmed().isEmpty()) {
detail.setValue(QContactDisplayLabel__FieldLabelGroup, group);
needSave = true;
}

if (needSave) {
return contact->saveDetail(&detail);
}

return true;
}

QString ContactsEngine::normalizedPhoneNumber(const QString &input)
Expand Down
2 changes: 1 addition & 1 deletion src/engine/contactsengine.h
Expand Up @@ -159,7 +159,7 @@ class ContactsEngine : public QtContactsSqliteExtensions::ContactManagerEngine
bool removeOOB(const QString &scope, const QStringList &keys);
bool removeOOB(const QString &scope);

static bool setContactDisplayLabel(QContact *contact, const QString &label);
static bool setContactDisplayLabel(QContact *contact, const QString &label, const QString &group);

static QString normalizedPhoneNumber(const QString &input);

Expand Down

0 comments on commit 1db5b4a

Please sign in to comment.