From e1562df16b81ceae2d836d771de995229c61d0dd Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 8 Sep 2017 11:54:39 +1000 Subject: [PATCH] [geonode] Don't block data source manager while connecting to a server Also add missing docstrings --- .../core/geocms/geonode/qgsgeonoderequest.sip | 182 +++++++++++--- .../geocms/geonode/qgsgeonodedataitems.cpp | 6 +- .../geonode/qgsgeonodenewconnection.cpp | 2 +- .../geocms/geonode/qgsgeonodesourceselect.cpp | 222 +++++++++--------- .../geocms/geonode/qgsgeonodesourceselect.h | 5 + src/core/geocms/geonode/qgsgeonoderequest.cpp | 121 ++++++---- src/core/geocms/geonode/qgsgeonoderequest.h | 190 +++++++++++++-- src/providers/wfs/qgswfsdataitems.cpp | 4 +- src/providers/wms/qgswmsdataitems.cpp | 4 +- tests/src/core/testqgsgeonodeconnection.cpp | 8 +- 10 files changed, 517 insertions(+), 227 deletions(-) diff --git a/python/core/geocms/geonode/qgsgeonoderequest.sip b/python/core/geocms/geonode/qgsgeonoderequest.sip index cea3a88b581..9d0133352a4 100644 --- a/python/core/geocms/geonode/qgsgeonoderequest.sip +++ b/python/core/geocms/geonode/qgsgeonoderequest.sip @@ -12,15 +12,43 @@ struct QgsGeoNodeStyle %TypeHeaderCode #include %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 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 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 getStyles( const QString &layerName ); + QList 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 &layers ); +%Docstring + Emitted when the result of a fetchLayers call has been received and processed. +%End }; diff --git a/src/app/geocms/geonode/qgsgeonodedataitems.cpp b/src/app/geocms/geonode/qgsgeonodedataitems.cpp index 258f710dbae..26e8aeccf55 100644 --- a/src/app/geocms/geonode/qgsgeonodedataitems.cpp +++ b/src/app/geocms/geonode/qgsgeonodedataitems.cpp @@ -38,9 +38,9 @@ QVector 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() ) { diff --git a/src/app/geocms/geonode/qgsgeonodenewconnection.cpp b/src/app/geocms/geonode/qgsgeonodenewconnection.cpp index 76054af4164..f1578ab7fe7 100644 --- a/src/app/geocms/geonode/qgsgeonodenewconnection.cpp +++ b/src/app/geocms/geonode/qgsgeonodenewconnection.cpp @@ -249,7 +249,7 @@ void QgsGeoNodeNewConnection::testConnection() QString url = txtUrl->text(); QgsGeoNodeRequest geonodeRequest( url, true ); - QList layers = geonodeRequest.getLayers(); + QList layers = geonodeRequest.fetchLayersBlocking(); QApplication::restoreOverrideCursor(); if ( !layers.empty() ) diff --git a/src/app/geocms/geonode/qgsgeonodesourceselect.cpp b/src/app/geocms/geonode/qgsgeonodesourceselect.cpp index 8985a6d8932..1140d812a55 100644 --- a/src/app/geocms/geonode/qgsgeonodesourceselect.cpp +++ b/src/app/geocms/geonode/qgsgeonodesourceselect.cpp @@ -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 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() diff --git a/src/app/geocms/geonode/qgsgeonodesourceselect.h b/src/app/geocms/geonode/qgsgeonodesourceselect.h index 22847c96813..e78d8faebdf 100644 --- a/src/app/geocms/geonode/qgsgeonodesourceselect.h +++ b/src/app/geocms/geonode/qgsgeonodesourceselect.h @@ -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. diff --git a/src/core/geocms/geonode/qgsgeonoderequest.cpp b/src/core/geocms/geonode/qgsgeonoderequest.cpp index f38fb6cbcd3..f9b38f2c2da 100644 --- a/src/core/geocms/geonode/qgsgeonoderequest.cpp +++ b/src/core/geocms/geonode/qgsgeonoderequest.cpp @@ -27,13 +27,6 @@ #include #include -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::getLayers() +void QgsGeoNodeRequest::fetchLayers() { - QList 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 layers; + if ( mError.isEmpty() ) + { + layers = parseLayers( this->lastResponse() ); + } + emit layersFetched( layers ); + + obj->deleteLater(); + } ); } -QgsGeoNodeStyle QgsGeoNodeRequest::getDefaultStyle( const QString &layerName ) +QList QgsGeoNodeRequest::fetchLayersBlocking() +{ + QList layers; + + QEventLoop loop; + connect( this, &QgsGeoNodeRequest::requestFinished, &loop, &QEventLoop::quit ); + QObject *obj = new QObject( this ); + connect( this, &QgsGeoNodeRequest::layersFetched, obj, [&]( const QList &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 layers = jsonObject.toVariantMap().value( QStringLiteral( "objects" ) ).toList(); if ( layers.count() < 1 ) @@ -92,16 +109,16 @@ QgsGeoNodeStyle QgsGeoNodeRequest::getDefaultStyle( const QString &layerName ) } -QList QgsGeoNodeRequest::getStyles( const QString &layerName ) +QList QgsGeoNodeRequest::fetchStylesBlocking( const QString &layerName ) { QList 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 styles = jsobObject.toVariantMap().value( QStringLiteral( "objects" ) ).toList(); @@ -120,9 +137,9 @@ QList 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::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 layers = getLayers(); + const QList 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 layers = getLayers(); + const QList 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 ); } + + diff --git a/src/core/geocms/geonode/qgsgeonoderequest.h b/src/core/geocms/geonode/qgsgeonoderequest.h index 85fbe3d7d2d..4bf7f39cd17 100644 --- a/src/core/geocms/geonode/qgsgeonoderequest.h +++ b/src/core/geocms/geonode/qgsgeonoderequest.h @@ -22,6 +22,12 @@ #include #include +/** + * \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 % 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 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 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 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 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 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 &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 parseLayers( const QByteArray &layerResponse ); + QgsGeoNodeStyle retrieveStyle( const QString &styleUrl ); + + QNetworkReply *requestUrl( const QString &url ); + }; #endif // QGSGEONODEREQUEST_H diff --git a/src/providers/wfs/qgswfsdataitems.cpp b/src/providers/wfs/qgswfsdataitems.cpp index a30cf415265..d5761929b39 100644 --- a/src/providers/wfs/qgswfsdataitems.cpp +++ b/src/providers/wfs/qgswfsdataitems.cpp @@ -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 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() ) { diff --git a/src/providers/wms/qgswmsdataitems.cpp b/src/providers/wms/qgswmsdataitems.cpp index d6fb0b15f6f..9b919acd54b 100644 --- a/src/providers/wms/qgswmsdataitems.cpp +++ b/src/providers/wms/qgswmsdataitems.cpp @@ -566,7 +566,7 @@ QVector 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 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() ) { diff --git a/tests/src/core/testqgsgeonodeconnection.cpp b/tests/src/core/testqgsgeonodeconnection.cpp index 28d0afc1da3..101edf30146 100644 --- a/tests/src/core/testqgsgeonodeconnection.cpp +++ b/tests/src/core/testqgsgeonodeconnection.cpp @@ -129,7 +129,7 @@ void TestQgsGeoNodeConnection::testLayerAPI() } QgsGeoNodeRequest geonodeRequest( mKartozaGeoNodeQGISServerURL, true ); - QList layers = geonodeRequest.getLayers(); + QList 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( "" ) ) ); - QgsGeoNodeStyle geoNodeStyle = geonodeRequest.getStyle( "76" ); + QgsGeoNodeStyle geoNodeStyle = geonodeRequest.fetchStyleBlocking( "76" ); QVERIFY( !geoNodeStyle.name.isEmpty() ); QVERIFY( geoNodeStyle.body.toString().startsWith( QStringLiteral( "" ) ) ); - QList geoNodeStyles = geonodeRequest.getStyles( QStringLiteral( "airports" ) ); + QList geoNodeStyles = geonodeRequest.fetchStylesBlocking( QStringLiteral( "airports" ) ); QgsDebugMsg( geoNodeStyles.count() ); QVERIFY( geoNodeStyles.count() == 2 );