mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
Implements alternate project's cache strategies
- FileSystem cache strategy - Periodic cache strategy - Null cache strategy
This commit is contained in:
parent
783a330a43
commit
12111098c2
@ -11,6 +11,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsConfigCache : QObject
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
@ -24,6 +25,13 @@ Cache for server configuration.
|
||||
%End
|
||||
public:
|
||||
|
||||
static void initialize( QgsServerSettings *settings );
|
||||
%Docstring
|
||||
Initialize from settings.
|
||||
|
||||
This method must be called prior any call to `QgsConfigCache.instance`
|
||||
%End
|
||||
|
||||
static QgsConfigCache *instance();
|
||||
%Docstring
|
||||
Returns the current instance.
|
||||
@ -53,10 +61,37 @@ value is ``False``).
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
QString strategyName() const;
|
||||
%Docstring
|
||||
Returns the name of the current strategy
|
||||
|
||||
.. versionadded:: 3.26
|
||||
%End
|
||||
|
||||
public:
|
||||
QgsConfigCache( QgsServerSettings *settings );
|
||||
%Docstring
|
||||
Initialize from settings
|
||||
%End
|
||||
|
||||
|
||||
private:
|
||||
QgsConfigCache();
|
||||
public slots:
|
||||
void removeChangedEntry( const QString &path );
|
||||
%Docstring
|
||||
Remove cache entry
|
||||
%End
|
||||
|
||||
void removeChangedEntries();
|
||||
%Docstring
|
||||
Remove all changed cache entries
|
||||
%End
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
|
@ -59,6 +59,8 @@ Provides some enum describing the environment currently supported for configurat
|
||||
QGIS_SERVER_WCS_SERVICE_URL,
|
||||
QGIS_SERVER_WMTS_SERVICE_URL,
|
||||
QGIS_SERVER_LANDING_PAGE_PREFIX,
|
||||
QGIS_SERVER_PROJECT_CACHE_CHECK_INTERVAL,
|
||||
QGIS_SERVER_PROJECT_CACHE_STRATEGY,
|
||||
};
|
||||
};
|
||||
|
||||
@ -293,6 +295,28 @@ variable QGIS_SERVER_DISABLE_GETPRINT.
|
||||
Returns the service URL from the setting.
|
||||
|
||||
.. versionadded:: 3.20
|
||||
%End
|
||||
|
||||
int projectCacheCheckInterval() const;
|
||||
%Docstring
|
||||
Returns the config cache check interval for the 'periodic' strategy.
|
||||
|
||||
.. versionadded:: 3.26
|
||||
%End
|
||||
|
||||
QString projectCacheStrategy() const;
|
||||
%Docstring
|
||||
Returns the project's cache strategy
|
||||
The default value is 'filesystem', the value can be changed by setting the environment
|
||||
variable QGIS_SERVER_PROJECT_CACHE_STRATEGY.
|
||||
Possible values are:
|
||||
|
||||
- 'filesystem': Use file system watcher for notifying projects change. Note that it works
|
||||
only with projects stored in files and not across mounted NFS volumes on Linux.
|
||||
- 'periodic': Timer based periodic check for project's changes. Works with all storage backend.
|
||||
- 'off': Disable completely internal project's cache handling
|
||||
|
||||
.. versionadded:: 3.26
|
||||
%End
|
||||
|
||||
static QString name( QgsServerSettingsEnv::EnvVar env );
|
||||
|
@ -14,7 +14,6 @@
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsconfigcache.h"
|
||||
#include "qgsmessagelog.h"
|
||||
#include "qgsserverexception.h"
|
||||
@ -23,27 +22,82 @@
|
||||
|
||||
#include <QFile>
|
||||
|
||||
QgsConfigCache *QgsConfigCache::sInstance = nullptr;
|
||||
|
||||
|
||||
QgsAbstractCacheStrategy *getStrategyFromSettings( QgsServerSettings *settings )
|
||||
{
|
||||
QgsAbstractCacheStrategy *strategy;
|
||||
if ( settings && settings->projectCacheStrategy() == QStringLiteral( "periodic" ) )
|
||||
{
|
||||
strategy = new QgsPeriodicCacheStrategy( settings->projectCacheCheckInterval() );
|
||||
QgsMessageLog::logMessage(
|
||||
QStringLiteral( "Initializing 'periodic' cache strategy" ),
|
||||
QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
|
||||
}
|
||||
else if ( settings && settings->projectCacheStrategy() == QStringLiteral( "off" ) )
|
||||
{
|
||||
strategy = new QgsNullCacheStrategy();
|
||||
QgsMessageLog::logMessage(
|
||||
QStringLiteral( "Initializing 'off' cache strategy" ),
|
||||
QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
|
||||
}
|
||||
else
|
||||
{
|
||||
strategy = new QgsFileSystemCacheStrategy();
|
||||
QgsMessageLog::logMessage(
|
||||
QStringLiteral( "Initializing 'filesystem' cache strategy" ),
|
||||
QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
|
||||
}
|
||||
|
||||
return strategy;
|
||||
}
|
||||
|
||||
|
||||
void QgsConfigCache::initialize( QgsServerSettings *settings )
|
||||
{
|
||||
if ( sInstance )
|
||||
{
|
||||
QgsMessageLog::logMessage(
|
||||
QStringLiteral( "Project's cache is already initialized" ),
|
||||
QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
|
||||
return;
|
||||
}
|
||||
|
||||
sInstance = new QgsConfigCache( getStrategyFromSettings( settings ) );
|
||||
}
|
||||
|
||||
QgsConfigCache *QgsConfigCache::instance()
|
||||
{
|
||||
static QgsConfigCache *sInstance = nullptr;
|
||||
|
||||
if ( !sInstance )
|
||||
sInstance = new QgsConfigCache();
|
||||
|
||||
{
|
||||
qFatal( "QgsConfigCache must be initialized before accessing QgsConfigCache instance." );
|
||||
Q_ASSERT( false );
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
QgsConfigCache::QgsConfigCache()
|
||||
QgsConfigCache::QgsConfigCache( QgsServerSettings *settings )
|
||||
: QgsConfigCache( getStrategyFromSettings( settings ) )
|
||||
{
|
||||
QObject::connect( &mFileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &QgsConfigCache::removeChangedEntry );
|
||||
|
||||
}
|
||||
|
||||
QgsConfigCache::QgsConfigCache( QgsAbstractCacheStrategy *strategy )
|
||||
: mStrategy( strategy )
|
||||
{
|
||||
mStrategy->attach( this );
|
||||
}
|
||||
|
||||
QgsConfigCache::QgsConfigCache() : QgsConfigCache( new QgsFileSystemCacheStrategy() )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const QgsProject *QgsConfigCache::project( const QString &path, const QgsServerSettings *settings )
|
||||
{
|
||||
if ( ! mProjectCache[ path ] )
|
||||
if ( !mProjectCache[ path ] )
|
||||
{
|
||||
|
||||
std::unique_ptr<QgsProject> prj( new QgsProject() );
|
||||
|
||||
// This is required by virtual layers that call QgsProject::instance() inside the constructor :(
|
||||
@ -107,8 +161,7 @@ const QgsProject *QgsConfigCache::project( const QString &path, const QgsServerS
|
||||
}
|
||||
}
|
||||
}
|
||||
mProjectCache.insert( path, prj.release() );
|
||||
mFileSystemWatcher.addPath( path );
|
||||
cacheProject( path, prj.release() );
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -117,7 +170,9 @@ const QgsProject *QgsConfigCache::project( const QString &path, const QgsServerS
|
||||
QStringLiteral( "Server" ), Qgis::MessageLevel::Critical );
|
||||
}
|
||||
}
|
||||
return mProjectCache[ path ];
|
||||
|
||||
auto entry = mProjectCache[ path ];
|
||||
return entry ? entry->second.get() : nullptr;
|
||||
}
|
||||
|
||||
QDomDocument *QgsConfigCache::xmlDocument( const QString &filePath )
|
||||
@ -152,25 +207,129 @@ QDomDocument *QgsConfigCache::xmlDocument( const QString &filePath )
|
||||
return nullptr;
|
||||
}
|
||||
mXmlDocumentCache.insert( filePath, xmlDoc );
|
||||
mFileSystemWatcher.addPath( filePath );
|
||||
xmlDoc = mXmlDocumentCache.object( filePath );
|
||||
Q_ASSERT( xmlDoc );
|
||||
}
|
||||
return xmlDoc;
|
||||
}
|
||||
|
||||
void QgsConfigCache::removeChangedEntry( const QString &path )
|
||||
|
||||
void QgsConfigCache::cacheProject( const QString &path, QgsProject *project )
|
||||
{
|
||||
mProjectCache.insert( path, new std::pair<QDateTime, std::unique_ptr<QgsProject> >( project->lastModified(), std::unique_ptr<QgsProject>( project ) ) );
|
||||
|
||||
mStrategy->entryInserted( path );
|
||||
}
|
||||
|
||||
void QgsConfigCache::removeEntry( const QString &path )
|
||||
{
|
||||
mProjectCache.remove( path );
|
||||
|
||||
//xml document must be removed last, as other config cache destructors may require it
|
||||
mXmlDocumentCache.remove( path );
|
||||
|
||||
mStrategy->entryRemoved( path );
|
||||
}
|
||||
|
||||
// slots
|
||||
|
||||
void QgsConfigCache::removeChangedEntry( const QString &path )
|
||||
{
|
||||
removeEntry( path );
|
||||
}
|
||||
|
||||
|
||||
void QgsConfigCache::removeChangedEntries()
|
||||
{
|
||||
// QCache::keys returns a QList so it is safe
|
||||
// to mutate while iterating
|
||||
const auto constKeys { mProjectCache.keys() };
|
||||
for ( const auto &path : std::as_const( constKeys ) )
|
||||
{
|
||||
const auto entry = mProjectCache[ path ];
|
||||
if ( entry && entry->first < entry->second->lastModified() )
|
||||
{
|
||||
removeEntry( path );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// File system invalidation strategy
|
||||
|
||||
|
||||
|
||||
QgsFileSystemCacheStrategy::QgsFileSystemCacheStrategy()
|
||||
{
|
||||
}
|
||||
|
||||
void QgsFileSystemCacheStrategy::attach( QgsConfigCache *cache )
|
||||
{
|
||||
QObject::connect( &mFileSystemWatcher, &QFileSystemWatcher::fileChanged, cache, &QgsConfigCache::removeChangedEntry );
|
||||
}
|
||||
|
||||
void QgsFileSystemCacheStrategy::entryRemoved( const QString &path )
|
||||
{
|
||||
mFileSystemWatcher.removePath( path );
|
||||
}
|
||||
|
||||
|
||||
void QgsConfigCache::removeEntry( const QString &path )
|
||||
void QgsFileSystemCacheStrategy::entryInserted( const QString &path )
|
||||
{
|
||||
removeChangedEntry( path );
|
||||
mFileSystemWatcher.addPath( path );
|
||||
}
|
||||
|
||||
// Periodic invalidation strategy
|
||||
|
||||
QgsPeriodicCacheStrategy::QgsPeriodicCacheStrategy( int interval )
|
||||
: mInterval( interval )
|
||||
{
|
||||
}
|
||||
|
||||
void QgsPeriodicCacheStrategy::attach( QgsConfigCache *cache )
|
||||
{
|
||||
QObject::connect( &mTimer, &QTimer::timeout, cache, &QgsConfigCache::removeChangedEntries );
|
||||
}
|
||||
|
||||
|
||||
|
||||
void QgsPeriodicCacheStrategy::entryRemoved( const QString &path )
|
||||
{
|
||||
Q_UNUSED( path )
|
||||
// No-op
|
||||
}
|
||||
|
||||
void QgsPeriodicCacheStrategy::entryInserted( const QString &path )
|
||||
{
|
||||
Q_UNUSED( path )
|
||||
if ( !mTimer.isActive() )
|
||||
{
|
||||
mTimer.start( mInterval );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsPeriodicCacheStrategy::setCheckInterval( int msec )
|
||||
{
|
||||
if ( mTimer.isActive() )
|
||||
{
|
||||
// Restart timer
|
||||
mTimer.start( msec );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Null strategy
|
||||
|
||||
void QgsNullCacheStrategy::attach( QgsConfigCache *cache )
|
||||
{
|
||||
Q_UNUSED( cache )
|
||||
}
|
||||
|
||||
void QgsNullCacheStrategy::entryRemoved( const QString &path )
|
||||
{
|
||||
Q_UNUSED( path )
|
||||
}
|
||||
|
||||
void QgsNullCacheStrategy::entryInserted( const QString &path )
|
||||
{
|
||||
Q_UNUSED( path )
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "qgsconfig.h"
|
||||
|
||||
#include <QCache>
|
||||
#include <QTimer>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QObject>
|
||||
#include <QDomDocument>
|
||||
@ -30,6 +31,44 @@
|
||||
#include "qgsproject.h"
|
||||
#include "qgsserversettings.h"
|
||||
|
||||
#ifndef SIP_RUN
|
||||
|
||||
class QgsConfigCache;
|
||||
|
||||
/**
|
||||
* \ingroup server
|
||||
* \brief Abstract base class for implementing
|
||||
* cache invalidation strategy
|
||||
*
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
class SERVER_EXPORT QgsAbstractCacheStrategy
|
||||
{
|
||||
public:
|
||||
//! The name of the strategy
|
||||
virtual QString name() const = 0;
|
||||
|
||||
/**
|
||||
* Called when an entry is removed from cache.
|
||||
* \param path The path of the project
|
||||
*/
|
||||
virtual void entryRemoved( const QString &path ) = 0;
|
||||
|
||||
/**
|
||||
* Called when an entry is removed from cache.
|
||||
* \param path The path of the project
|
||||
*/
|
||||
virtual void entryInserted( const QString &path ) = 0;
|
||||
|
||||
//! Attache cache to this strategy
|
||||
virtual void attach( QgsConfigCache *cache ) = 0;
|
||||
|
||||
virtual ~QgsAbstractCacheStrategy() = default;
|
||||
|
||||
};
|
||||
|
||||
#endif // SIP_RUN
|
||||
|
||||
/**
|
||||
* \ingroup server
|
||||
* \brief Cache for server configuration.
|
||||
@ -40,6 +79,13 @@ class SERVER_EXPORT QgsConfigCache : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
/**
|
||||
* Initialize from settings.
|
||||
*
|
||||
* This method must be called prior any call to `QgsConfigCache::instance`
|
||||
*/
|
||||
static void initialize( QgsServerSettings *settings );
|
||||
|
||||
/**
|
||||
* Returns the current instance.
|
||||
*/
|
||||
@ -65,21 +111,169 @@ class SERVER_EXPORT QgsConfigCache : public QObject
|
||||
*/
|
||||
const QgsProject *project( const QString &path, const QgsServerSettings *settings = nullptr );
|
||||
|
||||
/**
|
||||
* Returns the name of the current strategy
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
QString strategyName() const { return mStrategy->name(); }
|
||||
|
||||
public:
|
||||
//! Initialize from settings
|
||||
QgsConfigCache( QgsServerSettings *settings );
|
||||
|
||||
//! Initialize with a strategy implementation.
|
||||
QgsConfigCache( QgsAbstractCacheStrategy *strategy ) SIP_SKIP;
|
||||
|
||||
private:
|
||||
// SIP require this
|
||||
QgsConfigCache() SIP_FORCE;
|
||||
|
||||
//! Check for configuration file updates (remove entry from cache if file changes)
|
||||
QFileSystemWatcher mFileSystemWatcher;
|
||||
|
||||
private:
|
||||
//! Returns xml document for project file / sld or 0 in case of errors
|
||||
QDomDocument *xmlDocument( const QString &filePath );
|
||||
|
||||
QCache<QString, QDomDocument> mXmlDocumentCache;
|
||||
QCache<QString, QgsProject> mProjectCache;
|
||||
QCache<QString, std::pair<QDateTime, std::unique_ptr<QgsProject> > > mProjectCache;
|
||||
|
||||
private slots:
|
||||
//! Removes changed entry from this cache
|
||||
std::unique_ptr<QgsAbstractCacheStrategy> mStrategy;
|
||||
|
||||
private:
|
||||
//! Insert project in cache
|
||||
void cacheProject( const QString &path, QgsProject *project );
|
||||
|
||||
static QgsConfigCache *sInstance;
|
||||
|
||||
public slots:
|
||||
//! Remove cache entry
|
||||
void removeChangedEntry( const QString &path );
|
||||
|
||||
//! Remove all changed cache entries
|
||||
void removeChangedEntries();
|
||||
};
|
||||
|
||||
|
||||
#ifndef SIP_RUN
|
||||
|
||||
/**
|
||||
* \ingroup server
|
||||
* \brief File system cache strategy for server configuration
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
class SERVER_EXPORT QgsFileSystemCacheStrategy : public QgsAbstractCacheStrategy
|
||||
{
|
||||
public:
|
||||
//! Creates a new filesystem strategy
|
||||
QgsFileSystemCacheStrategy();
|
||||
|
||||
//! The name of the strategy
|
||||
QString name() const override { return QStringLiteral( "filesystem" ); };
|
||||
|
||||
//! Attach cache to this strategy
|
||||
void attach( QgsConfigCache *cache ) override;
|
||||
|
||||
/**
|
||||
* Called when an entry is removed from cache.
|
||||
* \param path The path of the project
|
||||
*/
|
||||
void entryRemoved( const QString &path ) override;
|
||||
|
||||
/**
|
||||
* Called when an entry is inserted
|
||||
* \param path The path of the project
|
||||
*/
|
||||
void entryInserted( const QString &path ) override;
|
||||
|
||||
private:
|
||||
//! For invalidating files
|
||||
QFileSystemWatcher mFileSystemWatcher;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup server
|
||||
* \brief Periodic system cache strategy for server configuration
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
class SERVER_EXPORT QgsPeriodicCacheStrategy: public QgsAbstractCacheStrategy
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Creates a new periodic strategy
|
||||
* \param interval The invalidation check interval in milliseconds
|
||||
*/
|
||||
QgsPeriodicCacheStrategy( int interval = 3000 );
|
||||
|
||||
//! The name of the strategy
|
||||
QString name() const override { return QStringLiteral( "periodic" ); };
|
||||
|
||||
/**
|
||||
* Sets the invalidation check interval for PeriodicStrategy
|
||||
*
|
||||
* If \a msec is set to a positive value then the cache will check for invalidation
|
||||
* every \a msec milliseconds
|
||||
*/
|
||||
void setCheckInterval( int msec );
|
||||
|
||||
//! Returns the invalidation check interval
|
||||
int checkInterval() const { return mInterval; }
|
||||
|
||||
//! Attaches cache to this strategy
|
||||
void attach( QgsConfigCache *owner ) override;
|
||||
|
||||
/**
|
||||
* Called when an entry is removed from cache.
|
||||
* \param path The path of the project
|
||||
*/
|
||||
void entryRemoved( const QString &path ) override;
|
||||
|
||||
/**
|
||||
* Called when an entry is inserted
|
||||
* \param path The path of the project
|
||||
*/
|
||||
void entryInserted( const QString &path ) override;
|
||||
|
||||
private:
|
||||
//! Timer for checking cache invalidation
|
||||
QTimer mTimer;
|
||||
|
||||
//! Interval in ms between two invalidation check
|
||||
int mInterval;
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup server
|
||||
* \brief Null system cache strategy for server configuration,
|
||||
* completely disable cache invalidation
|
||||
* invalidation
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
class SERVER_EXPORT QgsNullCacheStrategy: public QgsAbstractCacheStrategy
|
||||
{
|
||||
public:
|
||||
//! Creates a new null strategy
|
||||
QgsNullCacheStrategy() = default;
|
||||
|
||||
//! The name of the strategy
|
||||
QString name() const override { return QStringLiteral( "off" ); };
|
||||
|
||||
//! Attaches cache to this strategy
|
||||
void attach( QgsConfigCache *owner ) override;
|
||||
|
||||
/**
|
||||
* Called when an entry is removed from cache.
|
||||
* \param path The path of the project
|
||||
*/
|
||||
void entryRemoved( const QString &path ) override;
|
||||
|
||||
/**
|
||||
* Called when an entry is inserted
|
||||
* \param path The path of the project
|
||||
*/
|
||||
void entryInserted( const QString &path ) override;
|
||||
};
|
||||
|
||||
#endif // SIP_RUN
|
||||
|
||||
#endif // QGSCONFIGCACHE_H
|
||||
|
||||
|
@ -365,6 +365,9 @@ bool QgsServer::init()
|
||||
// qDebug() << QStringLiteral( "Initializing server modules from: %1" ).arg( modulePath );
|
||||
sServiceRegistry->init( modulePath, sServerInterface );
|
||||
|
||||
// Initialize config cache
|
||||
QgsConfigCache::initialize( sSettings );
|
||||
|
||||
sInitialized = true;
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Server initialized" ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
|
||||
return true;
|
||||
@ -453,13 +456,14 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
|
||||
printRequestParameters( params.toMap(), logLevel );
|
||||
|
||||
// Setup project (config file path)
|
||||
if ( ! project )
|
||||
if ( !project )
|
||||
{
|
||||
const QString configFilePath = configPath( *sConfigFilePath, params.map() );
|
||||
|
||||
// load the project if needed and not empty
|
||||
if ( ! configFilePath.isEmpty() )
|
||||
{
|
||||
// Note that QgsConfigCache::project( ... ) call QgsProject::setInstance(...)
|
||||
project = mConfigCache->project( configFilePath, sServerInterface->serverSettings() );
|
||||
}
|
||||
}
|
||||
@ -483,6 +487,7 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
|
||||
// Dispatcher: if SERVICE is set, we assume a OWS service, if not, let's try an API
|
||||
// TODO: QGIS 4 fix the OWS services and treat them as APIs
|
||||
QgsServerApi *api = nullptr;
|
||||
|
||||
if ( params.service().isEmpty() && ( api = sServiceRegistry->apiForRequest( request ) ) )
|
||||
{
|
||||
const QgsServerApiContext context { api->rootPath(), &request, &responseDecorator, project, sServerInterface };
|
||||
@ -490,7 +495,6 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Project is mandatory for OWS at this point
|
||||
if ( ! project )
|
||||
{
|
||||
|
@ -333,6 +333,29 @@ void QgsServerSettings::initSettings()
|
||||
QVariant()
|
||||
};
|
||||
mSettings[ sServiceUrl.envVar ] = sWmtsServiceUrl;
|
||||
|
||||
// the default config cache check interval
|
||||
const Setting sConfigCacheCheckInterval = { QgsServerSettingsEnv::QGIS_SERVER_PROJECT_CACHE_CHECK_INTERVAL,
|
||||
QgsServerSettingsEnv::DEFAULT_VALUE,
|
||||
QStringLiteral( "The default project cache check interval" ),
|
||||
QStringLiteral( "/qgis/server_project_cache_check_interval" ),
|
||||
QVariant::Int,
|
||||
QVariant( 0 ),
|
||||
QVariant()
|
||||
};
|
||||
mSettings[ sConfigCacheCheckInterval.envVar ] = sConfigCacheCheckInterval;
|
||||
|
||||
// the default config cache strategy
|
||||
const Setting sProjectCacheStrategy = { QgsServerSettingsEnv::QGIS_SERVER_PROJECT_CACHE_STRATEGY,
|
||||
QgsServerSettingsEnv::DEFAULT_VALUE,
|
||||
QStringLiteral( "Project's cache strategy. Possible values are 'off','filesystem' or 'periodic'" ),
|
||||
QStringLiteral( "/qgis/server_project_cache_strategy" ),
|
||||
QVariant::String,
|
||||
QVariant( "" ),
|
||||
QVariant()
|
||||
};
|
||||
mSettings[ sProjectCacheStrategy.envVar ] = sProjectCacheStrategy;
|
||||
|
||||
}
|
||||
|
||||
void QgsServerSettings::load()
|
||||
@ -615,3 +638,22 @@ QString QgsServerSettings::serviceUrl( const QString &service ) const
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int QgsServerSettings::projectCacheCheckInterval() const
|
||||
{
|
||||
return value( QgsServerSettingsEnv::QGIS_SERVER_PROJECT_CACHE_CHECK_INTERVAL ).toInt();
|
||||
}
|
||||
|
||||
QString QgsServerSettings::projectCacheStrategy() const
|
||||
{
|
||||
QString result = value( QgsServerSettingsEnv::QGIS_SERVER_PROJECT_CACHE_STRATEGY ).toString();
|
||||
if ( result.compare( QLatin1String( "filesystem" ) ) &&
|
||||
result.compare( QLatin1String( "periodic" ) ) &&
|
||||
result.compare( QLatin1String( "off" ) ) )
|
||||
{
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Invalid cache strategy, expecting 'filesystem', 'periodic' or 'off'. Using 'filesystem' as default." ), "Server", Qgis::MessageLevel::Warning );
|
||||
result = QStringLiteral( "filesystem" );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,8 @@ class SERVER_EXPORT QgsServerSettingsEnv : public QObject
|
||||
QGIS_SERVER_WCS_SERVICE_URL, //!< To set the WCS service URL if it's not present in the project. (since QGIS 3.20).
|
||||
QGIS_SERVER_WMTS_SERVICE_URL, //!< To set the WMTS service URL if it's not present in the project. (since QGIS 3.20).
|
||||
QGIS_SERVER_LANDING_PAGE_PREFIX, //! Prefix of the path component of the landing page base URL, default is empty (since QGIS 3.20).
|
||||
QGIS_SERVER_PROJECT_CACHE_CHECK_INTERVAL, //! Set the interval for cache invalidation strategy 'interval', default to 0 which select the legacy File system watcher (since QGIS 3.26).
|
||||
QGIS_SERVER_PROJECT_CACHE_STRATEGY, //! Set the project cache strategy. Possible values are 'filesystem', 'periodic' or 'off' (since QGIS 3.26).
|
||||
};
|
||||
Q_ENUM( EnvVar )
|
||||
};
|
||||
@ -294,6 +296,27 @@ class SERVER_EXPORT QgsServerSettings
|
||||
*/
|
||||
QString serviceUrl( const QString &service ) const;
|
||||
|
||||
/**
|
||||
* Returns the config cache check interval for the 'periodic' strategy.
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
int projectCacheCheckInterval() const;
|
||||
|
||||
/**
|
||||
* Returns the project's cache strategy
|
||||
* The default value is 'filesystem', the value can be changed by setting the environment
|
||||
* variable QGIS_SERVER_PROJECT_CACHE_STRATEGY.
|
||||
* Possible values are:
|
||||
*
|
||||
* - 'filesystem': Use file system watcher for notifying projects change. Note that it works
|
||||
* only with projects stored in files and not across mounted NFS volumes on Linux.
|
||||
* - 'periodic': Timer based periodic check for project's changes. Works with all storage backend.
|
||||
* - 'off': Disable completely internal project's cache handling
|
||||
*
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
QString projectCacheStrategy() const;
|
||||
|
||||
/**
|
||||
* Returns the string representation of a setting.
|
||||
* \since QGIS 3.16
|
||||
|
@ -510,4 +510,5 @@ if (WITH_SERVER)
|
||||
ADD_PYTHON_TEST(PyQgsServerModules test_qgsserver_modules.py)
|
||||
ADD_PYTHON_TEST(PyQgsServerRequest test_qgsserver_request.py)
|
||||
ADD_PYTHON_TEST(PyQgsServerResponse test_qgsserver_response.py)
|
||||
ADD_PYTHON_TEST(PyQgsServerConfigCache test_qgsserver_configcache.py)
|
||||
endif()
|
||||
|
204
tests/src/python/test_qgsserver_configcache.py
Normal file
204
tests/src/python/test_qgsserver_configcache.py
Normal file
@ -0,0 +1,204 @@
|
||||
"""QGIS Unit tests for QgsConfigCache
|
||||
|
||||
From build dir, run: ctest -R PyQgsServerConfigCache -V
|
||||
|
||||
.. note:: This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
"""
|
||||
__author__ = 'David Marteau'
|
||||
__date__ = '28/01/2022'
|
||||
__copyright__ = 'Copyright 2015, The QGIS Project'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import qgis # NOQA
|
||||
|
||||
from pathlib import Path
|
||||
from time import time
|
||||
|
||||
from qgis.testing import unittest
|
||||
from utilities import unitTestDataPath
|
||||
from qgis.server import (QgsConfigCache,
|
||||
QgsServerSettings)
|
||||
from qgis.core import QgsApplication, QgsProject
|
||||
|
||||
from test_qgsserver import QgsServerTestBase
|
||||
|
||||
|
||||
class TestQgsServerConfigCache(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
"""Run before all tests"""
|
||||
self._app = QgsApplication([], False)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
"""Run after all tests"""
|
||||
self._app.exitQgis
|
||||
|
||||
def test_periodic_cache_strategy(self):
|
||||
|
||||
path = Path(unitTestDataPath('qgis_server_project')) / 'project.qgs'
|
||||
self.assertTrue(path.exists())
|
||||
|
||||
os.environ['QGIS_SERVER_PROJECT_CACHE_STRATEGY'] = 'periodic'
|
||||
os.environ['QGIS_SERVER_PROJECT_CACHE_CHECK_INTERVAL'] = '3000'
|
||||
settings = QgsServerSettings()
|
||||
settings.load()
|
||||
|
||||
self.assertEqual(settings.projectCacheStrategy(), 'periodic')
|
||||
self.assertEqual(settings.projectCacheCheckInterval(), 3000)
|
||||
|
||||
cache = QgsConfigCache(settings)
|
||||
|
||||
self.assertEqual(cache.strategyName(), "periodic")
|
||||
|
||||
prj1 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj1)
|
||||
self.assertEqual(prj1.readEntry("TestConfigCache", "/", ""), ("", False))
|
||||
|
||||
# Change entry
|
||||
prj1.writeEntry("TestConfigCache", "/", "foobar")
|
||||
|
||||
# Query cache again
|
||||
prj2 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj2)
|
||||
# Ensure project is not reloaded
|
||||
self.assertEqual(prj2.readEntry("TestConfigCache", "/"), ('foobar', True))
|
||||
|
||||
# Change project modified time
|
||||
path.touch()
|
||||
|
||||
# Some delay
|
||||
st = time()
|
||||
while time() - st < 1:
|
||||
self._app.processEvents()
|
||||
|
||||
# Query cache again
|
||||
# Project should no be reloaded yet
|
||||
prj3 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj3)
|
||||
# Ensure project is not reloaded
|
||||
self.assertEqual(prj3.readEntry("TestConfigCache", "/"), ('foobar', True))
|
||||
|
||||
# Some delay
|
||||
st = time()
|
||||
while time() - st < 3:
|
||||
self._app.processEvents()
|
||||
|
||||
# Project should be reloaded now
|
||||
prj4 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj4)
|
||||
# Ensure project is reloaded
|
||||
self.assertEqual(prj4.readEntry("TestConfigCache", "/", ""), ("", False))
|
||||
|
||||
def test_file_watcher_invalidation(self):
|
||||
|
||||
path = Path(unitTestDataPath('qgis_server_project')) / 'project.qgs'
|
||||
self.assertTrue(path.exists())
|
||||
|
||||
os.environ['QGIS_SERVER_PROJECT_CACHE_STRATEGY'] = 'filesystem'
|
||||
settings = QgsServerSettings()
|
||||
settings.load()
|
||||
|
||||
self.assertEqual(settings.projectCacheStrategy(), 'filesystem')
|
||||
|
||||
cache = QgsConfigCache(settings)
|
||||
|
||||
self.assertEqual(cache.strategyName(), "filesystem")
|
||||
|
||||
prj1 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj1)
|
||||
self.assertEqual(prj1.readEntry("TestConfigCache", "/", ""), ("", False))
|
||||
|
||||
# Change entry
|
||||
prj1.writeEntry("TestConfigCache", "/", "foobaz")
|
||||
|
||||
# Query cache again
|
||||
prj2 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj2)
|
||||
# Ensure project is not reloaded
|
||||
self.assertEqual(prj2.readEntry("TestConfigCache", "/"), ('foobaz', True))
|
||||
|
||||
# Change project modified time
|
||||
path.touch()
|
||||
|
||||
# Some delay
|
||||
st = time()
|
||||
while time() - st < 1:
|
||||
self._app.processEvents()
|
||||
|
||||
# Query cache again
|
||||
# Project should be reloaded
|
||||
prj3 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj3)
|
||||
# Ensure project is reloaded
|
||||
self.assertEqual(prj3.readEntry("TestConfigCache", "/"), ("", False))
|
||||
|
||||
def test_null_invalidation(self):
|
||||
|
||||
path = Path(unitTestDataPath('qgis_server_project')) / 'project.qgs'
|
||||
self.assertTrue(path.exists())
|
||||
|
||||
os.environ['QGIS_SERVER_PROJECT_CACHE_STRATEGY'] = 'off'
|
||||
settings = QgsServerSettings()
|
||||
settings.load()
|
||||
|
||||
self.assertEqual(settings.projectCacheStrategy(), 'off')
|
||||
|
||||
cache = QgsConfigCache(settings)
|
||||
|
||||
self.assertEqual(cache.strategyName(), "off")
|
||||
|
||||
prj1 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj1)
|
||||
self.assertEqual(prj1.readEntry("TestConfigCache", "/", ""), ("", False))
|
||||
|
||||
# Change entry
|
||||
prj1.writeEntry("TestConfigCache", "/", "barbaz")
|
||||
|
||||
# Query cache again
|
||||
prj2 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj2)
|
||||
# Ensure project is not reloaded
|
||||
self.assertEqual(prj2.readEntry("TestConfigCache", "/"), ('barbaz', True))
|
||||
|
||||
# Change project modified time
|
||||
path.touch()
|
||||
|
||||
# Some delay
|
||||
st = time()
|
||||
while time() - st < 2:
|
||||
self._app.processEvents()
|
||||
|
||||
# Query cache again
|
||||
# Project should no be reloaded
|
||||
prj3 = cache.project(str(path))
|
||||
self.assertIsNotNone(prj3)
|
||||
# Ensure project is reloaded
|
||||
self.assertEqual(prj3.readEntry("TestConfigCache", "/"), ("barbaz", True))
|
||||
|
||||
def test_default_strategy_setting(self):
|
||||
|
||||
os.environ['QGIS_SERVER_PROJECT_CACHE_STRATEGY'] = ''
|
||||
settings = QgsServerSettings()
|
||||
settings.load()
|
||||
|
||||
self.assertEqual(settings.projectCacheStrategy(), 'filesystem')
|
||||
|
||||
def test_initialized_instance(self):
|
||||
|
||||
os.environ['QGIS_SERVER_PROJECT_CACHE_STRATEGY'] = 'off'
|
||||
settings = QgsServerSettings()
|
||||
settings.load()
|
||||
|
||||
QgsConfigCache.initialize(settings)
|
||||
|
||||
self.assertEqual(QgsConfigCache.instance().strategyName(), 'off')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user