[geonode] Don't block data source manager while connecting to a server

Also add missing docstrings
This commit is contained in:
Nyall Dawson 2017-09-08 11:54:39 +10:00
parent 1a19283634
commit e1562df16b
10 changed files with 517 additions and 227 deletions

View File

@ -12,15 +12,43 @@ struct QgsGeoNodeStyle
%TypeHeaderCode
#include <qgsgeonoderequest.h>
%End
QString id;
%Docstring
Unique style ID
%End
QString name;
%Docstring
Style name
%End
QString title;
%Docstring
Style title
%End
QDomDocument body;
%Docstring
DOM documenting containing style
%End
QString styleUrl;
%Docstring
Associated URL
%End
};
class QgsGeoNodeRequest : QObject
{
%Docstring
Request handler for GeoNode servers.
QgsGeoNodeRequest handles requesting and parsing service details from a GeoNode
server instance, for instance requesting all available layers or layer styles.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgsgeonoderequest.h"
@ -30,110 +58,192 @@ class QgsGeoNodeRequest : QObject
struct ServiceLayerDetail
{
QUuid uuid;
%Docstring
Unique identifier (generate on the client side, not at the GeoNode server)
%End
QString name;
%Docstring
Layer name
%End
QString typeName;
%Docstring
Layer type name
%End
QString title;
%Docstring
Layer title
%End
QString wmsURL;
%Docstring
WMS URL for layer
%End
QString wfsURL;
%Docstring
WFS URL for layer
%End
QString xyzURL;
%Docstring
XYZ tileserver URL for layer
%End
};
explicit QgsGeoNodeRequest( bool forceRefresh, QObject *parent = 0 );
QgsGeoNodeRequest( const QString &baseUrl, bool forceRefresh, QObject *parent = 0 );
%Docstring
Constructor for QgsGeoNodeRequest.
If ``forceRefresh`` is false, then cached copies of the request may be reused.
%End
QgsGeoNodeRequest( const QString &baseUrl, bool forceRefresh, QObject *parent = 0 );
virtual ~QgsGeoNodeRequest();
bool request( const QString &endPoint );
void request( const QString &endPoint );
%Docstring
Triggers a new request to the GeoNode server, with the requested ``endPoint``.
Any existing request will be aborted.
Calling this method does not block while waiting for a result.
\warning When using the non-blocking methods in this class, sending
overlapping requests results in undefined behavior. Use separate instances
of QgsGeoNodeRequest instead to avoid this.
.. seealso:: requestBlocking()
%End
bool requestBlocking( const QString &endPoint );
%Docstring
Triggers a new request to the GeoNode server, with the requested ``endPoint``.
Any existing request will be aborted.
Calling this method will block while waiting for a result. It should not be
used from any code which potentially blocks operation in the main GUI thread.
.. seealso:: request()
:rtype: bool
%End
QList<QgsGeoNodeRequest::ServiceLayerDetail> getLayers();
void fetchLayers();
%Docstring
Triggers a new request to fetch the list of available layers from the
server. When complete, the layersFetched() signal will be emitted
with the result.
This method is non-blocking and returns immediately.
\warning When using the non-blocking methods in this class, sending
overlapping requests results in undefined behavior. Use separate instances
of QgsGeoNodeRequest instead to avoid this.
.. seealso:: layersFetched()
.. seealso:: fetchLayersBlocking()
%End
QList<QgsGeoNodeRequest::ServiceLayerDetail> fetchLayersBlocking();
%Docstring
Requests the list of available layers from the server.
This method is blocking and will wait for results from the server before returning.
Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
.. seealso:: fetchLayers()
:rtype: list of QgsGeoNodeRequest.ServiceLayerDetail
%End
QList<QgsGeoNodeStyle> getStyles( const QString &layerName );
QList<QgsGeoNodeStyle> fetchStylesBlocking( const QString &layerName );
%Docstring
Requests the list of available styles for the layer
with matching ``layerName`` from the server.
This method is blocking and will wait for results from the server before returning.
Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
:rtype: list of QgsGeoNodeStyle
%End
QgsGeoNodeStyle getDefaultStyle( const QString &layerName );
QgsGeoNodeStyle fetchDefaultStyleBlocking( const QString &layerName );
%Docstring
Requests the default style for the layer with matching ``layerName`` from the server.
This method is blocking and will wait for results from the server before returning.
Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
:rtype: QgsGeoNodeStyle
%End
QgsGeoNodeStyle getStyle( const QString &styleID );
QgsGeoNodeStyle fetchStyleBlocking( const QString &styleId );
%Docstring
Requests the details for the style with matching ``styleId`` from the server.
This method is blocking and will wait for results from the server before returning.
Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
:rtype: QgsGeoNodeStyle
%End
QStringList serviceUrls( const QString &serviceType );
QStringList fetchServiceUrlsBlocking( const QString &serviceType );
%Docstring
Obtain list of unique URLs in the geonode
Requests the list of unique URLs for available services with matching ``serviceType`` from the server.
This method is blocking and will wait for results from the server before returning.
Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
:rtype: list of str
%End
QgsStringMap serviceUrlData( const QString &serviceType );
QgsStringMap fetchServiceUrlDataBlocking( const QString &serviceType );
%Docstring
Obtain map of layer name and url for a service type
Obtains a map of layer name to URL for available services with matching ``serviceType`` from the server.
This method is blocking and will wait for results from the server before returning.
Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
:rtype: QgsStringMap
%End
QString lastError() const;
%Docstring
Returns the most recent error string for any encountered errors, or an empty string if
no errors have been encountered.
:rtype: str
%End
QByteArray response() const;
QByteArray lastResponse() const;
%Docstring
Returns the most recent response obtained from the server.
:rtype: QByteArray
%End
QNetworkReply *reply() const;
QString protocol() const;
%Docstring
:rtype: QNetworkReply
Returns the network protocol (e.g. 'http') used for connecting with the server.
.. seealso:: setProtocol()
:rtype: str
%End
void setProtocol( const QString &protocol );
%Docstring
Sets the network ``protocol`` (e.g. 'http') used for connecting with the server.
.. seealso:: protocol()
%End
public slots:
void abort();
%Docstring
Abort network request immediately
Aborts any active network request immediately.
%End
QString getProtocol() const;
%Docstring
:rtype: str
%End
void setProtocol( const QString &protocol );
signals:
void statusChanged( const QString &statusQString );
%Docstring
emit a signal to be caught by qgisapp and display a statusQString on status bar
Emitted when the status of an ongoing request is changed.
%End
void requestFinished();
%Docstring
emit a signal once the request is finished
Emitted when the existing request has been completed.
%End
protected slots:
void replyFinished();
void replyProgress( qint64, qint64 );
protected:
void layersFetched( const QList<QgsGeoNodeRequest::ServiceLayerDetail> &layers );
%Docstring
Emitted when the result of a fetchLayers call has been received and processed.
%End
};

