2016-08-02 14:48:56 +03:00
|
|
|
/*
|
|
|
|
* Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
|
|
|
|
*
|
|
|
|
* 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 "config.h"
|
|
|
|
#include "propagateupload.h"
|
|
|
|
#include "owncloudpropagator_p.h"
|
|
|
|
#include "networkjobs.h"
|
|
|
|
#include "account.h"
|
|
|
|
#include "syncjournaldb.h"
|
|
|
|
#include "syncjournalfilerecord.h"
|
|
|
|
#include "utility.h"
|
|
|
|
#include "filesystem.h"
|
|
|
|
#include "propagatorjobs.h"
|
|
|
|
#include "syncengine.h"
|
|
|
|
#include "propagateremotemove.h"
|
2016-10-31 19:44:00 +03:00
|
|
|
#include "propagateremotedelete.h"
|
|
|
|
|
2016-08-02 14:48:56 +03:00
|
|
|
|
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QFileInfo>
|
|
|
|
#include <QDir>
|
|
|
|
#include <cmath>
|
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
namespace OCC {
|
|
|
|
|
|
|
|
QUrl PropagateUploadFileNG::chunkUrl(int chunk)
|
|
|
|
{
|
|
|
|
QString path = QLatin1String("remote.php/dav/uploads/")
|
2016-11-23 19:08:17 +03:00
|
|
|
+ _propagator->account()->davUser()
|
2016-08-02 14:48:56 +03:00
|
|
|
+ QLatin1Char('/') + QString::number(_transferId);
|
|
|
|
if (chunk >= 0) {
|
|
|
|
path += QLatin1Char('/') + QString::number(chunk);
|
|
|
|
}
|
2016-10-31 13:29:33 +03:00
|
|
|
return Utility::concatUrlPath(_propagator->account()->url(), path);
|
2016-08-02 14:48:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
State machine:
|
|
|
|
|
|
|
|
*----> doStartUpload()
|
|
|
|
Check the db: is there an entry?
|
2016-08-02 18:14:44 +03:00
|
|
|
/ \
|
|
|
|
no yes
|
|
|
|
/ \
|
|
|
|
/ PROPFIND
|
|
|
|
startNewUpload() <-+ +----------------------------\
|
|
|
|
| | | \
|
|
|
|
MKCOL + slotPropfindFinishedWithError() slotPropfindFinished()
|
2016-10-31 19:44:00 +03:00
|
|
|
| Is there stale files to remove?
|
|
|
|
slotMkColFinished() | |
|
|
|
|
| no yes
|
|
|
|
| | |
|
|
|
|
| | DeleteJob
|
|
|
|
| | |
|
|
|
|
+-----+<------------------------------------------------------+<--- slotDeleteJobFinished()
|
2016-08-02 14:48:56 +03:00
|
|
|
|
|
|
|
|
+----> startNextChunk() ---finished? --+
|
|
|
|
^ | |
|
|
|
|
+---------------+ |
|
|
|
|
|
|
|
|
|
+----------------------------------------+
|
|
|
|
|
|
|
|
|
+-> MOVE ------> moveJobFinished() ---> finalize()
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
void PropagateUploadFileNG::doStartUpload()
|
|
|
|
{
|
2016-08-02 18:14:44 +03:00
|
|
|
_duration.start();
|
|
|
|
_propagator->_activeJobList.append(this);
|
2016-08-02 14:48:56 +03:00
|
|
|
|
|
|
|
const SyncJournalDb::UploadInfo progressInfo = _propagator->_journal->getUploadInfo(_item->_file);
|
|
|
|
if (progressInfo._valid && Utility::qDateTimeToTime_t(progressInfo._modtime) == _item->_modtime ) {
|
|
|
|
_transferId = progressInfo._transferid;
|
2016-08-02 18:14:44 +03:00
|
|
|
auto url = chunkUrl();
|
|
|
|
auto job = new LsColJob(_propagator->account(), url, this);
|
|
|
|
_jobs.append(job);
|
|
|
|
job->setProperties(QList<QByteArray>() << "resourcetype" << "getcontentlength");
|
|
|
|
connect(job, SIGNAL(finishedWithoutError()), this, SLOT(slotPropfindFinished()));
|
|
|
|
connect(job, SIGNAL(finishedWithError(QNetworkReply*)),
|
|
|
|
this, SLOT(slotPropfindFinishedWithError()));
|
|
|
|
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
|
2016-08-03 18:43:03 +03:00
|
|
|
connect(job, SIGNAL(directoryListingIterated(QString,QMap<QString,QString>)),
|
|
|
|
this, SLOT(slotPropfindIterate(QString,QMap<QString,QString>)));
|
2016-08-02 18:14:44 +03:00
|
|
|
job->start();
|
|
|
|
return;
|
|
|
|
}
|
2016-08-02 14:48:56 +03:00
|
|
|
|
2016-08-02 18:14:44 +03:00
|
|
|
startNewUpload();
|
|
|
|
}
|
|
|
|
|
2016-08-03 18:43:03 +03:00
|
|
|
void PropagateUploadFileNG::slotPropfindIterate(const QString &name, const QMap<QString,QString> &properties)
|
|
|
|
{
|
|
|
|
if (name == chunkUrl().path()) {
|
|
|
|
return; // skip the info about the path itself
|
|
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
auto chunkId = name.mid(name.lastIndexOf('/')+1).toUInt(&ok);
|
|
|
|
if (ok) {
|
2016-11-14 12:42:07 +03:00
|
|
|
_serverChunks[chunkId] = properties["getcontentlength"].toULongLong();
|
2016-08-03 18:43:03 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-02 18:14:44 +03:00
|
|
|
void PropagateUploadFileNG::slotPropfindFinished()
|
|
|
|
{
|
|
|
|
auto job = qobject_cast<LsColJob *>(sender());
|
|
|
|
slotJobDestroyed(job); // remove it from the _jobs list
|
|
|
|
_propagator->_activeJobList.removeOne(this);
|
|
|
|
|
|
|
|
_currentChunk = 0;
|
|
|
|
_sent = 0;
|
|
|
|
while (_serverChunks.contains(_currentChunk)) {
|
|
|
|
_sent += _serverChunks[_currentChunk];
|
2016-10-31 19:44:00 +03:00
|
|
|
_serverChunks.remove(_currentChunk);
|
2016-08-02 18:14:44 +03:00
|
|
|
++_currentChunk;
|
2016-08-02 14:48:56 +03:00
|
|
|
}
|
2016-10-31 19:44:00 +03:00
|
|
|
|
2016-11-14 12:42:07 +03:00
|
|
|
if (_sent > _item->_size) {
|
|
|
|
// Normally this can't happen because the size is xor'ed with the transfer id, and it is
|
|
|
|
// therefore impossible that there is more data on the server than on the file.
|
|
|
|
qWarning() << "Inconsistency while resuming " << _item->_file
|
|
|
|
<< ": the size on the server (" << _sent << ") is bigger than the size of the file ("
|
|
|
|
<< _item->_size << ")";
|
|
|
|
startNewUpload();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-03 18:43:03 +03:00
|
|
|
qDebug() << "Resuming "<< _item->_file << " from chunk " << _currentChunk << "; sent ="<< _sent;
|
2016-10-31 19:44:00 +03:00
|
|
|
|
|
|
|
if (!_serverChunks.isEmpty()) {
|
|
|
|
qDebug() << "To Delete" << _serverChunks;
|
|
|
|
_propagator->_activeJobList.append(this);
|
|
|
|
_removeJobError = false;
|
|
|
|
|
|
|
|
// Make sure that if there is a "hole" and then a few more chunks, on the server
|
|
|
|
// we should remove the later chunks. Otherwise when we do dynamic chunk sizing, we may end up
|
|
|
|
// with corruptions if there are too many chunks, or if we abort and there are still stale chunks.
|
|
|
|
for (auto it = _serverChunks.begin(); it != _serverChunks.end(); ++it) {
|
|
|
|
auto job = new DeleteJob(_propagator->account(), Utility::concatUrlPath(chunkUrl(), QString::number(it.key())), this);
|
|
|
|
QObject::connect(job, SIGNAL(finishedSignal()), this, SLOT(slotDeleteJobFinished()));
|
|
|
|
_jobs.append(job);
|
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-02 18:14:44 +03:00
|
|
|
startNextChunk();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PropagateUploadFileNG::slotPropfindFinishedWithError()
|
|
|
|
{
|
|
|
|
auto job = qobject_cast<LsColJob *>(sender());
|
|
|
|
slotJobDestroyed(job); // remove it from the _jobs list
|
|
|
|
QNetworkReply::NetworkError err = job->reply()->error();
|
|
|
|
auto httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
auto status = classifyError(err, httpErrorCode, &_propagator->_anotherSyncNeeded);
|
|
|
|
if (status == SyncFileItem::FatalError) {
|
|
|
|
_propagator->_activeJobList.removeOne(this);
|
|
|
|
QString errorString = errorMessage(job->reply()->errorString(), job->reply()->readAll());
|
|
|
|
abortWithError(status, errorString);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
startNewUpload();
|
|
|
|
}
|
|
|
|
|
2016-10-31 19:44:00 +03:00
|
|
|
void PropagateUploadFileNG::slotDeleteJobFinished()
|
|
|
|
{
|
|
|
|
auto job = qobject_cast<DeleteJob *>(sender());
|
|
|
|
Q_ASSERT(job);
|
|
|
|
_jobs.remove(_jobs.indexOf(job));
|
|
|
|
|
|
|
|
QNetworkReply::NetworkError err = job->reply()->error();
|
|
|
|
if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) {
|
|
|
|
const int httpStatus = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
SyncFileItem::Status status = classifyError(err, httpStatus);
|
|
|
|
if (status == SyncFileItem::FatalError) {
|
|
|
|
abortWithError(status, job->errorString());
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
qWarning() << "DeleteJob errored out" << job->errorString() << job->reply()->url();
|
|
|
|
_removeJobError = true;
|
|
|
|
// Let the other jobs finish
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_jobs.isEmpty()) {
|
|
|
|
_propagator->_activeJobList.removeOne(this);
|
|
|
|
if (_removeJobError) {
|
|
|
|
// There was an error removing some files, just start over
|
|
|
|
startNewUpload();
|
|
|
|
} else {
|
|
|
|
startNextChunk();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-02 14:48:56 +03:00
|
|
|
|
2016-08-02 18:14:44 +03:00
|
|
|
void PropagateUploadFileNG::startNewUpload()
|
|
|
|
{
|
|
|
|
Q_ASSERT(_propagator->_activeJobList.count(this) == 1);
|
|
|
|
_transferId = qrand() ^ _item->_modtime ^ (_item->_size << 16) ^ qHash(_item->_file);
|
2016-08-02 14:48:56 +03:00
|
|
|
_sent = 0;
|
|
|
|
_currentChunk = 0;
|
|
|
|
|
|
|
|
emit progress(*_item, 0);
|
|
|
|
|
2016-08-02 18:14:44 +03:00
|
|
|
SyncJournalDb::UploadInfo pi;
|
|
|
|
pi._valid = true;
|
|
|
|
pi._transferid = _transferId;
|
|
|
|
pi._modtime = Utility::qDateTimeFromTime_t(_item->_modtime);
|
|
|
|
_propagator->_journal->setUploadInfo(_item->_file, pi);
|
|
|
|
_propagator->_journal->commit("Upload info");
|
2016-09-16 17:36:46 +03:00
|
|
|
QMap<QByteArray, QByteArray> headers;
|
|
|
|
headers["OC-Total-Length"] = QByteArray::number(_item->_size);
|
|
|
|
auto job = new MkColJob(_propagator->account(), chunkUrl(), headers, this);
|
2016-08-02 14:48:56 +03:00
|
|
|
|
|
|
|
connect(job, SIGNAL(finished(QNetworkReply::NetworkError)),
|
|
|
|
this, SLOT(slotMkColFinished(QNetworkReply::NetworkError)));
|
|
|
|
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
|
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PropagateUploadFileNG::slotMkColFinished(QNetworkReply::NetworkError)
|
|
|
|
{
|
|
|
|
_propagator->_activeJobList.removeOne(this);
|
|
|
|
auto job = qobject_cast<MkColJob *>(sender());
|
|
|
|
slotJobDestroyed(job); // remove it from the _jobs list
|
|
|
|
QNetworkReply::NetworkError err = job->reply()->error();
|
|
|
|
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
|
|
|
|
if (err != QNetworkReply::NoError || _item->_httpErrorCode != 201) {
|
|
|
|
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
|
|
|
|
&_propagator->_anotherSyncNeeded);
|
|
|
|
QString errorString = errorMessage(job->reply()->errorString(), job->reply()->readAll());
|
|
|
|
if (job->reply()->hasRawHeader("OC-ErrorString")) {
|
|
|
|
errorString = job->reply()->rawHeader("OC-ErrorString");
|
|
|
|
}
|
|
|
|
abortWithError(status, errorString);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
startNextChunk();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PropagateUploadFileNG::startNextChunk()
|
|
|
|
{
|
|
|
|
if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
|
|
|
|
return;
|
|
|
|
|
|
|
|
quint64 fileSize = _item->_size;
|
2016-11-14 12:42:07 +03:00
|
|
|
Q_ASSERT(fileSize >= _sent);
|
2016-08-02 14:48:56 +03:00
|
|
|
quint64 currentChunkSize = qMin(chunkSize(), fileSize - _sent);
|
|
|
|
|
2016-11-14 12:42:07 +03:00
|
|
|
if (currentChunkSize == 0) {
|
2016-08-02 14:48:56 +03:00
|
|
|
Q_ASSERT(_jobs.isEmpty()); // There should be no running job anymore
|
|
|
|
_finished = true;
|
|
|
|
// Finish with a MOVE
|
2016-11-16 11:30:29 +03:00
|
|
|
QString destination = QDir::cleanPath(_propagator->account()->url().path() + QLatin1Char('/')
|
|
|
|
+ _propagator->account()->davPath() + _propagator->_remoteFolder + _item->_file);
|
2016-08-02 14:48:56 +03:00
|
|
|
auto headers = PropagateUploadFileCommon::headers();
|
2016-09-16 17:15:09 +03:00
|
|
|
|
|
|
|
// "If-Match applies to the source, but we are interested in comparing the etag of the destination
|
|
|
|
auto ifMatch = headers.take("If-Match");
|
|
|
|
if (!ifMatch.isEmpty()) {
|
2016-10-20 12:16:06 +03:00
|
|
|
headers["If"] = "<" + destination.toUtf8() + "> ([" + ifMatch + "])";
|
2016-09-16 17:15:09 +03:00
|
|
|
}
|
2016-09-16 17:36:46 +03:00
|
|
|
if (!_transmissionChecksumType.isEmpty()) {
|
|
|
|
headers[checkSumHeaderC] = makeChecksumHeader(
|
|
|
|
_transmissionChecksumType, _transmissionChecksum);
|
|
|
|
}
|
|
|
|
|
2016-10-31 13:29:33 +03:00
|
|
|
auto job = new MoveJob(_propagator->account(), Utility::concatUrlPath(chunkUrl(), "/.file"),
|
2016-08-02 14:48:56 +03:00
|
|
|
destination, headers, this);
|
|
|
|
_jobs.append(job);
|
|
|
|
connect(job, SIGNAL(finishedSignal()), this, SLOT(slotMoveJobFinished()));
|
|
|
|
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
|
|
|
|
_propagator->_activeJobList.append(this);
|
|
|
|
job->start();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto device = new UploadDevice(&_propagator->_bandwidthManager);
|
|
|
|
const QString fileName = _propagator->getFilePath(_item->_file);
|
|
|
|
|
|
|
|
if (! device->prepareAndOpen(fileName, _sent, currentChunkSize)) {
|
|
|
|
qDebug() << "ERR: Could not prepare upload device: " << device->errorString();
|
|
|
|
|
|
|
|
// If the file is currently locked, we want to retry the sync
|
|
|
|
// when it becomes available again.
|
|
|
|
if (FileSystem::isFileLocked(fileName)) {
|
|
|
|
emit _propagator->seenLockedFile(fileName);
|
|
|
|
}
|
|
|
|
// Soft error because this is likely caused by the user modifying his files while syncing
|
|
|
|
abortWithError( SyncFileItem::SoftError, device->errorString() );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-09-16 17:36:46 +03:00
|
|
|
QMap<QByteArray, QByteArray> headers;
|
|
|
|
headers["OC-Chunk-Offset"] = QByteArray::number(_sent);
|
|
|
|
|
2016-08-02 14:48:56 +03:00
|
|
|
_sent += currentChunkSize;
|
|
|
|
QUrl url = chunkUrl(_currentChunk);
|
|
|
|
|
|
|
|
// job takes ownership of device via a QScopedPointer. Job deletes itself when finishing
|
Propagator: make sure every network job has a parent
This could make sure that the network job gets deleted if the parent job gets
deleted, and would avoid crashes like:
Crash: EXCEPTION_ACCESS_VIOLATION_READ at 0xffffffff8b008a04
File "qiodevice.cpp", line 1617, in QIODevice::errorString
File "propagatedownload.cpp", line 264, in OCC::GETFileJob::slotReadyRead
File "moc_propagatedownload.cpp", line 85, in OCC::GETFileJob::qt_static_metacall
File "qobject.cpp", line 3716, in QMetaObject::activate
File "moc_qiodevice.cpp", line 154, in QIODevice::readyRead
File "qnetworkreplyhttpimpl.cpp", line 1045, in QNetworkReplyHttpImplPrivate::replyDownloadData
(#5329)
2016-11-29 18:20:19 +03:00
|
|
|
PUTFileJob* job = new PUTFileJob(_propagator->account(), url, device, headers, _currentChunk, this);
|
2016-08-02 14:48:56 +03:00
|
|
|
_jobs.append(job);
|
|
|
|
connect(job, SIGNAL(finishedSignal()), this, SLOT(slotPutFinished()));
|
|
|
|
connect(job, SIGNAL(uploadProgress(qint64,qint64)),
|
|
|
|
this, SLOT(slotUploadProgress(qint64,qint64)));
|
|
|
|
connect(job, SIGNAL(uploadProgress(qint64,qint64)),
|
|
|
|
device, SLOT(slotJobUploadProgress(qint64,qint64)));
|
|
|
|
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
|
|
|
|
job->start();
|
|
|
|
_propagator->_activeJobList.append(this);
|
|
|
|
_currentChunk++;
|
|
|
|
|
|
|
|
// FIXME! parallel chunk?
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void PropagateUploadFileNG::slotPutFinished()
|
|
|
|
{
|
|
|
|
PUTFileJob *job = qobject_cast<PUTFileJob *>(sender());
|
|
|
|
Q_ASSERT(job);
|
|
|
|
slotJobDestroyed(job); // remove it from the _jobs list
|
|
|
|
|
|
|
|
qDebug() << 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);
|
|
|
|
|
|
|
|
_propagator->_activeJobList.removeOne(this);
|
|
|
|
|
|
|
|
if (_finished) {
|
|
|
|
// We have sent the finished signal already. We don't need to handle any remaining jobs
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QNetworkReply::NetworkError err = job->reply()->error();
|
|
|
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2)
|
2016-08-03 18:43:03 +03:00
|
|
|
if (err == QNetworkReply::OperationCanceledError && job->reply()->property("owncloud-should-soft-cancel").isValid()) {
|
2016-08-02 14:48:56 +03:00
|
|
|
// Abort the job and try again later.
|
|
|
|
// This works around a bug in QNAM wich might reuse a non-empty buffer for the next request.
|
|
|
|
qDebug() << "Forcing job abort on HTTP connection reset with Qt < 5.4.2.";
|
|
|
|
_propagator->_anotherSyncNeeded = true;
|
|
|
|
abortWithError(SyncFileItem::SoftError, tr("Forcing job abort on HTTP connection reset with Qt < 5.4.2."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (err != QNetworkReply::NoError) {
|
|
|
|
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
QByteArray replyContent = job->reply()->readAll();
|
|
|
|
qDebug() << replyContent; // display the XML error in the debug
|
|
|
|
QString errorString = errorMessage(job->errorString(), replyContent);
|
|
|
|
|
|
|
|
if (job->reply()->hasRawHeader("OC-ErrorString")) {
|
|
|
|
errorString = job->reply()->rawHeader("OC-ErrorString");
|
|
|
|
}
|
|
|
|
|
2016-09-10 13:30:14 +03:00
|
|
|
// FIXME! can this happen for the chunks?
|
2016-08-02 14:48:56 +03:00
|
|
|
if (_item->_httpErrorCode == 412) {
|
|
|
|
// Precondition Failed: Maybe the bad etag is in the database, we need to clear the
|
|
|
|
// parent folder etag so we won't read from DB next sync.
|
|
|
|
_propagator->_journal->avoidReadFromDbOnNextSync(_item->_file);
|
|
|
|
_propagator->_anotherSyncNeeded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
|
|
|
|
&_propagator->_anotherSyncNeeded);
|
|
|
|
abortWithError(status, errorString);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-11-14 12:42:07 +03:00
|
|
|
Q_ASSERT(_sent <= _item->_size);
|
|
|
|
bool finished = _sent == _item->_size;
|
2016-08-02 14:48:56 +03:00
|
|
|
|
|
|
|
// Check if the file still exists
|
|
|
|
const QString fullFilePath(_propagator->getFilePath(_item->_file));
|
|
|
|
if( !FileSystem::fileExists(fullFilePath) ) {
|
|
|
|
if (!finished) {
|
|
|
|
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
_propagator->_anotherSyncNeeded = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether the file changed since discovery.
|
|
|
|
if (! FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
|
|
|
|
_propagator->_anotherSyncNeeded = true;
|
|
|
|
if( !finished ) {
|
|
|
|
abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!finished) {
|
|
|
|
// Deletes an existing blacklist entry on successful chunk upload
|
|
|
|
if (_item->_hasBlacklistEntry) {
|
|
|
|
_propagator->_journal->wipeErrorBlacklistEntry(_item->_file);
|
|
|
|
_item->_hasBlacklistEntry = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
startNextChunk();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PropagateUploadFileNG::slotMoveJobFinished()
|
|
|
|
{
|
|
|
|
_propagator->_activeJobList.removeOne(this);
|
|
|
|
auto job = qobject_cast<MoveJob *>(sender());
|
|
|
|
slotJobDestroyed(job); // remove it from the _jobs list
|
|
|
|
QNetworkReply::NetworkError err = job->reply()->error();
|
|
|
|
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
|
2016-10-31 17:16:53 +03:00
|
|
|
if (err != QNetworkReply::NoError) {
|
2016-08-02 14:48:56 +03:00
|
|
|
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
|
|
|
|
&_propagator->_anotherSyncNeeded);
|
|
|
|
QString errorString = errorMessage(job->errorString(), job->reply()->readAll());
|
|
|
|
abortWithError(status, errorString);
|
|
|
|
return;
|
|
|
|
}
|
2016-10-31 17:16:53 +03:00
|
|
|
if (_item->_httpErrorCode != 201 && _item->_httpErrorCode != 204) {
|
|
|
|
abortWithError(SyncFileItem::NormalError, tr("Unexpected return code from server (%1)").arg(_item->_httpErrorCode));
|
|
|
|
return;
|
|
|
|
}
|
2016-08-02 14:48:56 +03:00
|
|
|
|
|
|
|
QByteArray fid = job->reply()->rawHeader("OC-FileID");
|
|
|
|
if(fid.isEmpty()) {
|
|
|
|
qWarning() << "Server did not return a OC-FileID" << _item->_file;
|
2016-09-10 13:30:14 +03:00
|
|
|
abortWithError(SyncFileItem::NormalError, tr("Missing File ID from server"));
|
|
|
|
return;
|
2016-08-02 14:48:56 +03:00
|
|
|
} else {
|
|
|
|
// the old file id should only be empty for new files uploaded
|
|
|
|
if( !_item->_fileId.isEmpty() && _item->_fileId != fid ) {
|
|
|
|
qDebug() << "WARN: File ID changed!" << _item->_fileId << fid;
|
|
|
|
}
|
|
|
|
_item->_fileId = fid;
|
|
|
|
}
|
|
|
|
|
|
|
|
_item->_etag = getEtagFromReply(job->reply());;
|
|
|
|
if (_item->_etag.isEmpty()) {
|
|
|
|
qWarning() << "Server did not return an ETAG" << _item->_file;
|
2016-09-10 13:30:14 +03:00
|
|
|
abortWithError(SyncFileItem::NormalError, tr("Missing ETag from server"));
|
|
|
|
return;
|
2016-08-02 14:48:56 +03:00
|
|
|
}
|
|
|
|
_item->_responseTimeStamp = job->responseTimestamp();
|
|
|
|
|
|
|
|
// performance logging
|
|
|
|
_item->_requestDuration = _stopWatch.stop();
|
|
|
|
qDebug() << "*==* duration UPLOAD" << _item->_size
|
|
|
|
<< _stopWatch.durationOfLap(QLatin1String("ContentChecksum"))
|
|
|
|
<< _stopWatch.durationOfLap(QLatin1String("TransmissionChecksum"))
|
|
|
|
<< _item->_requestDuration;
|
|
|
|
// The job might stay alive for the whole sync, release this tiny bit of memory.
|
|
|
|
_stopWatch.reset();
|
|
|
|
finalize();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PropagateUploadFileNG::slotUploadProgress(qint64 sent, qint64 total)
|
|
|
|
{
|
|
|
|
// Completion is signaled with sent=0, total=0; avoid accidentally
|
|
|
|
// resetting progress due to the sent being zero by ignoring it.
|
|
|
|
// finishedSignal() is bound to be emitted soon anyway.
|
|
|
|
// See https://bugreports.qt.io/browse/QTBUG-44782.
|
|
|
|
if (sent == 0 && total == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
emit progress(*_item, _sent + sent - total);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|