| 1 | // Copyright (C) 2016 The Qt Company Ltd. | 
| 2 | // Copyright (C) 2024 Ahmad Samir <a.samirh78@gmail.com> | 
| 3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 4 |  | 
| 5 | /*! | 
| 6 |     \since 6.8 | 
| 7 |     \class QDirListing | 
| 8 |     \inmodule QtCore | 
| 9 |     \ingroup  io | 
| 10 |     \brief The QDirListing class provides an STL-style iterator for directory entries. | 
| 11 |  | 
| 12 |     You can use QDirListing to navigate entries of a directory one at a time. | 
| 13 |     It is similar to QDir::entryList() and QDir::entryInfoList(), but because | 
| 14 |     it lists entries one at a time instead of all at once, it scales better | 
| 15 |     and is more suitable for large directories. It also supports listing | 
| 16 |     directory contents recursively, and following symbolic links. Unlike | 
| 17 |     QDir::entryList(), QDirListing does not support sorting. | 
| 18 |  | 
| 19 |     The QDirListing constructor takes a directory path string as | 
| 20 |     argument. Here's how to iterate over all entries recursively: | 
| 21 |  | 
| 22 |     \snippet code/src_corelib_io_qdirlisting.cpp 0 | 
| 23 |  | 
| 24 |     Here's how to find and read all regular files filtered by name, recursively: | 
| 25 |  | 
| 26 |     \snippet code/src_corelib_io_qdirlisting.cpp 1 | 
| 27 |  | 
| 28 |     Here's how to list only regular files, recursively: | 
| 29 |     \snippet code/src_corelib_io_qdirlisting.cpp 5 | 
| 30 |  | 
| 31 |     Here's how to list only regular files and symbolic links to regular | 
| 32 |     files, recursively: | 
| 33 |     \snippet code/src_corelib_io_qdirlisting.cpp 6 | 
| 34 |  | 
| 35 | //! [std-input-iterator-tag] | 
| 36 |     QDirListing::const_iterator models C++20 | 
| 37 |     \l{https://en.cppreference.com/w/cpp/iterator/input_iterator}{std::input_iterator}, | 
| 38 |     that is, it is a move-only, forward-only, single-pass iterator, that | 
| 39 |     doesn't allow random access. | 
| 40 | //! [std-input-iterator-tag] | 
| 41 |     It can be used in ranged-for loops (or with C++20 range algorithms that don't | 
| 42 |     require random access iterators). Dereferencing a valid iterator returns | 
| 43 |     a QDirListing::DirEntry object. The (c)end() sentinel marks the end of | 
| 44 |     the iteration. Dereferencing an iterator that is equal to \l{sentinel} is | 
| 45 |     undefined behavior. | 
| 46 |  | 
| 47 |     QDirListing::DirEntry offers a subset of QFileInfo's API (for example, | 
| 48 |     fileName(), filePath(), exists()). Internally, DirEntry only constructs | 
| 49 |     a QFileInfo object if needed, that is, if the info hasn't been already | 
| 50 |     fetched by other system functions. You can use DirEntry::fileInfo() | 
| 51 |     to get a QFileInfo. For example: | 
| 52 |  | 
| 53 |     \snippet code/src_corelib_io_qdirlisting.cpp 3 | 
| 54 |     \snippet code/src_corelib_io_qdirlisting.cpp 4 | 
| 55 |  | 
| 56 |     \sa QDir, QDir::entryList() | 
| 57 | */ | 
| 58 |  | 
| 59 | /*! \enum QDirListing::IteratorFlag | 
| 60 |  | 
| 61 |     This enum class describes flags that can be used to configure the behavior | 
| 62 |     of QDirListing. Values from this enumerator can be bitwise OR'ed together. | 
| 63 |  | 
| 64 |     \value Default | 
| 65 |         List all files, directories and symbolic links, including broken | 
| 66 |         symlinks (where the target doesn't exist). | 
| 67 |         Hidden files and directories and the special entries \c{.} and \c{..} | 
| 68 |         aren't listed by default. | 
| 69 |  | 
| 70 |     \value ExcludeFiles | 
| 71 |         Don't list regular files. When combined with ResolveSymlinks, symbolic | 
| 72 |         links to regular files will be excluded too. | 
| 73 |  | 
| 74 |     \value ExcludeDirs | 
| 75 |         Don't list directories. When combined with ResolveSymlinks, symbolic | 
| 76 |         links to directories will be excluded too. | 
| 77 |  | 
| 78 |     \value ExcludeSpecial | 
| 79 |         Don't list file system entries that are \e not directories, regular files, | 
| 80 |         nor symbolic links. | 
| 81 |         \list | 
| 82 |             \li On Unix, an example of a special file system entry is a FIFO, socket, | 
| 83 |                 character device, or block device. For more details on Linux, see the | 
| 84 |                 \l{https://www.man7.org/linux/man-pages/man2/mknod.2.html}{mknod manual page}. | 
| 85 |             \li On Windows (for historical reasons) \c .lnk files are considered special | 
| 86 |                 file system entries. | 
| 87 |         \endlist | 
| 88 |  | 
| 89 |     \value ResolveSymlinks | 
| 90 |         Filter symbolic links based on the type of the target of the link, | 
| 91 |         rather than the symbolic link itself. With this flag, broken symbolic | 
| 92 |         links (where the target doesn't exist) are excluded. This flag is | 
| 93 |         ignored on operating systems that don't support symbolic links. | 
| 94 |  | 
| 95 |     \value FilesOnly | 
| 96 |         Only regular files will be listed. When combined with ResolveSymlinks, | 
| 97 |         symbolic links to files will also be listed. | 
| 98 |  | 
| 99 |     \value DirsOnly | 
| 100 |         Only directories will be listed. When combined with ResolveSymlinks, | 
| 101 |         symbolic links to directories will also be listed. | 
| 102 |  | 
| 103 |     \value IncludeHidden | 
| 104 |         List hidden entries. When combined with Recursive, the iteration will | 
| 105 |         recurse into hidden sub-directories as well. | 
| 106 |  | 
| 107 |     \value IncludeDotAndDotDot | 
| 108 |         List the \c {.} and \c{..} special entries. | 
| 109 |  | 
| 110 |     \value CaseSensitive | 
| 111 |         The file glob patterns in the name filters passed to the QDirListing | 
| 112 |         constructor, will be matched case sensitively (for details, see | 
| 113 |         QDir::setNameFilters()). | 
| 114 |  | 
| 115 |     \value Recursive | 
| 116 |         List entries inside all sub-directories as well. When combined with | 
| 117 |         FollowDirSymlinks, symbolic links to directories will be iterated too. | 
| 118 |  | 
| 119 |     \value FollowDirSymlinks | 
| 120 |         When combined with Recursive, symbolic links to directories will be | 
| 121 |         iterated too. Symbolic link loops (e.g., link => . or link => ..) are | 
| 122 |         automatically detected and ignored. | 
| 123 | */ | 
| 124 |  | 
| 125 | #include "qdirlisting.h" | 
| 126 | #include "qdirentryinfo_p.h" | 
| 127 |  | 
| 128 | #include "qdir_p.h" | 
| 129 | #include "qdiriterator.h" | 
| 130 | #include "qabstractfileengine_p.h" | 
| 131 |  | 
| 132 | #if QT_CONFIG(regularexpression) | 
| 133 | #include <QtCore/qregularexpression.h> | 
| 134 | #endif | 
| 135 |  | 
| 136 | #include <QtCore/private/qfilesystemiterator_p.h> | 
| 137 | #include <QtCore/private/qfilesystementry_p.h> | 
| 138 | #include <QtCore/private/qfilesystemmetadata_p.h> | 
| 139 | #include <QtCore/private/qfilesystemengine_p.h> | 
| 140 | #include <QtCore/private/qfileinfo_p.h> | 
| 141 | #include <QtCore/private/qduplicatetracker_p.h> | 
| 142 |  | 
| 143 | #include <memory> | 
| 144 | #include <vector> | 
| 145 |  | 
| 146 | QT_BEGIN_NAMESPACE | 
| 147 |  | 
| 148 | using namespace Qt::StringLiterals; | 
| 149 |  | 
| 150 | static QDirListing::IteratorFlags toDirListingFlags(QDirIterator::IteratorFlags flags) | 
| 151 | { | 
| 152 |     using F = QDirListing::IteratorFlag; | 
| 153 |     QDirListing::IteratorFlags listerFlags; | 
| 154 |  | 
| 155 |     if (flags & QDirIterator::NoIteratorFlags) | 
| 156 |         listerFlags.setFlag(flag: F::Default); | 
| 157 |     if (flags & QDirIterator::FollowSymlinks) | 
| 158 |         listerFlags.setFlag(flag: F::FollowDirSymlinks); | 
| 159 |     if (flags & QDirIterator::Subdirectories) | 
| 160 |         listerFlags.setFlag(flag: F::Recursive); | 
| 161 |  | 
| 162 |     return listerFlags; | 
| 163 | } | 
| 164 |  | 
| 165 | class QDirListingPrivate | 
| 166 | { | 
| 167 | public: | 
| 168 |     void init(bool resolveEngine); | 
| 169 |     void advance(); | 
| 170 |     void beginIterating(); | 
| 171 |  | 
| 172 |     bool entryMatches(QDirEntryInfo &info); | 
| 173 |     void pushDirectory(QDirEntryInfo &info); | 
| 174 |     void pushInitialDirectory(); | 
| 175 |  | 
| 176 |     void checkAndPushDirectory(QDirEntryInfo &info); | 
| 177 |     bool matchesFilters(QDirEntryInfo &data) const; | 
| 178 |     bool hasIterators() const; | 
| 179 |  | 
| 180 |     bool matchesLegacyFilters(QDirEntryInfo &data) const; | 
| 181 |     void setLegacyFilters(QDir::Filters dirFilters, QDirIterator::IteratorFlags dirIteratorFlags) | 
| 182 |     { | 
| 183 |         useLegacyFilters = true; | 
| 184 |         legacyDirFilters = dirFilters; | 
| 185 |         iteratorFlags = toDirListingFlags(flags: dirIteratorFlags); | 
| 186 |     } | 
| 187 |  | 
| 188 |     std::unique_ptr<QAbstractFileEngine> engine; | 
| 189 |     QDirEntryInfo initialEntryInfo; | 
| 190 |     QStringList nameFilters; | 
| 191 |     QDirListing::IteratorFlags iteratorFlags; | 
| 192 |     QDirEntryInfo currentEntryInfo; | 
| 193 |  | 
| 194 |     bool useLegacyFilters = false; | 
| 195 |     QDir::Filters legacyDirFilters; | 
| 196 |  | 
| 197 | #if QT_CONFIG(regularexpression) | 
| 198 |     QList<QRegularExpression> nameRegExps; | 
| 199 |     bool regexMatchesName(const QString &fileName) const | 
| 200 |     { | 
| 201 |         if (nameRegExps.isEmpty()) | 
| 202 |             return true; | 
| 203 |         auto hasMatch = [&fileName](const auto &re) { return re.match(fileName).hasMatch(); }; | 
| 204 |         return std::any_of(first: nameRegExps.cbegin(), last: nameRegExps.cend(), pred: hasMatch); | 
| 205 |     } | 
| 206 | #endif | 
| 207 |  | 
| 208 |     using FEngineIteratorPtr = std::unique_ptr<QAbstractFileEngineIterator>; | 
| 209 |     std::vector<FEngineIteratorPtr> fileEngineIterators; | 
| 210 | #ifndef QT_NO_FILESYSTEMITERATOR | 
| 211 |     using FsIteratorPtr = std::unique_ptr<QFileSystemIterator>; | 
| 212 |     std::vector<FsIteratorPtr> nativeIterators; | 
| 213 | #endif | 
| 214 |  | 
| 215 |     // Loop protection | 
| 216 |     QDuplicateTracker<QString> visitedLinks; | 
| 217 | }; | 
| 218 |  | 
| 219 | void QDirListingPrivate::init(bool resolveEngine = true) | 
| 220 | { | 
| 221 |     if (nameFilters.contains(str: "*"_L1 )) | 
| 222 |         nameFilters.clear(); | 
| 223 |  | 
| 224 |     if (useLegacyFilters) { | 
| 225 |         if (legacyDirFilters == QDir::NoFilter) | 
| 226 |             legacyDirFilters = QDir::AllEntries; | 
| 227 |     } | 
| 228 |  | 
| 229 | #if QT_CONFIG(regularexpression) | 
| 230 |     nameRegExps.reserve(asize: nameFilters.size()); | 
| 231 |  | 
| 232 |     const bool isCase = [this] { | 
| 233 |         if (useLegacyFilters) | 
| 234 |             return legacyDirFilters.testAnyFlags(flags: QDir::CaseSensitive); | 
| 235 |         return iteratorFlags.testAnyFlags(flags: QDirListing::IteratorFlag::CaseSensitive); | 
| 236 |     }(); | 
| 237 |  | 
| 238 |     const auto cs = isCase ? Qt::CaseSensitive : Qt::CaseInsensitive; | 
| 239 |     for (const auto &filter : nameFilters) | 
| 240 |         nameRegExps.emplace_back(args: QRegularExpression::fromWildcard(pattern: filter, cs)); | 
| 241 | #endif | 
| 242 |  | 
| 243 |     if (resolveEngine) { | 
| 244 |         engine = QFileSystemEngine::createLegacyEngine(entry&: initialEntryInfo.entry, | 
| 245 |                                                        data&: initialEntryInfo.metaData); | 
| 246 |     } | 
| 247 | } | 
| 248 |  | 
| 249 | /*! | 
| 250 |     \internal | 
| 251 |  | 
| 252 |     Resets the iteration state (if any), so that calling begin()/cbegin() | 
| 253 |     always starts iterating anew. | 
| 254 | */ | 
| 255 | void QDirListingPrivate::beginIterating() | 
| 256 | { | 
| 257 | #ifndef QT_NO_FILESYSTEMITERATOR | 
| 258 |     nativeIterators.clear(); | 
| 259 | #endif | 
| 260 |     fileEngineIterators.clear(); | 
| 261 |     visitedLinks.clear(); | 
| 262 |     pushDirectory(info&: initialEntryInfo); | 
| 263 | } | 
| 264 |  | 
| 265 | void QDirListingPrivate::pushDirectory(QDirEntryInfo &entryInfo) | 
| 266 | { | 
| 267 |     const QString path = [&entryInfo] { | 
| 268 | #ifdef Q_OS_WIN | 
| 269 |         if (entryInfo.isSymLink()) | 
| 270 |             return entryInfo.canonicalFilePath(); | 
| 271 | #endif | 
| 272 |         return entryInfo.filePath(); | 
| 273 |     }(); | 
| 274 |  | 
| 275 |  | 
| 276 |     if (iteratorFlags.testAnyFlags(flags: QDirListing::IteratorFlag::FollowDirSymlinks)) { | 
| 277 |         // Stop link loops | 
| 278 |         if (visitedLinks.hasSeen(s: entryInfo.canonicalFilePath())) | 
| 279 |             return; | 
| 280 |     } | 
| 281 |  | 
| 282 |     if (engine) { | 
| 283 |         engine->setFileName(path); | 
| 284 |         if (auto it = engine->beginEntryList(path, filters: iteratorFlags, filterNames: nameFilters)) { | 
| 285 |             fileEngineIterators.emplace_back(args: std::move(it)); | 
| 286 |         } else { | 
| 287 |             // No iterator; no entry list. | 
| 288 |         } | 
| 289 |     } else { | 
| 290 | #ifndef QT_NO_FILESYSTEMITERATOR | 
| 291 |         QFileSystemEntry *fentry = nullptr; | 
| 292 |         if (entryInfo.fileInfoOpt) | 
| 293 |             fentry = &entryInfo.fileInfoOpt->d_ptr->fileEntry; | 
| 294 |         else | 
| 295 |             fentry = &entryInfo.entry; | 
| 296 |         nativeIterators.emplace_back(args: std::make_unique<QFileSystemIterator>(args&: *fentry, args&: iteratorFlags)); | 
| 297 | #else | 
| 298 |         qWarning("Qt was built with -no-feature-filesystemiterator: no files/plugins will be found!" ); | 
| 299 | #endif | 
| 300 |     } | 
| 301 | } | 
| 302 |  | 
| 303 | bool QDirListingPrivate::entryMatches(QDirEntryInfo &entryInfo) | 
| 304 | { | 
| 305 |     checkAndPushDirectory(info&: entryInfo); | 
| 306 |     if (useLegacyFilters) | 
| 307 |         return matchesLegacyFilters(data&: entryInfo); | 
| 308 |     return matchesFilters(data&: entryInfo); | 
| 309 | } | 
| 310 |  | 
| 311 | /*! | 
| 312 |     \internal | 
| 313 |  | 
| 314 |     Advances the internal iterator, either a QAbstractFileEngineIterator (e.g. | 
| 315 |     QResourceFileEngineIterator) or a QFileSystemIterator (which uses low-level | 
| 316 |     system methods, e.g. readdir() on Unix). The iterators are stored in a | 
| 317 |     vector. | 
| 318 |  | 
| 319 |     A typical example of doing recursive iteration: | 
| 320 |     - while iterating directory A we find a sub-dir B | 
| 321 |     - an iterator for B is added to the vector | 
| 322 |     - B's iterator is processed (vector.back()) first; then the loop | 
| 323 |       goes back to processing A's iterator | 
| 324 | */ | 
| 325 | void QDirListingPrivate::advance() | 
| 326 | { | 
| 327 |     // Use get() in both code paths below because the iterator returned by back() | 
| 328 |     // may be invalidated due to reallocation when appending new iterators in | 
| 329 |     // pushDirectory(). | 
| 330 |  | 
| 331 |     if (engine) { | 
| 332 |         while (!fileEngineIterators.empty()) { | 
| 333 |             // Find the next valid iterator that matches the filters. | 
| 334 |             QAbstractFileEngineIterator *it; | 
| 335 |             while (it = fileEngineIterators.back().get(), it->advance()) { | 
| 336 |                 QDirEntryInfo entryInfo; | 
| 337 |                 entryInfo.fileInfoOpt = it->currentFileInfo(); | 
| 338 |                 if (entryMatches(entryInfo)) { | 
| 339 |                     currentEntryInfo = std::move(entryInfo); | 
| 340 |                     return; | 
| 341 |                 } | 
| 342 |             } | 
| 343 |  | 
| 344 |             fileEngineIterators.pop_back(); | 
| 345 |         } | 
| 346 |     } else { | 
| 347 | #ifndef QT_NO_FILESYSTEMITERATOR | 
| 348 |         QDirEntryInfo entryInfo; | 
| 349 |         while (!nativeIterators.empty()) { | 
| 350 |             // Find the next valid iterator that matches the filters. | 
| 351 |             QFileSystemIterator *it; | 
| 352 |             while (it = nativeIterators.back().get(), | 
| 353 |                    it->advance(fileEntry&: entryInfo.entry, metaData&: entryInfo.metaData)) { | 
| 354 |                 if (entryMatches(entryInfo)) { | 
| 355 |                     currentEntryInfo = std::move(entryInfo); | 
| 356 |                     return; | 
| 357 |                 } | 
| 358 |                 entryInfo = {}; | 
| 359 |             } | 
| 360 |  | 
| 361 |             nativeIterators.pop_back(); | 
| 362 |         } | 
| 363 | #endif | 
| 364 |     } | 
| 365 | } | 
| 366 |  | 
| 367 | static bool isDotOrDotDot(QStringView fileName) | 
| 368 | { | 
| 369 |     return fileName == "."_L1  || fileName == ".."_L1 ; | 
| 370 | } | 
| 371 |  | 
| 372 | void QDirListingPrivate::checkAndPushDirectory(QDirEntryInfo &entryInfo) | 
| 373 | { | 
| 374 |     using F = QDirListing::IteratorFlag; | 
| 375 |     // If we're doing flat iteration, we're done. | 
| 376 |     if (!iteratorFlags.testAnyFlags(flags: F::Recursive)) | 
| 377 |         return; | 
| 378 |  | 
| 379 |     // Follow symlinks only when asked | 
| 380 |     if (!iteratorFlags.testAnyFlags(flags: F::FollowDirSymlinks) && entryInfo.isSymLink()) | 
| 381 |         return; | 
| 382 |  | 
| 383 |     // Never follow . and .. | 
| 384 |     if (isDotOrDotDot(fileName: entryInfo.fileName())) | 
| 385 |         return; | 
| 386 |  | 
| 387 |     // No hidden directories unless requested | 
| 388 |     const bool includeHidden = [this]() { | 
| 389 |         if (useLegacyFilters) | 
| 390 |             return legacyDirFilters.testAnyFlags(flags: QDir::AllDirs | QDir::Hidden); | 
| 391 |         return iteratorFlags.testAnyFlags(flags: QDirListing::IteratorFlag::IncludeHidden); | 
| 392 |     }(); | 
| 393 |     if (!includeHidden && entryInfo.isHidden()) | 
| 394 |         return; | 
| 395 |  | 
| 396 |     // Never follow non-directory entries | 
| 397 |     if (!entryInfo.isDir()) | 
| 398 |         return; | 
| 399 |  | 
| 400 |     pushDirectory(entryInfo); | 
| 401 | } | 
| 402 |  | 
| 403 | /*! | 
| 404 |     \internal | 
| 405 |  | 
| 406 |     Works the same as matchesFilters() but for the old QDir::Filters. | 
| 407 | */ | 
| 408 | bool QDirListingPrivate::matchesLegacyFilters(QDirEntryInfo &entryInfo) const | 
| 409 | { | 
| 410 |     Q_ASSERT(useLegacyFilters); | 
| 411 |  | 
| 412 |     const QString &fileName = entryInfo.fileName(); | 
| 413 |     if (fileName.isEmpty()) | 
| 414 |         return false; | 
| 415 |  | 
| 416 |     auto &filters = legacyDirFilters; | 
| 417 |  | 
| 418 |     // filter . and ..? | 
| 419 |     const bool dotOrDotDot = isDotOrDotDot(fileName); | 
| 420 |     const qsizetype fileNameSize = fileName.size(); | 
| 421 |     if ((filters & QDir::NoDot) && dotOrDotDot && fileNameSize == 1) | 
| 422 |         return false; | 
| 423 |     if ((filters & QDir::NoDotDot) && dotOrDotDot && fileNameSize == 2) | 
| 424 |         return false; | 
| 425 |  | 
| 426 |     // name filter | 
| 427 | #if QT_CONFIG(regularexpression) | 
| 428 |     // Pass all entries through name filters, except dirs if AllDirs is set | 
| 429 |     if (!(filters.testAnyFlags(flags: QDir::AllDirs) && entryInfo.isDir())) { | 
| 430 |         if (!regexMatchesName(fileName)) | 
| 431 |             return false; | 
| 432 |     } | 
| 433 | #endif | 
| 434 |     // skip symlinks | 
| 435 |     const bool skipSymlinks = filters.testAnyFlag(flag: QDir::NoSymLinks); | 
| 436 |     const bool includeSystem = filters.testAnyFlag(flag: QDir::System); | 
| 437 |     if (skipSymlinks && entryInfo.isSymLink()) { | 
| 438 |         // The only reason to save this file is if it is a broken link and we are requesting system files. | 
| 439 |         if (!includeSystem || entryInfo.exists()) | 
| 440 |             return false; | 
| 441 |     } | 
| 442 |  | 
| 443 |     // filter hidden | 
| 444 |     const bool includeHidden = filters.testAnyFlag(flag: QDir::Hidden); | 
| 445 |     if (!includeHidden && !dotOrDotDot && entryInfo.isHidden()) | 
| 446 |         return false; | 
| 447 |  | 
| 448 |     // filter system files | 
| 449 |     if (!includeSystem) { | 
| 450 |         if (!entryInfo.isFile() && !entryInfo.isDir() && !entryInfo.isSymLink()) | 
| 451 |             return false; | 
| 452 |         if (entryInfo.isSymLink() && !entryInfo.exists()) | 
| 453 |             return false; | 
| 454 |     } | 
| 455 |  | 
| 456 |     // skip directories | 
| 457 |     const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs)); | 
| 458 |     if (skipDirs && entryInfo.isDir()) | 
| 459 |         return false; | 
| 460 |  | 
| 461 |     // skip files | 
| 462 |     const bool skipFiles    = !(filters & QDir::Files); | 
| 463 |     if (skipFiles && entryInfo.isFile()) | 
| 464 |         // Basically we need a reason not to exclude this file otherwise we just eliminate it. | 
| 465 |         return false; | 
| 466 |  | 
| 467 |     // filter permissions | 
| 468 |     const auto perms = filters & QDir::PermissionMask; | 
| 469 |     const bool filterPermissions = perms != 0 && perms != QDir::PermissionMask; | 
| 470 |     if (filterPermissions) { | 
| 471 |         const bool doWritable = filters.testAnyFlags(flags: QDir::Writable); | 
| 472 |         const bool doExecutable = filters.testAnyFlags(flags: QDir::Executable); | 
| 473 |         const bool doReadable = filters.testAnyFlags(flags: QDir::Readable); | 
| 474 |         if ((doReadable && !entryInfo.isReadable()) | 
| 475 |             || (doWritable && !entryInfo.isWritable()) | 
| 476 |             || (doExecutable && !entryInfo.isExecutable())) { | 
| 477 |             return false; | 
| 478 |         } | 
| 479 |     } | 
| 480 |  | 
| 481 |     return true; | 
| 482 | } | 
| 483 |  | 
| 484 | /*! | 
| 485 |     \internal | 
| 486 |  | 
| 487 |     This function returns \c true if the current entry matches the filters | 
| 488 |     (i.e., the current entry will be returned as part of the directory | 
| 489 |     iteration); otherwise, \c false is returned. | 
| 490 | */ | 
| 491 | bool QDirListingPrivate::matchesFilters(QDirEntryInfo &entryInfo) const | 
| 492 | { | 
| 493 |     using F = QDirListing::IteratorFlag; | 
| 494 |  | 
| 495 |     const QString &fileName = entryInfo.fileName(); | 
| 496 |     if (fileName.isEmpty()) | 
| 497 |         return false; | 
| 498 |  | 
| 499 |     if (isDotOrDotDot(fileName)) // All done, other checks below don't matter in this case | 
| 500 |         return iteratorFlags.testAnyFlags(flags: F::IncludeDotAndDotDot); | 
| 501 |  | 
| 502 |     // name filter | 
| 503 | #if QT_CONFIG(regularexpression) | 
| 504 |     if (!regexMatchesName(fileName)) | 
| 505 |         return false; | 
| 506 | #endif // QT_CONFIG(regularexpression) | 
| 507 |  | 
| 508 |     if (!iteratorFlags.testAnyFlag(flag: F::IncludeHidden) && entryInfo.isHidden()) | 
| 509 |         return false; | 
| 510 |  | 
| 511 |     if (entryInfo.isSymLink()) { | 
| 512 |         // With ResolveSymlinks, we look at the type of the link's target, | 
| 513 |         // and exclude broken symlinks (where the target doesn't exist). | 
| 514 |         if (iteratorFlags.testAnyFlag(flag: F::ResolveSymlinks)) { | 
| 515 |             if (!entryInfo.exists()) | 
| 516 |                 return false; | 
| 517 |         } else if (iteratorFlags.testAnyFlags(flags: F::FilesOnly) | 
| 518 |                    || iteratorFlags.testAnyFlags(flags: F::DirsOnly)) { | 
| 519 |             return false; // symlink is not a file or dir | 
| 520 |         } | 
| 521 |     } | 
| 522 |  | 
| 523 |     if (iteratorFlags.testAnyFlag(flag: F::ExcludeSpecial) | 
| 524 |         && !entryInfo.isFile() && !entryInfo.isDir() && !entryInfo.isSymLink()) { | 
| 525 |         return false; | 
| 526 |     } | 
| 527 |  | 
| 528 |     if (iteratorFlags.testAnyFlags(flags: F::ExcludeDirs) && entryInfo.isDir()) | 
| 529 |         return false; | 
| 530 |  | 
| 531 |     if (iteratorFlags.testAnyFlags(flags: F::ExcludeFiles) && entryInfo.isFile()) | 
| 532 |         return false; | 
| 533 |  | 
| 534 |     return true; | 
| 535 | } | 
| 536 |  | 
| 537 | bool QDirListingPrivate::hasIterators() const | 
| 538 | { | 
| 539 |     if (engine) | 
| 540 |         return !fileEngineIterators.empty(); | 
| 541 |  | 
| 542 | #if !defined(QT_NO_FILESYSTEMITERATOR) | 
| 543 |     return !nativeIterators.empty(); | 
| 544 | #endif | 
| 545 |  | 
| 546 |     return false; | 
| 547 | } | 
| 548 |  | 
| 549 | /*! | 
| 550 |     Constructs a QDirListing that can iterate over \a path. | 
| 551 |  | 
| 552 |     You can pass options via \a flags to control how the directory should | 
| 553 |     be iterated. | 
| 554 |  | 
| 555 |     By default, \a flags is IteratorFlag::Default. | 
| 556 |  | 
| 557 |     \sa IteratorFlags | 
| 558 | */ | 
| 559 | QDirListing::QDirListing(const QString &path, IteratorFlags flags) | 
| 560 |     : d(new QDirListingPrivate) | 
| 561 | { | 
| 562 |     d->initialEntryInfo.entry = QFileSystemEntry(path); | 
| 563 |     d->iteratorFlags = flags; | 
| 564 |     d->init(); | 
| 565 | } | 
| 566 |  | 
| 567 | /*! | 
| 568 |     Constructs a QDirListing that can iterate over \a path. | 
| 569 |  | 
| 570 |     You can pass options via \a flags to control how the directory should | 
| 571 |     be iterated. By default, \a flags is IteratorFlag::Default. | 
| 572 |  | 
| 573 |     The listed entries will be filtered according to the file glob patterns | 
| 574 |     in \a nameFilters (see QDir::setNameFilters() for more details). | 
| 575 |  | 
| 576 |     For example, the following iterator could be used to iterate over audio | 
| 577 |     files: | 
| 578 |  | 
| 579 |     \snippet code/src_corelib_io_qdirlisting.cpp 2 | 
| 580 |  | 
| 581 |     \sa IteratorFlags, QDir::setNameFilters() | 
| 582 | */ | 
| 583 | QDirListing::QDirListing(const QString &path, const QStringList &nameFilters, IteratorFlags flags) | 
| 584 |     : d(new QDirListingPrivate) | 
| 585 | { | 
| 586 |     d->initialEntryInfo.entry = QFileSystemEntry(path); | 
| 587 |     d->nameFilters = nameFilters; | 
| 588 |     d->iteratorFlags = flags; | 
| 589 |     d->init(); | 
| 590 | } | 
| 591 |  | 
| 592 | /*! | 
| 593 |     \internal | 
| 594 |  | 
| 595 |     Only used by classes that still have to use QDir::Filters; for example, | 
| 596 |     QDir, such usage may be deprecated at some point. | 
| 597 |  | 
| 598 |     \a qdirFilters is converted to QDir::Filters and \a qdirIteratorFlags is | 
| 599 |     converted to QDirIterator::IteratorFlags (qdirlisting.h can't include | 
| 600 |     qdir.h or qdiriterator.h) and used to control the filtering of the | 
| 601 |     dir entries. | 
| 602 | */ | 
| 603 | QDirListing::QDirListing(const QString &path, const QStringList &nameFilters, uint qdirFilters, | 
| 604 |                          uint qdirIteratorFlags) | 
| 605 |     : d(new QDirListingPrivate) | 
| 606 | { | 
| 607 |     d->initialEntryInfo.entry = QFileSystemEntry(path); | 
| 608 |     d->nameFilters = nameFilters; | 
| 609 |     d->setLegacyFilters(dirFilters: QDir::Filters::fromInt(i: qdirFilters), | 
| 610 |                         dirIteratorFlags: QDirIterator::IteratorFlags::fromInt(i: qdirIteratorFlags)); | 
| 611 |     d->init(); | 
| 612 | } | 
| 613 |  | 
| 614 | /*! | 
| 615 |     \fn QDirListing::QDirListing(QDirListing &&other) | 
| 616 |  | 
| 617 |     Move constructor. Moves \a other into this QDirListing. | 
| 618 |  | 
| 619 | //! [partially-formed] | 
| 620 |     \note The moved-from object \a other is placed in a partially-formed state, | 
| 621 |     in which the only valid operations are destruction and assignment of a new | 
| 622 |     value. | 
| 623 | //! [partially-formed] | 
| 624 | */ | 
| 625 |  | 
| 626 | /*! | 
| 627 |     \fn QDirListing &QDirListing::operator=(QDirListing &&other) | 
| 628 |  | 
| 629 |     Move-assigns \a other to this QDirListing. | 
| 630 |  | 
| 631 |     \include qdirlisting.cpp partially-formed | 
| 632 | */ | 
| 633 |  | 
| 634 | /*! | 
| 635 |     Destroys the QDirListing. | 
| 636 | */ | 
| 637 | QDirListing::~QDirListing() | 
| 638 | { | 
| 639 |     delete d; | 
| 640 | } | 
| 641 |  | 
| 642 | /*! | 
| 643 |     Returns the directory path used to construct this QDirListing. | 
| 644 | */ | 
| 645 | QString QDirListing::iteratorPath() const | 
| 646 | { | 
| 647 |     return d->initialEntryInfo.filePath(); | 
| 648 | } | 
| 649 |  | 
| 650 | /*! | 
| 651 |     Returns the set of IteratorFlags used to construct this QDirListing. | 
| 652 | */ | 
| 653 | QDirListing::IteratorFlags QDirListing::iteratorFlags() const | 
| 654 | { | 
| 655 |     return d->iteratorFlags; | 
| 656 | } | 
| 657 |  | 
| 658 | /*! | 
| 659 |     Returns the list of file name glob filters used to construct this | 
| 660 |     QDirListing. | 
| 661 | */ | 
| 662 | QStringList QDirListing::nameFilters() const | 
| 663 | { | 
| 664 |     return d->nameFilters; | 
| 665 | } | 
| 666 |  | 
| 667 | /*! | 
| 668 |     \class QDirListing::const_iterator | 
| 669 |     \since 6.8 | 
| 670 |     \inmodule QtCore | 
| 671 |     \ingroup  io | 
| 672 |  | 
| 673 |     The iterator type returned by QDirListing::cbegin(). | 
| 674 |  | 
| 675 | //! [dirlisting-iterator-behavior] | 
| 676 |     \list | 
| 677 |     \li This is a forward-only, single-pass iterator (you cannot iterate | 
| 678 |         directory entries in reverse order) | 
| 679 |     \li Can't be copied, only \c{std::move()}d. | 
| 680 |     \li \include qdirlisting.cpp post-increment-partially-formed | 
| 681 |     \li Doesn't allow random access | 
| 682 |     \li Can be used in ranged-for loops; or with C++20 std::ranges algorithms | 
| 683 |         that don't require random access iterators | 
| 684 |     \li Dereferencing a valid iterator returns a \c{const DirEntry &} | 
| 685 |     \li (c)end() returns a \l QDirListing::sentinel that signals the end of | 
| 686 |         the iteration. Dereferencing an iterator that compares equal to end() | 
| 687 |         is undefined behavior | 
| 688 |     \endlist | 
| 689 | //! [dirlisting-iterator-behavior] | 
| 690 |  | 
| 691 |     \include qdirlisting.cpp ranges-algorithms-note | 
| 692 |  | 
| 693 |     \sa QDirListing, QDirListing::sentinel, QDirListing::DirEntry | 
| 694 | */ | 
| 695 |  | 
| 696 | /*! | 
| 697 |     \typealias QDirListing::const_iterator::reference | 
| 698 |  | 
| 699 |     A typedef for \c {const QDirListing::DirEntry &}. | 
| 700 | */ | 
| 701 |  | 
| 702 | /*! | 
| 703 |     \typealias QDirListing::const_iterator::pointer | 
| 704 |  | 
| 705 |     A typedef for \c {const QDirListing::DirEntry *}. | 
| 706 | */ | 
| 707 |  | 
| 708 | /*! | 
| 709 |     \class QDirListing::sentinel | 
| 710 |     \since 6.8 | 
| 711 |     \inmodule QtCore | 
| 712 |     \ingroup  io | 
| 713 |  | 
| 714 |     \l QDirListing returns an object of this type to signal the end of | 
| 715 |     iteration. Dereferencing a \l QDirListing::const_iterator that is | 
| 716 |     equal to \c sentinel{} is undefined behavior. | 
| 717 |  | 
| 718 |     \include qdirlisting.cpp ranges-algorithms-note | 
| 719 |  | 
| 720 |     \sa QDirListing, QDirListing::const_iterator, QDirListing::DirEntry | 
| 721 | */ | 
| 722 |  | 
| 723 | /*! | 
| 724 |     \fn QDirListing::const_iterator QDirListing::begin() const | 
| 725 |     \fn QDirListing::const_iterator QDirListing::cbegin() const | 
| 726 |     \fn QDirListing::sentinel QDirListing::end() const | 
| 727 |     \fn QDirListing::sentinel QDirListing::cend() const | 
| 728 |  | 
| 729 |     (c)begin() returns a QDirListing::const_iterator that can be used to | 
| 730 |     iterate over directory entries. | 
| 731 |  | 
| 732 |     \include qdirlisting.cpp dirlisting-iterator-behavior | 
| 733 |  | 
| 734 |     \note Each time (c)begin() is called on the same QDirListing object, | 
| 735 |     the internal state is reset and the iteration starts anew. | 
| 736 |  | 
| 737 |     (Some of the above restrictions are dictated by the underlying system | 
| 738 |     library functions' implementation). | 
| 739 |  | 
| 740 |     For example: | 
| 741 |     \snippet code/src_corelib_io_qdirlisting.cpp 0 | 
| 742 |  | 
| 743 |     Here's how to find and read all files filtered by name, recursively: | 
| 744 |     \snippet code/src_corelib_io_qdirlisting.cpp 1 | 
| 745 |  | 
| 746 | //! [ranges-algorithms-note] | 
| 747 |     \note The "classical" STL algorithms don't support iterator/sentinel, so | 
| 748 |     you need to use C++20 std::ranges algorithms for QDirListing, or else a | 
| 749 |     3rd-party library that provides range-based algorithms in C++17. | 
| 750 | //! [ranges-algorithms-note] | 
| 751 |  | 
| 752 |     \sa QDirListing::DirEntry | 
| 753 | */ | 
| 754 | QDirListing::const_iterator QDirListing::begin() const | 
| 755 | { | 
| 756 |     d->beginIterating(); | 
| 757 |     const_iterator it{d}; | 
| 758 |     ++it; | 
| 759 |     return it; | 
| 760 | } | 
| 761 |  | 
| 762 | /*! | 
| 763 |     \fn const QDirListing::DirEntry &QDirListing::const_iterator::operator*() const | 
| 764 |  | 
| 765 |     Returns a \c{const  QDirListing::DirEntry &} of the directory entry this | 
| 766 |     iterator points to. | 
| 767 | */ | 
| 768 |  | 
| 769 | /*! | 
| 770 |     \fn const QDirListing::DirEntry *QDirListing::const_iterator::operator->() const | 
| 771 |  | 
| 772 |     Returns a \c{const QDirListing::DirEntry *} to the directory entry this | 
| 773 |     iterator points to. | 
| 774 | */ | 
| 775 |  | 
| 776 | /*! | 
| 777 |     \fn QDirListing::const_iterator::operator++() | 
| 778 |  | 
| 779 |     Pre-increment operator. | 
| 780 |     Advances the iterator and returns a reference to it. | 
| 781 | */ | 
| 782 |  | 
| 783 | /*! | 
| 784 |     \fn void QDirListing::const_iterator::operator++(int) | 
| 785 |  | 
| 786 |     Post-increment operator. | 
| 787 |  | 
| 788 |     \include qdirlisting.cpp std-input-iterator-tag | 
| 789 |  | 
| 790 | //! [post-increment-partially-formed] | 
| 791 |     The return value of post-increment on objects that model | 
| 792 |     \c std::input_iterator is partially-formed (a copy of an iterator that | 
| 793 |     has since been advanced), the only valid operations on such an object | 
| 794 |     are destruction and assignment of a new iterator. Therefore the | 
| 795 |     post-increment operator advances the iterator and returns \c void. | 
| 796 | //! [post-increment-partially-formed] | 
| 797 | */ | 
| 798 |  | 
| 799 | /*! | 
| 800 |     \internal | 
| 801 |  | 
| 802 |     Implements the actual advancing. Not a member function to avoid forcing | 
| 803 |     DirEntry objects (and therefore const_iterator ones) onto the stack. | 
| 804 | */ | 
| 805 | auto QDirListing::next(DirEntry dirEntry) -> DirEntry | 
| 806 | { | 
| 807 |     dirEntry.dirListPtr->advance(); | 
| 808 |     if (!dirEntry.dirListPtr->hasIterators()) | 
| 809 |         return {}; // All done, make `this` equal to the end() iterator | 
| 810 |     return dirEntry; | 
| 811 | } | 
| 812 |  | 
| 813 | /*! | 
| 814 |     \class QDirListing::DirEntry | 
| 815 |     \inmodule QtCore | 
| 816 |     \ingroup  io | 
| 817 |  | 
| 818 |     Dereferencing a valid QDirListing::const_iterator returns a DirEntry | 
| 819 |     object. | 
| 820 |  | 
| 821 |     DirEntry offers a subset of QFileInfo's API (for example, fileName(), | 
| 822 |     filePath(), exists()). Internally, DirEntry only constructs a QFileInfo | 
| 823 |     object if needed, that is, if the info hasn't been already fetched | 
| 824 |     by other system functions. You can use DirEntry::fileInfo() to get a | 
| 825 |     QFileInfo. For example: | 
| 826 |  | 
| 827 |     \snippet code/src_corelib_io_qdirlisting.cpp 3 | 
| 828 |  | 
| 829 |     \snippet code/src_corelib_io_qdirlisting.cpp 4 | 
| 830 | */ | 
| 831 |  | 
| 832 | /*! | 
| 833 |     \fn QFileInfo QDirListing::DirEntry::fileInfo() const | 
| 834 |     \fn QString QDirListing::DirEntry::fileName() const | 
| 835 |     \fn QString QDirListing::DirEntry::baseName() const | 
| 836 |     \fn QString QDirListing::DirEntry::completeBaseName() const | 
| 837 |     \fn QString QDirListing::DirEntry::suffix() const | 
| 838 |     \fn QString QDirListing::DirEntry::bundleName() const | 
| 839 |     \fn QString QDirListing::DirEntry::completeSuffix() const | 
| 840 |     \fn QString QDirListing::DirEntry::filePath() const | 
| 841 |     \fn QString QDirListing::DirEntry::canonicalFilePath() const | 
| 842 |     \fn QString QDirListing::DirEntry::absoluteFilePath() const | 
| 843 |     \fn QString QDirListing::DirEntry::absolutePath() const | 
| 844 |     \fn bool QDirListing::DirEntry::isDir() const | 
| 845 |     \fn bool QDirListing::DirEntry::isFile() const | 
| 846 |     \fn bool QDirListing::DirEntry::isSymLink() const | 
| 847 |     \fn bool QDirListing::DirEntry::exists() const | 
| 848 |     \fn bool QDirListing::DirEntry::isHidden() const | 
| 849 |     \fn bool QDirListing::DirEntry::isReadable() const | 
| 850 |     \fn bool QDirListing::DirEntry::isWritable() const | 
| 851 |     \fn bool QDirListing::DirEntry::isExecutable() const | 
| 852 |     \fn qint64 QDirListing::DirEntry::size() const | 
| 853 |     \fn QDateTime QDirListing::DirEntry::fileTime(QFile::FileTime type, const QTimeZone &tz) const | 
| 854 |     \fn QDateTime QDirListing::DirEntry::birthTime(const QTimeZone &tz) const; | 
| 855 |     \fn QDateTime QDirListing::DirEntry::metadataChangeTime(const QTimeZone &tz) const; | 
| 856 |     \fn QDateTime QDirListing::DirEntry::lastModified(const QTimeZone &tz) const; | 
| 857 |     \fn QDateTime QDirListing::DirEntry::lastRead(const QTimeZone &tz) const; | 
| 858 |  | 
| 859 |     See the QFileInfo methods with the same names. | 
| 860 | */ | 
| 861 |  | 
| 862 | QFileInfo QDirListing::DirEntry::fileInfo() const | 
| 863 | { | 
| 864 |     return dirListPtr->currentEntryInfo.fileInfo(); | 
| 865 | } | 
| 866 |  | 
| 867 | QString QDirListing::DirEntry::fileName() const | 
| 868 | { | 
| 869 |     return dirListPtr->currentEntryInfo.fileName(); | 
| 870 | } | 
| 871 |  | 
| 872 | QString QDirListing::DirEntry::baseName() const | 
| 873 | { | 
| 874 |     return dirListPtr->currentEntryInfo.baseName(); | 
| 875 | } | 
| 876 |  | 
| 877 | QString QDirListing::DirEntry::completeBaseName() const | 
| 878 | { | 
| 879 |     return dirListPtr->currentEntryInfo.completeBaseName(); | 
| 880 | } | 
| 881 |  | 
| 882 | QString QDirListing::DirEntry::suffix() const | 
| 883 | { | 
| 884 |     return dirListPtr->currentEntryInfo.suffix(); | 
| 885 | } | 
| 886 |  | 
| 887 | QString QDirListing::DirEntry::bundleName() const | 
| 888 | { | 
| 889 |     return dirListPtr->currentEntryInfo.bundleName(); | 
| 890 | } | 
| 891 |  | 
| 892 | QString QDirListing::DirEntry::completeSuffix() const | 
| 893 | { | 
| 894 |     return dirListPtr->currentEntryInfo.completeSuffix(); | 
| 895 | } | 
| 896 |  | 
| 897 | QString QDirListing::DirEntry::filePath() const | 
| 898 | { | 
| 899 |     return dirListPtr->currentEntryInfo.filePath(); | 
| 900 | } | 
| 901 |  | 
| 902 | QString QDirListing::DirEntry::canonicalFilePath() const | 
| 903 | { | 
| 904 |     return dirListPtr->currentEntryInfo.canonicalFilePath(); | 
| 905 | } | 
| 906 |  | 
| 907 | QString QDirListing::DirEntry::absoluteFilePath() const | 
| 908 | { | 
| 909 |     return dirListPtr->currentEntryInfo.absoluteFilePath(); | 
| 910 | } | 
| 911 |  | 
| 912 | QString QDirListing::DirEntry::absolutePath() const | 
| 913 | { | 
| 914 |     return dirListPtr->currentEntryInfo.absolutePath(); | 
| 915 | } | 
| 916 |  | 
| 917 | bool QDirListing::DirEntry::isDir() const | 
| 918 | { | 
| 919 |     return dirListPtr->currentEntryInfo.isDir(); | 
| 920 | } | 
| 921 |  | 
| 922 | bool QDirListing::DirEntry::isFile() const | 
| 923 | { | 
| 924 |     return dirListPtr->currentEntryInfo.isFile(); | 
| 925 | } | 
| 926 |  | 
| 927 | bool QDirListing::DirEntry::isSymLink() const | 
| 928 | { | 
| 929 |     return dirListPtr->currentEntryInfo.isSymLink(); | 
| 930 | } | 
| 931 |  | 
| 932 | bool QDirListing::DirEntry::exists() const | 
| 933 | { | 
| 934 |     return dirListPtr->currentEntryInfo.exists(); | 
| 935 | } | 
| 936 |  | 
| 937 | bool QDirListing::DirEntry::isHidden() const | 
| 938 | { | 
| 939 |     return dirListPtr->currentEntryInfo.isHidden(); | 
| 940 | } | 
| 941 |  | 
| 942 | bool QDirListing::DirEntry::isReadable() const | 
| 943 | { | 
| 944 |     return dirListPtr->currentEntryInfo.isReadable(); | 
| 945 | } | 
| 946 |  | 
| 947 | bool QDirListing::DirEntry::isWritable() const | 
| 948 | { | 
| 949 |     return dirListPtr->currentEntryInfo.isWritable(); | 
| 950 | } | 
| 951 |  | 
| 952 | bool QDirListing::DirEntry::isExecutable() const | 
| 953 | { | 
| 954 |     return dirListPtr->currentEntryInfo.isExecutable(); | 
| 955 | } | 
| 956 |  | 
| 957 | qint64 QDirListing::DirEntry::size() const | 
| 958 | { | 
| 959 |     return dirListPtr->currentEntryInfo.size(); | 
| 960 | } | 
| 961 |  | 
| 962 | QDateTime QDirListing::DirEntry::fileTime(QFile::FileTime type, const QTimeZone &tz) const | 
| 963 | { | 
| 964 |     return dirListPtr->currentEntryInfo.fileTime(type, tz); | 
| 965 | } | 
| 966 |  | 
| 967 | QT_END_NAMESPACE | 
| 968 |  |