Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add QgsFileUtils.sidecarFilesForPath function, which returns all
sidecar files which exist for a given file path
  • Loading branch information
nyalldawson committed Aug 10, 2021
1 parent 512b67e commit ee0c050
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 0 deletions.
20 changes: 20 additions & 0 deletions python/core/auto_generated/qgsfileutils.sip.in
Expand Up @@ -133,6 +133,26 @@ Returns ``True`` if the specified ``path`` is assumed to reside on a slow device
network drive or other non-fixed device.

.. versionadded:: 3.20
%End

static QSet< QString > sidecarFilesForPath( const QString &path );
%Docstring
Returns a list of the sidecar files which exist for the dataset a the specified ``path``.

In this context a sidecar file is defined as a file which shares the same base filename
as a dataset, but which differs in file extension. It defines the list of additional
files which must be renamed or deleted alongside the main file associated with the
dataset in order to completely rename/delete the dataset.

For instance, if ``path`` specified a .shp file then the corresponding .dbf, .idx
and .prj files would be returned (amongst others).

.. note::

QGIS metadata files (.qmd) and map layer styling files (.qml) are NOT included
in the returned list.

.. versionadded:: 3.22
%End

};
Expand Down
23 changes: 23 additions & 0 deletions src/core/qgsfileutils.cpp
Expand Up @@ -16,6 +16,9 @@
#include "qgis.h"
#include "qgsexception.h"
#include "qgsconfig.h"
#include "qgsproviderregistry.h"
#include "qgsprovidermetadata.h"

#include <QObject>
#include <QRegularExpression>
#include <QFileInfo>
Expand Down Expand Up @@ -366,3 +369,23 @@ bool QgsFileUtils::pathIsSlowDevice( const QString &path )
}
return false;
}

QSet<QString> QgsFileUtils::sidecarFilesForPath( const QString &path )
{
QSet< QString > res;
const QStringList providers = QgsProviderRegistry::instance()->providerList();
for ( const QString &provider : providers )
{
const QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata( provider );
if ( metadata->providerCapabilities() & QgsProviderMetadata::FileBasedUris )
{
const QStringList possibleSidecars = metadata->sidecarFilesForUri( path );
for ( const QString &possibleSidecar : possibleSidecars )
{
if ( QFile::exists( possibleSidecar ) )
res.insert( possibleSidecar );
}
}
}
return res;
}
18 changes: 18 additions & 0 deletions src/core/qgsfileutils.h
Expand Up @@ -137,6 +137,24 @@ class CORE_EXPORT QgsFileUtils
*/
static bool pathIsSlowDevice( const QString &path );

/**
* Returns a list of the sidecar files which exist for the dataset a the specified \a path.
*
* In this context a sidecar file is defined as a file which shares the same base filename
* as a dataset, but which differs in file extension. It defines the list of additional
* files which must be renamed or deleted alongside the main file associated with the
* dataset in order to completely rename/delete the dataset.
*
* For instance, if \a path specified a .shp file then the corresponding .dbf, .idx
* and .prj files would be returned (amongst others).
*
* \note QGIS metadata files (.qmd) and map layer styling files (.qml) are NOT included
* in the returned list.
*
* \since QGIS 3.22
*/
static QSet< QString > sidecarFilesForPath( const QString &path );

};

#endif // QGSFILEUTILS_H
35 changes: 35 additions & 0 deletions tests/src/python/test_qgsfileutils.py
Expand Up @@ -16,6 +16,7 @@
import os
from qgis.core import QgsFileUtils
from qgis.testing import unittest
from utilities import unitTestDataPath


class TestQgsFileUtils(unittest.TestCase):
Expand Down Expand Up @@ -183,6 +184,40 @@ def testAutoFinder(self):
files = QgsFileUtils.findFile(filename, os.path.join(nest, 'nest2'), 2, 4)
self.assertEqual(files[0], os.path.join(nest, filename).replace(os.sep, '/'))

def test_sidecar_files_for_path(self):
"""
Test QgsFileUtils.sidecarFilesForPath
"""
# file which doesn't exist
self.assertFalse(QgsFileUtils.sidecarFilesForPath('/not a valid path'))
# a directory
self.assertFalse(QgsFileUtils.sidecarFilesForPath(unitTestDataPath()))
# not a spatial data file
self.assertFalse(QgsFileUtils.sidecarFilesForPath(f'{unitTestDataPath()}/kbs.qgs'))

# shapefile
self.assertEqual(QgsFileUtils.sidecarFilesForPath(f'{unitTestDataPath()}/lines.shp'),
{f'{unitTestDataPath()}/lines.shx', f'{unitTestDataPath()}/lines.dbf',
f'{unitTestDataPath()}/lines.prj'})
# gpkg
self.assertFalse(QgsFileUtils.sidecarFilesForPath(f'{unitTestDataPath()}/mixed_layers.gpkg'))

# MapInfo TAB file
self.assertEqual(QgsFileUtils.sidecarFilesForPath(f'{unitTestDataPath()}/ogr_types.tab'),
{f'{unitTestDataPath()}/ogr_types.dat', f'{unitTestDataPath()}/ogr_types.id',
f'{unitTestDataPath()}/ogr_types.map'})

# GML
self.assertEqual(QgsFileUtils.sidecarFilesForPath(f'{unitTestDataPath()}/invalidgeometries.gml'),
{f'{unitTestDataPath()}/invalidgeometries.gfs'})

# netcdf
self.assertEqual(QgsFileUtils.sidecarFilesForPath(f'{unitTestDataPath()}/landsat2.nc'),
{f'{unitTestDataPath()}/landsat2.nc.aux.xml'})
# tif
self.assertEqual(QgsFileUtils.sidecarFilesForPath(f'{unitTestDataPath()}/ALLINGES_RGF93_CC46_1_1.tif'),
{f'{unitTestDataPath()}/ALLINGES_RGF93_CC46_1_1.tfw'})


if __name__ == '__main__':
unittest.main()

0 comments on commit ee0c050

Please sign in to comment.