diff --git a/app/activeproject.cpp b/app/activeproject.cpp index acfb53093..39d855cb3 100644 --- a/app/activeproject.cpp +++ b/app/activeproject.cpp @@ -31,6 +31,7 @@ ActiveProject::ActiveProject( AppSettings &appSettings , ActiveLayer &activeLayer , LayersProxyModel &recordingLayerPM , LocalProjectsManager &localProjectsManager + , MerginApi *merginApi , QObject *parent ) : QObject( parent ) @@ -38,6 +39,7 @@ ActiveProject::ActiveProject( AppSettings &appSettings , mActiveLayer( activeLayer ) , mRecordingLayerPM( recordingLayerPM ) , mLocalProjectsManager( localProjectsManager ) + , mMerginApi( merginApi ) , mProjectLoadingLog( "" ) { // we used to have our own QgsProject instance, but unfortunately few pieces of qgis_core @@ -95,6 +97,7 @@ QString ActiveProject::projectFullName() const bool ActiveProject::load( const QString &filePath ) { + updateProjectMetadata(); return forceLoad( filePath, false ); } @@ -553,3 +556,68 @@ bool ActiveProject::positionTrackingSupported() const return mQgsProject->readBoolEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PositionTracking/Enabled" ), false ); } + +bool ActiveProject::updateProjectMetadata() +{ + if ( !mMerginApi ) + { + return false; + } + + QNetworkReply *reply = mMerginApi->getProjectInfo( projectFullName() ); + if ( !reply ) + { + return false; + } + + reply->request().setAttribute( static_cast( mMerginApi->AttrProjectFullName ), projectFullName() ); + + connect( reply, &QNetworkReply::finished, this, &ActiveProject::updateProjectMetadataReplyFinished ); + + return true; +} + +void ActiveProject::updateProjectMetadataReplyFinished() +{ + QNetworkReply *r = qobject_cast( sender() ); + Q_ASSERT( r ); + + QString projectFullName = r->request().attribute( static_cast( mMerginApi->AttrProjectFullName ) ).toString(); + + if ( r->error() == QNetworkReply::NoError ) + { + QByteArray data = r->readAll(); + + MerginProjectMetadata serverProject = MerginProjectMetadata::fromJson( data ); + + QString role = serverProject.role; + setProjectRole( role ); + } + else + { + QString serverMsg = mMerginApi->extractServerErrorMsg( r->readAll() ); + + QString projectDir = mQgsProject->absolutePath(); + MerginProjectMetadata cachedProjectMetadata = MerginProjectMetadata::fromCachedJson( mLocalProject.projectDir + "/" + mMerginApi->sMetadataFile ); + + QString role = cachedProjectMetadata.role; + setProjectRole( role ); + } + + r->deleteLater(); +} + +QString ActiveProject::projectRole() const +{ + return mProjectRole; +} + +void ActiveProject::setProjectRole( const QString &role ) +{ + if ( mProjectRole != role ) + { + mProjectRole = role; + + emit projectRoleChanged(); + } +} diff --git a/app/activeproject.h b/app/activeproject.h index b775303f6..54b2de4e5 100644 --- a/app/activeproject.h +++ b/app/activeproject.h @@ -22,6 +22,7 @@ #include "localprojectsmanager.h" #include "autosynccontroller.h" #include "inputmapsettings.h" +#include "../core/merginapi.h" /** * \brief The ActiveProject class can load a QGIS project and holds its data. @@ -33,6 +34,7 @@ class ActiveProject: public QObject Q_PROPERTY( QgsProject *qgsProject READ qgsProject NOTIFY qgsProjectChanged ) // QgsProject instance of active project, never changes Q_PROPERTY( AutosyncController *autosyncController READ autosyncController NOTIFY autosyncControllerChanged ) Q_PROPERTY( InputMapSettings *mapSettings READ mapSettings WRITE setMapSettings NOTIFY mapSettingsChanged ) + Q_PROPERTY( QString projectRole READ projectRole WRITE setProjectRole NOTIFY projectRoleChanged ) Q_PROPERTY( QString mapTheme READ mapTheme WRITE setMapTheme NOTIFY mapThemeChanged ) Q_PROPERTY( bool positionTrackingSupported READ positionTrackingSupported NOTIFY positionTrackingSupportedChanged ) @@ -43,6 +45,7 @@ class ActiveProject: public QObject , ActiveLayer &activeLayer , LayersProxyModel &recordingLayerPM , LocalProjectsManager &localProjectsManager + , MerginApi *mMerginApi , QObject *parent = nullptr ); virtual ~ActiveProject(); @@ -118,6 +121,18 @@ class ActiveProject: public QObject bool positionTrackingSupported() const; + /** + * Returns role/permission level of current user for this project + */ + Q_INVOKABLE QString projectRole() const; + void setProjectRole( const QString &role ); + + /** + * Creates a network request to fetch latest project information and define user role in this project + */ + Q_INVOKABLE bool updateProjectMetadata(); + void updateProjectMetadataReplyFinished(); + signals: void qgsProjectChanged(); void localProjectChanged( LocalProject project ); @@ -145,6 +160,8 @@ class ActiveProject: public QObject // Emited when the app (UI) should show tracking because there is a running tracking service void startPositionTracking(); + void projectRoleChanged(); + public slots: // Reloads project if current project path matches given path (its the same project) bool reloadProject( QString projectDir ); @@ -182,10 +199,12 @@ class ActiveProject: public QObject LayersProxyModel &mRecordingLayerPM; LocalProjectsManager &mLocalProjectsManager; InputMapSettings *mMapSettings = nullptr; + MerginApi *mMerginApi = nullptr; std::unique_ptr mAutosyncController; QString mProjectLoadingLog; + QString mProjectRole; /** * Reloads project. diff --git a/app/inpututils.cpp b/app/inpututils.cpp index 9f6328953..218c024e1 100644 --- a/app/inpututils.cpp +++ b/app/inpututils.cpp @@ -2222,14 +2222,3 @@ double InputUtils::pixelDistanceBetween( const QPointF &p1, const QPointF &p2 ) { return std::hypot( p1.x() - p2.x(), p1.y() - p2.y() ); } - -bool InputUtils::userHasEditableRole( QgsProject *project ) -{ - const QString &projectDir = project->absolutePath(); - QString metadataFilePath = projectDir + "/" + MerginApi::sMetadataFile; - MerginProjectMetadata metadata = MerginProjectMetadata::fromCachedJson( metadataFilePath ); - QString role = metadata.role; - - bool hasEditableRole = role != "reader" && role != "guest"; - return hasEditableRole; -} diff --git a/app/inpututils.h b/app/inpututils.h index 2bfd4066c..83be81f39 100644 --- a/app/inpututils.h +++ b/app/inpututils.h @@ -589,11 +589,6 @@ class InputUtils: public QObject */ static double pixelDistanceBetween( const QPointF &p1, const QPointF &p2 ); - /** - * Checks if the user can edit the project - */ - Q_INVOKABLE static bool userHasEditableRole( QgsProject *project ); - public slots: void onQgsLogMessageReceived( const QString &message, const QString &tag, Qgis::MessageLevel level ); diff --git a/app/main.cpp b/app/main.cpp index 2ff044e83..7e87609b0 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -494,7 +494,7 @@ int main( int argc, char *argv[] ) LayersProxyModel recordingLpm( &lm, LayerModelTypes::ActiveLayerSelection ); ActiveLayer al; - ActiveProject activeProject( as, al, recordingLpm, localProjectsManager ); + ActiveProject activeProject( as, al, recordingLpm, localProjectsManager, ma.get() ); std::unique_ptr vm( new VariablesManager( ma.get() ) ); vm->registerInputExpressionFunctions(); diff --git a/app/qml/main.qml b/app/qml/main.qml index 6aca0ff70..4cb127ddb 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -276,7 +276,7 @@ ApplicationWindow { MMToolbarButton { text: qsTr("Add") iconSource: __style.addIcon - visible: __inputUtils.userHasEditableRole( __activeProject.qgsProject ) + visible: __activeProject.projectRole !== "reader" onClicked: { if ( __recordingLayersModel.rowCount() > 0 ) { stateManager.state = "map" @@ -388,7 +388,6 @@ ApplicationWindow { } onOpenProjectRequested: function( projectPath ) { - __merginApi.updateProjectMetadata( projectPath ) //HERE __activeProject.load( projectPath ) } diff --git a/app/test/testactiveproject.cpp b/app/test/testactiveproject.cpp index 8b14abd24..4e6edf70c 100644 --- a/app/test/testactiveproject.cpp +++ b/app/test/testactiveproject.cpp @@ -39,7 +39,7 @@ void TestActiveProject::testProjectValidations() ActiveLayer al; LayersModel lm; LayersProxyModel lpm( &lm, LayerModelTypes::ActiveLayerSelection ); - ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager() ); + ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager(), mApi ); QSignalSpy spyReportIssues( &activeProject, &ActiveProject::reportIssue ); QSignalSpy spyErrorsFound( &activeProject, &ActiveProject::loadingErrorFound ); @@ -66,7 +66,7 @@ void TestActiveProject::testProjectLoadFailure() ActiveLayer al; LayersModel lm; LayersProxyModel lpm( &lm, LayerModelTypes::ActiveLayerSelection ); - ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager() ); + ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager(), mApi ); mApi->localProjectsManager().addLocalProject( projectdir, projectname ); @@ -88,7 +88,7 @@ void TestActiveProject::testPositionTrackingFlag() ActiveLayer al; LayersModel lm; LayersProxyModel lpm( &lm, LayerModelTypes::ActiveLayerSelection ); - ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager() ); + ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager(), mApi ); // project "planes" - tracking not enabled QString projectDir = TestUtils::testDataDir() + "/planes/"; diff --git a/app/test/testmerginapi.cpp b/app/test/testmerginapi.cpp index 815563bef..5ec9f37a0 100644 --- a/app/test/testmerginapi.cpp +++ b/app/test/testmerginapi.cpp @@ -2369,7 +2369,7 @@ void TestMerginApi::testAutosync() MapThemesModel mtm; AppSettings as; ActiveLayer al; LayersModel lm; LayersProxyModel lpm( &lm, LayerModelTypes::ActiveLayerSelection ); - ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager() ); + ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager(), mApi ); mApi->localProjectsManager().addLocalProject( projectdir, projectname ); diff --git a/core/merginapi.cpp b/core/merginapi.cpp index eac8749e4..c2f40bd23 100644 --- a/core/merginapi.cpp +++ b/core/merginapi.cpp @@ -1404,7 +1404,6 @@ void MerginApi::onPlanProductIdChanged() QNetworkReply *MerginApi::getProjectInfo( const QString &projectFullName, bool withAuth ) { - qDebug() << "HERE getProjectInfo 1: " << projectFullName; if ( withAuth && !validateAuth() ) { emit missingAuthorizationError( projectFullName ); @@ -3947,51 +3946,3 @@ DownloadQueueItem::DownloadQueueItem( const QString &fp, qint64 s, int v, qint64 tempFileName = CoreUtils::uuidWithoutBraces( QUuid::createUuid() ); } -bool MerginApi::updateProjectMetadata(const QString &filePath) -{ - qDebug() << "Entering updateProjectMetadata with filePath:" << filePath; - - // Extract the project directory from the file path - QFileInfo fileInfo(filePath); - QString projectDir = fileInfo.absolutePath(); - qDebug() << "Extracted projectDir:" << projectDir; - - // Try to find the project from local projects using directory - LocalProject projectInfo = mLocalProjects.projectFromDirectory(projectDir); - if (!projectInfo.isValid()) - { - qDebug() << "Could not find local project for path:" << filePath; - CoreUtils::log("update metadata", "Could not find local project for path: " + filePath); - return false; - } - qDebug() << "Found local project. Namespace:" << projectInfo.projectNamespace - << "Name:" << projectInfo.projectName; - - // Get the full project name from the local project info - QString projectFullName = getFullProjectName(projectInfo.projectNamespace, projectInfo.projectName); - qDebug() << "Full project name:" << projectFullName; - - QNetworkReply *r = getProjectInfo(projectFullName, true); - if (!r) - { - qDebug() << "Network reply is null for project name:" << projectFullName; - return false; - } - - qDebug() << "Network reply received. Checking for errors..."; - if (r->error() == QNetworkReply::NoError) - { - QByteArray data = r->readAll(); - qDebug() << "Received data:" << data; - MerginProjectMetadata serverProject = MerginProjectMetadata::fromJson(data); - qDebug() << "Parsed server project metadata successfully."; - return true; - } - else - { - qDebug() << "Network reply error:" << r->errorString(); - return false; - } -} - - diff --git a/core/merginapi.h b/core/merginapi.h index b198a5a00..9fc47ac64 100644 --- a/core/merginapi.h +++ b/core/merginapi.h @@ -573,7 +573,23 @@ class MerginApi: public QObject */ bool apiSupportsWorkspaces(); - Q_INVOKABLE bool updateProjectMetadata( const QString &filePath ); + /** Creates a request to get project details (list of project files). + */ + QNetworkReply *getProjectInfo( const QString &projectFullName, bool withAuth = true ); + + enum CustomAttribute + { + AttrProjectFullName = QNetworkRequest::User, + AttrTempFileName = QNetworkRequest::User + 1, + AttrWorkspaceName = QNetworkRequest::User + 2, + AttrAcceptFlag = QNetworkRequest::User + 3, + }; + + /** + * Extracts detail (message) of an error json. If its not json or detail cannot be parsed, the whole data are return; + * \param data Data received from mergin server on a request failed. + */ + QString extractServerErrorMsg( const QByteArray &data ); signals: void apiSupportsSubscriptionsChanged(); @@ -746,20 +762,11 @@ class MerginApi: public QObject */ QVariant extractServerErrorValue( const QByteArray &data, const QString &key ); /** - * Extracts detail (message) of an error json. If its not json or detail cannot be parsed, the whole data are return; - * \param data Data received from mergin server on a request failed. - */ - QString extractServerErrorMsg( const QByteArray &data ); - /** * Returns a temporary project path. * \param projectFullName */ QString getTempProjectDir( const QString &projectFullName ); - /** Creates a request to get project details (list of project files). - */ - QNetworkReply *getProjectInfo( const QString &projectFullName, bool withAuth = true ); - //! Called when pull of project data has finished to finalize things and emit sync finished signal void finalizeProjectPull( const QString &projectFullName ); @@ -801,14 +808,6 @@ class MerginApi: public QObject MerginSubscriptionInfo *mSubscriptionInfo; //owned by this (qml grouped-properties) MerginUserAuth *mUserAuth; //owned by this (qml grouped-properties) - enum CustomAttribute - { - AttrProjectFullName = QNetworkRequest::User, - AttrTempFileName = QNetworkRequest::User + 1, - AttrWorkspaceName = QNetworkRequest::User + 2, - AttrAcceptFlag = QNetworkRequest::User + 3, - }; - Transactions mTransactionalStatus; //projectFullname -> transactionStatus static const QSet sIgnoreExtensions; static const QSet sIgnoreImageExtensions;