Skip to content

Commit 9040ec1

Browse files
committedApr 5, 2016
[FEATURE] [WFS provider] Major overhaul to add WFS 1.1 and 2.0 support
First part of qgis/QGIS-Enhancement-Proposals#53 (QEP 35: WFS provider enhancements) Improvements: - Version autodetection - On-disk caching of downloaded features - Background download and progressive rendering - WFS 1.1 and 2.0 support - WFS 2.0 GetFeature paging - Add provider tests Fixes: - #10106: Panning a non-cached WFS layer causes selection to change - #9444: WFS client not requesting new features when not-cached #14156: WFS non cached: infinite flashing - #9450 : New WFS connection option - Max number of features returned - #14122: Implement WFS 2.0 client provider (partial. no joins or stored queries) Not in scope: WFS-T 1.1 and 2.0. But WFS-T 1.0 kept (and tested)
1 parent 62bd406 commit 9040ec1

34 files changed

+5226
-1752
lines changed
 

‎ci/travis/linux/qt5/blacklist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ PyQgsVectorFileWriter
6666
PyQgsVectorLayer
6767
PyQgsVirtualLayerDefinition
6868
PyQgsVirtualLayerProvider
69+
PyQgsWFSProvider
6970
PyQgsZonalStatistics
7071
qgis_alignrastertest
7172
qgis_composereffectstest

‎src/core/qgsowsconnection.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class CORE_EXPORT QgsOWSConnection : public QObject
5959
//! @deprecated use mConnectionInfo instead
6060
Q_DECL_DEPRECATED QString connectionInfo();
6161

62-
private:
62+
protected:
6363
QgsDataSourceURI mUri;
6464
QString mService;
6565
};

