25#include <QDirIterator>
28#include <QRegularExpression>
32#include "moc_qgsfileutils.cpp"
34using namespace Qt::StringLiterals;
38#include <sys/resource.h>
46#pragma comment( lib, "Shell32.lib" )
52 list << QObject::tr(
"KB" ) << QObject::tr(
"MB" ) << QObject::tr(
"GB" ) << QObject::tr(
"TB" );
54 QStringListIterator i( list );
55 QString unit = QObject::tr(
"B" );
57 double fileSize = bytes;
58 while ( fileSize >= 1024.0 && i.hasNext() )
63 return u
"%1 %2"_s.arg( QString::number( fileSize,
'f', bytes >= 1048576 ? 2 : 0 ), unit );
68 const thread_local QRegularExpression rx( u
"\\*\\.([a-zA-Z0-9\\.]+)"_s );
69 QStringList extensions;
70 QRegularExpressionMatchIterator matches = rx.globalMatch( filter );
72 while ( matches.hasNext() )
74 const QRegularExpressionMatch match = matches.next();
75 if ( match.hasMatch() )
77 QStringList newExtensions = match.capturedTexts();
78 newExtensions.pop_front();
79 extensions.append( newExtensions );
87 const thread_local QRegularExpression globPatternsRx( u
".*\\((.*?)\\)$"_s );
88 const QRegularExpressionMatch matches = globPatternsRx.match( filter );
89 if ( matches.hasMatch() )
90 return matches.captured( 1 );
97 QFileInfo fi( fileName );
98 const QString name = fi.fileName();
99 const QStringList parts = filter.split( u
";;"_s );
100 for (
const QString &part : parts )
102 const QStringList globPatterns =
wildcardsFromFilter( part ).split(
' ', Qt::SkipEmptyParts );
103 for (
const QString &glob : globPatterns )
105 const QString re = QRegularExpression::wildcardToRegularExpression( glob );
107 const QRegularExpression globRx( re );
108 if ( globRx.match( name ).hasMatch() )
117 if ( extensions.empty() || f.isEmpty() )
120 QString fileName = f;
122 for (
const QString &extension : std::as_const( extensions ) )
124 const QString extWithDot = extension.startsWith(
'.' ) ? extension :
'.' + extension;
125 if ( fileName.endsWith( extWithDot, Qt::CaseInsensitive ) )
134 const QString extension = extensions.at( 0 );
135 const QString extWithDot = extension.startsWith(
'.' ) ? extension :
'.' + extension;
136 fileName += extWithDot;
150 const thread_local QRegularExpression rx( u
"[/\\\\\\?%\\*\\:\\|\"<>]"_s );
152 s.replace( rx, u
"_"_s );
158 if ( path.isEmpty() )
162 QFileInfo fi( path );
164 currentPath = fi.dir();
166 currentPath = QDir( path );
168 QSet< QString > visited;
169 while ( !currentPath.exists() )
171 const QString parentPath = QDir::cleanPath( currentPath.absolutePath() + u
"/.."_s );
172 if ( visited.contains( parentPath ) )
175 if ( parentPath.isEmpty() || parentPath ==
"."_L1 )
177 currentPath = QDir( parentPath );
178 visited << parentPath;
181 const QString res = QDir::cleanPath( currentPath.absolutePath() );
183 if ( res == QDir::currentPath() )
186 return res ==
"."_L1 ? QString() : res;
189QStringList
QgsFileUtils::findFile(
const QString &file,
const QString &basePath,
int maxClimbs,
int searchCeilling,
const QString ¤tDir )
192 QString originalFolder;
194 const QString fileName( basePath.isEmpty() ? QFileInfo( file ).fileName() : file );
195 const QString baseFolder( basePath.isEmpty() ? QFileInfo( file ).path() : basePath );
197 if ( QFileInfo( baseFolder ).isDir() )
199 folder = QDir( baseFolder );
200 originalFolder = folder.absolutePath();
204 folder = QDir( QFileInfo( baseFolder ).absolutePath() );
205 originalFolder = folder.absolutePath();
208 QStringList searchedFolder = QStringList();
209 QString existingBase;
210 QString backupDirectory = QDir::currentPath();
211 QStringList foundFiles;
213 if ( !currentDir.isEmpty() && backupDirectory != currentDir && QDir( currentDir ).exists() )
214 QDir::setCurrent( currentDir );
217 while ( !folder.exists() && folder.absolutePath().count(
'/' ) > searchCeilling )
219 existingBase = folder.path();
220 if ( !folder.cdUp() )
221 folder = QFileInfo( existingBase ).absoluteDir();
225 if ( depth > ( maxClimbs + 4 ) )
228 bool folderExists = folder.exists();
230 if ( depth > maxClimbs )
233 if ( folder.absolutePath().count(
'/' ) < searchCeilling )
234 searchCeilling = folder.absolutePath().count(
'/' ) - 1;
236 while ( depth <= maxClimbs && folderExists && folder.absolutePath().count(
'/' ) >= searchCeilling )
238 QDirIterator localFinder( folder.path(), QStringList() << fileName, QDir::Files, QDirIterator::NoIteratorFlags );
239 searchedFolder.append( folder.absolutePath() );
240 if ( localFinder.hasNext() )
242 foundFiles << localFinder.next();
247 const QFileInfoList subdirs = folder.entryInfoList( QDir::AllDirs );
248 for (
const QFileInfo &subdir : subdirs )
250 if ( !searchedFolder.contains( subdir.absolutePath() ) )
252 QDirIterator subDirFinder( subdir.path(), QStringList() << fileName, QDir::Files, QDirIterator::Subdirectories );
253 if ( subDirFinder.hasNext() )
255 QString possibleFile = subDirFinder.next();
256 if ( !subDirFinder.hasNext() )
258 foundFiles << possibleFile;
262 foundFiles << possibleFile;
263 while ( subDirFinder.hasNext() )
265 foundFiles << subDirFinder.next();
273 if ( depth > maxClimbs )
276 folderExists = folder.cdUp();
279 if ( QDir::currentPath() == currentDir && currentDir != backupDirectory )
280 QDir::setCurrent( backupDirectory );
286std::unique_ptr< wchar_t[] > pathToWChar(
const QString &path )
288 const QString nativePath = QDir::toNativeSeparators( path );
290 std::unique_ptr< wchar_t[] > pathArray(
new wchar_t[
static_cast< uint
>( nativePath.length() + 1 )] );
291 nativePath.toWCharArray( pathArray.get() );
292 pathArray[
static_cast< size_t >( nativePath.length() )] = 0;
297void fileAttributesOld( HANDLE handle, DWORD &fileAttributes,
bool &hasFileAttributes )
299 hasFileAttributes =
false;
300 BY_HANDLE_FILE_INFORMATION info;
301 if ( GetFileInformationByHandle( handle, &info ) )
303 hasFileAttributes =
true;
304 fileAttributes = info.dwFileAttributes;
309void fileAttributesNew( HANDLE handle, DWORD &fileAttributes,
bool &hasFileAttributes )
311 hasFileAttributes =
false;
313 _FILE_BASIC_INFO infoEx;
314 if ( GetFileInformationByHandleEx( handle, FileBasicInfo, &infoEx,
sizeof( infoEx ) ) )
316 hasFileAttributes =
true;
317 fileAttributes = infoEx.FileAttributes;
322 fileAttributesOld( handle, fileAttributes, hasFileAttributes );
325 fileAttributesOld( handle, fileAttributes, hasFileAttributes );
329bool pathIsLikelyCloudStorage( QString path )
333 QDirIterator dirIt( path, QDir::Files );
334 if ( dirIt.hasNext() )
339 std::unique_ptr< wchar_t[] > pathArray = pathToWChar( path );
340 const HANDLE handle = CreateFileW( pathArray.get(), 0, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
nullptr );
341 if ( handle != INVALID_HANDLE_VALUE )
343 bool hasFileAttributes =
false;
344 DWORD attributes = 0;
345 fileAttributesNew( handle, attributes, hasFileAttributes );
346 CloseHandle( handle );
347 if ( hasFileAttributes )
361 return ( attributes & FILE_ATTRIBUTE_RECALL_ON_OPEN ) || ( attributes & FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS );
372 std::unique_ptr< wchar_t[] > pathArray = pathToWChar( path );
373 const UINT type = GetDriveTypeW( pathArray.get() );
379 case DRIVE_NO_ROOT_DIR:
382 case DRIVE_REMOVABLE:
401 const QString originalPath = QDir::cleanPath( path );
402 QString currentPath = originalPath;
404 while ( currentPath != prevPath )
406 if ( pathIsLikelyCloudStorage( currentPath ) )
409 prevPath = currentPath;
410 currentPath = QFileInfo( currentPath ).path();
427 if ( path.contains(
"fake_slow_path_for_unit_tests"_L1 ) )
458 for (
const QString &provider : providers )
464 for (
const QString &possibleSidecar : possibleSidecars )
466 if ( QFile::exists( possibleSidecar ) )
467 res.insert( possibleSidecar );
476 if ( !QFile::exists( oldPath ) )
478 error = QObject::tr(
"File does not exist" );
482 const QFileInfo oldPathInfo( oldPath );
486 const QString qmdPath = oldPathInfo.dir().filePath( oldPathInfo.completeBaseName() + u
".qmd"_s );
487 if ( QFile::exists( qmdPath ) )
488 sidecars.insert( qmdPath );
492 const QString qmlPath = oldPathInfo.dir().filePath( oldPathInfo.completeBaseName() + u
".qml"_s );
493 if ( QFile::exists( qmlPath ) )
494 sidecars.insert( qmlPath );
497 const QFileInfo newPathInfo( newPath );
501 errors.reserve( sidecars.size() );
503 for (
const QString &sidecar : std::as_const( sidecars ) )
505 const QFileInfo sidecarInfo( sidecar );
506 const QString newSidecarName = newPathInfo.dir().filePath( newPathInfo.completeBaseName() +
'.' + sidecarInfo.suffix() );
507 if ( newSidecarName != sidecar && QFile::exists( newSidecarName ) )
510 errors.append( QDir::toNativeSeparators( newSidecarName ) );
515 error = QObject::tr(
"Destination files already exist %1" ).arg( errors.join(
", "_L1 ) );
519 if ( !QFile::rename( oldPath, newPath ) )
521 error = QObject::tr(
"Could not rename %1" ).arg( QDir::toNativeSeparators( oldPath ) );
525 for (
const QString &sidecar : std::as_const( sidecars ) )
527 const QFileInfo sidecarInfo( sidecar );
528 const QString newSidecarName = newPathInfo.dir().filePath( newPathInfo.completeBaseName() +
'.' + sidecarInfo.suffix() );
529 if ( newSidecarName == sidecar )
532 if ( !QFile::rename( sidecar, newSidecarName ) )
534 errors.append( QDir::toNativeSeparators( sidecar ) );
540 error = QObject::tr(
"Could not rename %1" ).arg( errors.join(
", "_L1 ) );
549 struct rlimit rescLimit;
550 if ( getrlimit( RLIMIT_NOFILE, &rescLimit ) == 0 )
552 return rescLimit.rlim_cur;
563 DIR *dirp = opendir(
"/proc/self/fd" );
567 while (
struct dirent *entry = readdir( dirp ) )
569 if ( entry->d_type == DT_REG )
586 constexpr int SOME_MARGIN = 20;
587 return nFileCount > 0 && nFileLimit > 0 && nFileCount + filesToBeOpened > nFileLimit - SOME_MARGIN;
593 QString path = QDir::cleanPath( input );
594 if ( path.isEmpty() )
597 const QString fileName = QFileInfo( path ).fileName();
598 if ( !fileName.isEmpty() )
600 else if ( QFileInfo( path ).path() == path )
603 QString prevPath = path;
604 while ( ( path = QFileInfo( path ).path() ).length() < prevPath.length() )
606 const QString dirName = QDir( path ).dirName();
607 if ( dirName ==
"."_L1 )
610 result << ( !dirName.isEmpty() ? dirName : path );
614 std::reverse( result.begin(), result.end() );
618QString QgsFileUtils::uniquePath(
const QString &path )
620 if ( !QFileInfo::exists( path ) )
625 QFileInfo info { path };
626 const QString suffix { info.completeSuffix() };
627 const QString pathPattern { QString( suffix.isEmpty() ? path : path.chopped( suffix.length() + 1 ) ).append( suffix.isEmpty() ? u
"_%1"_s : u
"_%1."_s ).append( suffix ) };
629 QString uniquePath { pathPattern.arg( i ) };
630 while ( QFileInfo::exists( uniquePath ) )
633 uniquePath = pathPattern.arg( i );
640 QDir sourceDir( source );
641 if ( !sourceDir.exists() )
643 QgsDebugError( u
"Cannot copy %1 to %2, source directory does not exist"_s.arg( source, destination ) );
647 QDir destDir( destination );
648 if ( !destDir.exists() )
650 if ( !destDir.mkdir( destination ) )
652 QgsDebugError( u
"Cannot copy %1 to %2, could not make target directory"_s.arg( source, destination ) );
657 bool copiedAll =
true;
659 QDir::Filters fileFilters = QDir::Files;
662 fileFilters |= QDir::NoSymLinks;
664 const QStringList files = sourceDir.entryList( fileFilters );
665 for (
const QString &file : files )
667 const QString srcFileName = sourceDir.filePath( file );
668 const QString destFileName = destDir.filePath( file );
669 if ( !QFile::copy( srcFileName, destFileName ) )
671 QgsDebugError( u
"Cannot copy %1 to %2"_s.arg( srcFileName, destFileName ) );
676 QDir::Filters dirFilters = QDir::AllDirs | QDir::NoDotAndDotDot;
679 dirFilters |= QDir::NoSymLinks;
681 const QStringList dirs = sourceDir.entryList( dirFilters );
682 for (
const QString &dir : dirs )
684 const QString srcDirName = sourceDir.filePath( dir );
685 const QString destDirName = destDir.filePath( dir );
697 if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
699 QgsDebugError( u
"Could not open file for reading: %1"_s.arg( file.errorString() ) );
703 QTextStream in( &file );
704 const QString originalFileContent = in.readAll();
707 QString fileContent = originalFileContent;
708 fileContent.replace( searchString, replacement );
709 if ( fileContent == originalFileContent )
712 if ( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
714 QgsDebugError( u
"Could not open file for writing: %1"_s.arg( file.errorString() ) );
718 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.
@ NoSymLinks
If present, indicates that symbolic links should be skipped during the copy.
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 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.
QFlags< CopyFlag > CopyFlags
Flags controlling behavior of file copy operations.
static bool copyDirectory(const QString &source, const QString &destination, QgsFileUtils::CopyFlags flags=QgsFileUtils::CopyFlags())
Recursively copies a whole directory.
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)