From 5902ff507fe1b940eb96ff0ad26f3434e75e007d Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 19 Sep 2024 17:56:34 +0200 Subject: [PATCH 1/4] store lock tokens in database Signed-off-by: Matthieu Gallien --- src/common/syncjournaldb.cpp | 30 +++++++++++++++++++----------- src/common/syncjournalfilerecord.h | 1 + 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index 895ad115c4d81..bbdf63346bab7 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -49,7 +49,7 @@ 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, " \ - " lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe" \ + " lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, sharedByMe" \ " FROM metadata" \ " LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id" @@ -74,9 +74,10 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que rec._lockstate._lockEditorApp = query.stringValue(16); rec._lockstate._lockTime = query.int64Value(17); rec._lockstate._lockTimeout = query.int64Value(18); - rec._isShared = query.intValue(19) > 0; - rec._lastShareStateFetchedTimestamp = query.int64Value(20); - rec._sharedByMe = query.intValue(21) > 0; + rec._lockstate._lockToken = query.stringValue(19); + rec._isShared = query.intValue(20) > 0; + rec._lastShareStateFetchedTimestamp = query.int64Value(21); + rec._sharedByMe = query.intValue(22) > 0; } static QByteArray defaultJournalMode(const QString &dbPath) @@ -823,6 +824,7 @@ bool SyncJournalDb::updateMetadataTableStructure() addColumn(QStringLiteral("lockOwnerEditor"), QStringLiteral("TEXT")); addColumn(QStringLiteral("lockTime"), QStringLiteral("INTEGER")); addColumn(QStringLiteral("lockTimeout"), QStringLiteral("INTEGER")); + addColumn(QStringLiteral("lockToken"), QStringLiteral("TEXT")); SqlQuery query(_db); query.prepare("CREATE INDEX IF NOT EXISTS caseconflicts_basePath ON caseconflicts(basePath);"); @@ -984,8 +986,8 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & 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, lock, lockType, lockOwnerDisplayName, lockOwnerId, " - "lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe) " - "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, ?26, ?27, ?28);"), + "lockOwnerEditor, lockTime, lockTimeout, lockToken, isShared, lastShareStateFetchedTimestmap, sharedByMe) " + "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, ?26, ?27, ?28, ?29);"), _db); if (!query) { return query->error(); @@ -1016,9 +1018,10 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & query->bindValue(23, record._lockstate._lockEditorApp); query->bindValue(24, record._lockstate._lockTime); query->bindValue(25, record._lockstate._lockTimeout); - query->bindValue(26, record._isShared); - query->bindValue(27, record._lastShareStateFetchedTimestamp); - query->bindValue(28, record._sharedByMe); + query->bindValue(26, record._lockstate._lockToken); + query->bindValue(27, record._isShared); + query->bindValue(28, record._lastShareStateFetchedTimestamp); + query->bindValue(29, record._sharedByMe); if (!query->exec()) { return query->error(); @@ -1543,7 +1546,7 @@ bool SyncJournalDb::updateLocalMetadata(const QString &filename, const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordLocalMetadataQuery, QByteArrayLiteral("UPDATE metadata" " SET inode=?2, modtime=?3, filesize=?4, lock=?5, lockType=?6," " lockOwnerDisplayName=?7, lockOwnerId=?8, lockOwnerEditor = ?9," - " lockTime=?10, lockTimeout=?11" + " lockTime=?10, lockTimeout=?11, lockToken=?12" " WHERE phash == ?1;"), _db); if (!query) { @@ -1561,7 +1564,12 @@ bool SyncJournalDb::updateLocalMetadata(const QString &filename, query->bindValue(9, lockInfo._lockEditorApp); query->bindValue(10, lockInfo._lockTime); query->bindValue(11, lockInfo._lockTimeout); - return query->exec(); + query->bindValue(12, lockInfo._lockToken); + if (!query->exec()) { + qCDebug(lcDb) << "database error:" << query->error(); + return false; + } + return true; } Optional SyncJournalDb::hasHydratedOrDehydratedFiles(const QByteArray &filename) diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h index 7270fac137962..c7321c15f5600 100644 --- a/src/common/syncjournalfilerecord.h +++ b/src/common/syncjournalfilerecord.h @@ -39,6 +39,7 @@ struct SyncJournalFileLockInfo { QString _lockEditorApp; qint64 _lockTime = 0; qint64 _lockTimeout = 0; + QString _lockToken; }; /** From bc160637631a2586307d68108a85141f1e5aee6a Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 19 Sep 2024 18:50:33 +0200 Subject: [PATCH 2/4] receive and decode tokens for locks Signed-off-by: Matthieu Gallien --- src/libsync/discovery.cpp | 4 +++- src/libsync/discoveryphase.cpp | 7 +++++-- src/libsync/discoveryphase.h | 1 + src/libsync/lockfilejobs.cpp | 4 ++++ src/libsync/lockfilejobs.h | 1 + src/libsync/syncengine.cpp | 1 + src/libsync/syncfileitem.cpp | 3 +++ src/libsync/syncfileitem.h | 1 + 8 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 57699bee5d77a..dc1a0312ffd5a 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -668,6 +668,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it item->_lockEditorApp = serverEntry.lockEditorApp; item->_lockTime = serverEntry.lockTime; item->_lockTimeout = serverEntry.lockTimeout; + item->_lockToken = serverEntry.lockToken; qCDebug(lcDisco()) << "item lock for:" << item->_file << item->_locked @@ -676,7 +677,8 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it << item->_lockOwnerType << item->_lockEditorApp << item->_lockTime - << item->_lockTimeout; + << item->_lockTimeout + << item->_lockToken; // Check for missing server data { diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 0041205c6ca62..02722ceedac9b 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -415,7 +415,8 @@ void DiscoverySingleDirectoryJob::start() << "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"; + << "http://nextcloud.org/ns:lock-timeout" + << "http://nextcloud.org/ns:lock-token"; } props << "http://nextcloud.org/ns:is-mount-root"; @@ -545,7 +546,9 @@ static void propertyMapToRemoteInfo(const QMap &map, RemotePer result.lockTimeout = 0; } } - + if (property == "lock-token") { + result.lockToken = value; + } } if (result.isDirectory && map.contains("size")) { diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index 7d53f1336d698..ff38d739118ae 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -86,6 +86,7 @@ struct RemoteInfo QString lockEditorApp; qint64 lockTime = 0; qint64 lockTimeout = 0; + QString lockToken; }; struct LocalInfo diff --git a/src/libsync/lockfilejobs.cpp b/src/libsync/lockfilejobs.cpp index aa4b8af428731..77e48afcbc66f 100644 --- a/src/libsync/lockfilejobs.cpp +++ b/src/libsync/lockfilejobs.cpp @@ -115,6 +115,7 @@ void LockFileJob::setFileRecordLocked(SyncJournalFileRecord &record) const record._lockstate._lockEditorApp = _editorName; record._lockstate._lockTime = _lockTime; record._lockstate._lockTimeout = _lockTimeout; + record._lockstate._lockToken = _lockToken; if (!_etag.isEmpty()) { record._etag = _etag; } @@ -129,6 +130,7 @@ void LockFileJob::resetState() _userId.clear(); _lockTime = 0; _lockTimeout = 0; + _lockToken.clear(); } SyncJournalFileRecord LockFileJob::handleReply() @@ -241,6 +243,8 @@ void LockFileJob::decodeStartElement(const QString &name, _editorName = reader.readElementText(); } else if (name == QStringLiteral("getetag")) { _etag = reader.readElementText().toUtf8(); + } else if (name == QStringLiteral("lock-token")) { + _lockToken = reader.readElementText(); } } diff --git a/src/libsync/lockfilejobs.h b/src/libsync/lockfilejobs.h index 8b2e287f6d4c0..9b8be46c5523f 100644 --- a/src/libsync/lockfilejobs.h +++ b/src/libsync/lockfilejobs.h @@ -59,6 +59,7 @@ class OWNCLOUDSYNC_EXPORT LockFileJob : public AbstractNetworkJob QByteArray _etag; qint64 _lockTime = 0; qint64 _lockTimeout = 0; + QString _lockToken; QString _remoteSyncPathWithTrailingSlash; QString _localSyncPath; }; diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 592078dda140c..086a025dee4fa 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -445,6 +445,7 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item) lockInfo._lockOwnerType = static_cast(item->_lockOwnerType); lockInfo._lockOwnerDisplayName = item->_lockOwnerDisplayName; lockInfo._lockEditorApp = item->_lockOwnerDisplayName; + lockInfo._lockToken = item->_lockToken; if (!_journal->updateLocalMetadata(item->_file, item->_modtime, item->_size, item->_inode, lockInfo)) { qCWarning(lcEngine) << "Could not update local metadata for file" << item->_file; diff --git a/src/libsync/syncfileitem.cpp b/src/libsync/syncfileitem.cpp index 7ac7ca15378c3..6b32a5fae1ceb 100644 --- a/src/libsync/syncfileitem.cpp +++ b/src/libsync/syncfileitem.cpp @@ -125,6 +125,7 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri rec._lockstate._lockEditorApp = _lockEditorApp; rec._lockstate._lockTime = _lockTime; rec._lockstate._lockTimeout = _lockTimeout; + rec._lockstate._lockToken = _lockToken; // Update the inode if possible rec._inode = _inode; @@ -163,6 +164,7 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec item->_lockEditorApp = rec._lockstate._lockEditorApp; item->_lockTime = rec._lockstate._lockTime; item->_lockTimeout = rec._lockstate._lockTimeout; + item->_lockToken = rec._lockstate._lockToken; item->_sharedByMe = rec._sharedByMe; item->_isShared = rec._isShared; item->_lastShareStateFetchedTimestamp = rec._lastShareStateFetchedTimestamp; @@ -251,6 +253,7 @@ void SyncFileItem::updateLockStateFromDbRecord(const SyncJournalFileRecord &dbRe _lockEditorApp = dbRecord._lockstate._lockEditorApp; _lockTime = dbRecord._lockstate._lockTime; _lockTimeout = dbRecord._lockstate._lockTimeout; + _lockToken = dbRecord._lockstate._lockToken; } } diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index 3f6a52898611d..4bf481a138c86 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -326,6 +326,7 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem QString _lockEditorApp; qint64 _lockTime = 0; qint64 _lockTimeout = 0; + QString _lockToken; bool _isShared = false; time_t _lastShareStateFetchedTimestamp = 0; From 2a953b4bbe979196258b2445d27b6e0a540af459 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Mon, 23 Sep 2024 18:32:10 +0200 Subject: [PATCH 3/4] if a file is locked/ provide token during upload will add needed lock token in final move headers for chunk upload Signed-off-by: Matthieu Gallien --- src/libsync/propagateuploadng.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp index f778f79ef6c9a..a67225d2a2337 100644 --- a/src/libsync/propagateuploadng.cpp +++ b/src/libsync/propagateuploadng.cpp @@ -328,6 +328,9 @@ void PropagateUploadFileNG::finishUpload() const auto fileSize = _fileToUpload._size; headers[QByteArrayLiteral("OC-Total-Length")] = QByteArray::number(fileSize); + if (_item->_locked == SyncFileItem::LockStatus::LockedItem) { + headers[QByteArrayLiteral("If")] = (QLatin1String("<") + propagator()->account()->davUrl().toString() + _fileToUpload._file + "> (_lockToken.toUtf8() + ">)").toUtf8(); + } const auto job = new MoveJob(propagator()->account(), Utility::concatUrlPath(chunkUploadFolderUrl(), "/.file"), destination, headers, this); _jobs.append(job); From 55e1643b2614a785ecf6c3a77a81758e20c23325 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Mon, 23 Sep 2024 18:48:47 +0200 Subject: [PATCH 4/4] provide LOCK token when uploading a single file via PUT for simple webDAV upload via PUT verb, provide the needed lock token Signed-off-by: Matthieu Gallien --- src/libsync/propagateuploadv1.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp index a32e796a597de..7467b2ce31560 100644 --- a/src/libsync/propagateuploadv1.cpp +++ b/src/libsync/propagateuploadv1.cpp @@ -102,6 +102,10 @@ void PropagateUploadFileV1::startNextChunk() QString path = _fileToUpload._file; + if (_item->_locked == SyncFileItem::LockStatus::LockedItem) { + headers[QByteArrayLiteral("If")] = (QLatin1String("<") + propagator()->account()->davUrl().toString() + _fileToUpload._file + "> (_lockToken.toUtf8() + ">)").toUtf8(); + } + qint64 chunkStart = 0; qint64 currentChunkSize = fileSize; bool isFinalChunk = false;