Skip to content

Commit

Permalink
Merge pull request #4883 from opengisch/cloud_outdated
Browse files Browse the repository at this point in the history
Addition a project oudated and project >>file<< outdated states for cloud projects
  • Loading branch information
nirvn authored Jan 11, 2024
2 parents 72255c8 + e4623a8 commit 4634b14
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 12 deletions.
1 change: 1 addition & 0 deletions images/images.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@
<file>themes/qfield/xxxhdpi/ic_close_black_24dp.png</file>
<file>themes/qfield/nodpi/ic_cloud_24dp.svg</file>
<file>themes/qfield/nodpi/ic_cloud_active_24dp.svg</file>
<file>themes/qfield/nodpi/ic_cloud_attention_24dp.svg</file>
<file>themes/qfield/nodpi/ic_cloud_project_48dp.svg</file>
<file>themes/qfield/hdpi/ic_cloud_project_48dp.png</file>
<file>themes/qfield/mdpi/ic_cloud_project_48dp.png</file>
Expand Down
2 changes: 2 additions & 0 deletions images/themes/qfield/nodpi/ic_cloud_attention_24dp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 82 additions & 6 deletions src/core/qfieldcloudprojectsmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,60 @@ void QFieldCloudProjectsModel::refreshProjectModification( const QString &projec
}
}

void QFieldCloudProjectsModel::refreshProjectFileOutdatedStatus( const QString &projectId )
{
const QModelIndex projectIndex = findProjectIndex( projectId );
if ( !projectIndex.isValid() )
return;

CloudProject *project = mProjects[projectIndex.row()];
NetworkReply *reply = mCloudConnection->get( QStringLiteral( "/api/v1/files/%1/" ).arg( projectId ) );

connect( reply, &NetworkReply::finished, reply, [=]() {
if ( !findProject( projectId ) )
{
QgsLogger::debug( QStringLiteral( "Project %1: project has been deleted while refreshing project file outdated status." ).arg( projectId ) );
return;
}

QNetworkReply *rawReply = reply->reply();
reply->deleteLater();

if ( rawReply->error() != QNetworkReply::NoError )
{
QgsLogger::debug( QStringLiteral( "Project %1: failed to refresh the project file outdated satus. %2" ).arg( projectId, QFieldCloudConnection::errorString( rawReply ) ) );
return;
}

const QJsonArray files = QJsonDocument::fromJson( rawReply->readAll() ).array();
const QString lastProjectFileSha256 = QFieldCloudUtils::projectSetting( project->id, QStringLiteral( "lastProjectFileSha256" ), QString() ).toString();
for ( const auto file : files )
{
QVariantHash fileDetails = file.toObject().toVariantHash();
const QString fileName = fileDetails.value( "name" ).toString().toLower();
if ( fileName.endsWith( QStringLiteral( ".qgs" ) ) || fileName.endsWith( QStringLiteral( ".qgz" ) ) )
{
if ( lastProjectFileSha256.isEmpty() )
{
// First check, store for future comparison
QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "lastProjectFileSha256" ), fileDetails.value( "sha256" ).toString() );
}
else if ( lastProjectFileSha256 != fileDetails.value( "sha256" ).toString() )
{
project->projectFileIsOutdated = true;
QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "projectFileOudated" ), true );
emit dataChanged( projectIndex, projectIndex, QVector<int>() << ProjectFileOutdatedRole );

if ( mCurrentProjectId == projectId )
{
emit currentProjectDataChanged();
}
}
}
}
} );
}

QString QFieldCloudProjectsModel::layerFileName( const QgsMapLayer *layer ) const
{
return layer->dataProvider()->dataSourceUri().split( '|' )[0];
Expand Down Expand Up @@ -482,18 +536,24 @@ void QFieldCloudProjectsModel::projectRefreshData( const QString &projectId, con
project->canRepackage = projectData.value( "can_repackage" ).toBool();
project->needsRepackaging = projectData.value( "needs_repackaging" ).toBool();
project->lastRefreshedAt = QDateTime::currentDateTimeUtc();
project->dataLastUpdatedAt = QDateTime::fromString( projectData.value( "data_last_updated_at" ).toString(), Qt::ISODate );
project->isOutdated = project->lastLocalDataLastUpdatedAt.isValid() ? project->dataLastUpdatedAt > project->lastLocalDataLastUpdatedAt : false;

QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "name" ), project->name );
QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "owner" ), project->owner );
QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "description" ), project->description );
QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "userRole" ), project->userRole );
QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "updatedAt" ), project->updatedAt );
QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "isPrivate" ), project->isPrivate );
QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "canRepackage" ), project->canRepackage );
QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "needsRepackaging" ), project->needsRepackaging );

