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); abortWithError(status, job->errorStringParsingBody()); return; } startNewUpload(); }
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); abortWithError(status, job->errorStringParsingBody()); return; } startNextChunk(); }
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].size; _serverChunks.remove(_currentChunk); ++_currentChunk; } 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. qCCritical(lcPropagateUpload) << "Inconsistency while resuming " << _item->_file << ": the size on the server (" << _sent << ") is bigger than the size of the file (" << _item->_size << ")"; startNewUpload(); return; } qCInfo(lcPropagateUpload) << "Resuming " << _item->_file << " from chunk " << _currentChunk << "; sent =" << _sent; if (!_serverChunks.isEmpty()) { qCInfo(lcPropagateUpload) << "To Delete" << _serverChunks.keys(); 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(), it->originalName), this); QObject::connect(job, &DeleteJob::finishedSignal, this, &PropagateUploadFileNG::slotDeleteJobFinished); _jobs.append(job); job->start(); } _serverChunks.clear(); return; } 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(); if (err != QNetworkReply::NoError) { commonErrorHandling(job); return; } if (_item->_httpErrorCode != 201 && _item->_httpErrorCode != 204) { abortWithError(SyncFileItem::NormalError, tr("Unexpected return code from server (%1)").arg(_item->_httpErrorCode)); return; } QByteArray fid = job->reply()->rawHeader("OC-FileID"); if (fid.isEmpty()) { qCWarning(lcPropagateUpload) << "Server did not return a OC-FileID" << _item->_file; abortWithError(SyncFileItem::NormalError, tr("Missing File ID from server")); return; } else { // the old file id should only be empty for new files uploaded if (!_item->_fileId.isEmpty() && _item->_fileId != fid) { qCWarning(lcPropagateUpload) << "File ID changed!" << _item->_fileId << fid; } _item->_fileId = fid; } _item->_etag = getEtagFromReply(job->reply()); ; if (_item->_etag.isEmpty()) { qCWarning(lcPropagateUpload) << "Server did not return an ETAG" << _item->_file; abortWithError(SyncFileItem::NormalError, tr("Missing ETag from server")); return; } _item->_responseTimeStamp = job->responseTimestamp(); finalize(); }
void PropagateUploadFileQNAM::slotPutFinished() { PUTFileJob *job = qobject_cast<PUTFileJob *>(sender()); Q_ASSERT(job); slotJobDestroyed(job); // remove it from the _jobs list 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); _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) if (err == QNetworkReply::OperationCanceledError && job->reply()->property(owncloudShouldSoftCancelPropertyName).isValid()) { // 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(); if(checkForProblemsWithShared(_item->_httpErrorCode, tr("The file was edited locally but is part of a read only share. " "It is restored and your edit is in the conflict file."))) { return; } 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"); } 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; } _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // The server needs some time to process the request and provide us with a poll URL if (_item->_httpErrorCode == 202) { _finished = true; QString path = QString::fromUtf8(job->reply()->rawHeader("OC-Finish-Poll")); if (path.isEmpty()) { done(SyncFileItem::NormalError, tr("Poll URL missing")); return; } startPollJob(path); return; } // Check the file again post upload. // Two cases must be considered separately: If the upload is finished, // the file is on the server and has a changed ETag. In that case, // the etag has to be properly updated in the client journal, and because // of that we can bail out here with an error. But we can reschedule a // sync ASAP. // But if the upload is ongoing, because not all chunks were uploaded // yet, the upload can be stopped and an error can be displayed, because // the server hasn't registered the new file yet. QByteArray etag = getEtagFromReply(job->reply()); bool finished = etag.length() > 0; // 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.")); // FIXME: the legacy code was retrying for a few seconds. // and also checking that after the last chunk, and removed the file in case of INSTRUCTION_NEW return; } } if (!finished) { // Proceed to next chunk. if (_currentChunk >= _chunkCount) { if (!_jobs.empty()) { // just wait for the other job to finish. return; } _finished = true; done(SyncFileItem::NormalError, tr("The server did not acknowledge the last chunk. (No e-tag was present)")); return; } // Deletes an existing blacklist entry on successful chunk upload if (_item->_hasBlacklistEntry) { _propagator->_journal->wipeErrorBlacklistEntry(_item->_file); _item->_hasBlacklistEntry = false; } SyncJournalDb::UploadInfo pi; pi._valid = true; auto currentChunk = job->_chunk; foreach (auto *job, _jobs) { // Take the minimum finished one if (auto putJob = qobject_cast<PUTFileJob*>(job)) { currentChunk = qMin(currentChunk, putJob->_chunk - 1); } } pi._chunk = (currentChunk + _startChunk + 1) % _chunkCount ; // next chunk to start with pi._transferid = _transferId; pi._modtime = Utility::qDateTimeFromTime_t(_item->_modtime); _propagator->_journal->setUploadInfo(_item->_file, pi); _propagator->_journal->commit("Upload info"); startNextChunk(); return; } // the following code only happens after all chunks were uploaded. _finished = true; // the file id should only be empty for new files up- or downloaded QByteArray fid = job->reply()->rawHeader("OC-FileID"); if( !fid.isEmpty() ) { if( !_item->_fileId.isEmpty() && _item->_fileId != fid ) { qDebug() << "WARN: File ID changed!" << _item->_fileId << fid; } _item->_fileId = fid; } _item->_etag = etag; _item->_responseTimeStamp = job->responseTimestamp(); if (job->reply()->rawHeader("X-OC-MTime") != "accepted") { // X-OC-MTime is supported since owncloud 5.0. But not when chunking. // Normally Owncloud 6 always puts X-OC-MTime qWarning() << "Server does not support X-OC-MTime" << job->reply()->rawHeader("X-OC-MTime"); // Well, the mtime was not set done(SyncFileItem::SoftError, "Server does not support X-OC-MTime"); } // performance logging _item->_requestDuration = _stopWatch.stop(); qDebug() << "*==* duration UPLOAD" << _item->_size << _stopWatch.durationOfLap(QLatin1String("ContentChecksum")) << _stopWatch.durationOfLap(QLatin1String("TransmissionChecksum")) << _item->_requestDuration; finalize(*_item); }
void PropagateUploadFileQNAM::slotPutFinished() { PUTFileJob *job = qobject_cast<PUTFileJob *>(sender()); Q_ASSERT(job); slotJobDestroyed(job); // remove it from the _jobs list 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); _propagator->_activeJobs--; if (_finished) { // We have send the finished signal already. We don't need to handle any remaining jobs return; } QNetworkReply::NetworkError err = job->reply()->error(); if (err != QNetworkReply::NoError) { _item._httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if(checkForProblemsWithShared(_item._httpErrorCode, tr("The file was edited locally but is part of a read only share. " "It is restored and your edit is in the conflict file."))) { return; } QString errorString = job->errorString(); QByteArray replyContent = job->reply()->readAll(); qDebug() << replyContent; // display the XML error in the debug QRegExp rx("<s:message>(.*)</s:message>"); // Issue #1366: display server exception if (rx.indexIn(QString::fromUtf8(replyContent)) != -1) { errorString += QLatin1String(" (") + rx.cap(1) + QLatin1Char(')'); } if (job->reply()->hasRawHeader("OC-ErrorString")) { errorString = job->reply()->rawHeader("OC-ErrorString"); } 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; } abortWithError(classifyError(err, _item._httpErrorCode), errorString); 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) { _finished = true; QString path = QString::fromUtf8(job->reply()->rawHeader("OC-Finish-Poll")); if (path.isEmpty()) { done(SyncFileItem::NormalError, tr("Poll URL missing")); return; } startPollJob(path); return; } // Check the file again post upload. // Two cases must be considered separately: If the upload is finished, // the file is on the server and has a changed ETag. In that case, // the etag has to be properly updated in the client journal, and because // of that we can bail out here with an error. But we can reschedule a // sync ASAP. // But if the upload is ongoing, because not all chunks were uploaded // yet, the upload can be stopped and an error can be displayed, because // the server hasn't registered the new file yet. QByteArray etag = getEtagFromReply(job->reply()); bool finished = etag.length() > 0; // 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; } } // compare expected and real modification time of the file and size const time_t new_mtime = FileSystem::getModTime(fullFilePath); const quint64 new_size = static_cast<quint64>(FileSystem::getSize(fullFilePath)); QFileInfo fi(_propagator->getFilePath(_item._file)); if (new_mtime != _item._modtime || new_size != _item._size) { qDebug() << "The local file has changed during upload:" << "mtime: " << _item._modtime << "<->" << new_mtime << ", size: " << _item._size << "<->" << new_size << ", QFileInfo: " << Utility::qDateTimeToTime_t(fi.lastModified()) << fi.lastModified(); _propagator->_anotherSyncNeeded = true; if( !finished ) { abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync.")); // FIXME: the legacy code was retrying for a few seconds. // and also checking that after the last chunk, and removed the file in case of INSTRUCTION_NEW return; } } if (!finished) { // Proceed to next chunk. if (_currentChunk >= _chunkCount) { if (!_jobs.empty()) { // just wait for the other job to finish. return; } _finished = true; done(SyncFileItem::NormalError, tr("The server did not acknowledge the last chunk. (No e-tag were present)")); return; } SyncJournalDb::UploadInfo pi; pi._valid = true; auto currentChunk = job->_chunk; foreach (auto *job, _jobs) { // Take the minimum finished one currentChunk = qMin(currentChunk, job->_chunk - 1); } pi._chunk = (currentChunk + _startChunk + 1) % _chunkCount ; // next chunk to start with pi._transferid = _transferId; pi._modtime = Utility::qDateTimeFromTime_t(_item._modtime); _propagator->_journal->setUploadInfo(_item._file, pi); _propagator->_journal->commit("Upload info"); startNextChunk(); return; }
void PropagateUploadFileNG::slotPutFinished() { PUTFileJob *job = qobject_cast<PUTFileJob *>(sender()); ASSERT(job); slotJobDestroyed(job); // remove it from the _jobs list 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 (err != QNetworkReply::NoError) { _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); commonErrorHandling(job); return; } ENFORCE(_sent <= _item->_size, "can't send more than size"); // Adjust the chunk size for the time taken. // // Dynamic chunk sizing is enabled if the server configured a // target duration for each chunk upload. double targetDuration = propagator()->syncOptions()._targetChunkUploadDuration; if (targetDuration > 0) { double uploadTime = job->msSinceStart() + 1; // add one to avoid div-by-zero auto predictedGoodSize = static_cast<quint64>( _currentChunkSize / uploadTime * targetDuration); // The whole targeting is heuristic. The predictedGoodSize will fluctuate // quite a bit because of external factors (like available bandwidth) // and internal factors (like number of parallel uploads). // // We use an exponential moving average here as a cheap way of smoothing // the chunk sizes a bit. quint64 targetSize = (propagator()->_chunkSize + predictedGoodSize) / 2; // Adjust the dynamic chunk size _chunkSize used for sizing of the item's chunks to be send propagator()->_chunkSize = qBound( propagator()->syncOptions()._minChunkSize, targetSize, propagator()->syncOptions()._maxChunkSize); qCInfo(lcPropagateUpload) << "Chunked upload of" << _currentChunkSize << "bytes took" << uploadTime << "ms, desired is" << targetDuration << "ms, expected good chunk size is" << predictedGoodSize << "bytes and nudged next chunk size to " << propagator()->_chunkSize << "bytes"; } bool finished = _sent == _item->_size; // 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; } // Reset the error count on successful chunk upload auto uploadInfo = propagator()->_journal->getUploadInfo(_item->_file); uploadInfo._errorCount = 0; propagator()->_journal->setUploadInfo(_item->_file, uploadInfo); propagator()->_journal->commit("Upload info"); } startNextChunk(); }