View File

@ -38,9 +38,9 @@ QVector<QgsDataItem *> QgsGeoNodeConnectionItem::createChildren()
QString url = mConnection->uri().param( QStringLiteral( "url" ) );
QgsGeoNodeRequest geonodeRequest( url, true );
QStringList wmsUrl = geonodeRequest.serviceUrls( QStringLiteral( "WMS" ) );
QStringList wfsUrl = geonodeRequest.serviceUrls( QStringLiteral( "WFS" ) );
QStringList xyzUrl = geonodeRequest.serviceUrls( QStringLiteral( "XYZ" ) );
QStringList wmsUrl = geonodeRequest.fetchServiceUrlsBlocking( QStringLiteral( "WMS" ) );
QStringList wfsUrl = geonodeRequest.fetchServiceUrlsBlocking( QStringLiteral( "WFS" ) );
QStringList xyzUrl = geonodeRequest.fetchServiceUrlsBlocking( QStringLiteral( "XYZ" ) );
if ( !wmsUrl.isEmpty() )
{

View File

@ -249,7 +249,7 @@ void QgsGeoNodeNewConnection::testConnection()
QString url = txtUrl->text();
QgsGeoNodeRequest geonodeRequest( url, true );
QList<QgsGeoNodeRequest::ServiceLayerDetail> layers = geonodeRequest.getLayers();
QList<QgsGeoNodeRequest::ServiceLayerDetail> layers = geonodeRequest.fetchLayersBlocking();
QApplication::restoreOverrideCursor();
if ( !layers.empty() )

View File

@ -72,6 +72,11 @@ QgsGeoNodeSourceSelect::QgsGeoNodeSourceSelect( QWidget *parent, Qt::WindowFlags
treeView->setModel( mModelProxy );
}
QgsGeoNodeSourceSelect::~QgsGeoNodeSourceSelect()
{
emit abortRequests();
}
void QgsGeoNodeSourceSelect::addConnectionsEntryList()
{
QgsGeoNodeNewConnection nc( this );
@ -147,141 +152,146 @@ void QgsGeoNodeSourceSelect::showHelp()
void QgsGeoNodeSourceSelect::connectToGeonodeConnection()
{
QApplication::setOverrideCursor( Qt::BusyCursor );
QgsGeoNodeConnection connection( cmbConnections->currentText() );
QString url = connection.uri().param( QStringLiteral( "url" ) );
QgsGeoNodeRequest geonodeRequest( url, true );
QApplication::setOverrideCursor( Qt::WaitCursor );
const QList<QgsGeoNodeRequest::ServiceLayerDetail> layers = geonodeRequest.getLayers();
QApplication::restoreOverrideCursor();
if ( !layers.empty() )
QgsGeoNodeRequest *geonodeRequest = new QgsGeoNodeRequest( url, true );
connect( this, &QgsGeoNodeSourceSelect::abortRequests, geonodeRequest, &QgsGeoNodeRequest::abort );
connect( geonodeRequest, &QgsGeoNodeRequest::requestFinished, geonodeRequest, [geonodeRequest]
{
QgsDebugMsg( QStringLiteral( "Success, non empty layers %1" ).arg( layers.count( ) ) );
}
else
QApplication::restoreOverrideCursor();
geonodeRequest->deleteLater();
} );
connect( geonodeRequest, &QgsGeoNodeRequest::layersFetched, this, [ = ]( const QList< QgsGeoNodeRequest::ServiceLayerDetail > layers )
{
QgsMessageLog::logMessage( QStringLiteral( "Failed, empty layers" ), tr( "GeoNode" ) );
}
if ( mModel )
{
mModel->removeRows( 0, mModel->rowCount() );
}
if ( !layers.isEmpty() )
{
for ( const QgsGeoNodeRequest::ServiceLayerDetail &layer : layers )
if ( !layers.empty() )
{
QUuid uuid = layer.uuid;
QgsDebugMsg( QStringLiteral( "Success, non empty layers %1" ).arg( layers.count( ) ) );
}
else
{
QgsMessageLog::logMessage( QStringLiteral( "Failed, empty layers" ), tr( "GeoNode" ) );
}
QString wmsURL = layer.wmsURL;
QString wfsURL = layer.wfsURL;
QString xyzURL = layer.xyzURL;
if ( mModel )
{
mModel->removeRows( 0, mModel->rowCount() );
}
if ( !wmsURL.isEmpty() )
if ( !layers.isEmpty() )
{
for ( const QgsGeoNodeRequest::ServiceLayerDetail &layer : layers )
{
QStandardItem *titleItem = new QStandardItem( layer.title );
QStandardItem *nameItem;
if ( !layer.name.isEmpty() )
QUuid uuid = layer.uuid;
QString wmsURL = layer.wmsURL;
QString wfsURL = layer.wfsURL;
QString xyzURL = layer.xyzURL;
if ( !wmsURL.isEmpty() )
{
nameItem = new QStandardItem( layer.name );
QStandardItem *titleItem = new QStandardItem( layer.title );
QStandardItem *nameItem;
if ( !layer.name.isEmpty() )
{
nameItem = new QStandardItem( layer.name );
}
else
{
nameItem = new QStandardItem( layer.title );
}
QStandardItem *serviceTypeItem = new QStandardItem( tr( "Layer" ) );
QStandardItem *webServiceTypeItem = new QStandardItem( tr( "WMS" ) );
QString typeName = layer.typeName;
titleItem->setData( uuid, Qt::UserRole + 1 );
titleItem->setData( wmsURL, Qt::UserRole + 2 );
titleItem->setData( typeName, Qt::UserRole + 3 );
typedef QList< QStandardItem * > StandardItemList;
mModel->appendRow( StandardItemList() << titleItem << nameItem << serviceTypeItem << webServiceTypeItem );
}
else
{
nameItem = new QStandardItem( layer.title );
QgsDebugMsgLevel( QStringLiteral( "Layer %1 does not have WMS url." ).arg( layer.title ), 3 );
}
QStandardItem *serviceTypeItem = new QStandardItem( tr( "Layer" ) );
QStandardItem *webServiceTypeItem = new QStandardItem( tr( "WMS" ) );
QString typeName = layer.typeName;
titleItem->setData( uuid, Qt::UserRole + 1 );
titleItem->setData( wmsURL, Qt::UserRole + 2 );
titleItem->setData( typeName, Qt::UserRole + 3 );
typedef QList< QStandardItem * > StandardItemList;
mModel->appendRow( StandardItemList() << titleItem << nameItem << serviceTypeItem << webServiceTypeItem );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Layer %1 does not have WMS url." ).arg( layer.title ), 3 );
}
if ( !wfsURL.isEmpty() )
{
QStandardItem *titleItem = new QStandardItem( layer.title );
QStandardItem *nameItem;
if ( !layer.name.isEmpty() )
if ( !wfsURL.isEmpty() )
{
nameItem = new QStandardItem( layer.name );
QStandardItem *titleItem = new QStandardItem( layer.title );
QStandardItem *nameItem;
if ( !layer.name.isEmpty() )
{
nameItem = new QStandardItem( layer.name );
}
else
{
nameItem = new QStandardItem( layer.title );
}
QStandardItem *serviceTypeItem = new QStandardItem( tr( "Layer" ) );
QStandardItem *webServiceTypeItem = new QStandardItem( tr( "WFS" ) );
QString typeName = layer.typeName;
titleItem->setData( uuid, Qt::UserRole + 1 );
titleItem->setData( wfsURL, Qt::UserRole + 2 );
titleItem->setData( typeName, Qt::UserRole + 3 );
typedef QList< QStandardItem * > StandardItemList;
mModel->appendRow( StandardItemList() << titleItem << nameItem << serviceTypeItem << webServiceTypeItem );
}
else
{
nameItem = new QStandardItem( layer.title );
QgsDebugMsgLevel( QStringLiteral( "Layer %1 does not have WFS url." ).arg( layer.title ), 3 );
}
QStandardItem *serviceTypeItem = new QStandardItem( tr( "Layer" ) );
QStandardItem *webServiceTypeItem = new QStandardItem( tr( "WFS" ) );
QString typeName = layer.typeName;
titleItem->setData( uuid, Qt::UserRole + 1 );
titleItem->setData( wfsURL, Qt::UserRole + 2 );
titleItem->setData( typeName, Qt::UserRole + 3 );
typedef QList< QStandardItem * > StandardItemList;
mModel->appendRow( StandardItemList() << titleItem << nameItem << serviceTypeItem << webServiceTypeItem );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Layer %1 does not have WFS url." ).arg( layer.title ), 3 );
}
if ( !xyzURL.isEmpty() )
{
QStandardItem *titleItem = new QStandardItem( layer.title );
QStandardItem *nameItem;
if ( !layer.name.isEmpty() )
if ( !xyzURL.isEmpty() )
{
nameItem = new QStandardItem( layer.name );
QStandardItem *titleItem = new QStandardItem( layer.title );
QStandardItem *nameItem;
if ( !layer.name.isEmpty() )
{
nameItem = new QStandardItem( layer.name );
}
else
{
nameItem = new QStandardItem( layer.title );
}
QStandardItem *serviceTypeItem = new QStandardItem( tr( "Layer" ) );
QStandardItem *webServiceTypeItem = new QStandardItem( tr( "XYZ" ) );
QString typeName = layer.typeName;
titleItem->setData( uuid, Qt::UserRole + 1 );
titleItem->setData( xyzURL, Qt::UserRole + 2 );
titleItem->setData( typeName, Qt::UserRole + 3 );
typedef QList< QStandardItem * > StandardItemList;
mModel->appendRow( StandardItemList() << titleItem << nameItem << serviceTypeItem << webServiceTypeItem );
}
else
{
nameItem = new QStandardItem( layer.title );
QgsDebugMsgLevel( QStringLiteral( "Layer %1 does not have XYZ url." ).arg( layer.title ), 3 );
}
QStandardItem *serviceTypeItem = new QStandardItem( tr( "Layer" ) );
QStandardItem *webServiceTypeItem = new QStandardItem( tr( "XYZ" ) );
QString typeName = layer.typeName;
titleItem->setData( uuid, Qt::UserRole + 1 );
titleItem->setData( xyzURL, Qt::UserRole + 2 );
titleItem->setData( typeName, Qt::UserRole + 3 );
typedef QList< QStandardItem * > StandardItemList;
mModel->appendRow( StandardItemList() << titleItem << nameItem << serviceTypeItem << webServiceTypeItem );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Layer %1 does not have XYZ url." ).arg( layer.title ), 3 );
}
}
}
else
{
QMessageBox::critical( this, tr( "Connect to GeoNode" ), tr( "Cannot get any feature services" ) );
}
treeView->resizeColumnToContents( MODEL_IDX_TITLE );
treeView->resizeColumnToContents( MODEL_IDX_NAME );
treeView->resizeColumnToContents( MODEL_IDX_TYPE );
treeView->resizeColumnToContents( MODEL_IDX_WEB_SERVICE );
for ( int i = MODEL_IDX_TITLE; i < MODEL_IDX_WEB_SERVICE; i++ )
{
if ( treeView->columnWidth( i ) > 210 )
else
{
treeView->setColumnWidth( i, 210 );
QMessageBox::critical( this, tr( "Connect to GeoNode" ), tr( "Cannot get any feature services" ) );
}
}
QApplication::restoreOverrideCursor();
treeView->resizeColumnToContents( MODEL_IDX_TITLE );
treeView->resizeColumnToContents( MODEL_IDX_NAME );
treeView->resizeColumnToContents( MODEL_IDX_TYPE );
treeView->resizeColumnToContents( MODEL_IDX_WEB_SERVICE );
for ( int i = MODEL_IDX_TITLE; i < MODEL_IDX_WEB_SERVICE; i++ )
{
if ( treeView->columnWidth( i ) > 210 )
{
treeView->setColumnWidth( i, 210 );
}
}
} );
QApplication::setOverrideCursor( Qt::BusyCursor );
geonodeRequest->fetchLayers();
}
void QgsGeoNodeSourceSelect::saveGeonodeConnection()

