Skip to content

Commit

Permalink
Improve the default conflict resolution code for TWCSA
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Adams committed Sep 16, 2020
1 parent cc6f435 commit 4d277d7
Show file tree
Hide file tree
Showing 7 changed files with 372 additions and 41 deletions.
16 changes: 13 additions & 3 deletions src/engine/contactsengine.cpp
Expand Up @@ -1330,15 +1330,25 @@ ContactsEngine::ContactsEngine(const QString &name, const QMap<QString, QString>

/* Store the engine into a property of QCoreApplication, so that it can be
* retrieved by the extension code */
QCoreApplication::instance()->setProperty(CONTACT_MANAGER_ENGINE_PROP,
QVariant::fromValue(this));
QCoreApplication *app = QCoreApplication::instance();
QList<QVariant> engines = app->property(CONTACT_MANAGER_ENGINE_PROP).toList();
engines.append(QVariant::fromValue(this));
app->setProperty(CONTACT_MANAGER_ENGINE_PROP, engines);

m_managerUri = managerUri();
}

ContactsEngine::~ContactsEngine()
{
QCoreApplication::instance()->setProperty(CONTACT_MANAGER_ENGINE_PROP, 0);
QCoreApplication *app = QCoreApplication::instance();
QList<QVariant> engines = app->property(CONTACT_MANAGER_ENGINE_PROP).toList();
for (int i = 0; i < engines.size(); ++i) {
if (engines[i] == QVariant::fromValue(this)) {
engines.removeAt(i);
break;
}
}
app->setProperty(CONTACT_MANAGER_ENGINE_PROP, engines);
}

QString ContactsEngine::databaseUuid()
Expand Down
32 changes: 19 additions & 13 deletions src/engine/contactwriter.cpp
Expand Up @@ -1396,9 +1396,9 @@ QContactManager::Error ContactWriter::clearChangeFlags(const QList<QContactId> &
// fourth, clear any added/modified change flags for details of contacts specified in the list.
const QString detstatement(QString::fromLatin1("UPDATE Details SET changeFlags = unhandledChangeFlags, unhandledChangeFlags = 0 WHERE contactId = :contactId"));
ContactsDatabase::Query detquery(m_database.prepare(detstatement));
query.bindValue(QLatin1String(":contactId"), cids);
detquery.bindValue(QLatin1String(":contactId"), cids);
if (!ContactsDatabase::executeBatch(detquery)) {
query.reportError("Failed to clear detail change flags");
detquery.reportError("Failed to clear detail change flags");
if (!withinTransaction) {
rollbackTransaction();
}
Expand Down Expand Up @@ -1998,8 +1998,9 @@ quint32 writeCommonDetails(ContactsDatabase &db, quint32 contactId, quint32 deta
" :provenance,"
" :modifiable,"
" :nonexportable,"
" 1," // ChangeFlags::IsAdded
" %1)").arg(recordUnhandledChangeFlags ? QStringLiteral("1") : QStringLiteral("0"))
" %1,"
" %2)").arg(aggregateContact ? QStringLiteral("0") : QStringLiteral("1")) // ChangeFlags::IsAdded
.arg((aggregateContact || !recordUnhandledChangeFlags) ? QStringLiteral("0") : QStringLiteral("1"))
: QStringLiteral(
" UPDATE Details SET"
" detail = :detail,"
Expand All @@ -2009,12 +2010,11 @@ quint32 writeCommonDetails(ContactsDatabase &db, quint32 contactId, quint32 deta
" accessConstraints = :accessConstraints,"
" provenance = :provenance,"
" modifiable = :modifiable,"
" nonexportable = :nonexportable,"
" ChangeFlags = ChangeFlags | 2," // ChangeFlags::IsModified
" UnhandledChangeFlags = %1"
" nonexportable = :nonexportable"
" %1 %2"
" WHERE contactId = :contactId AND detailId = :detailId")
.arg(recordUnhandledChangeFlags ? QStringLiteral("UnhandledChangeFlags | 2")
: QStringLiteral("UnhandledChangeFlags")));
.arg(aggregateContact ? QString() : QStringLiteral(", ChangeFlags = ChangeFlags | 2")) // ChangeFlags::IsModified
.arg((aggregateContact || !recordUnhandledChangeFlags) ? QString() : QStringLiteral(", UnhandledChangeFlags = UnhandledChangeFlags | 2")));

ContactsDatabase::Query query(db.prepare(statement));

