diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index ca5ae1faaeaba..b5e4f97ca1dc7 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._lockstate._locked = query.intValue(12) > 0; + rec._lockstate._lockOwnerDisplayName = query.stringValue(13); + rec._lockstate._lockOwnerId = query.stringValue(14); + rec._lockstate._lockOwnerType = query.int64Value(15); + rec._lockstate._lockEditorApp = query.stringValue(16); + rec._lockstate._lockTime = query.int64Value(17); + rec._lockstate._lockTimeout = query.int64Value(18); } static QByteArray defaultJournalMode(const QString &dbPath) @@ -658,39 +666,31 @@ bool SyncJournalDb::updateMetadataTableStructure() return false; } - if (columns.indexOf("fileid") == -1) { - SqlQuery query(_db); - query.prepare("ALTER TABLE metadata ADD COLUMN fileid VARCHAR(128);"); - if (!query.exec()) { - sqlFail(QStringLiteral("updateMetadataTableStructure: Add column fileid"), query); - re = false; - } + const auto addColumn = [this, &columns, &re] (const QString &columnName, const QString &dataType, const bool withIndex = false) { + 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; + } - query.prepare("CREATE INDEX metadata_file_id ON metadata(fileid);"); - if (!query.exec()) { - sqlFail(QStringLiteral("updateMetadataTableStructure: create index fileid"), query); - re = false; - } - commitInternal(QStringLiteral("update database structure: add fileid col")); - } - if (columns.indexOf("remotePerm") == -1) { - SqlQuery query(_db); - query.prepare("ALTER TABLE metadata ADD COLUMN remotePerm VARCHAR(128);"); - if (!query.exec()) { - sqlFail(QStringLiteral("updateMetadataTableStructure: add column remotePerm"), query); - re = false; - } - commitInternal(QStringLiteral("update database structure (remotePerm)")); - } - if (columns.indexOf("filesize") == -1) { - SqlQuery query(_db); - query.prepare("ALTER TABLE metadata ADD COLUMN filesize BIGINT;"); - if (!query.exec()) { - sqlFail(QStringLiteral("updateDatabaseStructure: add column filesize"), query); - re = false; + if (withIndex) { + query.prepare(QStringLiteral("CREATE INDEX metadata_%1 ON metadata(%1);").arg(columnName).toLatin1()); + if (!query.exec()) { + sqlFail(QStringLiteral("updateMetadataTableStructure: create index %1").arg(columnName), query); + re = false; + } + } + commitInternal(QStringLiteral("update database structure: add %1 column").arg(columnName)); } - commitInternal(QStringLiteral("update database structure: add filesize col")); - } + }; + + addColumn(QStringLiteral("fileid"), QStringLiteral("VARCHAR(128)"), true); + addColumn(QStringLiteral("remotePerm"), QStringLiteral("VARCHAR(128)")); + addColumn(QStringLiteral("filesize"), QStringLiteral("BIGINT")); if (true) { SqlQuery query(_db); @@ -722,54 +722,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 +763,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 +884,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._lockstate._locked ? "true" : "false") << "lock owner type:" << record._lockstate._lockOwnerType + << "lock owner:" << record._lockstate._lockOwnerDisplayName << "lock owner id:" << record._lockstate._lockOwnerId + << "lock editor:" << record._lockstate._lockEditorApp; const qint64 phash = getPHash(record._path); if (!checkConnect()) { @@ -943,8 +911,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 +938,13 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & query->bindValue(16, contentChecksumTypeId); query->bindValue(17, record._e2eMangledName); query->bindValue(18, record._isE2eEncrypted); + query->bindValue(19, record._lockstate._locked ? 1 : 0); + query->bindValue(20, record._lockstate._lockOwnerType); + query->bindValue(21, record._lockstate._lockOwnerDisplayName); + query->bindValue(22, record._lockstate._lockOwnerId); + query->bindValue(23, record._lockstate._lockEditorApp); + query->bindValue(24, record._lockstate._lockTime); + query->bindValue(25, record._lockstate._lockTimeout); if (!query->exec()) { return query->error(); diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h index b47f98e5b5d73..9f3e0b5187d48 100644 --- a/src/common/syncjournalfilerecord.h +++ b/src/common/syncjournalfilerecord.h @@ -31,6 +31,16 @@ namespace OCC { class SyncFileItem; +struct SyncJournalFileLockInfo { + bool _locked = false; + QString _lockOwnerDisplayName; + QString _lockOwnerId; + qint64 _lockOwnerType = 0; + QString _lockEditorApp; + qint64 _lockTime = 0; + qint64 _lockTimeout = 0; +}; + /** * @brief The SyncJournalFileRecord class * @ingroup libsync @@ -70,6 +80,7 @@ class OCSYNC_EXPORT SyncJournalFileRecord QByteArray _checksumHeader; QByteArray _e2eMangledName; bool _isE2eEncrypted = false; + SyncJournalFileLockInfo _lockstate; }; bool OCSYNC_EXPORT diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 63b75a05fa25d..7527d06d53a46 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -363,6 +363,8 @@ 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._lockstate._locked ? "locked" : "not locked"; qCInfo(lcDisco).nospace() << "Processing " << path._original << " | valid: " << dbEntry.isValid() << "/" << hasLocal << "/" << hasServer << " | mtime: " << dbEntry._modtime << "/" << localEntry.modtime << "/" << serverEntry.modtime @@ -374,7 +376,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: " << localFileIsLocked << "//" << serverFileIsLocked; if (localEntry.isValid() && !serverEntry.isValid() @@ -483,6 +486,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..07202d41f210d 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._lockstate._locked = _locked == LockStatus::LockedItem; + rec._lockstate._lockOwnerDisplayName = _lockOwnerDisplayName; + rec._lockstate._lockOwnerId = _lockOwnerId; + rec._lockstate._lockOwnerType = static_cast(_lockOwnerType); + rec._lockstate._lockEditorApp = _lockEditorApp; + rec._lockstate._lockTime = _lockTime; + rec._lockstate._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._lockstate._locked ? LockStatus::LockedItem : LockStatus::UnlockedItem; + item->_lockOwnerDisplayName = rec._lockstate._lockOwnerDisplayName; + item->_lockOwnerId = rec._lockstate._lockOwnerId; + item->_lockOwnerType = static_cast(rec._lockstate._lockOwnerType); + item->_lockEditorApp = rec._lockstate._lockEditorApp; + item->_lockTime = rec._lockstate._lockTime; + item->_lockTimeout = rec._lockstate._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)