Skip to content

Commit

Permalink
Add QgsFileUtils::findClosestExistingPath to find the closest
Browse files Browse the repository at this point in the history
existing folder match for a path

E.g. if \a path is "/home/user/projects/2018/P4343"
and "/home/user/projects" exists but no "2018" subfolder
exists, then the function will return "/home/user/projects".

(Suprisingly no existing Qt method for this!)
  • Loading branch information
nyalldawson committed Jun 22, 2018
1 parent 1572314 commit 635f8ad
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 2 deletions.
8 changes: 8 additions & 0 deletions python/core/auto_generated/qgsfileutils.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ for filenames with an '_' character.
.. warning::

This method strips slashes from the filename, so it is safe to call with file names only, not complete paths.
%End

static QString findClosestExistingPath( const QString &path );
%Docstring
Returns the top-most existing folder from ``path``. E.g. if ``path`` is "/home/user/projects/2018/P4343"
and "/home/user/projects" exists but no "2018" subfolder exists, then the function will return "/home/user/projects".

.. versionadded:: 3.2
%End
};

Expand Down
27 changes: 25 additions & 2 deletions src/core/qgsfileutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include "qgsfileutils.h"
#include <QObject>
#include <QRegularExpression>
#include <QFileInfo>
#include <QDir>

QString QgsFileUtils::representFileSize( qint64 bytes )
{
Expand All @@ -29,7 +31,7 @@ QString QgsFileUtils::representFileSize( qint64 bytes )
unit = i.next();
bytes /= 1024.0;
}
return QString( "%1 %2" ).arg( QString::number( bytes ), unit );
return QStringLiteral( "%1 %2" ).arg( QString::number( bytes ), unit );
}

QStringList QgsFileUtils::extensionsFromFilter( const QString &filter )
Expand Down Expand Up @@ -86,8 +88,29 @@ QString QgsFileUtils::addExtensionFromFilter( const QString &fileName, const QSt

QString QgsFileUtils::stringToSafeFilename( const QString &string )
{
QRegularExpression rx( "[/\\\\\\?%\\*\\:\\|\"<>]" );
QRegularExpression rx( QStringLiteral( "[/\\\\\\?%\\*\\:\\|\"<>]" ) );
QString s = string;
s.replace( rx, QStringLiteral( "_" ) );
return s;
}

QString QgsFileUtils::findClosestExistingPath( const QString &path )
{
QDir currentPath;
QFileInfo fi( path );
if ( fi.isFile() )
currentPath = fi.dir();
else
currentPath = QDir( path );

while ( !currentPath.exists() )
{
const QString parentPath = QDir::cleanPath( currentPath.path() + QStringLiteral( "/.." ) );
if ( parentPath.isEmpty() || parentPath == '.' )
return QString();
currentPath = QDir( parentPath );
}

const QString res = QDir::cleanPath( currentPath.path() );
return res == '.' ? QString() : res;
}
8 changes: 8 additions & 0 deletions src/core/qgsfileutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ class CORE_EXPORT QgsFileUtils
* \warning This method strips slashes from the filename, so it is safe to call with file names only, not complete paths.
*/
static QString stringToSafeFilename( const QString &string );

/**
* Returns the top-most existing folder from \a path. E.g. if \a path is "/home/user/projects/2018/P4343"
* and "/home/user/projects" exists but no "2018" subfolder exists, then the function will return "/home/user/projects".
*
* \since QGIS 3.2

This comment has been minimized.

Copy link
@rouault

rouault Jun 22, 2018

Contributor

3.4 rather ?

*/
static QString findClosestExistingPath( const QString &path );
};

#endif // QGSFILEUTILS_H
31 changes: 31 additions & 0 deletions tests/src/python/test_qgsfileutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import qgis # NOQA

import tempfile
import os
from qgis.core import QgsFileUtils
from qgis.testing import unittest

Expand Down Expand Up @@ -61,6 +63,35 @@ def testStringToSafeFilename(self):
QgsFileUtils.stringToSafeFilename('rendered map_final? rev (12-03-1017)_real/\\?%*:|"<>.tif'),
'rendered map_final_ rev (12-03-1017)_real__________.tif')

def testFindClosestExistingPath(self):
self.assertEqual(QgsFileUtils.findClosestExistingPath(''), '')
self.assertEqual(QgsFileUtils.findClosestExistingPath('.'), '')
self.assertEqual(QgsFileUtils.findClosestExistingPath('a_very_unlikely_path_to_really_exist/because/no_one_would_have_a_folder_called/MapInfo is the bestest/'), '')
# sorry anyone not on linux!
self.assertEqual(QgsFileUtils.findClosestExistingPath('/usr/youve_been_hacked/by_the_l77t_krew'), '/usr')

base_path = tempfile.mkdtemp()
file = os.path.join(base_path, 'test.csv')
with open(file, 'wt') as f:
f.write('\n')

self.assertEqual(QgsFileUtils.findClosestExistingPath(os.path.join(base_path, 'a file name.bmp')), base_path) # non-existant file
self.assertEqual(QgsFileUtils.findClosestExistingPath(file), base_path) # real file!
self.assertEqual(QgsFileUtils.findClosestExistingPath(os.path.join(base_path, 'non/existant/subfolder')), base_path)

sub_folder1 = os.path.join(base_path, 'subfolder1')
os.mkdir(sub_folder1)
sub_folder2 = os.path.join(sub_folder1, 'subfolder2')
os.mkdir(sub_folder2)
bad_sub_folder = os.path.join(sub_folder2, 'nooo')
self.assertEqual(QgsFileUtils.findClosestExistingPath(bad_sub_folder), sub_folder2)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2), sub_folder2)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/.'), sub_folder2)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/..'), sub_folder1)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/../ddddddd'), sub_folder1)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/../subfolder2'), sub_folder2)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/../subfolder2/zxcv/asfdasd'), sub_folder2)


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

0 comments on commit 635f8ad

Please sign in to comment.