From b4de2afcbb913fc3c31e0517bfbc55d7ddd66065 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Wed, 16 Oct 2024 13:12:43 +0200 Subject: [PATCH 1/3] in automated tests, update modtime when modifying the content of a file why one would not update the modification time when modifying a remote file during automated tests there is no reason to not update the modification time as this is what is done by the server Signed-off-by: Matthieu Gallien --- test/syncenginetestutils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/syncenginetestutils.cpp b/test/syncenginetestutils.cpp index 933814925fd57..bb7c3f47e4a8f 100644 --- a/test/syncenginetestutils.cpp +++ b/test/syncenginetestutils.cpp @@ -173,6 +173,7 @@ void FileInfo::setContents(const QString &relativePath, char contentChar) FileInfo *file = findInvalidatingEtags(relativePath); Q_ASSERT(file); file->contentChar = contentChar; + file->lastModified = QDateTime::currentDateTimeUtc(); } void FileInfo::appendByte(const QString &relativePath) From 9a56c4836a561bdc5fdf0b3f884e5d2e96ba22e3 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Wed, 16 Oct 2024 15:06:13 +0200 Subject: [PATCH 2/3] handle NEW/NEW conflicts by being real conflicts NEW/NEW conflicts could sometime be ignored and replaced by update metadata instructions we stop doing this and handle them like any other conflicts that would cause more download from the server those conflicts would be solved automatically in case this is not a real conflict but the client was missing the server reply with the updated metadata will enable more changes to improve MOVE detection from server side Signed-off-by: Matthieu Gallien --- src/libsync/discovery.cpp | 2 +- test/testchunkingng.cpp | 22 ++++------------------ test/testsyncengine.cpp | 2 +- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index eac483715047b..d8c23346768e7 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -1546,7 +1546,7 @@ void ProcessDirectoryJob::processFileConflict(const SyncFileItemPtr &item, Proce // If there's no content hash, use heuristics if (serverEntry.checksumHeader.isEmpty()) { // If the size or mtime is different, it's definitely a conflict. - bool isConflict = (serverEntry.size != localEntry.size) || (serverEntry.modtime != localEntry.modtime); + bool isConflict = (serverEntry.size != localEntry.size) || (serverEntry.modtime != localEntry.modtime) || (dbEntry.isValid() && dbEntry._modtime != localEntry.modtime && serverEntry.modtime == localEntry.modtime); // It could be a conflict even if size and mtime match! // diff --git a/test/testchunkingng.cpp b/test/testchunkingng.cpp index 913d9f12ef5d0..85865a08ab44a 100644 --- a/test/testchunkingng.cpp +++ b/test/testchunkingng.cpp @@ -315,35 +315,22 @@ private slots: fakeFolder.localModifier().insert("A/a0", size); QVERIFY(!fakeFolder.syncOnce()); // error: abort! - // Now the next sync gets a NEW/NEW conflict and since there's no checksum - // it just becomes a UPDATE_METADATA - auto checkEtagUpdated = [&](SyncFileItemVector &items) { - QCOMPARE(items.size(), 1); - QCOMPARE(items[0]->_file, QLatin1String("A")); - SyncJournalFileRecord record; - QVERIFY(fakeFolder.syncJournal().getFileRecord(QByteArray("A/a0"), &record)); - QCOMPARE(record._etag, fakeFolder.remoteModifier().find("A/a0")->etag); - }; - auto connection = connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, checkEtagUpdated); QVERIFY(fakeFolder.syncOnce()); - disconnect(connection); QCOMPARE(nGET, 0); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - // Test 2: modified file upload aborted + nGET = 0; fakeFolder.localModifier().appendByte("A/a0"); QVERIFY(!fakeFolder.syncOnce()); // error: abort! // An EVAL/EVAL conflict is also UPDATE_METADATA when there's no checksums - connection = connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, checkEtagUpdated); QVERIFY(fakeFolder.syncOnce()); - disconnect(connection); - QCOMPARE(nGET, 0); + QCOMPARE(nGET, 1); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - // Test 3: modified file upload aborted, with good checksums + nGET = 0; fakeFolder.localModifier().appendByte("A/a0"); QVERIFY(!fakeFolder.syncOnce()); // error: abort! @@ -352,12 +339,11 @@ private slots: fakeFolder.remoteModifier().find("A/a0")->checksums = moveChecksumHeader; QVERIFY(fakeFolder.syncOnce()); - disconnect(connection); QCOMPARE(nGET, 0); // no new download, just a metadata update! QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - // Test 4: New file, that gets deleted locally before the next sync + nGET = 0; fakeFolder.localModifier().insert("A/a3", size); QVERIFY(!fakeFolder.syncOnce()); // error: abort! fakeFolder.localModifier().remove("A/a3"); diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index e3c5f499af8b4..fedffeb5372ee 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -427,7 +427,7 @@ private slots: QTest::newRow("Same mtime, but no server checksum -> ignored in reconcile") << true << QByteArray() - << 0; + << 1; QTest::newRow("Same mtime, weak server checksum differ -> downloaded") << true << QByteArray("Adler32:bad") From 08433c14896921edaff21fda9efa28d6070aebc4 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Wed, 16 Oct 2024 12:10:10 +0200 Subject: [PATCH 3/3] remove the enforcement of identical etag for a server side item MOVE orignally added by https://github.com/owncloud/client/pull/6632 most probably a too strong assumption on the behavior of the Nextcloud server better check real item metadata like fileid, size or modification time Signed-off-by: Matthieu Gallien --- src/libsync/discovery.cpp | 5 ++--- test/testsyncmove.cpp | 32 -------------------------------- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index d8c23346768e7..a2d3633c96b24 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -868,9 +868,8 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it done = true; return; } - if (!serverEntry.isDirectory && base._etag != serverEntry.etag) { - /* File with different etag, don't do a rename, but download the file again */ - qCInfo(lcDisco, "file etag different, not a rename"); + if (!serverEntry.isDirectory && (base._modtime != serverEntry.modtime || base._fileSize != serverEntry.size)) { + qCInfo(lcDisco, "file metadata different, forcing a download of the new file"); done = true; return; } diff --git a/test/testsyncmove.cpp b/test/testsyncmove.cpp index e985e0a722aa0..f3a6d7c1ddd35 100644 --- a/test/testsyncmove.cpp +++ b/test/testsyncmove.cpp @@ -739,38 +739,6 @@ private slots: } } - // https://github.com/owncloud/client/issues/6629#issuecomment-402450691 - // When a file is moved and the server mtime was not in sync, the local mtime should be kept - void testMoveAndMTimeChange() - { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - OperationCounter counter; - fakeFolder.setServerOverride(counter.functor()); - - // Changing the mtime on the server (without invalidating the etag) - fakeFolder.remoteModifier().find("A/a1")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-50000); - fakeFolder.remoteModifier().find("A/a2")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-40000); - - // Move a few files - fakeFolder.remoteModifier().rename("A/a1", "A/a1_server_renamed"); - fakeFolder.localModifier().rename("A/a2", "A/a2_local_renamed"); - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(counter.nGET, 0); - QCOMPARE(counter.nPUT, 0); - QCOMPARE(counter.nMOVE, 1); - QCOMPARE(counter.nDELETE, 0); - - // Another sync should do nothing - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(counter.nGET, 0); - QCOMPARE(counter.nPUT, 0); - QCOMPARE(counter.nMOVE, 1); - QCOMPARE(counter.nDELETE, 0); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } - // Test for https://github.com/owncloud/client/issues/6694 void testInvertFolderHierarchy() {