24#include <QDirIterator>
27#include <QRegularExpression>
31using namespace Qt::StringLiterals;
35#include <sys/resource.h>
43#pragma comment(lib,"Shell32.lib")
49 list << QObject::tr(
"KB" ) << QObject::tr(
"MB" ) << QObject::tr(
"GB" ) << QObject::tr(
"TB" );
51 QStringListIterator i( list );
52 QString unit = QObject::tr(
"B" );
54 double fileSize = bytes;
55 while ( fileSize >= 1024.0 && i.hasNext() )
60 return u
"%1 %2"_s.arg( QString::number( fileSize,
'f', bytes >= 1048576 ? 2 : 0 ), unit );
65 const thread_local QRegularExpression rx( u
"\\*\\.([a-zA-Z0-9\\.]+)"_s );
66 QStringList extensions;
67 QRegularExpressionMatchIterator matches = rx.globalMatch( filter );
69 while ( matches.hasNext() )
71 const QRegularExpressionMatch match = matches.next();
72 if ( match.hasMatch() )
74 QStringList newExtensions = match.capturedTexts();
75 newExtensions.pop_front();
76 extensions.append( newExtensions );
84 const thread_local QRegularExpression globPatternsRx( u
".*\\((.*?)\\)$"_s );
85 const QRegularExpressionMatch matches = globPatternsRx.match( filter );
86 if ( matches.hasMatch() )
87 return matches.captured( 1 );
94 QFileInfo fi( fileName );
95 const QString name = fi.fileName();
96 const QStringList parts = filter.split( u
";;"_s );
97 for (
const QString &part : parts )
99 const QStringList globPatterns =
wildcardsFromFilter( part ).split(
' ', Qt::SkipEmptyParts );
100 for (
const QString &glob : globPatterns )
102 const QString re = QRegularExpression::wildcardToRegularExpression( glob );
104 const QRegularExpression globRx( re );
105 if ( globRx.match( name ).hasMatch() )
114 if ( extensions.empty() || f.isEmpty() )
117 QString fileName = f;
119 for (
const QString &extension : std::as_const( extensions ) )
121 const QString extWithDot = extension.startsWith(
'.' ) ? extension :
'.' + extension;
122 if ( fileName.endsWith( extWithDot, Qt::CaseInsensitive ) )
131 const QString extension = extensions.at( 0 );
132 const QString extWithDot = extension.startsWith(
'.' ) ? extension :
'.' + extension;
133 fileName += extWithDot;
147 const thread_local QRegularExpression rx( u
"[/\\\\\\?%\\*\\:\\|\"<>]"_s );
149 s.replace( rx, u
"_"_s );
155 if ( path.isEmpty() )
159 QFileInfo fi( path );
161 currentPath = fi.dir();
163 currentPath = QDir( path );
165 QSet< QString > visited;
166 while ( !currentPath.exists() )
168 const QString parentPath = QDir::cleanPath( currentPath.absolutePath() + u
"/.."_s );
169 if ( visited.contains( parentPath ) )
172 if ( parentPath.isEmpty() || parentPath ==
"."_L1 )
174 currentPath = QDir( parentPath );
175 visited << parentPath;
178 const QString res = QDir::cleanPath( currentPath.absolutePath() );
180 if ( res == QDir::currentPath() )
183 return res ==
"."_L1 ? QString() : res;
186QStringList
QgsFileUtils::findFile(
const QString &file,
const QString &basePath,
int maxClimbs,
int searchCeilling,
const QString ¤tDir )
189 QString originalFolder;
191 const QString fileName( basePath.isEmpty() ? QFileInfo( file ).fileName() : file );
192 const QString baseFolder( basePath.isEmpty() ? QFileInfo( file ).path() : basePath );
194 if ( QFileInfo( baseFolder ).isDir() )
196 folder = QDir( baseFolder ) ;
197 originalFolder = folder.absolutePath();
201 folder = QDir( QFileInfo( baseFolder ).absolutePath() );
202 originalFolder = folder.absolutePath();
205 QStringList searchedFolder = QStringList();
206 QString existingBase;
207 QString backupDirectory = QDir::currentPath();
208 QStringList foundFiles;
210 if ( !currentDir.isEmpty() && backupDirectory != currentDir && QDir( currentDir ).exists() )
211 QDir::setCurrent( currentDir );
214 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 )
237 QDirIterator localFinder( folder.path(), QStringList() << fileName, QDir::Files, QDirIterator::NoIteratorFlags );
238 searchedFolder.append( folder.absolutePath() );
239 if ( localFinder.hasNext() )
241 foundFiles << localFinder.next();
246 const QFileInfoList subdirs = folder.entryInfoList( QDir::AllDirs );
247 for (
const QFileInfo &subdir : subdirs )
249 if ( ! searchedFolder.contains( subdir.absolutePath() ) )
251 QDirIterator subDirFinder( subdir.path(), QStringList() << fileName, QDir::Files, QDirIterator::Subdirectories );
252 if ( subDirFinder.hasNext() )
254 QString possibleFile = subDirFinder.next();
255 if ( !subDirFinder.hasNext() )
257 foundFiles << possibleFile;
261 foundFiles << possibleFile;
262 while ( subDirFinder.hasNext() )
264 foundFiles << subDirFinder.next();
272 if ( depth > maxClimbs )
275 folderExists = folder.cdUp();
278 if ( QDir::currentPath() == currentDir && currentDir != backupDirectory )
279 QDir::setCurrent( backupDirectory );
285std::unique_ptr< wchar_t[] > pathToWChar(
const QString &path )
287 const QString nativePath = QDir::toNativeSeparators( path );
289 std::unique_ptr< wchar_t[] > pathArray(
new wchar_t[
static_cast< uint
>( nativePath.length() + 1 )] );
290 nativePath.toWCharArray( pathArray.get() );
291 pathArray[
static_cast< size_t >( nativePath.length() )] = 0;
296void fileAttributesOld( HANDLE handle, DWORD &fileAttributes,
bool &hasFileAttributes )
298 hasFileAttributes =
false;
299 BY_HANDLE_FILE_INFORMATION info;
300 if ( GetFileInformationByHandle( handle, &info ) )
302 hasFileAttributes =
true;
303 fileAttributes = info.dwFileAttributes;
308void fileAttributesNew( HANDLE handle, DWORD &fileAttributes,
bool &hasFileAttributes )
310 hasFileAttributes =
false;
312 _FILE_BASIC_INFO infoEx;
313 if ( GetFileInformationByHandleEx(
316 &infoEx,
sizeof( infoEx ) ) )
318 hasFileAttributes =
true;
319 fileAttributes = infoEx.FileAttributes;
324 fileAttributesOld( handle, fileAttributes, hasFileAttributes );
327 fileAttributesOld( handle, fileAttributes, hasFileAttributes );
331bool pathIsLikelyCloudStorage( QString path )
335 QDirIterator dirIt( path, QDir::Files );
336 if ( dirIt.hasNext() )
341 std::unique_ptr< wchar_t[] > pathArray = pathToWChar( path );
342 const HANDLE handle = CreateFileW( pathArray.get(), 0, FILE_SHARE_READ,
343 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
nullptr );
344 if ( handle != INVALID_HANDLE_VALUE )
346 bool hasFileAttributes =
false;
347 DWORD attributes = 0;
348 fileAttributesNew( handle, attributes, hasFileAttributes );
349 CloseHandle( handle );
350 if ( hasFileAttributes )
364 return ( attributes & FILE_ATTRIBUTE_RECALL_ON_OPEN )
365 || ( attributes & FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS );
377 std::unique_ptr< wchar_t[] > pathArray = pathToWChar( path );
378 const UINT type = GetDriveTypeW( pathArray.get() );
384 case DRIVE_NO_ROOT_DIR:
387 case DRIVE_REMOVABLE:
407 const QString originalPath = QDir::cleanPath( path );
408 QString currentPath = originalPath;
410 while ( currentPath != prevPath )
412 if ( pathIsLikelyCloudStorage( currentPath ) )
415 prevPath = currentPath;
416 currentPath = QFileInfo( currentPath ).path();
433 if ( path.contains(
"fake_slow_path_for_unit_tests"_L1 ) )
466 for (
const QString &provider : providers )
472 for (
const QString &possibleSidecar : possibleSidecars )
474 if ( QFile::exists( possibleSidecar ) )
475 res.insert( possibleSidecar );
484 if ( !QFile::exists( oldPath ) )
486 error = QObject::tr(
"File does not exist" );
490 const QFileInfo oldPathInfo( oldPath );
494 const QString qmdPath = oldPathInfo.dir().filePath( oldPathInfo.completeBaseName() + u
".qmd"_s );
495 if ( QFile::exists( qmdPath ) )
496 sidecars.insert( qmdPath );
500 const QString qmlPath = oldPathInfo.dir().filePath( oldPathInfo.completeBaseName() + u
".qml"_s );
501 if ( QFile::exists( qmlPath ) )
502 sidecars.insert( qmlPath );
505 const QFileInfo newPathInfo( newPath );
509 errors.reserve( sidecars.size() );
511 for (
const QString &sidecar : std::as_const( sidecars ) )
513 const QFileInfo sidecarInfo( sidecar );
514 const QString newSidecarName = newPathInfo.dir().filePath( newPathInfo.completeBaseName() +
'.' + sidecarInfo.suffix() );
515 if ( newSidecarName != sidecar && QFile::exists( newSidecarName ) )
518 errors.append( QDir::toNativeSeparators( newSidecarName ) );
523 error = QObject::tr(
"Destination files already exist %1" ).arg( errors.join(
", "_L1 ) );
527 if ( !QFile::rename( oldPath, newPath ) )
529 error = QObject::tr(
"Could not rename %1" ).arg( QDir::toNativeSeparators( oldPath ) );
533 for (
const QString &sidecar : std::as_const( sidecars ) )
535 const QFileInfo sidecarInfo( sidecar );
536 const QString newSidecarName = newPathInfo.dir().filePath( newPathInfo.completeBaseName() +
'.' + sidecarInfo.suffix() );
537 if ( newSidecarName == sidecar )
540 if ( !QFile::rename( sidecar, newSidecarName ) )
542 errors.append( QDir::toNativeSeparators( sidecar ) );
548 error = QObject::tr(
"Could not rename %1" ).arg( errors.join(
", "_L1 ) );
557 struct rlimit rescLimit;
558 if ( getrlimit( RLIMIT_NOFILE, &rescLimit ) == 0 )
560 return rescLimit.rlim_cur;
571 DIR *dirp = opendir(
"/proc/self/fd" );
575 while (
struct dirent *entry = readdir( dirp ) )
577 if ( entry->d_type == DT_REG )
594 constexpr int SOME_MARGIN = 20;
595 return nFileCount > 0 && nFileLimit > 0 && nFileCount + filesToBeOpened > nFileLimit - SOME_MARGIN;
601 QString path = QDir::cleanPath( input );
602 if ( path.isEmpty() )
605 const QString fileName = QFileInfo( path ).fileName();
606 if ( !fileName.isEmpty() )
608 else if ( QFileInfo( path ).path() == path )
611 QString prevPath = path;
612 while ( ( path = QFileInfo( path ).path() ).length() < prevPath.length() )
614 const QString dirName = QDir( path ).dirName();
615 if ( dirName ==
"."_L1 )
618 result << ( !dirName.isEmpty() ? dirName : path );
622 std::reverse( result.begin(), result.end() );
626QString QgsFileUtils::uniquePath(
const QString &path )
628 if ( ! QFileInfo::exists( path ) )
633 QFileInfo info { path };
634 const QString suffix { info.completeSuffix() };
635 const QString pathPattern { QString( suffix.isEmpty() ? path : path.chopped( suffix.length() + 1 ) ).append( suffix.isEmpty() ? u
"_%1"_s : u
"_%1."_s ).append( suffix ) };
637 QString uniquePath { pathPattern.arg( i ) };
638 while ( QFileInfo::exists( uniquePath ) )
641 uniquePath = pathPattern.arg( i );
@ 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 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 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.