Skip to content

Commit 0bd81d2

Browse files
authored
Merge pull request #38323 from elpaso/server-landingpage-dirwatcher
Server landingpage dirwatcher
2 parents ee14d3d + a04bbe4 commit 0bd81d2

File tree

5 files changed

+104
-28
lines changed

5 files changed

+104
-28
lines changed

src/server/services/landingpage/qgslandingpagehandlers.cpp

+1-3
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,7 @@ json QgsLandingPageHandler::projectsData() const
6969
const auto constProjectKeys { availableProjects.keys() };
7070
for ( const auto &p : constProjectKeys )
7171
{
72-
auto info { QgsLandingPageUtils::projectInfo( availableProjects[ p ] ) };
73-
info[ "id" ] = p.toStdString();
74-
j.push_back( info );
72+
j.push_back( QgsLandingPageUtils::projectInfo( availableProjects[ p ] ) );
7573
}
7674
return j;
7775
}

src/server/services/landingpage/qgslandingpageutils.cpp

+66-23
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,65 @@
2424
#include "qgsvectorlayer.h"
2525
#include "nlohmann/json.hpp"
2626

27+
#include <mutex>
2728
#include <QCryptographicHash>
29+
#include <QFileSystemWatcher>
2830

2931
const QRegularExpression QgsLandingPageUtils::PROJECT_HASH_RE { QStringLiteral( "/(?<projectHash>[a-f0-9]{32})" ) };
3032
QMap<QString, QString> QgsLandingPageUtils::AVAILABLE_PROJECTS;
3133