View File

@ -42,11 +42,16 @@ class QgsGeoNodeSourceSelect: public QgsAbstractDataSourceWidget, private Ui::Qg
public:
QgsGeoNodeSourceSelect( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags, QgsProviderRegistry::WidgetMode widgetMode = QgsProviderRegistry::WidgetMode::None );
~QgsGeoNodeSourceSelect();
public slots:
void addButtonClicked() override;
signals:
void abortRequests();
private:
/** Stores the available CRS for a server connections.

View File

@ -27,13 +27,6 @@
#include <QUrl>
#include <QDomDocument>
QgsGeoNodeRequest::QgsGeoNodeRequest( bool forceRefresh, QObject *parent )
: QObject( parent )
, mForceRefresh( forceRefresh )
{
}
QgsGeoNodeRequest::QgsGeoNodeRequest( const QString &baseUrl, bool forceRefresh, QObject *parent )
: QObject( parent )
, mBaseUrl( baseUrl )
@ -57,27 +50,51 @@ void QgsGeoNodeRequest::abort()
}
}
QList<QgsGeoNodeRequest::ServiceLayerDetail> QgsGeoNodeRequest::getLayers()
void QgsGeoNodeRequest::fetchLayers()
{
QList<QgsGeoNodeRequest::ServiceLayerDetail> layers;
bool success = request( QStringLiteral( "/api/layers/" ) );
if ( !success )
request( QStringLiteral( "/api/layers/" ) );
QObject *obj = new QObject( this );
connect( this, &QgsGeoNodeRequest::requestFinished, obj, [obj, this ]
{
return layers;
}
return parseLayers( this->response() );
QList<QgsGeoNodeRequest::ServiceLayerDetail> layers;
if ( mError.isEmpty() )
{
layers = parseLayers( this->lastResponse() );
}
emit layersFetched( layers );
obj->deleteLater();
} );
}
QgsGeoNodeStyle QgsGeoNodeRequest::getDefaultStyle( const QString &layerName )
QList<QgsGeoNodeRequest::ServiceLayerDetail> QgsGeoNodeRequest::fetchLayersBlocking()
{
QList<QgsGeoNodeRequest::ServiceLayerDetail> layers;
QEventLoop loop;
connect( this, &QgsGeoNodeRequest::requestFinished, &loop, &QEventLoop::quit );
QObject *obj = new QObject( this );
connect( this, &QgsGeoNodeRequest::layersFetched, obj, [&]( const QList<QgsGeoNodeRequest::ServiceLayerDetail> &fetched )
{
layers = fetched;
} );
fetchLayers();
loop.exec( QEventLoop::ExcludeUserInputEvents );
delete obj;
return layers;
}
QgsGeoNodeStyle QgsGeoNodeRequest::fetchDefaultStyleBlocking( const QString &layerName )
{
QgsGeoNodeStyle defaultStyle;
bool success = request( QStringLiteral( "/api/layers?name=" ) + layerName );
bool success = requestBlocking( QStringLiteral( "/api/layers?name=" ) + layerName );
if ( !success )
{
return defaultStyle;
}
const QJsonDocument jsonDocument = QJsonDocument::fromJson( this->response() );
const QJsonDocument jsonDocument = QJsonDocument::fromJson( this->lastResponse() );
const QJsonObject jsonObject = jsonDocument.object();
const QList<QVariant> layers = jsonObject.toVariantMap().value( QStringLiteral( "objects" ) ).toList();
if ( layers.count() < 1 )
@ -92,16 +109,16 @@ QgsGeoNodeStyle QgsGeoNodeRequest::getDefaultStyle( const QString &layerName )
}
QList<QgsGeoNodeStyle> QgsGeoNodeRequest::getStyles( const QString &layerName )
QList<QgsGeoNodeStyle> QgsGeoNodeRequest::fetchStylesBlocking( const QString &layerName )
{
QList<QgsGeoNodeStyle> geoNodeStyles;
bool success = request( QStringLiteral( "/api/styles?layer__name=" ) + layerName );
bool success = requestBlocking( QStringLiteral( "/api/styles?layer__name=" ) + layerName );
if ( !success )
{
return geoNodeStyles;
}
const QJsonDocument jsonDocument = QJsonDocument::fromJson( this->response() );
const QJsonDocument jsonDocument = QJsonDocument::fromJson( this->lastResponse() );
const QJsonObject jsobObject = jsonDocument.object();
const QList<QVariant> styles = jsobObject.toVariantMap().value( QStringLiteral( "objects" ) ).toList();
@ -120,9 +137,9 @@ QList<QgsGeoNodeStyle> QgsGeoNodeRequest::getStyles( const QString &layerName )
}
QgsGeoNodeStyle QgsGeoNodeRequest::getStyle( const QString &styleID )
QgsGeoNodeStyle QgsGeoNodeRequest::fetchStyleBlocking( const QString &styleId )
{
QString endPoint = QStringLiteral( "/api/styles/" ) + styleID;
QString endPoint = QStringLiteral( "/api/styles/" ) + styleId;
return retrieveStyle( endPoint );
}
@ -134,7 +151,7 @@ void QgsGeoNodeRequest::replyProgress( qint64 bytesReceived, qint64 bytesTotal )
emit statusChanged( msg );
}
QString QgsGeoNodeRequest::getProtocol() const
QString QgsGeoNodeRequest::protocol() const
{
return mProtocol;
}
@ -159,7 +176,6 @@ void QgsGeoNodeRequest::replyFinished()
emit statusChanged( QStringLiteral( "GeoNode request redirected." ) );
const QUrl &toUrl = redirect.toUrl();
mGeoNodeReply->request();
if ( toUrl == mGeoNodeReply->url() )
{
mError = tr( "Redirect loop detected: %1" ).arg( toUrl.toString() );
@ -237,7 +253,6 @@ void QgsGeoNodeRequest::replyFinished()
}
emit requestFinished();
}
QList<QgsGeoNodeRequest::ServiceLayerDetail> QgsGeoNodeRequest::parseLayers( const QByteArray &layerResponse )
@ -354,12 +369,12 @@ QgsGeoNodeStyle QgsGeoNodeRequest::retrieveStyle( const QString &styleUrl )
{
QgsGeoNodeStyle geoNodeStyle;
bool success = request( styleUrl );
bool success = requestBlocking( styleUrl );
if ( !success )
{
return geoNodeStyle;
}
const QJsonDocument jsonDocument = QJsonDocument::fromJson( this->response() );
const QJsonDocument jsonDocument = QJsonDocument::fromJson( this->lastResponse() );
const QJsonObject jsonObject = jsonDocument.object();
const QVariantMap jsonMap = jsonObject.toVariantMap();
@ -368,13 +383,13 @@ QgsGeoNodeStyle QgsGeoNodeRequest::retrieveStyle( const QString &styleUrl )
geoNodeStyle.title = jsonMap.value( QStringLiteral( "title" ) ).toString();
geoNodeStyle.styleUrl = jsonMap.value( QStringLiteral( "style_url" ) ).toString();
success = request( geoNodeStyle.styleUrl );
success = requestBlocking( geoNodeStyle.styleUrl );
if ( !success )
{
return geoNodeStyle;
}
success = geoNodeStyle.body.setContent( this->response() );
success = geoNodeStyle.body.setContent( this->lastResponse() );
if ( !success )
{
return geoNodeStyle;
@ -383,11 +398,11 @@ QgsGeoNodeStyle QgsGeoNodeRequest::retrieveStyle( const QString &styleUrl )
return geoNodeStyle;
}
QStringList QgsGeoNodeRequest::serviceUrls( const QString &serviceType )
QStringList QgsGeoNodeRequest::fetchServiceUrlsBlocking( const QString &serviceType )
{
QStringList urls;
const QList<QgsGeoNodeRequest::ServiceLayerDetail> layers = getLayers();
const QList<QgsGeoNodeRequest::ServiceLayerDetail> layers = fetchLayersBlocking();
if ( layers.empty() )
{
@ -415,7 +430,7 @@ QStringList QgsGeoNodeRequest::serviceUrls( const QString &serviceType )
if ( !url.contains( QLatin1String( "://" ) ) )
{
url.prepend( getProtocol() );
url.prepend( protocol() );
}
if ( !urls.contains( url ) )
{
@ -426,11 +441,11 @@ QStringList QgsGeoNodeRequest::serviceUrls( const QString &serviceType )
return urls;
}
QgsStringMap QgsGeoNodeRequest::serviceUrlData( const QString &serviceType )
QgsStringMap QgsGeoNodeRequest::fetchServiceUrlDataBlocking( const QString &serviceType )
{
QgsStringMap urls;
const QList<QgsGeoNodeRequest::ServiceLayerDetail> layers = getLayers();
const QList<QgsGeoNodeRequest::ServiceLayerDetail> layers = fetchLayersBlocking();
if ( layers.empty() )
{
@ -460,7 +475,7 @@ QgsStringMap QgsGeoNodeRequest::serviceUrlData( const QString &serviceType )
QString layerName = layer.name;
if ( !url.contains( QLatin1String( "://" ) ) )
{
url.prepend( getProtocol() );
url.prepend( protocol() );
}
if ( !urls.contains( url ) )
{
@ -471,7 +486,7 @@ QgsStringMap QgsGeoNodeRequest::serviceUrlData( const QString &serviceType )
return urls;
}
bool QgsGeoNodeRequest::request( const QString &endPoint )
void QgsGeoNodeRequest::request( const QString &endPoint )
{
abort();
mIsAborted = false;
@ -480,25 +495,35 @@ bool QgsGeoNodeRequest::request( const QString &endPoint )
QgsDebugMsg( "Requesting to " + url );
setProtocol( url.split( QStringLiteral( "://" ) ).at( 0 ) );
QUrl layerUrl( url );
layerUrl.setScheme( getProtocol() );
layerUrl.setScheme( protocol() );
mError.clear();
mGeoNodeReply = requestUrl( url );
connect( mGeoNodeReply, &QNetworkReply::finished, this, &QgsGeoNodeRequest::replyFinished, Qt::DirectConnection );
connect( mGeoNodeReply, &QNetworkReply::downloadProgress, this, &QgsGeoNodeRequest::replyProgress, Qt::DirectConnection );
}
bool QgsGeoNodeRequest::requestBlocking( const QString &endPoint )
{
request( endPoint );
QEventLoop loop;
connect( this, &QgsGeoNodeRequest::requestFinished, &loop, &QEventLoop::quit );
loop.exec( QEventLoop::ExcludeUserInputEvents );
return mError.isEmpty();
}
QNetworkReply *QgsGeoNodeRequest::requestUrl( const QString &url )
{
QNetworkRequest request( url );
// Add authentication check here
request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, mForceRefresh ? QNetworkRequest::AlwaysNetwork : QNetworkRequest::PreferCache );
request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
mGeoNodeReply = QgsNetworkAccessManager::instance()->get( request );
connect( mGeoNodeReply, &QNetworkReply::finished, this, &QgsGeoNodeRequest::replyFinished, Qt::DirectConnection );
connect( mGeoNodeReply, &QNetworkReply::downloadProgress, this, &QgsGeoNodeRequest::replyProgress, Qt::DirectConnection );
QEventLoop loop;
connect( this, &QgsGeoNodeRequest::requestFinished, &loop, &QEventLoop::quit );
loop.exec( QEventLoop::ExcludeUserInputEvents );
return mError.isEmpty();
return QgsNetworkAccessManager::instance()->get( request );
}

View File

@ -22,6 +22,12 @@
#include <QObject>
#include <QUuid>
/**
* \ingroup core
* \class QgsGeoNodeStyle
* \brief Encapsulates information about a GeoNode layer style.
* \since QGIS 3.0
*/
struct CORE_EXPORT QgsGeoNodeStyle
{
#ifdef SIP_RUN
@ -29,26 +35,56 @@ struct CORE_EXPORT QgsGeoNodeStyle
#include <qgsgeonoderequest.h>
% End
#endif
//! Unique style ID
QString id;
//! Style name
QString name;
//! Style title
QString title;
//! DOM documenting containing style
QDomDocument body;
//! Associated URL
QString styleUrl;
};
/**
* \ingroup core
* \class QgsGeoNodeRequest
* \brief Request handler for GeoNode servers.
*
* QgsGeoNodeRequest handles requesting and parsing service details from a GeoNode
* server instance, for instance requesting all available layers or layer styles.
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsGeoNodeRequest : public QObject
{
Q_OBJECT
public:
/**
* Service layer details for an individual layer from a GeoNode connection.
*/
struct ServiceLayerDetail
{
//! Unique identifier (generate on the client side, not at the GeoNode server)
QUuid uuid;
//! Layer name
QString name;
//! Layer type name
QString typeName;
//! Layer title
QString title;
//! WMS URL for layer
QString wmsURL;
//! WFS URL for layer
QString wfsURL;
//! XYZ tileserver URL for layer
QString xyzURL;
};
@ -57,54 +93,155 @@ class CORE_EXPORT QgsGeoNodeRequest : public QObject
*
* If \a forceRefresh is false, then cached copies of the request may be reused.
*/
explicit QgsGeoNodeRequest( bool forceRefresh, QObject *parent = nullptr );
QgsGeoNodeRequest( const QString &baseUrl, bool forceRefresh, QObject *parent = nullptr );
virtual ~QgsGeoNodeRequest();
bool request( const QString &endPoint );
/**
* Triggers a new request to the GeoNode server, with the requested \a endPoint.
* Any existing request will be aborted.
*
* Calling this method does not block while waiting for a result.
*
* \warning When using the non-blocking methods in this class, sending
* overlapping requests results in undefined behavior. Use separate instances
* of QgsGeoNodeRequest instead to avoid this.
*
* \see requestBlocking()
*/
void request( const QString &endPoint );
QList<QgsGeoNodeRequest::ServiceLayerDetail> getLayers();
/**
* Triggers a new request to the GeoNode server, with the requested \a endPoint.
* Any existing request will be aborted.
*
* Calling this method will block while waiting for a result. It should not be
* used from any code which potentially blocks operation in the main GUI thread.
*
* \see request()
*/
bool requestBlocking( const QString &endPoint );
QList<QgsGeoNodeStyle> getStyles( const QString &layerName );
/**
* Triggers a new request to fetch the list of available layers from the
* server. When complete, the layersFetched() signal will be emitted
* with the result.
*
* This method is non-blocking and returns immediately.
*
* \warning When using the non-blocking methods in this class, sending
* overlapping requests results in undefined behavior. Use separate instances
* of QgsGeoNodeRequest instead to avoid this.
*
* \see layersFetched()
* \see fetchLayersBlocking()
*/
void fetchLayers();
QgsGeoNodeStyle getDefaultStyle( const QString &layerName );
/**
* Requests the list of available layers from the server.
*
* This method is blocking and will wait for results from the server before returning.
* Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
*
* \see fetchLayers()
*/
QList<QgsGeoNodeRequest::ServiceLayerDetail> fetchLayersBlocking();
QgsGeoNodeStyle getStyle( const QString &styleID );
/**
* Requests the list of available styles for the layer
* with matching \a layerName from the server.
*
* This method is blocking and will wait for results from the server before returning.
* Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
*
*/
QList<QgsGeoNodeStyle> fetchStylesBlocking( const QString &layerName );
//! Obtain list of unique URLs in the geonode
QStringList serviceUrls( const QString &serviceType );
/**
* Requests the default style for the layer with matching \a layerName from the server.
*
* This method is blocking and will wait for results from the server before returning.
* Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
*/
QgsGeoNodeStyle fetchDefaultStyleBlocking( const QString &layerName );
//! Obtain map of layer name and url for a service type
QgsStringMap serviceUrlData( const QString &serviceType );
/**
* Requests the details for the style with matching \a styleId from the server.
*
* This method is blocking and will wait for results from the server before returning.
* Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
*/
QgsGeoNodeStyle fetchStyleBlocking( const QString &styleId );
/**
* Requests the list of unique URLs for available services with matching \a serviceType from the server.
*
* This method is blocking and will wait for results from the server before returning.
* Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
*/
QStringList fetchServiceUrlsBlocking( const QString &serviceType );
/**
* Obtains a map of layer name to URL for available services with matching \a serviceType from the server.
*
* This method is blocking and will wait for results from the server before returning.
* Accordingly it should not be used from any code which potentially blocks operation in the main GUI thread.
*/
QgsStringMap fetchServiceUrlDataBlocking( const QString &serviceType );
/**
* Returns the most recent error string for any encountered errors, or an empty string if
* no errors have been encountered.
*/
QString lastError() const { return mError; }
QByteArray response() const { return mHttpGeoNodeResponse; }
/**
* Returns the most recent response obtained from the server.
*/
QByteArray lastResponse() const { return mHttpGeoNodeResponse; }
QNetworkReply *reply() const { return mGeoNodeReply; }
/**
* Returns the network protocol (e.g. 'http') used for connecting with the server.
* \see setProtocol()
*/
QString protocol() const;
//! Abort network request immediately
void abort();
QString getProtocol() const;
/**
* Sets the network \a protocol (e.g. 'http') used for connecting with the server.
* \see protocol()
*/
void setProtocol( const QString &protocol );
private:
QList<QgsGeoNodeRequest::ServiceLayerDetail> parseLayers( const QByteArray &layerResponse );
QgsGeoNodeStyle retrieveStyle( const QString &styleUrl );
public slots:
/**
* Aborts any active network request immediately.
*/
void abort();
signals:
//! \brief emit a signal to be caught by qgisapp and display a statusQString on status bar
/**
* Emitted when the status of an ongoing request is changed.
*/
void statusChanged( const QString &statusQString );
//! \brief emit a signal once the request is finished
/**
* Emitted when the existing request has been completed.
*/
void requestFinished();
protected slots:
/**
* Emitted when the result of a fetchLayers call has been received and processed.
*/
void layersFetched( const QList<QgsGeoNodeRequest::ServiceLayerDetail> &layers );
private slots:
void replyFinished();
void replyProgress( qint64, qint64 );
protected:
private:
//! URL part of URI (httpuri)
QString mProtocol;
@ -112,8 +249,6 @@ class CORE_EXPORT QgsGeoNodeRequest : public QObject
//! URL part of URI (httpuri)
QString mBaseUrl;
// QgsWmsAuthorization mAuth;
//! The reply to the geonode request
QNetworkReply *mGeoNodeReply = nullptr;
@ -129,6 +264,11 @@ class CORE_EXPORT QgsGeoNodeRequest : public QObject
bool mIsAborted = false;
bool mForceRefresh = false;
QList<QgsGeoNodeRequest::ServiceLayerDetail> parseLayers( const QByteArray &layerResponse );
QgsGeoNodeStyle retrieveStyle( const QString &styleUrl );
QNetworkReply *requestUrl( const QString &url );
};
#endif // QGSGEONODEREQUEST_H

