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