QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 <QObject>
18 #include <QRegularExpression>
19 #include <QFileInfo>
20 #include <QDir>
21 #include <QSet>
22 #include <QDirIterator>
23 
24 QString QgsFileUtils::representFileSize( qint64 bytes )
25 {
26  QStringList list;
27  list << QObject::tr( "KB" ) << QObject::tr( "MB" ) << QObject::tr( "GB" ) << QObject::tr( "TB" );
28 
29  QStringListIterator i( list );
30  QString unit = QObject::tr( "bytes" );
31 
32  while ( bytes >= 1024.0 && i.hasNext() )
33  {
34  unit = i.next();
35  bytes /= 1024.0;
36  }
37  return QStringLiteral( "%1 %2" ).arg( QString::number( bytes ), unit );
38 }
39 
40 QStringList QgsFileUtils::extensionsFromFilter( const QString &filter )
41 {
42  const QRegularExpression rx( QStringLiteral( "\\*\\.([a-zA-Z0-9]+)" ) );
43  QStringList extensions;
44  QRegularExpressionMatchIterator matches = rx.globalMatch( filter );
45 
46  while ( matches.hasNext() )
47  {
48  const QRegularExpressionMatch match = matches.next();
49  if ( match.hasMatch() )
50  {
51  QStringList newExtensions = match.capturedTexts();
52  newExtensions.pop_front(); // remove whole match
53  extensions.append( newExtensions );
54  }
55  }
56  return extensions;
57 }
58 
59 QString QgsFileUtils::wildcardsFromFilter( const QString &filter )
60 {
61  const QRegularExpression globPatternsRx( QStringLiteral( ".*\\((.*?)\\)$" ) );
62  const QRegularExpressionMatch matches = globPatternsRx.match( filter );
63  if ( matches.hasMatch() )
64  return matches.captured( 1 );
65  else
66  return QString();
67 }
68 
69 bool QgsFileUtils::fileMatchesFilter( const QString &fileName, const QString &filter )
70 {
71  QFileInfo fi( fileName );
72  const QString name = fi.fileName();
73  const QStringList parts = filter.split( QStringLiteral( ";;" ) );
74  for ( const QString &part : parts )
75  {
76 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
77  const QStringList globPatterns = wildcardsFromFilter( part ).split( ' ', QString::SkipEmptyParts );
78 #else
79  const QStringList globPatterns = wildcardsFromFilter( part ).split( ' ', Qt::SkipEmptyParts );
80 #endif
81  for ( const QString &glob : globPatterns )
82  {
83 #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
84  const QString re = QRegularExpression::wildcardToRegularExpression( glob );
85 
86  const QRegularExpression globRx( re );
87  if ( globRx.match( name ).hasMatch() )
88  return true;
89 #else
90  QRegExp rx( glob );
91  rx.setPatternSyntax( QRegExp::Wildcard );
92  if ( rx.indexIn( name ) != -1 )
93  return true;
94 #endif
95  }
96  }
97  return false;
98 }
99 
100 QString QgsFileUtils::ensureFileNameHasExtension( const QString &f, const QStringList &extensions )
101 {
102  if ( extensions.empty() || f.isEmpty() )
103  return f;
104 
105  QString fileName = f;
106  bool hasExt = false;
107  for ( const QString &extension : qgis::as_const( extensions ) )
108  {
109  const QString extWithDot = extension.startsWith( '.' ) ? extension : '.' + extension;
110  if ( fileName.endsWith( extWithDot, Qt::CaseInsensitive ) )
111  {
112  hasExt = true;
113  break;
114  }
115  }
116 
117  if ( !hasExt )
118  {
119  const QString extension = extensions.at( 0 );
120  const QString extWithDot = extension.startsWith( '.' ) ? extension : '.' + extension;
121  fileName += extWithDot;
122  }
123 
124  return fileName;
125 }
126 
127 QString QgsFileUtils::addExtensionFromFilter( const QString &fileName, const QString &filter )
128 {
129  const QStringList extensions = extensionsFromFilter( filter );
130  return ensureFileNameHasExtension( fileName, extensions );
131 }
132 
133 QString QgsFileUtils::stringToSafeFilename( const QString &string )
134 {
135  QRegularExpression rx( QStringLiteral( "[/\\\\\\?%\\*\\:\\|\"<>]" ) );
136  QString s = string;
137  s.replace( rx, QStringLiteral( "_" ) );
138  return s;
139 }
140 
141 QString QgsFileUtils::findClosestExistingPath( const QString &path )
142 {
143  if ( path.isEmpty() )
144  return QString();
145 
146  QDir currentPath;
147  QFileInfo fi( path );
148  if ( fi.isFile() )
149  currentPath = fi.dir();
150  else
151  currentPath = QDir( path );
152 
153  QSet< QString > visited;
154  while ( !currentPath.exists() )
155  {
156  const QString parentPath = QDir::cleanPath( currentPath.absolutePath() + QStringLiteral( "/.." ) );
157  if ( visited.contains( parentPath ) )
158  return QString(); // break circular links
159 
160  if ( parentPath.isEmpty() || parentPath == QLatin1String( "." ) )
161  return QString();
162  currentPath = QDir( parentPath );
163  visited << parentPath;
164  }
165 
166  const QString res = QDir::cleanPath( currentPath.absolutePath() );
167 
168  if ( res == QDir::currentPath() )
169  return QString(); // avoid default to binary folder if a filename alone is specified
170 
171  return res == QLatin1String( "." ) ? QString() : res;
172 }
173 
174 QStringList QgsFileUtils::findFile( const QString &file, const QString &basePath, int maxClimbs, int searchCeilling, const QString &currentDir )
175 {
176  int depth = 0;
177  QString originalFolder;
178  QDir folder;
179  const QString fileName( basePath.isEmpty() ? QFileInfo( file ).fileName() : file );
180  const QString baseFolder( basePath.isEmpty() ? QFileInfo( file ).path() : basePath );
181 
182  if ( QFileInfo( baseFolder ).isDir() )
183  {
184  folder = QDir( baseFolder ) ;
185  originalFolder = folder.absolutePath();
186  }
187  else // invalid folder or file path
188  {
189  folder = QDir( QFileInfo( baseFolder ).absolutePath() );
190  originalFolder = folder.absolutePath();
191  }
192 
193  QStringList searchedFolder = QStringList();
194  QString existingBase;
195  QString backupDirectory = QDir::currentPath();
196  QStringList foundFiles;
197 
198  if ( !currentDir.isEmpty() && backupDirectory != currentDir && QDir( currentDir ).exists() )
199  QDir::setCurrent( currentDir );
200 
201  // find the nearest existing folder
202  while ( !folder.exists() && folder.absolutePath().count( '/' ) > searchCeilling )
203  {
204 
205  existingBase = folder.path();
206  if ( !folder.cdUp() )
207  folder = QFileInfo( existingBase ).absoluteDir(); // using fileinfo to move up one level
208 
209  depth += 1;
210 
211  if ( depth > ( maxClimbs + 4 ) ) //break early when no folders can be found
212  break;
213  }
214  bool folderExists = folder.exists();
215 
216  if ( depth > maxClimbs )
217  maxClimbs = depth;
218 
219  if ( folder.absolutePath().count( '/' ) < searchCeilling )
220  searchCeilling = folder.absolutePath().count( '/' ) - 1;
221 
222  while ( depth <= maxClimbs && folderExists && folder.absolutePath().count( '/' ) >= searchCeilling )
223  {
224 
225  QDirIterator localFinder( folder.path(), QStringList() << fileName, QDir::Files, QDirIterator::NoIteratorFlags );
226  searchedFolder.append( folder.absolutePath() );
227  if ( localFinder.hasNext() )
228  {
229  foundFiles << localFinder.next();
230  return foundFiles;
231  }
232 
233 
234  const QFileInfoList subdirs = folder.entryInfoList( QDir::AllDirs );
235  for ( const QFileInfo &subdir : subdirs )
236  {
237  if ( ! searchedFolder.contains( subdir.absolutePath() ) )
238  {
239  QDirIterator subDirFinder( subdir.path(), QStringList() << fileName, QDir::Files, QDirIterator::Subdirectories );
240  if ( subDirFinder.hasNext() )
241  {
242  QString possibleFile = subDirFinder.next();
243  if ( !subDirFinder.hasNext() )
244  {
245  foundFiles << possibleFile;
246  return foundFiles;
247  }
248 
249  foundFiles << possibleFile;
250  while ( subDirFinder.hasNext() )
251  {
252  foundFiles << subDirFinder.next();
253  }
254  return foundFiles;
255  }
256  }
257  }
258  depth += 1;
259 
260  if ( depth > maxClimbs )
261  break;
262 
263  folderExists = folder.cdUp();
264  }
265 
266  if ( QDir::currentPath() == currentDir && currentDir != backupDirectory )
267  QDir::setCurrent( backupDirectory );
268 
269  return foundFiles;
270 }
271 
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 fileMatchesFilter(const QString &fileName, const QString &filter)
Returns true if the given fileName matches a file filter string.
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.