diff --git a/src/core/networkreply.cpp b/src/core/networkreply.cpp index 82041f39d9..649615be3f 100644 --- a/src/core/networkreply.cpp +++ b/src/core/networkreply.cpp @@ -39,12 +39,9 @@ void NetworkReply::abort() } -QNetworkReply *NetworkReply::reply() const +QNetworkReply *NetworkReply::currentRawReply() const { - if ( mIsFinished ) - return mReply; - - return nullptr; + return mReply; } @@ -90,6 +87,7 @@ void NetworkReply::initiateRequest() case QNetworkAccessManager::UnknownOperation: throw QStringLiteral( "Not implemented!" ); } + emit currentRawReplyChanged(); mReply->ignoreSslErrors( mExpectedSslErrors ); diff --git a/src/core/networkreply.h b/src/core/networkreply.h index 40e834f8b9..f852bdfd89 100644 --- a/src/core/networkreply.h +++ b/src/core/networkreply.h @@ -58,10 +58,10 @@ class NetworkReply : public QObject /** - * Get the QNetworkReply object once the CloudReply is finilized. Do not delete it manually. - * @return network reply + * Get the current `QNetworkReply` object. Note that it might get deleted even if the parent `NetworkReply` is not in case of redirect or internal retry. Do not delete it manually. + * @return network currentRawReply */ - QNetworkReply *reply() const; + QNetworkReply *currentRawReply() const; /** @@ -145,6 +145,11 @@ class NetworkReply : public QObject */ void temporaryErrorOccurred( QNetworkReply::NetworkError code ); + /** + * Emitted when reply has changed. + */ + void currentRawReplyChanged(); + private: /** * Upper bound of the delay between retries in milliseconds. diff --git a/src/core/platforms/android/androidplatformutilities.cpp b/src/core/platforms/android/androidplatformutilities.cpp index 7819c7b238..1c284b27f7 100644 --- a/src/core/platforms/android/androidplatformutilities.cpp +++ b/src/core/platforms/android/androidplatformutilities.cpp @@ -573,19 +573,19 @@ bool AndroidPlatformUtilities::checkPositioningPermissions() const auto r = QtAndroidPrivate::checkPermission( "android.permission.ACCESS_COARSE_LOCATION" ).result(); if ( r == QtAndroidPrivate::Denied ) { - return checkAndAcquirePermissions( QStringList() << "android.permission.ACCESS_FINE_LOCATION" ); + return checkAndAcquirePermissions( { QStringLiteral( "android.permission.ACCESS_FINE_LOCATION" ) } ); } return true; } bool AndroidPlatformUtilities::checkCameraPermissions() const { - return checkAndAcquirePermissions( QStringList() << "android.permission.CAMERA" ); + return checkAndAcquirePermissions( { QStringLiteral( "android.permission.CAMERA" ) } ); } bool AndroidPlatformUtilities::checkMicrophonePermissions() const { - return checkAndAcquirePermissions( QStringList() << "android.permission.RECORD_AUDIO" ); + return checkAndAcquirePermissions( { QStringLiteral( "android.permission.RECORD_AUDIO" ) } ); } bool AndroidPlatformUtilities::checkAndAcquirePermissions( QStringList permissions, bool forceAsk ) const @@ -714,7 +714,7 @@ QVariantMap AndroidPlatformUtilities::sceneMargins( QQuickWindow *window ) const void AndroidPlatformUtilities::uploadPendingAttachments( QFieldCloudConnection *connection ) const { // Request notification permission - checkAndAcquirePermissions( QStringList() << QStringLiteral( "android.permission.POST_NOTIFICATIONS" ) ); + checkAndAcquirePermissions( { QStringLiteral( "android.permission.POST_NOTIFICATIONS" ) } ); QTimer::singleShot( 500, [connection]() { if ( connection ) @@ -754,13 +754,13 @@ void AndroidPlatformUtilities::vibrate( int milliseconds ) const void AndroidPlatformUtilities::requestBackgroundPositioningPermissions() { - checkAndAcquirePermissions( QStringLiteral( "android.permission.ACCESS_BACKGROUND_LOCATION" ) ); + checkAndAcquirePermissions( { QStringLiteral( "android.permission.ACCESS_BACKGROUND_LOCATION" ) } ); } void AndroidPlatformUtilities::startPositioningService() const { // Request notification permission - checkAndAcquirePermissions( QStringLiteral( "android.permission.POST_NOTIFICATIONS" ) ); + checkAndAcquirePermissions( { QStringLiteral( "android.permission.POST_NOTIFICATIONS" ) } ); qInfo() << "Launching QField positioning service..."; QJniObject::callStaticMethod( "ch/opengis/" APP_PACKAGE_NAME "/QFieldPositioningService", diff --git a/src/core/qfieldcloudconnection.cpp b/src/core/qfieldcloudconnection.cpp index d4a47594b2..f55abf4a85 100644 --- a/src/core/qfieldcloudconnection.cpp +++ b/src/core/qfieldcloudconnection.cpp @@ -164,7 +164,7 @@ void QFieldCloudConnection::login() // Handle login redirect as an error state connect( reply, &NetworkReply::redirected, this, [=]() { - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); reply->deleteLater(); rawReply->deleteLater(); @@ -175,7 +175,7 @@ void QFieldCloudConnection::login() } ); connect( reply, &NetworkReply::finished, this, [=]() { - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); Q_ASSERT( reply->isFinished() ); Q_ASSERT( rawReply ); @@ -334,7 +334,7 @@ NetworkReply *QFieldCloudConnection::post( const QString &endpoint, const QVaria mPendingRequests++; setState( ConnectionState::Busy ); connect( reply, &NetworkReply::finished, this, [=]() { - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); if ( --mPendingRequests == 0 ) { if ( rawReply->error() != QNetworkReply::NoError ) @@ -395,7 +395,7 @@ NetworkReply *QFieldCloudConnection::get( QNetworkRequest &request, const QUrl & mPendingRequests++; setState( ConnectionState::Busy ); connect( reply, &NetworkReply::finished, this, [=]() { - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); if ( --mPendingRequests == 0 ) { if ( rawReply->error() != QNetworkReply::NoError ) @@ -602,7 +602,7 @@ void QFieldCloudConnection::processPendingAttachments() QString projectId = it.key(); QString fileName = it.value(); connect( attachmentCloudReply, &NetworkReply::finished, this, [=]() { - QNetworkReply *attachmentReply = attachmentCloudReply->reply(); + QNetworkReply *attachmentReply = attachmentCloudReply->currentRawReply(); attachmentCloudReply->deleteLater(); Q_ASSERT( attachmentCloudReply->isFinished() ); diff --git a/src/core/qfieldcloudprojectsmodel.cpp b/src/core/qfieldcloudprojectsmodel.cpp index 906896122f..11aabf2689 100644 --- a/src/core/qfieldcloudprojectsmodel.cpp +++ b/src/core/qfieldcloudprojectsmodel.cpp @@ -390,7 +390,7 @@ void QFieldCloudProjectsModel::refreshProjectFileOutdatedStatus( const QString & return; } - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); reply->deleteLater(); if ( rawReply->error() != QNetworkReply::NoError ) @@ -519,7 +519,7 @@ void QFieldCloudProjectsModel::projectRefreshData( const QString &projectId, con if ( !findProject( projectId ) ) return; - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); reply->deleteLater(); @@ -620,7 +620,7 @@ void QFieldCloudProjectsModel::projectStartJob( const QString &projectId, const return; } - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); if ( rawReply->error() != QNetworkReply::NoError ) { @@ -699,7 +699,7 @@ void QFieldCloudProjectsModel::projectGetJobStatus( const QString &projectId, co return; } - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); if ( rawReply->error() != QNetworkReply::NoError ) { @@ -1005,7 +1005,7 @@ void QFieldCloudProjectsModel::projectDownload( const QString &projectId ) return; } - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); reply->deleteLater(); @@ -1379,7 +1379,7 @@ void QFieldCloudProjectsModel::projectUpload( const QString &projectId, const bo emit dataChanged( projectIndex, projectIndex, QVector() << UploadDeltaProgressRole ); } ); connect( deltasCloudReply, &NetworkReply::finished, this, [=]() { - QNetworkReply *deltasReply = deltasCloudReply->reply(); + QNetworkReply *deltasReply = deltasCloudReply->currentRawReply(); deltasCloudReply->deleteLater(); Q_ASSERT( deltasCloudReply->isFinished() ); @@ -1561,7 +1561,7 @@ void QFieldCloudProjectsModel::refreshProjectDeltaList( const QString &projectId } connect( deltaStatusReply, &NetworkReply::finished, this, [=]() { - QNetworkReply *rawReply = deltaStatusReply->reply(); + QNetworkReply *rawReply = deltaStatusReply->currentRawReply(); deltaStatusReply->deleteLater(); Q_ASSERT( deltaStatusReply->isFinished() ); @@ -1591,7 +1591,7 @@ void QFieldCloudProjectsModel::projectGetDeltaStatus( const QString &projectId ) project->deltaFileUploadStatusString = QString(); connect( deltaStatusReply, &NetworkReply::finished, this, [=]() { - QNetworkReply *rawReply = deltaStatusReply->reply(); + QNetworkReply *rawReply = deltaStatusReply->currentRawReply(); deltaStatusReply->deleteLater(); Q_ASSERT( deltaStatusReply->isFinished() ); @@ -1688,7 +1688,7 @@ void QFieldCloudProjectsModel::layerObserverLayerEdited( const QString &layerId void QFieldCloudProjectsModel::projectListReceived() { NetworkReply *reply = qobject_cast( sender() ); - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); Q_ASSERT( rawReply ); @@ -1736,7 +1736,6 @@ NetworkReply *QFieldCloudProjectsModel::downloadFile( const QString &projectId, return mCloudConnection->get( request, QStringLiteral( "/api/v1/packages/%1/latest/files/%2/" ).arg( projectId, fileName ) ); } - void QFieldCloudProjectsModel::downloadFileConnections( const QString &projectId, const QString &fileName ) { const QModelIndex projectIndex = findProjectIndex( projectId ); @@ -1809,6 +1808,44 @@ void QFieldCloudProjectsModel::downloadFileConnections( const QString &projectId } ); connect( reply, &NetworkReply::downloadProgress, reply, [=]( int bytesReceived, int bytesTotal ) { + QNetworkReply *rawReply = reply->currentRawReply(); + if ( !rawReply ) + { + return; + } + + const QString temporaryFileName = project->downloadFileTransfers[fileName].tmpFile; + QFile file( temporaryFileName ); + QString errorMessageDetail; + QString errorMessage; + bool hasError = false; + + if ( file.open( QIODevice::WriteOnly | QIODevice::Append ) ) + { + file.write( rawReply->readAll() ); + + if ( file.error() != QFile::NoError ) + { + hasError = true; + errorMessageDetail = file.errorString(); + errorMessage = tr( "File system error. Failed to write file to temporary location `%1`." ).arg( temporaryFileName ); + } + } + else + { + hasError = true; + errorMessageDetail = file.errorString(); + errorMessage = tr( "File system error. Failed to open file for writing on temporary `%1`." ).arg( temporaryFileName ); + } + + // check if the code above failed with error + if ( hasError ) + { + logFailedDownload( project, fileName, errorMessage, errorMessageDetail ); + rawReply->abort(); + return; + } + if ( !findProject( projectId ) ) { QgsLogger::debug( QStringLiteral( "Project %1, file `%2`: updating download progress, but the project is deleted." ).arg( projectId, fileName ) ); @@ -1835,7 +1872,7 @@ void QFieldCloudProjectsModel::downloadFileConnections( const QString &projectId } QVector rolesChanged; - QNetworkReply *rawReply = reply->reply(); + QNetworkReply *rawReply = reply->currentRawReply(); Q_ASSERT( reply->isFinished() ); Q_ASSERT( reply ); @@ -1863,46 +1900,13 @@ void QFieldCloudProjectsModel::downloadFileConnections( const QString &projectId project->downloadBytesReceived += project->downloadFileTransfers[fileName].bytesTotal; project->downloadProgress = std::clamp( ( static_cast( project->downloadBytesReceived ) / std::max( project->downloadBytesTotal, 1 ) ), 0., 1. ); emit dataChanged( projectIndex, projectIndex, QVector() << DownloadProgressRole ); - - QFile file( project->downloadFileTransfers[fileName].tmpFile ); - - if ( file.open( QIODevice::ReadWrite ) ) - { - file.write( rawReply->readAll() ); - - if ( file.error() != QFile::NoError ) - { - hasError = true; - errorMessageDetail = file.errorString(); - errorMessage = tr( "File system error. Failed to write file to temporary location `%1`." ).arg( project->downloadFileTransfers[fileName].tmpFile ); - } - } - else - { - hasError = true; - errorMessageDetail = file.errorString(); - errorMessage = tr( "File system error. Failed to open file for writing on temporary `%1`." ).arg( project->downloadFileTransfers[fileName].tmpFile ); - } } // check if the code above failed with error if ( hasError ) { - project->downloadFilesFailed++; - - QgsLogger::debug( QStringLiteral( "Project %1, file `%2`: %3 %4" ).arg( errorMessage, fileName, errorMessage, errorMessageDetail ) ); - - // translate the user messages - const QString baseMessage = tr( "Project `%1`, file `%2`: %3" ).arg( project->name, fileName, errorMessage ); - const QString trimmedMessage = baseMessage + QStringLiteral( " " ) + tr( "System message: " ) - + ( ( errorMessageDetail.size() > 100 ) - ? ( errorMessageDetail.left( 100 ) + tr( " (see more in the QField error log)…" ) ) - : errorMessageDetail ); - - QgsMessageLog::logMessage( QStringLiteral( "%1\n%2" ).arg( baseMessage, errorMessageDetail ) ); - - emit projectDownloadFinished( projectId, trimmedMessage ); - + logFailedDownload( project, fileName, errorMessage, errorMessageDetail ); + rawReply->abort(); return; } @@ -2080,6 +2084,24 @@ void QFieldCloudProjectsModel::insertProjects( const QList &proj endInsertRows(); } +void QFieldCloudProjectsModel::logFailedDownload( CloudProject *project, const QString &fileName, const QString &errorMessage, const QString &errorMessageDetail ) +{ + project->downloadFilesFailed++; + + QgsLogger::debug( QStringLiteral( "Project %1, file `%2`: %3 %4" ).arg( errorMessage, fileName, errorMessage, errorMessageDetail ) ); + + // translate the user messages + const QString baseMessage = tr( "Project `%1`, file `%2`: %3" ).arg( project->name, fileName, errorMessage ); + const QString trimmedMessage = baseMessage + QStringLiteral( " " ) + tr( "System message: " ) + + ( ( errorMessageDetail.size() > 100 ) + ? ( errorMessageDetail.left( 100 ) + tr( " (see more in the QField error log)…" ) ) + : errorMessageDetail ); + + QgsMessageLog::logMessage( QStringLiteral( "%1\n%2" ).arg( baseMessage, errorMessageDetail ) ); + + emit projectDownloadFinished( project->id, trimmedMessage ); +} + void QFieldCloudProjectsModel::loadProjects( const QJsonArray &remoteProjects, bool skipLocalProjects ) { QgsProject *qgisProject = QgsProject::instance(); diff --git a/src/core/qfieldcloudprojectsmodel.h b/src/core/qfieldcloudprojectsmodel.h index 51d6cc8672..b11bb8daf2 100644 --- a/src/core/qfieldcloudprojectsmodel.h +++ b/src/core/qfieldcloudprojectsmodel.h @@ -496,6 +496,7 @@ class QFieldCloudProjectsModel : public QAbstractListModel void downloadFileConnections( const QString &projectId, const QString &fileName ); void loadProjects( const QJsonArray &remoteProjects = QJsonArray(), bool skipLocalProjects = false ); void insertProjects( const QList &projects ); + void logFailedDownload( CloudProject *project, const QString &fileName, const QString &errorMessage, const QString &errorMessageDetail ); }; Q_DECLARE_METATYPE( QFieldCloudProjectsModel::ProjectStatus ) diff --git a/src/qml/NavigationBar.qml b/src/qml/NavigationBar.qml index 84852621ba..792c50f1ab 100644 --- a/src/qml/NavigationBar.qml +++ b/src/qml/NavigationBar.qml @@ -133,7 +133,7 @@ Rectangle { property int distance: 0 property bool isTracing: false - onPressed: { + onPressed: mouse => { startX = mouse.x; startY = mouse.y; lastX = mouse.x; @@ -142,7 +142,7 @@ Rectangle { distance = 0; isTracing = true; } - onPositionChanged: { + onPositionChanged: mouse => { if (!isTracing) return; var currentVelocity = Math.abs(mouse.y - lastY); diff --git a/src/qml/QFieldCloudScreen.qml b/src/qml/QFieldCloudScreen.qml index da92d867e4..2aaf73c43d 100644 --- a/src/qml/QFieldCloudScreen.qml +++ b/src/qml/QFieldCloudScreen.qml @@ -175,7 +175,6 @@ Page { height: filterBar.defaultHeight width: projects.width / filterBar.count font: Theme.defaultFont - enabled: (cloudConnection.state === QFieldCloudConnection.Idle && cloudProjectsModel.busyProjectIds.length === 0) onClicked: { filterBar.currentIndex = index; } @@ -224,20 +223,26 @@ Page { section.labelPositioning: ViewSection.CurrentLabelAtStart | ViewSection.InlineLabels section.delegate: Component { /* section header: layer name */ - Rectangle { + Item { width: parent.width - height: 30 - color: Theme.controlBorderColor + height: 32 - Text { - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter + Rectangle { + width: parent.width + height: 30 + color: Theme.controlBorderColor + anchors.bottom: parent.bottom + + Text { + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + font.bold: true + font.pointSize: Theme.resultFont.pointSize + color: Theme.mainTextColor + text: section } - font.bold: true - font.pointSize: Theme.resultFont.pointSize - color: Theme.mainTextColor - text: section } } } @@ -266,7 +271,7 @@ Page { property int status: Status width: parent ? parent.width : undefined - height: line.height + height: line.height + 6 color: "transparent" ProgressBar {