From 04cc513bbd9443ee7100daa53979f78f8f6c72ba Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 25 Jul 2014 13:30:48 +0200 Subject: [PATCH] Poll for long running PUT: WIP Some PUT, may take a long time on the server to process (for example, the last chunk). It may take more time that the timeout. So in that case the server may reply with an url that we can poll for the etag This patch is still work in progress --- src/mirall/propagator_qnam.cpp | 68 ++++++++++++++++++++++++++++++++++ src/mirall/propagator_qnam.h | 27 ++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/mirall/propagator_qnam.cpp b/src/mirall/propagator_qnam.cpp index 6c0808c92..5b4cb067f 100644 --- a/src/mirall/propagator_qnam.cpp +++ b/src/mirall/propagator_qnam.cpp @@ -20,6 +20,7 @@ #include "utility.h" #include "filesystem.h" #include "propagatorjobs.h" +#include #include #include #include @@ -83,6 +84,14 @@ void PUTFileJob::slotTimeout() { reply()->abort(); } +void PollJob::start() +{ + setReply(davRequest("GET", path())); + connect(reply(), SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(resetTimeout())); + AbstractNetworkJob::start(); +} + + void PropagateUploadFileQNAM::start() { if (_propagator->_abortRequested.fetchAndAddRelaxed(0)) @@ -206,6 +215,7 @@ void PropagateUploadFileQNAM::startNextChunk() quint64 fileSize = _item._size; QMap headers; headers["OC-Total-Length"] = QByteArray::number(fileSize); + headers["OC-Async"] = "1"; headers["Content-Type"] = "application/octet-stream"; headers["X-OC-Mtime"] = QByteArray::number(qint64(_item._modtime)); if (!_item._etag.isEmpty() && _item._etag != "empty_etag") { @@ -292,6 +302,19 @@ void PropagateUploadFileQNAM::slotPutFinished() return; } + _item._httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // The server needs some time to process the request and provide with a poll URL + if (_item._httpErrorCode == 202) { + QString path = QString::fromUtf8(QByteArray::fromPercentEncoding(job->reply()->rawHeader("OC-Finish-Poll"))); + if (path.isEmpty()) { + _propagator->_activeJobs--; + done(SyncFileItem::NormalError, tr("Poll URL missing")); + return; + } + startPollJob(path); + return; + } + bool finished = job->reply()->hasRawHeader("ETag"); if (!finished) { @@ -383,6 +406,51 @@ void PropagateUploadFileQNAM::slotUploadProgress(qint64 sent, qint64) emit progress(_item, sent + _currentChunk * chunkSize()); } +void PropagateUploadFileQNAM::startPollJob(const QString& path) +{ + PollJob* job = new PollJob(AccountManager::instance()->account(), path, this); + job->setTimeout(_propagator->httpTimeout() * 10000); + connect(job, SIGNAL(finishedSignal(bool)), SLOT(slotPollFinished(bool))); +} + +void PropagateUploadFileQNAM::slotPollFinished(bool success) +{ + PollJob *job = qobject_cast(sender()); + Q_ASSERT(job); + qDebug() << Q_FUNC_INFO << job->reply()->request().url() << "FINISHED WITH STATUS" + << job->reply()->error() + << (job->reply()->error() == QNetworkReply::NoError ? QLatin1String("") : job->reply()->errorString()) + << job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute) + << job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute); + QNetworkReply::NetworkError err = job->reply()->error(); + if (!success || err != QNetworkReply::NoError) { + startPollJob(job->path()); + return; + } + + bool ok = false; + QVariantMap status = QtJson::parse(QString::fromUtf8(job->reply()->readAll()), ok).toMap(); + if (!ok || status.isEmpty()) { + _propagator->_activeJobs--; + done(SyncFileItem::NormalError, tr("Invalid json reply from the poll URL")); + // FIXME: retry? + return; + } + + // the following code only happens after all chunks were uploaded. + // the file id should only be empty for new files up- or downloaded + QByteArray fid = status["fileid"].toByteArray(); + if( !fid.isEmpty() ) { + if( !_item._fileId.isEmpty() && _item._fileId != fid ) { + qDebug() << "WARN: File ID changed!" << _item._fileId << fid; + } + _item._fileId = fid; + } + + _item._etag = status["etag"].toByteArray(); + _item._responseTimeStamp = job->responseTimestamp(); + finalize(_item); +} void PropagateUploadFileQNAM::abort() { diff --git a/src/mirall/propagator_qnam.h b/src/mirall/propagator_qnam.h index 3d87e2223..371ff40c3 100644 --- a/src/mirall/propagator_qnam.h +++ b/src/mirall/propagator_qnam.h @@ -80,6 +80,30 @@ signals: void uploadProgress(qint64,qint64); }; +class PollJob : public AbstractNetworkJob { + Q_OBJECT +public: + // Takes ownership of the device + explicit PollJob(Account* account, const QString &path, QObject *parent) + : AbstractNetworkJob(account, path, parent) {} + + virtual void start(); + + virtual bool finished() { + emit finishedSignal(true); + return true; + } + + virtual void slotTimeout() { +// emit finishedSignal(false); +// deleteLater(); + reply()->abort(); + } + +signals: + void finishedSignal(bool success); +}; + class PropagateUploadFileQNAM : public PropagateItemJob { Q_OBJECT @@ -96,10 +120,13 @@ public: void start(); private slots: void slotPutFinished(); + void slotPollFinished(bool success); void slotUploadProgress(qint64,qint64); void abort(); void startNextChunk(); void finalize(const SyncFileItem&); +private: + void startPollJob(const QString& path); };