View File

@ -227,7 +227,7 @@ QgsDataItem *QgsWfsDataItemProvider::createDataItem( const QString &path, QgsDat
QString url = connection.uri().param( "url" );
QgsGeoNodeRequest geonodeRequest( url, true );
QgsWFSDataSourceURI sourceUri( geonodeRequest.serviceUrls( QStringLiteral( "WFS" ) )[0] );
QgsWFSDataSourceURI sourceUri( geonodeRequest.fetchServiceUrlsBlocking( QStringLiteral( "WFS" ) )[0] );
QgsDebugMsg( QString( "WFS full uri: '%1'." ).arg( QString( sourceUri.uri() ) ) );
@ -251,7 +251,7 @@ QVector<QgsDataItem *> QgsWfsDataItemProvider::createDataItems( const QString &p
QString url = connection.uri().param( "url" );
QgsGeoNodeRequest geonodeRequest( url, true );
QStringList encodedUris( geonodeRequest.serviceUrls( QStringLiteral( "WFS" ) ) );
QStringList encodedUris( geonodeRequest.fetchServiceUrlsBlocking( QStringLiteral( "WFS" ) ) );
if ( !encodedUris.isEmpty() )
{

View File

@ -566,7 +566,7 @@ QVector<QgsDataItem *> QgsWmsDataItemProvider::createDataItems( const QString &p
QString url = connection.uri().param( "url" );
QgsGeoNodeRequest geonodeRequest( url, true );
QStringList encodedUris( geonodeRequest.serviceUrls( QStringLiteral( "WMS" ) ) );
QStringList encodedUris( geonodeRequest.fetchServiceUrlsBlocking( QStringLiteral( "WMS" ) ) );
if ( !encodedUris.isEmpty() )
{
@ -612,7 +612,7 @@ QVector<QgsDataItem *> QgsXyzTileDataItemProvider::createDataItems( const QStrin
QString url = connection.uri().param( "url" );
QgsGeoNodeRequest geonodeRequest( url, true );
QgsStringMap urlData( geonodeRequest.serviceUrlData( QStringLiteral( "XYZ" ) ) );
QgsStringMap urlData( geonodeRequest.fetchServiceUrlDataBlocking( QStringLiteral( "XYZ" ) ) );
if ( !urlData.isEmpty() )
{

View File

@ -129,7 +129,7 @@ void TestQgsGeoNodeConnection::testLayerAPI()
}
QgsGeoNodeRequest geonodeRequest( mKartozaGeoNodeQGISServerURL, true );
QList<QgsGeoNodeRequest::ServiceLayerDetail> layers = geonodeRequest.getLayers();
QList<QgsGeoNodeRequest::ServiceLayerDetail> layers = geonodeRequest.fetchLayersBlocking();
QString msg = QStringLiteral( "Number of layers: %1" ).arg( layers.count() );
QgsDebugMsg( msg );
QVERIFY( layers.count() > 0 );
@ -144,17 +144,17 @@ void TestQgsGeoNodeConnection::testStyleAPI()
}
QgsGeoNodeRequest geonodeRequest( mKartozaGeoNodeQGISServerURL, true );
QgsGeoNodeStyle defaultStyle = geonodeRequest.getDefaultStyle( QStringLiteral( "airports" ) );
QgsGeoNodeStyle defaultStyle = geonodeRequest.fetchDefaultStyleBlocking( QStringLiteral( "airports" ) );
QVERIFY( !defaultStyle.name.isEmpty() );
QVERIFY( defaultStyle.body.toString().startsWith( QStringLiteral( "<qgis" ) ) );
QVERIFY( defaultStyle.body.toString().contains( QStringLiteral( "</qgis>" ) ) );
QgsGeoNodeStyle geoNodeStyle = geonodeRequest.getStyle( "76" );
QgsGeoNodeStyle geoNodeStyle = geonodeRequest.fetchStyleBlocking( "76" );
QVERIFY( !geoNodeStyle.name.isEmpty() );
QVERIFY( geoNodeStyle.body.toString().startsWith( QStringLiteral( "<qgis" ) ) );
QVERIFY( geoNodeStyle.body.toString().contains( QStringLiteral( "</qgis>" ) ) );
QList<QgsGeoNodeStyle> geoNodeStyles = geonodeRequest.getStyles( QStringLiteral( "airports" ) );
QList<QgsGeoNodeStyle> geoNodeStyles = geonodeRequest.fetchStylesBlocking( QStringLiteral( "airports" ) );
QgsDebugMsg( geoNodeStyles.count() );
QVERIFY( geoNodeStyles.count() == 2 );