1 /***************************************************************************
2     qgsbacckgroundcachedshareddata.h
3     ---------------------
4     begin                : October 2019
5     copyright            : (C) 2016-2019 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 QGSBACKGROUNDCACHEDSHAREDDATA_H
17 #define QGSBACKGROUNDCACHEDSHAREDDATA_H
18 
19 #include "qgsbackgroundcachedfeatureiterator.h"
20 #include "qgsrectangle.h"
21 #include "qgsspatialiteutils.h"
22 #include "qgscachedirectorymanager.h"
23 
24 #include <QSet>
25 
26 #include <map>
27 
28 class QgsBackgroundCachedFeatureIterator;
29 class QgsFeatureDownloader;
30 class QgsFeatureDownloaderImpl;
31 class QgsThreadedFeatureDownloader;
32 
33 /**
34  * This class holds data, and logic, shared between the provider, QgsBackgroundCachedFeatureIterator
35  *  and QgsFeatureDownloader. It manages the on-disk cache, as a SpatiaLite
36  *  database.
37  *
38  *  The structure of the table in the database is the following one :
39  *
40  * - attribute fields of the DescribeFeatureType response
41  * - __qgis_gen_counter: generation counter
42  * - __qgis_unique_id: typically feature 'fid' or 'gml:id'
43  * - __qgis_hexwkb_geom: feature geometry as a hexadecimal encoded WKB string.
44  * - geometry: polygon with the bounding box of the geometry.
45  *
46  *  The generation counter is a synchronization mechanism between the iterator
47  *  that will try to return cached features first and then downloaded features.
48  *  It avoids the iterator to return features in duplicates, by returning features
49  *  that have just been serialized by the live downloader and notified to the
50  *  iterator.
51  *
52  *  The reason for not storing directly the geometry is that we may potentially
53  *  store in the future non-linear geometries that aren't handled by SpatiaLite.
54  *
55  *  It contains also methods used in WFS-T context to update the cache content,
56  *  from the changes initiated by the user.
57  */
58 class QgsBackgroundCachedSharedData
59 {
60   public:
61     QgsBackgroundCachedSharedData( const QString &providerName, const QString &componentTranslated );
62     virtual ~QgsBackgroundCachedSharedData();
63 
64     //////////// Methods to be used by provider
65 
66     //! Returns extent computed from currently downloaded features
67     const QgsRectangle &computedExtent() const;
68 
69     //! Returns whether the feature download is finished
downloadFinished()70     bool downloadFinished() const { return mDownloadFinished; }
71 
72     //! Returns layer feature count. Might issue a network request if issueRequestIfNeeded == true
73     long long getFeatureCount( bool issueRequestIfNeeded = true );
74 
75     //! Return a "consolidated" extent mixing the one from the capabilities from the one of the features already downloaded.
76     QgsRectangle consolidatedExtent() const;
77 
78     /**
79      * Used by provider's reloadData(). The effect is to invalid
80      * all the caching state, so that a new request results in fresh download.
81     */
82     void invalidateCache();
83 
84     //! Give a feature id, find the correspond fid/gml.id. Used for edition.
85     QString findUniqueId( QgsFeatureId fid ) const;
86 
87     //! Delete from the on-disk cache the features of given fid. Used byedition.
88     bool deleteFeatures( const QgsFeatureIds &fidlist );
89 
90     //! Change into the on-disk cache the passed geometries. Used by edition.
91     bool changeGeometryValues( const QgsGeometryMap &geometry_map );
92 
93     //! Change into the on-disk cache the passed attributes. Used by edition.
94     bool changeAttributeValues( const QgsChangedAttributesMap &attr_map );
95 
96     //////////// Methods to be used by feature iterator & downloader
97 
98     //// Getters
99 
100     //! Retrieve the dbId from the qgisId
101     QgsFeatureIds dbIdsFromQgisIds( const QgsFeatureIds &qgisIds ) const;
102 
103     //! Returns maximum number of features to download, or 0 if unlimited
requestLimit()104     int requestLimit() const { return mRequestLimit; }
105 
106     //! Returns whether the feature count is exact, or approximate/transient
isFeatureCountExact()107     bool isFeatureCountExact() const { return mFeatureCountExact; }
108 
109     //! Return source/provider CRS
sourceCrs()110     const QgsCoordinateReferenceSystem &sourceCrs() const { return mSourceCrs; }
111 
112     //! Return the provider for the Spatialite cache.
cacheDataProvider()113     QgsVectorDataProvider *cacheDataProvider() { return mCacheDataProvider.get(); }
114 
115     //! Return provider fields.
fields()116     const QgsFields &fields() const { return mFields; }
117 
118     //! Return the spatialite field name from the user visible column name
119     QString getSpatialiteFieldNameFromUserVisibleName( const QString &columnName ) const;
120 
121     //! Return the user visible FeatureID from the spatialite FeatureID
122     bool getUserVisibleIdFromSpatialiteId( QgsFeatureId dbId, QgsFeatureId &outId ) const;
123 
124     //! Return current BBOX used by the downloader
currentRect()125     const QgsRectangle &currentRect() const { return mRect; }
126 
127     //! Returns a unique identifier made from feature content
128     static QString getMD5( const QgsFeature &f );
129 
130     //! Filter expression to apply on client side
clientSideFilterExpression()131     const QString &clientSideFilterExpression() const { return mClientSideFilterExpression; }
132 
133     //// Actions
134 
135     /**
136      * Used by a QgsBackgroundCachedFeatureIterator to start a downloader and get the
137      * generation counter.
138     */
139     int registerToCache( QgsBackgroundCachedFeatureIterator *iterator, int limit, const QgsRectangle &rect = QgsRectangle() );
140 
141     /**
142      * Used by the rewind() method of an iterator so as to get the up-to-date
143      * generation counter.
144     */
145     int getUpdatedCounter();
146 
147     /**
148      * Used by the background downloader to serialize downloaded features into
149      * the cache. Also used by a insert operation.
150     */
151     void serializeFeatures( QVector<QgsFeatureUniqueIdPair> &featureList );
152 
153     //! Called by QgsFeatureDownloader::run() at the end of the download process.
154     void endOfDownload( bool success, long long featureCount, bool truncatedResponse, bool interrupted, const QString &errorMsg );
155 
156     //! Force an update of the feature count
157     void setFeatureCount( long long featureCount, bool featureCountExact );
158 
159     //! Returns the name of temporary directory. To be paired with releaseCacheDirectory()
160     QString acquireCacheDirectory();
161 
162     //! To be called when a temporary file is removed from the directory
163     void releaseCacheDirectory();
164 
165     //! Set whether the progress dialog should be hidden
setHideProgressDialog(bool b)166     void setHideProgressDialog( bool b ) { mHideProgressDialog = b; }
167 
168     //////// Pure virtual methods
169 
170     //! Instantiate a new feature downloader implementation.
171     virtual std::unique_ptr<QgsFeatureDownloaderImpl> newFeatureDownloaderImpl( QgsFeatureDownloader *, bool requestMadeFromMainThread ) = 0;
172 
173     //! Return whether the GetFeature request should include the request bounding box.
174     virtual bool isRestrictedToRequestBBOX() const = 0;
175 
176     //! Return whether the layer has a geometry field
177     virtual bool hasGeometry() const = 0;
178 
179     //! Return layer name
180     virtual QString layerName() const = 0;
181 
182     //! Called when an error must be raised to the provider
183     virtual void pushError( const QString &errorMsg ) const = 0;
184 
185   protected:
186 
187     //////////// Input members. Implementations should define them to meaningful values
188 
189     //! Attribute fields of the layer
190     QgsFields mFields;
191 
192     //! Source CRS
193     QgsCoordinateReferenceSystem mSourceCrs;
194 
195     //! SELECT DISTINCT
196     bool mDistinctSelect = false;
197 
198     //! Filter expression to apply on client side
199     QString mClientSideFilterExpression;
200 
201     //! Server-side or user-side limit of downloaded features (including with paging). Valid if > 0
202     long long mMaxFeatures = 0;
203 
204     //! Server-side limit of downloaded features (including with paging). Valid if > 0
205     long long mServerMaxFeatures = 0;
206 
207     //! Bounding box for the layer as returned by GetCapabilities
208     QgsRectangle mCapabilityExtent;
209 
210     //! Extent computed from downloaded features
211     QgsRectangle mComputedExtent;
212 
213     //! Flag is a /items request returns a numberMatched property
214     bool mHasNumberMatched = false;
215 
216     //! Whether progress dialog should be hidden
217     bool mHideProgressDialog = false;
218 
219     //////////// Methods
220 
221     //! Should be called in the destructor of the implementation of this class !
222     void cleanup();
223 
224     //! Returns true if it is likely that the server doesn't properly honor axis order.
detectPotentialServerAxisOrderIssueFromSingleFeatureExtent()225     virtual bool detectPotentialServerAxisOrderIssueFromSingleFeatureExtent() const { return false; }
226 
227   private:
228 
229     //! Cache directory manager
230     QgsCacheDirectoryManager &mCacheDirectoryManager;
231 
232     //! Main mutex to protect most data members that can be modified concurrently
233     mutable QMutex mMutex;
234 
235     //! Mutex used specifically by registerToCache()
236     QMutex mMutexRegisterToCache;
237 
238     //! Mutex used only by serializeFeatures()
239     QMutex mCacheWriteMutex;
240 
241     //! For error messages, name of the translated component. For example tr("WFS")
242     QString mComponentTranslated;
243 
244     //! Whether the downloader has finished (or been canceled)
245     bool mDownloadFinished = false;
246 
247     /**
248      * The generation counter. When a iterator is built or rewind, it gets the
249      * current value of the generation counter to query the features in the cache
250      * whose generation counter is <= the current value. That way the iterator
251      * can consume first cached features, and then deal with the features that are
252      * notified in live by the downloader.
253     */
254     int mGenCounter = 0;
255 
256     //! Spatial index of requested cached regions
257     QgsSpatialIndex mCachedRegions;
258 
259     //! Requested cached regions
260     QVector< QgsFeature > mRegions;
261 
262     //! Limit of retrieved number of features for the current request
263     int mRequestLimit = 0;
264 
265     //! Current BBOX used by the downloader
266     QgsRectangle mRect;
267 
268     //! The background feature downloader
269     std::unique_ptr<QgsThreadedFeatureDownloader> mDownloader;
270 
271     //! Filename of the on-disk cache
272     QString mCacheDbname;
273 
274     //! Tablename of the on-disk cache
275     QString mCacheTablename;
276 
277     //! The data provider of the on-disk cache
278     std::unique_ptr<QgsVectorDataProvider> mCacheDataProvider;
279 
280     //! Name of the gmlid, spatialite_id, qgis_id cache. This cache persists even after a layer reload so as to ensure feature id stability.
281     QString mCacheIdDbname;
282 
283     //! Connection to mCacheIdDbname
284     sqlite3_database_unique_ptr mCacheIdDb;
285 
286     //! Map each user visible field name to the column name in the spatialite DB cache
287     // This is useful when there are user visible fields with same name, but different case
288     std::map<QString, QString> mMapUserVisibleFieldNameToSpatialiteColumnName;
289 
290     //! Next value for qgisId column
291     QgsFeatureId mNextCachedIdQgisId = 1;
292 
293     //! Number of features that have been cached, or attempted to be cached
294     long long mTotalFeaturesAttemptedToBeCached = 0;
295 
296     //! Whether we have already tried fetching one feature after realizing that the capabilities extent is wrong
297     bool mTryFetchingOneFeature = false;
298 
299     //! Number of features of the layer
300     long long mFeatureCount = 0;
301 
302     //! Whether mFeatureCount value is exact or approximate / in construction
303     bool mFeatureCountExact = false;
304 
305     //! Whether a request has been issued to retrieve the number of features
306     bool mFeatureCountRequestIssued = false;
307 
308     ///////////////// METHODS ////////////////////////
309 
310     //! Create the on-disk cache and connect to it
311     bool createCache();
312 
313     /**
314      * Returns the set of unique ids that have already been downloaded and
315      * cached, so as to avoid to cache duplicates.
316     */
317     QSet<QString> getExistingCachedUniqueIds( const QVector<QgsFeatureUniqueIdPair> &featureList );
318 
319     /**
320      * Returns the set of md5 of features that have already been downloaded and
321      * cached, so as to avoid to cache duplicates.
322     */
323     QSet<QString> getExistingCachedMD5( const QVector<QgsFeatureUniqueIdPair> &featureList );
324 
325     ///////////////// PURE VIRTUAL METHODS ////////////////////////
326 
327     //! Called when the extent is updated
328     virtual void emitExtentUpdated() = 0;
329 
330     //! called by invalidateCache()
331     virtual void invalidateCacheBaseUnderLock() = 0;
332 
333     //! Return whether the server limit downloading a limiter number of features
334     virtual bool supportsLimitedFeatureCountDownloads() const = 0;
335 
336     //! Return whether a server-side (non-spatial) filter is applied
337     virtual bool hasServerSideFilter() const = 0;
338 
339     //! Return whether the server supports a (relatively) fast way of reporting the feature count
340     virtual bool supportsFastFeatureCount() const = 0;
341 
342     //! Launch a synchronous request for a single feature and return its extent.
343     virtual QgsRectangle getExtentFromSingleFeatureRequest() const = 0;
344 
345     //! Launch a synchronous request to count the number of features (return -1 in case of error)
346     virtual long long getFeatureCountFromServer() const = 0;
347 };
348 
349 #endif
350