emit dataChanged( projectIndex, projectIndex, QVector<int>::fromList( roleNames().keys() ) );
emit projectRefreshed( projectId, refreshReason );

if ( mCurrentProjectId == projectId )
{
emit currentProjectDataChanged();
}
} );
}

Expand Down Expand Up @@ -1331,6 +1391,7 @@ void QFieldCloudProjectsModel::projectUpload( const QString &projectId, const bo

emit dataChanged( projectIndex, projectIndex, QVector<int>() << StatusRole );
emit pushFinished( projectId, false );
projectRefreshData( projectId, ProjectRefreshReason::DeltaUploaded );
}
} );

Expand Down Expand Up @@ -1395,6 +1456,7 @@ void QFieldCloudProjectsModel::projectUpload( const QString &projectId, const bo
{
emit dataChanged( projectIndex, projectIndex, QVector<int>() << StatusRole );
emit pushFinished( projectId, false );
projectRefreshData( projectId, ProjectRefreshReason::DeltaUploaded );
}
}
} );
Expand Down Expand Up @@ -1813,12 +1875,19 @@ void QFieldCloudProjectsModel::downloadFileConnections( const QString &projectId
project->localPath = QFieldCloudUtils::localProjectFilePath( mUsername, projectId );
project->lastLocalExportedAt = QDateTime::currentDateTimeUtc().toString( Qt::ISODate );
project->lastLocalExportId = QUuid::createUuid().toString( QUuid::WithoutBraces );
project->lastLocalDataLastUpdatedAt = project->dataLastUpdatedAt;
project->isOutdated = false;
project->projectFileIsOutdated = false;

QFieldCloudUtils::setProjectSetting( projectId, QStringLiteral( "lastExportedAt" ), project->lastExportedAt );
QFieldCloudUtils::setProjectSetting( projectId, QStringLiteral( "lastExportId" ), project->lastExportId );
QFieldCloudUtils::setProjectSetting( projectId, QStringLiteral( "lastLocalExportedAt" ), project->lastLocalExportedAt );
QFieldCloudUtils::setProjectSetting( projectId, QStringLiteral( "lastLocalExportId" ), project->lastLocalExportId );
QFieldCloudUtils::setProjectSetting( projectId, QStringLiteral( "lastLocalDataLastUpdatedAt" ), project->lastLocalDataLastUpdatedAt );
QFieldCloudUtils::setProjectSetting( projectId, QStringLiteral( "lastProjectFileSha256" ), QString() );
QFieldCloudUtils::setProjectSetting( projectId, QStringLiteral( "projectFileOudated" ), false );

rolesChanged << StatusRole << LocalPathRole << CheckoutRole << LastLocalExportedAtRole;
rolesChanged << StatusRole << ProjectOutdatedRole << ProjectFileOutdatedRole << LocalPathRole << CheckoutRole << LastLocalExportedAtRole;

emit dataChanged( projectIndex, projectIndex, rolesChanged );
emit projectDownloadFinished( projectId );
Expand All @@ -1842,6 +1911,8 @@ QHash<int, QByteArray> QFieldCloudProjectsModel::roleNames() const
roles[ModificationRole] = "Modification";
roles[CheckoutRole] = "Checkout";
roles[StatusRole] = "Status";
roles[ProjectOutdatedRole] = "ProjectOutdated";
roles[ProjectFileOutdatedRole] = "ProjectFileOutdated";
roles[ErrorStatusRole] = "ErrorStatus";
roles[ErrorStringRole] = "ErrorString";
roles[DownloadProgressRole] = "DownloadProgress";
Expand Down Expand Up @@ -1875,6 +1946,9 @@ void QFieldCloudProjectsModel::reload( const QJsonArray &remoteProjects )
cloudProject->lastLocalExportId = QFieldCloudUtils::projectSetting( cloudProject->id, QStringLiteral( "lastLocalExportId" ) ).toString();
cloudProject->lastLocalExportedAt = QFieldCloudUtils::projectSetting( cloudProject->id, QStringLiteral( "lastLocalExportedAt" ) ).toString();
cloudProject->lastLocalPushDeltas = QFieldCloudUtils::projectSetting( cloudProject->id, QStringLiteral( "lastLocalPushDeltas" ) ).toString();
cloudProject->lastLocalDataLastUpdatedAt = QFieldCloudUtils::projectSetting( cloudProject->id, QStringLiteral( "lastLocalDataLastUpdatedAt" ) ).toDateTime();
cloudProject->isOutdated = cloudProject->dataLastUpdatedAt > cloudProject->lastLocalDataLastUpdatedAt;
cloudProject->projectFileIsOutdated = QFieldCloudUtils::projectSetting( cloudProject->id, QStringLiteral( "projectFileOudated" ), false ).toBool();

// generate local export id if not present. Possible reasons for missing localExportId are:
// - just upgraded QField that introduced the field
Expand All @@ -1895,17 +1969,15 @@ void QFieldCloudProjectsModel::reload( const QJsonArray &remoteProjects )
projectDetails.value( "name" ).toString(),
projectDetails.value( "description" ).toString(),
projectDetails.value( "user_role" ).toString(),
QString(),
RemoteCheckout,
ProjectStatus::Idle,
QDateTime::fromString( projectDetails.value( "data_last_updated_at" ).toString(), Qt::ISODate ),
projectDetails.value( "can_repackage" ).toBool(),
projectDetails.value( "needs_repackaging" ).toBool() );

