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