diff --git a/src/core/qgsdataitem.cpp b/src/core/qgsdataitem.cpp index 17f2e6436fd..4dd677c6a57 100644 --- a/src/core/qgsdataitem.cpp +++ b/src/core/qgsdataitem.cpp @@ -477,7 +477,6 @@ QVector QgsDirectoryItem::createChildren( ) QVector children; QDir dir( mPath ); QSettings settings; - bool scanZip = ( settings.value( "/qgis/scanZipInBrowser", QVariant( "basic" ) ).toString() != "no" ); QStringList entries = dir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name | QDir::IgnoreCase ); foreach( QString subdir, entries ) @@ -497,8 +496,10 @@ QVector QgsDirectoryItem::createChildren( ) QString path = dir.absoluteFilePath( name ); QFileInfo fileInfo( path ); + QString vsiPrefix = QgsZipItem::vsiPrefix( path ); // vsizip support was added to GDAL/OGR 1.6 but GDAL_VERSION_NUM not available here - if ( fileInfo.suffix() == "zip" && scanZip ) + if (( settings.value( "/qgis/scanZipInBrowser", QVariant( "basic" ) ).toString() != "no" ) && + ( vsiPrefix == "/vsizip/" || vsiPrefix == "/vsitar/" ) ) { QgsDataItem * item = QgsZipItem::itemFromPath( this, path, name ); if ( item ) @@ -747,6 +748,7 @@ QgsZipItem::QgsZipItem( QgsDataItem* parent, QString name, QString path ) { mType = Collection; //Zip?? mIcon = iconZip(); + mVsiPrefix = vsiPrefix( path ); if ( mProviderNames.size() == 0 ) { @@ -796,7 +798,6 @@ QgsZipItem::QgsZipItem( QgsDataItem* parent, QString name, QString path ) } } } - } QgsZipItem::~QgsZipItem() @@ -879,13 +880,12 @@ QVector QgsZipItem::createChildren( ) QVector children; QString tmpPath; QString childPath; - QSettings settings; QString scanZipSetting = settings.value( "/qgis/scanZipInBrowser", "basic" ).toString(); mZipFileList.clear(); - QgsDebugMsg( QString( "path = %1 name= %2 scanZipSetting= %3" ).arg( path() ).arg( name() ).arg( scanZipSetting ) ); + QgsDebugMsg( QString( "path = %1 name= %2 scanZipSetting= %3 vsiPrefix= %4" ).arg( path() ).arg( name() ).arg( scanZipSetting ).arg( mVsiPrefix ) ); // if scanZipBrowser == no: skip to the next file if ( scanZipSetting == "no" ) @@ -901,31 +901,14 @@ QVector QgsZipItem::createChildren( ) // return children; // } - // get list of files inside zip file - QgsDebugMsg( QString( "Open file %1 with gdal vsi" ).arg( path() ) ); - char **papszSiblingFiles = VSIReadDirRecursive1( QString( "/vsizip/" + path() ).toLocal8Bit().constData() ); - if ( papszSiblingFiles ) - { - for ( int i = 0; i < CSLCount( papszSiblingFiles ); i++ ) - { - tmpPath = papszSiblingFiles[i]; - QgsDebugMsg( QString( "Read file %1" ).arg( tmpPath ) ); - // skip directories (files ending with /) - if ( tmpPath.right( 1 ) != "/" ) - mZipFileList << tmpPath; - } - CSLDestroy( papszSiblingFiles ); - } - else - { - QgsDebugMsg( QString( "Error reading %1" ).arg( path() ) ); - } + // first get list of files + getZipFileList(); // loop over files inside zip foreach( QString fileName, mZipFileList ) { QFileInfo info( fileName ); - tmpPath = "/vsizip/" + path() + "/" + fileName; + tmpPath = mVsiPrefix + path() + "/" + fileName; QgsDebugMsg( "tmpPath = " + tmpPath ); // foreach( dataItem_t *dataItem, mDataItemPtr ) @@ -934,7 +917,7 @@ QVector QgsZipItem::createChildren( ) // ugly hack to remove .dbf file if there is a .shp file if ( mProviderNames[i] == "ogr" ) { - if ( info.suffix() == "dbf" ) + if ( info.suffix().toLower() == "dbf" ) { if ( mZipFileList.indexOf( fileName.left( fileName.count() - 4 ) + ".shp" ) != -1 ) continue; @@ -976,20 +959,38 @@ QVector QgsZipItem::createChildren( ) return children; } - +QString QgsZipItem::vsiPrefix( QString path ) +{ + if ( path.startsWith( "/vsizip/", Qt::CaseInsensitive ) || + path.endsWith( ".zip", Qt::CaseInsensitive ) ) + return "/vsizip/"; + else if ( path.startsWith( "/vsitar/", Qt::CaseInsensitive ) || + path.endsWith( ".tar", Qt::CaseInsensitive ) || + path.endsWith( ".tar.gz", Qt::CaseInsensitive ) || + path.endsWith( ".tgz", Qt::CaseInsensitive ) ) + return "/vsitar/"; + else if ( path.startsWith( "/vsigzip/", Qt::CaseInsensitive ) || + path.endsWith( ".gz", Qt::CaseInsensitive ) ) + return "/vsigzip/"; + else + return ""; +} QgsDataItem* QgsZipItem::itemFromPath( QgsDataItem* parent, QString path, QString name ) { QSettings settings; QString scanZipSetting = settings.value( "/qgis/scanZipInBrowser", "basic" ).toString(); - QString vsizipPath = path; + QString vsiPath = path; int zipFileCount = 0; + QStringList zipFileList; QFileInfo fileInfo( path ); + QString vsiPrefix = QgsZipItem::vsiPrefix( path ); QgsZipItem * zipItem = 0; + bool populated = false; - QgsDebugMsg( QString( "path = %1 name= %2 scanZipSetting= %3" ).arg( path ).arg( name ).arg( scanZipSetting ) ); + QgsDebugMsg( QString( "path = %1 name= %2 scanZipSetting= %3 vsiPrefix= %4" ).arg( path ).arg( name ).arg( scanZipSetting ).arg( vsiPrefix ) ); - // if scanZipBrowser == no: skip to the next file + // if scanZipBrowser == no: don't read the zip file if ( scanZipSetting == "no" ) { return 0; @@ -1007,29 +1008,47 @@ QgsDataItem* QgsZipItem::itemFromPath( QgsDataItem* parent, QString path, QStrin if ( zipItem ) { - // force populate zipItem - zipItem->populate(); - QgsDebugMsg( QString( "Got zipItem with %1 children, path=%2, name=%3" ).arg( zipItem->rowCount() ).arg( zipItem->path() ).arg( zipItem->name() ) ); + // force populate zipItem if it has less than 10 items and is not a .tgz or .tar.gz file (slow loading) + // for other items populating will be delayed until item is opened + // this might be polluting the tree with empty items but is necessary for performance reasons + // could also accept all files smaller than a certain size and add options for file count and/or size + + // first get list of files inside .zip or .tar files + if ( path.endsWith( ".zip", Qt::CaseInsensitive ) || + path.endsWith( ".tar", Qt::CaseInsensitive ) ) + { + zipFileList = zipItem->getZipFileList(); + } + // force populate if less than 10 items + if ( zipFileList.count() > 0 && zipFileList.count() <= 10 ) + { + zipItem->populate(); + populated = true; // there is no QgsDataItem::isPopulated() function + QgsDebugMsg( QString( "Got zipItem with %1 children, path=%2, name=%3" ).arg( zipItem->rowCount() ).arg( zipItem->path() ).arg( zipItem->name() ) ); + } + else + { + QgsDebugMsg( QString( "Delaying populating zipItem with path=%1, name=%2" ).arg( zipItem->path() ).arg( zipItem->name() ) ); + } } -// only display if has children -// other option would be to delay until item is opened, but we would be polluting the tree with empty items - if ( zipItem && zipItem->rowCount() > 1 ) + // only display if has children or if is not populated + if ( zipItem && ( !populated || zipItem->rowCount() > 1 ) ) { QgsDebugMsg( "returning zipItem" ); return zipItem; } -// if 1 or 0 child found, create a single data item using the normal path or the full path given by QgsZipItem + // if 1 or 0 child found, create a single data item using the normal path or the full path given by QgsZipItem else { if ( zipItem ) { - vsizipPath = zipItem->path(); - zipFileCount = zipItem->getZipFileList().count(); + vsiPath = zipItem->path(); + zipFileCount = zipFileList.count(); delete zipItem; } - QgsDebugMsg( QString( "will try to create a normal dataItem from path= %2 or %3" ).arg( path ).arg( vsizipPath ) ); + QgsDebugMsg( QString( "will try to create a normal dataItem from path= %2 or %3" ).arg( path ).arg( vsiPath ) ); // try to open using registered providers (gdal and ogr) for ( int i = 0; i < mProviderNames.size(); i++ ) @@ -1047,7 +1066,7 @@ QgsDataItem* QgsZipItem::itemFromPath( QgsDataItem* parent, QString path, QStrin item = dataItem( path, parent ); // try with /vsizip/ if ( ! item ) - item = dataItem( vsizipPath, parent ); + item = dataItem( vsiPath, parent ); if ( item ) return item; } @@ -1056,3 +1075,43 @@ QgsDataItem* QgsZipItem::itemFromPath( QgsDataItem* parent, QString path, QStrin return 0; } + +const QStringList & QgsZipItem::getZipFileList() +{ + if ( ! mZipFileList.isEmpty() ) + return mZipFileList; + + QString tmpPath; + QSettings settings; + QString scanZipSetting = settings.value( "/qgis/scanZipInBrowser", "basic" ).toString(); + + QgsDebugMsg( QString( "path = %1 name= %2 scanZipSetting= %3 vsiPrefix= %4" ).arg( path() ).arg( name() ).arg( scanZipSetting ).arg( mVsiPrefix ) ); + + // if scanZipBrowser == no: skip to the next file + if ( scanZipSetting == "no" ) + { + return mZipFileList; + } + + // get list of files inside zip file + QgsDebugMsg( QString( "Open file %1 with gdal vsi" ).arg( mVsiPrefix + path() ) ); + char **papszSiblingFiles = VSIReadDirRecursive1( QString( mVsiPrefix + path() ).toLocal8Bit().constData() ); + if ( papszSiblingFiles ) + { + for ( int i = 0; i < CSLCount( papszSiblingFiles ); i++ ) + { + tmpPath = papszSiblingFiles[i]; + QgsDebugMsg( QString( "Read file %1" ).arg( tmpPath ) ); + // skip directories (files ending with /) + if ( tmpPath.right( 1 ) != "/" ) + mZipFileList << tmpPath; + } + CSLDestroy( papszSiblingFiles ); + } + else + { + QgsDebugMsg( QString( "Error reading %1" ).arg( path() ) ); + } + + return mZipFileList; +} diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index 51feab8173b..82fb70a8a8e 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -298,6 +298,7 @@ class CORE_EXPORT QgsZipItem : public QgsDataCollectionItem Q_OBJECT protected: + QString mVsiPrefix; QStringList mZipFileList; public: @@ -305,17 +306,17 @@ class CORE_EXPORT QgsZipItem : public QgsDataCollectionItem ~QgsZipItem(); QVector createChildren(); - QStringList getFiles(); + const QStringList & getZipFileList(); static QVector mDataItemPtr; static QStringList mProviderNames; + static QString vsiPrefix( QString uri ); + static QgsDataItem* itemFromPath( QgsDataItem* parent, QString path, QString name ); static const QIcon &iconZip(); - const QStringList & getZipFileList() const { return mZipFileList; } - }; #endif // QGSDATAITEM_H diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index ea1f5254f2d..b5872451fec 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -642,6 +642,14 @@ QString QgsMapLayer::styleURI( ) // ideally we should look for .qml file inside zip file myURI.remove( 0, 8 ); } + else if ( myURI.startsWith( "/vsitar/", Qt::CaseInsensitive ) && + ( myURI.endsWith( ".tar", Qt::CaseInsensitive ) || + myURI.endsWith( ".tar.gz", Qt::CaseInsensitive ) || + myURI.endsWith( ".tgz", Qt::CaseInsensitive ) ) ) + { + // ideally we should look for .qml file inside tar file + myURI.remove( 0, 8 ); + } QFileInfo myFileInfo( myURI ); QString key; @@ -650,15 +658,18 @@ QString QgsMapLayer::styleURI( ) { // if file is using the /vsizip/ or /vsigzip/ mechanism, cleanup the name if ( myURI.endsWith( ".gz", Qt::CaseInsensitive ) ) - { myURI.chop( 3 ); - myFileInfo.setFile( myURI ); - } else if ( myURI.endsWith( ".zip", Qt::CaseInsensitive ) ) - { myURI.chop( 4 ); - myFileInfo.setFile( myURI ); - } + else if ( myURI.endsWith( ".tar", Qt::CaseInsensitive ) ) + myURI.chop( 4 ); + else if ( myURI.endsWith( ".tar.gz", Qt::CaseInsensitive ) ) + myURI.chop( 7 ); + else if ( myURI.endsWith( ".tgz", Qt::CaseInsensitive ) ) + myURI.chop( 4 ); + else if ( myURI.endsWith( ".gz", Qt::CaseInsensitive ) ) + myURI.chop( 3 ); + myFileInfo.setFile( myURI ); // get the file name for our .qml style file key = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + ".qml"; } diff --git a/src/providers/gdal/qgsgdaldataitems.cpp b/src/providers/gdal/qgsgdaldataitems.cpp index eb57aa74d57..245742d7f8f 100644 --- a/src/providers/gdal/qgsgdaldataitems.cpp +++ b/src/providers/gdal/qgsgdaldataitems.cpp @@ -399,10 +399,10 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) // zip settings + info QSettings settings; QString scanZipSetting = settings.value( "/qgis/scanZipInBrowser", "basic" ).toString(); - bool is_vsizip = ( thePath.startsWith( "/vsizip/" ) || - thePath.endsWith( ".zip", Qt::CaseInsensitive ) ); - bool is_vsigzip = ( thePath.startsWith( "/vsigzip/" ) || - thePath.endsWith( ".gz", Qt::CaseInsensitive ) ); + QString vsiPrefix = QgsZipItem::vsiPrefix( thePath ); + bool is_vsizip = ( vsiPrefix == "/vsizip/" ); + bool is_vsigzip = ( vsiPrefix == "/vsigzip/" ); + bool is_vsitar = ( vsiPrefix == "/vsitar/" ); // get suffix, removing .gz if present QString tmpPath = thePath; //path used for testing, not for layer creation @@ -414,10 +414,11 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) info.setFile( thePath ); QString name = info.fileName(); - QgsDebugMsg( "thePath= " + thePath + " tmpPath= " + tmpPath + " name= " + name + " suffix= " + suffix ); + QgsDebugMsg( "thePath= " + thePath + " tmpPath= " + tmpPath + " name= " + name + + " suffix= " + suffix + " vsiPrefix= " + vsiPrefix ); // allow only normal files or VSIFILE items to continue - if ( !info.isFile() && !is_vsizip && !is_vsigzip ) + if ( !info.isFile() && vsiPrefix == "" ) return 0; // get supported extensions @@ -434,10 +435,6 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) !extensions.contains( "aux.xml" ) ) return 0; - // skip .tar.gz files - if ( thePath.endsWith( ".tar.gz", Qt::CaseInsensitive ) ) - return 0; - // Filter files by extension if ( !extensions.contains( suffix ) ) { @@ -455,30 +452,27 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) return 0; } - // add /vsizip/ or /vsigzip/ to path if file extension is .zip or .gz - if ( is_vsigzip ) + // fix vsifile path and name + if ( vsiPrefix != "" ) { - if ( !thePath.startsWith( "/vsigzip/" ) ) - thePath = "/vsigzip/" + thePath; - } - else if ( is_vsizip ) - { - if ( !thePath.startsWith( "/vsizip/" ) ) - thePath = "/vsizip/" + thePath; + // add vsiPrefix to path if needed + if ( !thePath.startsWith( vsiPrefix ) ) + thePath = vsiPrefix + thePath; // if this is a /vsigzip/path_to_zip.zip/file_inside_zip remove the full path from the name - if ( thePath != "/vsizip/" + parentItem->path() ) + if (( is_vsizip || is_vsitar ) && ( thePath != vsiPrefix + parentItem->path() ) ) { name = thePath; - name = name.replace( "/vsizip/" + parentItem->path() + "/", "" ); + name = name.replace( vsiPrefix + parentItem->path() + "/", "" ); } } // return a /vsizip/ item without testing if: // zipfile and scan zip == "Basic scan" // not zipfile and scan items == "Check extension" - if (( is_vsizip && scanZipSetting == "basic" ) || - ( !is_vsizip && ( settings.value( "/qgis/scanItemsInBrowser", - "extension" ).toString() == "extension" ) ) ) + if ((( is_vsizip || is_vsitar ) && scanZipSetting == "basic" ) || + ( !is_vsizip && !is_vsitar && + ( settings.value( "/qgis/scanItemsInBrowser", + "extension" ).toString() == "extension" ) ) ) { // if this is a VRT file make sure it is raster VRT to avoid duplicates if ( suffix == "vrt" ) diff --git a/src/providers/gdal/qgsgdalprovider.cpp b/src/providers/gdal/qgsgdalprovider.cpp index 0ea91bf17cf..512229de43f 100644 --- a/src/providers/gdal/qgsgdalprovider.cpp +++ b/src/providers/gdal/qgsgdalprovider.cpp @@ -111,19 +111,12 @@ QgsGdalProvider::QgsGdalProvider( QString const & uri ) mGdalDataset = NULL; // Try to open using VSIFileHandler (see qgsogrprovider.cpp) - // TODO suppress error messages and report in debug, like in OGR provider - // TODO use the file name of the file inside the zip, needs unzip.h - if ( uri.endsWith( ".zip", Qt::CaseInsensitive ) ) + QString vsiPrefix = QgsZipItem::vsiPrefix( uri ); + if ( vsiPrefix != "" ) { - if ( !uri.startsWith( "/vsizip/" ) ) - setDataSourceUri( "/vsizip/" + uri ); - QgsDebugMsg( QString( "Trying /vsizip syntax, uri= %1" ).arg( dataSourceUri() ) ); - } - else if ( uri.endsWith( ".gz", Qt::CaseInsensitive ) ) - { - if ( !uri.startsWith( "/vsigzip/" ) ) - setDataSourceUri( "/vsigzip/" + uri ); - QgsDebugMsg( QString( "Trying /vsigzip syntax, uri= %1" ).arg( dataSourceUri() ) ); + if ( !uri.startsWith( vsiPrefix ) ) + setDataSourceUri( vsiPrefix + uri ); + QgsDebugMsg( QString( "Trying %1 syntax, uri= %2" ).arg( vsiPrefix ).arg( dataSourceUri() ) ); } // The uri is either a file name or encoded parameters for WCS @@ -1920,8 +1913,9 @@ void buildSupportedRasterFileFilterAndExtensions( QString & theFileFiltersString { QString glob = "*.zip"; glob += " *.gz"; + glob += " *.tar *.tar.gz *.tgz"; theFileFiltersString += ";;[GDAL] " + QObject::tr( "GDAL/OGR VSIFileHandler" ) + " (" + glob.toLower() + " " + glob.toUpper() + ")"; - theExtensions << "zip" << "gz"; + theExtensions << "zip" << "gz" << "tar" << "tar.gz" << "tgz"; } #endif @@ -1940,21 +1934,12 @@ QGISEXTERN bool isValidRasterFileName( QString const & theFileNameQString, QStri // Try to open using VSIFileHandler (see qgsogrprovider.cpp) // TODO suppress error messages and report in debug, like in OGR provider - if ( fileName.endsWith( ".zip", Qt::CaseInsensitive ) ) + QString vsiPrefix = QgsZipItem::vsiPrefix( fileName ); + if ( vsiPrefix != "" ) { - if ( !fileName.startsWith( "/vsizip/" ) ) - { - fileName = "/vsizip/" + fileName; - } - QgsDebugMsg( QString( "Trying /vsizip syntax, fileName= %1" ).arg( fileName ) ); - } - if ( fileName.endsWith( ".gz", Qt::CaseInsensitive ) ) - { - if ( !fileName.startsWith( "/vsigzip/" ) ) - { - fileName = "/vsigzip/" + fileName; - } - QgsDebugMsg( QString( "Trying /vsigzip syntax, fileName= %1" ).arg( fileName ) ); + if ( !fileName.startsWith( vsiPrefix ) ) + fileName = vsiPrefix + fileName; + QgsDebugMsg( QString( "Trying %1 syntax, fileName= %2" ).arg( vsiPrefix ).arg( fileName ) ); } //open the file using gdal making sure we have handled locale properly diff --git a/src/providers/ogr/qgsogrdataitems.cpp b/src/providers/ogr/qgsogrdataitems.cpp index 9f29b99f778..154375d1887 100644 --- a/src/providers/ogr/qgsogrdataitems.cpp +++ b/src/providers/ogr/qgsogrdataitems.cpp @@ -240,10 +240,10 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) // zip settings + info QSettings settings; QString scanZipSetting = settings.value( "/qgis/scanZipInBrowser", "basic" ).toString(); - bool is_vsizip = ( thePath.startsWith( "/vsizip/" ) || - thePath.endsWith( ".zip", Qt::CaseInsensitive ) ); - bool is_vsigzip = ( thePath.startsWith( "/vsigzip/" ) || - thePath.endsWith( ".gz", Qt::CaseInsensitive ) ); + QString vsiPrefix = QgsZipItem::vsiPrefix( thePath ); + bool is_vsizip = ( vsiPrefix == "/vsizip/" ); + bool is_vsigzip = ( vsiPrefix == "/vsigzip/" ); + bool is_vsitar = ( vsiPrefix == "/vsitar/" ); // get suffix, removing .gz if present QString tmpPath = thePath; //path used for testing, not for layer creation @@ -255,10 +255,11 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) info.setFile( thePath ); QString name = info.fileName(); - QgsDebugMsg( "thePath= " + thePath + " tmpPath= " + tmpPath + " name= " + name + " suffix= " + suffix ); + QgsDebugMsg( "thePath= " + thePath + " tmpPath= " + tmpPath + " name= " + name + + " suffix= " + suffix + " vsiPrefix= " + vsiPrefix ); // allow only normal files or VSIFILE items to continue - if ( !info.isFile() && !is_vsizip && !is_vsigzip ) + if ( !info.isFile() && vsiPrefix == "" ) return 0; QStringList myExtensions = fileExtensions(); @@ -272,10 +273,6 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) !myExtensions.contains( "shp.xml" ) ) return 0; - // skip .tar.gz files - if ( thePath.endsWith( ".tar.gz", Qt::CaseInsensitive ) ) - return 0; - // We have to filter by extensions, otherwise e.g. all Shapefile files are displayed // because OGR drive can open also .dbf, .shx. if ( myExtensions.indexOf( suffix ) < 0 ) @@ -302,30 +299,27 @@ QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem ) return 0; } - // add /vsizip/ or /vsigzip/ to path if file extension is .zip or .gz - if ( is_vsigzip ) + // fix vsifile path and name + if ( vsiPrefix != "" ) { - if ( !thePath.startsWith( "/vsigzip/" ) ) - thePath = "/vsigzip/" + thePath; - } - else if ( is_vsizip ) - { - if ( !thePath.startsWith( "/vsizip/" ) ) - thePath = "/vsizip/" + thePath; + // add vsiPrefix to path if needed + if ( !thePath.startsWith( vsiPrefix ) ) + thePath = vsiPrefix + thePath; // if this is a /vsigzip/path_to_zip.zip/file_inside_zip remove the full path from the name - if ( thePath != "/vsizip/" + parentItem->path() ) + if (( is_vsizip || is_vsitar ) && ( thePath != vsiPrefix + parentItem->path() ) ) { name = thePath; - name = name.replace( "/vsizip/" + parentItem->path() + "/", "" ); + name = name.replace( vsiPrefix + parentItem->path() + "/", "" ); } } // return a /vsizip/ item without testing if: // zipfile and scan zip == "Basic scan" // not zipfile and scan items == "Check extension" - if (( is_vsizip && scanZipSetting == "basic" ) || - ( !is_vsizip && ( settings.value( "/qgis/scanItemsInBrowser", - "extension" ).toString() == "extension" ) ) ) + if ((( is_vsizip || is_vsitar ) && scanZipSetting == "basic" ) || + ( !is_vsizip && !is_vsitar && + ( settings.value( "/qgis/scanItemsInBrowser", + "extension" ).toString() == "extension" ) ) ) { // if this is a VRT file make sure it is vector VRT to avoid duplicates if ( suffix == "vrt" ) diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index b4049e57a16..86beb6b93d5 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -264,26 +264,18 @@ QgsOgrProvider::QgsOgrProvider( QString const & uri ) // Try to open using VSIFileHandler // see http://trac.osgeo.org/gdal/wiki/UserDocs/ReadInZip - if ( mFilePath.endsWith( ".zip", Qt::CaseInsensitive ) ) + QString vsiPrefix = QgsZipItem::vsiPrefix( uri ); + if ( vsiPrefix != "" ) { // GDAL>=1.8.0 has write support for zip, but read and write operations // cannot be interleaved, so for now just use read-only. openReadOnly = true; - if ( !mFilePath.startsWith( "/vsizip/" ) ) + if ( !mFilePath.startsWith( vsiPrefix ) ) { - mFilePath = "/vsizip/" + mFilePath; + mFilePath = vsiPrefix + mFilePath; setDataSourceUri( mFilePath ); } - QgsDebugMsg( QString( "Trying /vsizip syntax, mFilePath= %1" ).arg( mFilePath ) ); - } - else if ( mFilePath.endsWith( ".gz", Qt::CaseInsensitive ) ) - { - if ( !mFilePath.startsWith( "/vsigzip/" ) ) - { - mFilePath = "/vsigzip/" + mFilePath; - setDataSourceUri( mFilePath ); - } - QgsDebugMsg( QString( "Trying /vsigzip syntax, mFilePath= %1" ).arg( mFilePath ) ); + QgsDebugMsg( QString( "Trying %1 syntax, mFilePath= %2" ).arg( vsiPrefix ).arg( mFilePath ) ); } QgsDebugMsg( "mFilePath: " + mFilePath ); @@ -1792,16 +1784,14 @@ QString createFilters( QString type ) // VSIFileHandler (.zip and .gz files) // see http://trac.osgeo.org/gdal/wiki/UserDocs/ReadInZip // Requires GDAL>=1.6.0 with libz support, let's assume we have it. - // For .zip this works only if there is one file (or dataset) in the root of the zip. - // Only tested with tiff, shape (zip) and spatialite (zip and gz). // This does not work for some file types, see VSIFileHandler doc. - // Ideally we should also add support for /vsitar/ (requires cpl_vsil_tar.cpp). #if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 1600 QSettings settings; if ( settings.value( "/qgis/scanZipInBrowser", "basic" ).toString() != "no" ) { - myFileFilters += createFileFilter_( QObject::tr( "GDAL/OGR VSIFileHandler" ), "*.zip *.gz" ); - myExtensions << "zip" << "gz"; + myFileFilters += createFileFilter_( QObject::tr( "GDAL/OGR VSIFileHandler" ), "*.zip *.gz *.tar *.tar.gz *.tgz" ); + myExtensions << "zip" << "gz" << "tar" << "tar.gz" << "tgz"; + } #endif diff --git a/tests/src/core/testziplayer.cpp b/tests/src/core/testziplayer.cpp index b4efc9c1f22..cc5991a697b 100644 --- a/tests/src/core/testziplayer.cpp +++ b/tests/src/core/testziplayer.cpp @@ -53,6 +53,7 @@ class TestZipLayer: public QObject bool testZipItem( QString myFileName, QString myChildName = "", QString myDriverName = "" ); // get layer transparency to test for .qml loading int getLayerTransparency( QString myFileName, QString myProviderKey, QString myScanZipSetting = "basic" ); + bool testZipItemTransparency( QString myFileName, QString myProviderKey, int myTarget ); private slots: @@ -65,20 +66,28 @@ class TestZipLayer: public QObject // tests // test for .zip and .gz files using all options void testPassthruVectorZip(); + void testPassthruVectorTar(); void testPassthruVectorGzip(); void testPassthruRasterZip(); + void testPassthruRasterTar(); void testPassthruRasterGzip(); // test both "Basic Scan" and "Full scan" for .zip files void testZipItemRaster(); + void testTarItemRaster(); void testZipItemVector(); + void testTarItemVector(); void testZipItemAll(); + void testTarItemAll(); // test that styles are loaded from .qml files outside zip files void testZipItemVectorTransparency(); - void testGZipItemVectorTransparency(); + void testTarItemVectorTransparency(); + void testGzipItemVectorTransparency(); void testZipItemRasterTransparency(); - void testGZipItemRasterTransparency(); + void testTarItemRasterTransparency(); + void testGzipItemRasterTransparency(); //make sure items inside subfolders can be read void testZipItemSubfolder(); + void testTarItemSubfolder(); //make sure .vrt items are loaded by proper provider (gdal/ogr) void testZipItemVRT(); }; @@ -225,6 +234,21 @@ int TestZipLayer::getLayerTransparency( QString myFileName, QString myProviderKe return myTransparency; } +bool TestZipLayer::testZipItemTransparency( QString myFileName, QString myProviderKey, int myTarget ) +{ + int myTransparency; + foreach( QString s, mScanZipSettings ) + { + myTransparency = getLayerTransparency( myFileName, myProviderKey, s ); + if ( myTransparency != myTarget ) + { + QWARN( QString( "Transparency of %1 is %2, should be %3" ).arg( myFileName ).arg( myTransparency ).arg( myTarget ).toLocal8Bit().data() ); + return false; + } + } + return true; +} + // slots void TestZipLayer::initTestCase() @@ -257,7 +281,6 @@ void TestZipLayer::cleanupTestCase() settings.setValue( "/qgis/scanZipInBrowser", mScanZipSetting ); } - void TestZipLayer::testPassthruVectorZip() { QSettings settings; @@ -275,6 +298,21 @@ void TestZipLayer::testPassthruVectorZip() } } +void TestZipLayer::testPassthruVectorTar() +{ + QSettings settings; + QString myFileName = mDataDir + "points2.tar"; +#if GDAL_VERSION_NUM < 1800 + QSKIP( "This test requires GDAL >= 1.8", SkipSingle ); +#endif + foreach( QString s, mScanZipSettings ) + { + settings.setValue( "/qgis/scanZipInBrowser", s ); + QVERIFY( s == settings.value( "/qgis/scanZipInBrowser" ).toString() ); + QVERIFY( testZipItemPassthru( myFileName, "ogr" ) ); + } +} + void TestZipLayer::testPassthruVectorGzip() { QSettings settings; @@ -300,6 +338,20 @@ void TestZipLayer::testPassthruRasterZip() } } +void TestZipLayer::testPassthruRasterTar() +{ +#if GDAL_VERSION_NUM < 1800 + QSKIP( "This test requires GDAL >= 1.8", SkipSingle ); +#endif + QSettings settings; + foreach( QString s, mScanZipSettings ) + { + settings.setValue( "/qgis/scanZipInBrowser", s ); + QVERIFY( s == settings.value( "/qgis/scanZipInBrowser" ).toString() ); + QVERIFY( testZipItemPassthru( mDataDir + "landsat_b1.tar", "gdal" ) ); + } +} + void TestZipLayer::testPassthruRasterGzip() { QSettings settings; @@ -322,6 +374,20 @@ void TestZipLayer::testZipItemRaster() } } +void TestZipLayer::testTarItemRaster() +{ +#if GDAL_VERSION_NUM < 1800 + QSKIP( "This test requires GDAL >= 1.8", SkipSingle ); +#endif + QSettings settings; + foreach( QString s, mScanZipSettings ) + { + settings.setValue( "/qgis/scanZipInBrowser", s ); + QVERIFY( s == settings.value( "/qgis/scanZipInBrowser" ).toString() ); + QVERIFY( testZipItem( mDataDir + "testtar.tgz", "landsat_b1.tif" ) ); + } +} + void TestZipLayer::testZipItemVector() { QSettings settings; @@ -333,6 +399,20 @@ void TestZipLayer::testZipItemVector() } } +void TestZipLayer::testTarItemVector() +{ +#if GDAL_VERSION_NUM < 1800 + QSKIP( "This test requires GDAL >= 1.8", SkipSingle ); +#endif + QSettings settings; + foreach( QString s, mScanZipSettings ) + { + settings.setValue( "/qgis/scanZipInBrowser", s ); + QVERIFY( s == settings.value( "/qgis/scanZipInBrowser" ).toString() ); + QVERIFY( testZipItem( mDataDir + "testtar.tgz", "points.shp" ) ); + } +} + void TestZipLayer::testZipItemAll() { // test file contains invalid items (tmp1.tif, tmp1.txt and tmp1.xml) @@ -345,55 +425,57 @@ void TestZipLayer::testZipItemAll() QVERIFY( testZipItem( mDataDir + "testzip.zip", "" ) ); } +void TestZipLayer::testTarItemAll() +{ +#if GDAL_VERSION_NUM < 1800 + QSKIP( "This test requires GDAL >= 1.8", SkipSingle ); +#endif + QSettings settings; + settings.setValue( "/qgis/scanZipInBrowser", "full" ); + QVERIFY( "full" == settings.value( "/qgis/scanZipInBrowser" ).toString() ); + QVERIFY( testZipItem( mDataDir + "testtar.tgz", "" ) ); +} void TestZipLayer::testZipItemVectorTransparency() { #if GDAL_VERSION_NUM < 1800 QSKIP( "This test requires GDAL >= 1.8", SkipSingle ); #endif - int myTarget = 250; - int myTransparency; - foreach( QString s, mScanZipSettings ) - { - myTransparency = getLayerTransparency( mDataDir + "points2.zip", "ogr", "basic" ); - QVERIFY2(( myTransparency == myTarget ), QString( "Transparency is %1, should be %2" ).arg( myTransparency ).arg( myTarget ).toLocal8Bit().data() ); - } + QVERIFY( testZipItemTransparency( mDataDir + "points2.zip", "ogr", 250 ) ); } -void TestZipLayer::testGZipItemVectorTransparency() +void TestZipLayer::testTarItemVectorTransparency() +{ +#if GDAL_VERSION_NUM < 1800 + QSKIP( "This test requires GDAL >= 1.8", SkipSingle ); +#endif + QVERIFY( testZipItemTransparency( mDataDir + "points2.tar", "ogr", 250 ) ); +} + +void TestZipLayer::testGzipItemVectorTransparency() { #if GDAL_VERSION_NUM < 1700 QSKIP( "This test requires GDAL >= 1.7", SkipSingle ); #endif - int myTarget = 250; - int myTransparency; - foreach( QString s, mScanZipSettings ) - { - myTransparency = getLayerTransparency( mDataDir + "points3.geojson.gz", "ogr", s ); - QVERIFY2(( myTransparency == myTarget ), QString( "Transparency is %1, should be %2" ).arg( myTransparency ).arg( myTarget ).toLocal8Bit().data() ); - } + QVERIFY( testZipItemTransparency( mDataDir + "points3.geojson.gz", "ogr", 250 ) ); } void TestZipLayer::testZipItemRasterTransparency() { - int myTarget = 250; - int myTransparency; - foreach( QString s, mScanZipSettings ) - { - myTransparency = getLayerTransparency( mDataDir + "landsat_b1.zip", "gdal", s ); - QVERIFY2(( myTransparency == myTarget ), QString( "Transparency is %1, should be %2" ).arg( myTransparency ).arg( myTarget ).toLocal8Bit().data() ); - } + QVERIFY( testZipItemTransparency( mDataDir + "landsat_b1.zip", "gdal", 250 ) ); } -void TestZipLayer::testGZipItemRasterTransparency() +void TestZipLayer::testTarItemRasterTransparency() { - int myTarget = 250; - int myTransparency; - foreach( QString s, mScanZipSettings ) - { - myTransparency = getLayerTransparency( mDataDir + "landsat_b1.tif.gz", "gdal", s ); - QVERIFY2(( myTransparency == myTarget ), QString( "Transparency is %1, should be %2" ).arg( myTransparency ).arg( myTarget ).toLocal8Bit().data() ); - } +#if GDAL_VERSION_NUM < 1800 + QSKIP( "This test requires GDAL >= 1.8", SkipSingle ); +#endif + QVERIFY( testZipItemTransparency( mDataDir + "landsat_b1.tar", "gdal", 250 ) ); +} + +void TestZipLayer::testGzipItemRasterTransparency() +{ + QVERIFY( testZipItemTransparency( mDataDir + "landsat_b1.tif.gz", "gdal", 250 ) ); } void TestZipLayer::testZipItemSubfolder() @@ -407,6 +489,17 @@ void TestZipLayer::testZipItemSubfolder() } } +void TestZipLayer::testTarItemSubfolder() +{ + QSettings settings; + foreach( QString s, mScanZipSettings ) + { + settings.setValue( "/qgis/scanZipInBrowser", s ); + QVERIFY( s == settings.value( "/qgis/scanZipInBrowser" ).toString() ); + QVERIFY( testZipItem( mDataDir + "testtar.tgz", "folder/folder2/landsat_b2.tif" ) ); + } +} + void TestZipLayer::testZipItemVRT() { diff --git a/tests/testdata/landsat_b1.tar b/tests/testdata/landsat_b1.tar new file mode 100644 index 00000000000..a97f57a5a51 Binary files /dev/null and b/tests/testdata/landsat_b1.tar differ diff --git a/tests/testdata/points2.tar b/tests/testdata/points2.tar new file mode 100644 index 00000000000..57def45f18d Binary files /dev/null and b/tests/testdata/points2.tar differ diff --git a/tests/testdata/testtar.tgz b/tests/testdata/testtar.tgz new file mode 100644 index 00000000000..35fc1f4d812 Binary files /dev/null and b/tests/testdata/testtar.tgz differ