Expand All @@ -2041,8 +2041,6 @@ quint32 writeCommonDetails(ContactsDatabase &db, quint32 contactId, quint32 deta
query.bindValue(":provenance", provenance);
query.bindValue(":modifiable", modifiable);
query.bindValue(":nonexportable", nonexportable);
query.bindValue(":changeFlags", typeName);
query.bindValue(":unhandledChangeFlags", typeName);

if (!ContactsDatabase::execute(query)) {
query.reportError(QStringLiteral("Failed to write common details for %1\ndetailUri: %2, linkedDetailUris: %3")
Expand Down Expand Up @@ -4425,7 +4423,11 @@ QContactManager::Error ContactWriter::create(QContact *contact, const DetailList

// update the timestamp if necessary (aggregate contacts should have a composed timestamp value)
if (!m_database.aggregating() || (contact->collectionId() != ContactCollectionId::apiId(ContactsDatabase::AggregateAddressbookCollectionId, m_managerUri))) {
updateTimestamp(contact, true); // set creation timestamp
// only update the timestamp for "normal" modifications, not updates caused by sync,
// as we should retain the revision timestamp for synced contacts.
if (!withinSyncUpdate) {
updateTimestamp(contact, true);
}
}

QContactManager::Error writeErr = enforceDetailConstraints(contact);
Expand Down Expand Up @@ -4568,7 +4570,11 @@ QContactManager::Error ContactWriter::update(QContact *contact, const DetailList
// update the modification timestamp (aggregate contacts should have a composed timestamp value)
if (!m_database.aggregating()
|| (contact->collectionId() != ContactCollectionId::apiId(ContactsDatabase::AggregateAddressbookCollectionId, m_managerUri))) {
updateTimestamp(contact, false);
// only update the timestamp for "normal" modifications, not updates caused by sync,
// as we should retain the revision timestamp for synced contacts.
if (!withinSyncUpdate) {
updateTimestamp(contact, false);
}
}

if (m_database.aggregating()
Expand Down
7 changes: 7 additions & 0 deletions src/extensions/contactdelta.h
Expand Up @@ -99,6 +99,13 @@ namespace QtContactsSqliteExtensions {
const QHash<QContactDetail::DetailType, QSet<int> > &ignorableDetailFields = defaultIgnorableDetailFields(),
const QSet<int> &ignorableCommonFields = defaultIgnorableCommonFields());

int exactContactMatchExistsInList(
const QContact &aContact,
const QList<QContact> &list,
const QSet<QContactDetail::DetailType> &ignorableDetailTypes,
const QHash<QContactDetail::DetailType, QSet<int> > &ignorableDetailFields,
const QSet<int> &ignorableCommonFields,
bool printDifferences = false);
}

#endif // CONTACTDELTA_H
113 changes: 113 additions & 0 deletions src/extensions/contactdelta_impl.h
Expand Up @@ -276,6 +276,98 @@ bool detailPairExactlyMatches(
return true;
}

int exactDetailMatchExistsInList(
const QContactDetail &det,
const QList<QContactDetail> &list,
const QHash<QContactDetail::DetailType, QSet<int> > &ignorableDetailFields,
QSet<int> ignorableCommonFields,
bool printDifferences)
{
for (int i = 0; i < list.size(); ++i) {
if (detailPairExactlyMatches(det, list[i], ignorableDetailFields, ignorableCommonFields, printDifferences)) {
return i; // exact match at this index.
}
}

return -1;
}

bool contactDetailsMatchExactly(
const QList<QContactDetail> &aDetails,
const QList<QContactDetail> &bDetails,
const QHash<QContactDetail::DetailType, QSet<int> > &ignorableDetailFields,
QSet<int> ignorableCommonFields,
bool printDifferences = false)
{
// for it to be an exact match:
// a) every detail in aDetails must exist in bDetails
// b) no extra details can exist in bDetails
if (aDetails.size() != bDetails.size()) {
if (Q_UNLIKELY(printDifferences)) {
// detail count differs, and continue the analysis to find out precisely what the differences are.
QTCONTACTS_SQLITE_DELTA_DEBUG_LOG("A has more details than B:" << aDetails.size() << ">" << bDetails.size());
} else {
// detail count differs, return immediately.
return false;
}
}

QList<QContactDetail> nonMatchedADetails;
QList<QContactDetail> nonMatchedBDetails = bDetails;
bool allADetailsHaveMatches = true;
foreach (const QContactDetail &aDetail, aDetails) {
int exactMatchIndex = exactDetailMatchExistsInList(
aDetail, nonMatchedBDetails, ignorableDetailFields,
ignorableCommonFields, false);
if (exactMatchIndex == -1) {
// no exact match for this detail.
allADetailsHaveMatches = false;
if (Q_UNLIKELY(printDifferences)) {
// we only record the difference if we're printing them.
nonMatchedADetails.append(aDetail);
} else {
// we only break if we're not printing all differences.
break;
}
} else {
// found a match for this detail.
// remove it from ldets so that duplicates in cdets
// don't mess up our detection.
nonMatchedBDetails.removeAt(exactMatchIndex);
}
}

if (allADetailsHaveMatches && nonMatchedBDetails.size() == 0) {
return true; // exact match
}

if (Q_UNLIKELY(printDifferences)) {
Q_FOREACH (const QContactDetail &ad, nonMatchedADetails) {
bool foundMatch = false;
for (int i = 0; i < nonMatchedBDetails.size(); ++i) {
const QContactDetail &bd = nonMatchedBDetails[i];
if (ad.type() == bd.type()) { // most likely a modification.
foundMatch = true;
QTCONTACTS_SQLITE_DELTA_DEBUG_LOG("Detail modified from A to B:");
detailPairExactlyMatches(ad, bd, ignorableDetailFields, ignorableCommonFields, printDifferences);
nonMatchedBDetails.removeAt(i);
break;
}
}
if (!foundMatch) {
QTCONTACTS_SQLITE_DELTA_DEBUG_LOG("New detail exists in contact A:");
QTCONTACTS_SQLITE_DELTA_DEBUG_DETAIL(ad);
}
}
Q_FOREACH (const QContactDetail &bd, nonMatchedBDetails) {
QTCONTACTS_SQLITE_DELTA_DEBUG_LOG("New detail exists in contact B:");
QTCONTACTS_SQLITE_DELTA_DEBUG_DETAIL(bd);
}
}

return false;
}

// move some information (modifiable, detail uris, provenance, database id)
// from the old detail to the new detail.
void constructModification(const QContactDetail &old, QContactDetail *update)
Expand Down Expand Up @@ -580,5 +672,26 @@ ContactDetailDelta QtContactsSqliteExtensions::determineContactDetailDelta(
return delta;
}

int QtContactsSqliteExtensions::exactContactMatchExistsInList(
const QContact &aContact,
const QList<QContact> &list,
const QSet<QContactDetail::DetailType> &ignorableDetailTypes,
const QHash<QContactDetail::DetailType, QSet<int> > &ignorableDetailFields,
const QSet<int> &ignorableCommonFields,
bool printDifferences)
{
QList<QContactDetail> aDetails = aContact.details();
removeIgnorableDetailsFromList(&aDetails, ignorableDetailTypes);
for (int i = 0; i < list.size(); ++i) {
QList<QContactDetail> bDetails = list[i].details();
removeIgnorableDetailsFromList(&bDetails, ignorableDetailTypes);
if (contactDetailsMatchExactly(aDetails, bDetails, ignorableDetailFields, ignorableCommonFields, printDifferences)) {
return i; // exact match at this index.
}
}

return -1;
}

#endif // CONTACTDELTA_IMPL_H

12 changes: 8 additions & 4 deletions src/extensions/qtcontacts-extensions_manager_impl.h
Expand Up @@ -43,12 +43,16 @@ namespace QtContactsSqliteExtensions {
ContactManagerEngine *contactManagerEngine(QContactManager &manager)
{
QCoreApplication *app = QCoreApplication::instance();
QVariant v = app->property(CONTACT_MANAGER_ENGINE_PROP);
QContactManagerEngine *engine = static_cast<QContactManagerEngine*>(v.value<QObject*>());
if (engine && engine->managerName() == manager.managerName()) {
return static_cast<QtContactsSqliteExtensions::ContactManagerEngine *>(engine);
QList<QVariant> engines = app->property(CONTACT_MANAGER_ENGINE_PROP).toList();
for (QVariant &v : engines) {
QContactManagerEngine *engine = static_cast<QContactManagerEngine*>(v.value<QObject*>());
if (engine && engine->managerName() == manager.managerName()) {
return static_cast<QtContactsSqliteExtensions::ContactManagerEngine *>(engine);
}
}

//ContactManagerEngine *cme = dynamic_cast<ContactManagerEngine *>(QContactManagerData::managerData(mgr)->m_engine);

return 0;
}

Expand Down
12 changes: 11 additions & 1 deletion src/extensions/twowaycontactsyncadaptor.h
Expand Up @@ -58,10 +58,13 @@ class TwoWayContactSyncAdaptor
ContinueAfterError
};

TwoWayContactSyncAdaptor(int accountId = 0, const QString &applicationName = QString(), const QMap<QString, QString> &params = QMap<QString, QString>());
TwoWayContactSyncAdaptor(int accountId = 0, const QString &applicationName = QString());
TwoWayContactSyncAdaptor(int accountId, const QString &applicationName, const QMap<QString, QString> &params);
TwoWayContactSyncAdaptor(int accountId, const QString &applicationName, QContactManager &mananger);
virtual ~TwoWayContactSyncAdaptor();

void setManager(QContactManager &manager);

// step two: start complete sync cycle
// - determine collection metadata changes made on remote server
// - determine collection metadata changes made on local device
Expand Down Expand Up @@ -140,6 +143,13 @@ class TwoWayContactSyncAdaptor

void performNextQueuedOperation();


struct IgnorableDetailsAndFields {
QSet<QContactDetail::DetailType> detailTypes;
QHash<QContactDetail::DetailType, QSet<int> > detailFields;
QSet<int> commonFields;
};
virtual IgnorableDetailsAndFields ignorableDetailsAndFields() const;
virtual QContact resolveConflictingChanges(const QContact &local, const QContact &remote, bool *identical);

virtual void syncFinishedSuccessfully();
Expand Down

0 comments on commit 4d277d7

Please sign in to comment.