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
12 changes: 9 additions & 3 deletions app/activeproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force )
if ( mAppSettings.autosyncAllowed() )
{
setAutosyncEnabled( true );
requestSync( SyncOptions::AutomaticRequest );
}

// in case tracking is running, we want to show the UI
Expand Down Expand Up @@ -329,7 +330,8 @@ void ActiveProject::setAutosyncEnabled( bool enabled )

mAutosyncController = std::make_unique<AutosyncController>( mQgsProject );

connect( mAutosyncController.get(), &AutosyncController::projectChangeDetected, this, &ActiveProject::requestSync );
connect( mAutosyncController.get(), &AutosyncController::projectSyncRequested, this, &ActiveProject::requestSync );
connect( this, &ActiveProject::appStateChanged, mAutosyncController.get(), &AutosyncController::checkSyncRequiredAfterAppStateChange );
}
else
{
Expand All @@ -341,9 +343,13 @@ void ActiveProject::setAutosyncEnabled( bool enabled )
}
}

void ActiveProject::requestSync()
void ActiveProject::requestSync( const SyncOptions::RequestOrigin requestOrigin )
{
emit syncActiveProject( mLocalProject );
if ( requestOrigin == SyncOptions::RequestOrigin::ManualRequest )
{
mAutosyncController->updateLastUpdateTime();
}
emit syncActiveProject( mLocalProject, requestOrigin );
}

void ActiveProject::setMapSettings( InputMapSettings *mapSettings )
Expand Down
7 changes: 5 additions & 2 deletions app/activeproject.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "autosynccontroller.h"
#include "inputmapsettings.h"
#include "merginprojectmetadata.h"
#include "synchronizationoptions.h"