‎src/core/qgsvectorlayer.h

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,23 @@ struct CORE_EXPORT QgsVectorJoinInfo
195195
*
196196
* Used to access data provided by a web feature service.
197197
*
198-
* The url can be a HTTP url to a WFS 1.0.0 server or a GML2 data file path.
199-
* Examples are http://foobar/wfs or /foo/bar/file.gml
200-
*
201-
* If a GML2 file path is provided the driver will attempt to read the schema from a
202-
* file in the same directory with the same basename + “.xsd”. This xsd file must be
203-
* in the same format as a WFS describe feature type response. If no xsd file is provide
204-
* then the driver will attempt to guess the attribute types from the file.
205-
*
206-
* In the case of a HTTP URL the ‘FILTER’ query string parameter can be used to filter
198+
* The url can be a HTTP url to a WFS server (legacy, e.g. http://foobar/wfs?TYPENAME=xxx&SRSNAME=yyy[&FILTER=zzz]), or,
199+
* starting with QGIS 2.16, a URI constructed using the QgsDataSourceURI class with the following parameters :
200+
* - url=string (mandatory): HTTP url to a WFS server endpoint. e.g http://foobar/wfs
201+
* - typename=string (mandatory): WFS typename
202+
* - srsname=string (recommended): SRS like 'EPSG:XXXX'
203+
* - username=string
204+
* - password=string
205+
* - authcfg=string
206+
* - version=auto/1.0.0/1.1.0/2.0.0
207+
* - filter=string: QGIS expression or OGC/FES filter
208+
* - retrictToRequestBBOX=1: to download only features in the view extent (or more generally
209+
* in the bounding box of the feature iterator)
210+
* - maxNumFeatures=number
211+
* - IgnoreAxisOrientation=1: to ignore EPSG axis order for WFS 1.1 or 2.0
212+
* - InvertAxisOrientation=1: to invert axis order
213+
*
214+
* The ‘FILTER’ query string parameter can be used to filter
207215
* the WFS feature type. The ‘FILTER’ key value can either be a QGIS expression
208216
* or an OGC XML filter. If the value is set to a QGIS expression the driver will
209217
* turn it into OGC XML filter before passing it to the WFS server. Beware the

‎src/gui/qgsnewhttpconnection.cpp

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ QgsNewHttpConnection::QgsNewHttpConnection(
5050
cmbDpiMode->addItem( tr( "UMN" ) );
5151
cmbDpiMode->addItem( tr( "GeoServer" ) );
5252

53+
cmbVersion->clear();
54+
cmbVersion->addItem( tr( "Auto-detect" ) );
55+
cmbVersion->addItem( tr( "1.0" ) );
56+
cmbVersion->addItem( tr( "1.1" ) );
57+
cmbVersion->addItem( tr( "2.0" ) );
58+
5359
mAuthConfigSelect = new QgsAuthConfigSelect( this );
5460
tabAuth->insertTab( 1, mAuthConfigSelect, tr( "Configurations" ) );
5561

@@ -92,7 +98,18 @@ QgsNewHttpConnection::QgsNewHttpConnection(
9298
}
9399
cmbDpiMode->setCurrentIndex( dpiIdx );
94100

101+
QString version = settings.value( key + "/version" ).toString();
102+
int versionIdx = 0; // AUTO
103+
if ( version == "1.0.0" )
104+
versionIdx = 1;
105+
else if ( version == "1.1.0" )
106+
versionIdx = 2;
107+
else if ( version == "2.0.0" )
108+
versionIdx = 3;
109+
cmbVersion->setCurrentIndex( versionIdx );
110+
95111
txtReferer->setText( settings.value( key + "/referer" ).toString() );
112+
txtMaxNumFeatures->setText( settings.value( key + "/maxnumfeatures" ).toString() );
96113

97114
txtUserName->setText( settings.value( credentialsKey + "/username" ).toString() );
98115
txtPassword->setText( settings.value( credentialsKey + "/password" ).toString() );
@@ -107,6 +124,20 @@ QgsNewHttpConnection::QgsNewHttpConnection(
107124

108125
if ( mBaseKey != "/Qgis/connections-wms/" )
109126
{
127+
if ( mBaseKey != "/Qgis/connections-wcs/" &&
128+
mBaseKey != "/Qgis/connections-wfs/" )
129+
{
130+
cbxIgnoreAxisOrientation->setVisible( false );
131+
cbxInvertAxisOrientation->setVisible( false );
132+
mGroupBox->layout()->removeWidget( cbxIgnoreAxisOrientation );
133+
mGroupBox->layout()->removeWidget( cbxInvertAxisOrientation );
134+
}
135+
136+
if ( mBaseKey == "/Qgis/connections-wfs/" )
137+
{
138+
cbxIgnoreAxisOrientation->setText( tr( "Ignore axis orientation (WFS 1.1/WFS 2.0)" ) );
139+
}
140+
110141
if ( mBaseKey == "/Qgis/connections-wcs/" )
111142
{
112143
cbxIgnoreGetMapURI->setText( tr( "Ignore GetCoverage URI reported in capabilities" ) );
@@ -115,12 +146,8 @@ QgsNewHttpConnection::QgsNewHttpConnection(
115146
else
116147
{
117148
cbxIgnoreGetMapURI->setVisible( false );
118-
cbxIgnoreAxisOrientation->setVisible( false );
119-
cbxInvertAxisOrientation->setVisible( false );
120149
cbxSmoothPixmapTransform->setVisible( false );
121150
mGroupBox->layout()->removeWidget( cbxIgnoreGetMapURI );
122-
mGroupBox->layout()->removeWidget( cbxIgnoreAxisOrientation );
123-
mGroupBox->layout()->removeWidget( cbxInvertAxisOrientation );
124151
mGroupBox->layout()->removeWidget( cbxSmoothPixmapTransform );
125152
}
126153

@@ -136,13 +163,23 @@ QgsNewHttpConnection::QgsNewHttpConnection(
136163
mGroupBox->layout()->removeWidget( txtReferer );
137164
lblReferer->setVisible( false );
138165
mGroupBox->layout()->removeWidget( lblReferer );
166+
}
139167

140-
// Adjust height
141-
int w = width();
142-
adjustSize();
143-
resize( w, height() );
168+
if ( mBaseKey != "/Qgis/connections-wfs/" )
169+
{
170+
cmbVersion->setVisible( false );
171+
mGroupBox->layout()->removeWidget( cmbVersion );
172+
lblMaxNumFeatures->setVisible( false );
173+
mGroupBox->layout()->removeWidget( lblMaxNumFeatures );
174+
txtMaxNumFeatures->setVisible( false );
175+
mGroupBox->layout()->removeWidget( txtMaxNumFeatures );
144176
}
145177

178+
// Adjust height
179+
int w = width();
180+
adjustSize();
181+
resize( w, height() );
182+
146183
on_txtName_textChanged( connName );
147184
}
148185

@@ -219,11 +256,18 @@ void QgsNewHttpConnection::accept()
219256
}
220257

221258
settings.setValue( key + "/url", url.toString() );
222-
if ( mBaseKey == "/Qgis/connections-wms/" || mBaseKey == "/Qgis/connections-wcs/" )
259+
260+
if ( mBaseKey == "/Qgis/connections-wms/" ||
261+
mBaseKey == "/Qgis/connections-wcs/" ||
262+
mBaseKey == "/Qgis/connections-wfs/" )
223263
{
224-
settings.setValue( key + "/ignoreGetMapURI", cbxIgnoreGetMapURI->isChecked() );
225264
settings.setValue( key + "/ignoreAxisOrientation", cbxIgnoreAxisOrientation->isChecked() );
226265
settings.setValue( key + "/invertAxisOrientation", cbxInvertAxisOrientation->isChecked() );
266+
}
267+
268+
if ( mBaseKey == "/Qgis/connections-wms/" || mBaseKey == "/Qgis/connections-wcs/" )
269+
{
270+
settings.setValue( key + "/ignoreGetMapURI", cbxIgnoreGetMapURI->isChecked() );
227271
settings.setValue( key + "/smoothPixmapTransform", cbxSmoothPixmapTransform->isChecked() );
228272

229273
int dpiMode = 0;
@@ -252,6 +296,28 @@ void QgsNewHttpConnection::accept()
252296
{
253297
settings.setValue( key + "/ignoreGetFeatureInfoURI", cbxIgnoreGetFeatureInfoURI->isChecked() );
254298
}
299+
if ( mBaseKey == "/Qgis/connections-wfs/" )
300+
{
301+
QString version = "auto";
302+
switch ( cmbVersion->currentIndex() )
303+
{
304+
case 0:
305+
version = "auto";
306+
break;
307+
case 1:
308+
version = "1.0.0";
309+
break;
310+
case 2:
311+
version = "1.1.0";
312+
break;
313+
case 3:
314+
version = "2.0.0";
315+
break;
316+
}
317+
settings.setValue( key + "/version", version );
318+
319+
settings.setValue( key + "/maxnumfeatures", txtMaxNumFeatures->text() );
320+
}
255321

256322
settings.setValue( key + "/referer", txtReferer->text() );
257323

‎src/providers/wfs/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ SET(WFS_SRCS
88
qgswfsdataitems.cpp
99
qgswfsfeatureiterator.cpp
1010
qgswfssourceselect.cpp
11+
qgswfsrequest.cpp
12+
qgswfsconnection.cpp
13+
qgswfsdatasourceuri.cpp
14+
qgswfsconstants.cpp
15+
qgswfsdescribefeaturetype.cpp
16+
qgswfsshareddata.cpp
17+
qgswfstransactionrequest.cpp
18+
qgswfsutils.cpp
1119
)
1220

1321
SET (WFS_MOC_HDRS
@@ -16,6 +24,11 @@ SET (WFS_MOC_HDRS
1624
qgswfsprovider.h
1725
qgswfsfeatureiterator.h
1826
qgswfssourceselect.h
27+
qgswfsrequest.h
28+
qgswfsdescribefeaturetype.h
29+
qgswfstransactionrequest.h
30+
qgswfsshareddata.h
31+
qgswfsutils.h
1932
)
2033

2134
########################################################
@@ -27,6 +40,7 @@ INCLUDE_DIRECTORIES (
2740
../../core
2841
../../core/auth
2942
../../core/geometry
43+
../../core/symbology-ng # needed by qgsvectorfilewriter.h
3044
../../gui
3145
../../gui/auth
3246
${CMAKE_CURRENT_BINARY_DIR}/../../ui
@@ -37,6 +51,8 @@ INCLUDE_DIRECTORIES(SYSTEM
3751
${EXPAT_INCLUDE_DIR}
3852
${QSCINTILLA_INCLUDE_DIR}
3953
${QCA_INCLUDE_DIR}
54+
${GDAL_INCLUDE_DIR} # needed by qgsvectorfilewriter.h
55+
${SQLITE3_INCLUDE_DIR}
4056
)
4157

4258
ADD_LIBRARY (wfsprovider MODULE ${WFS_SRCS} ${WFS_MOC_SRCS})

‎src/providers/wfs/qgswfscapabilities.cpp

Lines changed: 290 additions & 201 deletions
Large diffs are not rendered by default.

‎src/providers/wfs/qgswfscapabilities.h

Lines changed: 30 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,21 @@
1616
#define QGSWFSCAPABILITIES_H
1717

1818
#include <QObject>
19-
#include <QNetworkRequest>
19+
#include <QDomElement>
2020

2121
#include "qgsrectangle.h"
22-
#include "qgsdatasourceuri.h"
22+
#include "qgswfsrequest.h"
2323

24-
class QNetworkReply;
25-
26-
class QgsWFSCapabilities : public QObject
24+
/** Manages the GetCapabilities request */
25+
class QgsWFSCapabilities : public QgsWFSRequest
2726
{
2827
Q_OBJECT
2928
public:
30-
//explicit QgsWFSCapabilities( QString connName, QObject *parent = 0 );
3129
explicit QgsWFSCapabilities( const QString& theUri );
32-
33-
//! Append ? or & if necessary
34-
QString prepareUri( QString uri );
35-
36-
//! base service URI
37-
QString uri() const { return mBaseUrl; }
38-
//! URI to get capabilities
39-
QString uriGetCapabilities() const;
40-
//! URI to get schema of wfs layer
41-
QString uriDescribeFeatureType( const QString& typeName ) const;
42-
//! URI to get features
43-
//! @param filter can be an OGC filter xml or a QGIS expression (containing =,!=, <,>,<=, >=, AND, OR, NOT )
44-
QString uriGetFeature( const QString& typeName,
45-
QString crs = QString(),
46-
QString filter = QString(),
47-
const QgsRectangle& bBox = QgsRectangle() ) const;
30+
virtual ~QgsWFSCapabilities();
4831

4932
//! start network connection to get capabilities
50-
void requestCapabilities();
33+
bool requestCapabilities( bool synchronous );
5134

5235
//! description of a vector layer
5336
struct FeatureType
@@ -56,50 +39,49 @@ class QgsWFSCapabilities : public QObject
5639
QString title;
5740
QString abstract;
5841
QList<QString> crslist; // first is default
42+
QgsRectangle bboxLongLat;
43+
bool insertCap;
44+
bool updateCap;
45+
bool deleteCap;
5946
};
6047

6148
//! parsed get capabilities document
62-
struct GetCapabilities
49+
struct Capabilities
6350
{
64-
void clear() { featureTypes.clear(); }
51+
Capabilities();
52+
void clear();
6553

54+
QString version;
55+
bool supportsHits;
56+
bool supportsPaging;
57+
int maxFeatures;
6658
QList<FeatureType> featureTypes;
6759
};
6860

69-
enum ErrorCode { NoError, NetworkError, XmlError, ServerExceptionError, WFSVersionNotSupported };
70-
ErrorCode errorCode() { return mErrorCode; }
71-
QString errorMessage() { return mErrorMessage; }
72-
7361
//! return parsed capabilities - requestCapabilities() must be called before
74-
GetCapabilities capabilities() { return mCaps; }
75-
76-
//! set authorization header
77-
bool setAuthorization( QNetworkRequest &request ) const;
62+
const Capabilities& capabilities() const { return mCaps; }
7863

7964
signals:
65+
//! emitted when the capabilities have been fully parsed, or an error occured */
8066
void gotCapabilities();
8167

82-
public slots:
68+
private slots:
8369
void capabilitiesReplyFinished();
8470

8571
protected:
86-
//QString mConnName;
87-
//QString mUri;
88-
89-
QgsDataSourceURI mUri;
90-
91-
QString mBaseUrl;
72+
virtual QString errorMessageWithReason( const QString& reason ) override;
73+
virtual int defaultExpirationInSec() override;
9274

93-
QNetworkReply *mCapabilitiesReply;
94-
GetCapabilities mCaps;
95-
ErrorCode mErrorCode;
96-
QString mErrorMessage;
75+
private:
76+
Capabilities mCaps;
9777

98-
//! Username for basic http authentication
99-
QString mUserName;
78+
/** Takes <Operations> element and updates the capabilities*/
79+
void parseSupportedOperations( const QDomElement& operationsElem,
80+
bool& insertCap,
81+
bool& updateCap,
82+
bool& deleteCap );
10083

101-
//! Password for basic http authentication
102-
QString mPassword;
84+
static QString NormalizeSRSName( QString crsName );
10385
};
10486

10587
#endif // QGSWFSCAPABILITIES_H
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/***************************************************************************
2+
qgswfsconnection.cpp
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgswfsconnection.h"
17+
#include "qgswfsconstants.h"
18+
#include "qgslogger.h"
19+
20+
#include <QSettings>
21+
22+
QgsWFSConnection::QgsWFSConnection( const QString & theConnName )
23+
: QgsOWSConnection( "WFS", theConnName )
24+
{
25+
const QString& key = QgsWFSConstants::CONNECTIONS_WFS + mConnName;
26+
27+
QSettings settings;
28+
29+
const QString& version = settings.value( key + "/" + QgsWFSConstants::SETTINGS_VERSION ).toString();
30+
if ( !version.isEmpty() )
31+
{
32+
mUri.setParam( QgsWFSConstants::URI_PARAM_VERSION, version );
33+
}
34+
35+
const QString& maxnumfeatures = settings.value( key + "/" + QgsWFSConstants::SETTINGS_MAXNUMFEATURES ).toString();
36+
if ( !maxnumfeatures.isEmpty() )
37+
{
38+
mUri.setParam( QgsWFSConstants::URI_PARAM_MAXNUMFEATURES, maxnumfeatures );
39+
}
40+
41+
QgsDebugMsg( QString( "WFS full uri: '%1'." ).arg( QString( mUri.uri() ) ) );
42+
}
43+
44+
QStringList QgsWFSConnection::connectionList()
45+
{
46+
return QgsOWSConnection::connectionList( "WFS" );
47+
}
48+
49+
void QgsWFSConnection::deleteConnection( const QString & name )
50+
{
51+
QgsOWSConnection::deleteConnection( "WFS", name );
52+
}
53+
54+
QString QgsWFSConnection::selectedConnection()
55+
{
56+
return QgsOWSConnection::selectedConnection( "WFS" );
57+
}
58+
59+
void QgsWFSConnection::setSelectedConnection( const QString & name )
60+
{
61+
QgsOWSConnection::setSelectedConnection( "WFS", name );
62+
}

‎src/providers/wfs/qgswfsconnection.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/***************************************************************************
2+
qgswfsconnection.h
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSWFSCONNECTION_H
17+
#define QGSWFSCONNECTION_H
18+
19+
#include "qgsowsconnection.h"
20+
21+
class QgsWFSConnection : public QgsOWSConnection
22+
{
23+
public:
24+
/**
25+
* Constructor
26+
* @param theConnName connection name
27+
*/
28+
QgsWFSConnection( const QString & theConnName );
29+
30+
static QStringList connectionList();
31+
32+
static void deleteConnection( const QString & name );
33+
34+
static QString selectedConnection();
35+
static void setSelectedConnection( const QString & name );
36+
};
37+
38+
#endif

‎src/providers/wfs/qgswfsconstants.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/***************************************************************************
2+
qgswfsconstants.cpp
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgswfsconstants.h"
17+
18+
const QString QgsWFSConstants::GML_NAMESPACE( "http://www.opengis.net/gml" );
19+
const QString QgsWFSConstants::OGC_NAMESPACE( "http://www.opengis.net/ogc" );
20+
const QString QgsWFSConstants::OWS_NAMESPACE( "http://www.opengis.net/ows" );
21+
const QString QgsWFSConstants::WFS_NAMESPACE( "http://www.opengis.net/wfs" );
22+
const QString QgsWFSConstants::XMLSCHEMA_NAMESPACE( "http://www.w3.org/2001/XMLSchema" );
23+
24+
const QString QgsWFSConstants::URI_PARAM_URL( "url" );
25+
const QString QgsWFSConstants::URI_PARAM_USERNAME( "username" );
26+
const QString QgsWFSConstants::URI_PARAM_PASSWORD( "password" );
27+
const QString QgsWFSConstants::URI_PARAM_AUTHCFG( "authcfg" );
28+
const QString QgsWFSConstants::URI_PARAM_VERSION( "version" );
29+
const QString QgsWFSConstants::URI_PARAM_TYPENAME( "typename" );
30+
const QString QgsWFSConstants::URI_PARAM_SRSNAME( "srsname" );
31+
const QString QgsWFSConstants::URI_PARAM_FILTER( "filter" );
32+
const QString QgsWFSConstants::URI_PARAM_RESTRICT_TO_REQUEST_BBOX( "retrictToRequestBBOX" );
33+
const QString QgsWFSConstants::URI_PARAM_MAXNUMFEATURES( "maxNumFeatures" );
34+
const QString QgsWFSConstants::URI_PARAM_IGNOREAXISORIENTATION( "IgnoreAxisOrientation" );
35+
const QString QgsWFSConstants::URI_PARAM_INVERTAXISORIENTATION( "InvertAxisOrientation" );
36+
37+
const QString QgsWFSConstants::VERSION_AUTO( "auto" );
38+
39+
const QString QgsWFSConstants::CONNECTIONS_WFS( "/Qgis/connections-wfs/" );
40+
const QString QgsWFSConstants::SETTINGS_VERSION( "version" );
41+
const QString QgsWFSConstants::SETTINGS_MAXNUMFEATURES( "maxnumfeatures" );
42+
43+
const QString QgsWFSConstants::FIELD_GEN_COUNTER( "__qgis_gen_counter" );
44+
const QString QgsWFSConstants::FIELD_GMLID( "__qgis_gmlid" );
45+
const QString QgsWFSConstants::FIELD_HEXWKB_GEOM( "__qgis_hexwkb_geom" );

‎src/providers/wfs/qgswfsconstants.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/***************************************************************************
2+
qgswfsconstants.h
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSWFSCONSTANTS_H
17+
#define QGSWFSCONSTANTS_H
18+
19+
#include <QString>
20+
21+
struct QgsWFSConstants
22+
{
23+
static const QString GML_NAMESPACE;
24+
static const QString OGC_NAMESPACE;
25+
static const QString OWS_NAMESPACE;
26+
static const QString WFS_NAMESPACE;
27+
static const QString XMLSCHEMA_NAMESPACE;
28+
29+
// URI parameters
30+
static const QString URI_PARAM_URL;
31+
static const QString URI_PARAM_USERNAME;
32+
static const QString URI_PARAM_PASSWORD;
33+
static const QString URI_PARAM_AUTHCFG;
34+
static const QString URI_PARAM_VERSION;
35+
static const QString URI_PARAM_TYPENAME;
36+
static const QString URI_PARAM_SRSNAME;
37+
static const QString URI_PARAM_FILTER;
38+
static const QString URI_PARAM_RESTRICT_TO_REQUEST_BBOX;
39+
static const QString URI_PARAM_MAXNUMFEATURES;
40+
static const QString URI_PARAM_IGNOREAXISORIENTATION;
41+
static const QString URI_PARAM_INVERTAXISORIENTATION;
42+
43+
//
44+
static const QString VERSION_AUTO;
45+
46+
// Settings
47+
static const QString CONNECTIONS_WFS;
48+
static const QString SETTINGS_VERSION;
49+
static const QString SETTINGS_MAXNUMFEATURES;
50+
51+
// Special fields of the cache
52+
static const QString FIELD_GEN_COUNTER;
53+
static const QString FIELD_GMLID;
54+
static const QString FIELD_HEXWKB_GEOM;
55+
};
56+
57+
#endif // QGSWFSCONSTANTS_H

‎src/providers/wfs/qgswfsdataitems.cpp

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
* (at your option) any later version. *
1313
* *
1414
***************************************************************************/
15+
#include "qgsdataprovider.h"
1516
#include "qgslogger.h"
1617
#include "qgsnewhttpconnection.h"
17-
#include "qgsowsconnection.h"
18+
#include "qgswfsconstants.h"
19+
#include "qgswfsconnection.h"
1820
#include "qgswfscapabilities.h"
1921
#include "qgswfsdataitems.h"
20-
#include "qgswfsprovider.h"
2122
#include "qgswfssourceselect.h"
23+
#include "qgswfsdatasourceuri.h"
2224

2325
#include <QSettings>
2426
#include <QCoreApplication>
@@ -28,7 +30,7 @@
2830
QgsWFSLayerItem::QgsWFSLayerItem( QgsDataItem* parent, QString name, QgsDataSourceURI uri, QString featureType, QString title, QString crsString )
2931
: QgsLayerItem( parent, title, parent->path() + '/' + name, QString(), QgsLayerItem::Vector, "WFS" )
3032
{
31-
mUri = QgsWFSCapabilities( uri.encodedUri() ).uriGetFeature( featureType, crsString );
33+
mUri = QgsWFSDataSourceURI::build( uri.uri(), featureType, crsString );
3234
setState( Populated );
3335
mIconName = "mIconConnect.png";
3436
}
@@ -53,22 +55,17 @@ QgsWFSConnectionItem::~QgsWFSConnectionItem()
5355

5456
QVector<QgsDataItem*> QgsWFSConnectionItem::createChildren()
5557
{
56-
QgsDataSourceURI uri;
57-
uri.setEncodedUri( mUri );
58+
QgsDataSourceURI uri( mUri );
5859
QgsDebugMsg( "mUri = " + mUri );
5960

60-
mCapabilities = new QgsWFSCapabilities( mUri );
61+
QgsWFSCapabilities capabilities( mUri );
6162

62-
mCapabilities->requestCapabilities();
63-
64-
QEventLoop loop;
65-
connect( mCapabilities, SIGNAL( gotCapabilities() ), &loop, SLOT( quit() ) );
66-
loop.exec( QEventLoop::ExcludeUserInputEvents );
63+
capabilities.requestCapabilities( true );
6764

6865
QVector<QgsDataItem*> layers;
69-
if ( mCapabilities->errorCode() == QgsWFSCapabilities::NoError )
66+
if ( capabilities.errorCode() == QgsWFSCapabilities::NoError )
7067
{
71-
QgsWFSCapabilities::GetCapabilities caps = mCapabilities->capabilities();
68+
QgsWFSCapabilities::Capabilities caps = capabilities.capabilities();
7269
Q_FOREACH ( const QgsWFSCapabilities::FeatureType& featureType, caps.featureTypes )
7370
{
7471
//QgsWFSLayerItem* layer = new QgsWFSLayerItem( this, mName, featureType.name, featureType.title );
@@ -82,9 +79,6 @@ QVector<QgsDataItem*> QgsWFSConnectionItem::createChildren()
8279
// TODO: show the error without adding child
8380
}
8481

85-
mCapabilities->deleteLater();
86-
mCapabilities = nullptr;
87-
8882
return layers;
8983
}
9084

@@ -105,7 +99,7 @@ QList<QAction*> QgsWFSConnectionItem::actions()
10599

106100
void QgsWFSConnectionItem::editConnection()
107101
{
108-
QgsNewHttpConnection nc( nullptr, "/Qgis/connections-wfs/", mName );
102+
QgsNewHttpConnection nc( nullptr, QgsWFSConstants::CONNECTIONS_WFS, mName );
109103
nc.setWindowTitle( tr( "Modify WFS connection" ) );
110104

111105
if ( nc.exec() )
@@ -117,7 +111,7 @@ void QgsWFSConnectionItem::editConnection()
117111

118112
void QgsWFSConnectionItem::deleteConnection()
119113
{
120-
QgsOWSConnection::deleteConnection( "WFS", mName );
114+
QgsWFSConnection::deleteConnection( mName );
121115
// the parent should be updated
122116
mParent->refresh();
123117
}
@@ -143,11 +137,11 @@ QVector<QgsDataItem*> QgsWFSRootItem::createChildren()
143137
{
144138
QVector<QgsDataItem*> connections;
145139

146-
Q_FOREACH ( const QString& connName, QgsOWSConnection::connectionList( "WFS" ) )
140+
Q_FOREACH ( const QString& connName, QgsWFSConnection::connectionList() )
147141
{
148-
QgsOWSConnection connection( "WFS", connName );
142+
QgsWFSConnection connection( connName );
149143
QString path = "wfs:/" + connName;
150-
QgsDataItem * conn = new QgsWFSConnectionItem( this, connName, path, connection.uri().encodedUri() );
144+
QgsDataItem * conn = new QgsWFSConnectionItem( this, connName, path, connection.uri().uri() );
151145
connections.append( conn );
152146
}
153147
return connections;
@@ -178,7 +172,7 @@ void QgsWFSRootItem::connectionsChanged()
178172

179173
void QgsWFSRootItem::newConnection()
180174
{
181-
QgsNewHttpConnection nc( nullptr, "/Qgis/connections-wfs/" );
175+
QgsNewHttpConnection nc( nullptr, QgsWFSConstants::CONNECTIONS_WFS );
182176
nc.setWindowTitle( tr( "Create a new WFS connection" ) );
183177

184178
if ( nc.exec() )
@@ -211,10 +205,10 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem )
211205
if ( thePath.startsWith( "wfs:/" ) )
212206
{
213207
QString connectionName = thePath.split( '/' ).last();
214-
if ( QgsOWSConnection::connectionList( "WFS" ).contains( connectionName ) )
208+
if ( QgsWFSConnection::connectionList().contains( connectionName ) )
215209
{
216-
QgsOWSConnection connection( "WFS", connectionName );
217-
return new QgsWFSConnectionItem( parentItem, "WMS", thePath, connection.uri().encodedUri() );
210+
QgsWFSConnection connection( connectionName );
211+
return new QgsWFSConnectionItem( parentItem, "WMS", thePath, connection.uri().uri() );
218212
}
219213
}
220214

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/***************************************************************************
2+
qgswfsdatasourceuri.cpp
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgswfsconstants.h"
17+
#include "qgswfsdatasourceuri.h"
18+
#include "qgsmessagelog.h"
19+
20+
QgsWFSDataSourceURI::QgsWFSDataSourceURI( const QString& uri )
21+
: mURI( uri )
22+
{
23+
// Compatiblity with QGIS < 2.16 layer URI of the format
24+
// http://example.com/?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=x&SRSNAME=y&username=foo&password=
25+
if ( !mURI.hasParam( QgsWFSConstants::URI_PARAM_URL ) )
26+
{
27+
QUrl url( uri );
28+
QString srsname = url.queryItemValue( "SRSNAME" );
29+
QString bbox = url.queryItemValue( "BBOX" );
30+
QString typeName = url.queryItemValue( "TYPENAME" );
31+
QString filter = url.queryItemValue( "FILTER" );
32+
mAuth.mUserName = url.queryItemValue( QgsWFSConstants::URI_PARAM_USERNAME );
33+
mAuth.mPassword = url.queryItemValue( QgsWFSConstants::URI_PARAM_PASSWORD );
34+
mAuth.mAuthCfg = url.queryItemValue( QgsWFSConstants::URI_PARAM_AUTHCFG );
35+
36+
// Now remove all stuff that is not the core URL
37+
url.removeQueryItem( "SERVICE" );
38+
url.removeQueryItem( "VERSION" );
39+
url.removeQueryItem( "TYPENAME" );
40+
url.removeQueryItem( "REQUEST" );
41+
url.removeQueryItem( "BBOX" );
42+
url.removeQueryItem( "SRSNAME" );
43+
url.removeQueryItem( "FILTER" );
44+
url.removeQueryItem( QgsWFSConstants::URI_PARAM_USERNAME );
45+
url.removeQueryItem( QgsWFSConstants::URI_PARAM_PASSWORD );
46+
url.removeQueryItem( QgsWFSConstants::URI_PARAM_AUTHCFG );
47+
48+
mURI = QgsDataSourceURI();
49+
mURI.setParam( QgsWFSConstants::URI_PARAM_URL, url.toEncoded() );
50+
setTypeName( typeName );
51+
setSRSName( srsname );
52+
53+
//if the xml comes from the dialog, it needs to be a string to pass the validity test
54+
if ( filter.startsWith( '\'' ) && filter.endsWith( '\'' ) && filter.size() > 1 )
55+
{
56+
filter.chop( 1 );
57+
filter.remove( 0, 1 );
58+
}
59+
60+
setFilter( filter );
61+
if ( !bbox.isEmpty() )
62+
mURI.setParam( QgsWFSConstants::URI_PARAM_RESTRICT_TO_REQUEST_BBOX, "1" );
63+
}
64+
else
65+
{
66+
mAuth.mUserName = mURI.param( QgsWFSConstants::URI_PARAM_USERNAME );
67+
mAuth.mPassword = mURI.param( QgsWFSConstants::URI_PARAM_PASSWORD );
68+
mAuth.mAuthCfg = mURI.param( QgsWFSConstants::URI_PARAM_AUTHCFG );
69+
}
70+
}
71+
72+
QString QgsWFSDataSourceURI::uri()
73+
{
74+
return mURI.uri();
75+
}
76+
77+
QUrl QgsWFSDataSourceURI::baseURL( bool bIncludeServiceWFS ) const
78+
{
79+
QUrl url( mURI.param( QgsWFSConstants::URI_PARAM_URL ) );
80+
if ( bIncludeServiceWFS )
81+
{
82+
url.addQueryItem( "SERVICE", "WFS" );
83+
}
84+
return url;
85+
}
86+
87+
QString QgsWFSDataSourceURI::version() const
88+
{
89+
if ( !mURI.hasParam( QgsWFSConstants::URI_PARAM_VERSION ) )
90+
return QgsWFSConstants::VERSION_AUTO;
91+
return mURI.param( QgsWFSConstants::URI_PARAM_VERSION );
92+
}
93+
94+
int QgsWFSDataSourceURI::maxNumFeatures() const
95+
{
96+
if ( !mURI.hasParam( QgsWFSConstants::URI_PARAM_MAXNUMFEATURES ) )
97+
return 0;
98+
return mURI.param( QgsWFSConstants::URI_PARAM_MAXNUMFEATURES ).toInt();
99+
}
100+
101+
void QgsWFSDataSourceURI::setMaxNumFeatures( int maxNumFeatures )
102+
{
103+
mURI.removeParam( QgsWFSConstants::URI_PARAM_MAXNUMFEATURES );
104+
mURI.setParam( QgsWFSConstants::URI_PARAM_MAXNUMFEATURES , QString( maxNumFeatures ) );
105+
}
106+
107+
void QgsWFSDataSourceURI::setTypeName( const QString& typeName )
108+
{
109+
mURI.removeParam( QgsWFSConstants::URI_PARAM_TYPENAME );
110+
mURI.setParam( QgsWFSConstants::URI_PARAM_TYPENAME, typeName );
111+
}
112+
113+
QString QgsWFSDataSourceURI::typeName() const
114+
{
115+
return mURI.param( QgsWFSConstants::URI_PARAM_TYPENAME );
116+
}
117+
118+
void QgsWFSDataSourceURI::setSRSName( const QString& crsString )
119+
{
120+
mURI.removeParam( QgsWFSConstants::URI_PARAM_SRSNAME );
121+
if ( !crsString.isEmpty() )
122+
mURI.setParam( QgsWFSConstants::URI_PARAM_SRSNAME, crsString );
123+
}
124+
125+
QString QgsWFSDataSourceURI::SRSName() const
126+
{
127+
return mURI.param( QgsWFSConstants::URI_PARAM_SRSNAME );
128+
}
129+
130+
QString QgsWFSDataSourceURI::filter() const
131+
{
132+
return mURI.param( QgsWFSConstants::URI_PARAM_FILTER );
133+
}
134+
135+
void QgsWFSDataSourceURI::setFilter( const QString& filter )
136+
{
137+
mURI.removeParam( QgsWFSConstants::URI_PARAM_FILTER );
138+
if ( !filter.isEmpty() )
139+
{
140+
mURI.setParam( QgsWFSConstants::URI_PARAM_FILTER, filter );
141+
}
142+
}
143+
144+
bool QgsWFSDataSourceURI::isRestrictedToRequestBBOX() const
145+
{
146+
return mURI.hasParam( QgsWFSConstants::URI_PARAM_RESTRICT_TO_REQUEST_BBOX ) &&
147+
mURI.param( QgsWFSConstants::URI_PARAM_RESTRICT_TO_REQUEST_BBOX ).toInt() == 1;
148+
}
149+
150+
bool QgsWFSDataSourceURI::ignoreAxisOrientation() const
151+
{
152+
return mURI.hasParam( QgsWFSConstants::URI_PARAM_IGNOREAXISORIENTATION );
153+
}
154+
155+
bool QgsWFSDataSourceURI::invertAxisOrientation() const
156+
{
157+
return mURI.hasParam( QgsWFSConstants::URI_PARAM_INVERTAXISORIENTATION );
158+
}
159+
160+
QString QgsWFSDataSourceURI::build( const QString& baseUri,
161+
const QString& typeName,
162+
const QString& crsString,
163+
const QString& filter,
164+
bool restrictToCurrentViewExtent )
165+
{
166+
QgsWFSDataSourceURI uri( baseUri );
167+
uri.setTypeName( typeName );
168+
uri.setSRSName( crsString );
169+
uri.setFilter( filter );
170+
if ( restrictToCurrentViewExtent )
171+
uri.mURI.setParam( QgsWFSConstants::URI_PARAM_RESTRICT_TO_REQUEST_BBOX, "1" );
172+
return uri.uri();
173+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/***************************************************************************
2+
qgswfsdatasourceuri.h
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSWFSDATASOURCEURI_H
17+
#define QGSWFSDATASOURCEURI_H
18+
19+
#include "qgsauthmanager.h"
20+
#include "qgsdatasourceuri.h"
21+
#include "qgsrectangle.h"
22+
23+
#include <QNetworkRequest>
24+
#include <QString>
25+
26+
// TODO: merge with QgsWmsAuthorization?
27+
struct QgsWFSAuthorization
28+
{
29+
QgsWFSAuthorization( const QString& userName = QString(), const QString& password = QString(), const QString& authcfg = QString() )
30+
: mUserName( userName )
31+
, mPassword( password )
32+
, mAuthCfg( authcfg )
33+
{}
34+
35+
//! set authorization header
36+
bool setAuthorization( QNetworkRequest &request ) const
37+
{
38+
if ( !mAuthCfg.isEmpty() )
39+
{
40+
return QgsAuthManager::instance()->updateNetworkRequest( request, mAuthCfg );
41+
}
42+
else if ( !mUserName.isNull() || !mPassword.isNull() )
43+
{
44+
request.setRawHeader( "Authorization", "Basic " + QString( "%1:%2" ).arg( mUserName, mPassword ).toAscii().toBase64() );
45+
}
46+
return true;
47+
}
48+
49+
//! Username for basic http authentication
50+
QString mUserName;
51+
52+
//! Password for basic http authentication
53+
QString mPassword;
54+
55+
//! Authentication configuration ID
56+
QString mAuthCfg;
57+
};
58+
59+
/** Utility class that wraps a QgsDataSourceURI with conveniency
60+
* methods with the parameters used for a WFS URI.
61+
*/
62+
class QgsWFSDataSourceURI
63+
{
64+
public:
65+
66+
QgsWFSDataSourceURI( const QString& uri );
67+
68+
/** Return the URI */
69+
QString uri();
70+
71+
/** Return base URL (with SERVICE=WFS parameter if bIncludeServiceWFS=true) */
72+
QUrl baseURL( bool bIncludeServiceWFS = true ) const;
73+
74+
/** Get WFS version. Can be auto, 1.0.0, 1.1.0 or 2.0.0. */
75+
QString version() const;
76+
77+
/** Return user defined limit of features to download. 0=no limitation */
78+
int maxNumFeatures() const;
79+
80+
/** Set user defined limit of features to download */
81+
void setMaxNumFeatures( int maxNumFeatures );
82+
83+
/** Get typename (with prefix) */
84+
QString typeName() const;
85+
86+
/** Set typename (with prefix)*/
87+
void setTypeName( const QString& typeName );
88+
89+
/** Get SRS name (in the normalized form EPSG:xxxx) */
90+
QString SRSName() const;
91+
92+
/** Set SRS name (in the normalized form EPSG:xxxx) */
93+
void setSRSName( const QString& crsString );
94+
95+
/** Get OGC filter xml or a QGIS expression */
96+
QString filter() const;
97+
98+
/** Set OGC filter xml or a QGIS expression */
99+
void setFilter( const QString& filterIn );
100+
101+
/** Returns whether GetFeature request should include the request bounding box. Defaults to false */
102+
bool isRestrictedToRequestBBOX() const;
103+
104+
/** Returns whether axis orientation should be ignored (for WFS >= 1.1). Defaults to false */
105+
bool ignoreAxisOrientation() const;
106+
107+
/** Returns whether axis orientation should be inverted. Defaults to false */
108+
bool invertAxisOrientation() const;
109+
110+
/** Return authorization parameters */
111+
QgsWFSAuthorization& auth() { return mAuth; }
112+
113+
/** Builds a derived uri from a base uri */
114+
//! @param filter can be an OGC filter xml or a QGIS expression
115+
static QString build( const QString& uri,
116+
const QString& typeName,
117+
const QString& crsString = QString(),
118+
const QString& filter = QString(),
119+
bool restrictToCurrentViewExtent = false );
120+
121+
private:
122+
QgsDataSourceURI mURI;
123+
QgsWFSAuthorization mAuth;
124+
};
125+
126+
127+
#endif // QGSWFSDATASOURCEURI_H
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/***************************************************************************
2+
qgswfsdescribefeaturetype.cpp
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgswfsdescribefeaturetype.h"
17+
18+
QgsWFSDescribeFeatureType::QgsWFSDescribeFeatureType( const QString& theUri ) :
19+
QgsWFSRequest( theUri )
20+
{
21+
}
22+
23+
bool QgsWFSDescribeFeatureType::requestFeatureType( const QString& WFSVersion,
24+
const QString& typeName )
25+
{
26+
QUrl url( baseURL() );
27+
url.addQueryItem( "REQUEST", "DescribeFeatureType" );
28+
url.addQueryItem( "VERSION", WFSVersion );
29+
url.addQueryItem( "TYPENAME", typeName );
30+
31+
return sendGET( url, true, false );
32+
}
33+
34+
QString QgsWFSDescribeFeatureType::errorMessageWithReason( const QString& reason )
35+
{
36+
return tr( "Download of feature type failed: %1" ).arg( reason );
37+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/***************************************************************************
2+
qgswfsdescribefeaturetype.h
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSWFSDESCRIBEFEATURETYPE_H
16+
#define QGSWFSDESCRIBEFEATURETYPE_H
17+
18+
#include "qgswfsrequest.h"
19+
20+
/** Manages the DescribeFeatureType request */
21+
class QgsWFSDescribeFeatureType : public QgsWFSRequest
22+
{
23+
Q_OBJECT
24+
public:
25+
explicit QgsWFSDescribeFeatureType( const QString& theUri );
26+
27+
/** Issue the request */
28+
bool requestFeatureType( const QString& WFSVersion, const QString& typeName );
29+
30+
protected:
31+
virtual QString errorMessageWithReason( const QString& reason ) override;
32+
};
33+
34+
#endif // QGSWFSDESCRIBEFEATURETYPE_H

‎src/providers/wfs/qgswfsfeatureiterator.cpp

Lines changed: 787 additions & 65 deletions
Large diffs are not rendered by default.

‎src/providers/wfs/qgswfsfeatureiterator.h

Lines changed: 177 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
---------------------
44
begin : Januar 2013
55
copyright : (C) 2013 by Marco Hugentobler
6+
(C) 2016 by Even Rouault
67
email : marco dot hugentobler at sourcepole dot ch
8+
even.rouault at spatialys.com
79
***************************************************************************
810
* *
911
* This program is free software; you can redistribute it and/or modify *
@@ -16,52 +18,208 @@
1618
#define QGSWFSFEATUREITERATOR_H
1719

1820
#include "qgsfeatureiterator.h"
21+
#include "qgswfsrequest.h"
22+
#include "qgsgml.h"
23+
#include "qgsspatialindex.h"
1924

2025
class QgsWFSProvider;
21-
class QgsSpatialIndex;
22-
typedef QMap<QgsFeatureId, QgsFeature*> QgsFeaturePtrMap;
26+
class QgsWFSSharedData;
27+
class QgsVectorDataProvider;
28+
class QProgressDialog;
2329

30+
typedef QPair<QgsFeature, QString> QgsWFSFeatureGmlIdPair;
2431

25-
class QgsWFSFeatureSource : public QObject, public QgsAbstractFeatureSource
32+
33+
/** Utility class to issue a GetFeature resultType=hits request */
34+
class QgsWFSFeatureHitsAsyncRequest: public QgsWFSRequest
2635
{
2736
Q_OBJECT
37+
public:
38+
explicit QgsWFSFeatureHitsAsyncRequest( QgsWFSDataSourceURI& uri );
39+
~QgsWFSFeatureHitsAsyncRequest();
40+
41+
void launch( const QUrl& url );
42+
43+
/** Return result of request, or -1 if not known/error */
44+
int numberMatched() const { return mNumberMatched; }
45+
46+
signals:
47+
void gotHitsResponse();
48+
49+
private slots:
50+
void hitsReplyFinished();
51+
52+
protected:
53+
virtual QString errorMessageWithReason( const QString& reason ) override;
54+
55+
private:
56+
int mNumberMatched;
57+
};
2858

59+
/** This class runs one (or several if paging is needed) GetFeature request,
60+
process the results as soon as they arrived and notify them to the
61+
serializer to fill the case, and to the iterator that subscribed
62+
Instances of this class may be run in a dedicated thread (QgsWFSThreadedFeatureDownloader)
63+
A progress dialog may pop-up in GUI mode (if the download takes a certain time)
64+
to allow cancelling the download.
65+
*/
66+
class QgsWFSFeatureDownloader: public QgsWFSRequest
67+
{
68+
Q_OBJECT
2969
public:
30-
explicit QgsWFSFeatureSource( const QgsWFSProvider* p );
31-
~QgsWFSFeatureSource();
70+
explicit QgsWFSFeatureDownloader( QgsWFSSharedData* shared, QString filter = QString() );
71+
~QgsWFSFeatureDownloader();
3272

33-
QgsFeatureIterator getFeatures( const QgsFeatureRequest& request ) override;
73+
/** Start the download.
74+
* @param serializeFeatures whether to notify the sharedData serializer.
75+
* @param maxFeatures user-defined limit of features to download. Overrides
76+
* the one defined in the URI. Typically by the QgsWFSProvider,
77+
* when it cannot guess the geometry type.
78+
*/
79+
void run( bool serializeFeatures, int maxFeatures );
80+
81+
public slots:
82+
/** To interrupt the download. Thread-safe */
83+
void stop();
3484

3585
signals:
36-
void extentRequested( const QgsRectangle & );
86+
/** Emitted when new features have been received */
87+
void featureReceived( QVector<QgsWFSFeatureGmlIdPair> );
88+
89+
/** Emitted when the download is finished (successful or not) */
90+
void endOfDownload( bool success );
91+
92+
/** Used internally by the stop() method */
93+
void doStop();
94+
95+
/** Emitted with the total accumulated number of features downloaded. */
96+
void updateProgress( int totalFeatureCount );
3797

3898
protected:
99+
virtual QString errorMessageWithReason( const QString& reason ) override;
39100

40-
QgsFields mFields;
41-
QgsFeaturePtrMap mFeatures;
42-
QgsSpatialIndex* mSpatialIndex;
101+
private slots:
102+
void createProgressDialog();
103+
void startHitsRequest();
104+
void gotHitsResponse();
105+
void setStopFlag();
43106

44-
friend class QgsWFSFeatureIterator;
107+
private:
108+
QUrl buildURL( int startIndex, int maxFeatures, bool forHits );
109+
110+
/** Mutable data shared between provider, feature sources and downloader. */
111+
QgsWFSSharedData* mShared;
112+
/** WFS filter */
113+
QString mWFSFilter;
114+
/** Whether the download should stop */
115+
bool mStop;
116+
/** Progress dialog */
117+
QProgressDialog* mProgressDialog;
118+
/** If the progress dialog should be shown immediately, or if it should be
119+
let to QProgressDialog logic to decide when to show it */
120+
bool mProgressDialogShowImmediately;
121+
int mNumberMatched;
122+
QWidget* mMainWindow;
123+
QTimer* mTimer;
124+
QgsWFSFeatureHitsAsyncRequest mFeatureHitsAsyncRequest;
125+
int mTotalDownloadedFeatureCount;
45126
};
46127

47-
class QgsWFSFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsWFSFeatureSource>
128+
/** Downloader thread */
129+
class QgsWFSThreadedFeatureDownloader: public QThread
48130
{
131+
Q_OBJECT
49132
public:
50-
QgsWFSFeatureIterator( QgsWFSFeatureSource* source, bool ownSource, const QgsFeatureRequest& request );
133+
explicit QgsWFSThreadedFeatureDownloader( QgsWFSSharedData* shared );
134+
~QgsWFSThreadedFeatureDownloader();
135+
136+
/** Return downloader object */
137+
QgsWFSFeatureDownloader* downloader() { return mDownloader; }
138+
139+
/** Stops (synchronously) the download */
140+
void stop();
141+
142+
signals:
143+
/** Emitted when the thread is ready */
144+
void ready();
145+
146+
protected:
147+
/** Inherited from QThread. Starts the download */
148+
void run() override;
149+
150+
private:
151+
QgsWFSSharedData* mShared; //!< Mutable data shared between provider and feature sources
152+
QString mWFSFilter;
153+
QgsWFSFeatureDownloader* mDownloader;
154+
};
155+
156+
class QgsWFSFeatureSource;
157+
158+
/** Feature iterator. The iterator will internally both subscribe to a live
159+
downloader to receive 'fresh' features, and to a iterator on the features
160+
already cached. It will actually start by consuming cache features for
161+
initial feedback, and then process the live downloaded features. */
162+
class QgsWFSFeatureIterator : public QObject,
163+
public QgsAbstractFeatureIteratorFromSource<QgsWFSFeatureSource>
164+
{
165+
Q_OBJECT
166+
public:
167+
explicit QgsWFSFeatureIterator( QgsWFSFeatureSource* source, bool ownSource, const QgsFeatureRequest& request );
51168
~QgsWFSFeatureIterator();
52169

53170
bool rewind() override;
171+
54172
bool close() override;
55173

56-
protected:
57-
bool fetchFeature( QgsFeature& f ) override;
174+
void setInterruptionChecker( QgsInterruptionChecker* interruptionChecker ) override;
175+
176+
/** Used by QgsWFSSharedData::registerToCache() */
177+
void connectSignals( QObject* downloader );
58178

59-
/** Copies feature attributes / geometry from f to feature*/
60-
void copyFeature( const QgsFeature* f, QgsFeature& feature, bool fetchGeometry );
179+
private slots:
180+
void featureReceived( QVector<QgsWFSFeatureGmlIdPair> list );
181+
void endOfDownload( bool success );
182+
void checkInterruption();
61183

62184
private:
63-
QList<QgsFeatureId> mSelectedFeatures;
64-
QList<QgsFeatureId>::const_iterator mFeatureIterator;
185+
186+
bool fetchFeature( QgsFeature& f ) override;
187+
188+
/** Copies feature attributes / geometry from srcFeature to dstFeature*/
189+
void copyFeature( const QgsFeature& srcFeature, QgsFeature& dstFeature );
190+
191+
QSharedPointer<QgsWFSSharedData> mShared; //!< Mutable data shared between provider and feature sources
192+
193+
/** Feature list received from the downloader */
194+
QVector<QgsWFSFeatureGmlIdPair> mFeatureList;
195+
196+
/** Index in mFeatureList */
197+
int mCurFeatureIdx;
198+
199+
/** Subset of attributes (relatives to mShared->mFields) to fetch. Only valid if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes ) */
200+
QgsAttributeList mSubSetAttributes;
201+
202+
bool mDownloadFinished;
203+
QEventLoop* mLoop;
204+
QgsFeatureIterator mCacheIterator;
205+
QgsInterruptionChecker* mInterruptionChecker;
206+
};
207+
208+
/** Feature source */
209+
class QgsWFSFeatureSource : public QgsAbstractFeatureSource
210+
{
211+
public:
212+
explicit QgsWFSFeatureSource( const QgsWFSProvider* p );
213+
~QgsWFSFeatureSource();
214+
215+
/** Returns features matching the request */
216+
QgsFeatureIterator getFeatures( const QgsFeatureRequest& request ) override;
217+
218+
protected:
219+
220+
QSharedPointer<QgsWFSSharedData> mShared; //!< Mutable data shared between provider and feature sources
221+
222+
friend class QgsWFSFeatureIterator;
65223
};
66224

67225
#endif // QGSWFSFEATUREITERATOR_H

‎src/providers/wfs/qgswfsprovider.cpp

Lines changed: 264 additions & 1120 deletions
Large diffs are not rendered by default.

‎src/providers/wfs/qgswfsprovider.h

Lines changed: 57 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
-------------------
44
begin : July 25, 2006
55
copyright : (C) 2006 by Marco Hugentobler
6+
(C) 2016 by Even Rouault
67
email : marco dot hugentobler at karto dot baug dot ethz dot ch
8+
even.rouault at spatialys.com
79
***************************************************************************/
810

911
/***************************************************************************
@@ -18,66 +20,47 @@
1820
#ifndef QGSWFSPROVIDER_H
1921
#define QGSWFSPROVIDER_H
2022

21-
#include <QDomElement>
2223
#include "qgis.h"
23-
#include "qgsauthmanager.h"
2424
#include "qgsrectangle.h"
2525
#include "qgscoordinatereferencesystem.h"
2626
#include "qgsvectordataprovider.h"
27-
#include "qgsmaplayer.h"
28-
#include "qgsvectorlayer.h"
2927
#include "qgswfsfeatureiterator.h"
30-
31-
#include <QNetworkRequest>
28+
#include "qgswfsdatasourceuri.h"
3229

3330
class QgsRectangle;
34-
class QgsSpatialIndex;
35-
36-
// TODO: merge with QgsWmsAuthorization?
37-
struct QgsWFSAuthorization
38-
{
39-
QgsWFSAuthorization( const QString& userName = QString(), const QString& password = QString(), const QString& authcfg = QString() )
40-
: mUserName( userName )
41-
, mPassword( password )
42-
, mAuthCfg( authcfg )
43-
{}
44-
45-
//! set authorization header
46-
bool setAuthorization( QNetworkRequest &request ) const
47-
{
48-
if ( !mAuthCfg.isEmpty() )
49-
{
50-
return QgsAuthManager::instance()->updateNetworkRequest( request, mAuthCfg );
51-
}
52-
else if ( !mUserName.isNull() || !mPassword.isNull() )
53-
{
54-
request.setRawHeader( "Authorization", "Basic " + QString( "%1:%2" ).arg( mUserName, mPassword ).toAscii().toBase64() );
55-
}
56-
return true;
57-
}
58-
59-
//! Username for basic http authentication
60-
QString mUserName;
61-
62-
//! Password for basic http authentication
63-
QString mPassword;
64-
65-
//! Authentication configuration ID
66-
QString mAuthCfg;
67-
};
68-
69-
/** A provider reading features from a WFS server*/
31+
class QgsWFSSharedData;
32+
33+
34+
/** \ingroup WFSProvider
35+
*
36+
* A provider reading/write features from/into a WFS server.
37+
*
38+
* Below quick design notes on the whole provider.
39+
*
40+
* QgsWFSProvider class purpose:
41+
* - in constructor, do a GetCapabilities request to determine server-side feature limit,
42+
paging capabilities, WFS version, edition capabilities. Do a DescribeFeatureType request
43+
to determine fields, geometry name and type.
44+
* - in other methods, mostly WFS-T related operations.
45+
*
46+
* QgsWFSSharedData class purpose:
47+
* - contains logic shared by QgsWFSProvider, QgsWFSFeatureIterator and QgsWFSFeatureDownloader.
48+
* - one of its main function is to maintain a on-disk cache of the features retrieved
49+
* from the server. This cache is a Spatialite database.
50+
*
51+
* QgsWFSRequest class purpose: abstract base class to create WFS network requests,
52+
* such as QgsWFSCapabilities, QgsWFSDescribeFeatureType, QgsWFSFeatureDownloader,
53+
* QgsWFSFeatureHitsAsyncRequest, QgsWFSFeatureHitsRequest, QgsWFSTransactionRequest
54+
*
55+
* QgsWFSDataSourceURI class purpose: wrapper above QgsDataSourceURI to get/set
56+
* the specific attributes of a WFS URI.
57+
*
58+
*/
7059
class QgsWFSProvider : public QgsVectorDataProvider
7160
{
7261
Q_OBJECT
7362
public:
7463

75-
enum REQUEST_ENCODING
76-
{
77-
GET,
78-
FILE //reads from a file on disk
79-
};
80-
8164
explicit QgsWFSProvider( const QString& uri );
8265
~QgsWFSProvider();
8366

@@ -91,10 +74,17 @@ class QgsWFSProvider : public QgsVectorDataProvider
9174
long featureCount() const override;
9275

9376
const QgsFields& fields() const override;
94-
void rewind();
9577

9678
virtual QgsCoordinateReferenceSystem crs() override;
9779

80+
/** Accessor for sql where clause used to limit dataset */
81+
virtual QString subsetString() override;
82+
83+
/** Mutator for sql where clause used to limit dataset size */
84+
virtual bool setSubsetString( const QString& theSQL, bool updateFeatureCount = true ) override;
85+
86+
virtual bool supportsSubsetString() override { return true; }
87+
9888
/* Inherited from QgsDataProvider */
9989

10090
QgsRectangle extent() override;
@@ -106,14 +96,6 @@ class QgsWFSProvider : public QgsVectorDataProvider
10696

10797
/* new functions */
10898

109-
/** Sets the encoding type in which the provider makes requests and interprets
110-
results. Possibilities are GET, POST, SOAP*/
111-
void setRequestEncoding( QgsWFSProvider::REQUEST_ENCODING e ) {mRequestEncoding = e;}
112-
113-
/** Makes a GetFeatures, receives the features from the wfs server (as GML), converts them to QgsFeature and
114-
stores them in a vector*/
115-
int getFeature( const QString& uri );
116-
11799
//Editing operations
118100
/**
119101
* Adds a list of features
@@ -144,118 +126,48 @@ class QgsWFSProvider : public QgsVectorDataProvider
144126
*/
145127
virtual bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override;
146128

147-
/** Collects information about the field types. Is called internally from QgsWFSProvider ctor. The method delegates the work to request specific ones and gives back the name of the geometry attribute and the thematic attributes with their types*/
148-
int describeFeatureType( const QString& uri, QString& geometryAttribute,
149-
QgsFields& fields, QGis::WkbType& geomType );
150-
151129
public slots:
152130
/** Reloads the data from the source. Needs to be implemented by providers with data caches to
153131
synchronize with changes in the data source*/
154132
virtual void reloadData() override;
155133

156-
signals:
157-
void dataReadProgressMessage( const QString& message );
158-
159-
void dataChanged();
160-
161134
private slots:
162-
/** Receives the progress signals from QgsWFSData::dataReadProgress, generates a string
163-
and emits the dataReadProgressMessage signal*/
164-
void handleWFSProgressMessage( int done, int total );
165-
166-
/** Sets mNetworkRequestFinished flag to true*/
167-
void networkRequestFinished();
168135

169-
void extendExtent( const QgsRectangle & );
136+
void featureReceivedAnalyzeOneFeature( QVector<QgsWFSFeatureGmlIdPair> );
170137

171138
private:
172-
bool mNetworkRequestFinished;
173-
friend class QgsWFSFeatureSource;
139+
/** Mutable data shared between provider and feature sources */
140+
QSharedPointer<QgsWFSSharedData> mShared;
174141

175-
//! http authorization details
176-
QgsWFSAuthorization mAuth;
142+
friend class QgsWFSFeatureSource;
177143

178144
protected:
179-
/** Thematic attributes*/
180-
QgsFields mFields;
181-
/** Name of geometry attribute*/
182-
QString mGeometryAttribute;
183-
/** The encoding used for request/response. Can be GET, POST or SOAP*/
184-
REQUEST_ENCODING mRequestEncoding;
145+
146+
//! String used to define a subset of the layer
147+
QString mSubsetString;
148+
185149
/** Bounding box for the layer*/
186150
QgsRectangle mExtent;
187-
/** Spatial filter for the layer*/
188-
QgsRectangle mSpatialFilter;
189-
/** Flag if precise intersection test is needed. Otherwise, every feature is returned (even if a filter is set)*/
190-
bool mUseIntersect;
191-
/** A spatial index for fast access to a feature subset*/
192-
QgsSpatialIndex *mSpatialIndex;
193-
/** Vector where the ids of the selected features are inserted*/
194-
QList<QgsFeatureId> mSelectedFeatures;
195-
/** Iterator on the feature vector for use in rewind(), nextFeature(), etc...*/
196-
QList<QgsFeatureId>::iterator mFeatureIterator;
197-
/** Map <feature Id / feature> */
198-
QMap<QgsFeatureId, QgsFeature* > mFeatures;
199-
/** Stores the relation between provider ids and WFS server ids*/
200-
QMap<QgsFeatureId, QString > mIdMap;
201151
/** Geometry type of the features in this layer*/
202152
mutable QGis::WkbType mWKBType;
203-
/** Source CRS*/
204-
QgsCoordinateReferenceSystem mSourceCRS;
205-
int mFeatureCount;
206-
int mMaxFeatureCount;
207153
/** Flag if provider is valid*/
208154
bool mValid;
209-
bool mCached;
210-
bool mPendingRetrieval;
211155
/** Namespace URL of the server (comes from DescribeFeatureDocument)*/
212-
QString mWfsNamespace;
156+
QString mApplicationNamespace;
213157
/** Server capabilities for this layer (generated from capabilities document)*/
214158
int mCapabilities;
215-
#if 0
216-
/** GetRenderedOnly: layer asociated with this provider*/
217-
QgsVectorLayer *mLayer;
218-
/** GetRenderedOnly: fetch only features within canvas extent to be rendered*/
219-
bool mGetRenderedOnly;
220-
/** GetRenderedOnly initializaiton flat*/
221-
bool mInitGro;
222-
#endif
223-
/** If GetRenderedOnly, extent specified in WFS getFeatures; else empty (no constraint)*/
224-
QgsRectangle mGetExtent;
225-
226-
//encoding specific methods of getFeature
227-
int getFeatureGET( const QString& uri, const QString& geometryAttribute );
228-
int getFeaturePOST( const QString& uri, const QString& geometryAttribute );
229-
int getFeatureSOAP( const QString& uri, const QString& geometryAttribute );
230-
int getFeatureFILE( const QString& uri, const QString& geometryAttribute );
231-
//encoding specific methods of describeFeatureType
232-
int describeFeatureTypeGET( const QString& uri, QString& geometryAttribute, QgsFields& fields, QGis::WkbType& geomType );
233-
int describeFeatureTypePOST( const QString& uri, QString& geometryAttribute, QgsFields& fields );
234-
int describeFeatureTypeSOAP( const QString& uri, QString& geometryAttribute, QgsFields& fields );
235-
int describeFeatureTypeFile( const QString& uri, QString& geometryAttribute, QgsFields& fields, QGis::WkbType& geomType );
159+
160+
/** Collects information about the field types. Is called internally from QgsWFSProvider ctor.
161+
The method gives back the name of
162+
the geometry attribute and the thematic attributes with their types*/
163+
bool describeFeatureType( QString& geometryAttribute,
164+
QgsFields& fields, QGis::WkbType& geomType );
236165

237166
/** Reads the name of the geometry attribute, the thematic attributes and their types from a dom document. Returns 0 in case of success*/
238167
int readAttributesFromSchema( QDomDocument& schemaDoc, QString& geometryAttribute, QgsFields& fields, QGis::WkbType& geomType );
239-
/** This method tries to guess the geometry attribute and the other attribute names from the .gml file if no schema is present. Returns 0 in case of success*/
240-
int guessAttributesFromFile( const QString& uri, QString& geometryAttribute, QStringList &thematicAttributes, QGis::WkbType& geomType ) const;
241-
242-
//GML2 specific methods
243-
int getExtentFromGML2( QgsRectangle* extent, const QDomElement& wfsCollectionElement ) const;
244-
245-
int getFeaturesFromGML2( const QDomElement& wfsCollectionElement, const QString& geometryAttribute );
246-
/** Reads the <gml:coordinates> element and extracts the coordinates as points
247-
@param coords list where the found coordinates are appended
248-
@param elem the <gml:coordinates> element
249-
@return 0 in case of success*/
250-
int readGML2Coordinates( QList<QgsPoint> &coords, const QDomElement& elem ) const;
251-
/** Tries to create a QgsCoordinateReferenceSystem object and assign it to mSourceCRS. Returns 0 in case of success*/
252-
int setCRSFromGML2( const QDomElement& wfsCollectionElement );
253168

254169
//helper methods for WFS-T
255170

256-
/** Returns HTTP parameter value from url (or empty string if it does not exist)*/
257-
QString parameterFromUrl( const QString& name ) const;
258-
259171
/** Removes a possible namespace prefix from a typename*/
260172
void removeNamespacePrefix( QString& tname ) const;
261173
/** Returns namespace prefix (or an empty string if there is no prefix)*/
@@ -273,22 +185,12 @@ class QgsWFSProvider : public QgsVectorDataProvider
273185
bool transactionSuccess( const QDomDocument& serverResponse ) const;
274186
/** Returns the inserted ids*/
275187
QStringList insertedFeatureIds( const QDomDocument& serverResponse ) const;
276-
/** Returns a key suitable for new items*/
277-
QgsFeatureId findNewKey() const;
278-
/** Retrieve capabilities for this layer from GetCapabilities document (will be stored in mCapabilites)*/
279-
void getLayerCapabilities();
280-
/** Takes <Operations> element and updates the capabilities*/
281-
void appendSupportedOperations( const QDomElement& operationsElem, int& capabilities ) const;
188+
/** Retrieve version and capabilities for this layer from GetCapabilities document (will be stored in mCapabilites)*/
189+
bool getCapabilities();
282190
/** Records provider error*/
283191
void handleException( const QDomDocument& serverResponse );
284-
#if 0
285-
/** Initializes "Cache Features" inactive processing*/
286-
bool initGetRenderedOnly( const QgsRectangle &rect );
287-
#endif
288192
/** Converts DescribeFeatureType schema geometry property type to WKBType*/
289193
QGis::WkbType geomTypeFromPropertyType( const QString& attName, const QString& propType );
290-
291-
void deleteData();
292194
};
293195

294-
#endif
196+
#endif /* QGSWFSPROVIDER_H */

‎src/providers/wfs/qgswfsrequest.cpp

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
/***************************************************************************
2+
qgswfsrequest.cpp
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2011 by Martin Dobias
6+
(C) 2016 by Even Rouault
7+
email : wonder dot sk at gmail dot com
8+
even.rouault at spatialys.com
9+
***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgswfsrequest.h"
19+
#include "qgslogger.h"
20+
#include "qgsmessagelog.h"
21+
#include "qgsnetworkaccessmanager.h"
22+
23+
#include <QEventLoop>
24+
#include <QNetworkCacheMetaData>
25+
#include <QCryptographicHash> // just for testin file:// fake_qgis_http_endpoint hack
26+
27+
QgsWFSRequest::QgsWFSRequest( const QString& theUri )
28+
: mUri( theUri )
29+
, mReply( nullptr )
30+
, mErrorCode( QgsWFSRequest::NoError )
31+
, mIsAborted( false )
32+
, mForceRefresh( false )
33+
{
34+
QgsDebugMsg( "theUri = " + theUri );
35+
}
36+
37+
QgsWFSRequest::~QgsWFSRequest()
38+
{
39+
abort();
40+
}
41+
42+
bool QgsWFSRequest::sendGET( const QUrl& url, bool synchronous, bool forceRefresh, bool cache )
43+
{
44+
abort(); // cancel previous
45+
mIsAborted = false;
46+
47+
mErrorMessage.clear();
48+
mErrorCode = QgsWFSRequest::NoError;
49+
mForceRefresh = forceRefresh;
50+
mResponse.clear();
51+
52+
QUrl modifiedUrl( url );
53+
if ( modifiedUrl.toString().contains( "fake_qgis_http_endpoint" ) )
54+
{
55+
// Just for testing with local files instead of http:// ressources
56+
QString modifiedUrlString = modifiedUrl.toString();
57+
QgsDebugMsg( QString( "Get %1" ).arg( modifiedUrlString ) );
58+
modifiedUrlString = modifiedUrlString.mid( QString( "http://" ).size() );
59+
QString args = modifiedUrlString.mid( modifiedUrlString.indexOf( '?' ) );
60+
if ( modifiedUrlString.size() > 256 )
61+
{
62+
args = QCryptographicHash::hash( args.toUtf8(), QCryptographicHash::Md5 ).toHex();
63+
}
64+
else
65+
{
66+
args.replace( "?", "_" );
67+
args.replace( "&", "_" );
68+
args.replace( "<", "_" );
69+
args.replace( ">", "_" );
70+
args.replace( "'", "_" );
71+
args.replace( "\"", "_" );
72+
args.replace( " ", "_" );
73+
args.replace( ":", "_" );
74+
args.replace( "/", "_" );
75+
args.replace( "\n", "_" );
76+
}
77+
#ifdef Q_OS_WIN
78+
// Passing "urls" like "http://c:/path" to QUrl 'eats' the : after c,
79+
// so we must restore it
80+
if ( modifiedUrlString[1] == '/' )
81+
{
82+
modifiedUrlString = modifiedUrlString[0] + ":/" + modifiedUrlString.mid( 2 );
83+
}
84+
#endif
85+
modifiedUrlString = modifiedUrlString.mid( 0, modifiedUrlString.indexOf( '?' ) ) + args;
86+
QgsDebugMsg( QString( "Get %1 (after laundering)" ).arg( modifiedUrlString ) );
87+
modifiedUrl = QUrl::fromLocalFile( modifiedUrlString );
88+
}
89+
90+
QNetworkRequest request( modifiedUrl );
91+
if ( !mUri.auth().setAuthorization( request ) )
92+
{
93+
mErrorCode = QgsWFSRequest::NetworkError;
94+
mErrorMessage = errorMessageFailedAuth();
95+
QgsMessageLog::logMessage( mErrorMessage, tr( "WFS" ) );
96+
return false;
97+
}
98+
99+
if ( cache )
100+
{
101+
request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, forceRefresh ? QNetworkRequest::AlwaysNetwork : QNetworkRequest::PreferCache );
102+
request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
103+
}
104+
105+
mReply = QgsNetworkAccessManager::instance()->get( request );
106+
connect( mReply, SIGNAL( finished() ), this, SLOT( replyFinished() ) );
107+
connect( mReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( replyProgress( qint64, qint64 ) ) );
108+
109+
if ( !synchronous )
110+
return true;
111+
112+
QEventLoop loop;
113+
connect( this, SIGNAL( downloadFinished() ), &loop, SLOT( quit() ) );
114+
loop.exec( QEventLoop::ExcludeUserInputEvents );
115+
116+
return mErrorMessage.isEmpty();
117+
}
118+
119+
bool QgsWFSRequest::sendPOST( const QUrl& url, const QString& contentTypeHeader, const QByteArray& data )
120+
{
121+
abort(); // cancel previous
122+
mIsAborted = false;
123+
124+
mErrorMessage.clear();
125+
mErrorCode = QgsWFSRequest::NoError;
126+
mForceRefresh = true;
127+
mResponse.clear();
128+
129+
if ( url.toEncoded().contains( "fake_qgis_http_endpoint" ) )
130+
{
131+
// Hack for testing purposes
132+
QUrl modifiedUrl( url );
133+
modifiedUrl.addQueryItem( "POSTDATA", QString::fromUtf8( data ) );
134+
return sendGET( modifiedUrl, true, true, false );
135+
}
136+
137+
QNetworkRequest request( url );
138+
if ( !mUri.auth().setAuthorization( request ) )
139+
{
140+
mErrorCode = QgsWFSRequest::NetworkError;
141+
mErrorMessage = errorMessageFailedAuth();
142+
QgsMessageLog::logMessage( mErrorMessage, tr( "WFS" ) );
143+
return false;
144+
}
145+
request.setHeader( QNetworkRequest::ContentTypeHeader, contentTypeHeader );
146+
147+
mReply = QgsNetworkAccessManager::instance()->post( request, data );
148+
connect( mReply, SIGNAL( finished() ), this, SLOT( replyFinished() ) );
149+
connect( mReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( replyProgress( qint64, qint64 ) ) );
150+
151+
QEventLoop loop;
152+
connect( this, SIGNAL( downloadFinished() ), &loop, SLOT( quit() ) );
153+
loop.exec( QEventLoop::ExcludeUserInputEvents );
154+
155+
return mErrorMessage.isEmpty();
156+
}
157+
158+
void QgsWFSRequest::abort()
159+
{
160+
QgsDebugMsg( "Entered" );
161+
mIsAborted = true;
162+
if ( mReply )
163+
{
164+
mReply->deleteLater();
165+
mReply = nullptr;
166+
}
167+
}
168+
169+
void QgsWFSRequest::replyProgress( qint64 bytesReceived, qint64 bytesTotal )
170+
{
171+
QString msg = tr( "%1 of %2 bytes downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QString( "unknown number of" ) : QString::number( bytesTotal ) );
172+
QgsDebugMsg( msg );
173+
174+
if ( !mIsAborted && mReply )
175+
{
176+
if ( mReply->error() == QNetworkReply::NoError )
177+
{
178+
QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
179+
if ( !redirect.isNull() )
180+
{
181+
// We don't want to emit downloadProgress() for a redirect
182+
return;
183+
}
184+
}
185+
}
186+
187+
emit downloadProgress( bytesReceived, bytesTotal );
188+
}
189+
190+
void QgsWFSRequest::replyFinished()
191+
{
192+
QgsDebugMsg( "entering." );
193+
if ( !mIsAborted && mReply )
194+
{
195+
if ( mReply->error() == QNetworkReply::NoError )
196+
{
197+
QgsDebugMsg( "reply ok" );
198+
QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
199+
if ( !redirect.isNull() )
200+
{
201+
QgsDebugMsg( "Request redirected." );
202+
203+
const QUrl& toUrl = redirect.toUrl();
204+
mReply->request();
205+
if ( toUrl == mReply->url() )
206+
{
207+
mErrorMessage = tr( "Redirect loop detected: %1" ).arg( toUrl.toString() );
208+
QgsMessageLog::logMessage( mErrorMessage, tr( "WFS" ) );
209+
mResponse.clear();
210+
}
211+
else
212+
{
213+
QNetworkRequest request( toUrl );
214+
if ( !mUri.auth().setAuthorization( request ) )
215+
{
216+
mResponse.clear();
217+
mErrorMessage = errorMessageFailedAuth();
218+
mErrorCode = QgsWFSRequest::NetworkError;
219+
QgsMessageLog::logMessage( mErrorMessage, tr( "WFS" ) );
220+
emit downloadFinished();
221+
return;
222+
}
223+
request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, mForceRefresh ? QNetworkRequest::AlwaysNetwork : QNetworkRequest::PreferCache );
224+
request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
225+
226+
mReply->deleteLater();
227+
mReply = nullptr;
228+
229+
QgsDebugMsg( QString( "redirected: %1 forceRefresh=%2" ).arg( redirect.toString() ).arg( mForceRefresh ) );
230+
mReply = QgsNetworkAccessManager::instance()->get( request );
231+
connect( mReply, SIGNAL( finished() ), this, SLOT( replyFinished() ) );
232+
connect( mReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( replyProgress( qint64, qint64 ) ) );
233+
return;
234+
}
235+
}
236+
else
237+
{
238+
const QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance();
239+
240+
if ( nam->cache() )
241+
{
242+
QNetworkCacheMetaData cmd = nam->cache()->metaData( mReply->request().url() );
243+
244+
QNetworkCacheMetaData::RawHeaderList hl;
245+
Q_FOREACH ( const QNetworkCacheMetaData::RawHeader &h, cmd.rawHeaders() )
246+
{
247+
if ( h.first != "Cache-Control" )
248+
hl.append( h );
249+
}
250+
cmd.setRawHeaders( hl );
251+
252+
QgsDebugMsg( QString( "expirationDate:%1" ).arg( cmd.expirationDate().toString() ) );
253+
if ( cmd.expirationDate().isNull() )
254+
{
255+
cmd.setExpirationDate( QDateTime::currentDateTime().addSecs( defaultExpirationInSec() ) );
256+
}
257+
258+
nam->cache()->updateMetaData( cmd );
259+
}
260+
else
261+
{
262+
QgsDebugMsg( "No cache!" );
263+
}
264+
265+
#ifdef QGISDEBUG
266+
bool fromCache = mReply->attribute( QNetworkRequest::SourceIsFromCacheAttribute ).toBool();
267+
QgsDebugMsg( QString( "Reply was cached: %1" ).arg( fromCache ) );
268+
#endif
269+
270+
mResponse = mReply->readAll();
271+
272+
if ( mResponse.isEmpty() )
273+
{
274+
mErrorMessage = tr( "empty response: %1" ).arg( mReply->errorString() );
275+
mErrorCode = QgsWFSRequest::ServerExceptionError;
276+
}
277+
}
278+
}
279+
else
280+
{
281+
mErrorMessage = errorMessageWithReason( mReply->errorString() );
282+
mErrorCode = QgsWFSRequest::ServerExceptionError;
283+
QgsMessageLog::logMessage( mErrorMessage, tr( "WFS" ) );
284+
mResponse.clear();
285+
}
286+
}
287+
288+
if ( mReply )
289+
{
290+
mReply->deleteLater();
291+
mReply = nullptr;
292+
}
293+
294+
emit downloadFinished();
295+
}
296+
297+
QString QgsWFSRequest::errorMessageFailedAuth()
298+
{
299+
return errorMessageWithReason( tr( "network request update failed for authentication config" ) );
300+
}

‎src/providers/wfs/qgswfsrequest.h

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/***************************************************************************
2+
qgswfsrequest.h
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSWFSREQUEST_H
16+
#define QGSWFSREQUEST_H
17+
18+
#include <QObject>
19+
#include <QNetworkRequest>
20+
#include <QNetworkReply>
21+
#include <QUrl>
22+
23+
#include "qgswfsdatasourceuri.h"
24+
25+
/** Abstract base class for a WFS request. */
26+
class QgsWFSRequest : public QObject
27+
{
28+
Q_OBJECT
29+
public:
30+
explicit QgsWFSRequest( const QString& theUri );
31+
32+
virtual ~QgsWFSRequest();
33+
34+
/** \brief proceed to sending a GET request */
35+
bool sendGET( const QUrl& url, bool synchronous, bool forceRefresh = false, bool cache = true );
36+
37+
/** \brief proceed to sending a synchronous POST request */
38+
bool sendPOST( const QUrl& url, const QString& contentTypeHeader, const QByteArray& data );
39+
40+
enum ErrorCode { NoError,
41+
NetworkError,
42+
XmlError,
43+
ServerExceptionError,
44+
WFSVersionNotSupported
45+
};
46+
47+
/** \brief Return error code (after download/post) */
48+
ErrorCode errorCode() const { return mErrorCode; }
49+
50+
/** \brief Return error message (after download/post) */
51+
const QString& errorMessage() const { return mErrorMessage; }
52+
53+
/** \brief Return server response (after download/post) */
54+
const QByteArray& response() const { return mResponse; }
55+
56+
public slots:
57+
/** Abort network request immediately */
58+
void abort();
59+
60+
signals:
61+
/** \brief emit a signal when data arrives */
62+
void downloadProgress( qint64, qint64 );
63+
64+
/** \brief emit a signal once the download is finished */
65+
void downloadFinished();
66+
67+
protected slots:
68+
void replyProgress( qint64, qint64 );
69+
void replyFinished();
70+
71+
protected:
72+
/** URI */
73+
QgsWFSDataSourceURI mUri;
74+
75+
/** The reply to the request */
76+
QNetworkReply *mReply;
77+
78+
/** The error message associated with the last error. */
79+
QString mErrorMessage;
80+
81+
/** Error code */
82+
ErrorCode mErrorCode;
83+
84+
/** Raw response */
85+
QByteArray mResponse;
86+
87+
/** Whether the request is aborted. */
88+
bool mIsAborted;
89+
90+
/** Whether to force refresh (i.e. issue a network request and not use cache) */
91+
bool mForceRefresh;
92+
93+
protected:
94+
95+
//! base service URL
96+
QUrl baseURL() const { return mUri.baseURL(); }
97+
98+
/** Return (translated) error message, composed with a
99+
(possibly translated, but sometimes coming from server) reason */
100+
virtual QString errorMessageWithReason( const QString& reason ) = 0;
101+
102+
/** Return experiation delay in second */
103+
virtual int defaultExpirationInSec() { return 0; }
104+
105+
private:
106+
QString errorMessageFailedAuth();
107+
108+
};
109+
110+
#endif // QGSWFSREQUEST_H

‎src/providers/wfs/qgswfsshareddata.cpp

Lines changed: 931 additions & 0 deletions
Large diffs are not rendered by default.

‎src/providers/wfs/qgswfsshareddata.h

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/***************************************************************************
2+
qgswfsshareddata.h
3+
---------------------
4+
begin : March 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSWFSSHAREDDATA_H
16+
#define QGSWFSSHAREDDATA_H
17+
18+
#include "qgsspatialindex.h"
19+
#include "qgswfsfeatureiterator.h"
20+
#include "qgswfsrequest.h"
21+
22+
/** This class holds data, and logic, shared between QgsWFSProvider, QgsWFSFeatureIterator
23+
* and QgsWFSFeatureDownloader. It manages the on-disk cache, as a Spatialite
24+
* database.
25+
*
26+
* The structure of the table in the database is the following one :
27+
* - attribute fields of the DescribeFeatureType response
28+
* - __qgis_gen_counter: generation counter
29+
* - __qgis_gmlid: feature 'fid' or 'gml:id'
30+
* - __qgis_hexwkb_geom: feature geometry as a hexadecimal encoded WKB string.
31+
* - geometry: polygon with the bounding box of the geometry.
32+
*
33+
* The generation counter is a synchronization mechanism between the iterator
34+
* that will try to return cached features first and then downloaded features.
35+
* It avoids the iterator to return features in duplicates, by returning features
36+
* that have just been serialized by the live downloader and notified to the
37+
* iterator.
38+
*
39+
* The reason for not storing directly the geometry is that we may potentially
40+
* store in the future non-linear geometries that aren't handled by Spatialite.
41+
*
42+
* It contains also methods used in WFS-T context to update the cache content,
43+
* from the changes initiated by the user.
44+
*/
45+
class QgsWFSSharedData : public QObject
46+
{
47+
Q_OBJECT
48+
public:
49+
explicit QgsWFSSharedData( const QString& uri );
50+
~QgsWFSSharedData();
51+
52+
/** Used by a QgsWFSFeatureIterator to start a downloader and get the
53+
generation counter. */
54+
int registerToCache( QgsWFSFeatureIterator* iterator, QgsRectangle rect = QgsRectangle() );
55+
56+
/** Used by the rewind() method of an iterator so as to get the up-to-date
57+
generation counter. */
58+
int getUpdatedCounter();
59+
60+
/** Used by the background downloader to serialize downloaded features into
61+
the cache. Also used by a WFS-T insert operation */
62+
void serializeFeatures( QVector<QgsWFSFeatureGmlIdPair>& featureList );
63+
64+
/** Called by QgsWFSFeatureDownloader::run() at the end of the download process. */
65+
void endOfDownload( bool success, int featureCount );
66+
67+
/** Used by QgsWFSProvider::reloadData(). The effect is to invalid
68+
all the caching state, so that a new request results in fresh download */
69+
void invalidateCache();
70+
71+
/** Give a feature id, find the correspond fid/gml.id. Used by WFS-T */
72+
QString findGmlId( QgsFeatureId fid );
73+
74+
/** Delete from the on-disk cache the features of given fid. Used by WFS-T */
75+
bool deleteFeatures( const QgsFeatureIds& fidlist );
76+
77+
/** Change into the on-disk cache the passed geometries. Used by WFS-T */
78+
bool changeGeometryValues( const QgsGeometryMap &geometry_map );
79+
80+
/** Change into the on-disk cache the passed attributes. Used by WFS-T */
81+
bool changeAttributeValues( const QgsChangedAttributesMap &attr_map );
82+
83+
/** Force an update of the feature count */
84+
void setFeatureCount( int featureCount );
85+
86+
/** Return layer feature count. Might issue a GetFeature resultType=hits request */
87+
int getFeatureCount( bool issueRequestIfNeeded = true );
88+
89+
/** Return whether the feature count is exact, or approximate/transient */
90+
bool isFeatureCountExact() const { return mFeatureCountExact; }
91+
92+
/** Return whether the server support RESULTTYPE=hits */
93+
bool supportsHits() const { return mSupportsHits; }
94+
95+
/** Compute WFS filter from the filter in the URI */
96+
void computeFilter();
97+
98+
/** Return WFS filter */
99+
QString WFSFilter() const { return mWFSFilter; }
100+
101+
/** Return srsName */
102+
QString srsName() const;
103+
104+
protected:
105+
friend class QgsWFSFeatureIterator;
106+
friend class QgsWFSFeatureDownloader;
107+
friend class QgsWFSProvider;
108+
109+
/** Datasource URI */
110+
QgsWFSDataSourceURI mURI;
111+
112+
/** WFS version to use. Comes from GetCapabilities response */
113+
QString mWFSVersion;
114+
115+
/** Source CRS*/
116+
QgsCoordinateReferenceSystem mSourceCRS;
117+
118+
/** Attribute fields of the layer */
119+
QgsFields mFields;
120+
121+
/** Name of geometry attribute */
122+
QString mGeometryAttribute;
123+
124+
/** The data provider of the on-disk cache */
125+
QgsVectorDataProvider* mCacheDataProvider;
126+
127+
/** Current BBOX used by the downloader */
128+
QgsRectangle mRect;
129+
130+
/** Server-side or user-side limit of downloaded features (in a single GetFeature()). Valid if > 0 */
131+
int mMaxFeatures;
132+
133+
/** Server-side limit of downloaded features. Valid if > 0 */
134+
int mMaxFeaturesServer;
135+
136+
/** Whether resultType=hits is supported */
137+
bool mSupportsHits;
138+
139+
/** Whether paging is enabled (WFS 2.0) */
140+
bool mSupportsPaging;
141+
142+
private:
143+
144+
/** Main mutex to protect most data members that can be modified concurrently */
145+
QMutex mMutex;
146+
147+
/** Mutex used specifically by registerToCache() */
148+
QMutex mMutexRegisterToCache;
149+
150+
/** Mutex used only by serializeFeatures() */
151+
QMutex mCacheWriteMutex;
152+
153+
/** WFS filter */
154+
QString mWFSFilter;
155+
156+
/** The background feature downloader */
157+
QgsWFSThreadedFeatureDownloader* mDownloader;
158+
159+
/** Whether the downloader has finished (or been cancelled) */
160+
bool mDownloadFinished;
161+
162+
/** The generation counter. When a iterator is built or rewind, it gets the
163+
current value of the generation counter to query the features in the cache
164+
whose generation counter is <= the current value. That way the iterator
165+
can consume first cached features, and then deal with the features that are
166+
notified in live by the downloader. */
167+
int mGenCounter;
168+
169+
/** Number of features of the layer */
170+
int mFeatureCount;
171+
172+
/** Whether mFeatureCount value is exact or approximate / in construction */
173+
bool mFeatureCountExact;
174+
175+
/** Filename of the on-disk cache */
176+
QString mCacheDbname;
177+
178+
/** Tablename of the on-disk cache */
179+
QString mCacheTablename;
180+
181+
/** Spatial index of requested cached regions */
182+
QgsSpatialIndex mCachedRegions;
183+
184+
/** Requested cached regions */
185+
QVector< QgsFeature > mRegions;
186+
187+
/** Whether a GetFeature hits request has been issued to retrieve the number of features */
188+
bool mGetFeatureHitsIssued;
189+
190+
/** Number of features that have been cached, or attempted to be cached */
191+
int mTotalFeaturesAttemptedToBeCached;
192+
193+
/** Returns the set of gmlIds that have already been downloaded and
194+
cached, so as to avoid to cache duplicates. */
195+
QSet<QString> getExistingCachedGmlIds( const QVector<QgsWFSFeatureGmlIdPair>& featureList );
196+
197+
/** Create the on-disk cache and connect to it */
198+
bool createCache();
199+
};
200+
201+
/** Utility class to issue a GetFeature resultType=hits request */
202+
class QgsWFSFeatureHitsRequest: public QgsWFSRequest
203+
{
204+
Q_OBJECT
205+
public:
206+
explicit QgsWFSFeatureHitsRequest( QgsWFSDataSourceURI& uri );
207+
~QgsWFSFeatureHitsRequest();
208+
209+
/** Return the feature count, or -1 in case of error */
210+
int getFeatureCount( const QString& WFSVersion, const QString& filter );
211+
212+
protected:
213+
virtual QString errorMessageWithReason( const QString& reason ) override;
214+
};
215+
216+
#endif // QGSWFSSHAREDDATA_H

‎src/providers/wfs/qgswfssourceselect.cpp

Lines changed: 54 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
qgswfssourceselect.cpp
33
-------------------
44
begin : August 25, 2006
5-
copyright : (C) 2006 by Marco Hugentobler
5+
copyright : (C) 2016 by Marco Hugentobler
66
email : marco dot hugentobler at karto dot baug dot ethz dot ch
77
***************************************************************************/
88

@@ -15,10 +15,12 @@
1515
* *
1616
***************************************************************************/
1717

18+
#include "qgswfsconstants.h"
1819
#include "qgswfssourceselect.h"
19-
#include "qgsowsconnection.h"
20+
#include "qgswfsconnection.h"
2021
#include "qgswfscapabilities.h"
2122
#include "qgswfsprovider.h"
23+
#include "qgswfsdatasourceuri.h"
2224
#include "qgsnewhttpconnection.h"
2325
#include "qgsgenericprojectionselector.h"
2426
#include "qgsexpressionbuilderdialog.h"
@@ -27,7 +29,6 @@
2729
#include "qgscoordinatereferencesystem.h"
2830
#include "qgscoordinatetransform.h"
2931
#include "qgslogger.h"
30-
#include "qgsmapcanvas.h" //for current view extent
3132
#include "qgsmanageconnectionsdialog.h"
3233

3334
#include <QDomDocument>
@@ -37,6 +38,13 @@
3738
#include <QFileDialog>
3839
#include <QPainter>
3940

41+
enum
42+
{
43+
MODEL_IDX_TITLE,
44+
MODEL_IDX_NAME,
45+
MODEL_IDX_ABSTRACT,
46+
MODEL_IDX_FILTER
47+
};
4048

4149
QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget* parent, Qt::WindowFlags fl, bool embeddedMode )
4250
: QDialog( parent, fl )
@@ -84,11 +92,10 @@ QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget* parent, Qt::WindowFlags fl, boo
8492
mHoldDialogOpen->setChecked( settings.value( "/Windows/WFSSourceSelect/HoldDialogOpen", false ).toBool() );
8593

8694
mModel = new QStandardItemModel();
87-
mModel->setHorizontalHeaderItem( 0, new QStandardItem( "Title" ) );
88-
mModel->setHorizontalHeaderItem( 1, new QStandardItem( "Name" ) );
89-
mModel->setHorizontalHeaderItem( 2, new QStandardItem( "Abstract" ) );
90-
mModel->setHorizontalHeaderItem( 3, new QStandardItem( "Cache Feature" ) );
91-
mModel->setHorizontalHeaderItem( 4, new QStandardItem( "Filter" ) );
95+
mModel->setHorizontalHeaderItem( MODEL_IDX_TITLE, new QStandardItem( "Title" ) );
96+
mModel->setHorizontalHeaderItem( MODEL_IDX_NAME, new QStandardItem( "Name" ) );
97+
mModel->setHorizontalHeaderItem( MODEL_IDX_ABSTRACT, new QStandardItem( "Abstract" ) );
98+
mModel->setHorizontalHeaderItem( MODEL_IDX_FILTER, new QStandardItem( "Filter" ) );
9299

93100
mModelProxy = new QSortFilterProxyModel( this );
94101
mModelProxy->setSourceModel( mModel );
@@ -118,7 +125,7 @@ QgsWFSSourceSelect::~QgsWFSSourceSelect()
118125

119126
void QgsWFSSourceSelect::populateConnectionList()
120127
{
121-
QStringList keys = QgsOWSConnection::connectionList( "WFS" );
128+
QStringList keys = QgsWFSConnection::connectionList();
122129

123130
QStringList::Iterator it = keys.begin();
124131
cmbConnections->clear();
@@ -146,16 +153,16 @@ void QgsWFSSourceSelect::populateConnectionList()
146153
}
147154

148155
//set last used connection
149-
QString selectedConnection = QgsOWSConnection::selectedConnection( "WFS" );
156+
QString selectedConnection = QgsWFSConnection::selectedConnection();
150157
int index = cmbConnections->findText( selectedConnection );
151158
if ( index != -1 )
152159
{
153160
cmbConnections->setCurrentIndex( index );
154161
}
155162

156-
QgsOWSConnection connection( "WFS", cmbConnections->currentText() );
163+
QgsWFSConnection connection( cmbConnections->currentText() );
157164
delete mCapabilities;
158-
mCapabilities = new QgsWFSCapabilities( connection.uri().encodedUri() );
165+
mCapabilities = new QgsWFSCapabilities( connection.uri().uri() );
159166
connect( mCapabilities, SIGNAL( gotCapabilities() ), this, SLOT( capabilitiesReplyFinished() ) );
160167
}
161168

@@ -223,7 +230,7 @@ void QgsWFSSourceSelect::capabilitiesReplyFinished()
223230
return;
224231
}
225232

226-
QgsWFSCapabilities::GetCapabilities caps = mCapabilities->capabilities();
233+
QgsWFSCapabilities::Capabilities caps = mCapabilities->capabilities();
227234

228235
mAvailableCRS.clear();
229236
Q_FOREACH ( const QgsWFSCapabilities::FeatureType& featureType, caps.featureTypes )
@@ -234,34 +241,30 @@ void QgsWFSSourceSelect::capabilitiesReplyFinished()
234241
QStandardItem* abstractItem = new QStandardItem( featureType.abstract );
235242
abstractItem->setToolTip( "<font color=black>" + featureType.abstract + "</font>" );
236243
abstractItem->setTextAlignment( Qt::AlignLeft | Qt::AlignTop );
237-
QStandardItem* cachedItem = new QStandardItem();
238244
QStandardItem* filterItem = new QStandardItem();
239-
cachedItem->setCheckable( true );
240-
cachedItem->setCheckState( Qt::Checked );
241245

242246
typedef QList< QStandardItem* > StandardItemList;
243-
mModel->appendRow( StandardItemList() << titleItem << nameItem << abstractItem << cachedItem << filterItem );
247+
mModel->appendRow( StandardItemList() << titleItem << nameItem << abstractItem << filterItem );
244248

245249
// insert the available CRS into mAvailableCRS
246250
mAvailableCRS.insert( featureType.name, featureType.crslist );
247251
}
248252

249253
if ( !caps.featureTypes.isEmpty() )
250254
{
251-
treeView->resizeColumnToContents( 0 );
252-
treeView->resizeColumnToContents( 1 );
253-
treeView->resizeColumnToContents( 2 );
254-
treeView->resizeColumnToContents( 3 );
255-
for ( int i = 0; i < 2; i++ )
255+
treeView->resizeColumnToContents( MODEL_IDX_TITLE );
256+
treeView->resizeColumnToContents( MODEL_IDX_NAME );
257+
treeView->resizeColumnToContents( MODEL_IDX_ABSTRACT );
258+
for ( int i = MODEL_IDX_TITLE; i < MODEL_IDX_ABSTRACT; i++ )
256259
{
257260
if ( treeView->columnWidth( i ) > 300 )
258261
{
259262
treeView->setColumnWidth( i, 300 );
260263
}
261264
}
262-
if ( treeView->columnWidth( 2 ) > 150 )
265+
if ( treeView->columnWidth( MODEL_IDX_ABSTRACT ) > 150 )
263266
{
264-
treeView->setColumnWidth( 2, 150 );
267+
treeView->setColumnWidth( MODEL_IDX_ABSTRACT, 150 );
265268
}
266269
btnChangeSpatialRefSys->setEnabled( true );
267270
treeView->selectionModel()->select( mModel->index( 0, 0 ), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows );
@@ -277,7 +280,7 @@ void QgsWFSSourceSelect::capabilitiesReplyFinished()
277280

278281
void QgsWFSSourceSelect::addEntryToServerList()
279282
{
280-
QgsNewHttpConnection nc( nullptr, "/Qgis/connections-wfs/" );
283+
QgsNewHttpConnection nc( nullptr, QgsWFSConstants::CONNECTIONS_WFS );
281284
nc.setWindowTitle( tr( "Create a new WFS connection" ) );
282285

283286
if ( nc.exec() )
@@ -289,7 +292,7 @@ void QgsWFSSourceSelect::addEntryToServerList()
289292

290293
void QgsWFSSourceSelect::modifyEntryOfServerList()
291294
{
292-
QgsNewHttpConnection nc( nullptr, "/Qgis/connections-wfs/", cmbConnections->currentText() );
295+
QgsNewHttpConnection nc( nullptr, QgsWFSConstants::CONNECTIONS_WFS, cmbConnections->currentText() );
293296
nc.setWindowTitle( tr( "Modify WFS connection" ) );
294297

295298
if ( nc.exec() )
@@ -306,7 +309,7 @@ void QgsWFSSourceSelect::deleteEntryOfServerList()
306309
QMessageBox::StandardButton result = QMessageBox::information( this, tr( "Confirm Delete" ), msg, QMessageBox::Ok | QMessageBox::Cancel );
307310
if ( result == QMessageBox::Ok )
308311
{
309-
QgsOWSConnection::deleteConnection( "WFS", cmbConnections->currentText() );
312+
QgsWFSConnection::deleteConnection( cmbConnections->currentText() );
310313
cmbConnections->removeItem( cmbConnections->currentIndex() );
311314
emit connectionsChanged();
312315

@@ -338,7 +341,7 @@ void QgsWFSSourceSelect::connectToServer()
338341
}
339342
if ( mCapabilities )
340343
{
341-
mCapabilities->requestCapabilities();
344+
mCapabilities->requestCapabilities( false );
342345
}
343346
}
344347

@@ -352,42 +355,9 @@ void QgsWFSSourceSelect::addLayer()
352355
return;
353356
}
354357

355-
QgsOWSConnection connection( "WFS", cmbConnections->currentText() );
356-
QgsWFSCapabilities conn( connection.uri().encodedUri() );
358+
QgsWFSConnection connection( cmbConnections->currentText() );
357359

358360
QString pCrsString( labelCoordRefSys->text() );
359-
QgsCoordinateReferenceSystem pCrs( pCrsString );
360-
//prepare canvas extent info for layers with "cache features" option not set
361-
QgsRectangle extent;
362-
QVariant extentVariant = property( "MapExtent" );
363-
if ( extentVariant.isValid() )
364-
{
365-
QString extentString = extentVariant.toString();
366-
QStringList minMaxSplit = extentString.split( ':' );
367-
if ( minMaxSplit.size() > 1 )
368-
{
369-
QStringList xyMinSplit = minMaxSplit[0].split( ',' );
370-
QStringList xyMaxSplit = minMaxSplit[1].split( ',' );
371-
if ( xyMinSplit.size() > 1 && xyMaxSplit.size() > 1 )
372-
{
373-
extent.set( xyMinSplit[0].toDouble(), xyMinSplit[1].toDouble(),
374-
xyMaxSplit[0].toDouble(), xyMaxSplit[1].toDouble() );
375-
}
376-
}
377-
//does canvas have "on the fly" reprojection set?
378-
QVariant crsVariant = property( "MapCRS" );
379-
if ( crsVariant.isValid() )
380-
{ //transform between provider CRS set in source select dialog and canvas CRS
381-
QgsCoordinateReferenceSystem cCrs( crsVariant.toString() );
382-
if ( pCrs.isValid() && cCrs.isValid() )
383-
{
384-
QgsCoordinateTransform xform( pCrs, cCrs );
385-
extent = xform.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
386-
QgsDebugMsg( QString( "canvas transform: Canvas CRS=%1, Provider CRS=%2, BBOX=%3" )
387-
.arg( cCrs.authid(), pCrs.authid(), extent.asWktCoordinates() ) );
388-
}
389-
}
390-
}
391361

392362
//create layers that user selected from this WFS source
393363
QModelIndexList list = treeView->selectionModel()->selectedRows();
@@ -399,25 +369,24 @@ void QgsWFSSourceSelect::addLayer()
399369
continue;
400370
}
401371
int row = idx.row();
402-
QString typeName = mModel->item( row, 1 )->text(); //WFS repository's name for layer
403-
QString titleName = mModel->item( row, 0 )->text(); //WFS type name title for layer name (if option is set)
404-
QString filter = mModel->item( row, 4 )->text(); //optional filter specified by user
372+
QString typeName = mModel->item( row, MODEL_IDX_NAME )->text(); //WFS repository's name for layer
373+
QString titleName = mModel->item( row, MODEL_IDX_TITLE )->text(); //WFS type name title for layer name (if option is set)
374+
QString filter = mModel->item( row, MODEL_IDX_FILTER )->text(); //optional filter specified by user
405375
QString layerName = typeName;
406376
if ( cbxUseTitleLayerName->isChecked() && !titleName.isEmpty() )
407377
{
408378
layerName = titleName;
409379
}
410380
QgsDebugMsg( "Layer " + typeName + " Filter is " + filter );
411381

412-
//is "cache features" checked?
413-
//non-cached mode does not work anymore
414-
if ( !cbxFeatureCurrentViewExtent->isChecked() && mModel->item( row, 3 )->checkState() == Qt::Checked )
415-
{ //yes: entire WFS layer will be retrieved and cached
416-
mUri = conn.uriGetFeature( typeName, pCrsString, filter );
382+
//is "Only request features overlapping the view extent" checked?
383+
if ( !cbxFeatureCurrentViewExtent->isChecked() )
384+
{ //no: entire WFS layer will be retrieved and cached
385+
mUri = QgsWFSDataSourceURI::build( connection.uri().uri(), typeName, pCrsString, filter );
417386
}
418387
else
419-
{ //no: include BBOX of current canvas extent in URI
420-
mUri = conn.uriGetFeature( typeName, pCrsString, filter, extent );
388+
{ //yes
389+
mUri = QgsWFSDataSourceURI::build( connection.uri().uri(), typeName, pCrsString, filter, true );
421390
}
422391
emit addWfsLayer( mUri, layerName );
423392
}
@@ -434,23 +403,21 @@ void QgsWFSSourceSelect::buildQuery( const QModelIndex& index )
434403
{
435404
return;
436405
}
437-
QModelIndex filterIndex = index.sibling( index.row(), 4 );
438-
QString typeName = index.sibling( index.row(), 1 ).data().toString();
406+
QModelIndex filterIndex = index.sibling( index.row(), MODEL_IDX_FILTER );
407+
QString typeName = index.sibling( index.row(), MODEL_IDX_NAME ).data().toString();
439408

440409
//get available fields for wfs layer
441-
QgsWFSProvider p( "" ); //bypasses most provider instantiation logic
442-
QgsOWSConnection connection( "WFS", cmbConnections->currentText() );
443-
QgsWFSCapabilities conn( connection.uri().encodedUri() );
444-
QString uri = conn.uriDescribeFeatureType( typeName );
445-
446-
QgsFields fields;
447-
QString geometryAttribute;
448-
QGis::WkbType geomType;
449-
if ( p.describeFeatureType( uri, geometryAttribute, fields, geomType ) != 0 )
410+
QgsWFSConnection connection( cmbConnections->currentText() );
411+
QgsWFSDataSourceURI uri( connection.uri().uri() );
412+
uri.setTypeName( typeName );
413+
QgsWFSProvider p( uri.uri() );
414+
if ( !p.isValid() )
450415
{
451416
return;
452417
}
453418

419+
QgsFields fields( p.fields() );
420+
454421
//show expression builder
455422
QgsExpressionBuilderDialog d( nullptr, filterIndex.data().toString() );
456423

@@ -486,7 +453,7 @@ void QgsWFSSourceSelect::changeCRSFilter()
486453
QModelIndex currentIndex = treeView->selectionModel()->currentIndex();
487454
if ( currentIndex.isValid() )
488455
{
489-
QString currentTypename = currentIndex.sibling( currentIndex.row(), 1 ).data().toString();
456+
QString currentTypename = currentIndex.sibling( currentIndex.row(), MODEL_IDX_NAME ).data().toString();
490457
QgsDebugMsg( QString( "the current typename is: %1" ).arg( currentTypename ) );
491458

492459
QMap<QString, QStringList >::const_iterator crsIterator = mAvailableCRS.find( currentTypename );
@@ -514,12 +481,12 @@ void QgsWFSSourceSelect::changeCRSFilter()
514481
void QgsWFSSourceSelect::on_cmbConnections_activated( int index )
515482
{
516483
Q_UNUSED( index );
517-
QgsOWSConnection::setSelectedConnection( "WFS", cmbConnections->currentText() );
484+
QgsWFSConnection::setSelectedConnection( cmbConnections->currentText() );
518485

519-
QgsOWSConnection connection( "WFS", cmbConnections->currentText() );
486+
QgsWFSConnection connection( cmbConnections->currentText() );
520487

521488
delete mCapabilities;
522-
mCapabilities = new QgsWFSCapabilities( connection.uri().encodedUri() );
489+
mCapabilities = new QgsWFSCapabilities( connection.uri().uri() );
523490
connect( mCapabilities, SIGNAL( gotCapabilities() ), this, SLOT( capabilitiesReplyFinished() ) );
524491
}
525492

‎src/providers/wfs/qgswfssourceselect.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class QgsWFSSourceSelect: public QDialog, private Ui::QgsWFSSourceSelectBase
4949
~QgsWFSSourceSelect();
5050

5151
signals:
52-
void addWfsLayer( const QString& uri, const QString& typeName );
52+
void addWfsLayer( const QString& uri, const QString& layerName );
5353
void connectionsChanged();
5454

5555
private:
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/***************************************************************************
2+
qgswfstransactionrequest.cpp
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgswfstransactionrequest.h"
17+
#include "qgslogger.h"
18+
19+
QgsWFSTransactionRequest::QgsWFSTransactionRequest( const QString& theUri )
20+
: QgsWFSRequest( theUri )
21+
{
22+
}
23+
24+
bool QgsWFSTransactionRequest::send( const QDomDocument& doc, QDomDocument& serverResponse )
25+
{
26+
QUrl url( baseURL() );
27+
28+
QgsDebugMsg( doc.toString() );
29+
30+
if ( sendPOST( url, "text/xml", doc.toByteArray( -1 ) ) )
31+
{
32+
serverResponse.setContent( mResponse, true );
33+
return true;
34+
}
35+
return false;
36+
}
37+
38+
QString QgsWFSTransactionRequest::errorMessageWithReason( const QString& reason )
39+
{
40+
return tr( "Sending of transaction failed: %1" ).arg( reason );
41+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/***************************************************************************
2+
qgswfstransactionrequest.h
3+
---------------------
4+
begin : February 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSWFSTRANSACTIONREQUEST_H
16+
#define QGSWFSTRANSACTIONREQUEST_H
17+
18+
#include "qgswfsrequest.h"
19+
20+
/** Manages the Transaction requests */
21+
class QgsWFSTransactionRequest : public QgsWFSRequest
22+
{
23+
Q_OBJECT
24+
public:
25+
explicit QgsWFSTransactionRequest( const QString& theUri );
26+
27+
/** Send the transaction document and return the server response */
28+
bool send( const QDomDocument& doc, QDomDocument& serverResponse );
29+
30+
protected:
31+
virtual QString errorMessageWithReason( const QString& reason ) override;
32+
};
33+
34+
#endif // QGSWFSTRANSACTIONREQUEST_H

‎src/providers/wfs/qgswfsutils.cpp

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
/***************************************************************************
2+
qgswfsutils.cpp
3+
---------------------
4+
begin : March 2016
5+
copyright : (C) 2016 by Even Rouault
6+
email : even.rouault at spatialys.com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsapplication.h"
17+
#include "qgslogger.h"
18+
#include "qgswfsutils.h"
19+
20+
// 1 minute
21+
#define KEEP_ALIVE_DELAY (60 * 1000)
22+
23+
#include <QFile>
24+
#include <QDir>
25+
#include <QTimer>
26+
#include <QSharedMemory>
27+
#include <QDateTime>
28+
#include <QSettings>
29+
30+
QMutex QgsWFSUtils::gmMutex;
31+
QThread* QgsWFSUtils::gmThread = nullptr;
32+
bool QgsWFSUtils::gmKeepAliveWorks = false;
33+
int QgsWFSUtils::gmCounter = 0;
34+
35+
QString QgsWFSUtils::getBaseCacheDirectory( bool createIfNotExisting )
36+
{
37+
QSettings settings;
38+
QString cacheDirectory = settings.value( "cache/directory", QgsApplication::qgisSettingsDirPath() + "cache" ).toString();
39+
if ( createIfNotExisting )
40+
{
41+
QMutexLocker locker( &gmMutex );
42+
if ( !QDir( cacheDirectory ).exists( "wfsprovider" ) )
43+
{
44+
QgsDebugMsg( QString( "Creating main cache dir %1/wfsprovider" ).arg( cacheDirectory ) );
45+
QDir( cacheDirectory ).mkpath( "wfsprovider" );
46+
}
47+
}
48+
return QDir( cacheDirectory ).filePath( "wfsprovider" );
49+
}
50+
51+
QString QgsWFSUtils::getCacheDirectory( bool createIfNotExisting )
52+
{
53+
QString baseDirectory( getBaseCacheDirectory( createIfNotExisting ) );
54+
QString processPath( QString( "pid_%1" ).arg( QCoreApplication::applicationPid() ) );
55+
if ( createIfNotExisting )
56+
{
57+
QMutexLocker locker( &gmMutex );
58+
if ( !QDir( baseDirectory ).exists( processPath ) )
59+
{
60+
QgsDebugMsg( QString( "Creating our cache dir %1/%2" ).arg( baseDirectory ).arg( processPath ) );
61+
QDir( baseDirectory ).mkpath( processPath );
62+
}
63+
if ( gmCounter == 0 && gmKeepAliveWorks )
64+
{
65+
gmThread = new QgsWFSUtilsKeepAlive();
66+
gmThread->start();
67+
}
68+
gmCounter ++;
69+
}
70+
return QDir( baseDirectory ).filePath( processPath );
71+
}
72+
73+
QString QgsWFSUtils::acquireCacheDirectory()
74+
{
75+
return getCacheDirectory( true );
76+
}
77+
78+
void QgsWFSUtils::releaseCacheDirectory()
79+
{
80+
QMutexLocker locker( &gmMutex );
81+
gmCounter --;
82+
if ( gmCounter == 0 )
83+
{
84+
if ( gmThread != nullptr )
85+
{
86+
gmThread->exit();
87+
gmThread->wait();
88+
delete gmThread;
89+
gmThread = nullptr;
90+
}
91+
92+
// Destroys our cache directory, and the main cache directory if it is empty
93+
QString tmpDirname( getCacheDirectory( false ) );
94+
if ( QDir( tmpDirname ).exists() )
95+
{
96+
QgsDebugMsg( QString( "Removing our cache dir %1" ).arg( tmpDirname ) );
97+
removeDir( tmpDirname );
98+
99+
QString baseDirname( getBaseCacheDirectory( false ) );
100+
QDir baseDir( baseDirname );
101+
QFileInfoList fileList( baseDir.entryInfoList( QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files ) );
102+
if ( fileList.size() == 0 )
103+
{
104+
QgsDebugMsg( QString( "Removing main cache dir %1" ).arg( baseDirname ) );
105+
removeDir( baseDirname );
106+
}
107+
else
108+
{
109+
QgsDebugMsg( QString( "%1 entries remaining in %2" ).arg( fileList.size() ).arg( baseDirname ) );
110+
}
111+
}
112+
}
113+
}
114+
115+
bool QgsWFSUtils::removeDir( const QString &dirName )
116+
{
117+
QDir dir( dirName );
118+
QFileInfoList fileList( dir.entryInfoList( QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files ) );
119+
Q_FOREACH ( QFileInfo info, fileList )
120+
{
121+
bool result;
122+
if ( info.isDir() )
123+
{
124+
result = removeDir( info.absoluteFilePath() );
125+
}
126+
else
127+
{
128+
result = QFile::remove( info.absoluteFilePath() );
129+
}
130+
131+
if ( !result )
132+
break;
133+
}
134+
return dir.rmdir( dirName );
135+
}
136+
137+
138+
// We use a keep alive mechanism where every KEEP_ALIVE_DELAY ms we update
139+
// a shared memory segment with the current timestamp. This way, other QGIS
140+
// processes can check if the temporary directories of other process correspond
141+
// to alive or ghost processes.
142+
QgsWFSUtilsKeepAlive::QgsWFSUtilsKeepAlive()
143+
: mSharedMemory( QgsWFSUtils::createAndAttachSHM() )
144+
{
145+
updateTimestamp();
146+
}
147+
148+
QgsWFSUtilsKeepAlive::~QgsWFSUtilsKeepAlive()
149+
{
150+
delete mSharedMemory;
151+
}
152+
153+
void QgsWFSUtilsKeepAlive::updateTimestamp()
154+
{
155+
qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
156+
if ( mSharedMemory->lock() )
157+
{
158+
QgsDebugMsg( "Updating keep-alive" );
159+
memcpy( mSharedMemory->data(), &timestamp, sizeof( timestamp ) );
160+
mSharedMemory->unlock();
161+
}
162+
}
163+
164+
void QgsWFSUtilsKeepAlive::run()
165+
{
166+
QTimer timer;
167+
timer.setInterval( KEEP_ALIVE_DELAY );
168+
timer.start();
169+
connect( &timer, SIGNAL( timeout() ), this, SLOT( updateTimestamp() ) );
170+
QThread::exec();
171+
}
172+
173+
QSharedMemory* QgsWFSUtils::createAndAttachSHM()
174+
{
175+
QSharedMemory* sharedMemory = nullptr;
176+
// For debug purpose. To test in the case where shared memory mechanism doesn't work
177+
if ( getenv( "QGIS_USE_SHARED_MEMORY_KEEP_ALIVE" ) == nullptr )
178+
{
179+
sharedMemory = new QSharedMemory( QString( "qgis_wfs_pid_%1" ).arg( QCoreApplication::applicationPid() ) );
180+
if ( sharedMemory->create( sizeof( qint64 ) ) && sharedMemory->lock() && sharedMemory->unlock() )
181+
{
182+
return sharedMemory;
183+
}
184+
else
185+
{
186+
// Would happen on Unix in the quite unlikely situation where a past process
187+
// with the same PID as ours would have been killed before it destroyed
188+
// its shared memory segment. So we will recycle it.
189+
if ( sharedMemory->error() == QSharedMemory::AlreadyExists &&
190+
sharedMemory->attach() && sharedMemory->size() == ( int )sizeof( qint64 ) )
191+
{
192+
return sharedMemory;
193+
}
194+
}
195+
}
196+
delete sharedMemory;
197+
return nullptr;
198+
}
199+
200+
void QgsWFSUtils::init()
201+
{
202+
QSharedMemory* sharedMemory = createAndAttachSHM();
203+
gmKeepAliveWorks = sharedMemory != nullptr;
204+
delete sharedMemory;
205+
206+
if ( gmKeepAliveWorks )
207+
{
208+
QgsDebugMsg( QString( "Keep-alive mechanism works" ) );
209+
}
210+
else
211+
{
212+
QgsDebugMsg( QString( "Keep-alive mechanism does not work" ) );
213+
}
214+
215+
// Remove temporary directories of qgis instances that haven't demonstrated
216+
// a sign of life since 2 * KEEP_ALIVE_DELAY
217+
QDir dir( getBaseCacheDirectory( false ) );
218+
if ( dir.exists() )
219+
{
220+
const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
221+
QFileInfoList fileList( dir.entryInfoList( QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files ) );
222+
Q_FOREACH ( QFileInfo info, fileList )
223+
{
224+
if ( info.isDir() && info.fileName().startsWith( "pid_" ) )
225+
{
226+
QString pidStr( info.fileName().mid( 4 ) );
227+
qint64 pid = pidStr.toLongLong();
228+
bool canDelete = false;
229+
if ( pid == QCoreApplication::applicationPid() )
230+
{
231+
canDelete = true;
232+
}
233+
else if ( gmKeepAliveWorks )
234+
{
235+
canDelete = true;
236+
QSharedMemory otherSharedMemory( QString( "qgis_wfs_pid_%1" ).arg( pid ) );
237+
if ( otherSharedMemory.attach() )
238+
{
239+
if ( otherSharedMemory.size() == sizeof( qint64 ) )
240+
{
241+
if ( otherSharedMemory.lock() )
242+
{
243+
qint64 otherTimestamp;
244+
memcpy( &otherTimestamp, otherSharedMemory.data(), sizeof( qint64 ) );
245+
otherSharedMemory.unlock();
246+
if ( currentTimestamp > otherTimestamp && otherTimestamp > 0 &&
247+
currentTimestamp - otherTimestamp < 2 * KEEP_ALIVE_DELAY )
248+
{
249+
QgsDebugMsg( QString( "Cache dir %1 kept since process seems to be still alive" ).arg( info.absoluteFilePath() ) );
250+
canDelete = false;
251+
}
252+
else
253+
{
254+
QgsDebugMsg( QString( "Cache dir %1 to be destroyed since process seems to be no longer alive" ).arg( info.absoluteFilePath() ) );
255+
}
256+
257+
otherSharedMemory.unlock();
258+
}
259+
}
260+
otherSharedMemory.detach();
261+
}
262+
else
263+
{
264+
QgsDebugMsg( QString( "Cannot attach to shared memory segment of process %1. It must be ghost" ).arg( pid ) );
265+
}
266+
}
267+
else
268+
{
269+
// Fallback to a file timestamp based method, if for some reason,
270+
// the shared memory stuff doesn't seem to work
271+
const qint64 fileTimestamp = info.lastModified().toMSecsSinceEpoch();
272+
if ( currentTimestamp > fileTimestamp &&
273+
currentTimestamp - fileTimestamp < 24 * 3600 * 1000 )
274+
{
275+
QgsDebugMsg( QString( "Cache dir %1 kept since last modified in the past 24 hours" ).arg( info.absoluteFilePath() ) );
276+
canDelete = false;
277+
}
278+
else
279+
{
280+
QgsDebugMsg( QString( "Cache dir %1 to be destroyed since not modified in the past 24 hours" ).arg( info.absoluteFilePath() ) );
281+
canDelete = true;
282+
}
283+
}
284+
if ( canDelete )
285+
{
286+
QgsDebugMsg( QString( "Removing cache dir %1" ).arg( info.absoluteFilePath() ) );
287+
removeDir( info.absoluteFilePath() );
288+
}
289+
}
290+
}
291+
}
292+
}

0 commit comments

Comments
 (0)
Please sign in to comment.