1 /***************************************************************************
2                              qgsogrdataitems.cpp
3                              -------------------
4     begin                : 2011-04-01
5     copyright            : (C) 2011 Radim Blazek
6     email                : radim dot blazek 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 
16 #include "qgsogrdataitems.h"
17 ///@cond PRIVATE
18 
19 #include "qgsogrdbconnection.h"
20 
21 #include "qgslogger.h"
22 #include "qgsmessagelog.h"
23 #include "qgssettings.h"
24 #include "qgsproject.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsrasterlayer.h"
27 #include "qgsgeopackagedataitems.h"
28 #include "qgsogrutils.h"
29 #include "qgsproviderregistry.h"
30 #include "qgssqliteutils.h"
31 #include "symbology/qgsstyle.h"
32 
33 #include <QFileInfo>
34 #include <QTextStream>
35 #include <QAction>
36 #include <QMessageBox>
37 #include <QInputDialog>
38 #include <QFileDialog>
39 #include <QRegularExpression>
40 
41 #include <ogr_srs_api.h>
42 #include <cpl_error.h>
43 #include <cpl_conv.h>
44 #include <gdal.h>
45 
QgsOgrLayerItem(QgsDataItem * parent,const QString & name,const QString & path,const QString & uri,LayerType layerType,const QString & driverName,bool isSubLayer)46 QgsOgrLayerItem::QgsOgrLayerItem( QgsDataItem *parent,
47                                   const QString &name,
48                                   const QString &path,
49                                   const QString &uri,
50                                   LayerType layerType,
51                                   const QString &driverName,
52                                   bool isSubLayer )
53   : QgsLayerItem( parent, name, path, uri, layerType, QStringLiteral( "ogr" ) )
54   , mDriverName( driverName )
55   , mIsSubLayer( isSubLayer )
56 {
57   mIsSubLayer = isSubLayer;
58   mToolTip = uri;
59   const bool isIndex { QRegularExpression( R"(=idx_[^_]+_[^_]+.*$)" ).match( uri ).hasMatch() };
60   setState( ( driverName ==  QStringLiteral( "SQLite" ) && ! isIndex ) ? NotPopulated : Populated ); // children are accepted except for sqlite
61 }
62 
63 
createChildren()64 QVector<QgsDataItem *> QgsOgrLayerItem::createChildren()
65 {
66   QVector<QgsDataItem *> children;
67   // Geopackage is handled by QgsGeoPackageVectorLayerItem and QgsGeoPackageRasterLayerItem
68   // Proxy to spatialite provider data items because it implements the connections API
69   if ( mDriverName == QLatin1String( "SQLite" ) )
70   {
71     children.push_back( new QgsFieldsItem( this,
72                                            path() + QStringLiteral( "/columns/ " ),
73                                            QStringLiteral( R"(dbname="%1")" ).arg( parent()->path().replace( '"', QLatin1String( R"(\")" ) ) ),
74                                            QStringLiteral( "spatialite" ), QString(), name() ) );
75   }
76   return children;
77 }
78 
79 
layerTypeFromDb(const QString & geometryType)80 QgsLayerItem::LayerType QgsOgrLayerItem::layerTypeFromDb( const QString &geometryType )
81 {
82   if ( geometryType.contains( QStringLiteral( "Point" ), Qt::CaseInsensitive ) )
83   {
84     return QgsLayerItem::LayerType::Point;
85   }
86   else if ( geometryType.contains( QStringLiteral( "Polygon" ), Qt::CaseInsensitive ) )
87   {
88     return QgsLayerItem::LayerType::Polygon;
89   }
90   else if ( geometryType.contains( QStringLiteral( "LineString" ), Qt::CaseInsensitive ) )
91   {
92     return QgsLayerItem::LayerType::Line;
93   }
94   else if ( geometryType.contains( QStringLiteral( "Collection" ), Qt::CaseInsensitive ) )
95   {
96     return QgsLayerItem::LayerType::Vector;
97   }
98   // To be moved in a parent class that would also work for gdal and rasters
99   else if ( geometryType.contains( QStringLiteral( "Raster" ), Qt::CaseInsensitive ) )
100   {
101     return QgsLayerItem::LayerType::Raster;
102   }
103 
104   // fallback - try parsing as a WKT type string
105   switch ( QgsWkbTypes::geometryType( QgsWkbTypes::parseType( geometryType ) ) )
106   {
107     case QgsWkbTypes::PointGeometry:
108       return QgsLayerItem::LayerType::Point;
109     case QgsWkbTypes::LineGeometry:
110       return QgsLayerItem::LayerType::Line;
111     case QgsWkbTypes::PolygonGeometry:
112       return QgsLayerItem::LayerType::Polygon;
113     case QgsWkbTypes::UnknownGeometry:
114     case QgsWkbTypes::NullGeometry:
115       break;
116   }
117 
118   return QgsLayerItem::LayerType::TableLayer;
119 }
120 
isSubLayer() const121 bool QgsOgrLayerItem::isSubLayer() const
122 {
123   return mIsSubLayer;
124 }
125 
subLayers(const QString & path,const QString & driver)126 QList<QgsOgrDbLayerInfo *> QgsOgrLayerItem::subLayers( const QString &path, const QString &driver )
127 {
128 
129   QList<QgsOgrDbLayerInfo *> children;
130 
131   // Vector layers
132   const QgsVectorLayer::LayerOptions layerOptions { QgsProject::instance()->transformContext() };
133   QgsVectorLayer layer( path, QStringLiteral( "ogr_tmp" ), QStringLiteral( "ogr" ), layerOptions );
134   if ( layer.isValid( ) )
135   {
136     QVariantMap oriParts = QgsOgrProviderMetadata().decodeUri( path );
137 
138     // Collect mixed-geom layers
139     QMultiMap<int, QStringList> subLayersMap;
140     QgsOgrProvider *ogrProvider = qobject_cast<QgsOgrProvider *>( layer.dataProvider() );
141     const QStringList subLayersList( ogrProvider->subLayersWithoutFeatureCount( ) );
142     QMap< QString, int > mapLayerNameToCount;
143     bool uniqueNames = true;
144     int prevIdx = -1;
145     for ( const QString &descriptor : subLayersList )
146     {
147       QStringList pieces = descriptor.split( QgsDataProvider::sublayerSeparator() );
148       int idx = pieces[0].toInt();
149       subLayersMap.insert( idx, pieces );
150       if ( pieces.count() >= 4 && idx != prevIdx )
151       {
152         QString layerName = pieces[1];
153         int count = ++mapLayerNameToCount[layerName];
154         if ( count > 1 || layerName.isEmpty() )
155           uniqueNames = false;
156       }
157       prevIdx = idx;
158     }
159     prevIdx = -1;
160     const auto subLayerKeys = subLayersMap.keys( );
161     for ( const int &idx : subLayerKeys )
162     {
163       if ( idx == prevIdx )
164       {
165         continue;
166       }
167       prevIdx = idx;
168       QList<QStringList> values = subLayersMap.values( idx );
169       for ( int i = 0; i < values.size(); ++i )
170       {
171         QStringList pieces = values.at( i );
172         QString layerId = pieces[0];
173         QString name = pieces[1];
174         // QString featuresCount = pieces[2]; // Not used
175         QString geometryType = pieces[3];
176         QString geometryColumn = pieces[4];
177         QgsLayerItem::LayerType layerType;
178         layerType = QgsOgrLayerItem::layerTypeFromDb( geometryType );
179         // example URI for mixed-geoms geoms:    '/path/gdal_sample_v1.2_no_extensions.gpkg|layerid=7|geometrytype=Point'
180         // example URI for mixed-geoms attr table:    '/path/gdal_sample_v1.2_no_extensions.gpkg|layername=MyLayer|layerid=7'
181         // example URI for single geoms:    '/path/gdal_sample_v1.2_no_extensions.gpkg|layerid=6'
182         if ( layerType != QgsLayerItem::LayerType::NoType )
183         {
184           if ( geometryType.contains( QStringLiteral( "Collection" ), Qt::CaseInsensitive ) )
185           {
186             QgsDebugMsgLevel( QStringLiteral( "Layer %1 is a geometry collection: skipping %2" ).arg( name, path ), 3 );
187           }
188           else
189           {
190             QVariantMap parts( oriParts );
191             if ( uniqueNames )
192               parts.insert( QStringLiteral( "layerName" ), name );
193             else
194               parts.insert( QStringLiteral( "layerId" ), layerId );
195             if ( values.size() > 1 )
196             {
197               parts.insert( QStringLiteral( "geometryType" ), geometryType );
198             }
199             QString uri = QgsOgrProviderMetadata().encodeUri( parts );
200             QgsDebugMsgLevel( QStringLiteral( "Adding %1 Vector item %2 %3 %4" ).arg( driver, name, uri, geometryType ), 3 );
201             children.append( new QgsOgrDbLayerInfo( path, uri, name, geometryColumn, geometryType, layerType, driver ) );
202           }
203         }
204         else
205         {
206           QgsDebugMsgLevel( QStringLiteral( "Layer type is not a supported %1 Vector layer %2" ).arg( driver, path ), 3 );
207           QVariantMap parts( oriParts );
208           parts.insert( QStringLiteral( "layerId" ), layerId );
209           parts.insert( QStringLiteral( "layerName" ), name );
210           QString uri = QgsOgrProviderMetadata().encodeUri( parts );
211           children.append( new QgsOgrDbLayerInfo( path, uri, name, geometryColumn, geometryType, QgsLayerItem::LayerType::TableLayer, driver ) );
212         }
213       }
214     }
215   }
216 
217   // Raster layers
218   QgsRasterLayer::LayerOptions options;
219   options.loadDefaultStyle = false;
220   QgsRasterLayer rlayer( path, QStringLiteral( "gdal_tmp" ), QStringLiteral( "gdal" ), options );
221   if ( !rlayer.dataProvider()->subLayers( ).empty() )
222   {
223     const QStringList layers( rlayer.dataProvider()->subLayers( ) );
224     for ( const QString &uri : layers )
225     {
226       // Split on ':' since this is what comes out from the provider
227       QStringList pieces = uri.split( ':' );
228       QString name = pieces.value( pieces.length() - 1 );
229       QgsDebugMsgLevel( QStringLiteral( "Adding GeoPackage Raster item %1 %2" ).arg( name, uri ), 3 );
230       children.append( new QgsOgrDbLayerInfo( path, uri, name, QString(), QStringLiteral( "Raster" ), QgsLayerItem::LayerType::Raster, driver ) );
231     }
232   }
233   else if ( rlayer.isValid( ) )
234   {
235     // Get the identifier
236     GDALAllRegister();
237     // do not print errors, but write to debug
238     CPLPushErrorHandler( CPLQuietErrorHandler );
239     CPLErrorReset();
240     gdal::dataset_unique_ptr hDS( GDALOpen( path.toUtf8().constData(), GA_ReadOnly ) );
241     CPLPopErrorHandler();
242 
243     if ( ! hDS )
244     {
245       QgsDebugMsgLevel( QStringLiteral( "GDALOpen error # %1 : %2 " ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ), 2 );
246 
247     }
248     else
249     {
250       QString uri( QStringLiteral( "%1:%2" ).arg( driver, path ) );
251       QString name = GDALGetMetadataItem( hDS.get(), "IDENTIFIER", nullptr );
252       hDS.reset();
253       // Fallback: will not be able to delete the table
254       if ( name.isEmpty() )
255       {
256         name = QFileInfo( path ).fileName();
257       }
258       else
259       {
260         uri += QStringLiteral( ":%1" ).arg( name );
261       }
262 
263       QgsDebugMsgLevel( QStringLiteral( "Adding %1 Raster item %2 %3" ).arg( driver, name, path ), 3 );
264       children.append( new QgsOgrDbLayerInfo( path, uri, name, QString(), QStringLiteral( "Raster" ), QgsLayerItem::LayerType::Raster, driver ) );
265     }
266   }
267 
268   // There were problems in reading the file: throw
269   if ( ! layer.isValid() && ! rlayer.isValid() && children.isEmpty() )
270   {
271     QString errorMessage;
272     // If it is file based and the file exists, there might be a permission error, let's change
273     // the message to give the user a hint about this possibility.
274     if ( QFile::exists( path ) )
275     {
276       errorMessage = tr( "The file does not contain any layer or there was an error opening the file.\nCheck file and directory permissions on\n%1" ).arg( QDir::toNativeSeparators( path ) );
277     }
278     else
279     {
280       errorMessage = tr( "Layer is not valid (%1)" ).arg( path );
281     }
282     throw QgsOgrLayerNotValidException( errorMessage );
283   }
284 
285   return children;
286 }
287 
layerName() const288 QString QgsOgrLayerItem::layerName() const
289 {
290   QFileInfo info( name() );
291   if ( info.suffix() == QLatin1String( "gz" ) )
292     return info.baseName();
293   else
294     return info.completeBaseName();
295 }
296 
297 // -------
298 
dataItemForLayer(QgsDataItem * parentItem,QString name,QString path,GDALDatasetH hDataSource,int layerId,bool isSubLayer,bool uniqueNames)299 static QgsOgrLayerItem *dataItemForLayer( QgsDataItem *parentItem, QString name,
300     QString path, GDALDatasetH hDataSource,
301     int layerId,
302     bool isSubLayer, bool uniqueNames )
303 {
304   OGRLayerH hLayer = GDALDatasetGetLayer( hDataSource, layerId );
305   OGRFeatureDefnH hDef = OGR_L_GetLayerDefn( hLayer );
306 
307   QgsLayerItem::LayerType layerType = QgsLayerItem::Vector;
308   GDALDriverH hDriver = GDALGetDatasetDriver( hDataSource );
309   QString driverName = QString::fromUtf8( GDALGetDriverShortName( hDriver ) );
310   OGRwkbGeometryType ogrType = QgsOgrProvider::getOgrGeomType( driverName, hLayer );
311   QgsWkbTypes::Type wkbType = QgsOgrProviderUtils::qgisTypeFromOgrType( ogrType );
312   switch ( QgsWkbTypes::geometryType( wkbType ) )
313   {
314     case QgsWkbTypes::UnknownGeometry:
315       break;
316     case QgsWkbTypes::NullGeometry:
317       layerType = QgsLayerItem::TableLayer;
318       break;
319     case QgsWkbTypes::PointGeometry:
320       layerType = QgsLayerItem::Point;
321       break;
322     case QgsWkbTypes::LineGeometry:
323       layerType = QgsLayerItem::Line;
324       break;
325     case QgsWkbTypes::PolygonGeometry:
326       layerType = QgsLayerItem::Polygon;
327       break;
328   }
329 
330   QgsDebugMsgLevel( QStringLiteral( "ogrType = %1 layertype = %2" ).arg( ogrType ).arg( layerType ), 2 );
331 
332   QString layerUri = path;
333 
334   if ( isSubLayer )
335   {
336     // we are in a collection
337     name = QString::fromUtf8( OGR_FD_GetName( hDef ) );
338     QgsDebugMsgLevel( "OGR layer name : " + name, 2 );
339     if ( !uniqueNames )
340     {
341       layerUri += "|layerid=" + QString::number( layerId );
342     }
343     else
344     {
345       layerUri += "|layername=" + name;
346     }
347     path += '/' + name;
348   }
349   Q_ASSERT( !name.isEmpty() );
350 
351   QgsDebugMsgLevel( "OGR layer uri : " + layerUri, 2 );
352 
353   return new QgsOgrLayerItem( parentItem, name, path, layerUri, layerType, driverName, isSubLayer );
354 }
355 
356 // ----
357 
QgsOgrDataCollectionItem(QgsDataItem * parent,const QString & name,const QString & path)358 QgsOgrDataCollectionItem::QgsOgrDataCollectionItem( QgsDataItem *parent, const QString &name, const QString &path )
359   : QgsDataCollectionItem( parent, name, path )
360 {
361 }
362 
createChildren()363 QVector<QgsDataItem *> QgsOgrDataCollectionItem::createChildren()
364 {
365   QVector<QgsDataItem *> children;
366   QStringList skippedLayerNames;
367 
368   char **papszOptions = nullptr;
369   papszOptions = CSLSetNameValue( papszOptions, "@LIST_ALL_TABLES", "YES" );
370   gdal::dataset_unique_ptr hDataSource( GDALOpenEx( mPath.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, papszOptions, nullptr ) );
371   CSLDestroy( papszOptions );
372 
373   GDALDriverH hDriver = GDALGetDatasetDriver( hDataSource.get() );
374   const QString driverName = QString::fromUtf8( GDALGetDriverShortName( hDriver ) );
375   if ( driverName == QLatin1String( "SQLite" ) )
376   {
377     skippedLayerNames = QgsSqliteUtils::systemTables();
378   }
379 
380   if ( !hDataSource )
381     return children;
382   int numLayers = GDALDatasetGetLayerCount( hDataSource.get() );
383 
384   // Check if layer names are unique, so we can use |layername= in URI
385   QMap< QString, int > mapLayerNameToCount;
386   QList< int > skippedLayers;
387   bool uniqueNames = true;
388   for ( int i = 0; i < numLayers; ++i )
389   {
390     OGRLayerH hLayer = GDALDatasetGetLayer( hDataSource.get(), i );
391     OGRFeatureDefnH hDef = OGR_L_GetLayerDefn( hLayer );
392     QString layerName = QString::fromUtf8( OGR_FD_GetName( hDef ) );
393     ++mapLayerNameToCount[layerName];
394     if ( mapLayerNameToCount[layerName] > 1 )
395     {
396       uniqueNames = false;
397       break;
398     }
399     if ( ( driverName == QLatin1String( "SQLite" ) && layerName.contains( QRegularExpression( QStringLiteral( "idx_.*_geometry($|_.*)" ) ) ) )
400          || skippedLayerNames.contains( layerName ) )
401     {
402       skippedLayers << i;
403     }
404   }
405 
406   children.reserve( numLayers );
407   for ( int i = 0; i < numLayers; ++i )
408   {
409     if ( !skippedLayers.contains( i ) )
410     {
411       QgsOgrLayerItem *item = dataItemForLayer( this, QString(), mPath, hDataSource.get(), i, true, uniqueNames );
412       children.append( item );
413     }
414   }
415 
416   return children;
417 }
418 
saveConnection(const QString & path,const QString & ogrDriverName)419 bool QgsOgrDataCollectionItem::saveConnection( const QString &path, const QString &ogrDriverName )
420 {
421   QFileInfo fileInfo( path );
422   QString connName = fileInfo.fileName();
423   if ( ! path.isEmpty() )
424   {
425     bool ok = true;
426     while ( ok && ! QgsOgrDbConnection( connName, ogrDriverName ).path( ).isEmpty( ) )
427     {
428 
429       connName = QInputDialog::getText( nullptr, tr( "Add Connection" ),
430                                         tr( "A connection with the same name already exists,\nplease provide a new name:" ), QLineEdit::Normal,
431                                         QString(), &ok );
432     }
433     if ( ok && ! connName.isEmpty() )
434     {
435       QgsOgrDbConnection connection( connName, ogrDriverName );
436       connection.setPath( path );
437       connection.save();
438       return true;
439     }
440   }
441   return false;
442 }
443 
createConnection(const QString & name,const QString & extensions,const QString & ogrDriverName)444 bool QgsOgrDataCollectionItem::createConnection( const QString &name, const QString &extensions, const QString &ogrDriverName )
445 {
446   QString path = QFileDialog::getOpenFileName( nullptr, tr( "Open %1" ).arg( name ), QString(), extensions );
447   return saveConnection( path, ogrDriverName );
448 }
449 
hasDragEnabled() const450 bool QgsOgrDataCollectionItem::hasDragEnabled() const
451 {
452   return true;
453 }
454 
mimeUri() const455 QgsMimeDataUtils::Uri QgsOgrDataCollectionItem::mimeUri() const
456 {
457   QgsMimeDataUtils::Uri u;
458   u.providerKey = QStringLiteral( "ogr" );
459   u.uri = path();
460   u.layerType = QStringLiteral( "vector" );
461   return u;
462 }
463 
databaseConnection() const464 QgsAbstractDatabaseProviderConnection *QgsOgrDataCollectionItem::databaseConnection() const
465 {
466 
467   QgsAbstractDatabaseProviderConnection *conn { QgsDataCollectionItem::databaseConnection() };
468 
469   // There is a chance that this is a spatialite file, but spatialite is not handled by OGR and
470   // it's not even in core.
471   if ( ! conn )
472   {
473 
474     // test that file is valid with OGR
475     if ( OGRGetDriverCount() == 0 )
476     {
477       OGRRegisterAll();
478     }
479     // do not print errors, but write to debug
480     CPLPushErrorHandler( CPLQuietErrorHandler );
481     CPLErrorReset();
482     gdal::dataset_unique_ptr hDS( GDALOpenEx( path().toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
483     CPLPopErrorHandler();
484 
485     if ( ! hDS )
486     {
487       QgsDebugMsgLevel( QStringLiteral( "GDALOpen error # %1 : %2 on %3" ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ).arg( path() ), 2 );
488       return nullptr;
489     }
490 
491     GDALDriverH hDriver = GDALGetDatasetDriver( hDS.get() );
492     QString driverName = GDALGetDriverShortName( hDriver );
493 
494     if ( driverName == QLatin1String( "SQLite" ) )
495     {
496       QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "spatialite" ) ) };
497       if ( md )
498       {
499         QgsDataSourceUri uri;
500         uri.setDatabase( path( ) );
501         conn = static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( uri.uri(), {} ) );
502       }
503     }
504   }
505   return conn;
506 }
507 
508 // ---------------------------------------------------------------------------
509 
name()510 QString QgsOgrDataItemProvider::name()
511 {
512   return QStringLiteral( "OGR" );
513 }
514 
dataProviderKey() const515 QString QgsOgrDataItemProvider::dataProviderKey() const
516 {
517   return QStringLiteral( "ogr" );
518 }
519 
capabilities() const520 int QgsOgrDataItemProvider::capabilities() const
521 {
522   return QgsDataProvider::File | QgsDataProvider::Dir | QgsDataProvider::Net;
523 }
524 
createDataItem(const QString & pathIn,QgsDataItem * parentItem)525 QgsDataItem *QgsOgrDataItemProvider::createDataItem( const QString &pathIn, QgsDataItem *parentItem )
526 {
527   QString path( pathIn );
528   if ( path.isEmpty() )
529     return nullptr;
530 
531   QgsDebugMsgLevel( "thePath: " + path, 2 );
532 
533   // zip settings + info
534   QgsSettings settings;
535   QString scanZipSetting = settings.value( QStringLiteral( "qgis/scanZipInBrowser2" ), "basic" ).toString();
536   QString vsiPrefix = QgsZipItem::vsiPrefix( path );
537   bool is_vsizip = ( vsiPrefix == QLatin1String( "/vsizip/" ) );
538   bool is_vsigzip = ( vsiPrefix == QLatin1String( "/vsigzip/" ) );
539   bool is_vsitar = ( vsiPrefix == QLatin1String( "/vsitar/" ) );
540 
541   // should we check ext. only?
542   // check if scanItemsInBrowser2 == extension or parent dir in scanItemsFastScanUris
543   // TODO - do this in dir item, but this requires a way to inform which extensions are supported by provider
544   // maybe a callback function or in the provider registry?
545   bool scanExtSetting = false;
546   if ( ( settings.value( QStringLiteral( "qgis/scanItemsInBrowser2" ),
547                          "extension" ).toString() == QLatin1String( "extension" ) ) ||
548        ( parentItem && settings.value( QStringLiteral( "qgis/scanItemsFastScanUris" ),
549                                        QStringList() ).toStringList().contains( parentItem->path() ) ) ||
550        ( ( is_vsizip || is_vsitar ) && parentItem && parentItem->parent() &&
551          settings.value( QStringLiteral( "qgis/scanItemsFastScanUris" ),
552                          QStringList() ).toStringList().contains( parentItem->parent()->path() ) ) )
553   {
554     scanExtSetting = true;
555   }
556 
557   // get suffix, removing .gz if present
558   QString tmpPath = path; //path used for testing, not for layer creation
559   if ( is_vsigzip )
560     tmpPath.chop( 3 );
561   QFileInfo info( tmpPath );
562   QString suffix = info.suffix().toLower();
563 
564 // GDAL 3.1 Shapefile driver directly handles .shp.zip files
565   if ( path.endsWith( QLatin1String( ".shp.zip" ), Qt::CaseInsensitive ) &&
566        GDALIdentifyDriver( path.toUtf8().constData(), nullptr ) )
567   {
568     suffix = QStringLiteral( "shp.zip" );
569   }
570 
571   // extract basename with extension
572   info.setFile( path );
573   QString name = info.fileName();
574 
575   // If a .tab exists, then the corresponding .map/.dat is very likely a
576   // side-car file of the .tab
577   if ( suffix == QLatin1String( "map" ) || suffix == QLatin1String( "dat" ) )
578   {
579     if ( QFileInfo( QDir( info.path() ), info.baseName() + ".tab" ).exists() )
580       return nullptr;
581   }
582 
583   QgsDebugMsgLevel( "thePath= " + path + " tmpPath= " + tmpPath + " name= " + name
584                     + " suffix= " + suffix + " vsiPrefix= " + vsiPrefix, 3 );
585 
586   QStringList myExtensions = QgsOgrProviderUtils::fileExtensions();
587   QStringList dirExtensions = QgsOgrProviderUtils::directoryExtensions();
588 
589   // allow only normal files, supported directories, or VSIFILE items to continue
590   bool isOgrSupportedDirectory = info.isDir() && dirExtensions.contains( suffix );
591   if ( !isOgrSupportedDirectory && !info.isFile() && vsiPrefix.isEmpty() )
592     return nullptr;
593 
594   // skip *.aux.xml files (GDAL auxiliary metadata files),
595   // *.shp.xml files (ESRI metadata) and *.tif.xml files (TIFF metadata)
596   // unless that extension is in the list (*.xml might be though)
597   if ( path.endsWith( QLatin1String( ".aux.xml" ), Qt::CaseInsensitive ) &&
598        !myExtensions.contains( QStringLiteral( "aux.xml" ) ) )
599     return nullptr;
600   if ( path.endsWith( QLatin1String( ".shp.xml" ), Qt::CaseInsensitive ) &&
601        !myExtensions.contains( QStringLiteral( "shp.xml" ) ) )
602     return nullptr;
603   if ( path.endsWith( QLatin1String( ".tif.xml" ), Qt::CaseInsensitive ) &&
604        !myExtensions.contains( QStringLiteral( "tif.xml" ) ) )
605     return nullptr;
606 
607   // skip QGIS style xml files
608   if ( path.endsWith( QLatin1String( ".xml" ), Qt::CaseInsensitive ) &&
609        QgsStyle::isXmlStyleFile( path ) )
610     return nullptr;
611 
612   // We have to filter by extensions, otherwise e.g. all Shapefile files are displayed
613   // because OGR drive can open also .dbf, .shx.
614   if ( myExtensions.indexOf( suffix ) < 0 && !dirExtensions.contains( suffix ) )
615   {
616     bool matches = false;
617     const auto constWildcards = QgsOgrProviderUtils::wildcards();
618     for ( const QString &wildcard : constWildcards )
619     {
620       QRegExp rx( wildcard, Qt::CaseInsensitive, QRegExp::Wildcard );
621       if ( rx.exactMatch( info.fileName() ) )
622       {
623         matches = true;
624         break;
625       }
626     }
627     if ( !matches )
628       return nullptr;
629   }
630 
631   // .dbf should probably appear if .shp is not present
632   if ( suffix == QLatin1String( "dbf" ) )
633   {
634     QString pathShp = path.left( path.count() - 4 ) + ".shp";
635     if ( QFileInfo::exists( pathShp ) )
636       return nullptr;
637   }
638 
639   // fix vsifile path and name
640   if ( !vsiPrefix.isEmpty() )
641   {
642     // add vsiPrefix to path if needed
643     if ( !path.startsWith( vsiPrefix ) )
644       path = vsiPrefix + path;
645     // if this is a /vsigzip/path_to_zip.zip/file_inside_zip remove the full path from the name
646     // no need to change the name I believe
647 #if 0
648     if ( ( is_vsizip || is_vsitar ) && ( path != vsiPrefix + parentItem->path() ) )
649     {
650       name = path;
651       name = name.replace( vsiPrefix + parentItem->path() + '/', "" );
652     }
653 #endif
654   }
655 
656   // Filters out the OGR/GDAL supported formats that can contain multiple layers
657   // and should be treated like a DB: GeoPackage and SQLite
658   // NOTE: this formats are scanned for rasters too and they must
659   //       be skipped by "gdal" provider or the rasters will be listed
660   //       twice. ogrSupportedDbLayersExtensions must be kept in sync
661   //       with the companion variable (same name) in the gdal provider
662   //       class
663   // TODO: add more OGR supported multiple layers formats here!
664   static QStringList sOgrSupportedDbLayersExtensions { QStringLiteral( "gpkg" ),
665       QStringLiteral( "sqlite" ),
666       QStringLiteral( "db" ),
667       QStringLiteral( "gdb" ),
668       QStringLiteral( "kml" ),
669       QStringLiteral( "osm" ),
670       QStringLiteral( "mdb" ),
671       QStringLiteral( "accdb" ),
672       QStringLiteral( "xls" ),
673       QStringLiteral( "xlsx" ),
674       QStringLiteral( "gpx" ),
675       QStringLiteral( "pdf" ),
676       QStringLiteral( "pbf" ) };
677   static QStringList sOgrSupportedDbDriverNames { QStringLiteral( "GPKG" ),
678       QStringLiteral( "db" ),
679       QStringLiteral( "gdb" ),
680       QStringLiteral( "xlsx" ),
681       QStringLiteral( "xls" ),
682       QStringLiteral( "pgdb" )};
683 
684   // these extensions are trivial to read, so there's no need to rely on
685   // the extension only scan here -- avoiding it always gives us the correct data type
686   // and sublayer visibility
687   static QStringList sSkipFastTrackExtensions { QStringLiteral( "xlsx" ),
688       QStringLiteral( "ods" ),
689       QStringLiteral( "csv" ),
690       QStringLiteral( "nc" ),
691       QStringLiteral( "shp.zip" ) };
692 
693   // Fast track: return item without testing if:
694   // scanExtSetting or zipfile and scan zip == "Basic scan"
695   // netCDF files can be both raster or vector, so fallback to opening
696   if ( ( scanExtSetting ||
697          ( ( is_vsizip || is_vsitar ) && scanZipSetting == QLatin1String( "basic" ) ) ) &&
698        !sSkipFastTrackExtensions.contains( suffix ) )
699   {
700     // if this is a VRT file make sure it is vector VRT to avoid duplicates
701     if ( suffix == QLatin1String( "vrt" ) )
702     {
703       CPLPushErrorHandler( CPLQuietErrorHandler );
704       CPLErrorReset();
705       GDALDriverH hDriver = GDALIdentifyDriver( path.toUtf8().constData(), nullptr );
706       CPLPopErrorHandler();
707       if ( !hDriver || GDALGetDriverShortName( hDriver ) == QLatin1String( "VRT" ) )
708       {
709         QgsDebugMsgLevel( QStringLiteral( "Skipping VRT file because root is not a OGR VRT" ), 2 );
710         return nullptr;
711       }
712     }
713     // Handle collections
714     // Check if the layer has sublayers by comparing the extension
715     QgsDataItem *item = nullptr;
716     if ( ! sOgrSupportedDbLayersExtensions.contains( suffix ) )
717     {
718       item = new QgsOgrLayerItem( parentItem, name, path, path, QgsLayerItem::Vector );
719     }
720     else if ( suffix.compare( QLatin1String( "gpkg" ), Qt::CaseInsensitive ) == 0 )
721     {
722       item = new QgsGeoPackageCollectionItem( parentItem, name, path );
723     }
724     else
725     {
726       item = new QgsOgrDataCollectionItem( parentItem, name, path );
727     }
728 
729     if ( item )
730       return item;
731   }
732 
733   // Slow track: scan file contents
734   QgsDataItem *item = nullptr;
735 
736   // test that file is valid with OGR
737   if ( OGRGetDriverCount() == 0 )
738   {
739     OGRRegisterAll();
740   }
741   // do not print errors, but write to debug
742   CPLPushErrorHandler( CPLQuietErrorHandler );
743   CPLErrorReset();
744   gdal::dataset_unique_ptr hDS( GDALOpenEx( path.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
745   CPLPopErrorHandler();
746 
747   if ( ! hDS )
748   {
749     QgsDebugMsgLevel( QStringLiteral( "GDALOpen error # %1 : %2 on %3" ).arg( CPLGetLastErrorNo() ).arg( CPLGetLastErrorMsg() ).arg( path ), 2 );
750     return nullptr;
751   }
752 
753   GDALDriverH hDriver = GDALGetDatasetDriver( hDS.get() );
754   QString driverName = GDALGetDriverShortName( hDriver );
755   QgsDebugMsgLevel( QStringLiteral( "GDAL Driver : %1" ).arg( driverName ), 2 );
756   int numLayers = GDALDatasetGetLayerCount( hDS.get() );
757 
758   // GeoPackage needs a specialized data item, mainly because of raster deletion not
759   // yet implemented in GDAL (2.2.1)
760   if ( driverName == QLatin1String( "GPKG" ) )
761   {
762     item = new QgsGeoPackageCollectionItem( parentItem, name, path );
763   }
764   else if ( numLayers > 1 || sOgrSupportedDbDriverNames.contains( driverName ) )
765   {
766     item = new QgsOgrDataCollectionItem( parentItem, name, path );
767   }
768   else
769   {
770     item = dataItemForLayer( parentItem, name, path, hDS.get(), 0, false, true );
771   }
772   return item;
773 }
774 
handlesDirectoryPath(const QString & path)775 bool QgsOgrDataItemProvider::handlesDirectoryPath( const QString &path )
776 {
777   QFileInfo info( path );
778   QString suffix = info.suffix().toLower();
779 
780   QStringList dirExtensions = QgsOgrProviderUtils::directoryExtensions();
781   return dirExtensions.contains( suffix );
782 }
783 
784 ///@endcond
785