34+
std::once_flag initDirWatcher;
35+
3236
QMap<QString, QString> QgsLandingPageUtils::projects( )
3337
{
34-
// TODO: use a dir-watcher to invalidate
38+
39+
static QString QGIS_SERVER_PROJECTS_DIRECTORIES;
40+
static QString QGIS_SERVER_PROJECTS_PG_CONNECTIONS;
41+
42+
// Init directory watcher
43+
static QFileSystemWatcher dirWatcher;
44+
std::call_once( initDirWatcher, [ = ]
45+
{
46+
QObject::connect( &dirWatcher, &QFileSystemWatcher::directoryChanged, qApp, [ = ]( const QString & path )
47+
{
48+
QgsMessageLog::logMessage( QStringLiteral( "Directory '%1' has changed: project information cache cleared." ).arg( path ), QStringLiteral( "Landing Page" ), Qgis::MessageLevel::Info );
49+
AVAILABLE_PROJECTS.clear();
50+
} );
51+
} );
52+
53+
54+
const QString projectDir { QString( qgetenv( "QGIS_SERVER_PROJECTS_DIRECTORIES" ) ) };
55+
56+
// Clear cache if QGIS_SERVER_PROJECTS_DIRECTORIES has changed
57+
if ( projectDir != QGIS_SERVER_PROJECTS_DIRECTORIES )
58+
{
59+
QGIS_SERVER_PROJECTS_DIRECTORIES = projectDir;
60+
AVAILABLE_PROJECTS.clear();
61+
const QStringList cWatchedDirs { dirWatcher.directories() };
62+
dirWatcher.removePaths( cWatchedDirs );
63+
}
64+
65+
const QString pgConnections { QString( qgetenv( "QGIS_SERVER_PROJECTS_PG_CONNECTIONS" ) ) };
66+
67+
// Clear cache if QGIS_SERVER_PROJECTS_PG_CONNECTIONS has changed
68+
if ( pgConnections != QGIS_SERVER_PROJECTS_PG_CONNECTIONS )
69+
{
70+
QGIS_SERVER_PROJECTS_PG_CONNECTIONS = pgConnections;
71+
AVAILABLE_PROJECTS.clear();
72+
}
73+
74+
// Scan QGIS_SERVER_PROJECTS_DIRECTORIES
3575
if ( AVAILABLE_PROJECTS.isEmpty() )
3676
{
37-
for ( const auto &path : QString( qgetenv( "QGIS_SERVER_PROJECTS_DIRECTORIES" ) ).split( QStringLiteral( "||" ) ) )
77+
const auto cProjectDirs { projectDir.split( QStringLiteral( "||" ) ) };
78+
for ( const auto &path : cProjectDirs )
3879
{
3980
if ( ! path.isEmpty() )
4081
{
4182
const QDir dir { path };
4283
if ( dir.exists() )
4384
{
85+
dirWatcher.addPath( dir.path() );
4486
const auto constFiles { dir.entryList( ) };
4587
for ( const auto &f : constFiles )
4688
{
@@ -64,35 +106,36 @@ QMap<QString, QString> QgsLandingPageUtils::projects( )
64106
QgsMessageLog::logMessage( QStringLiteral( "QGIS_SERVER_PROJECTS_DIRECTORIES empty path: skipping." ), QStringLiteral( "Landing Page" ), Qgis::MessageLevel::Warning );
65107
}
66108
}
109+
}
67110

68-
// PG projects
69-
const auto storage { QgsApplication::instance()->projectStorageRegistry()->projectStorageFromType( QStringLiteral( "postgresql" ) ) };
70-
Q_ASSERT( storage );
71-
for ( const auto &connectionString : QString( qgetenv( "QGIS_SERVER_PROJECTS_PG_CONNECTIONS" ) ).split( QStringLiteral( "||" ) ) )
111+
// PG projects (there is no watcher for PG: scan every time)
112+
const auto storage { QgsApplication::instance()->projectStorageRegistry()->projectStorageFromType( QStringLiteral( "postgresql" ) ) };
113+
Q_ASSERT( storage );
114+
const auto cPgConnections { pgConnections.split( QStringLiteral( "||" ) ) };
115+
for ( const auto &connectionString : cPgConnections )
116+
{
117+
if ( ! connectionString.isEmpty() )
72118
{
73-
if ( ! connectionString.isEmpty() )
119+
const auto constProjects { storage->listProjects( connectionString ) };
120+
if ( ! constProjects.isEmpty() )
74121
{
75-
const auto constProjects { storage->listProjects( connectionString ) };
76-
if ( ! constProjects.isEmpty() )
77-
{
78-
for ( const auto &projectName : constProjects )
79-
{
80-
const QString projectFullPath { connectionString + QStringLiteral( "&project=%1" ).arg( projectName ) };
81-
const auto projectHash { QCryptographicHash::hash( projectFullPath.toUtf8(), QCryptographicHash::Md5 ).toHex() };
82-
AVAILABLE_PROJECTS[ projectHash ] = projectFullPath;
83-
QgsMessageLog::logMessage( QStringLiteral( "Adding postgres project '%1' with id '%2'" ).arg( projectName, QString::fromUtf8( projectHash ) ), QStringLiteral( "Landing Page" ), Qgis::MessageLevel::Warning );
84-
}
85-
}
86-
else
122+
for ( const auto &projectName : constProjects )
87123
{
88-
QgsMessageLog::logMessage( QStringLiteral( "QGIS_SERVER_PROJECTS_PG_CONNECTIONS entry '%1' was not found or has not projects: skipping." ).arg( connectionString ), QStringLiteral( "Landing Page" ), Qgis::MessageLevel::Warning );
124+
const QString projectFullPath { connectionString + QStringLiteral( "&project=%1" ).arg( projectName ) };
125+
const auto projectHash { QCryptographicHash::hash( projectFullPath.toUtf8(), QCryptographicHash::Md5 ).toHex() };
126+
AVAILABLE_PROJECTS[ projectHash ] = projectFullPath;
127+
QgsMessageLog::logMessage( QStringLiteral( "Adding postgres project '%1' with id '%2'" ).arg( projectName, QString::fromUtf8( projectHash ) ), QStringLiteral( "Landing Page" ), Qgis::MessageLevel::Warning );
89128
}
90129
}
91130
else
92131
{
93-
QgsMessageLog::logMessage( QStringLiteral( "QGIS_SERVER_PROJECTS_PG_CONNECTIONS empty connection: skipping." ), QStringLiteral( "Landing Page" ), Qgis::MessageLevel::Warning );
132+
QgsMessageLog::logMessage( QStringLiteral( "QGIS_SERVER_PROJECTS_PG_CONNECTIONS entry '%1' was not found or has not projects: skipping." ).arg( connectionString ), QStringLiteral( "Landing Page" ), Qgis::MessageLevel::Warning );
94133
}
95134
}
135+
else
136+
{
137+
QgsMessageLog::logMessage( QStringLiteral( "QGIS_SERVER_PROJECTS_PG_CONNECTIONS empty connection: skipping." ), QStringLiteral( "Landing Page" ), Qgis::MessageLevel::Warning );
138+
}
96139
}
97140

98141
return AVAILABLE_PROJECTS;
@@ -176,8 +219,8 @@ json QgsLandingPageUtils::projectInfo( const QString &projectUri )
176219
return jLinks;
177220
};
178221

179-
json info;
180-
info[ "id" ] = QCryptographicHash::hash( projectUri.toUtf8(), QCryptographicHash::Md5 ).toHex();
222+
json info = json::object();
223+
info[ "id" ] = QCryptographicHash::hash( projectUri.toUtf8(), QCryptographicHash::Md5 ).toHex();
181224
QgsProject p;
182225
if ( p.read( projectUri ) )
183226
{

tests/src/python/test_qgsserver_landingpage.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,13 @@ class QgsServerLandingPageTest(QgsServerAPITestBase):
5454
@classmethod
5555
def setUpClass(cls):
5656
super().setUpClass()
57-
directories = [os.path.join(unitTestDataPath('qgis_server'), 'landingpage', 'projects')]
58-
directories.append(os.path.join(unitTestDataPath('qgis_server'), 'landingpage', 'projects2'))
57+
58+
cls.temp_dir = QtCore.QTemporaryDir()
59+
60+
temp_dir = cls.temp_dir.path()
61+
shutil.copytree(os.path.join(unitTestDataPath('qgis_server'), 'landingpage'), os.path.join(temp_dir, 'landingpage'))
62+
63+
directories = [os.path.join(temp_dir, 'landingpage', 'projects'), os.path.join(temp_dir, 'landingpage', 'projects2')]
5964
os.environ['QGIS_SERVER_PROJECTS_DIRECTORIES'] = '||'.join(directories)
6065

6166
if not os.environ.get('TRAVIS', False):
@@ -149,6 +154,15 @@ def test_project_json(self):
149154
self.compareApi(
150155
request, None, 'test_project_{}.json'.format(name.replace('.', '_')), subdir='landingpage')
151156

157+
def test_landing_page_json_empty(self):
158+
"""Test landing page in JSON format with no projects"""
159+
160+
os.environ['QGIS_SERVER_PROJECTS_DIRECTORIES'] = ''
161+
os.environ['QGIS_SERVER_PROJECTS_PG_CONNECTIONS'] = ''
162+
request = QgsBufferServerRequest('http://server.qgis.org/index.json')
163+
self.compareApi(
164+
request, None, 'test_landing_page_empty_index.json', subdir='landingpage')
165+
152166

153167
if __name__ == '__main__':
154168
unittest.main()
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Content-Type: application/json
2+
3+
{
4+
"links": [
5+
{
6+
"href": "http://server.qgis.org/index.json",
7+
"rel": "self",
8+
"title": "Landing page as JSON",
9+
"type": "application/json"
10+
},
11+
{
12+
"href": "http://server.qgis.org/index.html",
13+
"rel": "alternate",
14+
"title": "Landing page as HTML",
15+
"type": "text/html"
16+
}
17+
],
18+
"projects": [],
19+
"projects_count": 0,
20+
"timeStamp": "2019-07-05T12:27:07Z"
21+
}

0 commit comments

Comments
 (0)