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/core/merginapi.cpp b/core/merginapi.cpp index ab91e0611..dceec9b0a 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..8486a204e 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 ); @@ -783,6 +786,8 @@ class MerginApi: public QObject // Pull slots void pullInfoReplyFinished(); + void isProjectSyncNeededFinished(); + void downloadItemReplyFinished( DownloadQueueItem item ); void cacheServerConfig(); @@ -979,6 +984,8 @@ class MerginApi: public QObject MerginServerType::ServerType mServerType = MerginServerType::ServerType::OLD; QString mServerDiagnosticLogsUrl = MerginApi::sDefaultReportLogUrl; + QSet mPendingSyncChecks; // not yet updated projects with isProjectSyncNeeded request + QOAuth2AuthorizationCodeFlow mOauth2Flow; #ifdef MOBILE_OS QOAuthUriSchemeReplyHandler *mOauth2ReplyHandler = nullptr; // parented by mOauth2Flow