diff --git a/src/gui/userstatusselectormodel.cpp b/src/gui/userstatusselectormodel.cpp index 6ae0dd256..fea9fb61c 100644 --- a/src/gui/userstatusselectormodel.cpp +++ b/src/gui/userstatusselectormodel.cpp @@ -436,7 +436,7 @@ QString UserStatusSelectorModel::clearAtReadable(const Optional &clearA } case ClearAtType::Timestamp: { - const int difference = static_cast(clearAt->_timestamp - _dateTimeProvider->currentDateTime().toTime_t()); + const int difference = static_cast(clearAt->_timestamp - _dateTimeProvider->currentDateTime().toSecsSinceEpoch()); return timeDifferenceToString(difference); } diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index c9a3b1d53..a7e41d8bc 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -539,19 +539,11 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( } else { item->_instruction = CSYNC_INSTRUCTION_SYNC; } - } else if (dbEntry._modtime <= 0 && serverEntry.modtime > 0) { + } else if (dbEntry._modtime != serverEntry.modtime && localEntry.size == serverEntry.size && dbEntry._fileSize == serverEntry.size && dbEntry._etag == serverEntry.etag) { item->_direction = SyncFileItem::Down; item->_modtime = serverEntry.modtime; item->_size = sizeOnServer; - if (serverEntry.isDirectory) { - ENFORCE(dbEntry.isDirectory()); - item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; - } else if (!localEntry.isValid() && _queryLocal != ParentNotChanged) { - // Deleted locally, changed on server - item->_instruction = CSYNC_INSTRUCTION_NEW; - } else { - item->_instruction = CSYNC_INSTRUCTION_SYNC; - } + item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; } else if (dbEntry._remotePerm != serverEntry.remotePerm || dbEntry._fileId != serverEntry.fileId || metaDataSizeNeedsUpdateForE2EeFilePlaceholder) { if (metaDataSizeNeedsUpdateForE2EeFilePlaceholder) { // we are updating placeholder sizes after migrating from older versions with VFS + E2EE implicit hydration not supported @@ -848,6 +840,13 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( if (_queryLocal != NormalQuery && _queryServer != NormalQuery) recurse = false; + if ((item->_direction == SyncFileItem::Down || item->_instruction == CSYNC_INSTRUCTION_CONFLICT || item->_instruction == CSYNC_INSTRUCTION_NEW || item->_instruction == CSYNC_INSTRUCTION_SYNC) && + (item->_modtime <= 0 || item->_modtime >= 0xFFFFFFFF)) { + item->_instruction = CSYNC_INSTRUCTION_ERROR; + item->_errorString = tr("Cannot sync due to invalid modification time"); + item->_status = SyncFileItem::Status::NormalError; + } + auto recurseQueryLocal = _queryLocal == ParentNotChanged ? ParentNotChanged : localEntry.isDirectory || item->_instruction == CSYNC_INSTRUCTION_RENAME ? NormalQuery : ParentDontExist; processFileFinalize(item, path, recurse, recurseQueryLocal, recurseQueryServer); }; @@ -875,14 +874,14 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( } path._original = originalPath; item->_originalFile = path._original; - item->_modtime = base._modtime; - item->_inode = base._inode; + item->_modtime = base.isValid() ? base._modtime : localEntry.modtime; + item->_inode = base.isValid() ? base._inode : localEntry.inode; item->_instruction = CSYNC_INSTRUCTION_RENAME; item->_direction = direction; - item->_fileId = base._fileId; - item->_remotePerm = base._remotePerm; - item->_etag = base._etag; - item->_type = base._type; + item->_fileId = base.isValid() ? base._fileId : QByteArray{}; + item->_remotePerm = base.isValid() ? base._remotePerm : RemotePermissions{}; + item->_etag = base.isValid() ? base._etag : QByteArray{}; + item->_type = base.isValid() ? base._type : localEntry.type; }; if (!localEntry.isValid()) { @@ -1001,8 +1000,8 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( item->_modtime = localEntry.modtime; item->_type = localEntry.isDirectory ? ItemTypeDirectory : ItemTypeFile; _childModified = true; - } else if (dbEntry._modtime > 0 && localEntry.modtime <= 0) { - item->_instruction = CSYNC_INSTRUCTION_SYNC; + } else if (dbEntry._modtime > 0 && (localEntry.modtime <= 0 || localEntry.modtime >= 0xFFFFFFFF) && dbEntry._fileSize == localEntry.size) { + item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; item->_direction = SyncFileItem::Down; item->_size = localEntry.size > 0 ? localEntry.size : dbEntry._fileSize; item->_modtime = dbEntry._modtime; diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 38f941dde..d71dca433 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -407,7 +407,10 @@ static void propertyMapToRemoteInfo(const QMap &map, RemoteInf } else if (property == QLatin1String("getlastmodified")) { const auto date = QDateTime::fromString(value, Qt::RFC2822Date); Q_ASSERT(date.isValid()); - result.modtime = date.toTime_t(); + result.modtime = 0; + if (date.toSecsSinceEpoch() > 0) { + result.modtime = date.toSecsSinceEpoch(); + } } else if (property == QLatin1String("getcontentlength")) { // See #4573, sometimes negative size values are returned bool ok = false; diff --git a/src/libsync/ocsuserstatusconnector.cpp b/src/libsync/ocsuserstatusconnector.cpp index 95f3810e2..19cb34b3d 100644 --- a/src/libsync/ocsuserstatusconnector.cpp +++ b/src/libsync/ocsuserstatusconnector.cpp @@ -110,18 +110,18 @@ quint64 clearAtEndOfToTimestamp(const OCC::ClearAt &clearAt) Q_ASSERT(clearAt._type == OCC::ClearAtType::EndOf); if (clearAt._endof == "day") { - return QDate::currentDate().addDays(1).startOfDay().toTime_t(); + return QDate::currentDate().addDays(1).startOfDay().toSecsSinceEpoch(); } else if (clearAt._endof == "week") { const auto days = Qt::Sunday - QDate::currentDate().dayOfWeek(); - return QDate::currentDate().addDays(days + 1).startOfDay().toTime_t(); + return QDate::currentDate().addDays(days + 1).startOfDay().toSecsSinceEpoch(); } qCWarning(lcOcsUserStatusConnector) << "Can not handle clear at endof day type" << clearAt._endof; - return QDateTime::currentDateTime().toTime_t(); + return QDateTime::currentDateTime().toSecsSinceEpoch(); } quint64 clearAtPeriodToTimestamp(const OCC::ClearAt &clearAt) { - return QDateTime::currentDateTime().addSecs(clearAt._period).toTime_t(); + return QDateTime::currentDateTime().addSecs(clearAt._period).toSecsSinceEpoch(); } quint64 clearAtToTimestamp(const OCC::ClearAt &clearAt) diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 5bdd72d5b..bc2922070 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -563,7 +563,6 @@ void PropagateDownloadFile::startAfterIsEncryptedIsChecked() return checksum_header.startsWith("SHA") || checksum_header.startsWith("MD5:"); }; - Q_ASSERT(_item->_modtime > 0); if (_item->_modtime <= 0) { qCWarning(lcPropagateDownload()) << "invalid modified time" << _item->_file << _item->_modtime; } diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 3ce782e3c..0306e0f8c 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -353,8 +353,10 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item) if (item->_type == ItemTypeFile) { const auto result = _syncOptions._vfs->convertToPlaceholder(filePath, *item); if (!result) { + item->_status = SyncFileItem::Status::NormalError; item->_instruction = CSYNC_INSTRUCTION_ERROR; item->_errorString = tr("Could not update file: %1").arg(result.error()); + emit itemCompleted(item); return; } } @@ -363,8 +365,10 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item) if (item->_type == ItemTypeVirtualFile) { auto r = _syncOptions._vfs->updateMetadata(filePath, item->_modtime, item->_size, item->_fileId); if (!r) { + item->_status = SyncFileItem::Status::NormalError; item->_instruction = CSYNC_INSTRUCTION_ERROR; item->_errorString = tr("Could not update virtual file metadata: %1").arg(r.error()); + emit itemCompleted(item); return; } } diff --git a/test/syncenginetestutils.cpp b/test/syncenginetestutils.cpp index a5eaaaaf7..1d813e3ff 100644 --- a/test/syncenginetestutils.cpp +++ b/test/syncenginetestutils.cpp @@ -188,6 +188,13 @@ void FileInfo::setModTime(const QString &relativePath, const QDateTime &modTime) file->lastModified = modTime; } +void FileInfo::setModTimeKeepEtag(const QString &relativePath, const QDateTime &modTime) +{ + FileInfo *file = find(relativePath); + Q_ASSERT(file); + file->lastModified = modTime; +} + FileInfo *FileInfo::find(PathComponents pathComponents, const bool invalidateEtags) { if (pathComponents.isEmpty()) { diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h index c434925cc..8abe50848 100644 --- a/test/syncenginetestutils.h +++ b/test/syncenginetestutils.h @@ -128,6 +128,8 @@ public: void setModTime(const QString &relativePath, const QDateTime &modTime) override; + void setModTimeKeepEtag(const QString &relativePath, const QDateTime &modTime); + FileInfo *find(PathComponents pathComponents, const bool invalidateEtags = false); FileInfo *createDir(const QString &relativePath); diff --git a/test/testlocaldiscovery.cpp b/test/testlocaldiscovery.cpp index fa9ae35ce..fa714129d 100644 --- a/test/testlocaldiscovery.cpp +++ b/test/testlocaldiscovery.cpp @@ -386,6 +386,204 @@ private slots: qDebug() << expectedState; QCOMPARE(fakeFolder.currentRemoteState(), expectedState); } + + void testBlockInvalidMtimeSyncRemote() + { + constexpr auto INVALID_MODTIME1 = 0; + constexpr auto INVALID_MODTIME2 = -3600; + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + const QString fooFileRootFolder("foo"); + const QString barFileRootFolder("bar"); + const QString blaFileRootFolder("bla"); + const QString fooFileSubFolder("subfolder/foo"); + const QString barFileSubFolder("subfolder/bar"); + const QString blaFileSubFolder("subfolder/bla"); + + fakeFolder.remoteModifier().insert(fooFileRootFolder); + fakeFolder.remoteModifier().insert(barFileRootFolder); + fakeFolder.remoteModifier().insert(blaFileRootFolder); + fakeFolder.remoteModifier().mkdir(QStringLiteral("subfolder")); + fakeFolder.remoteModifier().insert(fooFileSubFolder); + fakeFolder.remoteModifier().insert(barFileSubFolder); + fakeFolder.remoteModifier().insert(blaFileSubFolder); + + QVERIFY(fakeFolder.syncOnce()); + + fakeFolder.remoteModifier().setModTime(fooFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.remoteModifier().setModTime(barFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.remoteModifier().setModTime(blaFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.remoteModifier().setModTime(fooFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.remoteModifier().setModTime(barFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.remoteModifier().setModTime(blaFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + + QVERIFY(!fakeFolder.syncOnce()); + + QVERIFY(!fakeFolder.syncOnce()); + + fakeFolder.remoteModifier().setModTime(fooFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.remoteModifier().setModTime(barFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.remoteModifier().setModTime(blaFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.remoteModifier().setModTime(fooFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.remoteModifier().setModTime(barFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.remoteModifier().setModTime(blaFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + + QVERIFY(!fakeFolder.syncOnce()); + + QVERIFY(!fakeFolder.syncOnce()); + } + + void testBlockInvalidMtimeSyncLocal() + { + constexpr auto INVALID_MODTIME1 = 0; + constexpr auto INVALID_MODTIME2 = -3600; + + FakeFolder fakeFolder{FileInfo{}}; + + int nGET = 0; + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) { + if (op == QNetworkAccessManager::GetOperation) + ++nGET; + return nullptr; + }); + + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + const QString fooFileRootFolder("foo"); + const QString barFileRootFolder("bar"); + const QString blaFileRootFolder("bla"); + const QString fooFileSubFolder("subfolder/foo"); + const QString barFileSubFolder("subfolder/bar"); + const QString blaFileSubFolder("subfolder/bla"); + + fakeFolder.remoteModifier().insert(fooFileRootFolder); + fakeFolder.remoteModifier().insert(barFileRootFolder); + fakeFolder.remoteModifier().insert(blaFileRootFolder); + fakeFolder.remoteModifier().mkdir(QStringLiteral("subfolder")); + fakeFolder.remoteModifier().insert(fooFileSubFolder); + fakeFolder.remoteModifier().insert(barFileSubFolder); + fakeFolder.remoteModifier().insert(blaFileSubFolder); + + QVERIFY(fakeFolder.syncOnce()); + nGET = 0; + + fakeFolder.localModifier().setModTime(fooFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.localModifier().setModTime(barFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.localModifier().setModTime(blaFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.localModifier().setModTime(fooFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.localModifier().setModTime(barFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + fakeFolder.localModifier().setModTime(blaFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME1)); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 0); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 0); + + fakeFolder.localModifier().setModTime(fooFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.localModifier().setModTime(barFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.localModifier().setModTime(blaFileRootFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.localModifier().setModTime(fooFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.localModifier().setModTime(barFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + fakeFolder.localModifier().setModTime(blaFileSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MODTIME2)); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 0); + + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(nGET, 0); + } + + void testDoNotSyncInvalidFutureMtime() + { + constexpr auto FUTURE_MTIME = 0xFFFFFFFF; + constexpr auto CURRENT_MTIME = 1646057277; + + FakeFolder fakeFolder{FileInfo{}}; + 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().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().setModTime(fooFileRootFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.remoteModifier().setModTime(barFileRootFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.remoteModifier().setModTime(fooFileSubFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.remoteModifier().setModTime(barFileSubFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.remoteModifier().setModTime(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.remoteModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(fooFileRootFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.localModifier().setModTime(barFileRootFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.localModifier().setModTime(fooFileSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.localModifier().setModTime(barFileSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.localModifier().setModTime(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + + QVERIFY(!fakeFolder.syncOnce()); + } + + void testInvalidFutureMtimeRecovery() + { + constexpr auto FUTURE_MTIME = 0xFFFFFFFF; + constexpr auto CURRENT_MTIME = 1646057277; + + FakeFolder fakeFolder{FileInfo{}}; + 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().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().setModTimeKeepEtag(fooFileRootFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(barFileRootFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(fooFileSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(barFileSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.localModifier().setModTime(fooFileRootFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(barFileRootFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(fooFileSubFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(barFileSubFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + + QVERIFY(fakeFolder.syncOnce()); + + QVERIFY(fakeFolder.syncOnce()); + + auto expectedState = fakeFolder.currentLocalState(); + QCOMPARE(fakeFolder.currentRemoteState(), expectedState); + } }; QTEST_GUILESS_MAIN(TestLocalDiscovery) diff --git a/test/testsetuserstatusdialog.cpp b/test/testsetuserstatusdialog.cpp index 045463856..82a077ac6 100644 --- a/test/testsetuserstatusdialog.cpp +++ b/test/testsetuserstatusdialog.cpp @@ -158,7 +158,7 @@ createFakePredefinedStatuses(const QDateTime ¤tTime) OCC::Optional userStatusClearAt; OCC::ClearAt clearAt; clearAt._type = OCC::ClearAtType::Timestamp; - clearAt._timestamp = currentTime.addSecs(60 * 60).toTime_t(); + clearAt._timestamp = currentTime.addSecs(60 * 60).toSecsSinceEpoch(); userStatusClearAt = clearAt; statuses.emplace_back(userStatusId, userStatusMessage, userStatusIcon, @@ -198,7 +198,7 @@ private slots: { OCC::ClearAt clearAt; clearAt._type = OCC::ClearAtType::Timestamp; - clearAt._timestamp = currentDateTime.addDays(1).toTime_t(); + clearAt._timestamp = currentDateTime.addDays(1).toSecsSinceEpoch(); userStatusClearAt = clearAt; } @@ -488,7 +488,7 @@ private slots: OCC::UserStatus userStatus; OCC::ClearAt clearAt; clearAt._type = OCC::ClearAtType::Timestamp; - clearAt._timestamp = currentTime.addSecs(30).toTime_t(); + clearAt._timestamp = currentTime.addSecs(30).toSecsSinceEpoch(); userStatus.setClearAt(clearAt); auto fakeDateTimeProvider = std::make_unique(); @@ -503,7 +503,7 @@ private slots: OCC::UserStatus userStatus; OCC::ClearAt clearAt; clearAt._type = OCC::ClearAtType::Timestamp; - clearAt._timestamp = currentTime.addSecs(60).toTime_t(); + clearAt._timestamp = currentTime.addSecs(60).toSecsSinceEpoch(); userStatus.setClearAt(clearAt); auto fakeDateTimeProvider = std::make_unique(); @@ -518,7 +518,7 @@ private slots: OCC::UserStatus userStatus; OCC::ClearAt clearAt; clearAt._type = OCC::ClearAtType::Timestamp; - clearAt._timestamp = currentTime.addSecs(60 * 30).toTime_t(); + clearAt._timestamp = currentTime.addSecs(60 * 30).toSecsSinceEpoch(); userStatus.setClearAt(clearAt); auto fakeDateTimeProvider = std::make_unique(); @@ -533,7 +533,7 @@ private slots: OCC::UserStatus userStatus; OCC::ClearAt clearAt; clearAt._type = OCC::ClearAtType::Timestamp; - clearAt._timestamp = currentTime.addSecs(60 * 60).toTime_t(); + clearAt._timestamp = currentTime.addSecs(60 * 60).toSecsSinceEpoch(); userStatus.setClearAt(clearAt); auto fakeDateTimeProvider = std::make_unique(); @@ -548,7 +548,7 @@ private slots: OCC::UserStatus userStatus; OCC::ClearAt clearAt; clearAt._type = OCC::ClearAtType::Timestamp; - clearAt._timestamp = currentTime.addSecs(60 * 60 * 4).toTime_t(); + clearAt._timestamp = currentTime.addSecs(60 * 60 * 4).toSecsSinceEpoch(); userStatus.setClearAt(clearAt); auto fakeDateTimeProvider = std::make_unique(); @@ -563,7 +563,7 @@ private slots: OCC::UserStatus userStatus; OCC::ClearAt clearAt; clearAt._type = OCC::ClearAtType::Timestamp; - clearAt._timestamp = currentTime.addDays(1).toTime_t(); + clearAt._timestamp = currentTime.addDays(1).toSecsSinceEpoch(); userStatus.setClearAt(clearAt); auto fakeDateTimeProvider = std::make_unique(); @@ -578,7 +578,7 @@ private slots: OCC::UserStatus userStatus; OCC::ClearAt clearAt; clearAt._type = OCC::ClearAtType::Timestamp; - clearAt._timestamp = currentTime.addDays(7).toTime_t(); + clearAt._timestamp = currentTime.addDays(7).toSecsSinceEpoch(); userStatus.setClearAt(clearAt); auto fakeDateTimeProvider = std::make_unique(); diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index 1ec461aa1..5131bbbc2 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -1129,6 +1129,121 @@ private slots: QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } + + void testFolderWithFilesInError() + { + FakeFolder fakeFolder{FileInfo{}}; + + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * { + Q_UNUSED(outgoingData) + + if (op == QNetworkAccessManager::GetOperation) { + const auto fileName = getFilePathFromUrl(request.url()); + if (fileName == QStringLiteral("aaa/subfolder/foo")) { + return new FakeErrorReply(op, request, &fakeFolder.syncEngine(), 403); + } + } + return nullptr; + }); + + fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa/subfolder")); + fakeFolder.remoteModifier().insert(QStringLiteral("aaa/subfolder/bar")); + + QVERIFY(fakeFolder.syncOnce()); + + fakeFolder.remoteModifier().insert(QStringLiteral("aaa/subfolder/foo")); + QVERIFY(!fakeFolder.syncOnce()); + + QVERIFY(!fakeFolder.syncOnce()); + } + + void testInvalidMtimeRecoveryAtStart() + { + constexpr auto INVALID_MTIME = 0; + constexpr auto CURRENT_MTIME = 1646057277; + + FakeFolder fakeFolder{FileInfo{}}; + 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().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().setModTime(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME)); + fakeFolder.remoteModifier().insert(barFileAaaSubFolder); + fakeFolder.remoteModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME)); + + QVERIFY(!fakeFolder.syncOnce()); + + QVERIFY(!fakeFolder.syncOnce()); + + fakeFolder.remoteModifier().setModTimeKeepEtag(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + + QVERIFY(fakeFolder.syncOnce()); + + QVERIFY(fakeFolder.syncOnce()); + + auto expectedState = fakeFolder.currentLocalState(); + QCOMPARE(fakeFolder.currentRemoteState(), expectedState); + } + + void testInvalidMtimeRecovery() + { + constexpr auto INVALID_MTIME = 0; + constexpr auto CURRENT_MTIME = 1646057277; + + FakeFolder fakeFolder{FileInfo{}}; + 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().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().setModTime(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME)); + fakeFolder.remoteModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME)); + + QVERIFY(!fakeFolder.syncOnce()); + + QVERIFY(!fakeFolder.syncOnce()); + + fakeFolder.remoteModifier().setModTimeKeepEtag(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + + QVERIFY(fakeFolder.syncOnce()); + + QVERIFY(fakeFolder.syncOnce()); + + auto expectedState = fakeFolder.currentLocalState(); + QCOMPARE(fakeFolder.currentRemoteState(), expectedState); + } }; QTEST_GUILESS_MAIN(TestSyncEngine) diff --git a/test/testsyncjournaldb.cpp b/test/testsyncjournaldb.cpp index 718920446..44cd26257 100644 --- a/test/testsyncjournaldb.cpp +++ b/test/testsyncjournaldb.cpp @@ -107,8 +107,6 @@ private slots: QVERIFY(storedRecord._remotePerm == record._remotePerm); QVERIFY(storedRecord._checksumHeader == record._checksumHeader); - // qDebug()<< "OOOOO " << storedRecord._modtime.toTime_t() << record._modtime.toTime_t(); - // Attention: compare time_t types here, as QDateTime seem to maintain // milliseconds internally, which disappear in sqlite. Go for full seconds here. QVERIFY(storedRecord._modtime == record._modtime); diff --git a/test/testsyncvirtualfiles.cpp b/test/testsyncvirtualfiles.cpp index 882879c7b..bff6488e4 100644 --- a/test/testsyncvirtualfiles.cpp +++ b/test/testsyncvirtualfiles.cpp @@ -1517,6 +1517,149 @@ private slots: QCOMPARE(fakeFolder.currentLocalState().find("A/hello" DVSUFFIX)->size, 222); QCOMPARE(fakeFolder.currentLocalState().find("A/igno" DVSUFFIX)->size, 123); } + + void testUpdateMetadataErrorManagement() + { + FakeFolder fakeFolder{FileInfo{}}; + setupVfs(fakeFolder); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + // Existing files are propagated just fine in both directions + fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("aaa/subfolder")); + fakeFolder.remoteModifier().insert(QStringLiteral("aaa/subfolder/bar")); + QVERIFY(fakeFolder.syncOnce()); + + // New files on the remote create virtual files + fakeFolder.remoteModifier().setModTime(QStringLiteral("aaa/subfolder/bar"), QDateTime::fromSecsSinceEpoch(0)); + QVERIFY(!fakeFolder.syncOnce()); + + QVERIFY(!fakeFolder.syncOnce()); + } + + void testInvalidFutureMtimeRecovery() + { + constexpr auto FUTURE_MTIME = 0xFFFFFFFF; + constexpr auto CURRENT_MTIME = 1646057277; + + FakeFolder fakeFolder{FileInfo{}}; + setupVfs(fakeFolder); + 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().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().setModTimeKeepEtag(fooFileRootFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(barFileRootFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(fooFileSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(barFileSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(fooFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.remoteModifier().setModTimeKeepEtag(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + fakeFolder.localModifier().setModTime(fooFileRootFolder + DVSUFFIX, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(barFileRootFolder + DVSUFFIX, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(fooFileSubFolder + DVSUFFIX, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(barFileSubFolder + DVSUFFIX, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(fooFileAaaSubFolder + DVSUFFIX, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + fakeFolder.localModifier().setModTime(barFileAaaSubFolder + DVSUFFIX, QDateTime::fromSecsSinceEpoch(FUTURE_MTIME)); + + QVERIFY(fakeFolder.syncOnce()); + + QVERIFY(fakeFolder.syncOnce()); + } + + void testInvalidMtimeLocalDiscovery() + { + constexpr auto INVALID_MTIME1 = 0; + constexpr auto INVALID_MTIME2 = 0xFFFFFFFF; + constexpr auto CURRENT_MTIME = 1646057277; + + FakeFolder fakeFolder{FileInfo{}}; + setupVfs(fakeFolder); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QSignalSpy statusSpy(&fakeFolder.syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged); + + 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"); + + auto checkStatus = [&]() -> SyncFileStatus::SyncFileStatusTag { + auto file = QFileInfo{fakeFolder.syncEngine().localPath(), barFileAaaSubFolder}; + auto locPath = fakeFolder.syncEngine().localPath(); + auto itemFound = false; + // Start from the end to get the latest status + for (int i = statusSpy.size() - 1; i >= 0 && !itemFound; --i) { + if (QFileInfo(statusSpy.at(i)[0].toString()) == file) { + itemFound = true; + return statusSpy.at(i)[1].value().tag(); + } + } + + return {}; + }; + + fakeFolder.localModifier().insert(fooFileRootFolder); + fakeFolder.localModifier().insert(barFileRootFolder); + fakeFolder.localModifier().mkdir(QStringLiteral("subfolder")); + fakeFolder.localModifier().insert(fooFileSubFolder); + fakeFolder.localModifier().insert(barFileSubFolder); + fakeFolder.localModifier().mkdir(QStringLiteral("aaa")); + fakeFolder.localModifier().mkdir(QStringLiteral("aaa/subfolder")); + fakeFolder.localModifier().insert(fooFileAaaSubFolder); + fakeFolder.localModifier().insert(barFileAaaSubFolder); + fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME1)); + + fakeFolder.scheduleSync(); + fakeFolder.execUntilBeforePropagation(); + + QCOMPARE(checkStatus(), SyncFileStatus::StatusError); + + fakeFolder.execUntilFinished(); + + fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + + QVERIFY(fakeFolder.syncOnce()); + + fakeFolder.localModifier().appendByte(barFileAaaSubFolder); + fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME1)); + + fakeFolder.scheduleSync(); + fakeFolder.execUntilBeforePropagation(); + + QCOMPARE(checkStatus(), SyncFileStatus::StatusError); + + fakeFolder.execUntilFinished(); + + fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME)); + + QVERIFY(fakeFolder.syncOnce()); + + fakeFolder.localModifier().appendByte(barFileAaaSubFolder); + fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME2)); + + fakeFolder.scheduleSync(); + fakeFolder.execUntilBeforePropagation(); + + QCOMPARE(checkStatus(), SyncFileStatus::StatusError); + } }; QTEST_GUILESS_MAIN(TestSyncVirtualFiles)