2014-02-06 14:50:16 +04: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 .
*/
2015-05-15 11:50:55 +03:00
# include "config.h"
2014-11-11 14:16:14 +03:00
# include "propagateupload.h"
2017-12-18 00:38:43 +03:00
# include "propagateuploadencrypted.h"
2014-11-11 14:16:14 +03:00
# include "owncloudpropagator_p.h"
2014-02-06 14:50:16 +04:00
# include "networkjobs.h"
# include "account.h"
2017-09-01 19:11:43 +03:00
# include "common/syncjournaldb.h"
# include "common/syncjournalfilerecord.h"
2017-08-16 09:36:52 +03:00
# include "common/utility.h"
2014-02-18 15:54:40 +04:00
# include "filesystem.h"
2014-04-29 18:47:07 +04:00
# include "propagatorjobs.h"
2017-09-01 19:11:43 +03:00
# include "common/checksums.h"
2015-11-20 17:14:22 +03:00
# include "syncengine.h"
2016-01-06 12:01:22 +03:00
# include "propagateremotedelete.h"
2017-09-01 19:11:43 +03:00
# include "common/asserts.h"
2017-12-05 00:27:13 +03:00
# include "networkjobs.h"
2017-12-07 22:52:54 +03:00
# include "clientsideencryption.h"
2017-12-12 21:36:47 +03:00
# include "clientsideencryptionjobs.h"
2015-05-12 17:36:40 +03:00
2014-02-06 14:50:16 +04:00
# include <QNetworkAccessManager>
2014-02-17 16:48:56 +04:00
# include <QFileInfo>
2014-05-23 20:55:44 +04:00
# include <QDir>
2017-04-26 13:40:32 +03:00
# include <QJsonDocument>
# include <QJsonObject>
2017-12-05 00:45:10 +03:00
# include <QFileInfo>
2014-02-13 17:02:05 +04:00
# include <cmath>
2015-01-14 14:48:38 +03:00
# include <cstring>
2014-02-06 14:50:16 +04:00
2014-12-02 16:20:13 +03:00
namespace OCC {
2014-02-06 14:50:16 +04:00
2017-12-28 22:33:10 +03:00
Q_LOGGING_CATEGORY ( lcPutJob , " nextcloud.sync.networkjob.put " , QtInfoMsg )
Q_LOGGING_CATEGORY ( lcPollJob , " nextcloud.sync.networkjob.poll " , QtInfoMsg )
Q_LOGGING_CATEGORY ( lcPropagateUpload , " nextcloud.sync.propagator.upload " , QtInfoMsg )
2017-05-09 15:24:11 +03:00
2014-09-17 15:35:54 +04:00
/**
2015-02-26 13:00:06 +03:00
* We do not want to upload files that are currently being modified .
* To avoid that , we don ' t upload files that have a modification time
* that is too close to the current time .
2014-09-17 15:35:54 +04:00
*
2015-02-26 13:00:06 +03:00
* This interacts with the msBetweenRequestAndSync delay in the folder
* manager . If that delay between file - change notification and sync
* has passed , we should accept the file for upload here .
2014-09-17 15:35:54 +04:00
*/
2015-02-26 13:00:06 +03:00
static bool fileIsStillChanging ( const SyncFileItem & item )
{
const QDateTime modtime = Utility : : qDateTimeFromTime_t ( item . _modtime ) ;
2017-09-25 12:49:11 +03:00
const qint64 msSinceMod = modtime . msecsTo ( QDateTime : : currentDateTimeUtc ( ) ) ;
2015-02-26 13:00:06 +03:00
2015-11-20 17:14:22 +03:00
return msSinceMod < SyncEngine : : minimumFileAgeForUpload
2015-02-26 13:00:06 +03:00
// if the mtime is too much in the future we *do* upload the file
& & msSinceMod > - 10000 ;
}
2014-09-17 15:35:54 +04:00
2015-03-23 18:13:52 +03:00
PUTFileJob : : ~ PUTFileJob ( )
{
// Make sure that we destroy the QNetworkReply before our _device of which it keeps an internal pointer.
2018-11-12 20:46:39 +03:00
setReply ( nullptr ) ;
2015-03-23 18:13:52 +03:00
}
2014-02-06 14:50:16 +04:00
void PUTFileJob : : start ( )
{
QNetworkRequest req ;
for ( QMap < QByteArray , QByteArray > : : const_iterator it = _headers . begin ( ) ; it ! = _headers . end ( ) ; + + it ) {
req . setRawHeader ( it . key ( ) , it . value ( ) ) ;
}
2017-05-22 15:41:06 +03:00
req . setPriority ( QNetworkRequest : : LowPriority ) ; // Long uploads must not block non-propagation jobs.
2017-03-03 13:20:53 +03:00
if ( _url . isValid ( ) ) {
sendRequest ( " PUT " , _url , req , _device ) ;
} else {
sendRequest ( " PUT " , makeDavUrl ( path ( ) ) , req , _device ) ;
}
2014-02-06 14:50:16 +04:00
if ( reply ( ) - > error ( ) ! = QNetworkReply : : NoError ) {
2017-03-30 14:46:20 +03:00
qCWarning ( lcPutJob ) < < " Network error: " < < reply ( ) - > errorString ( ) ;
2014-02-06 14:50:16 +04:00
}
2014-03-14 16:03:16 +04:00
2017-09-20 11:14:48 +03:00
connect ( reply ( ) , & QNetworkReply : : uploadProgress , this , & PUTFileJob : : uploadProgress ) ;
connect ( this , & AbstractNetworkJob : : networkActivity , account ( ) . data ( ) , & Account : : propagatorNetworkActivity ) ;
2017-03-24 17:01:50 +03:00
_requestTimer . start ( ) ;
2014-02-06 14:50:16 +04:00
AbstractNetworkJob : : start ( ) ;
}
2014-07-25 15:30:48 +04:00
void PollJob : : start ( )
{
2014-08-28 13:27:08 +04:00
setTimeout ( 120 * 1000 ) ;
2014-07-29 21:51:26 +04:00
QUrl accountUrl = account ( ) - > url ( ) ;
QUrl finalUrl = QUrl : : fromUserInput ( accountUrl . scheme ( ) + QLatin1String ( " :// " ) + accountUrl . authority ( )
2014-11-11 12:10:46 +03:00
+ ( path ( ) . startsWith ( ' / ' ) ? QLatin1String ( " " ) : QLatin1String ( " / " ) ) + path ( ) ) ;
2017-03-03 13:20:53 +03:00
sendRequest ( " GET " , finalUrl ) ;
2017-09-20 11:14:48 +03:00
connect ( reply ( ) , & QNetworkReply : : downloadProgress , this , & AbstractNetworkJob : : resetTimeout ) ;
2014-07-25 15:30:48 +04:00
AbstractNetworkJob : : start ( ) ;
}
2014-07-28 14:12:52 +04:00
bool PollJob : : finished ( )
{
QNetworkReply : : NetworkError err = reply ( ) - > error ( ) ;
if ( err ! = QNetworkReply : : NoError ) {
2015-04-15 16:19:11 +03:00
_item - > _httpErrorCode = reply ( ) - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ;
_item - > _status = classifyError ( err , _item - > _httpErrorCode ) ;
2017-03-23 17:53:22 +03:00
_item - > _errorString = errorString ( ) ;
2015-03-04 14:00:25 +03:00
2015-04-15 16:19:11 +03:00
if ( _item - > _status = = SyncFileItem : : FatalError | | _item - > _httpErrorCode > = 400 ) {
if ( _item - > _status ! = SyncFileItem : : FatalError
& & _item - > _httpErrorCode ! = 503 ) {
2014-07-29 21:51:26 +04:00
SyncJournalDb : : PollInfo info ;
2015-04-15 16:19:11 +03:00
info . _file = _item - > _file ;
2014-07-29 21:51:26 +04:00
// no info._url removes it from the database
_journal - > setPollInfo ( info ) ;
2014-09-11 14:05:35 +04:00
_journal - > commit ( " remove poll info " ) ;
2014-07-29 21:51:26 +04:00
}
2014-07-29 17:51:22 +04:00
emit finishedSignal ( ) ;
return true ;
}
start ( ) ;
2014-07-28 14:12:52 +04:00
return false ;
}
2014-08-29 15:58:33 +04:00
QByteArray jsonData = reply ( ) - > readAll ( ) . trimmed ( ) ;
2017-03-30 14:46:20 +03:00
qCInfo ( lcPollJob ) < < " > " < < jsonData < < " < " < < reply ( ) - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ;
2017-04-26 13:40:32 +03:00
QJsonParseError jsonParseError ;
QJsonObject status = QJsonDocument : : fromJson ( jsonData , & jsonParseError ) . object ( ) ;
if ( jsonParseError . error ! = QJsonParseError : : NoError ) {
2015-04-15 16:19:11 +03:00
_item - > _errorString = tr ( " Invalid JSON reply from the poll URL " ) ;
_item - > _status = SyncFileItem : : NormalError ;
2014-07-28 14:12:52 +04:00
emit finishedSignal ( ) ;
return true ;
}
2017-04-26 13:40:32 +03:00
if ( status [ " unfinished " ] . toBool ( ) ) {
2014-07-29 17:51:22 +04:00
start ( ) ;
return false ;
2014-07-28 14:12:52 +04:00
}
2015-04-15 16:19:11 +03:00
_item - > _errorString = status [ " error " ] . toString ( ) ;
_item - > _status = _item - > _errorString . isEmpty ( ) ? SyncFileItem : : Success : SyncFileItem : : NormalError ;
2017-04-26 13:40:32 +03:00
_item - > _fileId = status [ " fileid " ] . toString ( ) . toUtf8 ( ) ;
_item - > _etag = status [ " etag " ] . toString ( ) . toUtf8 ( ) ;
2015-04-15 16:19:11 +03:00
_item - > _responseTimeStamp = responseTimestamp ( ) ;
2014-07-28 14:12:52 +04:00
SyncJournalDb : : PollInfo info ;
2015-04-15 16:19:11 +03:00
info . _file = _item - > _file ;
2014-07-28 14:12:52 +04:00
// no info._url removes it from the database
_journal - > setPollInfo ( info ) ;
2014-09-11 14:05:35 +04:00
_journal - > commit ( " remove poll info " ) ;
2014-07-28 14:12:52 +04:00
emit finishedSignal ( ) ;
return true ;
}
2020-07-01 14:19:08 +03:00
PropagateUploadFileCommon : : PropagateUploadFileCommon ( OwncloudPropagator * propagator , const SyncFileItemPtr & item )
: PropagateItemJob ( propagator , item )
, _finished ( false )
, _deleteExisting ( false )
2020-07-01 14:20:56 +03:00
, _parallelism ( FullParallelism )
2020-07-01 14:19:08 +03:00
, _uploadEncryptedHelper ( nullptr )
, _uploadingEncrypted ( false )
{
2020-07-01 15:12:18 +03:00
const auto rootPath = [ = ] ( ) {
const auto result = propagator - > _remoteFolder ;
if ( result . startsWith ( ' / ' ) ) {
return result . mid ( 1 ) ;
} else {
return result ;
}
} ( ) ;
const auto path = _item - > _file ;
const auto slashPosition = path . lastIndexOf ( ' / ' ) ;
const auto parentPath = slashPosition > = 0 ? path . left ( slashPosition ) : QString ( ) ;
SyncJournalFileRecord parentRec ;
bool ok = propagator - > _journal - > getFileRecord ( parentPath , & parentRec ) ;
if ( ! ok ) {
done ( SyncFileItem : : NormalError ) ;
return ;
}
const auto remoteParentPath = parentRec . _e2eMangledName . isEmpty ( ) ? parentPath : parentRec . _e2eMangledName ;
const auto absoluteRemoteParentPath = remoteParentPath . isEmpty ( ) ? rootPath : rootPath + remoteParentPath + ' / ' ;
const auto account = propagator - > account ( ) ;
if ( account - > capabilities ( ) . clientSideEncryptionAvailable ( ) & &
account - > e2e ( ) - > isFolderEncrypted ( absoluteRemoteParentPath ) ) {
_parallelism = WaitForFinished ;
}
2020-07-01 14:19:08 +03:00
}
2020-07-01 14:20:56 +03:00
PropagatorJob : : JobParallelism PropagateUploadFileCommon : : parallelism ( )
{
return _parallelism ;
}
2016-05-20 11:26:02 +03:00
void PropagateUploadFileCommon : : setDeleteExisting ( bool enabled )
{
_deleteExisting = enabled ;
}
void PropagateUploadFileCommon : : start ( )
2014-02-10 16:00:22 +04:00
{
2020-06-23 19:13:20 +03:00
const auto rootPath = [ = ] ( ) {
const auto result = propagator ( ) - > _remoteFolder ;
if ( result . startsWith ( ' / ' ) ) {
return result . mid ( 1 ) ;
} else {
return result ;
}
} ( ) ;
2020-06-30 17:04:55 +03:00
const auto path = _item - > _file ;
const auto slashPosition = path . lastIndexOf ( ' / ' ) ;
const auto parentPath = slashPosition > = 0 ? path . left ( slashPosition ) : QString ( ) ;
2020-06-23 19:13:20 +03:00
SyncJournalFileRecord parentRec ;
bool ok = propagator ( ) - > _journal - > getFileRecord ( parentPath , & parentRec ) ;
if ( ! ok ) {
done ( SyncFileItem : : NormalError ) ;
return ;
2017-12-04 23:33:43 +03:00
}
2020-06-23 19:13:20 +03:00
const auto remoteParentPath = parentRec . _e2eMangledName . isEmpty ( ) ? parentPath : parentRec . _e2eMangledName ;
2020-06-30 17:04:55 +03:00
const auto absoluteRemoteParentPath = remoteParentPath . isEmpty ( ) ? rootPath : rootPath + remoteParentPath + ' / ' ;
2020-06-23 19:13:20 +03:00
const auto account = propagator ( ) - > account ( ) ;
if ( ! account - > capabilities ( ) . clientSideEncryptionAvailable ( ) | |
2020-06-30 17:04:55 +03:00
! account - > e2e ( ) - > isFolderEncrypted ( absoluteRemoteParentPath ) ) {
2020-06-23 19:13:20 +03:00
setupUnencryptedFile ( ) ;
return ;
}
2020-06-30 12:21:02 +03:00
_uploadEncryptedHelper = new PropagateUploadEncrypted ( propagator ( ) , remoteParentPath , _item , this ) ;
2020-06-23 19:13:20 +03:00
connect ( _uploadEncryptedHelper , & PropagateUploadEncrypted : : folderNotEncrypted ,
this , & PropagateUploadFileCommon : : setupUnencryptedFile ) ;
connect ( _uploadEncryptedHelper , & PropagateUploadEncrypted : : finalized ,
this , & PropagateUploadFileCommon : : setupEncryptedFile ) ;
connect ( _uploadEncryptedHelper , & PropagateUploadEncrypted : : error ,
[ ] { qCDebug ( lcPropagateUpload ) < < " Error setting up encryption. " ; } ) ;
_uploadEncryptedHelper - > start ( ) ;
2017-12-04 23:33:43 +03:00
}
2017-12-05 00:27:13 +03:00
2017-12-21 01:36:49 +03:00
void PropagateUploadFileCommon : : setupEncryptedFile ( const QString & path , const QString & filename , quint64 size )
2017-12-05 00:27:13 +03:00
{
2017-12-29 19:23:28 +03:00
qCDebug ( lcPropagateUpload ) < < " Starting to upload encrypted file " < < path < < filename < < size ;
2017-12-21 02:35:23 +03:00
_uploadingEncrypted = true ;
2017-12-29 19:23:28 +03:00
_fileToUpload . _path = path ;
2017-12-21 01:36:49 +03:00
_fileToUpload . _file = filename ;
_fileToUpload . _size = size ;
2017-12-21 02:35:23 +03:00
startUploadFile ( ) ;
2017-12-18 00:38:43 +03:00
}
2017-12-07 21:39:30 +03:00
2017-12-18 00:38:43 +03:00
void PropagateUploadFileCommon : : setupUnencryptedFile ( )
{
2017-12-21 02:35:23 +03:00
_uploadingEncrypted = false ;
2017-12-05 00:45:10 +03:00
_fileToUpload . _file = _item - > _file ;
_fileToUpload . _size = _item - > _size ;
_fileToUpload . _path = propagator ( ) - > getFilePath ( _fileToUpload . _file ) ;
startUploadFile ( ) ;
2017-12-04 23:33:43 +03:00
}
2017-11-28 23:28:06 +03:00
2017-12-05 00:27:13 +03:00
void PropagateUploadFileCommon : : startUploadFile ( ) {
2017-01-17 16:29:12 +03:00
if ( propagator ( ) - > _abortRequested . fetchAndAddRelaxed ( 0 ) ) {
2014-02-13 17:02:05 +04:00
return ;
2015-02-27 17:27:49 +03:00
}
2014-02-10 16:00:22 +04:00
2017-03-07 13:47:38 +03:00
// Check if the specific file can be accessed
2017-11-28 23:28:06 +03:00
if ( propagator ( ) - > hasCaseClashAccessibilityProblem ( _fileToUpload . _file ) ) {
2017-03-07 13:47:38 +03:00
done ( SyncFileItem : : NormalError , tr ( " File %1 cannot be uploaded because another file with the same name, differing only in case, exists " ) . arg ( QDir : : toNativeSeparators ( _item - > _file ) ) ) ;
return ;
}
2017-07-12 10:58:15 +03:00
// Check if we believe that the upload will fail due to remote quota limits
const quint64 quotaGuess = propagator ( ) - > _folderQuota . value (
2017-11-28 23:28:06 +03:00
QFileInfo ( _fileToUpload . _file ) . path ( ) , std : : numeric_limits < quint64 > : : max ( ) ) ;
if ( _fileToUpload . _size > quotaGuess ) {
2017-07-12 10:58:15 +03:00
// Necessary for blacklisting logic
_item - > _httpErrorCode = 507 ;
emit propagator ( ) - > insufficientRemoteStorage ( ) ;
2017-11-28 23:28:06 +03:00
done ( SyncFileItem : : DetailError , tr ( " Upload of %1 exceeds the quota for the folder " ) . arg ( Utility : : octetsToString ( _fileToUpload . _size ) ) ) ;
2017-07-12 10:58:15 +03:00
return ;
}
2017-01-17 16:29:12 +03:00
propagator ( ) - > _activeJobList . append ( this ) ;
2016-01-06 12:01:22 +03:00
if ( ! _deleteExisting ) {
2017-12-24 19:08:04 +03:00
qDebug ( ) < < " Running the compute checksum " ;
2016-01-06 12:01:22 +03:00
return slotComputeContentChecksum ( ) ;
}
2017-12-24 19:08:04 +03:00
qDebug ( ) < < " Deleting the current " ;
2017-01-17 16:29:12 +03:00
auto job = new DeleteJob ( propagator ( ) - > account ( ) ,
2017-11-28 23:28:06 +03:00
propagator ( ) - > _remoteFolder + _fileToUpload . _file ,
2016-01-06 12:01:22 +03:00
this ) ;
_jobs . append ( job ) ;
2017-09-20 11:14:48 +03:00
connect ( job , & DeleteJob : : finishedSignal , this , & PropagateUploadFileCommon : : slotComputeContentChecksum ) ;
connect ( job , & QObject : : destroyed , this , & PropagateUploadFileCommon : : slotJobDestroyed ) ;
2016-01-06 12:01:22 +03:00
job - > start ( ) ;
}
2016-05-20 11:26:02 +03:00
void PropagateUploadFileCommon : : slotComputeContentChecksum ( )
2016-01-06 12:01:22 +03:00
{
2020-09-28 14:00:30 +03:00
qDebug ( ) < < " Trying to compute the checksum of the file " ;
2017-12-24 19:08:04 +03:00
qDebug ( ) < < " Still trying to understand if this is the local file or the uploaded one " ;
2017-01-17 16:29:12 +03:00
if ( propagator ( ) - > _abortRequested . fetchAndAddRelaxed ( 0 ) ) {
2016-01-06 12:01:22 +03:00
return ;
}
2017-12-24 19:08:04 +03:00
const QString filePath = propagator ( ) - > getFilePath ( _item - > _file ) ;
2015-05-15 16:39:26 +03:00
// remember the modtime before checksumming to be able to detect a file
2017-11-28 23:28:06 +03:00
// change during the checksum calculation - This goes inside of the _item->_file
// and not the _fileToUpload because we are checking the original file, not there
// probably temporary one.
2015-05-26 13:33:19 +03:00
_item - > _modtime = FileSystem : : getModTime ( filePath ) ;
2015-05-12 17:36:40 +03:00
2016-03-02 15:52:14 +03:00
QByteArray checksumType = contentChecksumType ( ) ;
2015-11-23 15:44:49 +03:00
// Maybe the discovery already computed the checksum?
2017-11-28 23:28:06 +03:00
// Should I compute the checksum of the original (_item->_file)
// or the maybe-modified? (_fileToUpload._file) ?
2017-06-14 13:14:46 +03:00
QByteArray existingChecksumType , existingChecksum ;
parseChecksumHeader ( _item - > _checksumHeader , & existingChecksumType , & existingChecksum ) ;
if ( existingChecksumType = = checksumType ) {
slotComputeTransmissionChecksum ( checksumType , existingChecksum ) ;
2015-11-23 15:44:49 +03:00
return ;
2015-11-23 13:53:06 +03:00
}
2015-11-23 15:44:49 +03:00
// Compute the content checksum.
auto computeChecksum = new ComputeChecksum ( this ) ;
2016-03-02 15:52:14 +03:00
computeChecksum - > setChecksumType ( checksumType ) ;
2015-11-23 15:44:49 +03:00
2017-09-20 11:14:48 +03:00
connect ( computeChecksum , & ComputeChecksum : : done ,
this , & PropagateUploadFileCommon : : slotComputeTransmissionChecksum ) ;
connect ( computeChecksum , & ComputeChecksum : : done ,
computeChecksum , & QObject : : deleteLater ) ;
2015-11-23 13:53:06 +03:00
computeChecksum - > start ( filePath ) ;
}
2016-05-20 11:26:02 +03:00
void PropagateUploadFileCommon : : slotComputeTransmissionChecksum ( const QByteArray & contentChecksumType , const QByteArray & contentChecksum )
2015-11-23 13:53:06 +03:00
{
2017-06-14 13:14:46 +03:00
_item - > _checksumHeader = makeChecksumHeader ( contentChecksumType , contentChecksum ) ;
2015-11-23 13:53:06 +03:00
// Reuse the content checksum as the transmission checksum if possible
const auto supportedTransmissionChecksums =
2017-01-17 16:29:12 +03:00
propagator ( ) - > account ( ) - > capabilities ( ) . supportedChecksumTypes ( ) ;
2015-11-23 13:53:06 +03:00
if ( supportedTransmissionChecksums . contains ( contentChecksumType ) ) {
slotStartUpload ( contentChecksumType , contentChecksum ) ;
return ;
2015-10-01 16:00:33 +03:00
}
2015-11-23 13:53:06 +03:00
// Compute the transmission checksum.
2015-10-14 16:03:40 +03:00
auto computeChecksum = new ComputeChecksum ( this ) ;
2015-10-15 10:54:01 +03:00
if ( uploadChecksumEnabled ( ) ) {
2017-01-17 16:29:12 +03:00
computeChecksum - > setChecksumType ( propagator ( ) - > account ( ) - > capabilities ( ) . uploadChecksumType ( ) ) ;
2015-10-15 10:54:01 +03:00
} else {
2015-10-15 10:39:49 +03:00
computeChecksum - > setChecksumType ( QByteArray ( ) ) ;
}
2015-10-01 16:00:33 +03:00
2017-09-20 11:14:48 +03:00
connect ( computeChecksum , & ComputeChecksum : : done ,
this , & PropagateUploadFileCommon : : slotStartUpload ) ;
connect ( computeChecksum , & ComputeChecksum : : done ,
computeChecksum , & QObject : : deleteLater ) ;
2017-01-17 16:29:12 +03:00
const QString filePath = propagator ( ) - > getFilePath ( _item - > _file ) ;
2015-10-14 16:03:40 +03:00
computeChecksum - > start ( filePath ) ;
2015-05-12 17:36:40 +03:00
}
2016-05-20 11:26:02 +03:00
void PropagateUploadFileCommon : : slotStartUpload ( const QByteArray & transmissionChecksumType , const QByteArray & transmissionChecksum )
2015-05-12 17:36:40 +03:00
{
2016-05-20 16:06:07 +03:00
// Remove ourselfs from the list of active job, before any posible call to done()
// When we start chunks, we will add it again, once for every chunks.
2017-01-17 16:29:12 +03:00
propagator ( ) - > _activeJobList . removeOne ( this ) ;
2016-05-20 16:06:07 +03:00
2017-06-14 13:14:46 +03:00
_transmissionChecksumHeader = makeChecksumHeader ( transmissionChecksumType , transmissionChecksum ) ;
2015-10-14 16:45:44 +03:00
2017-06-14 13:14:46 +03:00
// If no checksum header was not set, reuse the transmission checksum as the content checksum.
if ( _item - > _checksumHeader . isEmpty ( ) ) {
_item - > _checksumHeader = _transmissionChecksumHeader ;
2016-02-25 19:17:14 +03:00
}
2017-12-24 18:30:39 +03:00
const QString fullFilePath = _fileToUpload . _path ;
const QString originalFilePath = propagator ( ) - > getFilePath ( _item - > _file ) ;
2015-02-27 17:27:49 +03:00
if ( ! FileSystem : : fileExists ( fullFilePath ) ) {
2018-03-25 22:54:08 +03:00
if ( _uploadingEncrypted ) {
_uploadEncryptedHelper - > unlockFolder ( ) ;
}
done ( SyncFileItem : : SoftError , tr ( " File Removed (start upload) %1 " ) . arg ( fullFilePath ) ) ;
2014-02-13 17:02:05 +04:00
return ;
2014-02-10 16:00:22 +04:00
}
2016-05-18 17:42:55 +03:00
time_t prevModtime = _item - > _modtime ; // the _item value was set in PropagateUploadFile::start()
2015-05-12 17:36:40 +03:00
// but a potential checksum calculation could have taken some time during which the file could
// have been changed again, so better check again here.
2014-02-10 16:00:22 +04:00
2017-12-24 18:30:39 +03:00
_item - > _modtime = FileSystem : : getModTime ( originalFilePath ) ;
2015-05-26 13:33:19 +03:00
if ( prevModtime ! = _item - > _modtime ) {
2017-01-17 16:29:12 +03:00
propagator ( ) - > _anotherSyncNeeded = true ;
2018-03-25 22:54:08 +03:00
if ( _uploadingEncrypted ) {
_uploadEncryptedHelper - > unlockFolder ( ) ;
}
2017-12-24 19:08:04 +03:00
qDebug ( ) < < " prevModtime " < < prevModtime < < " Curr " < < _item - > _modtime ;
2015-05-20 23:44:12 +03:00
done ( SyncFileItem : : SoftError , tr ( " Local file changed during syncing. It will be resumed. " ) ) ;
2019-09-17 19:39:51 +03:00
return ;
2015-05-12 17:36:40 +03:00
}
2015-02-27 17:27:49 +03:00
quint64 fileSize = FileSystem : : getSize ( fullFilePath ) ;
2017-11-28 23:28:06 +03:00
_fileToUpload . _size = fileSize ;
2014-09-17 15:35:54 +04:00
// But skip the file if the mtime is too close to 'now'!
// That usually indicates a file that is still being changed
// or not yet fully copied to the destination.
2015-04-15 16:19:11 +03:00
if ( fileIsStillChanging ( * _item ) ) {
2017-01-17 16:29:12 +03:00
propagator ( ) - > _anotherSyncNeeded = true ;
2018-03-25 22:54:08 +03:00
if ( _uploadingEncrypted ) {
_uploadEncryptedHelper - > unlockFolder ( ) ;
}
2014-09-17 15:35:54 +04:00
done ( SyncFileItem : : SoftError , tr ( " Local file changed during sync. " ) ) ;
return ;
}
2016-05-20 11:26:02 +03:00
doStartUpload ( ) ;
2014-02-13 17:02:05 +04:00
}
2014-02-10 16:00:22 +04:00
2015-01-14 17:14:17 +03:00
UploadDevice : : UploadDevice ( BandwidthManager * bwm )
: _read ( 0 )
2014-09-29 12:30:39 +04:00
, _bandwidthManager ( bwm )
, _bandwidthQuota ( 0 )
, _readWithProgress ( 0 )
, _bandwidthLimited ( false )
, _choked ( false )
{
_bandwidthManager - > registerUploadDevice ( this ) ;
}
2014-02-10 16:00:22 +04:00
2014-09-29 12:30:39 +04:00
UploadDevice : : ~ UploadDevice ( )
{
2015-01-14 16:27:24 +03:00
if ( _bandwidthManager ) {
_bandwidthManager - > unregisterUploadDevice ( this ) ;
}
2014-09-29 12:30:39 +04:00
}
2014-02-13 17:02:05 +04:00
2015-01-14 17:14:17 +03:00
bool UploadDevice : : prepareAndOpen ( const QString & fileName , qint64 start , qint64 size )
2015-01-14 14:48:38 +03:00
{
_data . clear ( ) ;
2015-01-14 17:14:17 +03:00
_read = 0 ;
QFile file ( fileName ) ;
QString openError ;
2015-03-12 15:18:08 +03:00
if ( ! FileSystem : : openAndSeekFileSharedRead ( & file , & openError , start ) ) {
2015-01-14 17:14:17 +03:00
setErrorString ( openError ) ;
2015-01-14 14:48:38 +03:00
return false ;
}
2015-01-14 17:14:17 +03:00
2015-03-12 15:18:08 +03:00
size = qBound ( 0ll , size , FileSystem : : getSize ( fileName ) - start ) ;
2015-01-14 17:14:17 +03:00
_data . resize ( size ) ;
auto read = file . read ( _data . data ( ) , size ) ;
if ( read ! = size ) {
setErrorString ( file . errorString ( ) ) ;
return false ;
}
return QIODevice : : open ( QIODevice : : ReadOnly ) ;
2015-01-14 14:48:38 +03:00
}
2014-09-29 12:30:39 +04:00
qint64 UploadDevice : : writeData ( const char * , qint64 )
{
2017-02-07 15:52:15 +03:00
ASSERT ( false , " write to read only device " ) ;
2014-09-29 12:30:39 +04:00
return 0 ;
}
2014-09-15 19:55:55 +04:00
2014-09-29 12:30:39 +04:00
qint64 UploadDevice : : readData ( char * data , qint64 maxlen )
{
2015-01-14 17:14:17 +03:00
if ( _data . size ( ) - _read < = 0 ) {
2014-09-29 12:30:39 +04:00
// at end
2015-02-06 13:19:41 +03:00
if ( _bandwidthManager ) {
_bandwidthManager - > unregisterUploadDevice ( this ) ;
}
2014-09-29 12:30:39 +04:00
return - 1 ;
}
2015-01-14 17:14:17 +03:00
maxlen = qMin ( maxlen , _data . size ( ) - _read ) ;
2014-09-29 12:30:39 +04:00
if ( maxlen = = 0 ) {
2014-02-13 17:02:05 +04:00
return 0 ;
2014-02-10 16:00:22 +04:00
}
2014-09-29 12:30:39 +04:00
if ( isChoked ( ) ) {
return 0 ;
2014-02-10 16:00:22 +04:00
}
2014-09-29 12:30:39 +04:00
if ( isBandwidthLimited ( ) ) {
maxlen = qMin ( maxlen , _bandwidthQuota ) ;
if ( maxlen < = 0 ) { // no quota
return 0 ;
2014-07-14 21:53:42 +04:00
}
2014-09-29 12:30:39 +04:00
_bandwidthQuota - = maxlen ;
2014-02-13 17:02:05 +04:00
}
2015-01-14 14:48:38 +03:00
std : : memcpy ( data , _data . data ( ) + _read , maxlen ) ;
_read + = maxlen ;
return maxlen ;
2014-09-29 12:30:39 +04:00
}
void UploadDevice : : slotJobUploadProgress ( qint64 sent , qint64 t )
2014-11-11 14:16:14 +03:00
{
2014-09-29 12:30:39 +04:00
if ( sent = = 0 | | t = = 0 ) {
return ;
2014-04-04 17:41:35 +04:00
}
2014-09-29 12:30:39 +04:00
_readWithProgress = sent ;
}
2014-04-04 17:41:35 +04:00
2014-09-29 12:30:39 +04:00
bool UploadDevice : : atEnd ( ) const
{
2015-01-14 17:14:17 +03:00
return _read > = _data . size ( ) ;
2014-09-29 12:30:39 +04:00
}
2014-04-05 16:25:41 +04:00
2014-09-29 12:30:39 +04:00
qint64 UploadDevice : : size ( ) const
{
2015-01-14 17:14:17 +03:00
return _data . size ( ) ;
2014-09-29 12:30:39 +04:00
}
qint64 UploadDevice : : bytesAvailable ( ) const
{
2015-01-14 17:14:17 +03:00
return _data . size ( ) - _read + QIODevice : : bytesAvailable ( ) ;
2014-09-29 12:30:39 +04:00
}
2014-04-05 16:25:41 +04:00
2014-09-29 12:30:39 +04:00
// random access, we can seek
bool UploadDevice : : isSequential ( ) const
{
return false ;
}
bool UploadDevice : : seek ( qint64 pos )
{
2015-01-14 17:14:17 +03:00
if ( ! QIODevice : : seek ( pos ) ) {
return false ;
}
if ( pos < 0 | | pos > _data . size ( ) ) {
return false ;
}
2014-09-29 12:30:39 +04:00
_read = pos ;
2015-01-14 17:07:41 +03:00
return true ;
2014-09-29 12:30:39 +04:00
}
2014-04-04 17:41:35 +04:00
2014-09-29 12:30:39 +04:00
void UploadDevice : : giveBandwidthQuota ( qint64 bwq )
{
if ( ! atEnd ( ) ) {
_bandwidthQuota = bwq ;
QMetaObject : : invokeMethod ( this , " readyRead " , Qt : : QueuedConnection ) ; // tell QNAM that we have quota
2014-04-04 17:41:35 +04:00
}
2014-09-29 12:30:39 +04:00
}
void UploadDevice : : setBandwidthLimited ( bool b )
{
_bandwidthLimited = b ;
QMetaObject : : invokeMethod ( this , " readyRead " , Qt : : QueuedConnection ) ;
}
void UploadDevice : : setChoked ( bool b )
{
_choked = b ;
if ( ! _choked ) {
QMetaObject : : invokeMethod ( this , " readyRead " , Qt : : QueuedConnection ) ;
}
}
2014-02-06 14:50:16 +04:00
2016-05-20 11:26:02 +03:00
void PropagateUploadFileCommon : : startPollJob ( const QString & path )
2014-07-25 15:30:48 +04:00
{
2020-05-18 21:54:23 +03:00
auto * job = new PollJob ( propagator ( ) - > account ( ) , path , _item ,
2017-01-17 16:29:12 +03:00
propagator ( ) - > _journal , propagator ( ) - > _localDir , this ) ;
2017-09-20 11:14:48 +03:00
connect ( job , & PollJob : : finishedSignal , this , & PropagateUploadFileCommon : : slotPollFinished ) ;
2014-07-28 14:12:52 +04:00
SyncJournalDb : : PollInfo info ;
2015-04-15 16:19:11 +03:00
info . _file = _item - > _file ;
2014-07-28 14:12:52 +04:00
info . _url = path ;
2015-04-15 16:19:11 +03:00
info . _modtime = _item - > _modtime ;
2017-01-17 16:29:12 +03:00
propagator ( ) - > _journal - > setPollInfo ( info ) ;
propagator ( ) - > _journal - > commit ( " add poll info " ) ;
propagator ( ) - > _activeJobList . append ( this ) ;
2014-07-29 21:51:26 +04:00
job - > start ( ) ;
2014-07-25 15:30:48 +04:00
}
2016-05-20 11:26:02 +03:00
void PropagateUploadFileCommon : : slotPollFinished ( )
2014-07-25 15:30:48 +04:00
{
2020-05-18 21:54:23 +03:00
auto * job = qobject_cast < PollJob * > ( sender ( ) ) ;
2017-02-07 15:52:15 +03:00
ASSERT ( job ) ;
2014-07-25 15:30:48 +04:00
2017-01-17 16:29:12 +03:00
propagator ( ) - > _activeJobList . removeOne ( this ) ;
2014-11-24 15:33:13 +03:00
2015-04-15 16:19:11 +03:00
if ( job - > _item - > _status ! = SyncFileItem : : Success ) {
done ( job - > _item - > _status , job - > _item - > _errorString ) ;
2014-07-25 15:30:48 +04:00
return ;
}
2016-05-20 11:26:02 +03:00
finalize ( ) ;
2014-07-25 15:30:48 +04:00
}
2014-03-14 16:03:16 +04:00
2018-03-27 11:39:58 +03:00
void PropagateUploadFileCommon : : done ( SyncFileItem : : Status status , const QString & errorString )
{
_finished = true ;
PropagateItemJob : : done ( status , errorString ) ;
}
2017-01-13 16:44:45 +03:00
void PropagateUploadFileCommon : : checkResettingErrors ( )
{
if ( _item - > _httpErrorCode = = 412
2017-01-17 16:29:12 +03:00
| | propagator ( ) - > account ( ) - > capabilities ( ) . httpErrorCodesThatResetFailingChunkedUploads ( ) . contains ( _item - > _httpErrorCode ) ) {
auto uploadInfo = propagator ( ) - > _journal - > getUploadInfo ( _item - > _file ) ;
2017-01-13 16:44:45 +03:00
uploadInfo . _errorCount + = 1 ;
if ( uploadInfo . _errorCount > 3 ) {
2017-03-30 14:46:20 +03:00
qCInfo ( lcPropagateUpload ) < < " Reset transfer of " < < _item - > _file
2017-01-13 16:44:45 +03:00
< < " due to repeated error " < < _item - > _httpErrorCode ;
uploadInfo = SyncJournalDb : : UploadInfo ( ) ;
} else {
2017-03-30 14:46:20 +03:00
qCInfo ( lcPropagateUpload ) < < " Error count for maybe-reset error " < < _item - > _httpErrorCode
2017-01-13 16:44:45 +03:00
< < " on file " < < _item - > _file
< < " is " < < uploadInfo . _errorCount ;
}
2017-01-17 16:29:12 +03:00
propagator ( ) - > _journal - > setUploadInfo ( _item - > _file , uploadInfo ) ;
propagator ( ) - > _journal - > commit ( " Upload info " ) ;
2017-01-13 16:44:45 +03:00
}
}
2017-07-07 15:12:10 +03:00
void PropagateUploadFileCommon : : commonErrorHandling ( AbstractNetworkJob * job )
{
QByteArray replyContent ;
QString errorString = job - > errorStringParsingBody ( & replyContent ) ;
qCDebug ( lcPropagateUpload ) < < replyContent ; // display the XML error in the debug
if ( _item - > _httpErrorCode = = 412 ) {
// Precondition Failed: Either an etag or a checksum mismatch.
// 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 ;
}
// Ensure errors that should eventually reset the chunked upload are tracked.
checkResettingErrors ( ) ;
SyncFileItem : : Status status = classifyError ( job - > reply ( ) - > error ( ) , _item - > _httpErrorCode ,
& propagator ( ) - > _anotherSyncNeeded ) ;
2017-07-07 16:11:00 +03:00
2017-07-12 10:58:15 +03:00
// Insufficient remote storage.
2017-07-07 16:11:00 +03:00
if ( _item - > _httpErrorCode = = 507 ) {
2017-07-12 10:58:15 +03:00
// Update the quota expectation
2017-11-28 23:28:06 +03:00
/* store the quota for the real local file using the information
* on the file to upload , that could have been modified by
* filters or something . */
2017-07-12 10:58:15 +03:00
const auto path = QFileInfo ( _item - > _file ) . path ( ) ;
auto quotaIt = propagator ( ) - > _folderQuota . find ( path ) ;
if ( quotaIt ! = propagator ( ) - > _folderQuota . end ( ) ) {
2017-11-28 23:28:06 +03:00
quotaIt . value ( ) = qMin ( quotaIt . value ( ) , _fileToUpload . _size - 1 ) ;
2017-07-12 10:58:15 +03:00
} else {
2017-11-28 23:28:06 +03:00
propagator ( ) - > _folderQuota [ path ] = _fileToUpload . _size - 1 ;
2017-07-12 10:58:15 +03:00
}
// Set up the error
2017-07-12 13:38:53 +03:00
status = SyncFileItem : : DetailError ;
2017-11-28 23:28:06 +03:00
errorString = tr ( " Upload of %1 exceeds the quota for the folder " ) . arg ( Utility : : octetsToString ( _fileToUpload . _size ) ) ;
2017-07-07 16:11:00 +03:00
emit propagator ( ) - > insufficientRemoteStorage ( ) ;
}
2017-07-07 15:12:10 +03:00
abortWithError ( status , errorString ) ;
}
2018-05-16 16:08:33 +03:00
void PropagateUploadFileCommon : : adjustLastJobTimeout ( AbstractNetworkJob * job , quint64 fileSize )
{
2020-08-18 22:46:30 +03:00
constexpr double threeMinutes = 3.0 * 60 * 1000 ;
2018-05-16 16:08:33 +03:00
job - > setTimeout ( qBound (
job - > timeoutMsec ( ) ,
// Calculate 3 minutes for each gigabyte of data
2020-08-18 22:46:30 +03:00
qRound64 ( threeMinutes * fileSize / 1e9 ) ,
2018-05-16 16:08:33 +03:00
// Maximum of 30 minutes
2020-08-18 22:46:30 +03:00
static_cast < qint64 > ( 30 * 60 * 1000 ) ) ) ;
2018-05-16 16:08:33 +03:00
}
2016-05-20 11:26:02 +03:00
void PropagateUploadFileCommon : : slotJobDestroyed ( QObject * job )
2014-09-15 19:55:55 +04:00
{
_jobs . erase ( std : : remove ( _jobs . begin ( ) , _jobs . end ( ) , job ) , _jobs . end ( ) ) ;
}
2014-03-14 16:03:16 +04:00
2015-01-14 14:48:38 +03:00
// This function is used whenever there is an error occuring and jobs might be in progress
2016-05-20 11:26:02 +03:00
void PropagateUploadFileCommon : : abortWithError ( SyncFileItem : : Status status , const QString & error )
2015-01-14 14:48:38 +03:00
{
2017-08-11 01:03:03 +03:00
abort ( AbortType : : Synchronous ) ;
2015-01-14 14:48:38 +03:00
done ( status , error ) ;
}
2016-05-20 11:26:02 +03:00
QMap < QByteArray , QByteArray > PropagateUploadFileCommon : : headers ( )
{
QMap < QByteArray , QByteArray > headers ;
headers [ " OC-Async " ] = " 1 " ;
headers [ " Content-Type " ] = " application/octet-stream " ;
headers [ " X-OC-Mtime " ] = QByteArray : : number ( qint64 ( _item - > _modtime ) ) ;
if ( _item - > _file . contains ( " .sys.admin#recall# " ) ) {
// This is a file recall triggered by the admin. Note: the
// recall list file created by the admin and downloaded by the
// client (.sys.admin#recall#) also falls into this category
// (albeit users are not supposed to mess up with it)
// We use a special tag header so that the server may decide to store this file away in some admin stage area
// And not directly in the user's area (which would trigger redownloads etc).
headers [ " OC-Tag " ] = " .sys.admin#recall# " ;
}
if ( ! _item - > _etag . isEmpty ( ) & & _item - > _etag ! = " empty_etag "
& & _item - > _instruction ! = CSYNC_INSTRUCTION_NEW // On new files never send a If-Match
& & _item - > _instruction ! = CSYNC_INSTRUCTION_TYPE_CHANGE
& & ! _deleteExisting ) {
// We add quotes because the owncloud server always adds quotes around the etag, and
// csync_owncloud.c's owncloud_file_id always strips the quotes.
headers [ " If-Match " ] = ' " ' + _item - > _etag + ' " ' ;
}
2017-12-02 13:40:43 +03:00
// Set up a conflict file header pointing to the original file
auto conflictRecord = propagator ( ) - > _journal - > conflictRecord ( _item - > _file . toUtf8 ( ) ) ;
if ( conflictRecord . isValid ( ) ) {
headers [ " OC-Conflict " ] = " 1 " ;
if ( ! conflictRecord . baseFileId . isEmpty ( ) )
headers [ " OC-ConflictBaseFileId " ] = conflictRecord . baseFileId ;
if ( conflictRecord . baseModtime ! = - 1 )
headers [ " OC-ConflictBaseMtime " ] = QByteArray : : number ( conflictRecord . baseModtime ) ;
if ( ! conflictRecord . baseEtag . isEmpty ( ) )
headers [ " OC-ConflictBaseEtag " ] = conflictRecord . baseEtag ;
}
2020-07-08 16:16:53 +03:00
if ( _uploadEncryptedHelper & & ! _uploadEncryptedHelper - > _folderToken . isEmpty ( ) ) {
headers . insert ( " e2e-token " , _uploadEncryptedHelper - > _folderToken ) ;
}
2016-05-20 11:26:02 +03:00
return headers ;
}
void PropagateUploadFileCommon : : finalize ( )
{
2017-07-12 10:58:15 +03:00
// Update the quota, if known
auto quotaIt = propagator ( ) - > _folderQuota . find ( QFileInfo ( _item - > _file ) . path ( ) ) ;
if ( quotaIt ! = propagator ( ) - > _folderQuota . end ( ) )
2017-11-28 23:28:06 +03:00
quotaIt . value ( ) - = _fileToUpload . _size ;
2017-07-12 10:58:15 +03:00
2017-11-28 23:28:06 +03:00
// Update the database entry - use the local file, not the temporary one.
2018-01-29 00:26:07 +03:00
const auto filePath = propagator ( ) - > getFilePath ( _item - > _file ) ;
const auto fileRecord = _item - > toSyncJournalFileRecordWithInode ( filePath ) ;
if ( ! propagator ( ) - > _journal - > setFileRecord ( fileRecord ) ) {
2016-05-20 11:26:02 +03:00
done ( SyncFileItem : : FatalError , tr ( " Error writing metadata to the database " ) ) ;
return ;
}
2017-07-12 10:58:15 +03:00
2016-05-20 11:26:02 +03:00
// Remove from the progress database:
2017-01-17 16:29:12 +03:00
propagator ( ) - > _journal - > setUploadInfo ( _item - > _file , SyncJournalDb : : UploadInfo ( ) ) ;
propagator ( ) - > _journal - > commit ( " upload file start " ) ;
2016-05-20 11:26:02 +03:00
2017-12-21 02:35:23 +03:00
if ( _uploadingEncrypted ) {
2018-03-25 22:54:08 +03:00
_uploadEncryptedHelper - > unlockFolder ( ) ;
2017-12-21 02:35:23 +03:00
}
2018-03-25 22:54:08 +03:00
done ( SyncFileItem : : Success ) ;
2016-05-20 11:26:02 +03:00
}
2017-08-11 01:03:03 +03:00
2018-05-14 14:07:39 +03:00
void PropagateUploadFileCommon : : abortNetworkJobs (
PropagatorJob : : AbortType abortType ,
const std : : function < bool ( AbstractNetworkJob * ) > & mayAbortJob )
{
// Count the number of jobs that need aborting, and emit the overall
// abort signal when they're all done.
QSharedPointer < int > runningCount ( new int ( 0 ) ) ;
auto oneAbortFinished = [ this , runningCount ] ( ) {
( * runningCount ) - - ;
if ( * runningCount = = 0 ) {
emit this - > abortFinished ( ) ;
2017-08-11 01:03:03 +03:00
}
2018-05-14 14:07:39 +03:00
} ;
// Abort all running jobs, except for explicitly excluded ones
foreach ( AbstractNetworkJob * job , _jobs ) {
auto reply = job - > reply ( ) ;
if ( ! reply | | ! reply - > isRunning ( ) )
continue ;
( * runningCount ) + + ;
// If a job should not be aborted that means we'll never abort before
// the hard abort timeout signal comes as runningCount will never go to
// zero.
// We may however finish before that if the un-abortable job completes
// normally.
if ( ! mayAbortJob ( job ) )
continue ;
// Abort the job
if ( abortType = = AbortType : : Asynchronous ) {
// Connect to finished signal of job reply to asynchonously finish the abort
connect ( reply , & QNetworkReply : : finished , this , oneAbortFinished ) ;
}
reply - > abort ( ) ;
2017-08-11 01:03:03 +03:00
}
2018-05-14 14:07:39 +03:00
if ( * runningCount = = 0 & & abortType = = AbortType : : Asynchronous )
2017-08-11 01:03:03 +03:00
emit abortFinished ( ) ;
}
2014-12-10 15:01:36 +03:00
}