const QString projectPrefix = QStringLiteral( "QFieldCloud/projects/%1" ).arg( cloudProject->id );
QFieldCloudUtils::setProjectSetting( cloudProject->id, QStringLiteral( "owner" ), cloudProject->owner );
QFieldCloudUtils::setProjectSetting( cloudProject->id, QStringLiteral( "name" ), cloudProject->name );
QFieldCloudUtils::setProjectSetting( cloudProject->id, QStringLiteral( "description" ), cloudProject->description );
QFieldCloudUtils::setProjectSetting( cloudProject->id, QStringLiteral( "updatedAt" ), cloudProject->updatedAt );
QFieldCloudUtils::setProjectSetting( cloudProject->id, QStringLiteral( "userRole" ), cloudProject->userRole );
QFieldCloudUtils::setProjectSetting( cloudProject->id, QStringLiteral( "canRepackage" ), cloudProject->canRepackage );
QFieldCloudUtils::setProjectSetting( cloudProject->id, QStringLiteral( "needsRepackaging" ), cloudProject->needsRepackaging );
Expand Down Expand Up @@ -1956,7 +2028,7 @@ void QFieldCloudProjectsModel::reload( const QJsonArray &remoteProjects )
const QString updatedAt = QFieldCloudUtils::projectSetting( projectId, QStringLiteral( "updatedAt" ) ).toString();
const QString userRole = QFieldCloudUtils::projectSetting( projectId, QStringLiteral( "userRole" ) ).toString();

CloudProject *cloudProject = new CloudProject( projectId, true, owner, name, description, userRole, QString(), LocalCheckout, ProjectStatus::Idle, false, false );
CloudProject *cloudProject = new CloudProject( projectId, true, owner, name, description, userRole, LocalCheckout, ProjectStatus::Idle, QDateTime(), false, false );

cloudProject->localPath = QFieldCloudUtils::localProjectFilePath( username, cloudProject->id );
QDir localPath( QStringLiteral( "%1/%2/%3" ).arg( QFieldCloudUtils::localCloudDirectory(), username, cloudProject->id ) );
Expand Down Expand Up @@ -2007,6 +2079,10 @@ QVariant QFieldCloudProjectsModel::data( const QModelIndex &index, int role ) co
return static_cast<int>( mProjects.at( index.row() )->checkout );
case StatusRole:
return static_cast<int>( mProjects.at( index.row() )->status );
case ProjectOutdatedRole:
return mProjects.at( index.row() )->isOutdated;
case ProjectFileOutdatedRole:
return mProjects.at( index.row() )->projectFileIsOutdated;
case ErrorStatusRole:
return static_cast<int>( mProjects.at( index.row() )->errorStatus );
case ErrorStringRole:
Expand Down
15 changes: 12 additions & 3 deletions src/core/qfieldcloudprojectsmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class QFieldCloudProjectsModel : public QAbstractListModel
ModificationRole,
CheckoutRole,
StatusRole,
ProjectOutdatedRole,
ProjectFileOutdatedRole,
ErrorStatusRole,
ErrorStringRole,
DownloadProgressRole,
Expand Down Expand Up @@ -164,6 +166,7 @@ class QFieldCloudProjectsModel : public QAbstractListModel
enum class ProjectRefreshReason
{
Package,
DeltaUploaded
};

Q_ENUM( ProjectRefreshReason )
Expand Down Expand Up @@ -248,6 +251,9 @@ class QFieldCloudProjectsModel : public QAbstractListModel
//! Updates the project modification for given \a projectId.
Q_INVOKABLE void refreshProjectModification( const QString &projectId );

//! Refreshes the project file (.qgs, .qgz) outdated status.
Q_INVOKABLE void refreshProjectFileOutdatedStatus( const QString &projectId );

//! Returns the model role names.
QHash<int, QByteArray> roleNames() const override;

