Skip to content

Commit ab83455

Browse files
committed
Store last modified time in project storage + retrieve it
1 parent 0f5ea53 commit ab83455

File tree

5 files changed

+106
-2
lines changed

5 files changed

+106
-2
lines changed

python/core/qgsprojectstorage.sip.in

+20
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ and registered in QgsProjectStorageRegistry.
2424
#include "qgsprojectstorage.h"
2525
%End
2626
public:
27+
28+
class Metadata
29+
{
30+
%Docstring
31+
Metadata associated with a project
32+
%End
33+
34+
%TypeHeaderCode
35+
#include "qgsprojectstorage.h"
36+
%End
37+
public:
38+
QDateTime lastModified;
39+
};
40+
2741
virtual ~QgsProjectStorage();
2842

2943
virtual QString type() = 0;
@@ -63,6 +77,12 @@ was successful.
6377
%Docstring
6478
Rename an existing project at the given URI to a different URI. Returns true if renaming
6579
was successful.
80+
%End
81+
82+
virtual bool readProjectMetadata( const QString &uri, QgsProjectStorage::Metadata &metadata /Out/ );
83+
%Docstring
84+
Reads project metadata (e.g. last modified time) if this is supported by the storage implementation.
85+
Returns true if the metadata were read with success.
6686
%End
6787

6888
virtual QString visibleName();

src/core/qgsprojectstorage.h

+16
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
#define QGSPROJECTSTORAGE_H
1818

1919
#include "qgis_core.h"
20+
#include "qgis_sip.h"
2021

22+
#include <QDateTime>
2123
#include <QString>
2224

