From 506ba022f18ee9252b14af8f9f1bfd3312828a8d Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Tue, 11 Nov 2014 12:16:14 +0100 Subject: [PATCH] Split propagator_qnam.cpp into propagateupload.cpp and propagatedownload.cpp --- src/libsync/CMakeLists.txt | 3 +- src/libsync/bandwidthmanager.cpp | 3 +- src/libsync/owncloudpropagator.cpp | 3 +- src/libsync/owncloudpropagator_p.h | 31 + src/libsync/propagatedownload.cpp | 525 +++++++++++++++++ src/libsync/propagatedownload.h | 117 ++++ ...ropagator_qnam.cpp => propagateupload.cpp} | 533 +----------------- .../{propagator_qnam.h => propagateupload.h} | 98 +--- src/libsync/propagator_legacy.cpp | 2 +- 9 files changed, 686 insertions(+), 629 deletions(-) create mode 100644 src/libsync/propagatedownload.cpp create mode 100644 src/libsync/propagatedownload.h rename src/libsync/{propagator_qnam.cpp => propagateupload.cpp} (55%) rename src/libsync/{propagator_qnam.h => propagateupload.h} (54%) diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 45dcc8a9a..15a7ff2e0 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -49,7 +49,8 @@ set(libsync_SRCS progressdispatcher.cpp propagatorjobs.cpp propagator_legacy.cpp - propagator_qnam.cpp + propagatedownload.cpp + propagateupload.cpp quotainfo.cpp syncengine.cpp syncfilestatus.cpp diff --git a/src/libsync/bandwidthmanager.cpp b/src/libsync/bandwidthmanager.cpp index 319bf234e..e7662c267 100644 --- a/src/libsync/bandwidthmanager.cpp +++ b/src/libsync/bandwidthmanager.cpp @@ -13,7 +13,8 @@ */ #include "owncloudpropagator.h" -#include "propagator_qnam.h" +#include "propagatedownload.h" +#include "propagateupload.h" #include "propagatorjobs.h" #include "propagator_legacy.h" #include "utility.h" diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index a4c690b2b..df5f1162e 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -16,7 +16,8 @@ #include "owncloudpropagator.h" #include "syncjournaldb.h" #include "syncjournalfilerecord.h" -#include "propagator_qnam.h" +#include "propagatedownload.h" +#include "propagateupload.h" #include "propagatorjobs.h" #include "propagator_legacy.h" #include "mirallconfigfile.h" diff --git a/src/libsync/owncloudpropagator_p.h b/src/libsync/owncloudpropagator_p.h index 3f98f6604..6cc3e7fb6 100644 --- a/src/libsync/owncloudpropagator_p.h +++ b/src/libsync/owncloudpropagator_p.h @@ -15,6 +15,9 @@ #pragma once +#include +#include "syncfileitem.h" + namespace Mirall { inline QByteArray parseEtag(const char *header) { @@ -28,5 +31,33 @@ inline QByteArray parseEtag(const char *header) { return arr; } +inline QByteArray getEtagFromReply(QNetworkReply *reply) +{ + QByteArray ret = parseEtag(reply->rawHeader("OC-ETag")); + if (ret.isEmpty()) { + ret = parseEtag(reply->rawHeader("ETag")); + } + return ret; +} + +/** + * Fiven an error from the network, map to a SyncFileItem::Status error + */ +inline SyncFileItem::Status classifyError(QNetworkReply::NetworkError nerror, int httpCode) { + Q_ASSERT (nerror != QNetworkReply::NoError); // we should only be called when there is an error + + if (nerror > QNetworkReply::NoError && nerror <= QNetworkReply::UnknownProxyError) { + // network error or proxy error -> fatal + return SyncFileItem::FatalError; + } + + if (httpCode == 412) { + // "Precondition Failed" + // Happens when the e-tag has changed + return SyncFileItem::SoftError; + } + + return SyncFileItem::NormalError; +} } diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp new file mode 100644 index 000000000..50da078aa --- /dev/null +++ b/src/libsync/propagatedownload.cpp @@ -0,0 +1,525 @@ +/* + * Copyright (C) by Olivier Goffart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "owncloudpropagator_p.h" +#include "propagatedownload.h" +#include "networkjobs.h" +#include "account.h" +#include "syncjournaldb.h" +#include "syncjournalfilerecord.h" +#include "utility.h" +#include "filesystem.h" +#include "propagatorjobs.h" +#include +#include +#include +#include +#include + +namespace Mirall { + +// DOES NOT take owncership of the device. +GETFileJob::GETFileJob(Account* account, const QString& path, QFile *device, + const QMap &headers, QByteArray expectedEtagForResume, + quint64 _resumeStart, QObject* parent) +: AbstractNetworkJob(account, path, parent), + _device(device), _headers(headers), _expectedEtagForResume(expectedEtagForResume), + _resumeStart(_resumeStart) , _errorStatus(SyncFileItem::NoStatus) +, _bandwidthLimited(false), _bandwidthChoked(false), _bandwidthQuota(0), _bandwidthManager(0) +, _hasEmittedFinishedSignal(false) +{ +} + +GETFileJob::GETFileJob(Account* account, const QUrl& url, QFile *device, + const QMap &headers, + QObject* parent) +: AbstractNetworkJob(account, url.toEncoded(), parent), + _device(device), _headers(headers), _resumeStart(0), + _errorStatus(SyncFileItem::NoStatus), _directDownloadUrl(url) +, _bandwidthLimited(false), _bandwidthChoked(false), _bandwidthQuota(0), _bandwidthManager(0) +, _hasEmittedFinishedSignal(false) +{ +} + + +void GETFileJob::start() { + if (_resumeStart > 0) { + _headers["Range"] = "bytes=" + QByteArray::number(_resumeStart) +'-'; + _headers["Accept-Ranges"] = "bytes"; + qDebug() << "Retry with range " << _headers["Range"]; + } + + QNetworkRequest req; + for(QMap::const_iterator it = _headers.begin(); it != _headers.end(); ++it) { + req.setRawHeader(it.key(), it.value()); + } + + if (_directDownloadUrl.isEmpty()) { + setReply(davRequest("GET", path(), req)); + } else { + // Use direct URL + setReply(davRequest("GET", _directDownloadUrl, req)); + } + setupConnections(reply()); + + reply()->setReadBufferSize(16 * 1024); // keep low so we can easier limit the bandwidth + qDebug() << Q_FUNC_INFO << _bandwidthManager << _bandwidthChoked << _bandwidthLimited; + if (_bandwidthManager) { + _bandwidthManager->registerDownloadJob(this); + } + + if( reply()->error() != QNetworkReply::NoError ) { + qWarning() << Q_FUNC_INFO << " Network error: " << reply()->errorString(); + } + + connect(reply(), SIGNAL(metaDataChanged()), this, SLOT(slotMetaDataChanged())); + connect(reply(), SIGNAL(readyRead()), this, SLOT(slotReadyRead())); + connect(reply(), SIGNAL(downloadProgress(qint64,qint64)), this, SIGNAL(downloadProgress(qint64,qint64))); + connect(this, SIGNAL(networkActivity()), account(), SIGNAL(propagatorNetworkActivity())); + + AbstractNetworkJob::start(); +} + +void GETFileJob::slotMetaDataChanged() +{ + // For some reason setting the read buffer in GETFileJob::start doesn't seem to go + // through the HTTP layer thread(?) + reply()->setReadBufferSize(16 * 1024); + + int httpStatus = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // If the status code isn't 2xx, don't write the reply body to the file. + // For any error: handle it when the job is finished, not here. + if (httpStatus / 100 != 2) { + _device->close(); + return; + } + if (reply()->error() != QNetworkReply::NoError) { + return; + } + _etag = getEtagFromReply(reply()); + + if (!_directDownloadUrl.isEmpty() && !_etag.isEmpty()) { + qDebug() << Q_FUNC_INFO << "Direct download used, ignoring server ETag" << _etag; + _etag = QByteArray(); // reset received ETag + } else if (!_directDownloadUrl.isEmpty()) { + // All fine, ETag empty and directDownloadUrl used + } else if (_etag.isEmpty()) { + qDebug() << Q_FUNC_INFO << "No E-Tag reply by server, considering it invalid"; + _errorString = tr("No E-Tag received from server, check Proxy/Gateway"); + _errorStatus = SyncFileItem::NormalError; + reply()->abort(); + return; + } else if (!_expectedEtagForResume.isEmpty() && _expectedEtagForResume != _etag) { + qDebug() << Q_FUNC_INFO << "We received a different E-Tag for resuming!" + << _expectedEtagForResume << "vs" << _etag; + _errorString = tr("We received a different E-Tag for resuming. Retrying next time."); + _errorStatus = SyncFileItem::NormalError; + reply()->abort(); + return; + } + + quint64 start = 0; + QByteArray ranges = reply()->rawHeader("Content-Range"); + if (!ranges.isEmpty()) { + QRegExp rx("bytes (\\d+)-"); + if (rx.indexIn(ranges) >= 0) { + start = rx.cap(1).toULongLong(); + } + } + if (start != _resumeStart) { + qDebug() << Q_FUNC_INFO << "Wrong content-range: "<< ranges << " while expecting start was" << _resumeStart; + if (start == 0) { + // device don't support range, just stry again from scratch + _device->close(); + if (!_device->open(QIODevice::WriteOnly)) { + _errorString = _device->errorString(); + _errorStatus = SyncFileItem::NormalError; + reply()->abort(); + return; + } + _resumeStart = 0; + } else { + _errorString = tr("Server returned wrong content-range"); + _errorStatus = SyncFileItem::NormalError; + reply()->abort(); + return; + } + } + +} + +void GETFileJob::setBandwidthManager(BandwidthManager *bwm) +{ + _bandwidthManager = bwm; +} + +void GETFileJob::setChoked(bool c) +{ + _bandwidthChoked = c; + QMetaObject::invokeMethod(this, "slotReadyRead", Qt::QueuedConnection); +} + +void GETFileJob::setBandwidthLimited(bool b) +{ + _bandwidthLimited = b; + QMetaObject::invokeMethod(this, "slotReadyRead", Qt::QueuedConnection); +} + +void GETFileJob::giveBandwidthQuota(qint64 q) +{ + _bandwidthQuota = q; + qDebug() << Q_FUNC_INFO << "Got" << q << "bytes"; + QMetaObject::invokeMethod(this, "slotReadyRead", Qt::QueuedConnection); +} + +qint64 GETFileJob::currentDownloadPosition() +{ + if (_device && _device->pos() > 0 && _device->pos() > qint64(_resumeStart)) { + return _device->pos(); + } + return _resumeStart; +} + +void GETFileJob::slotReadyRead() +{ + int bufferSize = qMin(1024*8ll , reply()->bytesAvailable()); + QByteArray buffer(bufferSize, Qt::Uninitialized); + + //qDebug() << Q_FUNC_INFO << reply()->bytesAvailable() << reply()->isOpen() << reply()->isFinished(); + + while(reply()->bytesAvailable() > 0) { + if (_bandwidthChoked) { + qDebug() << Q_FUNC_INFO << "Download choked"; + break; + } + qint64 toRead = bufferSize; + if (_bandwidthLimited) { + toRead = qMin(qint64(bufferSize), _bandwidthQuota); + if (toRead == 0) { + qDebug() << Q_FUNC_INFO << "Out of quota"; + break; + } + _bandwidthQuota -= toRead; + //qDebug() << Q_FUNC_INFO << "Reading" << toRead << "remaining" << _bandwidthQuota; + } + + qint64 r = reply()->read(buffer.data(), toRead); + if (r < 0) { + _errorString = reply()->errorString(); + _errorStatus = SyncFileItem::NormalError; + qDebug() << "Error while reading from device: " << _errorString; + reply()->abort(); + return; + } + + if (_device->isOpen()) { + qint64 w = _device->write(buffer.constData(), r); + if (w != r) { + _errorString = _device->errorString(); + _errorStatus = SyncFileItem::NormalError; + qDebug() << "Error while writing to file" << w << r << _errorString; + reply()->abort(); + return; + } + } + } + + //qDebug() << Q_FUNC_INFO << "END" << reply()->isFinished() << reply()->bytesAvailable() << _hasEmittedFinishedSignal; + if (reply()->isFinished() && reply()->bytesAvailable() == 0) { + qDebug() << Q_FUNC_INFO << "Actually finished!"; + if (_bandwidthManager) { + _bandwidthManager->unregisterDownloadJob(this); + } + if (!_hasEmittedFinishedSignal) { + emit finishedSignal(); + } + _hasEmittedFinishedSignal = true; + deleteLater(); + } +} + +void GETFileJob::slotTimeout() +{ + _errorString = tr("Connection Timeout"); + _errorStatus = SyncFileItem::FatalError; + reply()->abort(); +} + +void PropagateDownloadFileQNAM::start() +{ + if (_propagator->_abortRequested.fetchAndAddRelaxed(0)) + return; + + qDebug() << Q_FUNC_INFO << _item._file << _propagator->_activeJobs; + + // do a klaas' case clash check. + if( _propagator->localFileNameClash(_item._file) ) { + done( SyncFileItem::NormalError, tr("File %1 can not be downloaded because of a local file name clash!") + .arg(QDir::toNativeSeparators(_item._file)) ); + return; + } + + emit progress(_item, 0); + + QString tmpFileName; + QByteArray expectedEtagForResume; + const SyncJournalDb::DownloadInfo progressInfo = _propagator->_journal->getDownloadInfo(_item._file); + if (progressInfo._valid) { + // if the etag has changed meanwhile, remove the already downloaded part. + if (progressInfo._etag != _item._etag) { + QFile::remove(_propagator->getFilePath(progressInfo._tmpfile)); + _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo()); + } else { + tmpFileName = progressInfo._tmpfile; + expectedEtagForResume = progressInfo._etag; + } + + } + + if (tmpFileName.isEmpty()) { + tmpFileName = _item._file; + //add a dot at the begining of the filename to hide the file. + int slashPos = tmpFileName.lastIndexOf('/'); + tmpFileName.insert(slashPos+1, '.'); + //add the suffix + tmpFileName += ".~" + QString::number(uint(qrand()), 16); + } + + _tmpFile.setFileName(_propagator->getFilePath(tmpFileName)); + if (!_tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) { + done(SyncFileItem::NormalError, _tmpFile.errorString()); + return; + } + + FileSystem::setFileHidden(_tmpFile.fileName(), true); + + { + SyncJournalDb::DownloadInfo pi; + pi._etag = _item._etag; + pi._tmpfile = tmpFileName; + pi._valid = true; + _propagator->_journal->setDownloadInfo(_item._file, pi); + _propagator->_journal->commit("download file start"); + } + + + QMap headers; + + quint64 startSize = _tmpFile.size(); + if (startSize > 0) { + if (startSize == _item._size) { + qDebug() << "File is already complete, no need to download"; + downloadFinished(); + return; + } + } + + if (_item._directDownloadUrl.isEmpty()) { + // Normal job, download from oC instance + _job = new GETFileJob(AccountManager::instance()->account(), + _propagator->_remoteFolder + _item._file, + &_tmpFile, headers, expectedEtagForResume, startSize); + } else { + // We were provided a direct URL, use that one + qDebug() << Q_FUNC_INFO << "directDownloadUrl given for " << _item._file << _item._directDownloadUrl; + + // Direct URLs don't support resuming, so clear an existing tmp file + if (startSize > 0) { + qDebug() << Q_FUNC_INFO << "resuming not supported for directDownloadUrl, deleting temporary"; + _tmpFile.close(); + if (!_tmpFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) { + done(SyncFileItem::NormalError, _tmpFile.errorString()); + return; + } + startSize = 0; + } + + if (!_item._directDownloadCookies.isEmpty()) { + headers["Cookie"] = _item._directDownloadCookies.toUtf8(); + } + + QUrl url = QUrl::fromUserInput(_item._directDownloadUrl); + _job = new GETFileJob(AccountManager::instance()->account(), + url, + &_tmpFile, headers); + } + _job->setBandwidthManager(&_propagator->_bandwidthManager); + _job->setTimeout(_propagator->httpTimeout() * 1000); + connect(_job, SIGNAL(finishedSignal()), this, SLOT(slotGetFinished())); + connect(_job, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(slotDownloadProgress(qint64,qint64))); + _propagator->_activeJobs ++; + _job->start(); + emitReady(); +} + +void PropagateDownloadFileQNAM::slotGetFinished() +{ + _propagator->_activeJobs--; + + GETFileJob *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()); + + QNetworkReply::NetworkError err = job->reply()->error(); + if (err != QNetworkReply::NoError) { + _item._httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + // If we sent a 'Range' header and get 416 back, we want to retry + // without the header. + bool badRangeHeader = job->resumeStart() > 0 && _item._httpErrorCode == 416; + if (badRangeHeader) { + qDebug() << Q_FUNC_INFO << "server replied 416 to our range request, trying again without"; + _propagator->_anotherSyncNeeded = true; + } + + // Don't keep the temporary file if it is empty or we + // used a bad range header. + if (_tmpFile.size() == 0 || badRangeHeader) { + _tmpFile.close(); + _tmpFile.remove(); + _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo()); + } + + _propagator->_activeJobs--; + SyncFileItem::Status status = job->errorStatus(); + if (status == SyncFileItem::NoStatus) { + status = classifyError(err, _item._httpErrorCode); + } + if (badRangeHeader) { + // Can't do this in classifyError() because 416 without a + // Range header should result in NormalError. + status = SyncFileItem::SoftError; + } + done(status, job->errorString()); + return; + } + + if (!job->etag().isEmpty()) { + // The etag will be empty if we used a direct download URL. + // (If it was really empty by the server, the GETFileJob will have errored + _item._etag = parseEtag(job->etag()); + } + _item._requestDuration = job->duration(); + _item._responseTimeStamp = job->responseTimestamp(); + + _tmpFile.close(); + _tmpFile.flush(); + downloadFinished(); +} + +QString makeConflictFileName(const QString &fn, const QDateTime &dt) +{ + QString conflictFileName(fn); + // Add _conflict-XXXX before the extention. + int dotLocation = conflictFileName.lastIndexOf('.'); + // If no extention, add it at the end (take care of cases like foo/.hidden or foo.bar/file) + if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) { + dotLocation = conflictFileName.size(); + } + QString timeString = dt.toString("yyyyMMdd-hhmmss"); + + // Additional marker + QByteArray conflictFileUserName = qgetenv("CSYNC_CONFLICT_FILE_USERNAME"); + if (conflictFileUserName.isEmpty()) + conflictFileName.insert(dotLocation, "_conflict-" + timeString); + else + conflictFileName.insert(dotLocation, "_conflict_" + QString::fromUtf8(conflictFileUserName) + "-" + timeString); + + return conflictFileName; +} + +void PropagateDownloadFileQNAM::downloadFinished() +{ + + QString fn = _propagator->getFilePath(_item._file); + + // In case of file name clash, report an error + // This can happen if another parallel download saved a clashing file. + if (_propagator->localFileNameClash(_item._file)) { + done( SyncFileItem::NormalError, tr("File %1 cannot be saved because of a local file name clash!") + .arg(QDir::toNativeSeparators(_item._file)) ); + return; + } + + // In case of conflict, make a backup of the old file + // Ignore conflicts where both files are binary equal + bool isConflict = _item._instruction == CSYNC_INSTRUCTION_CONFLICT + && !FileSystem::fileEquals(fn, _tmpFile.fileName()); + if (isConflict) { + QFile f(fn); + QString conflictFileName = makeConflictFileName(fn, Utility::qDateTimeFromTime_t(_item._modtime)); + if (!f.rename(conflictFileName)) { + //If the rename fails, don't replace it. + done(SyncFileItem::NormalError, f.errorString()); + return; + } + } + + QFileInfo existingFile(fn); + if(existingFile.exists() && existingFile.permissions() != _tmpFile.permissions()) { + _tmpFile.setPermissions(existingFile.permissions()); + } + + FileSystem::setFileHidden(_tmpFile.fileName(), false); + + QString error; + if (!FileSystem::renameReplace(_tmpFile.fileName(), fn, &error)) { + // If we moved away the original file due to a conflict but can't + // put the downloaded file in its place, we are in a bad spot: + // If we do nothing the next sync run will assume the user deleted + // the file! + // To avoid that, the file is removed from the metadata table entirely + // which makes it look like we're just about to initially download + // it. + if (isConflict) { + _propagator->_journal->deleteFileRecord(fn); + _propagator->_journal->commit("download finished"); + _propagator->_anotherSyncNeeded = true; + } + + done(SyncFileItem::NormalError, error); + return; + } + + existingFile.refresh(); + // Maybe we downloaded a newer version of the file than we thought we would... + // Get up to date information for the journal. + FileSystem::setModTime(fn, _item._modtime); + _item._size = existingFile.size(); + + _propagator->_journal->setFileRecord(SyncJournalFileRecord(_item, fn)); + _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo()); + _propagator->_journal->commit("download file start2"); + done(isConflict ? SyncFileItem::Conflict : SyncFileItem::Success); +} + +void PropagateDownloadFileQNAM::slotDownloadProgress(qint64 received, qint64) +{ + if (!_job) return; + emit progress(_item, received + _job->resumeStart()); +} + + +void PropagateDownloadFileQNAM::abort() +{ + if (_job && _job->reply()) + _job->reply()->abort(); +} + + +} diff --git a/src/libsync/propagatedownload.h b/src/libsync/propagatedownload.h new file mode 100644 index 000000000..a74ba68f7 --- /dev/null +++ b/src/libsync/propagatedownload.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) by Olivier Goffart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#pragma once + +#include "owncloudpropagator.h" +#include "networkjobs.h" + +#include +#include + +namespace Mirall { + +class GETFileJob : public AbstractNetworkJob { + Q_OBJECT + QFile* _device; + QMap _headers; + QString _errorString; + QByteArray _expectedEtagForResume; + quint64 _resumeStart; + SyncFileItem::Status _errorStatus; + QUrl _directDownloadUrl; + QByteArray _etag; + bool _bandwidthLimited; // if _bandwidthQuota will be used + bool _bandwidthChoked; // if download is paused (won't read on readyRead()) + qint64 _bandwidthQuota; + BandwidthManager *_bandwidthManager; + bool _hasEmittedFinishedSignal; +public: + + // DOES NOT take owncership of the device. + explicit GETFileJob(Account* account, const QString& path, QFile *device, + const QMap &headers, QByteArray expectedEtagForResume, + quint64 resumeStart, QObject* parent = 0); + // For directDownloadUrl: + explicit GETFileJob(Account* account, const QUrl& url, QFile *device, + const QMap &headers, + QObject* parent = 0); + virtual ~GETFileJob() { + if (_bandwidthManager) { + _bandwidthManager->unregisterDownloadJob(this); + } + } + + virtual void start() Q_DECL_OVERRIDE; + virtual bool finished() Q_DECL_OVERRIDE { + qDebug() << Q_FUNC_INFO << reply()->bytesAvailable() << _hasEmittedFinishedSignal; + if (reply()->bytesAvailable()) { + qDebug() << Q_FUNC_INFO << "Not all read yet because of bandwidth limits"; + return false; + } else { + if (_bandwidthManager) { + _bandwidthManager->unregisterDownloadJob(this); + } + if (!_hasEmittedFinishedSignal) { + emit finishedSignal(); + } + _hasEmittedFinishedSignal = true; + return true; // discard + } + } + + void setBandwidthManager(BandwidthManager *bwm); + void setChoked(bool c); + void setBandwidthLimited(bool b); + void giveBandwidthQuota(qint64 q); + qint64 currentDownloadPosition(); + + QString errorString() { + return _errorString.isEmpty() ? reply()->errorString() : _errorString; + } + + SyncFileItem::Status errorStatus() { return _errorStatus; } + + virtual void slotTimeout() Q_DECL_OVERRIDE; + + QByteArray &etag() { return _etag; } + quint64 resumeStart() { return _resumeStart; } + + +signals: + void finishedSignal(); + void downloadProgress(qint64,qint64); +private slots: + void slotReadyRead(); + void slotMetaDataChanged(); +}; + + +class PropagateDownloadFileQNAM : public PropagateItemJob { + Q_OBJECT + QPointer _job; + +// QFile *_file; + QFile _tmpFile; +public: + PropagateDownloadFileQNAM(OwncloudPropagator* propagator,const SyncFileItem& item) + : PropagateItemJob(propagator, item) {} + void start() Q_DECL_OVERRIDE; +private slots: + void slotGetFinished(); + void abort() Q_DECL_OVERRIDE; + void downloadFinished(); + void slotDownloadProgress(qint64,qint64); +}; + +} diff --git a/src/libsync/propagator_qnam.cpp b/src/libsync/propagateupload.cpp similarity index 55% rename from src/libsync/propagator_qnam.cpp rename to src/libsync/propagateupload.cpp index b0c079150..d5ac518da 100644 --- a/src/libsync/propagator_qnam.cpp +++ b/src/libsync/propagateupload.cpp @@ -12,7 +12,8 @@ * for more details. */ -#include "propagator_qnam.h" +#include "propagateupload.h" +#include "owncloudpropagator_p.h" #include "networkjobs.h" #include "account.h" #include "syncjournaldb.h" @@ -51,35 +52,6 @@ static qint64 chunkSize() { return chunkSize; } -static QByteArray get_etag_from_reply(QNetworkReply *reply) -{ - QByteArray ret = parseEtag(reply->rawHeader("OC-ETag")); - if (ret.isEmpty()) { - ret = parseEtag(reply->rawHeader("ETag")); - } - return ret; -} - -/** - * Fiven an error from the network, map to a SyncFileItem::Status error - */ -static SyncFileItem::Status classifyError(QNetworkReply::NetworkError nerror, int httpCode) { - Q_ASSERT (nerror != QNetworkReply::NoError); // we should only be called when there is an error - - if (nerror > QNetworkReply::NoError && nerror <= QNetworkReply::UnknownProxyError) { - // network error or proxy error -> fatal - return SyncFileItem::FatalError; - } - - if (httpCode == 412) { - // "Precondition Failed" - // Happens when the e-tag has changed - return SyncFileItem::SoftError; - } - - return SyncFileItem::NormalError; -} - void PUTFileJob::start() { QNetworkRequest req; for(QMap::const_iterator it = _headers.begin(); it != _headers.end(); ++it) { @@ -288,7 +260,7 @@ qint64 UploadDevice::readData(char* data, qint64 maxlen) { } void UploadDevice::slotJobUploadProgress(qint64 sent, qint64 t) -{ +{ //qDebug() << Q_FUNC_INFO << sent << _read << t << _size << _bandwidthQuota; if (sent == 0 || t == 0) { return; @@ -584,7 +556,7 @@ void PropagateUploadFileQNAM::slotPutFinished() _item._fileId = fid; } - QByteArray etag = get_etag_from_reply(job->reply()); + QByteArray etag = getEtagFromReply(job->reply()); _item._etag = etag; _item._responseTimeStamp = job->responseTimestamp(); @@ -679,499 +651,4 @@ void PropagateUploadFileQNAM::abort() } } -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// DOES NOT take owncership of the device. -GETFileJob::GETFileJob(Account* account, const QString& path, QFile *device, - const QMap &headers, QByteArray expectedEtagForResume, - quint64 _resumeStart, QObject* parent) -: AbstractNetworkJob(account, path, parent), - _device(device), _headers(headers), _expectedEtagForResume(expectedEtagForResume), - _resumeStart(_resumeStart) , _errorStatus(SyncFileItem::NoStatus) -, _bandwidthLimited(false), _bandwidthChoked(false), _bandwidthQuota(0), _bandwidthManager(0) -, _hasEmittedFinishedSignal(false) -{ -} - -GETFileJob::GETFileJob(Account* account, const QUrl& url, QFile *device, - const QMap &headers, - QObject* parent) -: AbstractNetworkJob(account, url.toEncoded(), parent), - _device(device), _headers(headers), _resumeStart(0), - _errorStatus(SyncFileItem::NoStatus), _directDownloadUrl(url) -, _bandwidthLimited(false), _bandwidthChoked(false), _bandwidthQuota(0), _bandwidthManager(0) -, _hasEmittedFinishedSignal(false) -{ -} - - -void GETFileJob::start() { - if (_resumeStart > 0) { - _headers["Range"] = "bytes=" + QByteArray::number(_resumeStart) +'-'; - _headers["Accept-Ranges"] = "bytes"; - qDebug() << "Retry with range " << _headers["Range"]; - } - - QNetworkRequest req; - for(QMap::const_iterator it = _headers.begin(); it != _headers.end(); ++it) { - req.setRawHeader(it.key(), it.value()); - } - - if (_directDownloadUrl.isEmpty()) { - setReply(davRequest("GET", path(), req)); - } else { - // Use direct URL - setReply(davRequest("GET", _directDownloadUrl, req)); - } - setupConnections(reply()); - - reply()->setReadBufferSize(16 * 1024); // keep low so we can easier limit the bandwidth - qDebug() << Q_FUNC_INFO << _bandwidthManager << _bandwidthChoked << _bandwidthLimited; - if (_bandwidthManager) { - _bandwidthManager->registerDownloadJob(this); - } - - if( reply()->error() != QNetworkReply::NoError ) { - qWarning() << Q_FUNC_INFO << " Network error: " << reply()->errorString(); - } - - connect(reply(), SIGNAL(metaDataChanged()), this, SLOT(slotMetaDataChanged())); - connect(reply(), SIGNAL(readyRead()), this, SLOT(slotReadyRead())); - connect(reply(), SIGNAL(downloadProgress(qint64,qint64)), this, SIGNAL(downloadProgress(qint64,qint64))); - connect(this, SIGNAL(networkActivity()), account(), SIGNAL(propagatorNetworkActivity())); - - AbstractNetworkJob::start(); -} - -void GETFileJob::slotMetaDataChanged() -{ - // For some reason setting the read buffer in GETFileJob::start doesn't seem to go - // through the HTTP layer thread(?) - reply()->setReadBufferSize(16 * 1024); - - int httpStatus = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - // If the status code isn't 2xx, don't write the reply body to the file. - // For any error: handle it when the job is finished, not here. - if (httpStatus / 100 != 2) { - _device->close(); - return; - } - if (reply()->error() != QNetworkReply::NoError) { - return; - } - _etag = get_etag_from_reply(reply()); - - if (!_directDownloadUrl.isEmpty() && !_etag.isEmpty()) { - qDebug() << Q_FUNC_INFO << "Direct download used, ignoring server ETag" << _etag; - _etag = QByteArray(); // reset received ETag - } else if (!_directDownloadUrl.isEmpty()) { - // All fine, ETag empty and directDownloadUrl used - } else if (_etag.isEmpty()) { - qDebug() << Q_FUNC_INFO << "No E-Tag reply by server, considering it invalid"; - _errorString = tr("No E-Tag received from server, check Proxy/Gateway"); - _errorStatus = SyncFileItem::NormalError; - reply()->abort(); - return; - } else if (!_expectedEtagForResume.isEmpty() && _expectedEtagForResume != _etag) { - qDebug() << Q_FUNC_INFO << "We received a different E-Tag for resuming!" - << _expectedEtagForResume << "vs" << _etag; - _errorString = tr("We received a different E-Tag for resuming. Retrying next time."); - _errorStatus = SyncFileItem::NormalError; - reply()->abort(); - return; - } - - quint64 start = 0; - QByteArray ranges = reply()->rawHeader("Content-Range"); - if (!ranges.isEmpty()) { - QRegExp rx("bytes (\\d+)-"); - if (rx.indexIn(ranges) >= 0) { - start = rx.cap(1).toULongLong(); - } - } - if (start != _resumeStart) { - qDebug() << Q_FUNC_INFO << "Wrong content-range: "<< ranges << " while expecting start was" << _resumeStart; - if (start == 0) { - // device don't support range, just stry again from scratch - _device->close(); - if (!_device->open(QIODevice::WriteOnly)) { - _errorString = _device->errorString(); - _errorStatus = SyncFileItem::NormalError; - reply()->abort(); - return; - } - _resumeStart = 0; - } else { - _errorString = tr("Server returned wrong content-range"); - _errorStatus = SyncFileItem::NormalError; - reply()->abort(); - return; - } - } - -} - -void GETFileJob::setBandwidthManager(BandwidthManager *bwm) -{ - _bandwidthManager = bwm; -} - -void GETFileJob::setChoked(bool c) -{ - _bandwidthChoked = c; - QMetaObject::invokeMethod(this, "slotReadyRead", Qt::QueuedConnection); -} - -void GETFileJob::setBandwidthLimited(bool b) -{ - _bandwidthLimited = b; - QMetaObject::invokeMethod(this, "slotReadyRead", Qt::QueuedConnection); -} - -void GETFileJob::giveBandwidthQuota(qint64 q) -{ - _bandwidthQuota = q; - qDebug() << Q_FUNC_INFO << "Got" << q << "bytes"; - QMetaObject::invokeMethod(this, "slotReadyRead", Qt::QueuedConnection); -} - -qint64 GETFileJob::currentDownloadPosition() -{ - if (_device && _device->pos() > 0 && _device->pos() > qint64(_resumeStart)) { - return _device->pos(); - } - return _resumeStart; -} - -void GETFileJob::slotReadyRead() -{ - int bufferSize = qMin(1024*8ll , reply()->bytesAvailable()); - QByteArray buffer(bufferSize, Qt::Uninitialized); - - //qDebug() << Q_FUNC_INFO << reply()->bytesAvailable() << reply()->isOpen() << reply()->isFinished(); - - while(reply()->bytesAvailable() > 0) { - if (_bandwidthChoked) { - qDebug() << Q_FUNC_INFO << "Download choked"; - break; - } - qint64 toRead = bufferSize; - if (_bandwidthLimited) { - toRead = qMin(qint64(bufferSize), _bandwidthQuota); - if (toRead == 0) { - qDebug() << Q_FUNC_INFO << "Out of quota"; - break; - } - _bandwidthQuota -= toRead; - //qDebug() << Q_FUNC_INFO << "Reading" << toRead << "remaining" << _bandwidthQuota; - } - - qint64 r = reply()->read(buffer.data(), toRead); - if (r < 0) { - _errorString = reply()->errorString(); - _errorStatus = SyncFileItem::NormalError; - qDebug() << "Error while reading from device: " << _errorString; - reply()->abort(); - return; - } - - if (_device->isOpen()) { - qint64 w = _device->write(buffer.constData(), r); - if (w != r) { - _errorString = _device->errorString(); - _errorStatus = SyncFileItem::NormalError; - qDebug() << "Error while writing to file" << w << r << _errorString; - reply()->abort(); - return; - } - } - } - - //qDebug() << Q_FUNC_INFO << "END" << reply()->isFinished() << reply()->bytesAvailable() << _hasEmittedFinishedSignal; - if (reply()->isFinished() && reply()->bytesAvailable() == 0) { - qDebug() << Q_FUNC_INFO << "Actually finished!"; - if (_bandwidthManager) { - _bandwidthManager->unregisterDownloadJob(this); - } - if (!_hasEmittedFinishedSignal) { - emit finishedSignal(); - } - _hasEmittedFinishedSignal = true; - deleteLater(); - } -} - -void GETFileJob::slotTimeout() -{ - _errorString = tr("Connection Timeout"); - _errorStatus = SyncFileItem::FatalError; - reply()->abort(); -} - -void PropagateDownloadFileQNAM::start() -{ - if (_propagator->_abortRequested.fetchAndAddRelaxed(0)) - return; - - qDebug() << Q_FUNC_INFO << _item._file << _propagator->_activeJobs; - - // do a klaas' case clash check. - if( _propagator->localFileNameClash(_item._file) ) { - done( SyncFileItem::NormalError, tr("File %1 can not be downloaded because of a local file name clash!") - .arg(QDir::toNativeSeparators(_item._file)) ); - return; - } - - emit progress(_item, 0); - - QString tmpFileName; - QByteArray expectedEtagForResume; - const SyncJournalDb::DownloadInfo progressInfo = _propagator->_journal->getDownloadInfo(_item._file); - if (progressInfo._valid) { - // if the etag has changed meanwhile, remove the already downloaded part. - if (progressInfo._etag != _item._etag) { - QFile::remove(_propagator->getFilePath(progressInfo._tmpfile)); - _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo()); - } else { - tmpFileName = progressInfo._tmpfile; - expectedEtagForResume = progressInfo._etag; - } - - } - - if (tmpFileName.isEmpty()) { - tmpFileName = _item._file; - //add a dot at the begining of the filename to hide the file. - int slashPos = tmpFileName.lastIndexOf('/'); - tmpFileName.insert(slashPos+1, '.'); - //add the suffix - tmpFileName += ".~" + QString::number(uint(qrand()), 16); - } - - _tmpFile.setFileName(_propagator->getFilePath(tmpFileName)); - if (!_tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) { - done(SyncFileItem::NormalError, _tmpFile.errorString()); - return; - } - - FileSystem::setFileHidden(_tmpFile.fileName(), true); - - { - SyncJournalDb::DownloadInfo pi; - pi._etag = _item._etag; - pi._tmpfile = tmpFileName; - pi._valid = true; - _propagator->_journal->setDownloadInfo(_item._file, pi); - _propagator->_journal->commit("download file start"); - } - - - QMap headers; - - quint64 startSize = _tmpFile.size(); - if (startSize > 0) { - if (startSize == _item._size) { - qDebug() << "File is already complete, no need to download"; - downloadFinished(); - return; - } - } - - if (_item._directDownloadUrl.isEmpty()) { - // Normal job, download from oC instance - _job = new GETFileJob(AccountManager::instance()->account(), - _propagator->_remoteFolder + _item._file, - &_tmpFile, headers, expectedEtagForResume, startSize); - } else { - // We were provided a direct URL, use that one - qDebug() << Q_FUNC_INFO << "directDownloadUrl given for " << _item._file << _item._directDownloadUrl; - - // Direct URLs don't support resuming, so clear an existing tmp file - if (startSize > 0) { - qDebug() << Q_FUNC_INFO << "resuming not supported for directDownloadUrl, deleting temporary"; - _tmpFile.close(); - if (!_tmpFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) { - done(SyncFileItem::NormalError, _tmpFile.errorString()); - return; - } - startSize = 0; - } - - if (!_item._directDownloadCookies.isEmpty()) { - headers["Cookie"] = _item._directDownloadCookies.toUtf8(); - } - - QUrl url = QUrl::fromUserInput(_item._directDownloadUrl); - _job = new GETFileJob(AccountManager::instance()->account(), - url, - &_tmpFile, headers); - } - _job->setBandwidthManager(&_propagator->_bandwidthManager); - _job->setTimeout(_propagator->httpTimeout() * 1000); - connect(_job, SIGNAL(finishedSignal()), this, SLOT(slotGetFinished())); - connect(_job, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(slotDownloadProgress(qint64,qint64))); - _propagator->_activeJobs ++; - _job->start(); - emitReady(); -} - -void PropagateDownloadFileQNAM::slotGetFinished() -{ - _propagator->_activeJobs--; - - GETFileJob *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()); - - QNetworkReply::NetworkError err = job->reply()->error(); - if (err != QNetworkReply::NoError) { - _item._httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - // If we sent a 'Range' header and get 416 back, we want to retry - // without the header. - bool badRangeHeader = job->resumeStart() > 0 && _item._httpErrorCode == 416; - if (badRangeHeader) { - qDebug() << Q_FUNC_INFO << "server replied 416 to our range request, trying again without"; - _propagator->_anotherSyncNeeded = true; - } - - // Don't keep the temporary file if it is empty or we - // used a bad range header. - if (_tmpFile.size() == 0 || badRangeHeader) { - _tmpFile.close(); - _tmpFile.remove(); - _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo()); - } - - _propagator->_activeJobs--; - SyncFileItem::Status status = job->errorStatus(); - if (status == SyncFileItem::NoStatus) { - status = classifyError(err, _item._httpErrorCode); - } - if (badRangeHeader) { - // Can't do this in classifyError() because 416 without a - // Range header should result in NormalError. - status = SyncFileItem::SoftError; - } - done(status, job->errorString()); - return; - } - - if (!job->etag().isEmpty()) { - // The etag will be empty if we used a direct download URL. - // (If it was really empty by the server, the GETFileJob will have errored - _item._etag = parseEtag(job->etag()); - } - _item._requestDuration = job->duration(); - _item._responseTimeStamp = job->responseTimestamp(); - - _tmpFile.close(); - _tmpFile.flush(); - downloadFinished(); -} - -QString makeConflictFileName(const QString &fn, const QDateTime &dt) -{ - QString conflictFileName(fn); - // Add _conflict-XXXX before the extention. - int dotLocation = conflictFileName.lastIndexOf('.'); - // If no extention, add it at the end (take care of cases like foo/.hidden or foo.bar/file) - if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) { - dotLocation = conflictFileName.size(); - } - QString timeString = dt.toString("yyyyMMdd-hhmmss"); - - // Additional marker - QByteArray conflictFileUserName = qgetenv("CSYNC_CONFLICT_FILE_USERNAME"); - if (conflictFileUserName.isEmpty()) - conflictFileName.insert(dotLocation, "_conflict-" + timeString); - else - conflictFileName.insert(dotLocation, "_conflict_" + QString::fromUtf8(conflictFileUserName) + "-" + timeString); - - return conflictFileName; -} - -void PropagateDownloadFileQNAM::downloadFinished() -{ - - QString fn = _propagator->getFilePath(_item._file); - - // In case of file name clash, report an error - // This can happen if another parallel download saved a clashing file. - if (_propagator->localFileNameClash(_item._file)) { - done( SyncFileItem::NormalError, tr("File %1 cannot be saved because of a local file name clash!") - .arg(QDir::toNativeSeparators(_item._file)) ); - return; - } - - // In case of conflict, make a backup of the old file - // Ignore conflicts where both files are binary equal - bool isConflict = _item._instruction == CSYNC_INSTRUCTION_CONFLICT - && !FileSystem::fileEquals(fn, _tmpFile.fileName()); - if (isConflict) { - QFile f(fn); - QString conflictFileName = makeConflictFileName(fn, Utility::qDateTimeFromTime_t(_item._modtime)); - if (!f.rename(conflictFileName)) { - //If the rename fails, don't replace it. - done(SyncFileItem::NormalError, f.errorString()); - return; - } - } - - QFileInfo existingFile(fn); - if(existingFile.exists() && existingFile.permissions() != _tmpFile.permissions()) { - _tmpFile.setPermissions(existingFile.permissions()); - } - - FileSystem::setFileHidden(_tmpFile.fileName(), false); - - QString error; - if (!FileSystem::renameReplace(_tmpFile.fileName(), fn, &error)) { - // If we moved away the original file due to a conflict but can't - // put the downloaded file in its place, we are in a bad spot: - // If we do nothing the next sync run will assume the user deleted - // the file! - // To avoid that, the file is removed from the metadata table entirely - // which makes it look like we're just about to initially download - // it. - if (isConflict) { - _propagator->_journal->deleteFileRecord(fn); - _propagator->_journal->commit("download finished"); - _propagator->_anotherSyncNeeded = true; - } - - done(SyncFileItem::NormalError, error); - return; - } - - existingFile.refresh(); - // Maybe we downloaded a newer version of the file than we thought we would... - // Get up to date information for the journal. - FileSystem::setModTime(fn, _item._modtime); - _item._size = existingFile.size(); - - _propagator->_journal->setFileRecord(SyncJournalFileRecord(_item, fn)); - _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo()); - _propagator->_journal->commit("download file start2"); - done(isConflict ? SyncFileItem::Conflict : SyncFileItem::Success); -} - -void PropagateDownloadFileQNAM::slotDownloadProgress(qint64 received, qint64) -{ - if (!_job) return; - emit progress(_item, received + _job->resumeStart()); -} - - -void PropagateDownloadFileQNAM::abort() -{ - if (_job && _job->reply()) - _job->reply()->abort(); -} - - -} +} \ No newline at end of file diff --git a/src/libsync/propagator_qnam.h b/src/libsync/propagateupload.h similarity index 54% rename from src/libsync/propagator_qnam.h rename to src/libsync/propagateupload.h index 454f47d96..412059715 100644 --- a/src/libsync/propagator_qnam.h +++ b/src/libsync/propagateupload.h @@ -1,6 +1,5 @@ /* * Copyright (C) by Olivier Goffart - * Copyright (C) by Klaas Freitag * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,9 +13,7 @@ */ #pragma once - #include "owncloudpropagator.h" -#include "owncloudpropagator_p.h" #include "networkjobs.h" #include @@ -134,98 +131,5 @@ private: void startPollJob(const QString& path); }; - -class GETFileJob : public AbstractNetworkJob { - Q_OBJECT - QFile* _device; - QMap _headers; - QString _errorString; - QByteArray _expectedEtagForResume; - quint64 _resumeStart; - SyncFileItem::Status _errorStatus; - QUrl _directDownloadUrl; - QByteArray _etag; - bool _bandwidthLimited; // if _bandwidthQuota will be used - bool _bandwidthChoked; // if download is paused (won't read on readyRead()) - qint64 _bandwidthQuota; - BandwidthManager *_bandwidthManager; - bool _hasEmittedFinishedSignal; -public: - - // DOES NOT take owncership of the device. - explicit GETFileJob(Account* account, const QString& path, QFile *device, - const QMap &headers, QByteArray expectedEtagForResume, - quint64 resumeStart, QObject* parent = 0); - // For directDownloadUrl: - explicit GETFileJob(Account* account, const QUrl& url, QFile *device, - const QMap &headers, - QObject* parent = 0); - virtual ~GETFileJob() { - if (_bandwidthManager) { - _bandwidthManager->unregisterDownloadJob(this); - } - } - - virtual void start() Q_DECL_OVERRIDE; - virtual bool finished() Q_DECL_OVERRIDE { - qDebug() << Q_FUNC_INFO << reply()->bytesAvailable() << _hasEmittedFinishedSignal; - if (reply()->bytesAvailable()) { - qDebug() << Q_FUNC_INFO << "Not all read yet because of bandwidth limits"; - return false; - } else { - if (_bandwidthManager) { - _bandwidthManager->unregisterDownloadJob(this); - } - if (!_hasEmittedFinishedSignal) { - emit finishedSignal(); - } - _hasEmittedFinishedSignal = true; - return true; // discard - } - } - - void setBandwidthManager(BandwidthManager *bwm); - void setChoked(bool c); - void setBandwidthLimited(bool b); - void giveBandwidthQuota(qint64 q); - qint64 currentDownloadPosition(); - - QString errorString() { - return _errorString.isEmpty() ? reply()->errorString() : _errorString; - } - - SyncFileItem::Status errorStatus() { return _errorStatus; } - - virtual void slotTimeout() Q_DECL_OVERRIDE; - - QByteArray &etag() { return _etag; } - quint64 resumeStart() { return _resumeStart; } - - -signals: - void finishedSignal(); - void downloadProgress(qint64,qint64); -private slots: - void slotReadyRead(); - void slotMetaDataChanged(); -}; - - -class PropagateDownloadFileQNAM : public PropagateItemJob { - Q_OBJECT - QPointer _job; - -// QFile *_file; - QFile _tmpFile; -public: - PropagateDownloadFileQNAM(OwncloudPropagator* propagator,const SyncFileItem& item) - : PropagateItemJob(propagator, item) {} - void start() Q_DECL_OVERRIDE; -private slots: - void slotGetFinished(); - void abort() Q_DECL_OVERRIDE; - void downloadFinished(); - void slotDownloadProgress(qint64,qint64); -}; - } + diff --git a/src/libsync/propagator_legacy.cpp b/src/libsync/propagator_legacy.cpp index 7b6a9bf7d..30e5ced94 100644 --- a/src/libsync/propagator_legacy.cpp +++ b/src/libsync/propagator_legacy.cpp @@ -483,7 +483,7 @@ void PropagateDownloadFileLegacy::notify_status_cb(void* userdata, ne_session_st } } -extern QString makeConflictFileName(const QString &fn, const QDateTime &dt); // _qnam.cpp +extern QString makeConflictFileName(const QString &fn, const QDateTime &dt); // propagatedownload.cpp void PropagateDownloadFileLegacy::start() {