Expand Down Expand Up @@ -350,9 +356,9 @@ class QFieldCloudProjectsModel : public QAbstractListModel
const QString &name,
const QString &description,
const QString &userRole,
const QString &updatedAt,
const ProjectCheckouts &checkout,
const ProjectStatus &status,
const QDateTime &dataLastUpdatedAt,
bool canRepackage,
bool needsRepackaging )
: id( id )
Expand All @@ -361,9 +367,9 @@ class QFieldCloudProjectsModel : public QAbstractListModel
, name( name )
, description( description )
, userRole( userRole )
, updatedAt( updatedAt )
, checkout( checkout )
, status( status )
, dataLastUpdatedAt( dataLastUpdatedAt )
, canRepackage( canRepackage )
, needsRepackaging( needsRepackaging )
{}
Expand All @@ -377,12 +383,14 @@ class QFieldCloudProjectsModel : public QAbstractListModel
QString name;
QString description;
QString userRole;
QString updatedAt;
ProjectErrorStatus errorStatus = ProjectErrorStatus::NoErrorStatus;
ProjectCheckouts checkout;
ProjectStatus status;
QDateTime dataLastUpdatedAt;
bool canRepackage = false;
bool needsRepackaging = false;
bool isOutdated = false;
bool projectFileIsOutdated = false;

ProjectModifications modification = ProjectModification::NoModification;
QString localPath;
Expand Down Expand Up @@ -414,6 +422,7 @@ class QFieldCloudProjectsModel : public QAbstractListModel
QString lastLocalExportId;
QString lastLocalPushDeltas;

QDateTime lastLocalDataLastUpdatedAt;
QDateTime lastRefreshedAt;
QMap<JobType, Job> jobs;
};
Expand Down
2 changes: 1 addition & 1 deletion src/qml/DashBoard.qml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Drawer {
return Theme.getThemeVectorIcon('ic_cloud_active_24dp');
}
case QFieldCloudProjectsModel.Idle:
return Theme.getThemeVectorIcon('ic_cloud_active_24dp');
return cloudProjectsModel.currentProjectData.ProjectFileOutdated ? Theme.getThemeVectorIcon('ic_cloud_attention_24dp') : Theme.getThemeVectorIcon('ic_cloud_active_24dp');
default: Theme.getThemeVectorIcon( 'ic_cloud_24dp' );
}
bgcolor: "transparent"
Expand Down
10 changes: 8 additions & 2 deletions src/qml/QFieldCloudPopup.qml
Original file line number Diff line number Diff line change
Expand Up @@ -605,11 +605,17 @@ Popup {
function show() {
visible = !visible

if ( cloudProjectsModel.currentProjectId && cloudConnection.hasToken && cloudConnection.status === QFieldCloudConnection.Disconnected )
if ( cloudProjectsModel.currentProjectId && cloudConnection.hasToken && cloudConnection.status === QFieldCloudConnection.Disconnected ) {
cloudConnection.login();
}

if ( cloudConnection.status === QFieldCloudConnection.Connecting )
if ( cloudConnection.status === QFieldCloudConnection.Connecting ) {
displayToast(qsTr('Connecting cloud'))
} else if ( cloudProjectsModel.currentProjectData.ProjectFileOutdated ) {
displayToast(qsTr('This project has an updated project file on the cloud, you are advised to synchronize.'), 'warning')
} else if ( cloudProjectsModel.currentProjectData.ProjectOutdated ) {
displayToast(qsTr('This project has updated data on the cloud, you should synchronize.'))
}
}

function projectUpload(shouldDownloadUpdates) {
Expand Down
3 changes: 3 additions & 0 deletions src/qml/QFieldCloudScreen.qml
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ Page {
break
case QFieldCloudProjectsModel.LocalAndRemoteCheckout:
status = qsTr( 'Available locally' )
if (ProjectOutdated) {
status += qsTr( ', updated data available on the cloud');
}
break
default:
break
Expand Down
9 changes: 9 additions & 0 deletions src/qml/qgismobileapp.qml
Original file line number Diff line number Diff line change
Expand Up @@ -3326,6 +3326,10 @@ ApplicationWindow {
if (cloudProjectsModel.layerObserver.deltaFileWrapper.hasError()) {
cloudPopup.show()
}

if (cloudConnection.status === QFieldCloudConnection.LoggedIn) {
cloudProjectsModel.refreshProjectFileOutdatedStatus(cloudProjectId)
}
} else {
projectInfo.hasInsertRights = true
projectInfo.hasEditRights = true
Expand Down Expand Up @@ -3589,6 +3593,11 @@ ApplicationWindow {
displayToast(qsTr('Signed in'))
// Go ahead and upload pending attachments in the background
platformUtilities.uploadPendingAttachments(cloudConnection);

var cloudProjectId = QFieldCloudUtils.getProjectId(qgisProject.fileName)
if (cloudProjectId) {
cloudProjectsModel.refreshProjectFileOutdatedStatus(cloudProjectId)
}
}
previousStatus = cloudConnection.status
}
Expand Down

1 comment on commit 4634b14

@qfield-fairy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.