QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgsfileutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsfileutils.cpp
3  ---------------------
4  begin : November 2017
5  copyright : (C) 2017 by Etienne Trimaille
6  email : etienne.trimaille 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 #include "qgsfileutils.h"
16 #include "qgis.h"
17 #include "qgsexception.h"
18 #include "qgsconfig.h"
19 #include <QObject>
20 #include <QRegularExpression>
21 #include <QFileInfo>
22 #include <QDir>
23 #include <QSet>
24 #include <QDirIterator>
25 
26 #ifdef MSVC
27 #include <Windows.h>
28 #include <ShlObj.h>
29 #pragma comment(lib,"Shell32.lib")
30 #endif
31 
32 QString QgsFileUtils::representFileSize( qint64 bytes )
33 {
34  QStringList list;
35  list << QObject::tr( "KB" ) << QObject::tr( "MB" ) << QObject::tr( "GB" ) << QObject::tr( "TB" );
36 
37  QStringListIterator i( list );
38  QString unit = QObject::tr( "bytes" );
39 
40  while ( bytes >= 1024.0 && i.hasNext() )
41  {
42  unit = i.next();
43  bytes /= 1024.0;
44  }
45  return QStringLiteral( "%1 %2" ).arg( QString::number( bytes ), unit );
46 }
47 
48 QStringList QgsFileUtils::extensionsFromFilter( const QString &filter )
49 {
50  const QRegularExpression rx( QStringLiteral( "\\*\\.([a-zA-Z0-9]+)" ) );
51  QStringList extensions;
52  QRegularExpressionMatchIterator matches = rx.globalMatch( filter );
53 
54  while ( matches.hasNext() )
55  {
56  const QRegularExpressionMatch match = matches.next();
57  if ( match.hasMatch() )
58  {
59  QStringList newExtensions = match.capturedTexts();
60  newExtensions.pop_front(); // remove whole match
61  extensions.append( newExtensions );
62  }
63  }
64  return extensions;
65 }
66 
67 QString QgsFileUtils::wildcardsFromFilter( const QString &filter )
68 {
69  const QRegularExpression globPatternsRx( QStringLiteral( ".*\\((.*?)\\)$" ) );
70  const QRegularExpressionMatch matches = globPatternsRx.match( filter );
71  if ( matches.hasMatch() )
72  return matches.captured( 1 );
73  else
74  return QString();
75 }
76 
77 bool QgsFileUtils::fileMatchesFilter( const QString &fileName, const QString &filter )
78 {
79  QFileInfo fi( fileName );
80  const QString name = fi.fileName();
81  const QStringList parts = filter.split( QStringLiteral( ";;" ) );
82  for ( const QString &part : parts )
83  {
84 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
85  const QStringList globPatterns = wildcardsFromFilter( part ).split( ' ', QString::SkipEmptyParts );
86 #else
87  const QStringList globPatterns = wildcardsFromFilter( part ).split( ' ', Qt::SkipEmptyParts );
88 #endif
89  for ( const QString &glob : globPatterns )
90  {
91  const QString re = QRegularExpression::wildcardToRegularExpression( glob );
92 
93  const QRegularExpression globRx( re );
94  if ( globRx.match( name ).hasMatch() )
95  return true;
96  }
97  }
98  return false;
99 }
100 
101 QString QgsFileUtils::ensureFileNameHasExtension( const QString &f, const QStringList &extensions )
102 {
103  if ( extensions.empty() || f.isEmpty() )
104  return f;
105 
106  QString fileName = f;
107  bool hasExt = false;
108  for ( const QString &extension : std::as_const( extensions ) )
109  {
110  const QString extWithDot = extension.startsWith( '.' ) ? extension : '.' + extension;
111  if ( fileName.endsWith( extWithDot, Qt::CaseInsensitive ) )
112  {
113  hasExt = true;
114  break;
115  }
116  }
117 
118  if ( !hasExt )
119  {
120  const QString extension = extensions.at( 0 );
121  const QString extWithDot = extension.startsWith( '.' ) ? extension : '.' + extension;
122  fileName += extWithDot;
123  }
124 
125  return fileName;
126 }
127 
128 QString QgsFileUtils::addExtensionFromFilter( const QString &fileName, const QString &filter )
129 {
130  const QStringList extensions = extensionsFromFilter( filter );
131  return ensureFileNameHasExtension( fileName, extensions );
132 }
133 
134 QString QgsFileUtils::stringToSafeFilename( const QString &string )
135 {
136  QRegularExpression rx( QStringLiteral( "[/\\\\\\?%\\*\\:\\|\"<>]" ) );
137  QString s = string;
138  s.replace( rx, QStringLiteral( "_" ) );
139  return s;
140 }
141 
142 QString QgsFileUtils::findClosestExistingPath( const QString &path )
143 {
144  if ( path.isEmpty() )
145  return QString();
146 
147  QDir currentPath;
148  QFileInfo fi( path );
149  if ( fi.isFile() )
150  currentPath = fi.dir();
151  else
152  currentPath = QDir( path );
153 
154  QSet< QString > visited;
155  while ( !currentPath.exists() )
156  {
157  const QString parentPath = QDir::cleanPath( currentPath.absolutePath() + QStringLiteral( "/.." ) );
158  if ( visited.contains( parentPath ) )
159  return QString(); // break circular links
160 
161  if ( parentPath.isEmpty() || parentPath == QLatin1String( "." ) )
162  return QString();
163  currentPath = QDir( parentPath );
164  visited << parentPath;
165  }
166 
167  const QString res = QDir::cleanPath( currentPath.absolutePath() );
168 
169  if ( res == QDir::currentPath() )
170  return QString(); // avoid default to binary folder if a filename alone is specified
171 
172  return res == QLatin1String( "." ) ? QString() : res;
173 }
174 
175 QStringList QgsFileUtils::findFile( const QString &file, const QString &basePath, int maxClimbs, int searchCeilling, const QString &currentDir )
176 {
177  int depth = 0;
178  QString originalFolder;
179  QDir folder;
180  const QString fileName( basePath.isEmpty() ? QFileInfo( file ).fileName() : file );
181  const QString baseFolder( basePath.isEmpty() ? QFileInfo( file ).path() : basePath );
182 
183  if ( QFileInfo( baseFolder ).isDir() )
184  {
185  folder = QDir( baseFolder ) ;
186  originalFolder = folder.absolutePath();
187  }
188  else // invalid folder or file path
189  {
190  folder = QDir( QFileInfo( baseFolder ).absolutePath() );
191  originalFolder = folder.absolutePath();
192  }
193 
194  QStringList searchedFolder = QStringList();
195  QString existingBase;
196  QString backupDirectory = QDir::currentPath();
197  QStringList foundFiles;
198 
199  if ( !currentDir.isEmpty() && backupDirectory != currentDir && QDir( currentDir ).exists() )
200  QDir::setCurrent( currentDir );
201 
202  // find the nearest existing folder
203  while ( !folder.exists() && folder.absolutePath().count( '/' ) > searchCeilling )
204  {
205 
206  existingBase = folder.path();
207  if ( !folder.cdUp() )
208  folder = QFileInfo( existingBase ).absoluteDir(); // using fileinfo to move up one level
209 
210  depth += 1;
211 
212  if ( depth > ( maxClimbs + 4 ) ) //break early when no folders can be found
213  break;
214  }
215  bool folderExists = folder.exists();
216 
217  if ( depth > maxClimbs )
218  maxClimbs = depth;
219 
220  if ( folder.absolutePath().count( '/' ) < searchCeilling )
221  searchCeilling = folder.absolutePath().count( '/' ) - 1;
222 
223  while ( depth <= maxClimbs && folderExists && folder.absolutePath().count( '/' ) >= searchCeilling )
224  {
225 
226  QDirIterator localFinder( folder.path(), QStringList() << fileName, QDir::Files, QDirIterator::NoIteratorFlags );
227  searchedFolder.append( folder.absolutePath() );
228  if ( localFinder.hasNext() )
229  {
230  foundFiles << localFinder.next();
231  return foundFiles;
232  }
233 
234 
235  const QFileInfoList subdirs = folder.entryInfoList( QDir::AllDirs );
236  for ( const QFileInfo &subdir : subdirs )
237  {
238  if ( ! searchedFolder.contains( subdir.absolutePath() ) )
239  {
240  QDirIterator subDirFinder( subdir.path(), QStringList() << fileName, QDir::Files, QDirIterator::Subdirectories );
241  if ( subDirFinder.hasNext() )
242  {
243  QString possibleFile = subDirFinder.next();
244  if ( !subDirFinder.hasNext() )
245  {
246  foundFiles << possibleFile;
247  return foundFiles;
248  }
249 
250  foundFiles << possibleFile;
251  while ( subDirFinder.hasNext() )
252  {
253  foundFiles << subDirFinder.next();
254  }
255  return foundFiles;
256  }
257  }
258  }
259  depth += 1;
260 
261  if ( depth > maxClimbs )
262  break;
263 
264  folderExists = folder.cdUp();
265  }
266 
267  if ( QDir::currentPath() == currentDir && currentDir != backupDirectory )
268  QDir::setCurrent( backupDirectory );
269 
270  return foundFiles;
271 }
272 
273 #ifdef MSVC
274 std::unique_ptr< wchar_t[] > pathToWChar( const QString &path )
275 {
276  const QString nativePath = QDir::toNativeSeparators( path );
277 
278  std::unique_ptr< wchar_t[] > pathArray( new wchar_t[static_cast< uint>( nativePath.length() + 1 )] );
279  nativePath.toWCharArray( pathArray.get() );
280  pathArray[static_cast< size_t >( nativePath.length() )] = 0;
281  return pathArray;
282 }
283 #endif
284 
286 {
287 #ifdef MSVC
288  auto pathType = [ = ]( const QString & path ) -> DriveType
289  {
290  std::unique_ptr< wchar_t[] > pathArray = pathToWChar( path );
291  const UINT type = GetDriveTypeW( pathArray.get() );
292  switch ( type )
293  {
294  case DRIVE_UNKNOWN:
296 
297  case DRIVE_NO_ROOT_DIR:
299 
300  case DRIVE_REMOVABLE:
302 
303  case DRIVE_FIXED:
304  return Qgis::DriveType::Fixed;
305 
306  case DRIVE_REMOTE:
308 
309  case DRIVE_CDROM:
310  return Qgis::DriveType::CdRom;
311 
312  case DRIVE_RAMDISK:
314  }
315 
316  return Unknown;
317 
318  };
319 
320  const QString originalPath = QDir::cleanPath( path );
321  QString currentPath = originalPath;
322  QString prevPath;
323  while ( currentPath != prevPath )
324  {
325  prevPath = currentPath;
326  currentPath = QFileInfo( currentPath ).path();
327  const DriveType type = pathType( currentPath );
328  if ( type != Unknown && type != Invalid )
329  return type;
330  }
331  return Unknown;
332 
333 #else
334  ( void )path;
335  throw QgsNotSupportedException( QStringLiteral( "Determining drive type is not supported on this platform" ) );
336 #endif
337 }
338 
339 bool QgsFileUtils::pathIsSlowDevice( const QString &path )
340 {
341 #ifdef ENABLE_TESTS
342  if ( path.contains( QLatin1String( "fake_slow_path_for_unit_tests" ) ) )
343  return true;
344 #endif
345 
346  try
347  {
348  const Qgis::DriveType type = driveType( path );
349  switch ( type )
350  {
355  return false;
356 
360  return true;
361  }
362  }
363  catch ( QgsNotSupportedException & )
364  {
365 
366  }
367  return false;
368 }
DriveType
Drive types.
Definition: qgis.h:337
@ Fixed
Fixed drive.
@ Invalid
Invalid path.
@ Unknown
Unknown type.
@ RamDisk
RAM disk.
@ Removable
Removable drive.
@ Remote
Remote drive.
static QString stringToSafeFilename(const QString &string)
Converts a string to a safe filename, replacing characters which are not safe for filenames with an '...
static QStringList findFile(const QString &file, const QString &basepath=QString(), int maxClimbs=4, int searchCeiling=4, const QString &currentDir=QString())
Will check basepath in an outward spiral up to maxClimbs levels to check if file exists.
static QString wildcardsFromFilter(const QString &filter)
Given a filter string like "GeoTIFF Files (*.tiff *.tif)", extracts the wildcard portion of this filt...
static bool pathIsSlowDevice(const QString &path)
Returns true if the specified path is assumed to reside on a slow device, e.g.
static bool fileMatchesFilter(const QString &fileName, const QString &filter)
Returns true if the given fileName matches a file filter string.
static Qgis::DriveType driveType(const QString &path) SIP_THROW(QgsNotSupportedException)
Returns the drive type for the given path.
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
static QString representFileSize(qint64 bytes)
Returns the human size from bytes.
static QString addExtensionFromFilter(const QString &fileName, const QString &filter)
Ensures that a fileName ends with an extension from the specified filter string.
static QString findClosestExistingPath(const QString &path)
Returns the top-most existing folder from path.
static QStringList extensionsFromFilter(const QString &filter)
Returns a list of the extensions contained within a file filter string.
Custom exception class which is raised when an operation is not supported.
Definition: qgsexception.h:118