QGIS API Documentation  3.23.0-Master (dd0cd13a00)
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 
111  break;
112  }
114 }
115 
117 {
118  return mDetails.name();
119 }
120 
121 //
122 // QgsFileDataCollectionItem
123 //
124 
125 QgsFileDataCollectionItem::QgsFileDataCollectionItem( QgsDataItem *parent, const QString &name, const QString &path, const QList<QgsProviderSublayerDetails> &sublayers )
126  : QgsDataCollectionItem( parent, name, path )
127  , mSublayers( sublayers )
128 {
131  else
133 
134  if ( !qgsVsiPrefix( path ).isEmpty() )
135  {
136  mIconName = QStringLiteral( "/mIconZip.svg" );
137  }
138 }
139 
141 {
142  QList< QgsProviderSublayerDetails> sublayers;
144  || mSublayers.empty() )
145  {
147  }
148  else
149  {
150  sublayers = mSublayers;
151  }
152  // only ever use the initial sublayers for first population -- after that we requery when asked to create children,
153  // or the item won't "refresh" and update its sublayers when the actual file changes
154  mSublayers.clear();
155  // remove the fast flag -- after the first population we need to requery the dataset
157 
158  QVector<QgsDataItem *> children;
159  children.reserve( sublayers.size() );
160  for ( const QgsProviderSublayerDetails &sublayer : std::as_const( sublayers ) )
161  {
162  QgsProviderSublayerItem *item = new QgsProviderSublayerItem( this, sublayer.name(), sublayer, QString() );
163  children.append( item );
164  }
165 
166  return children;
167 }
168 
170 {
171  return true;
172 }
173 
175 {
176  QgsMimeDataUtils::Uri collectionUri;
177  collectionUri.uri = path();
178  collectionUri.layerType = QStringLiteral( "collection" );
179  collectionUri.filePath = path();
180  return { collectionUri };
181 }
182 
184 {
185  // sqlite gets special handling because of the spatialite provider which supports the api required database connections
186  const QFileInfo fi( mPath );
187  if ( fi.suffix().toLower() != QLatin1String( "sqlite" ) && fi.suffix().toLower() != QLatin1String( "db" ) )
188  {
189  return nullptr;
190  }
191 
193 
194  // test that file is valid with OGR
195  if ( OGRGetDriverCount() == 0 )
196  {
197  OGRRegisterAll();
198  }
199  // do not print errors, but write to debug
200  CPLPushErrorHandler( CPLQuietErrorHandler );
201  CPLErrorReset();
202  gdal::dataset_unique_ptr hDS( GDALOpenEx( path().toUtf8().constData(), GDAL_OF_VECTOR | GDAL_OF_READONLY, nullptr, nullptr, nullptr ) );
203  CPLPopErrorHandler();
204 
205  if ( ! hDS )
206  {
207  QgsDebugMsgLevel( QStringLiteral( "GDALOpen error # %1 : %2 on %3" ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ).arg( path() ), 2 );
208  return nullptr;
209  }
210 
211  GDALDriverH hDriver = GDALGetDatasetDriver( hDS.get() );
212  QString driverName = GDALGetDriverShortName( hDriver );
213 
214  if ( driverName == QLatin1String( "SQLite" ) )
215  {
216  QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "spatialite" ) ) };
217  if ( md )
218  {
219  QgsDataSourceUri uri;
220  uri.setDatabase( path( ) );
221  conn = static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( uri.uri(), {} ) );
222  }
223  }
224  return conn;
225 }
226 
227 //
228 // QgsFileBasedDataItemProvider
229 //
230 
232 {
233  return QStringLiteral( "files" );
234 }
235 
237 {
239 }
240 
242 {
243  if ( path.isEmpty() )
244  return nullptr;
245 
246  const QFileInfo info( path );
247  QString suffix = info.suffix().toLower();
248  const QString name = info.fileName();
249 
250  // special handling for some suffixes
251  if ( suffix.compare( QLatin1String( "gpkg" ), Qt::CaseInsensitive ) == 0 )
252  {
253  // Geopackage is special -- it gets a dedicated collection item type
254  QgsGeoPackageCollectionItem *item = new QgsGeoPackageCollectionItem( parentItem, name, path );
255  item->setCapabilities( item->capabilities2() | Qgis::BrowserItemCapability::ItemRepresentsFile );
256  return item;
257  }
258  else if ( suffix == QLatin1String( "txt" ) )
259  {
260  // never ever show .txt files as datasets in browser -- they are only used for geospatial data in extremely rare cases
261  // and are predominantly just noise in the browser
262  return nullptr;
263  }
264  // If a .tab exists, then the corresponding .map/.dat is very likely a
265  // side-car file of the .tab
266  else if ( suffix == QLatin1String( "map" ) || suffix == QLatin1String( "dat" ) )
267  {
268  if ( QFile::exists( QDir( info.path() ).filePath( info.baseName() + ".tab" ) ) || QFile::exists( QDir( info.path() ).filePath( info.baseName() + ".TAB" ) ) )
269  return nullptr;
270  }
271  // .dbf and .shx should only appear if .shp is not present
272  else if ( suffix == QLatin1String( "dbf" ) || suffix == QLatin1String( "shx" ) )
273  {
274  if ( QFile::exists( QDir( info.path() ).filePath( info.baseName() + ".shp" ) ) || QFile::exists( QDir( info.path() ).filePath( info.baseName() + ".SHP" ) ) )
275  return nullptr;
276  }
277  // skip QGIS style xml files
278  else if ( suffix == QLatin1String( "xml" ) && QgsStyle::isXmlStyleFile( path ) )
279  {
280  return nullptr;
281  }
282  // GDAL 3.1 Shapefile driver directly handles .shp.zip files
283  else if ( path.endsWith( QLatin1String( ".shp.zip" ), Qt::CaseInsensitive ) &&
284  GDALIdentifyDriverEx( path.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr ) )
285  {
286  suffix = QStringLiteral( "shp.zip" );
287  }
288  // special handling for mbtiles files
289  else if ( suffix == QLatin1String( "mbtiles" ) )
290  {
291  QgsMbTiles reader( path );
292  if ( reader.open() )
293  {
294  if ( reader.metadataValue( QStringLiteral( "format" ) ) == QLatin1String( "pbf" ) )
295  {
296  // these are vector tiles
297  QUrlQuery uq;
298  uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
299  uq.addQueryItem( QStringLiteral( "url" ), path );
300  const QString encodedUri = uq.toString();
301  QgsVectorTileLayerItem *item = new QgsVectorTileLayerItem( parentItem, name, path, encodedUri );
302  item->setCapabilities( item->capabilities2() | Qgis::BrowserItemCapability::ItemRepresentsFile );
303  return item;
304  }
305  else
306  {
307  // handled by WMS provider
308  QUrlQuery uq;
309  uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
310  uq.addQueryItem( QStringLiteral( "url" ), QUrl::fromLocalFile( path ).toString() );
311  const QString encodedUri = uq.toString();
312  QgsLayerItem *item = new QgsLayerItem( parentItem, name, path, encodedUri, Qgis::BrowserLayerType::Raster, QStringLiteral( "wms" ) );
315  return item;
316  }
317  }
318  }
319 
320  // hide blocklisted URIs, such as .aux.xml files
321  if ( QgsProviderRegistry::instance()->uriIsBlocklisted( path ) )
322  return nullptr;
323 
324  QgsSettings settings;
325 
326  Qgis::SublayerQueryFlags queryFlags = Qgis::SublayerQueryFlags();
327 
328  // should we fast scan only?
329  if ( ( settings.value( QStringLiteral( "qgis/scanItemsInBrowser2" ),
330  "extension" ).toString() == QLatin1String( "extension" ) ) ||
331  ( parentItem && settings.value( QStringLiteral( "qgis/scanItemsFastScanUris" ),
332  QStringList() ).toStringList().contains( parentItem->path() ) ) )
333  {
334  queryFlags |= Qgis::SublayerQueryFlag::FastScan;
335  }
336 
337  const QList<QgsProviderSublayerDetails> sublayers = QgsProviderRegistry::instance()->querySublayers( path, queryFlags );
338 
339  if ( sublayers.size() == 1
342  )
343  {
344  QgsProviderSublayerItem *item = new QgsProviderSublayerItem( parentItem, name, sublayers.at( 0 ), path );
346  return item;
347  }
348  else if ( !sublayers.empty() )
349  {
350  QgsFileDataCollectionItem *item = new QgsFileDataCollectionItem( parentItem, name, path, sublayers );
352  return item;
353  }
354  else
355  {
356  return nullptr;
357  }
358 }
359 
361 {
362  QFileInfo info( path );
363  QString suffix = info.suffix().toLower();
364 
365  QStringList dirExtensions = QgsOgrProviderUtils::directoryExtensions();
366  return dirExtensions.contains( suffix );
367 }
@ 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:314
@ 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.
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:938
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
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.