/**
* \brief The ActiveProject class can load a QGIS project and holds its data.
Expand Down Expand Up @@ -165,7 +166,7 @@ class ActiveProject: public QObject

void mapSettingsChanged();

void syncActiveProject( const LocalProject &project );
void syncActiveProject( const LocalProject &project, SyncOptions::RequestOrigin requestOrigin );

void mapThemeChanged( const QString &mapTheme );

Expand All @@ -180,13 +181,15 @@ class ActiveProject: public QObject

void photoSketchingEnabledChanged();

void appStateChanged( Qt::ApplicationState state );

public slots:
// Reloads project if current project path matches given path (it's the same project)
bool reloadProject( QString projectDir );

void setAutosyncEnabled( bool enabled );

void requestSync();
void requestSync( SyncOptions::RequestOrigin requestOrigin = SyncOptions::RequestOrigin::ManualRequest );

private:

Expand Down
45 changes: 43 additions & 2 deletions app/autosynccontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@
#include "qgsproject.h"
#include "qgsvectorlayer.h"

// 1 minute
constexpr int SYNC_INTERVAL = 60000;
// 10 seconds
constexpr int SYNC_CHECK_TIMEOUT = 10000;

AutosyncController::AutosyncController(
QgsProject *openedQgsProject,
QObject *parent
)
: QObject( parent )
, mQgsProject( openedQgsProject )
// set to current timestamp as we sync with project open
, mLastUpdateTime( QDateTime::currentDateTime() )
{
if ( !mQgsProject )
{
Expand All @@ -35,10 +42,44 @@ AutosyncController::AutosyncController(
{
if ( !vecLayer->readOnly() )
{
QObject::connect( vecLayer, &QgsVectorLayer::afterCommitChanges, this, &AutosyncController::projectChangeDetected );
connect( vecLayer, &QgsVectorLayer::afterCommitChanges, this, [&]
{
mLastUpdateTime = QDateTime::currentDateTime();
emit projectSyncRequested( SyncOptions::RequestOrigin::AutomaticRequest );
} );
}
}
}

//every 10 seconds check if last sync was a 60 seconds or more ago and sync if it's true
mTimer = std::make_unique<QTimer>( this );
connect( mTimer.get(), &QTimer::timeout, this, [&]
{
if ( QDateTime::currentDateTime() - mLastUpdateTime >= std::chrono::milliseconds( SYNC_INTERVAL ) )
{
mLastUpdateTime = QDateTime::currentDateTime();
emit projectSyncRequested( SyncOptions::RequestOrigin::AutomaticRequest );
}
} );
mTimer->start( SYNC_CHECK_TIMEOUT );
}

void AutosyncController::updateLastUpdateTime()
{
mLastUpdateTime = QDateTime::currentDateTime();
}

AutosyncController::~AutosyncController() = default;
void AutosyncController::checkSyncRequiredAfterAppStateChange( const Qt::ApplicationState state )
{
if ( state != Qt::ApplicationState::ApplicationActive )
{
mTimer->stop();
return;
}
const bool isLongerThanSyncInterval = QDateTime::currentDateTime() - mLastUpdateTime >= std::chrono::milliseconds( SYNC_INTERVAL );
if ( isLongerThanSyncInterval )
{
mLastUpdateTime = QDateTime::currentDateTime();
emit projectSyncRequested( SyncOptions::RequestOrigin::AutomaticRequest );
}
}
16 changes: 13 additions & 3 deletions app/autosynccontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
#ifndef AUTOSYNCCONTROLLER_H
#define AUTOSYNCCONTROLLER_H

#include <QObject>
#include <QDateTime>
#include <QTimer>

#include "synchronizationoptions.h"

class QgsProject;

Expand All @@ -21,15 +24,22 @@ class AutosyncController : public QObject
public:

explicit AutosyncController( QgsProject *openedQgsProject, QObject *parent = nullptr );
virtual ~AutosyncController();
~AutosyncController() override = default;

// Set mLastUpdateTime to "now", triggered by manual sync
void updateLastUpdateTime();

signals:
void projectSyncRequested( SyncOptions::RequestOrigin origin );

void projectChangeDetected();
public slots:
void checkSyncRequiredAfterAppStateChange( Qt::ApplicationState state );

private:

QgsProject *mQgsProject = nullptr; // not owned
QDateTime mLastUpdateTime;
std::unique_ptr<QTimer> mTimer = nullptr;
};

#endif // AUTOSYNCCONTROLLER_H
5 changes: 3 additions & 2 deletions app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -603,9 +603,9 @@ int main( int argc, char *argv[] )
notificationModel.addError( message );
} );

QObject::connect( &activeProject, &ActiveProject::syncActiveProject, &syncManager, [&syncManager]( const LocalProject & project )
QObject::connect( &activeProject, &ActiveProject::syncActiveProject, &syncManager, [&syncManager]( const LocalProject & project, const SyncOptions::RequestOrigin requestOrigin )
{
syncManager.syncProject( project, SyncOptions::Authorized, SyncOptions::Retry );
syncManager.syncProject( project, SyncOptions::Authorized, SyncOptions::Retry, requestOrigin );
} );

QObject::connect( &activeProject, &ActiveProject::projectReloaded, &lambdaContext, [merginApi = ma.get(), &activeProject]()
Expand Down Expand Up @@ -649,6 +649,7 @@ int main( int argc, char *argv[] )
} );
// Direct connections
QObject::connect( &app, &QGuiApplication::applicationStateChanged, &pk, &PositionKit::appStateChanged );
QObject::connect( &app, &QGuiApplication::applicationStateChanged, &activeProject, &ActiveProject::appStateChanged );
QObject::connect( &pw, &ProjectWizard::projectCreated, &localProjectsManager, &LocalProjectsManager::addLocalProject );
QObject::connect( &activeProject, &ActiveProject::projectReloaded, vm.get(), &VariablesManager::merginProjectChanged );
QObject::connect( &activeProject, &ActiveProject::projectWillBeReloaded, &inputProjUtils, &InputProjUtils::resetHandlers );
Expand Down
16 changes: 8 additions & 8 deletions app/qml/main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,14 @@ ApplicationWindow {
}
}
}

function onProjectAlreadyOnLatestVersion( projectFullName )
{
if ( projectFullName === __activeProject.projectFullName() )
{
__notificationModel.addSuccess( qsTr( "Up to date" ) )
}
}
}

Connections {
Expand Down Expand Up @@ -1050,14 +1058,6 @@ ApplicationWindow {
}
}

function onProjectAlreadyOnLatestVersion( projectFullName )
{
if ( projectFullName === __activeProject.projectFullName() )
{
__notificationModel.addSuccess( qsTr( "Up to date" ) )
}
}

function onProjectCreationFailed()
{
syncButton.iconRotateAnimationRunning = false
Expand Down
2 changes: 1 addition & 1 deletion app/qml/settings/MMSettingsPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ MMPage {
MMSettingsComponents.MMSettingsSwitch {
width: parent.width
title: qsTr("Automatically sync changes")
description: qsTr("Each time you save changes, the app will sync automatically")
description: qsTr("Your project is synced automatically to keep you up to date")
checked: AppSettings.autosyncAllowed

onClicked: AppSettings.autosyncAllowed = !checked
Expand Down
57 changes: 37 additions & 20 deletions app/synchronizationmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,32 @@ SynchronizationManager::SynchronizationManager(
{
if ( mMerginApi )
{
QObject::connect( mMerginApi, &MerginApi::pushCanceled, this, &SynchronizationManager::onProjectSyncCanceled );
QObject::connect( mMerginApi, &MerginApi::syncProjectFinished, this, &SynchronizationManager::onProjectSyncFinished );
QObject::connect( mMerginApi, &MerginApi::networkErrorOccurred, this, &SynchronizationManager::onProjectSyncFailure );
QObject::connect( mMerginApi, &MerginApi::projectCreated, this, &SynchronizationManager::onProjectCreated );
QObject::connect( mMerginApi, &MerginApi::projectAttachedToMergin, this, &SynchronizationManager::onProjectAttachedToMergin );
QObject::connect( mMerginApi, &MerginApi::syncProjectStatusChanged, this, &SynchronizationManager::onProjectSyncProgressChanged );
QObject::connect( mMerginApi, &MerginApi::projectReloadNeededAfterSync, this, &SynchronizationManager::onProjectReloadNeededAfterSync );
connect( mMerginApi, &MerginApi::pushCanceled, this, &SynchronizationManager::onProjectSyncCanceled );
connect( mMerginApi, &MerginApi::syncProjectFinished, this, &SynchronizationManager::onProjectSyncFinished );
connect( mMerginApi, &MerginApi::networkErrorOccurred, this, &SynchronizationManager::onProjectSyncFailure );
connect( mMerginApi, &MerginApi::projectCreated, this, &SynchronizationManager::onProjectCreated );
connect( mMerginApi, &MerginApi::projectAttachedToMergin, this, &SynchronizationManager::onProjectAttachedToMergin );
connect( mMerginApi, &MerginApi::syncProjectStatusChanged, this, &SynchronizationManager::onProjectSyncProgressChanged );
connect( mMerginApi, &MerginApi::projectReloadNeededAfterSync, this, &SynchronizationManager::onProjectReloadNeededAfterSync );
connect( mMerginApi, &MerginApi::projectAlreadyOnLatestVersion, this, [&]( const QString & projectFullName )
{
const SyncProcess &process = mSyncProcesses[projectFullName];
if ( process.requestOrigin == SyncOptions::ManualRequest )
{
emit projectAlreadyOnLatestVersion( projectFullName );
}
} );
}
}

SynchronizationManager::~SynchronizationManager() = default;

void SynchronizationManager::syncProject( const Project &project, SyncOptions::Authorization auth, SyncOptions::Strategy strategy )
void SynchronizationManager::syncProject( const Project &project, SyncOptions::Authorization auth, SyncOptions::Strategy strategy, const SyncOptions::RequestOrigin
requestOrigin )
{
if ( project.isLocal() )
{
syncProject( project.local, auth, strategy );
syncProject( project.local, auth, strategy, requestOrigin );
return;
}

Expand All @@ -48,12 +57,14 @@ void SynchronizationManager::syncProject( const Project &project, SyncOptions::A
SyncProcess &process = mSyncProcesses[project.fullName()]; // gets or creates
process.pending = true;
process.strategy = strategy;
process.requestOrigin = requestOrigin;

emit syncStarted( project.fullName() );
}
}

void SynchronizationManager::syncProject( const LocalProject &project, SyncOptions::Authorization auth, SyncOptions::Strategy strategy )
void SynchronizationManager::syncProject( const LocalProject &project, SyncOptions::Authorization auth, SyncOptions::Strategy strategy, const SyncOptions::
RequestOrigin requestOrigin )
{
if ( !project.isValid() )
{
Expand All @@ -62,7 +73,10 @@ void SynchronizationManager::syncProject( const LocalProject &project, SyncOptio

if ( !project.hasMerginMetadata() )
{
emit syncError( project.id(), SynchronizationError::NotAMerginProject );
if ( requestOrigin == SyncOptions::ManualRequest )
{
emit syncError( project.id(), SynchronizationError::NotAMerginProject );
}
return;
}

Expand Down Expand Up @@ -97,6 +111,7 @@ void SynchronizationManager::syncProject( const LocalProject &project, SyncOptio
SyncProcess &process = mSyncProcesses[projectFullName]; // gets or creates
process.pending = true;
process.strategy = strategy;
process.requestOrigin = requestOrigin;

emit syncStarted( projectFullName );
}
Expand Down Expand Up @@ -247,7 +262,7 @@ void SynchronizationManager::onProjectCreated( const QString &projectFullName, b
void SynchronizationManager::onProjectSyncFailure(
const QString &message,
const QString &topic,
int errorCode,
const int errorCode,
const QString &projectFullName )
{
if ( projectFullName.isEmpty() )
Expand All @@ -264,14 +279,18 @@ void SynchronizationManager::onProjectSyncFailure(

SyncProcess &process = mSyncProcesses[projectFullName];

SynchronizationError::ErrorType error = SynchronizationError::errorType( errorCode, message );
const SynchronizationError::ErrorType error = SynchronizationError::errorType( errorCode, message );

// We only retry twice
bool eligibleForRetry = process.strategy == SyncOptions::Retry &&
process.retriesCount < 2 &&
!SynchronizationError::isPermanent( error );
// We only retry twice for synchronization requested by user
const bool eligibleForRetry = process.strategy == SyncOptions::Retry &&
process.retriesCount < 2 &&
!SynchronizationError::isPermanent( error ) &&
process.requestOrigin == SyncOptions::ManualRequest;

emit syncError( projectFullName, error, eligibleForRetry, message );
if ( process.requestOrigin == SyncOptions::ManualRequest )
{
emit syncError( projectFullName, error, eligibleForRetry, message );
}

if ( eligibleForRetry )
{
Expand All @@ -288,8 +307,6 @@ void SynchronizationManager::onProjectSyncFailure(
{
mSyncProcesses.remove( projectFullName );
emit syncFinished( projectFullName, false, -1, false );

return;
}
}

Expand Down
12 changes: 9 additions & 3 deletions app/synchronizationmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct SyncProcess
bool awaitsRetry; // not currently being synced, but awaits to be synced
int retriesCount = 0;
SyncOptions::Strategy strategy = SyncOptions::Singleshot;
SyncOptions::RequestOrigin requestOrigin;
// In future: current state (push/pull)
};

Expand Down Expand Up @@ -63,20 +64,25 @@ class SynchronizationManager : public QObject
void syncFinished( const QString &projectFullName, bool success, int newVersion, bool reloadNeeded );

void syncError( const QString &projectFullName, int errorType, bool willRetry = false, const QString &errorMessage = QLatin1String() );
void projectAlreadyOnLatestVersion( const QString &projectFullName );

public slots:

/**
* \brief syncProject Starts synchronization of a project if there are local/server changes to be applied
*
* \param project Project struct instance
* \param withAut Bears an information whether authorization should be included in sync requests.
* \param auth Bears an information whether authorization should be included in sync requests.
* Authorization can be omitted for pull of public projects
* \param strategy Describes whether sync will be tried again after temporary error
* \param requestOrigin Flags if the request is coming from user or autosync controller
*/
void syncProject( const LocalProject &project, SyncOptions::Authorization auth = SyncOptions::Authorized, SyncOptions::Strategy strategy = SyncOptions::Singleshot );
void syncProject( const LocalProject &project, SyncOptions::Authorization auth = SyncOptions::Authorized, SyncOptions::Strategy strategy = SyncOptions::Singleshot, SyncOptions
::RequestOrigin requestOrigin = SyncOptions::RequestOrigin::ManualRequest );

//! Overloaded method, allows to sync with Project instance. Can be used in case of first download of remote project (it has invalid LocalProject info).
void syncProject( const Project &project, SyncOptions::Authorization auth = SyncOptions::Authorized, SyncOptions::Strategy strategy = SyncOptions::Singleshot );
void syncProject( const Project &project, SyncOptions::Authorization auth = SyncOptions::Authorized, SyncOptions::Strategy strategy = SyncOptions::Singleshot, SyncOptions
::RequestOrigin requestOrigin = SyncOptions::RequestOrigin::ManualRequest );

// Handling of synchronization changes from MerginApi
void onProjectSyncCanceled( const QString &projectFullName, bool hasError );
Expand Down
Loading
Loading