Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/core/qfieldcloud/qfieldcloudconnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ class QFieldCloudConnection : public QObject
void providerConfigurationChanged();
void userInformationChanged();
void pendingAttachmentsUploadFinished();
void pendingAttachmentsAdded();
void error();

void loginFailed( const QString &reason );
Expand Down
43 changes: 32 additions & 11 deletions src/core/utils/qfieldcloudutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -68,13 +73,27 @@ 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();

const QString path = QFileInfo( fileName ).canonicalFilePath();
if ( path.isEmpty() )
return QString();

if ( !cloudPath.isEmpty() && basePath.startsWith( cloudPath ) )
return baseDir.dirName();
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::AnchorAtOffsetMatchOption );

if ( match.hasMatch() )
{
return match.captured( 1 );
}

return QString();
}
Expand Down Expand Up @@ -233,7 +252,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();

Expand All @@ -254,16 +273,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<QString, QString> *fileChecksumMap, const bool &checkSumCheck )
void QFieldCloudUtils::writeToAttachmentsFile( const QString &username, const QString &projectId, const QStringList &fileNames, const QHash<QString, QString> *fileChecksumMap, const bool &checkSumCheck, QFieldCloudConnection *cloudConnection )
{
const QString localCloudUSerDirectory = QLatin1String( "%1/%2/" ).arg( QFieldCloudUtils::localCloudDirectory(), username );
QLockFile attachmentsLock( QStringLiteral( "%1/attachments.lock" ).arg( localCloudUSerDirectory ) );
Expand All @@ -285,8 +304,10 @@ void QFieldCloudUtils::writeToAttachmentsFile( const QString &username, const QS
writeFileDetails( fileName, projectId, fileChecksumMap, checkSumCheck, attachmentsStream );
}
}

attachmentsFile.close();

if ( cloudConnection )
emit cloudConnection->pendingAttachmentsAdded();
}
}

Expand Down
13 changes: 9 additions & 4 deletions src/core/utils/qfieldcloudutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
* \note The returned path will never have have a trailing '/' or '\' .
*/
static const QString localCloudDirectory();

Expand All @@ -107,10 +108,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 );

Expand Down Expand Up @@ -159,7 +164,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<QString, QString> *fileChecksumMap, const bool &checkSumCheck );
static void writeToAttachmentsFile( const QString &username, const QString &projectId, const QStringList &fileNames, const QHash<QString, QString> *fileChecksumMap, const bool &checkSumCheck, QFieldCloudConnection *cloudConnection = nullptr );

static void writeFilesFromDirectory( const QString &dirPath, const QString &projectId, const QHash<QString, QString> *fileChecksumMap, const bool &checkSumCheck, QTextStream &attachmentsStream );

Expand Down
45 changes: 39 additions & 6 deletions src/qml/QFieldCloudScreen.qml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Page {
id: qfieldCloudScreen

signal finished
signal viewProjectFolder(string projectPath)

property LayerObserver layerObserver
property string requestedProjectDetails: ""
Expand Down Expand Up @@ -471,6 +472,7 @@ Page {
projectActions.projectLocalPath = LocalPath;
downloadProject.visible = LocalPath === '' && Status !== QFieldCloudProject.ProjectStatus.Downloading;
openProject.visible = LocalPath !== '';
viewProjectFolder.visible = LocalPath !== '';
removeProject.visible = LocalPath !== '';
cancelDownloadProject.visible = Status === QFieldCloudProject.ProjectStatus.Downloading;
projectActions.popup(gc.x + width - projectActions.width, gc.y - height);
Expand Down Expand Up @@ -942,6 +944,20 @@ Page {
}
}

MenuItem {
id: viewProjectFolder

font: Theme.defaultFont
width: parent.width
height: visible ? 48 : 0
leftPadding: Theme.menuItemLeftPadding

text: qsTr("View Project Folder")
onTriggered: {
qfieldCloudScreen.viewProjectFolder(projectActions.projectLocalPath);
}
}

MenuItem {
id: removeProject

Expand All @@ -952,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();
}
}

Expand All @@ -974,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
Expand Down
50 changes: 43 additions & 7 deletions src/qml/QFieldLocalDataPickerScreen.qml
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,23 @@ Page {
bottomMargin: sceneBottomMargin
paddingMultiplier: 2

MenuItem {
id: viewFile

enabled: itemMenu.itemMetaType != LocalFilesModel.Folder
visible: enabled

font: Theme.defaultFont
width: parent.width
height: enabled ? 48 : 0
leftPadding: Theme.menuItemLeftPadding

text: qsTr("View file")
onTriggered: {
platformUtilities.open(itemMenu.itemPath);
}
}

// File items
MenuItem {
id: sendDatasetTo
Expand All @@ -464,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
Expand All @@ -474,9 +491,9 @@ 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)));
pushFilesToQFieldCloudConnection.enabled = true;
pushFilesToQFieldCloudConnection.sendingMultiple = true;
QFieldCloudUtils.addPendingAttachments(cloudConnection.userInformation.username, QFieldCloudUtils.getProjectId(table.model.currentPath), [itemMenu.itemPath], cloudConnection, true);
}
}

Expand Down Expand Up @@ -862,9 +879,8 @@ Page {
}
}
if (fileNames.length > 0) {
QFieldCloudUtils.addPendingAttachments(projectInfo.cloudUserInformation.username, cloudProjectsModel.currentProjectId, fileNames, cloudConnection, true);
platformUtilities.uploadPendingAttachments(cloudConnection);
localFilesModel.clearSelection();
pushFilesToQFieldCloudConnection.enabled = 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."));
}
Expand All @@ -873,6 +889,26 @@ Page {
}
}

Connections {
id: pushFilesToQFieldCloudConnection
enabled: false
target: cloudConnection

property bool sendingMultiple: false

function onPendingAttachmentsAdded() {
platformUtilities.uploadPendingAttachments(cloudConnection);
if (pushFilesToQFieldCloudConnection.sendingMultiple) {
displayToast(qsTr("‘%1’ is being uploaded to QFieldCloud").arg(FileUtils.fileName(itemMenu.itemPath)));
pushFilesToQFieldCloudConnection.sendingMultiple = false;
} else {
localFilesModel.clearSelection();
displayToast(qsTr("Items being uploaded to QFieldCloud"));
}
pushFilesToQFieldCloudConnection.enabled = false;
}
}

QfDialog {
id: importUrlDialog
title: qsTr("Import URL")
Expand Down
6 changes: 6 additions & 0 deletions src/qml/qgismobileapp.qml
Original file line number Diff line number Diff line change
Expand Up @@ -4359,6 +4359,12 @@ ApplicationWindow {
}

Component.onCompleted: focusstack.addFocusTaker(this)

onViewProjectFolder: projectPath => {
qfieldLocalDataPickerScreen.projectFolderView = true;
qfieldLocalDataPickerScreen.model.resetToPath(projectPath);
qfieldLocalDataPickerScreen.visible = true;
}
}

QFieldCloudPopup {
Expand Down
Loading