2325
class QIODevice;
@@ -34,6 +36,14 @@ class QgsReadWriteContext;
3436
class CORE_EXPORT QgsProjectStorage
3537
{
3638
public:
39+
40+
//! Metadata associated with a project
41+
class Metadata
42+
{
43+
public:
44+
QDateTime lastModified;
45+
};
46+
3747
virtual ~QgsProjectStorage();
3848

3949
/**
@@ -73,6 +83,12 @@ class CORE_EXPORT QgsProjectStorage
7383
*/
7484
virtual bool renameProject( const QString &uri, const QString &uriNew ) { Q_UNUSED( uri ); Q_UNUSED( uriNew ); return false; }
7585

86+
/**
87+
* Reads project metadata (e.g. last modified time) if this is supported by the storage implementation.
88+
* Returns true if the metadata were read with success.
89+
*/
90+
virtual bool readProjectMetadata( const QString &uri, QgsProjectStorage::Metadata &metadata SIP_OUT ) { Q_UNUSED( uri ); Q_UNUSED( metadata ); return false; }
91+
7692
/**
7793
* Returns human-readable name of the storage. Used as the menu item text in QGIS. Empty name
7894
* indicates that the storage does not implement GUI support (showLoadGui() and showSaveGui()).

src/providers/postgres/qgspostgresprojectstorage.cpp

+58-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,32 @@
66
#include "qgsreadwritecontext.h"
77

88
#include <QIODevice>
9+
#include <QJsonDocument>
10+
#include <QJsonObject>
911
#include <QUrl>
1012

13+
14+
static bool _parseMetadataDocument( const QJsonDocument &doc, QgsProjectStorage::Metadata &metadata )
15+
{
16+
if ( !doc.isObject() )
17+
return false;
18+
19+
QJsonObject docObj = doc.object();
20+
metadata.lastModified = QDateTime();
21+
if ( docObj.contains( "last_modified_time" ) )
22+
{
23+
QString lastModifiedTimeStr = docObj["last_modified_time"].toString();
24+
if ( !lastModifiedTimeStr.isEmpty() )
25+
{
26+
QDateTime lastModifiedUtc = QDateTime::fromString( lastModifiedTimeStr, Qt::ISODate );
27+
lastModifiedUtc.setTimeSpec( Qt::UTC );
28+
metadata.lastModified = lastModifiedUtc.toLocalTime();
29+
}
30+
}
31+
return true;
32+
}
33+
34+
1135
static bool _projectsTableExists( QgsPostgresConn &conn, const QString &schemaName )
1236
{
1337
QString tableName( "qgis_projects" );
@@ -123,13 +147,18 @@ bool QgsPostgresProjectStorage::writeProject( const QString &uri, QIODevice *dev
123147
// read from device and write to the table
124148
QByteArray content = device->readAll();
125149

126-
QString metadata = "{ \"last_modified\": 123 }"; // TODO: real metadata
150+
QString metadataExpr = QStringLiteral( "(%1 || (now() at time zone 'utc')::text || %2 || current_user || %3)::jsonb" ).arg(
151+
QgsPostgresConn::quotedValue( "{ \"last_modified_time\": \"" ),
152+
QgsPostgresConn::quotedValue( "\", \"last_modified_user\": \"" ),
153+
QgsPostgresConn::quotedValue( "\" }" )
154+
);
127155

128156
// TODO: would be useful to have QByteArray version of PQexec() to avoid bytearray -> string -> bytearray conversion
129157
QString sql( "INSERT INTO %1.qgis_projects VALUES (%2, %3, E'\\\\x" );
130158
sql = sql.arg( QgsPostgresConn::quotedIdentifier( projectUri.schemaName ),
131159
QgsPostgresConn::quotedValue( projectUri.projectName ),
132-
QgsPostgresConn::quotedValue( metadata ) );
160+
metadataExpr // no need to quote: already quoted
161+
);
133162
sql += QString::fromAscii( content.toHex() );
134163
sql += "') ON CONFLICT (name) DO UPDATE SET content = EXCLUDED.content, metadata = EXCLUDED.metadata;";
135164

@@ -164,6 +193,33 @@ bool QgsPostgresProjectStorage::removeProject( const QString &uri )
164193
}
165194

166195

196+
bool QgsPostgresProjectStorage::readProjectMetadata( const QString &uri, QgsProjectStorage::Metadata &metadata )
197+
{
198+
QgsPostgresProjectUri projectUri = parseUri( uri );
199+
if ( !projectUri.valid )
200+
return false;
201+
202+
QgsPostgresConn *conn = QgsPostgresConnPool::instance()->acquireConnection( projectUri.connInfo.connectionInfo( false ) );
203+
204+
bool ok = false;
205+
QString sql( QStringLiteral( "SELECT metadata FROM %1.qgis_projects WHERE name = %2" ).arg( QgsPostgresConn::quotedIdentifier( projectUri.schemaName ), QgsPostgresConn::quotedValue( projectUri.projectName ) ) );
206+
QgsPostgresResult result( conn->PQexec( sql ) );
207+
if ( result.PQresultStatus() == PGRES_TUPLES_OK )
208+
{
209+
if ( result.PQntuples() == 1 )
210+
{
211+
QString metadataStr = result.PQgetvalue( 0, 0 );
212+
QJsonDocument doc( QJsonDocument::fromJson( metadataStr.toUtf8() ) );
213+
ok = _parseMetadataDocument( doc, metadata );
214+
}
215+
}
216+
217+
QgsPostgresConnPool::instance()->releaseConnection( conn );
218+
219+
return ok;
220+
}
221+
222+
167223
QgsPostgresProjectUri QgsPostgresProjectStorage::parseUri( const QString &uri )
168224
{
169225
QUrl u = QUrl::fromEncoded( uri.toUtf8() );

src/providers/postgres/qgspostgresprojectstorage.h

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class QgsPostgresProjectStorage : public QgsProjectStorage
3535

3636
virtual bool removeProject( const QString &uri ) override;
3737

38+
virtual bool readProjectMetadata( const QString &uri, QgsProjectStorage::Metadata &metadata ) override;
39+
3840
private:
3941
static QgsPostgresProjectUri parseUri( const QString &uri );
4042
};

tests/src/python/test_project_storage_postgres.py

+10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
QgsVectorLayer,
2929
QgsProject,
3030
)
31+
from PyQt5.QtCore import QDateTime
3132
from qgis.testing import start_app, unittest
3233
from utilities import unitTestDataPath
3334

@@ -101,6 +102,15 @@ def testSaveLoadProject(self):
101102

102103
self.assertEqual(len(prj2.mapLayers()), 1)
103104

105+
# try to see project's metadata
106+
107+
res, metadata = prj_storage.readProjectMetadata(project_uri)
108+
self.assertTrue(res)
109+
time_project = metadata.lastModified
110+
time_now = QDateTime.currentDateTime()
111+
time_diff = time_now.secsTo(time_project)
112+
self.assertTrue(abs(time_diff) < 10)
113+
104114
# try to remove the project
105115

106116
res = prj_storage.removeProject(project_uri)

0 commit comments

Comments
 (0)