From bf4f94c081a61f920bf5a26837fd6f1da7c9c053 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Thu, 12 Mar 2026 16:05:01 +0200 Subject: [PATCH 1/2] Added modifications --- app/notificationmodel.cpp | 6 ++++++ app/notificationmodel.h | 4 +++- app/qml/main.qml | 21 ++++++++++++++++++ app/synchronizationmanager.cpp | 1 + core/merginapi.cpp | 39 ++++++++++++++++++++++++++++++++++ core/merginapi.h | 8 +++++++ 6 files changed, 78 insertions(+), 1 deletion(-) diff --git a/app/notificationmodel.cpp b/app/notificationmodel.cpp index d5fc1c74a..247ab6f57 100644 --- a/app/notificationmodel.cpp +++ b/app/notificationmodel.cpp @@ -156,6 +156,12 @@ void NotificationModel::onNotificationClicked( uint id ) emit showSyncFailedDialogClicked(); break; } + case NotificationType::ActionType::ShowProjectNewVersionAction: + { + remove( id ); + emit showProjectNewVersionClicked(); + break; + } default: break; } } diff --git a/app/notificationmodel.h b/app/notificationmodel.h index 91711029c..76420f323 100644 --- a/app/notificationmodel.h +++ b/app/notificationmodel.h @@ -43,7 +43,8 @@ class NotificationType NoAction, ShowProjectIssuesAction, ShowSwitchWorkspaceAction, - ShowSyncFailedDialog + ShowSyncFailedDialog, + ShowProjectNewVersionAction }; Q_ENUM( ActionType ) @@ -111,6 +112,7 @@ class NotificationModel : public QAbstractListModel void rowCountChanged(); void showProjectIssuesActionClicked(); void showSwitchWorkspaceActionClicked(); + void showProjectNewVersionClicked(); void showSyncFailedDialogClicked(); private: diff --git a/app/qml/main.qml b/app/qml/main.qml index d417de897..7b518cb77 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -84,6 +84,10 @@ ApplicationWindow { // Stop/Start sync animation when user goes to map syncButton.iconRotateAnimationRunning = ( __syncManager.hasPendingSync( __activeProject.projectFullName() ) ) + + if ( __activeProject.projectFullName() !== "" ) { + __merginApi.isProjectSyncNeeded( __activeProject.projectFullName(), true ) + } } else if ( stateManager.state === "projects" ) { projectController.openPanel() @@ -1090,6 +1094,17 @@ ApplicationWindow { { ssoExpiredTokenDialog.open() } + + function onProjectSyncRequired( projectFullName ) + { + if ( __activeProject.projectFullName() === projectFullName ) + { + __notificationModel.addInfo( + __inputUtils.htmlLink( qsTr( "There is a new version of the project available" ), __style.forestColor ), + MM.NotificationType.ShowProjectNewVersionAction + ) + } + } } Connections { @@ -1112,6 +1127,9 @@ ApplicationWindow { function onShowSyncFailedDialogClicked() { syncFailedDialog.open() } + function onShowProjectNewVersionClicked() { + __activeProject.requestSync() + } } Connections { @@ -1159,6 +1177,9 @@ ApplicationWindow { AppSettings.defaultProject = __activeProject.localProject.qgisProjectFilePath ?? "" AppSettings.activeProject = __activeProject.localProject.qgisProjectFilePath ?? "" + if ( __activeProject.projectFullName() !== "" ) { + __merginApi.isProjectSyncNeeded( __activeProject.projectFullName(), true ) + } } function onProjectWillBeReloaded() { diff --git a/app/synchronizationmanager.cpp b/app/synchronizationmanager.cpp index f5322de7c..a65a70eca 100644 --- a/app/synchronizationmanager.cpp +++ b/app/synchronizationmanager.cpp @@ -328,3 +328,4 @@ void SynchronizationManager::onProjectReloadNeededAfterSync( const QString &proj } } + diff --git a/core/merginapi.cpp b/core/merginapi.cpp index ab91e0611..20e2ebd45 100644 --- a/core/merginapi.cpp +++ b/core/merginapi.cpp @@ -4597,3 +4597,42 @@ bool MerginApi::serverVersionIsAtLeast( const int requiredMajor, const int requi // check patch return serverPatch >= requiredPatch; } + +void MerginApi::isProjectSyncNeededFinished() +{ + QNetworkReply *r = qobject_cast( sender() ); + Q_ASSERT( r ); + + QString projectFullName = r->request().attribute( static_cast( AttrProjectFullName ) ).toString(); + mPendingSyncChecks.remove( projectFullName ); + + if ( r->error() == QNetworkReply::NoError ) + { + QByteArray data = r->readAll(); + MerginProjectMetadata serverProject = MerginProjectMetadata::fromJson( data ); + + // Skip if a sync is already in progress for this project + if ( !mTransactionalStatus.contains( projectFullName ) ) + { + LocalProject projectInfo = mLocalProjects.projectFromMerginName( projectFullName ); + if ( projectInfo.isValid() && projectInfo.localVersion != -1 && projectInfo.localVersion < serverProject.version ) + { + emit projectSyncRequired( projectFullName ); + } + } + } + r->deleteLater(); +} + +void MerginApi::isProjectSyncNeeded( const QString &projectFullName, bool withAuth ) +{ + if ( mPendingSyncChecks.contains( projectFullName ) ) + return; + + QNetworkReply *reply = getProjectInfo( projectFullName, withAuth ); + if ( !reply ) + return; + + mPendingSyncChecks.insert( projectFullName ); + connect( reply, &QNetworkReply::finished, this, &MerginApi::isProjectSyncNeededFinished ); +} diff --git a/core/merginapi.h b/core/merginapi.h index 202e042c6..d94a1664e 100644 --- a/core/merginapi.h +++ b/core/merginapi.h @@ -645,6 +645,8 @@ class MerginApi: public QObject */ Q_INVOKABLE void reloadProjectRole( const QString &projectFullName ); + Q_INVOKABLE void isProjectSyncNeeded( const QString &projectFullName, bool withAuth ); + /** * Returns the network manager used for Mergin API requests */ @@ -689,6 +691,7 @@ class MerginApi: public QObject void listProjectsFinished( const MerginProjectsList &merginProjects, int projectCount, int page, QString requestId ); void listProjectsFailed(); + void projectSyncRequired( const QString &projectFullName ); void listProjectsByNameFinished( const MerginProjectsList &merginProjects, QString requestId ); void syncProjectFinished( const QString &projectFullName, bool successfully, int version ); void projectReloadNeededAfterSync( const QString &projectFullName ); @@ -777,12 +780,15 @@ class MerginApi: public QObject void userSelfRegistrationEnabledChanged(); + private slots: void listProjectsReplyFinished( QString requestId ); void listProjectsByNameReplyFinished( QString requestId ); // Pull slots void pullInfoReplyFinished(); + void isProjectSyncNeededFinished(); + void downloadItemReplyFinished( DownloadQueueItem item ); void cacheServerConfig(); @@ -979,6 +985,8 @@ class MerginApi: public QObject MerginServerType::ServerType mServerType = MerginServerType::ServerType::OLD; QString mServerDiagnosticLogsUrl = MerginApi::sDefaultReportLogUrl; + QSet mPendingSyncChecks; //!< projects with an in-flight isProjectSyncNeeded request + QOAuth2AuthorizationCodeFlow mOauth2Flow; #ifdef MOBILE_OS QOAuthUriSchemeReplyHandler *mOauth2ReplyHandler = nullptr; // parented by mOauth2Flow From 09be596647fbdf731581f6eb189e2e43f6fe9c9f Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Thu, 12 Mar 2026 16:13:54 +0200 Subject: [PATCH 2/2] Small changes --- app/synchronizationmanager.cpp | 1 - core/merginapi.cpp | 2 +- core/merginapi.h | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/synchronizationmanager.cpp b/app/synchronizationmanager.cpp index a65a70eca..f5322de7c 100644 --- a/app/synchronizationmanager.cpp +++ b/app/synchronizationmanager.cpp @@ -328,4 +328,3 @@ void SynchronizationManager::onProjectReloadNeededAfterSync( const QString &proj } } - diff --git a/core/merginapi.cpp b/core/merginapi.cpp index 20e2ebd45..dceec9b0a 100644 --- a/core/merginapi.cpp +++ b/core/merginapi.cpp @@ -4611,7 +4611,7 @@ void MerginApi::isProjectSyncNeededFinished() QByteArray data = r->readAll(); MerginProjectMetadata serverProject = MerginProjectMetadata::fromJson( data ); - // Skip if a sync is already in progress for this project + // skip if a sync is already in progress for this project if ( !mTransactionalStatus.contains( projectFullName ) ) { LocalProject projectInfo = mLocalProjects.projectFromMerginName( projectFullName ); diff --git a/core/merginapi.h b/core/merginapi.h index d94a1664e..8486a204e 100644 --- a/core/merginapi.h +++ b/core/merginapi.h @@ -780,7 +780,6 @@ class MerginApi: public QObject void userSelfRegistrationEnabledChanged(); - private slots: void listProjectsReplyFinished( QString requestId ); void listProjectsByNameReplyFinished( QString requestId ); @@ -985,7 +984,7 @@ class MerginApi: public QObject MerginServerType::ServerType mServerType = MerginServerType::ServerType::OLD; QString mServerDiagnosticLogsUrl = MerginApi::sDefaultReportLogUrl; - QSet mPendingSyncChecks; //!< projects with an in-flight isProjectSyncNeeded request + QSet mPendingSyncChecks; // not yet updated projects with isProjectSyncNeeded request QOAuth2AuthorizationCodeFlow mOauth2Flow; #ifdef MOBILE_OS