From 802971b2c457af29b71e800be884b46c5b162363 Mon Sep 17 00:00:00 2001 From: Mohsen Date: Sat, 13 Sep 2025 16:43:13 +0330 Subject: [PATCH 01/15] QF-6684 - Allow QFieldCloud to access Project folder view without .. without opening the project. --- src/qml/QFieldCloudScreen.qml | 16 ++++++++++++++++ src/qml/qgismobileapp.qml | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/src/qml/QFieldCloudScreen.qml b/src/qml/QFieldCloudScreen.qml index 3cf04d16c1..db4e049b48 100644 --- a/src/qml/QFieldCloudScreen.qml +++ b/src/qml/QFieldCloudScreen.qml @@ -12,6 +12,7 @@ Page { id: qfieldCloudScreen signal finished + signal openProjectFolder(string projectPath) property LayerObserver layerObserver property string requestedProjectDetails: "" @@ -471,6 +472,7 @@ Page { projectActions.projectLocalPath = LocalPath; downloadProject.visible = LocalPath === '' && Status !== QFieldCloudProject.ProjectStatus.Downloading; openProject.visible = LocalPath !== ''; + openProjectFolder.visible = LocalPath !== ''; removeProject.visible = LocalPath !== ''; cancelDownloadProject.visible = Status === QFieldCloudProject.ProjectStatus.Downloading; projectActions.popup(gc.x + width - projectActions.width, gc.y - height); @@ -942,6 +944,20 @@ Page { } } + MenuItem { + id: openProjectFolder + + font: Theme.defaultFont + width: parent.width + height: visible ? 48 : 0 + leftPadding: Theme.menuItemLeftPadding + + text: qsTr("Open Project Folder") + onTriggered: { + qfieldCloudScreen.openProjectFolder(projectActions.projectLocalPath); + } + } + MenuItem { id: removeProject diff --git a/src/qml/qgismobileapp.qml b/src/qml/qgismobileapp.qml index 8620ca9678..360b384db1 100644 --- a/src/qml/qgismobileapp.qml +++ b/src/qml/qgismobileapp.qml @@ -4359,6 +4359,12 @@ ApplicationWindow { } Component.onCompleted: focusstack.addFocusTaker(this) + + onOpenProjectFolder: projectPath => { + qfieldLocalDataPickerScreen.projectFolderView = true; + qfieldLocalDataPickerScreen.model.resetToPath(projectPath); + qfieldLocalDataPickerScreen.visible = true; + } } QFieldCloudPopup { From ca9d5647073a169fb40026f9dd6bab0516f03ade Mon Sep 17 00:00:00 2001 From: Mohsen Date: Sat, 13 Sep 2025 17:29:21 +0330 Subject: [PATCH 02/15] Rename OpenProjectFolder to ViewProjectFolder. --- src/qml/QFieldCloudScreen.qml | 10 +++++----- src/qml/qgismobileapp.qml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qml/QFieldCloudScreen.qml b/src/qml/QFieldCloudScreen.qml index db4e049b48..31ff4bfba1 100644 --- a/src/qml/QFieldCloudScreen.qml +++ b/src/qml/QFieldCloudScreen.qml @@ -12,7 +12,7 @@ Page { id: qfieldCloudScreen signal finished - signal openProjectFolder(string projectPath) + signal viewProjectFolder(string projectPath) property LayerObserver layerObserver property string requestedProjectDetails: "" @@ -472,7 +472,7 @@ Page { projectActions.projectLocalPath = LocalPath; downloadProject.visible = LocalPath === '' && Status !== QFieldCloudProject.ProjectStatus.Downloading; openProject.visible = LocalPath !== ''; - openProjectFolder.visible = LocalPath !== ''; + viewProjectFolder.visible = LocalPath !== ''; removeProject.visible = LocalPath !== ''; cancelDownloadProject.visible = Status === QFieldCloudProject.ProjectStatus.Downloading; projectActions.popup(gc.x + width - projectActions.width, gc.y - height); @@ -945,16 +945,16 @@ Page { } MenuItem { - id: openProjectFolder + id: viewProjectFolder font: Theme.defaultFont width: parent.width height: visible ? 48 : 0 leftPadding: Theme.menuItemLeftPadding - text: qsTr("Open Project Folder") + text: qsTr("View Project Folder") onTriggered: { - qfieldCloudScreen.openProjectFolder(projectActions.projectLocalPath); + qfieldCloudScreen.viewProjectFolder(projectActions.projectLocalPath); } } diff --git a/src/qml/qgismobileapp.qml b/src/qml/qgismobileapp.qml index 360b384db1..dbc40bc40c 100644 --- a/src/qml/qgismobileapp.qml +++ b/src/qml/qgismobileapp.qml @@ -4360,7 +4360,7 @@ ApplicationWindow { Component.onCompleted: focusstack.addFocusTaker(this) - onOpenProjectFolder: projectPath => { + onViewProjectFolder: projectPath => { qfieldLocalDataPickerScreen.projectFolderView = true; qfieldLocalDataPickerScreen.model.resetToPath(projectPath); qfieldLocalDataPickerScreen.visible = true; From ff0f79b18b8ea04fe83bce0519707f2681c9782b Mon Sep 17 00:00:00 2001 From: Mohsen Date: Sat, 13 Sep 2025 21:08:31 +0330 Subject: [PATCH 03/15] Add View file. --- src/qml/QFieldLocalDataPickerScreen.qml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index 924de73780..223bf38945 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -445,6 +445,23 @@ Page { bottomMargin: sceneBottomMargin paddingMultiplier: 2 + MenuItem { + id: viewFile + + enabled: true + visible: enabled + + font: Theme.defaultFont + width: parent.width + height: enabled ? 48 : 0 + leftPadding: Theme.menuItemLeftPadding + + text: qsTr("View") + onTriggered: { + platformUtilities.open(itemMenu.itemPath); + } + } + // File items MenuItem { id: sendDatasetTo From b84bc0d558c50fbbaae4be1740617c998f3591de Mon Sep 17 00:00:00 2001 From: Mohsen Date: Sat, 13 Sep 2025 21:45:46 +0330 Subject: [PATCH 04/15] Add remove confirmation. --- src/qml/QFieldCloudScreen.qml | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/qml/QFieldCloudScreen.qml b/src/qml/QFieldCloudScreen.qml index 31ff4bfba1..3481215283 100644 --- a/src/qml/QFieldCloudScreen.qml +++ b/src/qml/QFieldCloudScreen.qml @@ -968,12 +968,7 @@ Page { text: qsTr("Remove Stored Project") onTriggered: { - cloudProjectsModel.removeLocalProject(projectActions.projectId); - iface.removeRecentProject(projectActions.projectLocalPath); - welcomeScreen.model.reloadModel(); - if (projectActions.projectLocalPath === qgisProject.fileName) { - iface.clearProject(); - } + confirmRemoveDialog.open(); } } @@ -990,6 +985,28 @@ Page { } } + QfDialog { + id: confirmRemoveDialog + parent: mainWindow.contentItem + title: removeProject.text + Label { + width: parent.width + wrapMode: Text.WordWrap + text: qsTr("Are you sure you want to remove `%1`?").arg(projectActions.projectName) + } + onAccepted: { + cloudProjectsModel.removeLocalProject(projectActions.projectId); + iface.removeRecentProject(projectActions.projectLocalPath); + welcomeScreen.model.reloadModel(); + if (projectActions.projectLocalPath === qgisProject.fileName) { + iface.clearProject(); + } + } + onRejected: { + visible = false; + } + } + Connections { id: codeReaderConnection target: codeReader From adb21090b1010e8d816acc6de12f9d5f47e465dc Mon Sep 17 00:00:00 2001 From: Mohsen Date: Sun, 14 Sep 2025 16:07:20 +0330 Subject: [PATCH 05/15] View file not on folders. --- src/qml/QFieldLocalDataPickerScreen.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index 223bf38945..6ea459cb2d 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -448,7 +448,7 @@ Page { MenuItem { id: viewFile - enabled: true + enabled: itemMenu.itemMetaType != LocalFilesModel.Folder visible: enabled font: Theme.defaultFont @@ -456,7 +456,7 @@ Page { height: enabled ? 48 : 0 leftPadding: Theme.menuItemLeftPadding - text: qsTr("View") + text: qsTr("View file") onTriggered: { platformUtilities.open(itemMenu.itemPath); } From 797beb04b602d449f9eee7519668787f596f1e42 Mon Sep 17 00:00:00 2001 From: Mohsen Date: Tue, 16 Sep 2025 09:46:20 +0330 Subject: [PATCH 06/15] Re-Enable `Push to QFieldCloud`. --- src/core/qfieldcloud/qfieldcloudconnection.h | 1 + src/core/utils/qfieldcloudutils.cpp | 12 +++++++----- src/core/utils/qfieldcloudutils.h | 2 +- src/qml/QFieldCloudScreen.qml | 1 + src/qml/QFieldLocalDataPickerScreen.qml | 11 +++++++---- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/core/qfieldcloud/qfieldcloudconnection.h b/src/core/qfieldcloud/qfieldcloudconnection.h index 1de2573ddb..1c331b1bc6 100644 --- a/src/core/qfieldcloud/qfieldcloudconnection.h +++ b/src/core/qfieldcloud/qfieldcloudconnection.h @@ -231,6 +231,7 @@ class QFieldCloudConnection : public QObject void providerConfigurationChanged(); void userInformationChanged(); void pendingAttachmentsUploadFinished(); + void allAttachmentsWritten(); void error(); void loginFailed( const QString &reason ); diff --git a/src/core/utils/qfieldcloudutils.cpp b/src/core/utils/qfieldcloudutils.cpp index fe704fbc3c..11c56485b1 100644 --- a/src/core/utils/qfieldcloudutils.cpp +++ b/src/core/utils/qfieldcloudutils.cpp @@ -233,7 +233,7 @@ void QFieldCloudUtils::addPendingAttachments( const QString &username, const QSt params.insert( "skip_metadata", 1 ); NetworkReply *reply = cloudConnection->get( QStringLiteral( "/api/v1/files/%1/" ).arg( projectId ), params ); - connect( reply, &NetworkReply::finished, reply, [reply, username, projectId, fileNames, checkSumCheck]() { + connect( reply, &NetworkReply::finished, reply, [reply, username, projectId, fileNames, checkSumCheck, cloudConnection]() { QNetworkReply *rawReply = reply->currentRawReply(); reply->deleteLater(); @@ -254,16 +254,16 @@ void QFieldCloudUtils::addPendingAttachments( const QString &username, const QSt fileChecksumMap.insert( fileName, cloudEtag ); } - QFieldCloudUtils::writeToAttachmentsFile( username, projectId, fileNames, &fileChecksumMap, checkSumCheck ); + writeToAttachmentsFile( username, projectId, fileNames, &fileChecksumMap, checkSumCheck, cloudConnection ); } ); } else { - writeToAttachmentsFile( username, projectId, fileNames, nullptr, false ); + writeToAttachmentsFile( username, projectId, fileNames, nullptr, false, cloudConnection ); } } -void QFieldCloudUtils::writeToAttachmentsFile( const QString &username, const QString &projectId, const QStringList &fileNames, const QHash *fileChecksumMap, const bool &checkSumCheck ) +void QFieldCloudUtils::writeToAttachmentsFile( const QString &username, const QString &projectId, const QStringList &fileNames, const QHash *fileChecksumMap, const bool &checkSumCheck, QFieldCloudConnection *cloudConnection ) { const QString localCloudUSerDirectory = QLatin1String( "%1/%2/" ).arg( QFieldCloudUtils::localCloudDirectory(), username ); QLockFile attachmentsLock( QStringLiteral( "%1/attachments.lock" ).arg( localCloudUSerDirectory ) ); @@ -285,8 +285,10 @@ void QFieldCloudUtils::writeToAttachmentsFile( const QString &username, const QS writeFileDetails( fileName, projectId, fileChecksumMap, checkSumCheck, attachmentsStream ); } } - attachmentsFile.close(); + + if ( cloudConnection ) + emit cloudConnection->allAttachmentsWritten(); } } diff --git a/src/core/utils/qfieldcloudutils.h b/src/core/utils/qfieldcloudutils.h index 2e88f1ae8c..6ff9ca6fde 100644 --- a/src/core/utils/qfieldcloudutils.h +++ b/src/core/utils/qfieldcloudutils.h @@ -159,7 +159,7 @@ class QFieldCloudUtils : public QObject private: static inline const QString errorCodeOverQuota { QStringLiteral( "over_quota" ) }; - static void writeToAttachmentsFile( const QString &username, const QString &projectId, const QStringList &fileNames, const QHash *fileChecksumMap, const bool &checkSumCheck ); + static void writeToAttachmentsFile( const QString &username, const QString &projectId, const QStringList &fileNames, const QHash *fileChecksumMap, const bool &checkSumCheck, QFieldCloudConnection *cloudConnection = nullptr ); static void writeFilesFromDirectory( const QString &dirPath, const QString &projectId, const QHash *fileChecksumMap, const bool &checkSumCheck, QTextStream &attachmentsStream ); diff --git a/src/qml/QFieldCloudScreen.qml b/src/qml/QFieldCloudScreen.qml index 3481215283..7c68dad3bb 100644 --- a/src/qml/QFieldCloudScreen.qml +++ b/src/qml/QFieldCloudScreen.qml @@ -954,6 +954,7 @@ Page { text: qsTr("View Project Folder") onTriggered: { + cloudProjectsModel.currentProjectId = QFieldCloudUtils.getProjectId(projectActions.projectLocalPath); qfieldCloudScreen.viewProjectFolder(projectActions.projectLocalPath); } } diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index 6ea459cb2d..63667ca953 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -481,7 +481,7 @@ Page { MenuItem { id: pushDatasetToCloud - enabled: (itemMenu.itemMetaType == LocalFilesModel.Dataset && itemMenu.itemType == LocalFilesModel.RasterDataset && cloudProjectsModel.currentProjectId) || (itemMenu.itemMetaType == LocalFilesModel.Folder && itemMenu.itemWithinQFieldCloudProjectFolder) + enabled: (itemMenu.itemMetaType == LocalFilesModel.File) || (itemMenu.itemMetaType == LocalFilesModel.Dataset && itemMenu.itemType == LocalFilesModel.RasterDataset && cloudProjectsModel.currentProjectId) || (itemMenu.itemMetaType == LocalFilesModel.Folder && itemMenu.itemWithinQFieldCloudProjectFolder) visible: enabled font: Theme.defaultFont @@ -491,9 +491,12 @@ Page { text: qsTr("Push to QFieldCloud") onTriggered: { - QFieldCloudUtils.addPendingAttachments(projectInfo.cloudUserInformation.username, cloudProjectsModel.currentProjectId, [itemMenu.itemPath], cloudConnection, true); - platformUtilities.uploadPendingAttachments(cloudConnection); - displayToast(qsTr("‘%1’ is being uploaded to QFieldCloud").arg(FileUtils.fileName(itemMenu.itemPath))); + QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, cloudProjectsModel.currentProjectId, [itemMenu.itemPath], cloudConnection, true); + cloudConnection.onAllAttachmentsWritten.connect(function handler() { + platformUtilities.uploadPendingAttachments(cloudConnection); + displayToast(qsTr("‘%1’ is being uploaded to QFieldCloud").arg(FileUtils.fileName(itemMenu.itemPath))); + cloudConnection.onAllAttachmentsWritten.disconnect(handler); + }); } } From 0df0e1b79505eb05b35e9a18f25af12fd640466c Mon Sep 17 00:00:00 2001 From: Mohsen Date: Tue, 16 Sep 2025 10:05:30 +0330 Subject: [PATCH 07/15] Also push folders. --- src/core/qfieldcloud/qfieldcloudconnection.cpp | 4 ++++ src/qml/QFieldLocalDataPickerScreen.qml | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/core/qfieldcloud/qfieldcloudconnection.cpp b/src/core/qfieldcloud/qfieldcloudconnection.cpp index 05aab6e46c..b4dce186be 100644 --- a/src/core/qfieldcloud/qfieldcloudconnection.cpp +++ b/src/core/qfieldcloud/qfieldcloudconnection.cpp @@ -862,6 +862,10 @@ void QFieldCloudConnection::processPendingAttachments() return; } } + if ( httpCode == 201 ) + { + qInfo() << QStringLiteral( "Upload succeeded for %1" ).arg( fileName ); + } if ( httpCode != 201 && httpCode != 404 ) { diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index 63667ca953..859ebed77b 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -882,9 +882,12 @@ Page { } } if (fileNames.length > 0) { - QFieldCloudUtils.addPendingAttachments(projectInfo.cloudUserInformation.username, cloudProjectsModel.currentProjectId, fileNames, cloudConnection, true); - platformUtilities.uploadPendingAttachments(cloudConnection); - localFilesModel.clearSelection(); + QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, cloudProjectsModel.currentProjectId, fileNames, cloudConnection, true); + cloudConnection.onAllAttachmentsWritten.connect(function handler() { + platformUtilities.uploadPendingAttachments(cloudConnection); + localFilesModel.clearSelection(); + cloudConnection.onAllAttachmentsWritten.disconnect(handler); + }); } else { displayToast(qsTr("Please select one or more files to push to QFieldCloud.")); } From 7bc5b4e2c6715d0ee5a162f00883163825709260 Mon Sep 17 00:00:00 2001 From: Mohsen Date: Tue, 23 Sep 2025 21:39:28 +0330 Subject: [PATCH 08/15] Address review 1. --- src/core/qfieldcloud/qfieldcloudconnection.cpp | 4 ---- src/core/qfieldcloud/qfieldcloudconnection.h | 2 +- src/core/utils/qfieldcloudutils.cpp | 2 +- src/qml/QFieldLocalDataPickerScreen.qml | 8 ++++---- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/core/qfieldcloud/qfieldcloudconnection.cpp b/src/core/qfieldcloud/qfieldcloudconnection.cpp index b4dce186be..05aab6e46c 100644 --- a/src/core/qfieldcloud/qfieldcloudconnection.cpp +++ b/src/core/qfieldcloud/qfieldcloudconnection.cpp @@ -862,10 +862,6 @@ void QFieldCloudConnection::processPendingAttachments() return; } } - if ( httpCode == 201 ) - { - qInfo() << QStringLiteral( "Upload succeeded for %1" ).arg( fileName ); - } if ( httpCode != 201 && httpCode != 404 ) { diff --git a/src/core/qfieldcloud/qfieldcloudconnection.h b/src/core/qfieldcloud/qfieldcloudconnection.h index 1c331b1bc6..e27fb978e5 100644 --- a/src/core/qfieldcloud/qfieldcloudconnection.h +++ b/src/core/qfieldcloud/qfieldcloudconnection.h @@ -231,7 +231,7 @@ class QFieldCloudConnection : public QObject void providerConfigurationChanged(); void userInformationChanged(); void pendingAttachmentsUploadFinished(); - void allAttachmentsWritten(); + void pendingAttachmentsAdded(); void error(); void loginFailed( const QString &reason ); diff --git a/src/core/utils/qfieldcloudutils.cpp b/src/core/utils/qfieldcloudutils.cpp index 11c56485b1..b9d08bad70 100644 --- a/src/core/utils/qfieldcloudutils.cpp +++ b/src/core/utils/qfieldcloudutils.cpp @@ -288,7 +288,7 @@ void QFieldCloudUtils::writeToAttachmentsFile( const QString &username, const QS attachmentsFile.close(); if ( cloudConnection ) - emit cloudConnection->allAttachmentsWritten(); + emit cloudConnection->pendingAttachmentsAdded(); } } diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index 859ebed77b..501c6216e9 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -492,10 +492,10 @@ Page { text: qsTr("Push to QFieldCloud") onTriggered: { QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, cloudProjectsModel.currentProjectId, [itemMenu.itemPath], cloudConnection, true); - cloudConnection.onAllAttachmentsWritten.connect(function handler() { + cloudConnection.onPendingAttachmentsAdded.connect(function handler() { platformUtilities.uploadPendingAttachments(cloudConnection); displayToast(qsTr("‘%1’ is being uploaded to QFieldCloud").arg(FileUtils.fileName(itemMenu.itemPath))); - cloudConnection.onAllAttachmentsWritten.disconnect(handler); + cloudConnection.onPendingAttachmentsAdded.disconnect(handler); }); } } @@ -883,10 +883,10 @@ Page { } if (fileNames.length > 0) { QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, cloudProjectsModel.currentProjectId, fileNames, cloudConnection, true); - cloudConnection.onAllAttachmentsWritten.connect(function handler() { + cloudConnection.onPendingAttachmentsAdded.connect(function handler() { platformUtilities.uploadPendingAttachments(cloudConnection); localFilesModel.clearSelection(); - cloudConnection.onAllAttachmentsWritten.disconnect(handler); + cloudConnection.onPendingAttachmentsAdded.disconnect(handler); }); } else { displayToast(qsTr("Please select one or more files to push to QFieldCloud.")); From a8a5d45aec0172b78b5e8866d3791ce5255d9475 Mon Sep 17 00:00:00 2001 From: Mohsen Date: Tue, 23 Sep 2025 22:54:09 +0330 Subject: [PATCH 09/15] Address review 2. Use Connections instead of Js approach. --- src/qml/QFieldLocalDataPickerScreen.qml | 32 +++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index 501c6216e9..8b246891e7 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -491,12 +491,9 @@ Page { text: qsTr("Push to QFieldCloud") onTriggered: { + pushFilesToQFieldCloudConnection.enabled = true; + pushFilesToQFieldCloudConnection.sendingItem = true; QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, cloudProjectsModel.currentProjectId, [itemMenu.itemPath], cloudConnection, true); - cloudConnection.onPendingAttachmentsAdded.connect(function handler() { - platformUtilities.uploadPendingAttachments(cloudConnection); - displayToast(qsTr("‘%1’ is being uploaded to QFieldCloud").arg(FileUtils.fileName(itemMenu.itemPath))); - cloudConnection.onPendingAttachmentsAdded.disconnect(handler); - }); } } @@ -882,12 +879,8 @@ Page { } } if (fileNames.length > 0) { + pushFilesToQFieldCloudConnection.enabled = true; QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, cloudProjectsModel.currentProjectId, fileNames, cloudConnection, true); - cloudConnection.onPendingAttachmentsAdded.connect(function handler() { - platformUtilities.uploadPendingAttachments(cloudConnection); - localFilesModel.clearSelection(); - cloudConnection.onPendingAttachmentsAdded.disconnect(handler); - }); } else { displayToast(qsTr("Please select one or more files to push to QFieldCloud.")); } @@ -896,6 +889,25 @@ Page { } } + Connections { + id: pushFilesToQFieldCloudConnection + enabled: false + target: cloudConnection + + property bool sendingItem: false + + function onPendingAttachmentsAdded() { + platformUtilities.uploadPendingAttachments(cloudConnection); + if (pushFilesToQFieldCloudConnection.sendingItem) { + displayToast(qsTr("‘%1’ is being uploaded to QFieldCloud").arg(FileUtils.fileName(itemMenu.itemPath))); + pushFilesToQFieldCloudConnection.sendingItem = false; + } else { + localFilesModel.clearSelection(); + } + pushFilesToQFieldCloudConnection.enabled = false; + } + } + QfDialog { id: importUrlDialog title: qsTr("Import URL") From 39f9314305f90321a39e10782f84f1f3248a3c19 Mon Sep 17 00:00:00 2001 From: Mohsen Date: Thu, 25 Sep 2025 23:49:48 +0330 Subject: [PATCH 10/15] Update getProjectId(). --- src/core/utils/qfieldcloudutils.cpp | 29 ++++++++++++++++++++----- src/core/utils/qfieldcloudutils.h | 10 ++++++--- src/qml/QFieldCloudScreen.qml | 1 - src/qml/QFieldLocalDataPickerScreen.qml | 4 ++-- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/core/utils/qfieldcloudutils.cpp b/src/core/utils/qfieldcloudutils.cpp index b9d08bad70..37f252f51e 100644 --- a/src/core/utils/qfieldcloudutils.cpp +++ b/src/core/utils/qfieldcloudutils.cpp @@ -68,13 +68,30 @@ bool QFieldCloudUtils::isCloudAction( const QgsMapLayer *layer ) const QString QFieldCloudUtils::getProjectId( const QString &fileName ) { - QFileInfo fi( fileName ); - QDir baseDir = fi.isDir() ? fi.canonicalFilePath() : fi.canonicalPath(); - QString basePath = QFileInfo( baseDir.path() ).canonicalFilePath(); - QString cloudPath = QFileInfo( localCloudDirectory() ).canonicalFilePath(); + if ( fileName.isEmpty() ) + return QString(); - if ( !cloudPath.isEmpty() && basePath.startsWith( cloudPath ) ) - return baseDir.dirName(); + const QString path = QFileInfo( fileName ).canonicalFilePath(); + if ( path.isEmpty() ) + return QString(); + + const QString cloudPath = QFieldCloudUtils::localCloudDirectory(); + if ( cloudPath.isEmpty() || !path.startsWith( cloudPath ) ) + return QString(); + + const QRegularExpression re( + QStringLiteral( "^%1[/\\\\]([^/\\\\]+)[/\\\\]([^/\\\\]+)" ) + .arg( QRegularExpression::escape( cloudPath ) ) ); + const QRegularExpressionMatch match = re.match( path, 0, + QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption ); + + if ( match.hasMatch() ) + { + const QString username = match.captured( 1 ); + const QString projectId = match.captured( 2 ); + Q_UNUSED( username ); + return projectId; + } return QString(); } diff --git a/src/core/utils/qfieldcloudutils.h b/src/core/utils/qfieldcloudutils.h index 6ff9ca6fde..63a07e566a 100644 --- a/src/core/utils/qfieldcloudutils.h +++ b/src/core/utils/qfieldcloudutils.h @@ -107,10 +107,14 @@ class QFieldCloudUtils : public QObject static bool isCloudAction( const QgsMapLayer *layer ); /** - * Returns the cloud project id. + * Returns the cloud project ID for a given file path. * - * @param fileName file name of the project to be checked - * @return const QString either UUID-like string or a null string in case of failure + * This function checks if the given file path is under the QField local cloud + * directory. If it is, it extracts and returns the project ID (UUID-like string) + * from the path. Otherwise, it returns a null QString. + * + * @param fileName Full path to a file or directory inside a cloud project + * @return QString Project ID if found; otherwise, an empty string */ Q_INVOKABLE static const QString getProjectId( const QString &fileName ); diff --git a/src/qml/QFieldCloudScreen.qml b/src/qml/QFieldCloudScreen.qml index 7c68dad3bb..3481215283 100644 --- a/src/qml/QFieldCloudScreen.qml +++ b/src/qml/QFieldCloudScreen.qml @@ -954,7 +954,6 @@ Page { text: qsTr("View Project Folder") onTriggered: { - cloudProjectsModel.currentProjectId = QFieldCloudUtils.getProjectId(projectActions.projectLocalPath); qfieldCloudScreen.viewProjectFolder(projectActions.projectLocalPath); } } diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index 8b246891e7..b3d036a6ce 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -493,7 +493,7 @@ Page { onTriggered: { pushFilesToQFieldCloudConnection.enabled = true; pushFilesToQFieldCloudConnection.sendingItem = true; - QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, cloudProjectsModel.currentProjectId, [itemMenu.itemPath], cloudConnection, true); + QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, QFieldCloudUtils.getProjectId(table.model.currentPath), [itemMenu.itemPath], cloudConnection, true); } } @@ -880,7 +880,7 @@ Page { } if (fileNames.length > 0) { pushFilesToQFieldCloudConnection.enabled = true; - QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, cloudProjectsModel.currentProjectId, fileNames, cloudConnection, true); + QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, QFieldCloudUtils.getProjectId(table.model.currentPath), fileNames, cloudConnection, true); } else { displayToast(qsTr("Please select one or more files to push to QFieldCloud.")); } From c23c6a7bfb4d27b7653f4d05c85144932d60083b Mon Sep 17 00:00:00 2001 From: Mohsen Date: Mon, 29 Sep 2025 23:07:53 +0330 Subject: [PATCH 11/15] Corrects project ID extraction. --- src/core/utils/qfieldcloudutils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/utils/qfieldcloudutils.cpp b/src/core/utils/qfieldcloudutils.cpp index 37f252f51e..563c7c0490 100644 --- a/src/core/utils/qfieldcloudutils.cpp +++ b/src/core/utils/qfieldcloudutils.cpp @@ -83,7 +83,7 @@ const QString QFieldCloudUtils::getProjectId( const QString &fileName ) QStringLiteral( "^%1[/\\\\]([^/\\\\]+)[/\\\\]([^/\\\\]+)" ) .arg( QRegularExpression::escape( cloudPath ) ) ); const QRegularExpressionMatch match = re.match( path, 0, - QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption ); + QRegularExpression::NormalMatch, QRegularExpression::AnchorAtOffsetMatchOption ); if ( match.hasMatch() ) { From 6de52d31d7d27b8dcbfa8a7b09daba5dd068efb0 Mon Sep 17 00:00:00 2001 From: Mohsen Date: Tue, 30 Sep 2025 20:39:20 +0330 Subject: [PATCH 12/15] Removes unused username variable. --- src/core/utils/qfieldcloudutils.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/utils/qfieldcloudutils.cpp b/src/core/utils/qfieldcloudutils.cpp index 563c7c0490..0d1e9e36f3 100644 --- a/src/core/utils/qfieldcloudutils.cpp +++ b/src/core/utils/qfieldcloudutils.cpp @@ -87,9 +87,7 @@ const QString QFieldCloudUtils::getProjectId( const QString &fileName ) if ( match.hasMatch() ) { - const QString username = match.captured( 1 ); const QString projectId = match.captured( 2 ); - Q_UNUSED( username ); return projectId; } From fce9b8264008b8bed93c41a4def3438de89fe0a5 Mon Sep 17 00:00:00 2001 From: Mohsen Date: Tue, 30 Sep 2025 21:38:29 +0330 Subject: [PATCH 13/15] Rename sendingItem and toast in both modes. --- src/qml/QFieldLocalDataPickerScreen.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index b3d036a6ce..83cd7fb67b 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -492,7 +492,7 @@ Page { text: qsTr("Push to QFieldCloud") onTriggered: { pushFilesToQFieldCloudConnection.enabled = true; - pushFilesToQFieldCloudConnection.sendingItem = true; + pushFilesToQFieldCloudConnection.sendingMultiple = true; QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, QFieldCloudUtils.getProjectId(table.model.currentPath), [itemMenu.itemPath], cloudConnection, true); } } @@ -894,15 +894,16 @@ Page { enabled: false target: cloudConnection - property bool sendingItem: false + property bool sendingMultiple: false function onPendingAttachmentsAdded() { platformUtilities.uploadPendingAttachments(cloudConnection); - if (pushFilesToQFieldCloudConnection.sendingItem) { + if (pushFilesToQFieldCloudConnection.sendingMultiple) { displayToast(qsTr("‘%1’ is being uploaded to QFieldCloud").arg(FileUtils.fileName(itemMenu.itemPath))); - pushFilesToQFieldCloudConnection.sendingItem = false; + pushFilesToQFieldCloudConnection.sendingMultiple = false; } else { localFilesModel.clearSelection(); + displayToast(qsTr("Items being uploaded to QFieldCloud")); } pushFilesToQFieldCloudConnection.enabled = false; } From 291008feeb46081ab06369573a2465b734768fd2 Mon Sep 17 00:00:00 2001 From: Mohsen Date: Tue, 30 Sep 2025 21:48:00 +0330 Subject: [PATCH 14/15] Ensures cloud directory path has no trailing slash. --- src/core/utils/qfieldcloudutils.cpp | 5 +++++ src/core/utils/qfieldcloudutils.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/core/utils/qfieldcloudutils.cpp b/src/core/utils/qfieldcloudutils.cpp index 0d1e9e36f3..93e02bde32 100644 --- a/src/core/utils/qfieldcloudutils.cpp +++ b/src/core/utils/qfieldcloudutils.cpp @@ -40,6 +40,11 @@ const QString QFieldCloudUtils::localCloudDirectory() QString cloudDirectoryPath = sLocalCloudDirectory.isNull() ? PlatformUtilities::instance()->systemLocalDataLocation( QStringLiteral( "cloud_projects" ) ) : sLocalCloudDirectory; + // Remove trailing '/' or '\' if present + while ( !cloudDirectoryPath.isEmpty() && ( cloudDirectoryPath.endsWith( '/' ) || cloudDirectoryPath.endsWith( '\\' ) ) ) + { + cloudDirectoryPath.chop( 1 ); + } return cloudDirectoryPath; } diff --git a/src/core/utils/qfieldcloudutils.h b/src/core/utils/qfieldcloudutils.h index 63a07e566a..d945076cb2 100644 --- a/src/core/utils/qfieldcloudutils.h +++ b/src/core/utils/qfieldcloudutils.h @@ -90,6 +90,7 @@ class QFieldCloudUtils : public QObject /** * Returns the path to the local cloud directory. * By default inside the user profile unless overwritten with setLocalCloudDirectory + * Ensures the returned path does NOT have a trailing '/' or '\' to avoid issues with regex operations. */ static const QString localCloudDirectory(); From 987cb754bd20043a381ea23278d4874afebe96cb Mon Sep 17 00:00:00 2001 From: Mohsen Date: Fri, 3 Oct 2025 02:19:06 +0330 Subject: [PATCH 15/15] Corrects project ID extraction from file path. Address review. --- src/core/utils/qfieldcloudutils.cpp | 5 ++--- src/core/utils/qfieldcloudutils.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/utils/qfieldcloudutils.cpp b/src/core/utils/qfieldcloudutils.cpp index 93e02bde32..27d28d22fd 100644 --- a/src/core/utils/qfieldcloudutils.cpp +++ b/src/core/utils/qfieldcloudutils.cpp @@ -85,15 +85,14 @@ const QString QFieldCloudUtils::getProjectId( const QString &fileName ) return QString(); const QRegularExpression re( - QStringLiteral( "^%1[/\\\\]([^/\\\\]+)[/\\\\]([^/\\\\]+)" ) + QStringLiteral( "^%1[/\\\\][^/\\\\]+[/\\\\]([^/\\\\]+)" ) .arg( QRegularExpression::escape( cloudPath ) ) ); const QRegularExpressionMatch match = re.match( path, 0, QRegularExpression::NormalMatch, QRegularExpression::AnchorAtOffsetMatchOption ); if ( match.hasMatch() ) { - const QString projectId = match.captured( 2 ); - return projectId; + return match.captured( 1 ); } return QString(); diff --git a/src/core/utils/qfieldcloudutils.h b/src/core/utils/qfieldcloudutils.h index d945076cb2..ea653338c2 100644 --- a/src/core/utils/qfieldcloudutils.h +++ b/src/core/utils/qfieldcloudutils.h @@ -90,7 +90,7 @@ class QFieldCloudUtils : public QObject /** * Returns the path to the local cloud directory. * By default inside the user profile unless overwritten with setLocalCloudDirectory - * Ensures the returned path does NOT have a trailing '/' or '\' to avoid issues with regex operations. + * \note The returned path will never have have a trailing '/' or '\' . */ static const QString localCloudDirectory();