From a7fae418a007ab68666355be72f8a6c88ef5916d Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Fri, 8 Apr 2022 10:23:50 +0200 Subject: [PATCH] fetch and store in sync database information abot lock state of files fetch lock properties from server decode them and store them in sync database test to ensure we do properly handle those properties Signed-off-by: Matthieu Gallien --- src/common/syncjournaldb.cpp | 103 ++++++++++++++--------------- src/common/syncjournalfilerecord.h | 7 ++ src/libsync/discovery.cpp | 14 +++- src/libsync/discoveryphase.cpp | 48 ++++++++++++++ src/libsync/discoveryphase.h | 8 +++ src/libsync/propagatedownload.cpp | 6 ++ src/libsync/syncfileitem.cpp | 14 ++++ src/libsync/syncfileitem.h | 23 +++++++ test/syncenginetestutils.cpp | 2 + test/testlocaldiscovery.cpp | 49 ++++++++++++++ 10 files changed, 221 insertions(+), 53 deletions(-) diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index ca5ae1faaeaba..2261ef4d7367f 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -48,7 +48,8 @@ Q_LOGGING_CATEGORY(lcDb, "nextcloud.sync.database", QtInfoMsg) #define GET_FILE_RECORD_QUERY \ "SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \ - " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted " \ + " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, " \ + " lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout " \ " FROM metadata" \ " LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id" @@ -66,6 +67,13 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que rec._checksumHeader = query.baValue(9); rec._e2eMangledName = query.baValue(10); rec._isE2eEncrypted = query.intValue(11) > 0; + rec._locked = query.intValue(12) > 0; + rec._lockOwnerDisplayName = query.stringValue(13); + rec._lockOwnerId = query.stringValue(14); + rec._lockOwnerType = query.int64Value(15); + rec._lockEditorApp = query.stringValue(16); + rec._lockTime = query.int64Value(17); + rec._lockTimeout = query.int64Value(18); } static QByteArray defaultJournalMode(const QString &dbPath) @@ -658,6 +666,20 @@ bool SyncJournalDb::updateMetadataTableStructure() return false; } + const auto addColumn = [this, &columns, &re] (const QString &columnName, const QString &dataType) { + const auto latin1ColumnName = columnName.toLatin1(); + if (columns.indexOf(latin1ColumnName) == -1) { + SqlQuery query(_db); + const auto request = QStringLiteral("ALTER TABLE metadata ADD COLUMN %1 %2;").arg(columnName).arg(dataType); + query.prepare(request.toLatin1()); + if (!query.exec()) { + sqlFail(QStringLiteral("updateMetadataTableStructure: add %1 column").arg(columnName), query); + re = false; + } + commitInternal(QStringLiteral("update database structure: add %1 column").arg(columnName)); + } + }; + if (columns.indexOf("fileid") == -1) { SqlQuery query(_db); query.prepare("ALTER TABLE metadata ADD COLUMN fileid VARCHAR(128);"); @@ -722,54 +744,11 @@ bool SyncJournalDb::updateMetadataTableStructure() commitInternal(QStringLiteral("update database structure: add parent index")); } - if (columns.indexOf("ignoredChildrenRemote") == -1) { - SqlQuery query(_db); - query.prepare("ALTER TABLE metadata ADD COLUMN ignoredChildrenRemote INT;"); - if (!query.exec()) { - sqlFail(QStringLiteral("updateMetadataTableStructure: add ignoredChildrenRemote column"), query); - re = false; - } - commitInternal(QStringLiteral("update database structure: add ignoredChildrenRemote col")); - } - - if (columns.indexOf("contentChecksum") == -1) { - SqlQuery query(_db); - query.prepare("ALTER TABLE metadata ADD COLUMN contentChecksum TEXT;"); - if (!query.exec()) { - sqlFail(QStringLiteral("updateMetadataTableStructure: add contentChecksum column"), query); - re = false; - } - commitInternal(QStringLiteral("update database structure: add contentChecksum col")); - } - if (columns.indexOf("contentChecksumTypeId") == -1) { - SqlQuery query(_db); - query.prepare("ALTER TABLE metadata ADD COLUMN contentChecksumTypeId INTEGER;"); - if (!query.exec()) { - sqlFail(QStringLiteral("updateMetadataTableStructure: add contentChecksumTypeId column"), query); - re = false; - } - commitInternal(QStringLiteral("update database structure: add contentChecksumTypeId col")); - } - - if (!columns.contains("e2eMangledName")) { - SqlQuery query(_db); - query.prepare("ALTER TABLE metadata ADD COLUMN e2eMangledName TEXT;"); - if (!query.exec()) { - sqlFail(QStringLiteral("updateMetadataTableStructure: add e2eMangledName column"), query); - re = false; - } - commitInternal(QStringLiteral("update database structure: add e2eMangledName col")); - } - - if (!columns.contains("isE2eEncrypted")) { - SqlQuery query(_db); - query.prepare("ALTER TABLE metadata ADD COLUMN isE2eEncrypted INTEGER;"); - if (!query.exec()) { - sqlFail(QStringLiteral("updateMetadataTableStructure: add isE2eEncrypted column"), query); - re = false; - } - commitInternal(QStringLiteral("update database structure: add isE2eEncrypted col")); - } + addColumn(QStringLiteral("ignoredChildrenRemote"), QStringLiteral("INT")); + addColumn(QStringLiteral("contentChecksum"), QStringLiteral("TEXT")); + addColumn(QStringLiteral("contentChecksumTypeId"), QStringLiteral("INTEGER")); + addColumn(QStringLiteral("e2eMangledName"), QStringLiteral("TEXT")); + addColumn(QStringLiteral("isE2eEncrypted"), QStringLiteral("INTEGER")); auto uploadInfoColumns = tableColumns("uploadinfo"); if (uploadInfoColumns.isEmpty()) @@ -806,6 +785,14 @@ bool SyncJournalDb::updateMetadataTableStructure() commitInternal(QStringLiteral("update database structure: add e2eMangledName index")); } + addColumn(QStringLiteral("lock"), QStringLiteral("INTEGER")); + addColumn(QStringLiteral("lockType"), QStringLiteral("INTEGER")); + addColumn(QStringLiteral("lockOwnerDisplayName"), QStringLiteral("TEXT")); + addColumn(QStringLiteral("lockOwnerId"), QStringLiteral("TEXT")); + addColumn(QStringLiteral("lockOwnerEditor"), QStringLiteral("TEXT")); + addColumn(QStringLiteral("lockTime"), QStringLiteral("INTEGER")); + addColumn(QStringLiteral("lockTimeout"), QStringLiteral("INTEGER")); + return re; } @@ -919,7 +906,10 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & << "modtime:" << record._modtime << "type:" << record._type << "etag:" << record._etag << "fileId:" << record._fileId << "remotePerm:" << record._remotePerm.toString() << "fileSize:" << record._fileSize << "checksum:" << record._checksumHeader - << "e2eMangledName:" << record.e2eMangledName() << "isE2eEncrypted:" << record._isE2eEncrypted; + << "e2eMangledName:" << record.e2eMangledName() << "isE2eEncrypted:" << record._isE2eEncrypted + << "lock:" << (record._locked ? "true" : "false") << "lock owner type:" << record._lockOwnerType + << "lock owner:" << record._lockOwnerDisplayName << "lock owner id:" << record._lockOwnerId + << "lock editor:" << record._lockEditorApp; const qint64 phash = getPHash(record._path); if (!checkConnect()) { @@ -943,8 +933,10 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & int contentChecksumTypeId = mapChecksumType(checksumType); const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata " - "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted) " - "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18);"), + "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, " + "contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, lock, lockType, lockOwnerDisplayName, lockOwnerId, " + "lockOwnerEditor, lockTime, lockTimeout) " + "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25);"), _db); if (!query) { return query->error(); @@ -968,6 +960,13 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & query->bindValue(16, contentChecksumTypeId); query->bindValue(17, record._e2eMangledName); query->bindValue(18, record._isE2eEncrypted); + query->bindValue(19, record._locked ? 1 : 0); + query->bindValue(20, record._lockOwnerType); + query->bindValue(21, record._lockOwnerDisplayName); + query->bindValue(22, record._lockOwnerId); + query->bindValue(23, record._lockEditorApp); + query->bindValue(24, record._lockTime); + query->bindValue(25, record._lockTimeout); if (!query->exec()) { return query->error(); diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h index b47f98e5b5d73..bb3091b0925bf 100644 --- a/src/common/syncjournalfilerecord.h +++ b/src/common/syncjournalfilerecord.h @@ -70,6 +70,13 @@ class OCSYNC_EXPORT SyncJournalFileRecord QByteArray _checksumHeader; QByteArray _e2eMangledName; bool _isE2eEncrypted = false; + bool _locked = false; + QString _lockOwnerDisplayName; + QString _lockOwnerId; + qint64 _lockOwnerType = 0; + QString _lockEditorApp; + qint64 _lockTime = 0; + qint64 _lockTimeout = 0; }; bool OCSYNC_EXPORT diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 63b75a05fa25d..488d0ca0630b7 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -363,6 +363,9 @@ void ProcessDirectoryJob::processFile(PathTuple path, { const char *hasServer = serverEntry.isValid() ? "true" : _queryServer == ParentNotChanged ? "db" : "false"; const char *hasLocal = localEntry.isValid() ? "true" : _queryLocal == ParentNotChanged ? "db" : "false"; + const auto serverFileIsLocked = serverEntry.locked == SyncFileItem::LockStatus::LockedItem ? "locked" : "not locked"; + const auto localFileIsLocked = dbEntry._locked ? "locked" : "not locked"; + const auto fileIsLocked = serverEntry.isValid() ? serverFileIsLocked : localFileIsLocked; qCInfo(lcDisco).nospace() << "Processing " << path._original << " | valid: " << dbEntry.isValid() << "/" << hasLocal << "/" << hasServer << " | mtime: " << dbEntry._modtime << "/" << localEntry.modtime << "/" << serverEntry.modtime @@ -374,7 +377,8 @@ void ProcessDirectoryJob::processFile(PathTuple path, << " | inode: " << dbEntry._inode << "/" << localEntry.inode << "/" << " | type: " << dbEntry._type << "/" << localEntry.type << "/" << (serverEntry.isDirectory ? ItemTypeDirectory : ItemTypeFile) << " | e2ee: " << dbEntry._isE2eEncrypted << "/" << serverEntry.isE2eEncrypted - << " | e2eeMangledName: " << dbEntry.e2eMangledName() << "/" << serverEntry.e2eMangledName; + << " | e2eeMangledName: " << dbEntry.e2eMangledName() << "/" << serverEntry.e2eMangledName + << " | file lock: " << fileIsLocked; if (localEntry.isValid() && !serverEntry.isValid() @@ -483,6 +487,14 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( Q_ASSERT(serverEntry.e2eMangledName.startsWith(rootPath)); return serverEntry.e2eMangledName.mid(rootPath.length()); }(); + item->_locked = serverEntry.locked; + item->_lockOwnerDisplayName = serverEntry.lockOwnerDisplayName; + item->_lockOwnerId = serverEntry.lockOwnerId; + item->_lockOwnerType = serverEntry.lockOwnerType; + item->_lockEditorApp = serverEntry.lockEditorApp; + item->_lockTime = serverEntry.lockTime; + item->_lockTimeout = serverEntry.lockTimeout; + qCInfo(lcDisco()) << item->_locked << item->_lockOwnerDisplayName << item->_lockOwnerId << item->_lockOwnerType << item->_lockEditorApp << item->_lockTime << item->_lockTimeout; // Check for missing server data { diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index d71dca4335cf5..67b91ad872c79 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -378,6 +378,15 @@ void DiscoverySingleDirectoryJob::start() if (_account->capabilities().clientSideEncryptionAvailable()) { props << "http://nextcloud.org/ns:is-encrypted"; } + if (_account->capabilities().filesLockAvailable()) { + props << "http://nextcloud.org/ns:lock" + << "http://nextcloud.org/ns:lock-owner-displayname" + << "http://nextcloud.org/ns:lock-owner" + << "http://nextcloud.org/ns:lock-owner-type" + << "http://nextcloud.org/ns:lock-owner-editor" + << "http://nextcloud.org/ns:lock-time" + << "http://nextcloud.org/ns:lock-timeout"; + } lsColJob->setProperties(props); @@ -445,7 +454,46 @@ static void propertyMapToRemoteInfo(const QMap &map, RemoteInf } } else if (property == "is-encrypted" && value == QStringLiteral("1")) { result.isE2eEncrypted = true; + } else if (property == "lock") { + result.locked = (value == QStringLiteral("1") ? SyncFileItem::LockStatus::LockedItem : SyncFileItem::LockStatus::UnlockedItem); + } + if (property == "lock-owner-displayname") { + result.lockOwnerDisplayName = value; + } + if (property == "lock-owner") { + result.lockOwnerId = value; + } + if (property == "lock-owner-type") { + auto ok = false; + const auto intConvertedValue = value.toULongLong(&ok); + if (ok) { + result.lockOwnerType = static_cast(intConvertedValue); + } else { + result.lockOwnerType = SyncFileItem::LockOwnerType::UserLock; + } + } + if (property == "lock-owner-editor") { + result.lockEditorApp = value; } + if (property == "lock-time") { + auto ok = false; + const auto intConvertedValue = value.toULongLong(&ok); + if (ok) { + result.lockTime = intConvertedValue; + } else { + result.lockTime = 0; + } + } + if (property == "lock-timeout") { + auto ok = false; + const auto intConvertedValue = value.toULongLong(&ok); + if (ok) { + result.lockTimeout = intConvertedValue; + } else { + result.lockTimeout = 0; + } + } + } if (result.isDirectory && map.contains("size")) { diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index cd210a644639c..3f5240dd1de31 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -65,6 +65,14 @@ struct RemoteInfo QString directDownloadUrl; QString directDownloadCookies; + + SyncFileItem::LockStatus locked = SyncFileItem::LockStatus::UnlockedItem; + QString lockOwnerDisplayName; + QString lockOwnerId; + SyncFileItem::LockOwnerType lockOwnerType = SyncFileItem::LockOwnerType::UserLock; + QString lockEditorApp; + qint64 lockTime = 0; + qint64 lockTimeout = 0; }; struct LocalInfo diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index bc2922070038d..57e5572deb82b 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -1211,6 +1211,12 @@ void PropagateDownloadFile::downloadFinished() return; } + qCInfo(lcPropagateDownload()) << propagator()->account()->davUser() << propagator()->account()->davDisplayName() << propagator()->account()->displayName(); + if (_item->_locked == SyncFileItem::LockStatus::LockedItem && (_item->_lockOwnerType != SyncFileItem::LockOwnerType::UserLock || _item->_lockOwnerId != propagator()->account()->davUser())) { + qCInfo(lcPropagateDownload()) << "file is locked: making it read only"; + FileSystem::setFileReadOnly(fn, true); + } + FileSystem::setFileHidden(fn, false); // Maybe we downloaded a newer version of the file than we thought we would... diff --git a/src/libsync/syncfileitem.cpp b/src/libsync/syncfileitem.cpp index cc7afe0f0e811..f13dacae3eb64 100644 --- a/src/libsync/syncfileitem.cpp +++ b/src/libsync/syncfileitem.cpp @@ -45,6 +45,13 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri rec._checksumHeader = _checksumHeader; rec._e2eMangledName = _encryptedFileName.toUtf8(); rec._isE2eEncrypted = _isEncrypted; + rec._locked = _locked == LockStatus::LockedItem; + rec._lockOwnerDisplayName = _lockOwnerDisplayName; + rec._lockOwnerId = _lockOwnerId; + rec._lockOwnerType = static_cast(_lockOwnerType); + rec._lockEditorApp = _lockEditorApp; + rec._lockTime = _lockTime; + rec._lockTimeout = _lockTimeout; // Update the inode if possible rec._inode = _inode; @@ -75,6 +82,13 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec item->_checksumHeader = rec._checksumHeader; item->_encryptedFileName = rec.e2eMangledName(); item->_isEncrypted = rec._isE2eEncrypted; + item->_locked = rec._locked ? LockStatus::LockedItem : LockStatus::UnlockedItem; + item->_lockOwnerDisplayName = rec._lockOwnerDisplayName; + item->_lockOwnerId = rec._lockOwnerId; + item->_lockOwnerType = static_cast(rec._lockOwnerType); + item->_lockEditorApp = rec._lockEditorApp; + item->_lockTime = rec._lockTime; + item->_lockTimeout = rec._lockTimeout; return item; } diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index a38b4e94fc2c7..9dc6fa4f522d0 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -93,6 +93,21 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem }; Q_ENUM(Status) + enum class LockStatus { + UnlockedItem = 0, + LockedItem = 1, + }; + + Q_ENUM(LockStatus) + + enum class LockOwnerType : int{ + UserLock = 0, + AppLock = 1, + TokenLock = 2, + }; + + Q_ENUM(LockOwnerType) + SyncJournalFileRecord toSyncJournalFileRecordWithInode(const QString &localFileName) const; /** Creates a basic SyncFileItem from a DB record @@ -278,6 +293,14 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem QString _directDownloadUrl; QString _directDownloadCookies; + + LockStatus _locked = LockStatus::UnlockedItem; + QString _lockOwnerId; + QString _lockOwnerDisplayName; + LockOwnerType _lockOwnerType = LockOwnerType::UserLock; + QString _lockEditorApp; + qint64 _lockTime = 0; + qint64 _lockTimeout = 0; }; inline bool operator<(const SyncFileItemPtr &item1, const SyncFileItemPtr &item2) diff --git a/test/syncenginetestutils.cpp b/test/syncenginetestutils.cpp index 1d813e3ff3a0c..86873ca8ff5a5 100644 --- a/test/syncenginetestutils.cpp +++ b/test/syncenginetestutils.cpp @@ -298,11 +298,13 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces // Don't care about the request and just return a full propfind const QString davUri { QStringLiteral("DAV:") }; const QString ocUri { QStringLiteral("http://owncloud.org/ns") }; + const QString ncUri { QStringLiteral("http://nextcloud.org/ns") }; QBuffer buffer { &payload }; buffer.open(QIODevice::WriteOnly); QXmlStreamWriter xml(&buffer); xml.writeNamespace(davUri, QStringLiteral("d")); xml.writeNamespace(ocUri, QStringLiteral("oc")); + xml.writeNamespace(ncUri, QStringLiteral("nc")); xml.writeStartDocument(); xml.writeStartElement(davUri, QStringLiteral("multistatus")); auto writeFileResponse = [&](const FileInfo &fileInfo) { diff --git a/test/testlocaldiscovery.cpp b/test/testlocaldiscovery.cpp index d84f95345ffbb..c3719d0fc0117 100644 --- a/test/testlocaldiscovery.cpp +++ b/test/testlocaldiscovery.cpp @@ -594,6 +594,55 @@ private slots: auto expectedState = fakeFolder.currentLocalState(); QCOMPARE(fakeFolder.currentRemoteState(), expectedState); } + + void testDiscoverLockChanges() + { +// Logger::instance()->setLogDebug(true); + + FakeFolder fakeFolder{FileInfo{}}; + fakeFolder.syncEngine().account()->setCapabilities({{"activity", QVariantMap{{"apiv2", QVariantList{"filters", "filters-api", "previews", "rich-strings"}}}}, + {"bruteforce", QVariantMap{{"delay", 0}}}, + {"core", QVariantMap{{"pollinterval", 60}, {"webdav-root", "remote.php/webdav"}}}, + {"dav", QVariantMap{{"chunking", "1.0"}}}, + {"files", QVariantMap{{"bigfilechunking", true}, {"blacklisted_files", QVariantList{".htaccess"}}, + {"comments", true}, + {"directEditing", QVariantMap{{"etag", "c748e8fc588b54fc5af38c4481a19d20"}, {"url", "https://nextcloud.local/ocs/v2.php/apps/files/api/v1/directEditing"}}}, + {"locking", "1.0"}}}}); + + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + const QString fooFileRootFolder("foo"); + const QString barFileRootFolder("bar"); + const QString fooFileSubFolder("subfolder/foo"); + const QString barFileSubFolder("subfolder/bar"); + const QString fooFileAaaSubFolder("aaa/subfolder/foo"); + const QString barFileAaaSubFolder("aaa/subfolder/bar"); + + fakeFolder.remoteModifier().insert(fooFileRootFolder); + + fakeFolder.remoteModifier().insert(barFileRootFolder); + fakeFolder.remoteModifier().find("bar")->extraDavProperties = "1" + "0" + "user1" + "user1" + "user1" + "164804670720020"; + + fakeFolder.remoteModifier().mkdir(QStringLiteral("subfolder")); + fakeFolder.remoteModifier().insert(fooFileSubFolder); + fakeFolder.remoteModifier().insert(barFileSubFolder); + fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa/subfolder")); + fakeFolder.remoteModifier().insert(fooFileAaaSubFolder); + fakeFolder.remoteModifier().insert(barFileAaaSubFolder); + + QVERIFY(fakeFolder.syncOnce()); + + fakeFolder.remoteModifier().find("bar")->extraDavProperties = "0"; + + fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem); + QVERIFY(fakeFolder.syncOnce()); + } }; QTEST_GUILESS_MAIN(TestLocalDiscovery)