From ac0a3ac8363dd13499d7ae9f420fd0431b95baa6 Mon Sep 17 00:00:00 2001 From: Hannah von Reth Date: Wed, 27 Jan 2021 14:15:38 +0100 Subject: [PATCH] Remove PollJob support --- changelog/unreleased/8398 | 5 + src/common/syncjournaldb.cpp | 57 ------- src/common/syncjournaldb.h | 10 -- src/libsync/owncloudpropagator.cpp | 50 ------ src/libsync/owncloudpropagator.h | 39 ----- src/libsync/propagateupload.cpp | 107 ------------- src/libsync/propagateupload.h | 38 +---- src/libsync/propagateuploadng.cpp | 8 +- src/libsync/propagateuploadv1.cpp | 9 +- src/libsync/syncengine.cpp | 19 --- src/libsync/syncengine.h | 1 - src/libsync/syncfileitem.h | 2 +- test/CMakeLists.txt | 1 - test/testasyncop.cpp | 242 ----------------------------- 14 files changed, 9 insertions(+), 579 deletions(-) create mode 100644 changelog/unreleased/8398 delete mode 100644 test/testasyncop.cpp diff --git a/changelog/unreleased/8398 b/changelog/unreleased/8398 new file mode 100644 index 00000000000..1c2b37ca8b9 --- /dev/null +++ b/changelog/unreleased/8398 @@ -0,0 +1,5 @@ +Change: We removed the support for async jobs using OC-JobStatus-Location + +We removed the support of async polling jobs after discovering potential issues. + +https://github.com/owncloud/client/pull/8398 diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index 70028f7a34c..7cbfc55e292 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -458,15 +458,6 @@ bool SyncJournalDb::checkConnect() return sqlFail(QStringLiteral("Create table blacklist"), createQuery); } - createQuery.prepare("CREATE TABLE IF NOT EXISTS async_poll(" - "path VARCHAR(4096)," - "modtime INTEGER(8)," - "filesize BIGINT," - "pollpath VARCHAR(4096));"); - if (!createQuery.exec()) { - return sqlFail(QStringLiteral("Create table async_poll"), createQuery); - } - // create the selectivesync table. createQuery.prepare("CREATE TABLE IF NOT EXISTS selectivesync (" "path VARCHAR(4096)," @@ -1719,54 +1710,6 @@ void SyncJournalDb::setErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord query->exec(); } -QVector SyncJournalDb::getPollInfos() -{ - QMutexLocker locker(&_mutex); - - QVector res; - - if (!checkConnect()) - return res; - - SqlQuery query("SELECT path, modtime, filesize, pollpath FROM async_poll", _db); - - if (!query.exec()) { - return res; - } - - while (query.next().hasData) { - PollInfo info; - info._file = query.stringValue(0); - info._modtime = query.int64Value(1); - info._fileSize = query.int64Value(2); - info._url = query.stringValue(3); - res.append(info); - } - return res; -} - -void SyncJournalDb::setPollInfo(const SyncJournalDb::PollInfo &info) -{ - QMutexLocker locker(&_mutex); - if (!checkConnect()) { - return; - } - - if (info._url.isEmpty()) { - qCDebug(lcDb) << "Deleting Poll job" << info._file; - SqlQuery query("DELETE FROM async_poll WHERE path=?", _db); - query.bindValue(1, info._file); - query.exec(); - } else { - SqlQuery query("INSERT OR REPLACE INTO async_poll (path, modtime, filesize, pollpath) VALUES( ? , ? , ? , ? )", _db); - query.bindValue(1, info._file); - query.bindValue(2, info._modtime); - query.bindValue(3, info._fileSize); - query.bindValue(4, info._url); - query.exec(); - } -} - QStringList SyncJournalDb::getSelectiveSyncList(SyncJournalDb::SelectiveSyncListType type, bool *ok) { QStringList result; diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h index 55b356bf27b..26e12c83c82 100644 --- a/src/common/syncjournaldb.h +++ b/src/common/syncjournaldb.h @@ -133,14 +133,6 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject bool isChunked() const { return _transferid != 0; } }; - struct PollInfo - { - QString _file; // The relative path of a file - QString _url; // the poll url. (This pollinfo is invalid if _url is empty) - qint64 _modtime; // The modtime of the file being uploaded - qint64 _fileSize; - }; - DownloadInfo getDownloadInfo(const QString &file); void setDownloadInfo(const QString &file, const DownloadInfo &i); QVector getAndDeleteStaleDownloadInfos(const QSet &keep); @@ -159,8 +151,6 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject void avoidRenamesOnNextSync(const QString &path) { avoidRenamesOnNextSync(path.toUtf8()); } void avoidRenamesOnNextSync(const QByteArray &path); - void setPollInfo(const PollInfo &); - QVector getPollInfos(); enum SelectiveSyncListType { /** The black list is the list of folders that are unselected in the selective sync dialog. diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index cd87a3ef07b..b349a4d9f0e 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -47,7 +47,6 @@ namespace OCC { Q_LOGGING_CATEGORY(lcPropagator, "sync.propagator", QtInfoMsg) Q_LOGGING_CATEGORY(lcDirectory, "sync.propagator.directory", QtInfoMsg) -Q_LOGGING_CATEGORY(lcCleanupPolls, "sync.propagator.cleanuppolls", QtInfoMsg) qint64 criticalFreeSpaceLimit() { @@ -1102,55 +1101,6 @@ void PropagateRootDirectory::slotDirDeletionJobsFinished(SyncFileItem::Status st // ================================================================================ -CleanupPollsJob::~CleanupPollsJob() -{ -} - -void CleanupPollsJob::start() -{ - if (_pollInfos.empty()) { - emit finished(); - deleteLater(); - return; - } - - auto info = _pollInfos.first(); - _pollInfos.pop_front(); - SyncJournalFileRecord record; - SyncFileItemPtr item(new SyncFileItem); - item->_file = info._file; - item->_modtime = info._modtime; - item->_size = info._fileSize; - PollJob *job = new PollJob(_account, info._url, item, _journal, _localPath, this); - connect(job, &PollJob::finishedSignal, this, &CleanupPollsJob::slotPollFinished); - job->start(); -} - -void CleanupPollsJob::slotPollFinished() -{ - PollJob *job = qobject_cast(sender()); - OC_ASSERT(job); - if (job->_item->_status == SyncFileItem::FatalError) { - emit aborted(job->_item->_errorString); - deleteLater(); - return; - } else if (job->_item->_status != SyncFileItem::Success) { - qCWarning(lcCleanupPolls) << "There was an error with file " << job->_item->_file << job->_item->_errorString; - } else { - if (!OwncloudPropagator::updateMetadata(*job->_item, _localPath, *_journal, *_vfs)) { - qCWarning(lcCleanupPolls) << "database error"; - job->_item->_status = SyncFileItem::FatalError; - job->_item->_errorString = tr("Error writing metadata to the database"); - emit aborted(job->_item->_errorString); - deleteLater(); - return; - } - _journal->setUploadInfo(job->_item->_file, SyncJournalDb::UploadInfo()); - } - // Continue with the next entry, or finish - start(); -} - QString OwncloudPropagator::fullRemotePath(const QString &tmp_file_name) const { // TODO: should this be part of the _item (SyncFileItemPtr)? diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h index cd3df302e9b..2bb0c5821fc 100644 --- a/src/libsync/owncloudpropagator.h +++ b/src/libsync/owncloudpropagator.h @@ -605,45 +605,6 @@ private slots: const QString _remoteFolder; // remote folder, ends with '/' }; - -/** - * @brief Job that wait for all the poll jobs to be completed - * @ingroup libsync - */ -class CleanupPollsJob : public QObject -{ - Q_OBJECT - QVector _pollInfos; - AccountPtr _account; - SyncJournalDb *_journal; - QString _localPath; - QSharedPointer _vfs; - -public: - explicit CleanupPollsJob(const QVector &pollInfos, AccountPtr account, - SyncJournalDb *journal, const QString &localPath, const QSharedPointer &vfs, QObject *parent = nullptr) - : QObject(parent) - , _pollInfos(pollInfos) - , _account(account) - , _journal(journal) - , _localPath(localPath) - , _vfs(vfs) - { - } - - ~CleanupPollsJob() override; - - /** - * Start the job. After the job is completed, it will emit either finished or aborted, and it - * will destroy itself. - */ - void start(); -signals: - void finished(); - void aborted(const QString &error); -private slots: - void slotPollFinished(); -}; } #endif diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp index 92d80e487ad..0eb3a3791ef 100644 --- a/src/libsync/propagateupload.cpp +++ b/src/libsync/propagateupload.cpp @@ -38,7 +38,6 @@ namespace OCC { Q_LOGGING_CATEGORY(lcPutJob, "sync.networkjob.put", QtInfoMsg) -Q_LOGGING_CATEGORY(lcPollJob, "sync.networkjob.poll", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropagateUpload, "sync.propagator.upload", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropagateUploadV1, "sync.propagator.upload.v1", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropagateUploadNG, "sync.propagator.upload.ng", QtInfoMsg) @@ -102,81 +101,6 @@ bool PUTFileJob::finished() return true; } -void PollJob::start() -{ - setTimeout(120 * 1000); - QUrl accountUrl = account()->url(); - QUrl finalUrl = QUrl::fromUserInput(accountUrl.scheme() + QStringLiteral("://") + accountUrl.authority() - + (path().startsWith(QLatin1Char('/')) ? QString() : QStringLiteral("/")) + path()); - sendRequest("GET", finalUrl); - connect(reply(), &QNetworkReply::downloadProgress, this, &AbstractNetworkJob::resetTimeout, Qt::UniqueConnection); - AbstractNetworkJob::start(); -} - -bool PollJob::finished() -{ - QNetworkReply::NetworkError err = reply()->error(); - if (err != QNetworkReply::NoError) { - _item->_httpErrorCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - _item->_requestId = requestId(); - _item->_status = classifyError(err, _item->_httpErrorCode); - _item->_errorString = errorString(); - - if (_item->_status == SyncFileItem::FatalError || _item->_httpErrorCode >= 400) { - if (_item->_status != SyncFileItem::FatalError - && _item->_httpErrorCode != 503) { - SyncJournalDb::PollInfo info; - info._file = _item->_file; - // no info._url removes it from the database - _journal->setPollInfo(info); - _journal->commit(QStringLiteral("remove poll info")); - } - emit finishedSignal(); - return true; - } - QTimer::singleShot(8 * 1000, this, &PollJob::start); - return false; - } - - QByteArray jsonData = reply()->readAll().trimmed(); - QJsonParseError jsonParseError; - QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object(); - qCInfo(lcPollJob) << ">" << jsonData << "<" << reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << json << jsonParseError.errorString(); - if (jsonParseError.error != QJsonParseError::NoError) { - _item->_errorString = tr("Invalid JSON reply from the poll URL"); - _item->_status = SyncFileItem::NormalError; - emit finishedSignal(); - return true; - } - - auto status = json[QStringLiteral("status")].toString(); - if (status == QLatin1String("init") || status == QLatin1String("started")) { - QTimer::singleShot(5 * 1000, this, &PollJob::start); - return false; - } - - _item->_responseTimeStamp = responseTimestamp(); - _item->_httpErrorCode = json[QStringLiteral("errorCode")].toInt(); - - if (status == QLatin1String("finished")) { - _item->_status = SyncFileItem::Success; - _item->_fileId = json[QStringLiteral("fileId")].toString().toUtf8(); - _item->_etag = parseEtag(json[QStringLiteral("ETag")].toString().toUtf8()); - } else { // error - _item->_status = classifyError(QNetworkReply::UnknownContentError, _item->_httpErrorCode); - _item->_errorString = json[QStringLiteral("errorMessage")].toString(); - } - - SyncJournalDb::PollInfo info; - info._file = _item->_file; - // no info._url removes it from the database - _journal->setPollInfo(info); - _journal->commit(QStringLiteral("remove poll info")); - - emit finishedSignal(); - return true; -} - void PropagateUploadFileCommon::setDeleteExisting(bool enabled) { _deleteExisting = enabled; @@ -480,37 +404,6 @@ void UploadDevice::setChoked(bool b) } } -void PropagateUploadFileCommon::startPollJob(const QString &path) -{ - PollJob *job = new PollJob(propagator()->account(), path, _item, - propagator()->_journal, propagator()->localPath(), this); - connect(job, &PollJob::finishedSignal, this, &PropagateUploadFileCommon::slotPollFinished); - SyncJournalDb::PollInfo info; - info._file = _item->_file; - info._url = path; - info._modtime = _item->_modtime; - info._fileSize = _item->_size; - propagator()->_journal->setPollInfo(info); - propagator()->_journal->commit(QStringLiteral("add poll info")); - propagator()->_activeJobList.append(this); - job->start(); -} - -void PropagateUploadFileCommon::slotPollFinished() -{ - PollJob *job = qobject_cast(sender()); - OC_ASSERT(job); - - propagator()->_activeJobList.removeOne(this); - - if (job->_item->_status != SyncFileItem::Success) { - done(job->_item->_status, job->_item->_errorString); - return; - } - - finalize(); -} - void PropagateUploadFileCommon::done(SyncFileItem::Status status, const QString &errorString) { _finished = true; diff --git a/src/libsync/propagateupload.h b/src/libsync/propagateupload.h index 964d0dc8844..5c8d8daee97 100644 --- a/src/libsync/propagateupload.h +++ b/src/libsync/propagateupload.h @@ -145,38 +145,6 @@ class PUTFileJob : public AbstractNetworkJob }; -/** - * @brief This job implements the asynchronous PUT - * - * If the server replies to a PUT with a OC-JobStatus-Location path, we will query this url until the server - * replies with an etag. - * @ingroup libsync - */ -class PollJob : public AbstractNetworkJob -{ - Q_OBJECT - SyncJournalDb *_journal; - QString _localPath; - -public: - SyncFileItemPtr _item; - // Takes ownership of the device - explicit PollJob(AccountPtr account, const QString &path, const SyncFileItemPtr &item, - SyncJournalDb *journal, const QString &localPath, QObject *parent) - : AbstractNetworkJob(account, path, parent) - , _journal(journal) - , _localPath(localPath) - , _item(item) - { - } - - void start() override; - bool finished() override; - -signals: - void finishedSignal(); -}; - /** * @brief The PropagateUploadFileCommon class is the code common between all chunking algorithms * @ingroup libsync @@ -195,7 +163,7 @@ class PollJob : public AbstractNetworkJob * . * . * v - * finalize() or abortWithError() or startPollJob() + * finalize() or abortWithError() */ class PropagateUploadFileCommon : public PropagateItemJob { @@ -246,16 +214,12 @@ private slots: public: virtual void doStartUpload() = 0; - void startPollJob(const QString &path); void finalize(); void abortWithError(SyncFileItem::Status status, const QString &error); public slots: void slotJobDestroyed(QObject *job); -private slots: - void slotPollFinished(); - protected: void done(SyncFileItem::Status status, const QString &errorString = QString()) override; diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp index ac69699c67e..95f8836ecf4 100644 --- a/src/libsync/propagateuploadng.cpp +++ b/src/libsync/propagateuploadng.cpp @@ -517,13 +517,7 @@ void PropagateUploadFileNG::slotMoveJobFinished() } if (_item->_httpErrorCode == 202) { - QString path = QString::fromUtf8(job->reply()->rawHeader("OC-JobStatus-Location")); - if (path.isEmpty()) { - done(SyncFileItem::NormalError, tr("Poll URL missing")); - return; - } - _finished = true; - startPollJob(path); + done(SyncFileItem::NormalError, tr("The server did ask for a removed legacy feature(polling)")); return; } diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp index c7ecc5c8ae1..0ba211441d2 100644 --- a/src/libsync/propagateuploadv1.cpp +++ b/src/libsync/propagateuploadv1.cpp @@ -216,15 +216,8 @@ void PropagateUploadFileV1::slotPutFinished() return; } - // The server needs some time to process the request and provide us with a poll URL if (_item->_httpErrorCode == 202) { - QString path = QString::fromUtf8(job->reply()->rawHeader("OC-JobStatus-Location")); - if (path.isEmpty()) { - done(SyncFileItem::NormalError, tr("Poll URL missing")); - return; - } - _finished = true; - startPollJob(path); + done(SyncFileItem::NormalError, tr("The server did ask for a removed legacy feature(polling)")); return; } diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 46d5ddf2a2e..b97135ab382 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -411,19 +411,6 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item) void SyncEngine::startSync() { - if (_journal->exists()) { - QVector pollInfos = _journal->getPollInfos(); - if (!pollInfos.isEmpty()) { - qCInfo(lcEngine) << "Finish Poll jobs before starting a sync"; - CleanupPollsJob *job = new CleanupPollsJob(pollInfos, _account, - _journal, _localPath, _syncOptions._vfs, this); - connect(job, &CleanupPollsJob::finished, this, &SyncEngine::startSync); - connect(job, &CleanupPollsJob::aborted, this, &SyncEngine::slotCleanPollsJobAborted); - job->start(); - return; - } - } - if (s_anySyncRunning || _syncRunning) { OC_ASSERT(false); return; @@ -754,12 +741,6 @@ void SyncEngine::slotDiscoveryFinished() finish(); } -void SyncEngine::slotCleanPollsJobAborted(const QString &error) -{ - syncError(error); - finalize(false); -} - void SyncEngine::setNetworkLimits(int upload, int download) { _uploadLimit = upload; diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index 903397d5b33..83e2a9dde25 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -190,7 +190,6 @@ private slots: void slotPropagationFinished(bool success); void slotProgress(const SyncFileItem &item, qint64 curent); void updateFileTotal(const SyncFileItem &item, qint64 newSize); - void slotCleanPollsJobAborted(const QString &error); /** Records that a file was touched by a job. */ void slotAddTouchedFile(const QString &fn); diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index ca92eccc33e..dfd217a7ec2 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -92,7 +92,7 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem /** Creates a basic SyncFileItem from a DB record * * This is intended in particular for read-update-write cycles that need - * to go through a a SyncFileItem, like PollJob. + * to go through a a SyncFileItem. */ static SyncFileItemPtr fromSyncJournalFileRecord(const SyncJournalFileRecord &rec); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 557de20e39d..327b1d8a2b6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,7 +25,6 @@ owncloud_add_test(SyncConflict) owncloud_add_test(SyncFileStatusTracker) owncloud_add_test(Download) owncloud_add_test(ChunkingNg) -owncloud_add_test(AsyncOp) owncloud_add_test(UploadReset) owncloud_add_test(AllFilesDeleted) owncloud_add_test(Blacklist) diff --git a/test/testasyncop.cpp b/test/testasyncop.cpp deleted file mode 100644 index 1f296c8f5de..00000000000 --- a/test/testasyncop.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * - */ - -#include -#include "syncenginetestutils.h" -#include - -using namespace OCC; - -class FakeAsyncReply : public FakeReply -{ - Q_OBJECT - QByteArray _pollLocation; - -public: - FakeAsyncReply(const QByteArray &pollLocation, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent) - : FakeReply { parent } - , _pollLocation(pollLocation) - { - setRequest(request); - setUrl(request.url()); - setOperation(op); - open(QIODevice::ReadOnly); - - QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection); - } - - Q_INVOKABLE void respond() - { - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 202); - setRawHeader("OC-JobStatus-Location", _pollLocation); - emit metaDataChanged(); - emit finished(); - } - - void abort() override {} - qint64 readData(char *, qint64) override { return 0; } -}; - - -class TestAsyncOp : public QObject -{ - Q_OBJECT - -private slots: - - void asyncUploadOperations() - { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "chunking", "1.0" } } } }); - // Reduce max chunk size a bit so we get more chunks - SyncOptions options; - options._maxChunkSize = 20 * 1000; - fakeFolder.syncEngine().setSyncOptions(options); - int nGET = 0; - - // This test is made of several testcases. - // the testCases maps a filename to a couple of callback. - // When a file is uploaded, the fake server will always return the 202 code, and will set - // the `perform` functor to what needs to be done to complete the transaction. - // The testcase consist of the `pollRequest` which will be called when the sync engine - // calls the poll url. - struct TestCase - { - using PollRequest_t = std::function; - - // for older compilers like on Debian 8 - explicit TestCase(PollRequest_t request = nullptr) - : pollRequest(request) - {} - - PollRequest_t pollRequest; - std::function perform = nullptr; - }; - QHash testCases; - - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * { - auto path = request.url().path(); - - if (op == QNetworkAccessManager::GetOperation && path.startsWith("/async-poll/")) { - auto file = path.mid(sizeof("/async-poll/") - 1); - Q_ASSERT(testCases.contains(file)); - auto &testCase = testCases[file]; - return testCase.pollRequest(&testCase, request); - } - - if (op == QNetworkAccessManager::PutOperation && !path.contains("/uploads/")) { - // Not chunking - auto file = getFilePathFromUrl(request.url()); - Q_ASSERT(testCases.contains(file)); - auto &testCase = testCases[file]; - Q_ASSERT(!testCase.perform); - auto putPayload = outgoingData->readAll(); - testCase.perform = [putPayload, request, &fakeFolder] { - return FakePutReply::perform(fakeFolder.remoteModifier(), request, putPayload); - }; - return new FakeAsyncReply("/async-poll/" + file.toUtf8(), op, request, &fakeFolder.syncEngine()); - } else if (request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") { - QString file = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination"))); - Q_ASSERT(testCases.contains(file)); - auto &testCase = testCases[file]; - Q_ASSERT(!testCase.perform); - testCase.perform = [request, &fakeFolder] { - return FakeChunkMoveReply::perform(fakeFolder.uploadState(), fakeFolder.remoteModifier(), request); - }; - return new FakeAsyncReply("/async-poll/" + file.toUtf8(), op, request, &fakeFolder.syncEngine()); - } else if (op == QNetworkAccessManager::GetOperation) { - nGET++; - } - return nullptr; - }); - - - // Callback to be used to finalize the transaction and return the success - auto successCallback = [](TestCase *tc, const QNetworkRequest &request) { - tc->pollRequest = [](TestCase *, const QNetworkRequest &) -> QNetworkReply * { std::abort(); }; // shall no longer be called - FileInfo *info = tc->perform(); - QByteArray body = "{ \"status\":\"finished\", \"ETag\":\"\\\"" + info->etag + "\\\"\", \"fileId\":\"" + info->fileId + "\"}\n"; - return new FakePayloadReply(QNetworkAccessManager::GetOperation, request, body, nullptr); - }; - // Callback that never finishes - auto waitForeverCallback = [](TestCase *, const QNetworkRequest &request) { - QByteArray body = "{\"status\":\"started\"}\n"; - return new FakePayloadReply(QNetworkAccessManager::GetOperation, request, body, nullptr); - }; - // Callback that simulate an error. - auto errorCallback = [](TestCase *tc, const QNetworkRequest &request) { - tc->pollRequest = [](TestCase *, const QNetworkRequest &) -> QNetworkReply * { std::abort(); }; // shall no longer be called; - QByteArray body = "{\"status\":\"error\",\"errorCode\":500,\"errorMessage\":\"TestingErrors\"}\n"; - return new FakePayloadReply(QNetworkAccessManager::GetOperation, request, body, nullptr); - }; - // This lambda takes another functor as a parameter, and returns a callback that will - // tell the client needs to poll again, and further call to the poll url will call the - // given callback - auto waitAndChain = [](const TestCase::PollRequest_t &chain) { - return [chain](TestCase *tc, const QNetworkRequest &request) { - tc->pollRequest = chain; - QByteArray body = "{\"status\":\"started\"}\n"; - return new FakePayloadReply(QNetworkAccessManager::GetOperation, request, body, nullptr); - }; - }; - - // Create a testcase by creating a file of a given size locally and assigning it a callback - auto insertFile = [&](const QString &file, int size, TestCase::PollRequest_t cb) { - fakeFolder.localModifier().insert(file, size); - testCases[file] = TestCase(std::move(cb)); - }; - fakeFolder.localModifier().mkdir("success"); - insertFile("success/chunked_success", options._maxChunkSize * 3, successCallback); - insertFile("success/single_success", 300, successCallback); - insertFile("success/chunked_patience", options._maxChunkSize * 3, - waitAndChain(waitAndChain(successCallback))); - insertFile("success/single_patience", 300, - waitAndChain(waitAndChain(successCallback))); - fakeFolder.localModifier().mkdir("err"); - insertFile("err/chunked_error", options._maxChunkSize * 3, errorCallback); - insertFile("err/single_error", 300, errorCallback); - insertFile("err/chunked_error2", options._maxChunkSize * 3, waitAndChain(errorCallback)); - insertFile("err/single_error2", 300, waitAndChain(errorCallback)); - - // First sync should finish by itself. - // All the things in "success/" should be transfered, the things in "err/" not - QVERIFY(!fakeFolder.syncOnce()); - QCOMPARE(nGET, 0); - QCOMPARE(*fakeFolder.currentLocalState().find("success"), - *fakeFolder.currentRemoteState().find("success")); - testCases.clear(); - testCases["err/chunked_error"] = TestCase(successCallback); - testCases["err/chunked_error2"] = TestCase(successCallback); - testCases["err/single_error"] = TestCase(successCallback); - testCases["err/single_error2"] = TestCase(successCallback); - - fakeFolder.localModifier().mkdir("waiting"); - insertFile("waiting/small", 300, waitForeverCallback); - insertFile("waiting/willNotConflict", 300, waitForeverCallback); - insertFile("waiting/big", options._maxChunkSize * 3, - waitAndChain(waitAndChain([&](TestCase *tc, const QNetworkRequest &request) { - QTimer::singleShot(0, &fakeFolder.syncEngine(), &SyncEngine::abort); - return waitAndChain(waitForeverCallback)(tc, request); - }))); - - fakeFolder.syncJournal().wipeErrorBlacklist(); - - // This second sync will redo the files that had errors - // But the waiting folder will not complete before it is aborted. - QVERIFY(!fakeFolder.syncOnce()); - QCOMPARE(nGET, 0); - QCOMPARE(*fakeFolder.currentLocalState().find("err"), - *fakeFolder.currentRemoteState().find("err")); - - testCases["waiting/small"].pollRequest = waitAndChain(waitAndChain(successCallback)); - testCases["waiting/big"].pollRequest = waitAndChain(successCallback); - testCases["waiting/willNotConflict"].pollRequest = - [&fakeFolder, &successCallback](TestCase *tc, const QNetworkRequest &request) { - auto &remoteModifier = fakeFolder.remoteModifier(); // successCallback destroys the capture - auto reply = successCallback(tc, request); - // This is going to succeed, and after we just change the file. - // This should not be a conflict, but this should be downloaded in the - // next sync - remoteModifier.appendByte("waiting/willNotConflict"); - return reply; - }; - - - int nPUT = 0; - int nMOVE = 0; - int nDELETE = 0; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - auto path = request.url().path(); - if (op == QNetworkAccessManager::GetOperation && path.startsWith("/async-poll/")) { - auto file = path.mid(sizeof("/async-poll/") - 1); - Q_ASSERT(testCases.contains(file)); - auto &testCase = testCases[file]; - return testCase.pollRequest(&testCase, request); - } else if (op == QNetworkAccessManager::PutOperation) { - nPUT++; - } else if (op == QNetworkAccessManager::GetOperation) { - nGET++; - } else if (op == QNetworkAccessManager::DeleteOperation) { - nDELETE++; - } else if (request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") { - nMOVE++; - } - return nullptr; - }); - - // This last sync will do the waiting stuff - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(nGET, 1); // "waiting/willNotConflict" - QCOMPARE(nPUT, 0); - QCOMPARE(nMOVE, 0); - QCOMPARE(nDELETE, 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } -}; - -QTEST_GUILESS_MAIN(TestAsyncOp) -#include "testasyncop.moc"