QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsfilebaseddataitemprovider.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsfilebaseddataitemprovider.cpp
3  --------------------------------------
4  Date : July 2021
5  Copyright : (C) 2021 by Nyall Dawson
6  Email : nyall dot dawson at gmail dot 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 
17 #include "qgsdataprovider.h"
18 #include "qgsproviderregistry.h"
19 #include "qgslogger.h"
20 #include "qgssettings.h"
21 #include "qgszipitem.h"
22 #include "qgsogrproviderutils.h"
23 #include "qgsstyle.h"
24 #include "qgsgdalutils.h"
25 #include "qgsgeopackagedataitems.h"
27 #include "qgsfieldsitem.h"
28 #include "qgsproviderutils.h"
29 #include "qgsmbtiles.h"
30 #include "qgsvectortiledataitems.h"
31 #include "qgsprovidermetadata.h"
32 #include <QUrlQuery>
33 
34 //
35 // QgsProviderSublayerItem
36 //
37 
39  const QgsProviderSublayerDetails &details, const QString &filePath )
40  : QgsLayerItem( parent, name, filePath.isEmpty() ? details.uri() : filePath, details.uri(), layerTypeFromSublayer( details ), details.providerKey() )
41  , mDetails( details )
42 {
43  mToolTip = details.uri();
44 
45  // no children, except for sqlite, which gets special handling because of the unusual situation with the spatialite provider
47 }
48 
50 {
51  QVector<QgsDataItem *> children;
52 
53  if ( mDetails.type() == QgsMapLayerType::VectorLayer )
54  {
55  // sqlite gets special handling because of the spatialite provider which supports the api required for a fields item.
56  // TODO -- allow read only fields items to be created directly from vector layers, so that all vector layers can show field items.
57  if ( mDetails.driverName() == QLatin1String( "SQLite" ) )
58  {
59  children.push_back( new QgsFieldsItem( this,
60  path() + QStringLiteral( "/columns/ " ),
61  QStringLiteral( R"(dbname="%1")" ).arg( parent()->path().replace( '"', QLatin1String( R"(\")" ) ) ),
62  QStringLiteral( "spatialite" ), QString(), name() ) );
63  }
64  }
65  return children;
66 }
67 
68 Qgis::BrowserLayerType QgsProviderSublayerItem::layerTypeFromSublayer( const QgsProviderSublayerDetails &sublayer )
69 {
70  switch ( sublayer.type() )
71  {
73  {
74  switch ( QgsWkbTypes::geometryType( sublayer.wkbType() ) )
75  {
78 
81 
84 
87 
90  }
91 
92  break;
93  }
96 
99 
102 
105 
108 
110  break;
111  }
113 }
114 
116 {
117  return mDetails.name();
118 }
119 
120 //
121 // QgsFileDataCollectionItem
122 //
123 
124 QgsFileDataCollectionItem::QgsFileDataCollectionItem( QgsDataItem *parent, const QString &name, const QString &path, const QList<QgsProviderSublayerDetails> &sublayers )
125  : QgsDataCollectionItem( parent, name, path )
126  , mSublayers( sublayers )
127 {
130  else
132 
133  if ( !qgsVsiPrefix( path ).isEmpty() )
134  {
135  mIconName = QStringLiteral( "/mIconZip.svg" );
136  }
137 }
138 
140 {
141  QList< QgsProviderSublayerDetails> sublayers;
143  || mSublayers.empty() )
144  {
146  }
147  else
148  {
149  sublayers = mSublayers;
150  }
151  // only ever use the initial sublayers for first population -- after that we requery when asked to create children,
152  // or the item won't "refresh" and update its sublayers when the actual file changes
153  mSublayers.clear();
154  // remove the fast flag -- after the first population we need to requery the dataset
156 
157  QVector<QgsDataItem *> children;
158  children.reserve( sublayers.size() );
159  for ( const QgsProviderSublayerDetails &sublayer : std::as_const( sublayers ) )
160  {
161  QgsProviderSublayerItem *item = new QgsProviderSublayerItem( this, sublayer.name(), sublayer, QString() );
162  children.append( item );
163  }
164 
165  return children;
166 }
167 
169 {
170  return true;
171 }
172 
174 {
175  QgsMimeDataUtils::Uri collectionUri;
176  collectionUri.uri = path();
177  collectionUri.layerType = QStringLiteral( "collection" );
178  collectionUri.filePath = path();
179  return { collectionUri };
180 }
181 
183 {
184  // sqlite gets special handling because of the spatialite provider which supports the api required database connections
185  const QFileInfo fi( mPath );
186  if ( fi.suffix().toLower() != QLatin1String( "sqlite" ) && fi.suffix().toLower() != QLatin1String( "db" ) )
187  {
188  return nullptr;
189  }
190 
192 
193  // test that file is valid with OGR
194  if ( OGRGetDriverCount() == 0 )
195  {
196  OGRRegisterAll();
197  }
198  // do not print errors, but write to debug
199  CPLPushErrorHandler( CPLQuietErrorHandler );
200  CPLErrorReset();
201  gdal::dataset_unique_ptr hDS( GDALOpenEx( path().toUtf8().constData(), GDAL_OF_VECTOR | GDAL_OF_READONLY, nullptr, nullptr, nullptr ) );
202  CPLPopErrorHandler();
203 
204  if ( ! hDS )
205  {
206  QgsDebugMsgLevel( QStringLiteral( "GDALOpen error # %1 : %2 on %3" ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ).arg( path() ), 2 );
207  return nullptr;
208  }
209 
210  GDALDriverH hDriver = GDALGetDatasetDriver( hDS.get() );
211  QString driverName = GDALGetDriverShortName( hDriver );
212 
213  if ( driverName == QLatin1String( "SQLite" ) )
214  {
215  QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "spatialite" ) ) };
216  if ( md )
217  {
218  QgsDataSourceUri uri;
219  uri.setDatabase( path( ) );
220  conn = static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( uri.uri(), {} ) );
221  }
222  }
223  return conn;
224 }
225 
226 //
227 // QgsFileBasedDataItemProvider
228 //
229 
231 {
232  return QStringLiteral( "files" );
233 }
234 
236 {
238 }
239 
241 {
242  if ( path.isEmpty() )
243  return nullptr;
244 
245  const QFileInfo info( path );
246  QString suffix = info.suffix().toLower();
247  const QString name = info.fileName();
248 
249  // special handling for some suffixes
250  if ( suffix.compare( QLatin1String( "gpkg" ), Qt::CaseInsensitive ) == 0 )
251  {
252  // Geopackage is special -- it gets a dedicated collection item type
253  QgsGeoPackageCollectionItem *item = new QgsGeoPackageCollectionItem( parentItem, name, path );
254  item->setCapabilities( item->capabilities2() | Qgis::BrowserItemCapability::ItemRepresentsFile );
255  return item;
256  }
257  else if ( suffix == QLatin1String( "txt" ) )
258  {
259  // never ever show .txt files as datasets in browser -- they are only used for geospatial data in extremely rare cases
260  // and are predominantly just noise in the browser
261  return nullptr;
262  }
263  // If a .tab exists, then the corresponding .map/.dat is very likely a
264  // side-car file of the .tab
265  else if ( suffix == QLatin1String( "map" ) || suffix == QLatin1String( "dat" ) )
266  {
267  if ( QFile::exists( QDir( info.path() ).filePath( info.baseName() + ".tab" ) ) || QFile::exists( QDir( info.path() ).filePath( info.baseName() + ".TAB" ) ) )
268  return nullptr;
269  }
270  // .dbf and .shx should only appear if .shp is not present
271  else if ( suffix == QLatin1String( "dbf" ) || suffix == QLatin1String( "shx" ) )
272  {
273  if ( QFile::exists( QDir( info.path() ).filePath( info.baseName() + ".shp" ) ) || QFile::exists( QDir( info.path() ).filePath( info.baseName() + ".SHP" ) ) )
274  return nullptr;
275  }
276  // skip QGIS style xml files
277  else if ( suffix == QLatin1String( "xml" ) && QgsStyle::isXmlStyleFile( path ) )
278  {
279  return nullptr;
280  }
281  // GDAL 3.1 Shapefile driver directly handles .shp.zip files
282  else if ( path.endsWith( QLatin1String( ".shp.zip" ), Qt::CaseInsensitive ) &&
283  GDALIdentifyDriverEx( path.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr ) )
284  {
285  suffix = QStringLiteral( "shp.zip" );
286  }
287  // special handling for mbtiles files
288  else if ( suffix == QLatin1String( "mbtiles" ) )
289  {
290  QgsMbTiles reader( path );
291  if ( reader.open() )
292  {
293  if ( reader.metadataValue( QStringLiteral( "format" ) ) == QLatin1String( "pbf" ) )
294  {
295  // these are vector tiles
296  QUrlQuery uq;
297  uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
298  uq.addQueryItem( QStringLiteral( "url" ), path );
299  const QString encodedUri = uq.toString();
300  QgsVectorTileLayerItem *item = new QgsVectorTileLayerItem( parentItem, name, path, encodedUri );
301  item->setCapabilities( item->capabilities2() | Qgis::BrowserItemCapability::ItemRepresentsFile );
302  return item;
303  }
304  else
305  {
306  // handled by WMS provider
307  QUrlQuery uq;
308  uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
309  uq.addQueryItem( QStringLiteral( "url" ), QUrl::fromLocalFile( path ).toString() );
310  const QString encodedUri = uq.toString();
311  QgsLayerItem *item = new QgsLayerItem( parentItem, name, path, encodedUri, Qgis::BrowserLayerType::Raster, QStringLiteral( "wms" ) );
314  return item;
315  }
316  }
317  }
318 
319  // hide blocklisted URIs, such as .aux.xml files
320  if ( QgsProviderRegistry::instance()->uriIsBlocklisted( path ) )
321  return nullptr;
322 
323  QgsSettings settings;
324 
325  Qgis::SublayerQueryFlags queryFlags = Qgis::SublayerQueryFlags();
326 
327  // should we fast scan only?
328  if ( ( settings.value( QStringLiteral( "qgis/scanItemsInBrowser2" ),
329  "extension" ).toString() == QLatin1String( "extension" ) ) ||
330  ( parentItem && settings.value( QStringLiteral( "qgis/scanItemsFastScanUris" ),
331  QStringList() ).toStringList().contains( parentItem->path() ) ) )
332  {
333  queryFlags |= Qgis::SublayerQueryFlag::FastScan;
334  }
335 
336  const QList<QgsProviderSublayerDetails> sublayers = QgsProviderRegistry::instance()->querySublayers( path, queryFlags );
337 
338  if ( sublayers.size() == 1
341  )
342  {
343  QgsProviderSublayerItem *item = new QgsProviderSublayerItem( parentItem, name, sublayers.at( 0 ), path );
345  return item;
346  }
347  else if ( !sublayers.empty() )
348  {
349  QgsFileDataCollectionItem *item = new QgsFileDataCollectionItem( parentItem, name, path, sublayers );
351  return item;
352  }
353  else
354  {
355  return nullptr;
356  }
357 }
358 
360 {
361  QFileInfo info( path );
362  QString suffix = info.suffix().toLower();
363 
364  QStringList dirExtensions = QgsOgrProviderUtils::directoryExtensions();
365  return dirExtensions.contains( suffix );
366 }
@ NotPopulated
Children not yet created.
@ Populated
Children created.
@ Fertile
Can create children. Even items without this capability may have children, but cannot create them,...
@ ItemRepresentsFile
Item's path() directly represents a file on disk (since QGIS 3.22)
@ Fast
CreateChildren() is fast enough to be run in main thread when refreshing items, most root items (wms,...
@ FastScan
Indicates that the provider must scan for sublayers using the fastest possible approach – e....
@ ResolveGeometryType
Attempt to resolve the geometry type for vector sublayers.
BrowserLayerType
Browser item layer types.
Definition: qgis.h:300
@ Point
Vector point layer.
@ Plugin
Plugin based layer.
@ Line
Vector line layer.
@ Polygon
Vector polygon layer.
@ Vector
Generic vector layer.
@ VectorTile
Vector tile layer.
@ Raster
Raster layer.
@ TableLayer
Vector non-spatial layer.
@ PointCloud
Point cloud layer.
The QgsAbstractDatabaseProviderConnection class provides common functionality for DB based connection...
A Collection: logical collection of layers or subcollections, e.g.
Base class for all items in the model.
Definition: qgsdataitem.h:46
QString mToolTip
Definition: qgsdataitem.h:456
QString mPath
Definition: qgsdataitem.h:455
QVector< QgsDataItem * > children() const
Definition: qgsdataitem.h:337
QgsDataItem * parent() const
Gets item parent.
Definition: qgsdataitem.h:330
QString mIconName
Definition: qgsdataitem.h:457
QString name() const
Returns the name of the item (the displayed text for the item).
Definition: qgsdataitem.h:345
QString path() const
Definition: qgsdataitem.h:354
virtual void setState(Qgis::BrowserItemState state)
Set item state.
virtual void setCapabilities(Qgis::BrowserItemCapabilities capabilities)
Sets the capabilities for the data item.
Definition: qgsdataitem.h:310
virtual Qgis::BrowserItemCapabilities capabilities2() const
Returns the capabilities for the data item.
Definition: qgsdataitem.h:303
Class for storing the component parts of a RDBMS data source URI (e.g.
QString uri(bool expandAuthConfig=true) const
Returns the complete URI as a string.
void setDatabase(const QString &database)
Sets the URI database name.
A collection of field items with some internal logic to retrieve the fields and a the vector layer in...
Definition: qgsfieldsitem.h:34
QString name() override
Human-readable name of the provider name.
int capabilities() const override
Returns combination of flags from QgsDataProvider::DataCapabilities.
bool handlesDirectoryPath(const QString &path) override
Returns true if the provider will handle the directory at the specified path.
QgsDataItem * createDataItem(const QString &path, QgsDataItem *parentItem) override
Create a new instance of QgsDataItem (or nullptr) for given path and parent item.
A data collection item for file based data collections (e.g.
bool hasDragEnabled() const override
Returns true if the item may be dragged.
QVector< QgsDataItem * > createChildren() override
Create children.
QgsMimeDataUtils::UriList mimeUris() const override
Returns mime URIs for the data item, most data providers will only return a single URI but some data ...
QgsAbstractDatabaseProviderConnection * databaseConnection() const override
For data items that represent a DB connection or one of its children, this method returns a connectio...
QgsFileDataCollectionItem(QgsDataItem *parent, const QString &name, const QString &path, const QList< QgsProviderSublayerDetails > &sublayers)
Constructor for QgsFileDataCollectionItem.
Item that represents a layer that can be opened with one of the providers.
Definition: qgslayeritem.h:30
Utility class for reading and writing MBTiles files (which are SQLite3 databases).
Definition: qgsmbtiles.h:39
bool open()
Tries to open the file, returns true on success.
Definition: qgsmbtiles.cpp:32
QString metadataValue(const QString &key)
Requests metadata value for the given key.
Definition: qgsmbtiles.cpp:83
QList< QgsMimeDataUtils::Uri > UriList
Holds data provider key, description, and associated shared library file or function pointer informat...
QList< QgsProviderSublayerDetails > querySublayers(const QString &uri, Qgis::SublayerQueryFlags flags=Qgis::SublayerQueryFlags(), QgsFeedback *feedback=nullptr) const
Queries the specified uri and returns a list of any valid sublayers found in the dataset which can be...
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
Contains details about a sub layer available from a dataset.
QgsWkbTypes::Type wkbType() const
Returns the layer's WKB type, or QgsWkbTypes::Unknown if the WKB type is not application or unknown.
QString uri() const
Returns the layer's URI.
QgsMapLayerType type() const
Returns the layer type.
QString driverName() const
Returns the layer's driver name.
QString name() const
Returns the layer's name.
A generic data item for file based layers.
QVector< QgsDataItem * > createChildren() override
Create children.
QgsProviderSublayerItem(QgsDataItem *parent, const QString &name, const QgsProviderSublayerDetails &details, const QString &filePath)
Constructor for QgsProviderSublayerItem.
static bool sublayerDetailsAreIncomplete(const QList< QgsProviderSublayerDetails > &details, QgsProviderUtils::SublayerCompletenessFlags flags=QgsProviderUtils::SublayerCompletenessFlags())
Returns true if the sublayer details are incomplete, and require a more in-depth scan.
@ IgnoreUnknownGeometryType
Indicates that an unknown geometry type should not be considered as incomplete.
@ IgnoreUnknownFeatureCount
Indicates that an unknown feature count should not be considered as incomplete.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
static bool isXmlStyleFile(const QString &path)
Tests if the file at path is a QGIS style XML file.
Definition: qgsstyle.cpp:3038
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:968
@ PointCloudLayer
Added in 3.18.
@ MeshLayer
Added in 3.2.
@ VectorTileLayer
Added in 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:138
QString qgsVsiPrefix(const QString &path)
Definition: qgis.cpp:200
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QString filePath
Path to file, if uri is associated with a file.
QString uri
Identifier of the data source recognized by its providerKey.
QString layerType
Type of URI.