25#include <QDirIterator>
28#include <QRegularExpression>
32using namespace Qt::StringLiterals;
36#include <sys/resource.h>
44#pragma comment( lib, "Shell32.lib" )
50 list << QObject::tr(
"KB" ) << QObject::tr(
"MB" ) << QObject::tr(
"GB" ) << QObject::tr(
"TB" );
52 QStringListIterator i( list );
53 QString unit = QObject::tr(
"B" );
55 double fileSize = bytes;
56 while ( fileSize >= 1024.0 && i.hasNext() )
61 return u
"%1 %2"_s.arg( QString::number( fileSize,
'f', bytes >= 1048576 ? 2 : 0 ), unit );
66 const thread_local QRegularExpression rx( u
"\\*\\.([a-zA-Z0-9\\.]+)"_s );
67 QStringList extensions;
68 QRegularExpressionMatchIterator matches = rx.globalMatch( filter );
70 while ( matches.hasNext() )
72 const QRegularExpressionMatch match = matches.next();
73 if ( match.hasMatch() )
75 QStringList newExtensions = match.capturedTexts();
76 newExtensions.pop_front();
77 extensions.append( newExtensions );
85 const thread_local QRegularExpression globPatternsRx( u
".*\\((.*?)\\)$"_s );
86 const QRegularExpressionMatch matches = globPatternsRx.match( filter );
87 if ( matches.hasMatch() )
88 return matches.captured( 1 );
95 QFileInfo fi( fileName );
96 const QString name = fi.fileName();
97 const QStringList parts = filter.split( u
";;"_s );
98 for (
const QString &part : parts )
100 const QStringList globPatterns =
wildcardsFromFilter( part ).split(
' ', Qt::SkipEmptyParts );
101 for (
const QString &glob : globPatterns )
103 const QString re = QRegularExpression::wildcardToRegularExpression( glob );
105 const QRegularExpression globRx( re );
106 if ( globRx.match( name ).hasMatch() )
115 if ( extensions.empty() || f.isEmpty() )
118 QString fileName = f;
120 for (
const QString &extension : std::as_const( extensions ) )
122 const QString extWithDot = extension.startsWith(
'.' ) ? extension :
'.' + extension;
123 if ( fileName.endsWith( extWithDot, Qt::CaseInsensitive ) )
132 const QString extension = extensions.at( 0 );
133 const QString extWithDot = extension.startsWith(
'.' ) ? extension :
'.' + extension;
134 fileName += extWithDot;
148 const thread_local QRegularExpression rx( u
"[/\\\\\\?%\\*\\:\\|\"<>]"_s );
150 s.replace( rx, u
"_"_s );
156 if ( path.isEmpty() )
160 QFileInfo fi( path );
162 currentPath = fi.dir();
164 currentPath = QDir( path );
166 QSet< QString > visited;
167 while ( !currentPath.exists() )
169 const QString parentPath = QDir::cleanPath( currentPath.absolutePath() + u
"/.."_s );
170 if ( visited.contains( parentPath ) )
173 if ( parentPath.isEmpty() || parentPath ==
"."_L1 )
175 currentPath = QDir( parentPath );
176 visited << parentPath;
179 const QString res = QDir::cleanPath( currentPath.absolutePath() );
181 if ( res == QDir::currentPath() )
184 return res ==
"."_L1 ? QString() : res;
187QStringList
QgsFileUtils::findFile(
const QString &file,
const QString &basePath,
int maxClimbs,
int searchCeilling,
const QString ¤tDir )
190 QString originalFolder;
192 const QString fileName( basePath.isEmpty() ? QFileInfo( file ).fileName() : file );
193 const QString baseFolder( basePath.isEmpty() ? QFileInfo( file ).path() : basePath );
195 if ( QFileInfo( baseFolder ).isDir() )
197 folder = QDir( baseFolder );
198 originalFolder = folder.absolutePath();
202 folder = QDir( QFileInfo( baseFolder ).absolutePath() );
203 originalFolder = folder.absolutePath();
206 QStringList searchedFolder = QStringList();
207 QString existingBase;
208 QString backupDirectory = QDir::currentPath();
209 QStringList foundFiles;
211 if ( !currentDir.isEmpty() && backupDirectory != currentDir && QDir( currentDir ).exists() )
212 QDir::setCurrent( currentDir );
215 while ( !folder.exists() && folder.absolutePath().count(
'/' ) > searchCeilling )
217 existingBase = folder.path();
218 if ( !folder.cdUp() )
219 folder = QFileInfo( existingBase ).absoluteDir();
223 if ( depth > ( maxClimbs + 4 ) )
226 bool folderExists = folder.exists();
228 if ( depth > maxClimbs )
231 if ( folder.absolutePath().count(
'/' ) < searchCeilling )
232 searchCeilling = folder.absolutePath().count(
'/' ) - 1;
234 while ( depth <= maxClimbs && folderExists && folder.absolutePath().count(
'/' ) >= searchCeilling )
236 QDirIterator localFinder( folder.path(), QStringList() << fileName, QDir::Files, QDirIterator::NoIteratorFlags );
237 searchedFolder.append( folder.absolutePath() );
238 if ( localFinder.hasNext() )
240 foundFiles << localFinder.next();
245 const QFileInfoList subdirs = folder.entryInfoList( QDir::AllDirs );
246 for (
const QFileInfo &subdir : subdirs )
248 if ( !searchedFolder.contains( subdir.absolutePath() ) )
250 QDirIterator subDirFinder( subdir.path(), QStringList() << fileName, QDir::Files, QDirIterator::Subdirectories );
251 if ( subDirFinder.hasNext() )
253 QString possibleFile = subDirFinder.next();
254 if ( !subDirFinder.hasNext() )
256 foundFiles << possibleFile;
260 foundFiles << possibleFile;
261 while ( subDirFinder.hasNext() )
263 foundFiles << subDirFinder.next();
271 if ( depth > maxClimbs )
274 folderExists = folder.cdUp();
277 if ( QDir::currentPath() == currentDir && currentDir != backupDirectory )
278 QDir::setCurrent( backupDirectory );
284std::unique_ptr< wchar_t[] > pathToWChar(
const QString &path )
286 const QString nativePath = QDir::toNativeSeparators( path );
288 std::unique_ptr< wchar_t[] > pathArray(
new wchar_t[
static_cast< uint
>( nativePath.length() + 1 )] );
289 nativePath.toWCharArray( pathArray.get() );
290 pathArray[
static_cast< size_t >( nativePath.length() )] = 0;
295void fileAttributesOld( HANDLE handle, DWORD &fileAttributes,
bool &hasFileAttributes )
297 hasFileAttributes =
false;
298 BY_HANDLE_FILE_INFORMATION info;
299 if ( GetFileInformationByHandle( handle, &info ) )
301 hasFileAttributes =
true;
302 fileAttributes = info.dwFileAttributes;
307void fileAttributesNew( HANDLE handle, DWORD &fileAttributes,
bool &hasFileAttributes )
309 hasFileAttributes =
false;
311 _FILE_BASIC_INFO infoEx;
312 if ( GetFileInformationByHandleEx( handle, FileBasicInfo, &infoEx,
sizeof( infoEx ) ) )
314 hasFileAttributes =
true;
315 fileAttributes = infoEx.FileAttributes;
320 fileAttributesOld( handle, fileAttributes, hasFileAttributes );
323 fileAttributesOld( handle, fileAttributes, hasFileAttributes );
327bool pathIsLikelyCloudStorage( QString path )
331 QDirIterator dirIt( path, QDir::Files );
332 if ( dirIt.hasNext() )
337 std::unique_ptr< wchar_t[] > pathArray = pathToWChar( path );
338 const HANDLE handle = CreateFileW( pathArray.get(), 0, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
nullptr );
339 if ( handle != INVALID_HANDLE_VALUE )
341 bool hasFileAttributes =
false;
342 DWORD attributes = 0;
343 fileAttributesNew( handle, attributes, hasFileAttributes );
344 CloseHandle( handle );
345 if ( hasFileAttributes )
359 return ( attributes & FILE_ATTRIBUTE_RECALL_ON_OPEN ) || ( attributes & FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS );
370 std::unique_ptr< wchar_t[] > pathArray = pathToWChar( path );
371 const UINT type = GetDriveTypeW( pathArray.get() );
377 case DRIVE_NO_ROOT_DIR:
380 case DRIVE_REMOVABLE:
399 const QString originalPath = QDir::cleanPath( path );
400 QString currentPath = originalPath;
402 while ( currentPath != prevPath )
404 if ( pathIsLikelyCloudStorage( currentPath ) )
407 prevPath = currentPath;
408 currentPath = QFileInfo( currentPath ).path();
425 if ( path.contains(
"fake_slow_path_for_unit_tests"_L1 ) )
456 for (
const QString &provider : providers )
462 for (
const QString &possibleSidecar : possibleSidecars )
464 if ( QFile::exists( possibleSidecar ) )
465 res.insert( possibleSidecar );
474 if ( !QFile::exists( oldPath ) )
476 error = QObject::tr(
"File does not exist" );
480 const QFileInfo oldPathInfo( oldPath );
484 const QString qmdPath = oldPathInfo.dir().filePath( oldPathInfo.completeBaseName() + u
".qmd"_s );
485 if ( QFile::exists( qmdPath ) )
486 sidecars.insert( qmdPath );
490 const QString qmlPath = oldPathInfo.dir().filePath( oldPathInfo.completeBaseName() + u
".qml"_s );
491 if ( QFile::exists( qmlPath ) )
492 sidecars.insert( qmlPath );
495 const QFileInfo newPathInfo( newPath );
499 errors.reserve( sidecars.size() );
501 for (
const QString &sidecar : std::as_const( sidecars ) )
503 const QFileInfo sidecarInfo( sidecar );
504 const QString newSidecarName = newPathInfo.dir().filePath( newPathInfo.completeBaseName() +
'.' + sidecarInfo.suffix() );
505 if ( newSidecarName != sidecar && QFile::exists( newSidecarName ) )
508 errors.append( QDir::toNativeSeparators( newSidecarName ) );
513 error = QObject::tr(
"Destination files already exist %1" ).arg( errors.join(
", "_L1 ) );
517 if ( !QFile::rename( oldPath, newPath ) )
519 error = QObject::tr(
"Could not rename %1" ).arg( QDir::toNativeSeparators( oldPath ) );
523 for (
const QString &sidecar : std::as_const( sidecars ) )
525 const QFileInfo sidecarInfo( sidecar );
526 const QString newSidecarName = newPathInfo.dir().filePath( newPathInfo.completeBaseName() +
'.' + sidecarInfo.suffix() );
527 if ( newSidecarName == sidecar )
530 if ( !QFile::rename( sidecar, newSidecarName ) )
532 errors.append( QDir::toNativeSeparators( sidecar ) );
538 error = QObject::tr(
"Could not rename %1" ).arg( errors.join(
", "_L1 ) );
547 struct rlimit rescLimit;
548 if ( getrlimit( RLIMIT_NOFILE, &rescLimit ) == 0 )
550 return rescLimit.rlim_cur;
561 DIR *dirp = opendir(
"/proc/self/fd" );
565 while (
struct dirent *entry = readdir( dirp ) )
567 if ( entry->d_type == DT_REG )
584 constexpr int SOME_MARGIN = 20;
585 return nFileCount > 0 && nFileLimit > 0 && nFileCount + filesToBeOpened > nFileLimit - SOME_MARGIN;
591 QString path = QDir::cleanPath( input );
592 if ( path.isEmpty() )
595 const QString fileName = QFileInfo( path ).fileName();
596 if ( !fileName.isEmpty() )
598 else if ( QFileInfo( path ).path() == path )
601 QString prevPath = path;
602 while ( ( path = QFileInfo( path ).path() ).length() < prevPath.length() )
604 const QString dirName = QDir( path ).dirName();
605 if ( dirName ==
"."_L1 )
608 result << ( !dirName.isEmpty() ? dirName : path );
612 std::reverse( result.begin(), result.end() );
616QString QgsFileUtils::uniquePath(
const QString &path )
618 if ( !QFileInfo::exists( path ) )
623 QFileInfo info { path };
624 const QString suffix { info.completeSuffix() };
625 const QString pathPattern { QString( suffix.isEmpty() ? path : path.chopped( suffix.length() + 1 ) ).append( suffix.isEmpty() ? u
"_%1"_s : u
"_%1."_s ).append( suffix ) };
627 QString uniquePath { pathPattern.arg( i ) };
628 while ( QFileInfo::exists( uniquePath ) )
631 uniquePath = pathPattern.arg( i );
638 QDir sourceDir( source );
639 if ( !sourceDir.exists() )
641 QgsDebugError( u
"Cannot copy %1 to %2, source directory does not exist"_s.arg( source, destination ) );
645 QDir destDir( destination );
646 if ( !destDir.exists() )
648 if ( !destDir.mkdir( destination ) )
650 QgsDebugError( u
"Cannot copy %1 to %2, could not make target directory"_s.arg( source, destination ) );
655 bool copiedAll =
true;
656 const QStringList files = sourceDir.entryList( QDir::Files );
657 for (
const QString &file : files )
659 const QString srcFileName = sourceDir.filePath( file );
660 const QString destFileName = destDir.filePath( file );
661 if ( !QFile::copy( srcFileName, destFileName ) )
663 QgsDebugError( u
"Cannot copy %1 to %2"_s.arg( srcFileName, destFileName ) );
667 const QStringList dirs = sourceDir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot );
668 for (
const QString &dir : dirs )
670 const QString srcDirName = sourceDir.filePath( dir );
671 const QString destDirName = destDir.filePath( dir );
683 if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
685 QgsDebugError( u
"Could not open file for reading: %1"_s.arg( file.errorString() ) );
689 QTextStream in( &file );
690 const QString originalFileContent = in.readAll();
693 QString fileContent = originalFileContent;
694 fileContent.replace( searchString, replacement );
695 if ( fileContent == originalFileContent )
698 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
700 QgsDebugError( u
"Could not open file for writing: %1"_s.arg( file.errorString() ) );
704 QTextStream out( &file );
@ Cloud
Cloud storage – files may be remote or locally stored, depending on user configuration.
@ Removable
Removable drive.
@ IncludeMetadataFile
Indicates that any associated .qmd metadata file should be included with the operation.
@ IncludeStyleFile
Indicates that any associated .qml styling file should be included with the operation.
QFlags< FileOperationFlag > FileOperationFlags
File operation flags.
static QString stringToSafeFilename(const QString &string)
Converts a string to a safe filename, replacing characters which are not safe for filenames with an '...
static bool replaceTextInFile(const QString &path, const QString &searchString, const QString &replacement)
Replaces all occurrences of a given string in a file.
static QStringList findFile(const QString &file, const QString &basepath=QString(), int maxClimbs=4, int searchCeiling=4, const QString ¤tDir=QString())
Will check basepath in an outward spiral up to maxClimbs levels to check if file exists.
static int openedFileCount()
Returns the number of currently opened files by the process.
static QString wildcardsFromFilter(const QString &filter)
Given a filter string like GeoTIFF Files (*.tiff *.tif), extracts the wildcard portion of this filter...
static bool renameDataset(const QString &oldPath, const QString &newPath, QString &error, Qgis::FileOperationFlags flags=Qgis::FileOperationFlag::IncludeMetadataFile|Qgis::FileOperationFlag::IncludeStyleFile)
Renames the dataset at oldPath to newPath, renaming both the file at oldPath and all associated sidec...
static QSet< QString > sidecarFilesForPath(const QString &path)
Returns a list of the sidecar files which exist for the dataset a the specified path.
static bool pathIsSlowDevice(const QString &path)
Returns true if the specified path is assumed to reside on a slow device, e.g.
static bool isCloseToLimitOfOpenedFiles(int filesToBeOpened=1)
Returns whether when opening new file(s) will reach, or nearly reach, the limit of simultaneously ope...
static Qgis::DriveType driveType(const QString &path)
Returns the drive type for the given path.
static bool copyDirectory(const QString &source, const QString &destination)
Creates a unique file path name from a desired path by appending _<n> (where <n> is an integer number...
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 int openedFileLimit()
Returns the limit of simultaneously opened files by the process.
static QStringList splitPathToComponents(const QString &path)
Given a file path, returns a list of all the components leading to that path.
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.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QStringList providerList() const
Returns list of available providers by their keys.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
#define QgsDebugError(str)