Commit 3b97cb96 authored by chriadam's avatar chriadam

Merge branch 'jb44707-core-apps' into 'master'

Jb44707 core apps

See merge request !15
parents 1b709ae3 e0a0fc6f
......@@ -10,6 +10,7 @@ BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Sql)
BuildRequires: pkgconfig(Qt5DBus)
BuildRequires: pkgconfig(Qt5Contacts)
BuildRequires: pkgconfig(mlite5)
Requires: qt5-plugin-sqldriver-sqlite
%description
......@@ -23,7 +24,6 @@ Requires: qt5-plugin-sqldriver-sqlite
Summary: Unit tests for qtcontacts-sqlite-qt5
Group: System/Libraries
BuildRequires: pkgconfig(Qt5Test)
Requires: blts-tools
Requires: %{name} = %{version}-%{release}
%description tests
......@@ -32,6 +32,7 @@ This package contains unit tests for the qtcontacts-sqlite-qt5 library.
%files tests
%defattr(-,root,root,-)
/opt/tests/qtcontacts-sqlite-qt5/*
%{_libdir}/qtcontacts-sqlite-qt5/libtestdlgg.so
%package extensions
Summary: QtContacts extension headers for qtcontacts-sqlite-qt5
......@@ -51,7 +52,7 @@ This package contains extension headers for the qtcontacts-sqlite-qt5 library.
%setup -q -n %{name}-%{version}
%build
%qmake5
%qmake5 "VERSION=%{version}"
make %{?_smp_mflags}
%install
......
......@@ -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[] =
......@@ -1410,7 +1411,10 @@ static QString buildOrderBy(const QContactSortOrder &order, QStringList *joins,
return QString();
}
QString sortExpression(QStringLiteral("%1.%2").arg(detail.joinToSort ? detail.table : QStringLiteral("Contacts")).arg(field.column));
const bool isDisplayLabelGroup = detail.detailType == QContactDisplayLabel::Type && field.field == QContactDisplayLabel__FieldLabelGroup;
QString sortExpression(QStringLiteral("%1.%2")
.arg(detail.joinToSort ? detail.table : QStringLiteral("Contacts"))
.arg(isDisplayLabelGroup ? QStringLiteral("DisplayLabelGroupSortOrder") : field.column));
bool sortBlanks = true;
bool collate = true;
bool localized = field.fieldType == LocalizedField;
......@@ -1457,7 +1461,7 @@ static QString buildOrderBy(const QContactSortOrder &order, QStringList *joins,
result.append(sortExpression);
if (collate) {
if (!isDisplayLabelGroup && collate) {
if (localized && useLocale) {
result.append(QLatin1String(" COLLATE localeCollation"));
} else {
......@@ -1935,7 +1939,30 @@ QContactManager::Error ContactReader::queryContacts(
QContactManager::Error err = QContactManager::NoError;
const QString idsQueryStatement(QString::fromLatin1(
"SELECT Contacts.* "
"SELECT " // order and content 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));
......@@ -2117,22 +2144,22 @@ 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);
......@@ -2140,27 +2167,27 @@ QContactManager::Error ContactReader::queryContacts(
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;
......@@ -2171,7 +2198,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);
......
......@@ -31,14 +31,18 @@
#include "contactsdatabase.h"
#include "contactsengine.h"
#include "defaultdlggenerator.h"
#include "conversion_p.h"
#include "trace_p.h"
#include <QContactGender>
#include <QContactName>
#include <QContactDisplayLabel>
#include <QPluginLoader>
#include <QElapsedTimer>
#include <QStandardPaths>
#include <QDir>
#include <QElapsedTimer>
#include <QFile>
#include <QFileInfo>
#include <QLocale>
......@@ -63,6 +67,8 @@ static const char *createContactsTable =
"\n CREATE TABLE Contacts ("
"\n contactId INTEGER PRIMARY KEY ASC AUTOINCREMENT,"
"\n displayLabel TEXT,"
"\n displayLabelGroup TEXT,"
"\n displayLabelGroupSortOrder INTEGER,"
"\n firstName TEXT,"
"\n lowerFirstName TEXT,"
"\n lastName TEXT,"
......@@ -350,6 +356,11 @@ static const char *createOOBTable =
"\n value BLOB,"
"\n compressed INTEGER DEFAULT 0);";
static const char *createDbSettingsTable =
"\n CREATE TABLE DbSettings ("
"\n name TEXT PRIMARY KEY,"
"\n value TEXT );";
static const char *createRemoveTrigger =
"\n CREATE TRIGGER RemoveContactDetails"
"\n BEFORE DELETE"
......@@ -635,6 +646,7 @@ static const char *createStatements[] =
createRelationshipsTable,
createDeletedContactsTable,
createOOBTable,
createDbSettingsTable,
createRemoveTrigger,
createContactsSyncTargetIndex,
createContactsFirstNameIndex,
......@@ -1286,6 +1298,11 @@ static const char *upgradeVersion16[] = {
"PRAGMA user_version=17",
0 // NULL-terminated
};
static const char *upgradeVersion17[] = {
createDbSettingsTable,
"PRAGMA user_version=18",
0 // NULL-terminated
};
typedef bool (*UpgradeFunction)(QSqlDatabase &database);
......@@ -1679,6 +1696,48 @@ 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();
}
// add the display label group sort order column (precalculated sort index)
{
QSqlQuery alterQuery(database);
const QString statement = QStringLiteral("ALTER TABLE Contacts ADD COLUMN displayLabelGroupSortOrder INTEGER");
if (!alterQuery.prepare(statement)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare add display label group sort order 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 sort order column: %1\n%2")
.arg(alterQuery.lastError().text())
.arg(statement));
return false;
}
alterQuery.finish();
}
return true;
}
struct UpgradeOperation {
UpgradeFunction fn;
const char **statements;
......@@ -1702,9 +1761,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)
{
......@@ -1749,6 +1809,195 @@ static bool finalizeTransaction(QSqlDatabase &database, bool success)
template <typename T> static int lengthOf(T) { return 0; }
template <typename T, int N> static int lengthOf(const T(&)[N]) { return N; }
static bool executeDisplayLabelGroupLocalizationStatements(QSqlDatabase &database, ContactsDatabase *cdb, bool *changed = Q_NULLPTR)
{
// determine if the current system locale is equal to that used for the display label groups.
// if not, update them all.
bool sameLocale = false;
bool settingExists = false;
const QString localeName = QLocale().name();
{
QSqlQuery selectQuery(database);
selectQuery.setForwardOnly(true);
const QString statement = QStringLiteral("SELECT Value FROM DbSettings WHERE Name = 'LocaleName'");
if (!selectQuery.prepare(statement)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare locale setting selection query: %1\n%2")
.arg(selectQuery.lastError().text())
.arg(statement));
return false;
}
if (!selectQuery.exec()) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to select locale setting value: %1\n%2")
.arg(selectQuery.lastError().text())
.arg(statement));
return false;
}
if (selectQuery.next()) {
settingExists = true;
if (selectQuery.value(0).toString() == localeName) {
sameLocale = true; // no need to update the display label groups due to locale.
}
}
}
// update the database settings with the current locale name if needed.
if (!sameLocale) {
QSqlQuery setLocaleQuery(database);
const QString statement = settingExists
? QStringLiteral("UPDATE DbSettings SET Value = ? WHERE Name = 'LocaleName'")
: QStringLiteral("INSERT INTO DbSettings (Name, Value) VALUES ('LocaleName', ?)");
if (!setLocaleQuery.prepare(statement)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare locale setting update query: %1\n%2")
.arg(setLocaleQuery.lastError().text())
.arg(statement));
return false;
}
setLocaleQuery.addBindValue(QVariant(localeName));
if (!setLocaleQuery.exec()) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to update locale setting value: %1\n%2")
.arg(setLocaleQuery.lastError().text())
.arg(statement));
return false;
}
}
#ifndef HAS_MLITE
bool sameGroupProperty = true;
#else
// also determine if the current system setting for deriving the group from the first vs last
// name is the same since the display label groups were generated.
// if not, update them all.
bool sameGroupProperty = false;
const QString groupProperty = cdb->displayLabelGroupPreferredProperty();
{
QSqlQuery selectQuery(database);
selectQuery.setForwardOnly(true);
const QString statement = QStringLiteral("SELECT Value FROM DbSettings WHERE Name = 'GroupProperty'");
if (!selectQuery.prepare(statement)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare group property setting selection query: %1\n%2")
.arg(selectQuery.lastError().text())
.arg(statement));
return false;
}
if (!selectQuery.exec()) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to select group property setting value: %1\n%2")
.arg(selectQuery.lastError().text())
.arg(statement));
return false;
}
if (selectQuery.next()) {
settingExists = true;
if (selectQuery.value(0).toString() == groupProperty) {
sameGroupProperty = true; // no need to update the display label groups due to group property.
}
}
}
// update the database settings with the current group property name if needed.
if (!sameGroupProperty) {
QSqlQuery setGroupPropertyQuery(database);
const QString statement = settingExists
? QStringLiteral("UPDATE DbSettings SET Value = ? WHERE Name = 'GroupProperty'")
: QStringLiteral("INSERT INTO DbSettings (Name, Value) VALUES ('GroupProperty', ?)");
if (!setGroupPropertyQuery.prepare(statement)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare group property setting update query: %1\n%2")
.arg(setGroupPropertyQuery.lastError().text())
.arg(statement));
return false;
}
setGroupPropertyQuery.addBindValue(QVariant(groupProperty));
if (!setGroupPropertyQuery.exec()) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to update group property setting value: %1\n%2")
.arg(setGroupPropertyQuery.lastError().text())
.arg(statement));
return false;
}
}
#endif // HAS_MLITE
if (sameLocale && sameGroupProperty) {
// no need to update the previously generated display label groups.
if (changed) *changed = false;
return true;
} else {
if (changed) *changed = true;
}
// for every single contact in our database, read the data required to generate the display label group data.
QVariantList contactIds;
QVariantList displayLabelGroups;
QVariantList displayLabelGroupSortOrders;
{
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);
QContactName n;
n.setFirstName(firstName);
n.setLastName(lastName);
QContactDisplayLabel dl;
dl.setLabel(displayLabel);
QContact c;
c.saveDetail(&n);
c.saveDetail(&dl);
const QString dlg = cdb->determineDisplayLabelGroup(c);
displayLabelGroups.append(dlg);
displayLabelGroupSortOrders.append(cdb->displayLabelGroupSortValue(dlg));
}
selectQuery.finish();
}
// now write the generated data back to the database.
// do it in batches, otherwise it can fail if any single batch is too big.
{
for (int i = 0; i < displayLabelGroups.size(); i += 167) {
const QVariantList groups = displayLabelGroups.mid(i, qMin(displayLabelGroups.size() - i, 167));
const QVariantList sortorders = displayLabelGroupSortOrders.mid(i, qMin(displayLabelGroups.size() - i, 167));
const QVariantList ids = contactIds.mid(i, qMin(displayLabelGroups.size() - i, 167));
QSqlQuery updateQuery(database);
const QString statement = QStringLiteral("UPDATE Contacts SET displayLabelGroup = ?, displayLabelGroupSortOrder = ? 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(groups);
updateQuery.addBindValue(sortorders);
updateQuery.addBindValue(ids);
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;
}
static bool executeUpgradeStatements(QSqlDatabase &database)
{
// Check that the defined schema matches the array of upgrade scripts
......@@ -1825,12 +2074,15 @@ static bool checkDatabase(QSqlDatabase &database)
return false;
}
static bool upgradeDatabase(QSqlDatabase &database)
static bool upgradeDatabase(QSqlDatabase &database, ContactsDatabase *cdb)
{
if (!beginTransaction(database))
return false;
bool success = executeUpgradeStatements(database);
if (success) {
success = executeDisplayLabelGroupLocalizationStatements(database, cdb);
}
return finalizeTransaction(database, success);
}
......@@ -1909,7 +2161,7 @@ static bool executeSelfContactStatements(QSqlDatabase &database, const bool aggr
return true;
}
static bool prepareDatabase(QSqlDatabase &database, const bool aggregating, QString &localeName)
static bool prepareDatabase(QSqlDatabase &database, ContactsDatabase *cdb, const bool aggregating, QString &localeName)
{
if (!configureDatabase(database, localeName))
return false;
......@@ -1921,6 +2173,9 @@ static bool prepareDatabase(QSqlDatabase &database, const bool aggregating, QStr
if (success) {
success = executeSelfContactStatements(database, aggregating);
}
if (success) {
success = executeDisplayLabelGroupLocalizationStatements(database, cdb);
}
return finalizeTransaction(database, success);
}
......@@ -2440,6 +2695,88 @@ static size_t databaseOwnershipIndex = 0;
static size_t databaseConnectionsIndex = 1;
static size_t writeAccessIndex = 2;
static QVector<QtContactsSqliteExtensions::DisplayLabelGroupGenerator*> initializeDisplayLabelGroupGenerators()
{
QVector<QtContactsSqliteExtensions::DisplayLabelGroupGenerator*> generators;
const QString pluginsPath = QStringLiteral("/usr/lib/qtcontacts-sqlite-qt5/");
QDir pluginDir(pluginsPath);
const QStringList pluginNames = pluginDir.entryList();
for (const QString &plugin : pluginNames) {
if (plugin.endsWith(QStringLiteral(".so"))) {
QPluginLoader loader(pluginsPath + plugin);
QtContactsSqliteExtensions::DisplayLabelGroupGenerator *generator = qobject_cast<QtContactsSqliteExtensions::DisplayLabelGroupGenerator *>(loader.instance());
bool inserted = false;
const int prio = generator->priority();
for (int i = 0; i < generators.size(); ++i) {
if (generators.at(i)->priority() < prio) {
generators.insert(i, generator);
inserted = true;
break;
}
}
if (!inserted) {
generators.append(generator);
}
}
}
return generators;
}
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
......@@ -2501,11 +2838,26 @@ 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)
#ifdef HAS_MLITE
, m_groupPropertyConf(QLatin1String("/org/nemomobile/contacts/group_property"))
#endif // HAS_MLITE
{
#ifdef HAS_MLITE
QObject::connect(&m_groupPropertyConf, &MGConfItem::valueChanged, [this, engine] {
this->regenerateDisplayLabelGroups();
// expensive, but if we don't do it, in multi-process case some clients may not get updated...
// if contacts backend were daemonised, this problem would go away...
// Emit some engine signals asynchronously.
QMetaObject::invokeMethod(engine, "_q_displayLabelGroupsChanged", Qt::QueuedConnection);
QMetaObject::invokeMethod(engine, "dataChanged", Qt::QueuedConnection);
});
#endif // HAS_MLITE
}
ContactsDatabase::~ContactsDatabase()
......@@ -2539,6 +2891,48 @@ bool ContactsDatabase::open(const QString &connectionName, bool nonprivileged, b
{
QMutexLocker locker(accessMutex());
if (m_dlgGenerators.isEmpty()) {
for (auto generator : s_dlgGenerators) {
if (generator && (generator->name().contains(QStringLiteral("test")) == autoTest)) {
m_dlgGenerators.append(generator);
}
}
m_dlgGenerators.append(m_defaultGenerator.data());
// 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 (!knownDisplayLabelGroups.contains(group)) {
knownDisplayLabelGroups.append(group);
}
}
}
}
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()) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Unable to open database when already open: %1").arg(connectionName));
return false;
......@@ -2585,7 +2979,7 @@ bool ContactsDatabase::open(const QString &connectionName, bool nonprivileged, b
return false;
}
if (!databasePreexisting && !prepareDatabase(m_database, aggregating(), m_localeName)) {
if (!databasePreexisting && !prepareDatabase(m_database, this, aggregating(), m_localeName)) {
QTCONTACTS_SQLITE_WARNING(QString::fromLatin1("Failed to prepare contacts database - removing: %1")
.arg(m_database.lastError().text()));
......@@ -2615,7 +3009,7 @@ bool ContactsDatabase::open(const QString &connectionName, bool nonprivileged, b
return false;
}
if (!upgradeDatabase(m_database)) {