Add QDirListing, an STL-style iterator for directory entries

This class offers a forward-only const_iterator, that matches the system
low-level functions' logic (e.g. readdir()/dirstream logic). This
iterator is a std::input_iterator_tag.

QDirIterator uses Java-style iterators that have a couple of issues:
- They don't fit the logic of the underlying native system functions
  (readdir()/__dirstream and co.), there is no way to know if there is a
  next entry except by advancing the iterator (calling readdir()) first
- As a consequence of the above, two QFileInfo objects, current and next,
  had to be used to fit that paradigm; and the code always
  iterated/stat'ed an extra entry past the one we want, e.g. when
  filtering

The next step is porting QAbstractFileEngineIterator and its subclasses
to be like QFileSystemIterator, i.e. replace hasNext()/next() with a `bool
advance()` virtual method. This is easier to reason about than the
Java-style iterators, and is more in-line with the new class.

Discussed-on: https://lists.qt-project.org/pipermail/development/2023-December/044745.html
Change-Id: I8e696cefdca18d8c78f803efdb83a73dd43eb720
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Ahmad Samir 2024-01-30 15:22:30 +02:00
parent 78c33a7741
commit c39a0d1e89
12 changed files with 722 additions and 382 deletions

View File

@ -116,6 +116,7 @@ qt_internal_add_module(Core
io/qdataurl.cpp io/qdataurl_p.h io/qdataurl.cpp io/qdataurl_p.h
io/qdebug.cpp io/qdebug.h io/qdebug_p.h io/qdebug.cpp io/qdebug.h io/qdebug_p.h
io/qdir.cpp io/qdir.h io/qdir_p.h io/qdir.cpp io/qdir.h io/qdir_p.h
io/qdirlisting.cpp io/qdirlisting.h io/qdirentryinfo_p.h
io/qdiriterator.cpp io/qdiriterator.h io/qdiriterator.cpp io/qdiriterator.h
io/qfile.cpp io/qfile.h io/qfile_p.h io/qfile.cpp io/qfile.h io/qfile_p.h
io/qfiledevice.cpp io/qfiledevice.h io/qfiledevice_p.h io/qfiledevice.cpp io/qfiledevice.h io/qfiledevice_p.h

View File

@ -1,11 +1,16 @@
// Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QDirListing>
using namespace Qt::StringLiterals;
[[maybe_unused]] static void func() {
{
//! [0] //! [0]
QDirIterator it("/etc", QDirIterator::Subdirectories); using ItFlag = QDirListing::IteratorFlag;
while (it.hasNext()) { for (const auto &dirEntry : QDirListing(u"/etc"_s, ItFlag::Recursive)) {
QString dir = it.next(); qDebug() << dirEntry.filePath();
qDebug() << dir;
// /etc/. // /etc/.
// /etc/.. // /etc/..
// /etc/X11 // /etc/X11
@ -13,16 +18,53 @@ while (it.hasNext()) {
// ... // ...
} }
//! [0] //! [0]
}
{
//! [1] //! [1]
QDirIterator it("/sys", QStringList() << "scaling_cur_freq", QDir::NoFilter, QDirIterator::Subdirectories); using ItFlag = QDirListing::IteratorFlag;
while (it.hasNext()) { QDirListing dirList(u"/sys"_s, QStringList{u"scaling_cur_freq"_s},
QFile f(it.next()); QDir::NoFilter, ItFlag::Recursive);
for (const auto &dirEntry : dirList) {
QFile f(dirEntry.filePath());
f.open(QIODevice::ReadOnly); f.open(QIODevice::ReadOnly);
qDebug() << f.fileName() << f.readAll().trimmed().toDouble() / 1000 << "MHz"; qDebug() << f.fileName() << f.readAll().trimmed().toDouble() / 1000 << "MHz";
} }
//! [1] //! [1]
}
{
//! [2] //! [2]
QDirIterator audioFileIt(audioPath, {"*.mp3", "*.wav"}, QDir::Files); QDirListing audioFileIt(u"/home/johndoe/"_s, {"*.mp3", "*.wav"}, QDir::Files);
//! [2] //! [2]
}
{
//! [3]
using ItFlag = QDirListing::IteratorFlag;
for (const auto &dirEntry : QDirListing(u"/etc"_s, ItFlag::Recursive)) {
// Faster
if (dirEntry.fileName().endsWith(u".conf")) { /* ... */ }
// This works, but might be potentially slower, since it has to construct a
// QFileInfo, whereas (depending on the implemnetation) the fileName could
// be known already
if (dirEntry.fileInfo().fileName().endsWith(u".conf")) { /* ... */ }
}
//! [3]
}
{
//! [4]
using ItFlag = QDirListing::IteratorFlag;
for (const auto &dirEntry : QDirListing(u"/etc"_s, ItFlag::Recursive)) {
// Both approaches are the same, because DirEntry will have to construct
// a QFileInfo to get this info (for example, by calling system stat())
if (dirEntry.size() >= 4'000 /* 4KB */) { /* ...*/ }
if (dirEntry.fileInfo().size() >= 4'000 /* 4KB */) { /* ... */ }
}
//! [4]
}
}

View File

@ -218,6 +218,7 @@ private:
Q_DISABLE_COPY_MOVE(QAbstractFileEngineIterator) Q_DISABLE_COPY_MOVE(QAbstractFileEngineIterator)
friend class QDirIterator; friend class QDirIterator;
friend class QDirIteratorPrivate; friend class QDirIteratorPrivate;
friend class QDirListingPrivate;
void setPath(const QString &path); void setPath(const QString &path);
QScopedPointer<QAbstractFileEngineIteratorPrivate> d; QScopedPointer<QAbstractFileEngineIteratorPrivate> d;
}; };

View File

@ -243,6 +243,7 @@ private:
friend Q_CORE_EXPORT bool comparesEqual(const QDir &lhs, const QDir &rhs); friend Q_CORE_EXPORT bool comparesEqual(const QDir &lhs, const QDir &rhs);
Q_DECLARE_EQUALITY_COMPARABLE(QDir) Q_DECLARE_EQUALITY_COMPARABLE(QDir)
friend class QDirIterator; friend class QDirIterator;
friend class QDirListing;
// Q_DECLARE_PRIVATE equivalent for shared data pointers // Q_DECLARE_PRIVATE equivalent for shared data pointers
QDirPrivate *d_func(); QDirPrivate *d_func();
const QDirPrivate *d_func() const { return d_ptr.constData(); } const QDirPrivate *d_func() const { return d_ptr.constData(); }

View File

@ -0,0 +1,156 @@
// Copyright (C) 2024 Ahmad Samir <a.samirh78@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QDIRENTRYINFO_P_H
#define QDIRENTRYINFO_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/private/qfileinfo_p.h>
#include <QtCore/private/qfilesystementry_p.h>
#include <QtCore/private/qfilesystemmetadata_p.h>
QT_BEGIN_NAMESPACE
class QDirEntryInfo
{
const QFileSystemMetaData &ensureFilled(QFileSystemMetaData::MetaDataFlags what)
{
if (!metaData.hasFlags(what))
QFileSystemEngine::fillMetaData(entry, metaData, what);
return metaData;
}
public:
const QFileInfo &fileInfo()
{
if (!fileInfoOpt) {
fileInfoOpt.emplace(new QFileInfoPrivate(entry, metaData));
metaData.clear();
}
return *fileInfoOpt;
}
QString fileName()
{ return fileInfoOpt ? fileInfoOpt->fileName() : entry.fileName(); }
QString baseName()
{ return fileInfoOpt ? fileInfoOpt->baseName() : entry.baseName(); }
QString completeBaseName() const
{ return fileInfoOpt ? fileInfoOpt->completeBaseName() : entry.completeBaseName(); }
QString suffix() const
{ return fileInfoOpt ? fileInfoOpt->suffix() : entry.suffix(); }
QString completeSuffix() const
{ return fileInfoOpt ? fileInfoOpt->completeSuffix() : entry.completeSuffix(); }
QString filePath()
{ return fileInfoOpt ? fileInfoOpt->filePath() : entry.filePath(); }
QString bundleName() { return fileInfo().bundleName(); }
QString canonicalFilePath()
{
// QFileInfo caches these strings
return fileInfo().canonicalFilePath();
}
QString absoluteFilePath() {
// QFileInfo caches these strings
return fileInfo().absoluteFilePath();
}
QString absolutePath() {
// QFileInfo caches these strings
return fileInfo().absolutePath();
}
bool isDir() {
if (fileInfoOpt)
return fileInfoOpt->isDir();
return ensureFilled(QFileSystemMetaData::DirectoryType).isDirectory();
}
bool isFile() {
if (fileInfoOpt)
return fileInfoOpt->isFile();
return ensureFilled(QFileSystemMetaData::FileType).isFile();
}
bool isSymLink() {
if (fileInfoOpt)
return fileInfoOpt->isSymLink();
return ensureFilled(QFileSystemMetaData::LegacyLinkType).isLegacyLink();
}
bool isSymbolicLink() {
if (fileInfoOpt)
return fileInfoOpt->isSymbolicLink();
return ensureFilled(QFileSystemMetaData::LinkType).isLink();
}
bool exists() {
if (fileInfoOpt)
return fileInfoOpt->exists();
return ensureFilled(QFileSystemMetaData::ExistsAttribute).exists();
}
bool isHidden() {
if (fileInfoOpt)
return fileInfoOpt->isHidden();
return ensureFilled(QFileSystemMetaData::HiddenAttribute).isHidden();
}
bool isReadable() {
if (fileInfoOpt)
return fileInfoOpt->isReadable();
return ensureFilled(QFileSystemMetaData::UserReadPermission).isReadable();
}
bool isWritable() {
if (fileInfoOpt)
return fileInfoOpt->isWritable();
return ensureFilled(QFileSystemMetaData::UserWritePermission).isWritable();
}
bool isExecutable() {
if (fileInfoOpt)
return fileInfoOpt->isExecutable();
return ensureFilled(QFileSystemMetaData::UserExecutePermission).isExecutable();
}
qint64 size() { return fileInfo().size(); }
QDateTime fileTime(QFile::FileTime type, const QTimeZone &tz)
{
return fileInfo().fileTime(type, tz);
}
private:
friend class QDirListingPrivate;
friend class QDirListing;
QFileSystemEntry entry;
QFileSystemMetaData metaData;
std::optional<QFileInfo> fileInfoOpt;
};
QT_END_NAMESPACE
#endif // QDIRENTRYINFO_P_H

View File

@ -1,65 +1,73 @@
// Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2024 Ahmad Samir <a.samirh78@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
/*! /*!
\since 4.3 \since 6.8
\class QDirIterator \class QDirListing
\inmodule QtCore \inmodule QtCore
\brief The QDirIterator class provides an iterator for directory entrylists. \brief The QDirListing class provides an STL-style iterator for directory entries.
You can use QDirIterator to navigate entries of a directory one at a time. You can use QDirListing to navigate entries of a directory one at a time.
It is similar to QDir::entryList() and QDir::entryInfoList(), but because It is similar to QDir::entryList() and QDir::entryInfoList(), but because
it lists entries one at a time instead of all at once, it scales better it lists entries one at a time instead of all at once, it scales better
and is more suitable for large directories. It also supports listing and is more suitable for large directories. It also supports listing
directory contents recursively, and following symbolic links. Unlike directory contents recursively, and following symbolic links. Unlike
QDir::entryList(), QDirIterator does not support sorting. QDir::entryList(), QDirListing does not support sorting.
The QDirIterator constructor takes a QDir or a directory as The QDirListing constructor takes a QDir or a directory path as
argument. After construction, the iterator is located before the first argument. Here's how to iterate over all entries recursively:
directory entry. Here's how to iterate over all the entries sequentially:
\snippet code/src_corelib_io_qdiriterator.cpp 0 \snippet code/src_corelib_io_qdirlisting.cpp 0
Here's how to find and read all files filtered by name, recursively: Here's how to find and read all files filtered by name, recursively:
\snippet code/src_corelib_io_qdiriterator.cpp 1 \snippet code/src_corelib_io_qdirlisting.cpp 1
The next() and nextFileInfo() functions advance the iterator and return Iterators constructed by QDirListing (QDirListing::const_iterator) are
the path or the QFileInfo of the next directory entry. You can also call forward-only (you cannot iterate directories in reverse order) and don't
filePath() or fileInfo() to get the current file path or QFileInfo without allow random access. They can be used in ranged-for loops (or with STL
first advancing the iterator. The fileName() function returns only the alogrithms that don't require random access iterators). Dereferencing
name of the file, similar to how QDir::entryList() works. a valid iterator returns a QDirListing::DirEntry object. The (c)end()
iterator marks the end of the iteration. Dereferencing the end iterator
is undefiend behavior.
Unlike Qt's container iterators, QDirIterator is uni-directional (i.e., QDirListing::DirEntry offers a subset of QFileInfo's API (for example,
you cannot iterate directories in reverse order) and does not allow random fileName(), filePath(), exists()). Internally, DirEntry only constructs
access. a QFileInfo object if needed, that is, if the info hasn't been already
fetched by other system functions. You can use DirEntry::fileInfo()
to get a QFileInfo. For example:
\snippet code/src_corelib_io_qdirlisting.cpp 3
\snippet code/src_corelib_io_qdirlisting.cpp 4
\sa QDir, QDir::entryList() \sa QDir, QDir::entryList()
*/ */
/*! \enum QDirIterator::IteratorFlag /*! \enum QDirListing::IteratorFlag
This enum describes flags that you can combine to configure the behavior This enum class describes flags can be used to configure the behavior of
of QDirIterator. QDirListing. These flags can be bitwise OR'ed together.
\value NoIteratorFlags The default value, representing no flags. The \value NoFlag The default value, representing no flags. The iterator
iterator will return entries for the assigned path. will return entries for the assigned path.
\value Subdirectories List entries inside all subdirectories as well. \value FollowSymlinks When combined with Recursive, this flag enables
iterating through all subdirectories of the assigned path, following
all symbolic links. Symbolic link loops (e.g., link => . or link =>
..) are automatically detected and ignored.
\value FollowSymlinks When combined with Subdirectories, this flag \value Recursive List entries inside all subdirectories as well.
enables iterating through all subdirectories of the assigned path,
following all symbolic links. Symbolic link loops (e.g., "link" => "." or
"link" => "..") are automatically detected and ignored.
*/ */
#include "qdiriterator.h" #include "qdirlisting.h"
#include "qdirentryinfo_p.h"
#include "qdir_p.h" #include "qdir_p.h"
#include "qabstractfileengine_p.h" #include "qabstractfileengine_p.h"
#include <QtCore/qset.h> #include <QtCore/qset.h>
#include <QtCore/qstack.h>
#include <QtCore/qvariant.h>
#if QT_CONFIG(regularexpression) #if QT_CONFIG(regularexpression)
#include <QtCore/qregularexpression.h> #include <QtCore/qregularexpression.h>
#endif #endif
@ -79,23 +87,26 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
class QDirIteratorPrivate class QDirListingPrivate
{ {
public: public:
void init(bool resolveEngine); void init(bool resolveEngine);
void advance(); void advance();
bool entryMatches(const QString & fileName, const QFileInfo &fileInfo); bool entryMatches(QDirEntryInfo &info);
void pushDirectory(const QFileInfo &fileInfo); void pushDirectory(QDirEntryInfo &info);
void checkAndPushDirectory(const QFileInfo &); void pushInitialDirectory();
bool matchesFilters(const QString &fileName, const QFileInfo &fi) const;
void checkAndPushDirectory(QDirEntryInfo &info);
bool matchesFilters(QDirEntryInfo &data) const;
bool hasIterators() const;
std::unique_ptr<QAbstractFileEngine> engine; std::unique_ptr<QAbstractFileEngine> engine;
QDirEntryInfo initialEntryInfo;
QFileSystemEntry dirEntry;
QStringList nameFilters; QStringList nameFilters;
QDir::Filters filters; QDir::Filters filters;
QDirIterator::IteratorFlags iteratorFlags; QDirListing::IteratorFlags iteratorFlags;
QDirEntryInfo currentEntryInfo;
#if QT_CONFIG(regularexpression) #if QT_CONFIG(regularexpression)
QList<QRegularExpression> nameRegExps; QList<QRegularExpression> nameRegExps;
@ -108,14 +119,11 @@ public:
std::stack<FsIteratorPtr, std::vector<FsIteratorPtr>> nativeIterators; std::stack<FsIteratorPtr, std::vector<FsIteratorPtr>> nativeIterators;
#endif #endif
QFileInfo currentFileInfo;
QFileInfo nextFileInfo;
// Loop protection // Loop protection
QDuplicateTracker<QString> visitedLinks; QDuplicateTracker<QString> visitedLinks;
}; };
void QDirIteratorPrivate::init(bool resolveEngine = true) void QDirListingPrivate::init(bool resolveEngine = true)
{ {
if (nameFilters.contains("*"_L1)) if (nameFilters.contains("*"_L1))
nameFilters.clear(); nameFilters.clear();
@ -131,31 +139,26 @@ void QDirIteratorPrivate::init(bool resolveEngine = true)
nameRegExps.append(re); nameRegExps.append(re);
} }
#endif #endif
QFileSystemMetaData metaData;
if (resolveEngine)
engine.reset(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(dirEntry, metaData));
QFileInfo fileInfo(new QFileInfoPrivate(dirEntry, metaData));
// Populate fields for hasNext() and next() if (resolveEngine)
pushDirectory(fileInfo); engine.reset(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(
advance(); initialEntryInfo.entry, initialEntryInfo.metaData));
pushDirectory(initialEntryInfo);
} }
/*! void QDirListingPrivate::pushDirectory(QDirEntryInfo &entryInfo)
\internal
*/
void QDirIteratorPrivate::pushDirectory(const QFileInfo &fileInfo)
{ {
QString path = fileInfo.filePath(); QString path = entryInfo.filePath();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (fileInfo.isSymLink()) if (entryInfo.isSymLink())
path = fileInfo.canonicalFilePath(); path = entryInfo.canonicalFilePath();
#endif #endif
if ((iteratorFlags & QDirIterator::FollowSymlinks)) { if (iteratorFlags.testAnyFlags(QDirListing::IteratorFlag::FollowSymlinks)) {
// Stop link loops // Stop link loops
if (visitedLinks.hasSeen(fileInfo.canonicalFilePath())) if (visitedLinks.hasSeen(entryInfo.canonicalFilePath()))
return; return;
} }
@ -170,27 +173,22 @@ void QDirIteratorPrivate::pushDirectory(const QFileInfo &fileInfo)
} }
} else { } else {
#ifndef QT_NO_FILESYSTEMITERATOR #ifndef QT_NO_FILESYSTEMITERATOR
nativeIterators.emplace(std::make_unique<QFileSystemIterator>( QFileSystemEntry *fentry = nullptr;
fileInfo.d_ptr->fileEntry, filters)); if (entryInfo.fileInfoOpt)
fentry = &entryInfo.fileInfoOpt->d_ptr->fileEntry;
else
fentry = &entryInfo.entry;
nativeIterators.emplace(std::make_unique<QFileSystemIterator>(*fentry, filters));
#else #else
qWarning("Qt was built with -no-feature-filesystemiterator: no files/plugins will be found!"); qWarning("Qt was built with -no-feature-filesystemiterator: no files/plugins will be found!");
#endif #endif
} }
} }
inline bool QDirIteratorPrivate::entryMatches(const QString & fileName, const QFileInfo &fileInfo) bool QDirListingPrivate::entryMatches(QDirEntryInfo &entryInfo)
{ {
checkAndPushDirectory(fileInfo); checkAndPushDirectory(entryInfo);
return matchesFilters(entryInfo);
if (matchesFilters(fileName, fileInfo)) {
currentFileInfo = nextFileInfo;
nextFileInfo = fileInfo;
//We found a matching entry.
return true;
}
return false;
} }
/*! /*!
@ -208,7 +206,7 @@ inline bool QDirIteratorPrivate::entryMatches(const QString & fileName, const QF
- B's iterator is processed (the top() of the stack) first; then loop - B's iterator is processed (the top() of the stack) first; then loop
goes back to processing A's iterator goes back to processing A's iterator
*/ */
void QDirIteratorPrivate::advance() void QDirListingPrivate::advance()
{ {
// Use get() in both code paths below because the iterator returned by top() // Use get() in both code paths below because the iterator returned by top()
// may be invalidated due to reallocation when appending new iterators in // may be invalidated due to reallocation when appending new iterators in
@ -220,79 +218,73 @@ void QDirIteratorPrivate::advance()
QAbstractFileEngineIterator *it; QAbstractFileEngineIterator *it;
while (it = fileEngineIterators.top().get(), it->hasNext()) { while (it = fileEngineIterators.top().get(), it->hasNext()) {
it->next(); it->next();
if (entryMatches(it->currentFileName(), it->currentFileInfo())) QDirEntryInfo entryInfo;
entryInfo.fileInfoOpt = it->currentFileInfo();
if (entryMatches(entryInfo)) {
currentEntryInfo = std::move(entryInfo);
return; return;
}
} }
fileEngineIterators.pop(); fileEngineIterators.pop();
} }
} else { } else {
#ifndef QT_NO_FILESYSTEMITERATOR #ifndef QT_NO_FILESYSTEMITERATOR
QFileSystemEntry nextEntry; QDirEntryInfo entryInfo;
QFileSystemMetaData nextMetaData;
while (!nativeIterators.empty()) { while (!nativeIterators.empty()) {
// Find the next valid iterator that matches the filters. // Find the next valid iterator that matches the filters.
QFileSystemIterator *it; QFileSystemIterator *it;
while (it = nativeIterators.top().get(), it->advance(nextEntry, nextMetaData)) { while (it = nativeIterators.top().get(), it->advance(entryInfo.entry, entryInfo.metaData)) {
QFileInfo info(new QFileInfoPrivate(nextEntry, nextMetaData)); if (entryMatches(entryInfo)) {
currentEntryInfo = std::move(entryInfo);
if (entryMatches(nextEntry.fileName(), info))
return; return;
nextMetaData = QFileSystemMetaData(); }
entryInfo = {};
} }
nativeIterators.pop(); nativeIterators.pop();
} }
#endif #endif
} }
currentFileInfo = nextFileInfo;
nextFileInfo = QFileInfo();
} }
/*! void QDirListingPrivate::checkAndPushDirectory(QDirEntryInfo &entryInfo)
\internal
*/
void QDirIteratorPrivate::checkAndPushDirectory(const QFileInfo &fileInfo)
{ {
using F = QDirListing::IteratorFlag;
// If we're doing flat iteration, we're done. // If we're doing flat iteration, we're done.
if (!(iteratorFlags & QDirIterator::Subdirectories)) if (!iteratorFlags.testAnyFlags(F::Recursive))
return; return;
// Never follow non-directory entries // Never follow non-directory entries
if (!fileInfo.isDir()) if (!entryInfo.isDir())
return; return;
// Follow symlinks only when asked // Follow symlinks only when asked
if (!(iteratorFlags & QDirIterator::FollowSymlinks) && fileInfo.isSymLink()) if (!iteratorFlags.testAnyFlags(F::FollowSymlinks) && entryInfo.isSymLink())
return; return;
// Never follow . and .. // Never follow . and ..
QString fileName = fileInfo.fileName(); const QString &fileName = entryInfo.fileName();
if ("."_L1 == fileName || ".."_L1 == fileName) if ("."_L1 == fileName || ".."_L1 == fileName)
return; return;
// No hidden directories unless requested // No hidden directories unless requested
if (!(filters & QDir::AllDirs) && !(filters & QDir::Hidden) && fileInfo.isHidden()) if (!filters.testAnyFlags(QDir::AllDirs | QDir::Hidden) && entryInfo.isHidden())
return; return;
pushDirectory(fileInfo); pushDirectory(entryInfo);
} }
/*! /*!
\internal \internal
This convenience function implements the iterator's filtering logics and This functions returns \c true if the current entry matches the filters
applies then to the current directory entry. (i.e., the current entry will be returned as part of the directory
iteration); otherwise, \c false is returned.
It returns \c true if the current entry matches the filters (i.e., the
current entry will be returned as part of the directory iteration);
otherwise, false is returned.
*/ */
bool QDirListingPrivate::matchesFilters(QDirEntryInfo &entryInfo) const
bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInfo &fi) const
{ {
const QString &fileName = entryInfo.fileName();
if (fileName.isEmpty()) if (fileName.isEmpty())
return false; return false;
@ -309,7 +301,7 @@ bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInf
// name filter // name filter
#if QT_CONFIG(regularexpression) #if QT_CONFIG(regularexpression)
// Pass all entries through name filters, except dirs if the AllDirs // Pass all entries through name filters, except dirs if the AllDirs
if (!nameFilters.isEmpty() && !((filters & QDir::AllDirs) && fi.isDir())) { if (!nameFilters.isEmpty() && !(filters.testAnyFlags(QDir::AllDirs) && entryInfo.isDir())) {
bool matched = false; bool matched = false;
for (const auto &re : nameRegExps) { for (const auto &re : nameRegExps) {
if (re.match(fileName).hasMatch()) { if (re.match(fileName).hasMatch()) {
@ -324,30 +316,30 @@ bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInf
// skip symlinks // skip symlinks
const bool skipSymlinks = filters.testAnyFlag(QDir::NoSymLinks); const bool skipSymlinks = filters.testAnyFlag(QDir::NoSymLinks);
const bool includeSystem = filters.testAnyFlag(QDir::System); const bool includeSystem = filters.testAnyFlag(QDir::System);
if (skipSymlinks && fi.isSymLink()) { if (skipSymlinks && entryInfo.isSymLink()) {
// The only reason to save this file is if it is a broken link and we are requesting system files. // The only reason to save this file is if it is a broken link and we are requesting system files.
if (!includeSystem || fi.exists()) if (!includeSystem || entryInfo.exists())
return false; return false;
} }
// filter hidden // filter hidden
const bool includeHidden = filters.testAnyFlag(QDir::Hidden); const bool includeHidden = filters.testAnyFlag(QDir::Hidden);
if (!includeHidden && !dotOrDotDot && fi.isHidden()) if (!includeHidden && !dotOrDotDot && entryInfo.isHidden())
return false; return false;
// filter system files // filter system files
if (!includeSystem && (!(fi.isFile() || fi.isDir() || fi.isSymLink()) if (!includeSystem && (!(entryInfo.isFile() || entryInfo.isDir() || entryInfo.isSymLink())
|| (!fi.exists() && fi.isSymLink()))) || (!entryInfo.exists() && entryInfo.isSymLink())))
return false; return false;
// skip directories // skip directories
const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs)); const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
if (skipDirs && fi.isDir()) if (skipDirs && entryInfo.isDir())
return false; return false;
// skip files // skip files
const bool skipFiles = !(filters & QDir::Files); const bool skipFiles = !(filters & QDir::Files);
if (skipFiles && fi.isFile()) if (skipFiles && entryInfo.isFile())
// Basically we need a reason not to exclude this file otherwise we just eliminate it. // Basically we need a reason not to exclude this file otherwise we just eliminate it.
return false; return false;
@ -358,35 +350,47 @@ bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInf
const bool doExecutable = !filterPermissions || (filters & QDir::Executable); const bool doExecutable = !filterPermissions || (filters & QDir::Executable);
const bool doReadable = !filterPermissions || (filters & QDir::Readable); const bool doReadable = !filterPermissions || (filters & QDir::Readable);
if (filterPermissions if (filterPermissions
&& ((doReadable && !fi.isReadable()) && ((doReadable && !entryInfo.isReadable())
|| (doWritable && !fi.isWritable()) || (doWritable && !entryInfo.isWritable())
|| (doExecutable && !fi.isExecutable()))) { || (doExecutable && !entryInfo.isExecutable()))) {
return false; return false;
} }
return true; return true;
} }
bool QDirListingPrivate::hasIterators() const
{
if (engine)
return !fileEngineIterators.empty();
#if !defined(QT_NO_FILESYSTEMITERATOR)
return !nativeIterators.empty();
#endif
return false;
}
/*! /*!
Constructs a QDirIterator that can iterate over \a dir's entrylist, using Constructs a QDirListing that can iterate over \a dir's entries, using
\a dir's name filters and regular filters. You can pass options via \a \a dir's name filters and the QDir::Filters set in \a dir. You can pass
flags to decide how the directory should be iterated. options via \a flags to decide how the directory should be iterated.
By default, \a flags is NoIteratorFlags, which provides the same behavior By default, \a flags is NoIteratorFlags, which provides the same behavior
as in QDir::entryList(). as in QDir::entryList().
The sorting in \a dir is ignored. The sorting in \a dir is ignored.
\note To list symlinks that point to non existing files, QDir::System must be \note To list symlinks that point to non existing files, QDir::System
passed to the flags. must be set in \a dir's QDir::Filters.
\sa hasNext(), next(), IteratorFlags \sa hasNext(), next(), IteratorFlags
*/ */
QDirIterator::QDirIterator(const QDir &dir, IteratorFlags flags) QDirListing::QDirListing(const QDir &dir, IteratorFlags flags)
: d(new QDirIteratorPrivate) : d(new QDirListingPrivate)
{ {
const QDirPrivate *other = dir.d_ptr.constData(); const QDirPrivate *other = dir.d_ptr.constData();
d->dirEntry = other->dirEntry; d->initialEntryInfo.entry = other->dirEntry;
d->nameFilters = other->nameFilters; d->nameFilters = other->nameFilters;
d->filters = other->filters; d->filters = other->filters;
d->iteratorFlags = flags; d->iteratorFlags = flags;
@ -395,50 +399,47 @@ QDirIterator::QDirIterator(const QDir &dir, IteratorFlags flags)
} }
/*! /*!
Constructs a QDirIterator that can iterate over \a path, with no name Constructs a QDirListing that can iterate over \a path. Entries are
filtering and \a filters for entry filtering. You can pass options via \a filtered according to \a filters. You can pass options via \a flags to
flags to decide how the directory should be iterated. decide how the directory should be iterated.
By default, \a filters is QDir::NoFilter, and \a flags is NoIteratorFlags, By default, \a filters is QDir::NoFilter, and \a flags is NoIteratorFlags,
which provides the same behavior as in QDir::entryList(). which provides the same behavior as in QDir::entryList().
\note To list symlinks that point to non existing files, QDir::System must be \note To list symlinks that point to non existing files, QDir::System
passed to the flags. must be set in \a filters.
\sa hasNext(), next(), IteratorFlags \sa hasNext(), next(), IteratorFlags
*/ */
QDirIterator::QDirIterator(const QString &path, QDir::Filters filters, IteratorFlags flags) QDirListing::QDirListing(const QString &path, QDir::Filters filters, IteratorFlags flags)
: d(new QDirIteratorPrivate) : d(new QDirListingPrivate)
{ {
d->dirEntry = QFileSystemEntry(path); d->initialEntryInfo.entry = QFileSystemEntry(path);
d->filters = filters; d->filters = filters;
d->iteratorFlags = flags; d->iteratorFlags = flags;
d->init(); d->init();
} }
/*! /*!
Constructs a QDirIterator that can iterate over \a path. You can pass Constructs a QDirListing that can iterate over \a path. You can pass
options via \a flags to decide how the directory should be iterated. options via \a flags to decide how the directory should be iterated.
By default, \a flags is NoIteratorFlags, which provides the same behavior By default, \a flags is NoIteratorFlags, which provides the same behavior
as in QDir::entryList(). as in QDir::entryList().
\note To list symlinks that point to non existing files, QDir::System must be
passed to the flags.
\sa hasNext(), next(), IteratorFlags \sa hasNext(), next(), IteratorFlags
*/ */
QDirIterator::QDirIterator(const QString &path, IteratorFlags flags) QDirListing::QDirListing(const QString &path, IteratorFlags flags)
: d(new QDirIteratorPrivate) : d(new QDirListingPrivate)
{ {
d->dirEntry = QFileSystemEntry(path); d->initialEntryInfo.entry = QFileSystemEntry(path);
d->filters = QDir::NoFilter; d->filters = QDir::NoFilter;
d->iteratorFlags = flags; d->iteratorFlags = flags;
d->init(); d->init();
} }
/*! /*!
Constructs a QDirIterator that can iterate over \a path, using \a Constructs a QDirListing that can iterate over \a path, using \a
nameFilters and \a filters. You can pass options via \a flags to decide nameFilters and \a filters. You can pass options via \a flags to decide
how the directory should be iterated. how the directory should be iterated.
@ -448,18 +449,18 @@ QDirIterator::QDirIterator(const QString &path, IteratorFlags flags)
For example, the following iterator could be used to iterate over audio For example, the following iterator could be used to iterate over audio
files: files:
\snippet code/src_corelib_io_qdiriterator.cpp 2 \snippet code/src_corelib_io_qdirlisting.cpp 2
\note To list symlinks that point to non existing files, QDir::System must be \note To list symlinks that point to non existing files, QDir::System
passed to the flags. must be set in \a flags.
\sa hasNext(), next(), IteratorFlags, QDir::setNameFilters() \sa hasNext(), next(), IteratorFlags, QDir::setNameFilters()
*/ */
QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters, QDirListing::QDirListing(const QString &path, const QStringList &nameFilters, QDir::Filters filters,
QDir::Filters filters, IteratorFlags flags) IteratorFlags flags)
: d(new QDirIteratorPrivate) : d(new QDirListingPrivate)
{ {
d->dirEntry = QFileSystemEntry(path); d->initialEntryInfo.entry = QFileSystemEntry(path);
d->nameFilters = nameFilters; d->nameFilters = nameFilters;
d->filters = filters; d->filters = filters;
d->iteratorFlags = flags; d->iteratorFlags = flags;
@ -467,109 +468,207 @@ QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters,
} }
/*! /*!
Destroys the QDirIterator. Destroys the QDirListing.
*/ */
QDirIterator::~QDirIterator() QDirListing::~QDirListing() = default;
/*!
Returns the directory path used to construct this QDirListing.
*/
QString QDirListing::iteratorPath() const
{ {
return d->initialEntryInfo.filePath();
} }
/*! /*!
Advances the iterator to the next entry, and returns the file path of this \fn QDirListing::const_iterator QDirListing::begin() const
new entry. If hasNext() returns \c false, this function does nothing, and \fn QDirListing::const_iterator QDirListing::cbegin() const
returns an empty QString. \fn QDirListing::const_iterator QDirListing::end() const
\fn QDirListing::const_iterator QDirListing::cend() const
You can call fileName() or filePath() to get the current entry's file name begin()/cbegin() returns a QDirListing::const_iterator that enables
or path, or fileInfo() to get a QFileInfo for the current entry. iterating over directory entries using a ranged-for loop; dereferencing
this iterator returns a \c{const QFileInfo &}.
Call nextFileInfo() instead of next() if you're interested in the QFileInfo. end()/cend() return a sentinel const_iterator that signals the end of
the iteration. Dereferencing this iterator is undefined behavior.
\sa hasNext(), nextFileInfo(), fileName(), filePath(), fileInfo() For example:
\snippet code/src_corelib_io_qdirlisting.cpp 0
Here's how to find and read all files filtered by name, recursively:
\snippet code/src_corelib_io_qdirlisting.cpp 1
\note As this is a unidirectional (forward-only) iterator, calling
begin()/cbegin() more than once on the same QDirListing object could
result in unexpected behavior (for example, some entries being skipped).
\sa fileInfo(), fileName(), filePath()
*/ */
QString QDirIterator::next() QDirListing::const_iterator QDirListing::begin() const
{ {
d->advance(); const_iterator it{d.get()};
return filePath(); return ++it;
} }
/*! /*!
\since 6.3 \fn const QDirListing::DirEntry &QDirListing::const_iterator::operator*() const
Advances the iterator to the next entry, and returns the file info of this Returns a \c{const QDirListing::DirEntry &} of the directory entry this
new entry. If hasNext() returns \c false, this function does nothing, and iterator points to.
returns an empty QFileInfo.
You can call fileName() or filePath() to get the current entry's file name
or path, or fileInfo() to get a QFileInfo for the current entry.
Call next() instead of nextFileInfo() when all you need is the filePath().
\sa hasNext(), fileName(), filePath(), fileInfo()
*/ */
QFileInfo QDirIterator::nextFileInfo()
/*!
\fn const QDirListing::DirEntry *QDirListing::const_iterator::operator->() const
Returns a \c{const QDirListing::DirEntry *} to the directory entry this
iterator points to.
*/
/*!
Advances the iterator and returns a reference to it.
*/
QDirListing::const_iterator &QDirListing::const_iterator::operator++()
{ {
d->advance(); dirListPtr->advance();
return fileInfo(); if (!dirListPtr->hasIterators())
*this = {}; // All done, make `this` the end() iterator
return *this;
} }
/*! /*!
Returns \c true if there is at least one more entry in the directory; \fn QFileInfo QDirListing::DirEntry::fileInfo() const
otherwise, false is returned. \fn QString QDirListing::DirEntry::fileName() const
\fn QString QDirListing::DirEntry::baseName() const
\fn QString QDirListing::DirEntry::completeBaseName() const
\fn QString QDirListing::DirEntry::suffix() const
\fn QString QDirListing::DirEntry::bundleName() const
\fn QString QDirListing::DirEntry::completeSuffix() const
\fn QString QDirListing::DirEntry::filePath() const
\fn QString QDirListing::DirEntry::canonicalFilePath() const
\fn QString QDirListing::DirEntry::absoluteFilePath() const
\fn QString QDirListing::DirEntry::absolutePath() const
\fn bool QDirListing::DirEntry::isDir() const
\fn bool QDirListing::DirEntry::isFile() const
\fn bool QDirListing::DirEntry::isSymLink() const
\fn bool QDirListing::DirEntry::exists() const
\fn bool QDirListing::DirEntry::isHidden() const
\fn bool QDirListing::DirEntry::isReadable() const
\fn bool QDirListing::DirEntry::isWritable() const
\fn bool QDirListing::DirEntry::isExecutable() const
\fn qint64 QDirListing::DirEntry::size() const
\fn QDateTime QDirListing::DirEntry::fileTime(QFile::FileTime type, const QTimeZone &tz) const
\fn QDateTime QDirListing::DirEntry::birthTime(const QTimeZone &tz) const;
\fn QDateTime QDirListing::DirEntry::metadataChangeTime(const QTimeZone &tz) const;
\fn QDateTime QDirListing::DirEntry::lastModified(const QTimeZone &tz) const;
\fn QDateTime QDirListing::DirEntry::lastRead(const QTimeZone &tz) const;
\sa next(), nextFileInfo(), fileName(), filePath(), fileInfo() See the QFileInfo methods with the same names.
*/ */
bool QDirIterator::hasNext() const
QFileInfo QDirListing::DirEntry::fileInfo() const
{ {
if (d->engine) return dirListPtr->currentEntryInfo.fileInfo();
return !d->fileEngineIterators.empty();
else
#ifndef QT_NO_FILESYSTEMITERATOR
return !d->nativeIterators.empty();
#else
return false;
#endif
} }
/*! QString QDirListing::DirEntry::fileName() const
Returns the file name for the current directory entry, without the path
prepended.
This function is convenient when iterating a single directory. When using
the QDirIterator::Subdirectories flag, you can use filePath() to get the
full path.
\sa filePath(), fileInfo()
*/
QString QDirIterator::fileName() const
{ {
return d->currentFileInfo.fileName(); return dirListPtr->currentEntryInfo.fileName();
} }
/*! QString QDirListing::DirEntry::baseName() const
Returns the full file path for the current directory entry.
\sa fileInfo(), fileName()
*/
QString QDirIterator::filePath() const
{ {
return d->currentFileInfo.filePath(); return dirListPtr->currentEntryInfo.baseName();
} }
/*! QString QDirListing::DirEntry::completeBaseName() const
Returns a QFileInfo for the current directory entry.
\sa filePath(), fileName()
*/
QFileInfo QDirIterator::fileInfo() const
{ {
return d->currentFileInfo; return dirListPtr->currentEntryInfo.completeBaseName();
} }
/*! QString QDirListing::DirEntry::suffix() const
Returns the base directory of the iterator.
*/
QString QDirIterator::path() const
{ {
return d->dirEntry.filePath(); return dirListPtr->currentEntryInfo.suffix();
}
QString QDirListing::DirEntry::bundleName() const
{
return dirListPtr->currentEntryInfo.bundleName();
}
QString QDirListing::DirEntry::completeSuffix() const
{
return dirListPtr->currentEntryInfo.completeSuffix();
}
QString QDirListing::DirEntry::filePath() const
{
return dirListPtr->currentEntryInfo.filePath();
}
QString QDirListing::DirEntry::canonicalFilePath() const
{
return dirListPtr->currentEntryInfo.canonicalFilePath();
}
QString QDirListing::DirEntry::absoluteFilePath() const
{
return dirListPtr->currentEntryInfo.absoluteFilePath();
}
QString QDirListing::DirEntry::absolutePath() const
{
return dirListPtr->currentEntryInfo.absolutePath();
}
bool QDirListing::DirEntry::isDir() const
{
return dirListPtr->currentEntryInfo.isDir();
}
bool QDirListing::DirEntry::isFile() const
{
return dirListPtr->currentEntryInfo.isFile();
}
bool QDirListing::DirEntry::isSymLink() const
{
return dirListPtr->currentEntryInfo.isSymLink();
}
bool QDirListing::DirEntry::exists() const
{
return dirListPtr->currentEntryInfo.exists();
}
bool QDirListing::DirEntry::isHidden() const
{
return dirListPtr->currentEntryInfo.isHidden();
}
bool QDirListing::DirEntry::isReadable() const
{
return dirListPtr->currentEntryInfo.isReadable();
}
bool QDirListing::DirEntry::isWritable() const
{
return dirListPtr->currentEntryInfo.isWritable();
}
bool QDirListing::DirEntry::isExecutable() const
{
return dirListPtr->currentEntryInfo.isExecutable();
}
qint64 QDirListing::DirEntry::size() const
{
return dirListPtr->currentEntryInfo.size();
}
QDateTime QDirListing::DirEntry::fileTime(QFile::FileTime type, const QTimeZone &tz) const
{
return dirListPtr->currentEntryInfo.fileTime(type, tz);
} }
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -1,55 +1,119 @@
// Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 The Qt Company Ltd.
// Copyright (C) 2024 Ahmad Samir <a.samirh78@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QDIRITERATOR_H #ifndef QDILISTING_H
#define QDIRITERATOR_H #define QDILISTING_H
#include <QtCore/qdir.h> #include <QtCore/qdir.h>
#include <iterator>
#include <memory>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QDirIteratorPrivate; class QDirListingPrivate;
class Q_CORE_EXPORT QDirIterator
class Q_CORE_EXPORT QDirListing
{ {
public: public:
enum IteratorFlag { enum class IteratorFlag {
NoIteratorFlags = 0x0, NoFlag = 0x0,
FollowSymlinks = 0x1, FollowSymlinks = 0x1,
Subdirectories = 0x2 Recursive = 0x2
}; };
Q_DECLARE_FLAGS(IteratorFlags, IteratorFlag) Q_DECLARE_FLAGS(IteratorFlags, IteratorFlag)
QDirIterator(const QDir &dir, IteratorFlags flags = NoIteratorFlags); QDirListing(const QDir &dir, IteratorFlags flags = IteratorFlag::NoFlag);
QDirIterator(const QString &path, QDirListing(const QString &path, IteratorFlags flags = IteratorFlag::NoFlag);
IteratorFlags flags = NoIteratorFlags); QDirListing(const QString &path, QDir::Filters filter,
QDirIterator(const QString &path, IteratorFlags flags = IteratorFlag::NoFlag);
QDir::Filters filter, QDirListing(const QString &path, const QStringList &nameFilters,
IteratorFlags flags = NoIteratorFlags); QDir::Filters filters = QDir::NoFilter,
QDirIterator(const QString &path, IteratorFlags flags = IteratorFlag::NoFlag);
const QStringList &nameFilters,
QDir::Filters filters = QDir::NoFilter,
IteratorFlags flags = NoIteratorFlags);
~QDirIterator(); ~QDirListing();
QString next(); QString iteratorPath() const;
QFileInfo nextFileInfo();
bool hasNext() const;
QString fileName() const; class Q_CORE_EXPORT DirEntry
QString filePath() const; {
QFileInfo fileInfo() const; friend class QDirListing;
QString path() const; QDirListingPrivate *dirListPtr = nullptr;
public:
QString fileName() const;
QString baseName() const;
QString completeBaseName() const;
QString suffix() const;
QString bundleName() const;
QString completeSuffix() const;
QString filePath() const;
bool isDir() const;
bool isFile() const;
bool isSymLink() const;
bool exists() const;
bool isHidden() const;
bool isReadable() const;
bool isWritable() const;
bool isExecutable() const;
QFileInfo fileInfo() const;
QString canonicalFilePath() const;
QString absoluteFilePath() const;
QString absolutePath() const;
qint64 size() const;
QDateTime birthTime(const QTimeZone &tz) const { return fileTime(QFile::FileBirthTime, tz); }
QDateTime metadataChangeTime(const QTimeZone &tz) const { return fileTime(QFile::FileMetadataChangeTime, tz); }
QDateTime lastModified(const QTimeZone &tz) const { return fileTime(QFile::FileModificationTime, tz); }
QDateTime lastRead(const QTimeZone &tz) const { return fileTime(QFile::FileAccessTime, tz); }
QDateTime fileTime(QFile::FileTime type, const QTimeZone &tz) const;
};
class const_iterator
{
friend class QDirListing;
const_iterator(QDirListingPrivate *dp) : dirListPtr(dp) { dirEntry.dirListPtr = dp; }
QDirListingPrivate *dirListPtr = nullptr;
DirEntry dirEntry;
public:
using iterator_category = std::input_iterator_tag;
using value_type = DirEntry;
using difference_type = qint64;
using pointer = const value_type *;
using reference = const value_type &;
const_iterator() = default;
reference operator*() const { return dirEntry; }
pointer operator->() const { return &dirEntry; }
Q_CORE_EXPORT const_iterator &operator++();
const_iterator operator++(int) { auto tmp = *this; operator++(); return tmp; };
friend bool operator==(const const_iterator &lhs, const const_iterator &rhs)
{
// This is only used for the sentinel end iterator
return lhs.dirListPtr == nullptr && rhs.dirListPtr == nullptr;
}
friend bool operator!=(const const_iterator &lhs, const const_iterator &rhs)
{ return !(lhs == rhs); }
};
const_iterator begin() const;
const_iterator cbegin() const { return begin(); }
const_iterator end() const { return {}; }
const_iterator cend() const { return end(); }
// Qt compatibility
const_iterator constBegin() const { return begin(); }
const_iterator constEnd() const { return end(); }
private: private:
Q_DISABLE_COPY(QDirIterator) Q_DISABLE_COPY(QDirListing)
QScopedPointer<QDirIteratorPrivate> d; std::unique_ptr<QDirListingPrivate> d;
friend class QDir; friend class QDir;
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(QDirIterator::IteratorFlags) Q_DECLARE_OPERATORS_FOR_FLAGS(QDirListing::IteratorFlags)
QT_END_NAMESPACE QT_END_NAMESPACE
#endif #endif // QDILISTING_H

View File

@ -22,6 +22,7 @@ class QFileInfoPrivate;
class Q_CORE_EXPORT QFileInfo class Q_CORE_EXPORT QFileInfo
{ {
friend class QDirIteratorPrivate; friend class QDirIteratorPrivate;
friend class QDirListingPrivate;
public: public:
explicit QFileInfo(QFileInfoPrivate *d); explicit QFileInfo(QFileInfoPrivate *d);

View File

@ -16,6 +16,7 @@ endif()
add_subdirectory(qbuffer) add_subdirectory(qbuffer)
add_subdirectory(qdataurl) add_subdirectory(qdataurl)
add_subdirectory(qdiriterator) add_subdirectory(qdiriterator)
add_subdirectory(qdirlisting)
add_subdirectory(qfile) add_subdirectory(qfile)
add_subdirectory(largefile) add_subdirectory(largefile)
add_subdirectory(qfileselector) add_subdirectory(qfileselector)

View File

@ -0,0 +1 @@
tst_qdirlisting

View File

@ -2,44 +2,45 @@
# SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: BSD-3-Clause
##################################################################### #####################################################################
## tst_qdiriterator Test: ## tst_qdirlisting Test:
##################################################################### #####################################################################
if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(tst_qdiriterator LANGUAGES CXX) project(tst_qdirlisting LANGUAGES CXX)
find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
endif() endif()
# Collect test data # Collect test data
list(APPEND test_data "entrylist") list(APPEND test_data "entrylist")
qt_internal_add_test(tst_qdiriterator qt_internal_add_test(tst_qdirlisting
SOURCES SOURCES
tst_qdiriterator.cpp tst_qdirlisting.cpp
LIBRARIES LIBRARIES
Qt::Core
Qt::CorePrivate Qt::CorePrivate
TESTDATA ${test_data} TESTDATA ${test_data}
) )
# Resources: # Resources:
set(qdiriterator_resource_files set(qdirlisting_resource_files
"entrylist/directory/dummy" "entrylist/directory/dummy"
"entrylist/file" "entrylist/file"
) )
qt_internal_add_resource(tst_qdiriterator "qdiriterator" qt_internal_add_resource(tst_qdirlisting "qdirlisting"
PREFIX PREFIX
"/testdata/" "/testdata/"
FILES FILES
${qdiriterator_resource_files} ${qdirlisting_resource_files}
) )
## Scopes: ## Scopes:
##################################################################### #####################################################################
qt_internal_extend_target(tst_qdiriterator CONDITION CONFIG___contains___builtin_testdata qt_internal_extend_target(tst_qdirlisting CONDITION CONFIG___contains___builtin_testdata
DEFINES DEFINES
BUILTIN_TESTDATA BUILTIN_TESTDATA
) )

View File

@ -5,7 +5,7 @@
#include <qcoreapplication.h> #include <qcoreapplication.h>
#include <qdebug.h> #include <qdebug.h>
#include <qdiriterator.h> #include <qdirlisting.h>
#include <qfileinfo.h> #include <qfileinfo.h>
#include <qstringlist.h> #include <qstringlist.h>
#include <QSet> #include <QSet>
@ -25,10 +25,12 @@
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
Q_DECLARE_METATYPE(QDirIterator::IteratorFlags) Q_DECLARE_METATYPE(QDirListing::IteratorFlags)
Q_DECLARE_METATYPE(QDir::Filters) Q_DECLARE_METATYPE(QDir::Filters)
class tst_QDirIterator : public QObject using ItFlag = QDirListing::IteratorFlag;
class tst_QDirListing : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -90,23 +92,21 @@ private:
QTemporaryDir m_dataDir; QTemporaryDir m_dataDir;
}; };
void tst_QDirIterator::initTestCase() void tst_QDirListing::initTestCase()
{ {
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
QString testdata_dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); QString testdata_dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
QString resourceSourcePath = QStringLiteral(":/testdata"); QString resourceSourcePath = QStringLiteral(":/testdata");
QDirIterator it(resourceSourcePath, QDirIterator::Subdirectories); for (const auto &dirEntry : QDirListing(resourceSourcePath, ItFlag::Recursive)) {
while (it.hasNext()) { if (!dirEntry.isDir()) {
QFileInfo fileInfo = it.nextFileInfo(); const QString &filePath = dirEntry.filePath();
if (!fileInfo.isDir()) {
QString destination = testdata_dir + QLatin1Char('/') QString destination = testdata_dir + QLatin1Char('/')
+ fileInfo.filePath().mid(resourceSourcePath.length()); + filePath.sliced(resourceSourcePath.length());
QFileInfo destinationFileInfo(destination); QFileInfo destinationFileInfo(destination);
if (!destinationFileInfo.exists()) { if (!destinationFileInfo.exists()) {
QDir().mkpath(destinationFileInfo.path()); QDir().mkpath(destinationFileInfo.path());
if (!QFile::copy(fileInfo.filePath(), destination)) if (!QFile::copy(filePath, destination))
qWarning("Failed to copy %s", qPrintable(fileInfo.filePath())); qWarning("Failed to copy %s", qPrintable(filePath));
} }
} }
@ -180,16 +180,16 @@ void tst_QDirIterator::initTestCase()
#endif #endif
} }
void tst_QDirIterator::iterateRelativeDirectory_data() void tst_QDirListing::iterateRelativeDirectory_data()
{ {
QTest::addColumn<QString>("dirName"); // relative from current path or abs QTest::addColumn<QString>("dirName"); // relative from current path or abs
QTest::addColumn<QDirIterator::IteratorFlags>("flags"); QTest::addColumn<QDirListing::IteratorFlags>("flags");
QTest::addColumn<QDir::Filters>("filters"); QTest::addColumn<QDir::Filters>("filters");
QTest::addColumn<QStringList>("nameFilters"); QTest::addColumn<QStringList>("nameFilters");
QTest::addColumn<QStringList>("entries"); QTest::addColumn<QStringList>("entries");
QTest::newRow("no flags") QTest::newRow("no flags")
<< QString("entrylist") << QDirIterator::IteratorFlags{} << QString("entrylist") << QDirListing::IteratorFlags{}
<< QDir::Filters(QDir::NoFilter) << QStringList("*") << QDir::Filters(QDir::NoFilter) << QStringList("*")
<< QString( << QString(
"entrylist/.," "entrylist/.,"
@ -205,7 +205,7 @@ void tst_QDirIterator::iterateRelativeDirectory_data()
"entrylist/writable").split(','); "entrylist/writable").split(',');
QTest::newRow("NoDot") QTest::newRow("NoDot")
<< QString("entrylist") << QDirIterator::IteratorFlags{} << QString("entrylist") << QDirListing::IteratorFlags{}
<< QDir::Filters(QDir::AllEntries | QDir::NoDot) << QStringList("*") << QDir::Filters(QDir::AllEntries | QDir::NoDot) << QStringList("*")
<< QString( << QString(
"entrylist/..," "entrylist/..,"
@ -220,7 +220,7 @@ void tst_QDirIterator::iterateRelativeDirectory_data()
"entrylist/writable").split(','); "entrylist/writable").split(',');
QTest::newRow("NoDotDot") QTest::newRow("NoDotDot")
<< QString("entrylist") << QDirIterator::IteratorFlags{} << QString("entrylist") << QDirListing::IteratorFlags{}
<< QDir::Filters(QDir::AllEntries | QDir::NoDotDot) << QStringList("*") << QDir::Filters(QDir::AllEntries | QDir::NoDotDot) << QStringList("*")
<< QString( << QString(
"entrylist/.," "entrylist/.,"
@ -235,7 +235,7 @@ void tst_QDirIterator::iterateRelativeDirectory_data()
"entrylist/writable").split(','); "entrylist/writable").split(',');
QTest::newRow("NoDotAndDotDot") QTest::newRow("NoDotAndDotDot")
<< QString("entrylist") << QDirIterator::IteratorFlags{} << QString("entrylist") << QDirListing::IteratorFlags{}
<< QDir::Filters(QDir::AllEntries | QDir::NoDotAndDotDot) << QStringList("*") << QDir::Filters(QDir::AllEntries | QDir::NoDotAndDotDot) << QStringList("*")
<< QString( << QString(
"entrylist/file," "entrylist/file,"
@ -249,7 +249,7 @@ void tst_QDirIterator::iterateRelativeDirectory_data()
"entrylist/writable").split(','); "entrylist/writable").split(',');
QTest::newRow("QDir::Subdirectories | QDir::FollowSymlinks") QTest::newRow("QDir::Subdirectories | QDir::FollowSymlinks")
<< QString("entrylist") << QDirIterator::IteratorFlags(QDirIterator::Subdirectories | QDirIterator::FollowSymlinks) << QString("entrylist") << QDirListing::IteratorFlags(ItFlag::Recursive | ItFlag::FollowSymlinks)
<< QDir::Filters(QDir::NoFilter) << QStringList("*") << QDir::Filters(QDir::NoFilter) << QStringList("*")
<< QString( << QString(
"entrylist/.," "entrylist/.,"
@ -268,7 +268,7 @@ void tst_QDirIterator::iterateRelativeDirectory_data()
"entrylist/writable").split(','); "entrylist/writable").split(',');
QTest::newRow("QDir::Subdirectories / QDir::Files") QTest::newRow("QDir::Subdirectories / QDir::Files")
<< QString("entrylist") << QDirIterator::IteratorFlags(QDirIterator::Subdirectories) << QString("entrylist") << QDirListing::IteratorFlags(ItFlag::Recursive)
<< QDir::Filters(QDir::Files) << QStringList("*") << QDir::Filters(QDir::Files) << QStringList("*")
<< QString("entrylist/directory/dummy," << QString("entrylist/directory/dummy,"
"entrylist/file," "entrylist/file,"
@ -278,7 +278,7 @@ void tst_QDirIterator::iterateRelativeDirectory_data()
"entrylist/writable").split(','); "entrylist/writable").split(',');
QTest::newRow("QDir::Subdirectories | QDir::FollowSymlinks / QDir::Files") QTest::newRow("QDir::Subdirectories | QDir::FollowSymlinks / QDir::Files")
<< QString("entrylist") << QDirIterator::IteratorFlags(QDirIterator::Subdirectories | QDirIterator::FollowSymlinks) << QString("entrylist") << QDirListing::IteratorFlags(ItFlag::Recursive | QDirListing::IteratorFlag::FollowSymlinks)
<< QDir::Filters(QDir::Files) << QStringList("*") << QDir::Filters(QDir::Files) << QStringList("*")
<< QString("entrylist/file," << QString("entrylist/file,"
#ifndef Q_NO_SYMLINKS #ifndef Q_NO_SYMLINKS
@ -288,47 +288,31 @@ void tst_QDirIterator::iterateRelativeDirectory_data()
"entrylist/writable").split(','); "entrylist/writable").split(',');
QTest::newRow("empty, default") QTest::newRow("empty, default")
<< QString("empty") << QDirIterator::IteratorFlags{} << QString("empty") << QDirListing::IteratorFlags{}
<< QDir::Filters(QDir::NoFilter) << QStringList("*") << QDir::Filters(QDir::NoFilter) << QStringList("*")
<< QString("empty/.,empty/..").split(','); << QString("empty/.,empty/..").split(',');
QTest::newRow("empty, QDir::NoDotAndDotDot") QTest::newRow("empty, QDir::NoDotAndDotDot")
<< QString("empty") << QDirIterator::IteratorFlags{} << QString("empty") << QDirListing::IteratorFlags{}
<< QDir::Filters(QDir::NoDotAndDotDot) << QStringList("*") << QDir::Filters(QDir::NoDotAndDotDot) << QStringList("*")
<< QStringList(); << QStringList();
} }
void tst_QDirIterator::iterateRelativeDirectory() void tst_QDirListing::iterateRelativeDirectory()
{ {
QFETCH(QString, dirName); QFETCH(QString, dirName);
QFETCH(QDirIterator::IteratorFlags, flags); QFETCH(QDirListing::IteratorFlags, flags);
QFETCH(QDir::Filters, filters); QFETCH(QDir::Filters, filters);
QFETCH(QStringList, nameFilters); QFETCH(QStringList, nameFilters);
QFETCH(const QStringList, entries); QFETCH(const QStringList, entries);
QDirIterator it(dirName, nameFilters, filters, flags);
QStringList list; QStringList list;
while (it.hasNext()) { for (const auto &dirEntry : QDirListing(dirName, nameFilters, filters, flags)) {
QString next = it.next();
QString fileName = it.fileName();
QString filePath = it.filePath();
QString path = it.path();
QFileInfo info = it.fileInfo();
QCOMPARE(path, dirName);
QCOMPARE(next, filePath);
QCOMPARE(info, QFileInfo(next));
QCOMPARE(fileName, info.fileName());
QCOMPARE(filePath, info.filePath());
// Using canonical file paths for final comparison // Using canonical file paths for final comparison
list << info.canonicalFilePath(); list << dirEntry.fileInfo().canonicalFilePath();
} }
// The order of items returned by QDirIterator is not guaranteed. // The order of items returned by QDirListing is not guaranteed.
list.sort(); list.sort();
QStringList sortedEntries; QStringList sortedEntries;
@ -344,43 +328,42 @@ void tst_QDirIterator::iterateRelativeDirectory()
QCOMPARE(list, sortedEntries); QCOMPARE(list, sortedEntries);
} }
void tst_QDirIterator::iterateResource_data() void tst_QDirListing::iterateResource_data()
{ {
QTest::addColumn<QString>("dirName"); // relative from current path or abs QTest::addColumn<QString>("dirName"); // relative from current path or abs
QTest::addColumn<QDirIterator::IteratorFlags>("flags"); QTest::addColumn<QDirListing::IteratorFlags>("flags");
QTest::addColumn<QDir::Filters>("filters"); QTest::addColumn<QDir::Filters>("filters");
QTest::addColumn<QStringList>("nameFilters"); QTest::addColumn<QStringList>("nameFilters");
QTest::addColumn<QStringList>("entries"); QTest::addColumn<QStringList>("entries");
QTest::newRow("invalid") << QString::fromLatin1(":/testdata/burpaburpa") << QDirIterator::IteratorFlags{} QTest::newRow("invalid") << QString::fromLatin1(":/testdata/burpaburpa") << QDirListing::IteratorFlags{}
<< QDir::Filters(QDir::NoFilter) << QStringList(QLatin1String("*")) << QDir::Filters(QDir::NoFilter) << QStringList(QLatin1String("*"))
<< QStringList(); << QStringList();
QTest::newRow("qrc:/testdata") << u":/testdata/"_s << QDirIterator::IteratorFlags{} QTest::newRow("qrc:/testdata") << u":/testdata/"_s << QDirListing::IteratorFlags{}
<< QDir::Filters(QDir::NoFilter) << QStringList(QLatin1String("*")) << QDir::Filters(QDir::NoFilter) << QStringList(QLatin1String("*"))
<< QString::fromLatin1(":/testdata/entrylist").split(QLatin1String(",")); << QString::fromLatin1(":/testdata/entrylist").split(QLatin1String(","));
QTest::newRow("qrc:/testdata/entrylist") << u":/testdata/entrylist"_s << QDirIterator::IteratorFlags{} QTest::newRow("qrc:/testdata/entrylist") << u":/testdata/entrylist"_s << QDirListing::IteratorFlags{}
<< QDir::Filters(QDir::NoFilter) << QStringList(QLatin1String("*")) << QDir::Filters(QDir::NoFilter) << QStringList(QLatin1String("*"))
<< QString::fromLatin1(":/testdata/entrylist/directory,:/testdata/entrylist/file").split(QLatin1String(",")); << QString::fromLatin1(":/testdata/entrylist/directory,:/testdata/entrylist/file").split(QLatin1String(","));
QTest::newRow("qrc:/testdata recursive") << u":/testdata"_s QTest::newRow("qrc:/testdata recursive") << u":/testdata"_s
<< QDirIterator::IteratorFlags(QDirIterator::Subdirectories) << QDirListing::IteratorFlags(ItFlag::Recursive)
<< QDir::Filters(QDir::NoFilter) << QStringList(QLatin1String("*")) << QDir::Filters(QDir::NoFilter) << QStringList(QLatin1String("*"))
<< QString::fromLatin1(":/testdata/entrylist,:/testdata/entrylist/directory,:/testdata/entrylist/directory/dummy,:/testdata/entrylist/file").split(QLatin1String(",")); << QString::fromLatin1(":/testdata/entrylist,:/testdata/entrylist/directory,:/testdata/entrylist/directory/dummy,:/testdata/entrylist/file").split(QLatin1String(","));
} }
void tst_QDirIterator::iterateResource() void tst_QDirListing::iterateResource()
{ {
QFETCH(QString, dirName); QFETCH(QString, dirName);
QFETCH(QDirIterator::IteratorFlags, flags); QFETCH(QDirListing::IteratorFlags, flags);
QFETCH(QDir::Filters, filters); QFETCH(QDir::Filters, filters);
QFETCH(QStringList, nameFilters); QFETCH(QStringList, nameFilters);
QFETCH(QStringList, entries); QFETCH(QStringList, entries);
QDirIterator it(dirName, nameFilters, filters, flags);
QStringList list; QStringList list;
while (it.hasNext()) { for (const auto &dirEntry : QDirListing(dirName, nameFilters, filters, flags)) {
const QString dir = it.next(); QString dir = dirEntry.fileInfo().filePath();
if (!dir.startsWith(":/qt-project.org")) if (!dir.startsWith(":/qt-project.org"))
list << dir; list.emplace_back(std::move(dir));
} }
list.sort(); list.sort();
@ -395,7 +378,7 @@ void tst_QDirIterator::iterateResource()
QCOMPARE(list, sortedEntries); QCOMPARE(list, sortedEntries);
} }
void tst_QDirIterator::stopLinkLoop() void tst_QDirListing::stopLinkLoop()
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
// ### Sadly, this is a platform difference right now. // ### Sadly, this is a platform difference right now.
@ -418,12 +401,14 @@ void tst_QDirIterator::stopLinkLoop()
createLink("..", "entrylist/directory/entrylist4.lnk"); createLink("..", "entrylist/directory/entrylist4.lnk");
#endif #endif
QDirIterator it(QLatin1String("entrylist"), QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); constexpr auto flags = ItFlag::Recursive | ItFlag::FollowSymlinks;
QDirListing dirIter(u"entrylist"_s, flags);
QStringList list; QStringList list;
int max = 200; int max = 200;
while (--max && it.hasNext()) auto it = dirIter.begin();
it.nextFileInfo(); while (--max && it != dirIter.end())
QVERIFY(max); ++it;
QCOMPARE_GT(max, 0);
// The goal of this test is only to ensure that the test above don't malfunction // The goal of this test is only to ensure that the test above don't malfunction
} }
@ -451,7 +436,7 @@ public:
#endif #endif
#ifdef QT_BUILD_INTERNAL #ifdef QT_BUILD_INTERNAL
void tst_QDirIterator::engineWithNoIterator() void tst_QDirListing::engineWithNoIterator()
{ {
EngineWithNoIteratorHandler handler; EngineWithNoIteratorHandler handler;
@ -464,113 +449,98 @@ class CustomEngineHandler : public QAbstractFileEngineHandler
public: public:
QAbstractFileEngine *create(const QString &fileName) const override QAbstractFileEngine *create(const QString &fileName) const override
{ {
// We want to test QFSFileEngine specifically, so force QDirIterator to use it // We want to test QFSFileEngine specifically, so force QDirListing to use it
// over the default QFileSystemEngine // over the default QFileSystemEngine
return new QFSFileEngine(fileName); return new QFSFileEngine(fileName);
} }
}; };
void tst_QDirIterator::testQFsFileEngineIterator() void tst_QDirListing::testQFsFileEngineIterator()
{ {
QFETCH(QString, dirName); QFETCH(QString, dirName);
QFETCH(QStringList, nameFilters); QFETCH(QStringList, nameFilters);
QFETCH(QDir::Filters, filters); QFETCH(QDir::Filters, filters);
QFETCH(QDirIterator::IteratorFlags, flags); QFETCH(QDirListing::IteratorFlags, flags);
if (dirName == u"empty") if (dirName == u"empty")
return; // This row isn't useful in this test return; // This row isn't useful in this test
CustomEngineHandler handler; CustomEngineHandler handler;
bool isEmpty = true; bool isEmpty = true;
QDirIterator iter(dirName, nameFilters, filters, flags); for (const auto &dirEntry : QDirListing(u"entrylist"_s, nameFilters, filters, flags)) {
while (iter.hasNext()) { if (dirEntry.filePath().contains(u"entrylist"))
const QFileInfo &fi = iter.nextFileInfo();
if (fi.filePath().contains(u"entrylist"))
isEmpty = false; // At least one entry in `entrylist` dir isEmpty = false; // At least one entry in `entrylist` dir
} }
QVERIFY(!isEmpty); QVERIFY(!isEmpty); // At least one entry
} }
#endif #endif
void tst_QDirIterator::absoluteFilePathsFromRelativeIteratorPath() void tst_QDirListing::absoluteFilePathsFromRelativeIteratorPath()
{ {
QDirIterator it("entrylist/", QDir::NoDotAndDotDot); for (const auto &dirEntry : QDirListing(u"entrylist/"_s, QDir::NoDotAndDotDot))
while (it.hasNext()) QVERIFY(dirEntry.absoluteFilePath().contains("entrylist"));
QVERIFY(it.nextFileInfo().absoluteFilePath().contains("entrylist"));
} }
void tst_QDirIterator::recurseWithFilters() const void tst_QDirListing::recurseWithFilters() const
{ {
QStringList nameFilters;
nameFilters.append("*.txt");
QDirIterator it("recursiveDirs/", nameFilters, QDir::Files,
QDirIterator::Subdirectories);
QSet<QString> actualEntries; QSet<QString> actualEntries;
QSet<QString> expectedEntries; QSet<QString> expectedEntries;
expectedEntries.insert(QString::fromLatin1("recursiveDirs/dir1/textFileB.txt")); expectedEntries.insert(QString::fromLatin1("recursiveDirs/dir1/textFileB.txt"));
expectedEntries.insert(QString::fromLatin1("recursiveDirs/textFileA.txt")); expectedEntries.insert(QString::fromLatin1("recursiveDirs/textFileA.txt"));
QVERIFY(it.hasNext()); for (const auto &dirEntry : QDirListing(u"recursiveDirs/"_s, QStringList{u"*.txt"_s},
actualEntries.insert(it.next()); QDir::Files, ItFlag::Recursive)) {
QVERIFY(it.hasNext()); actualEntries.insert(dirEntry.filePath());
actualEntries.insert(it.next()); }
QCOMPARE(actualEntries, expectedEntries);
QVERIFY(!it.hasNext()); QCOMPARE(actualEntries, expectedEntries);
} }
void tst_QDirIterator::longPath() void tst_QDirListing::longPath()
{ {
QDir dir; QDir dir;
dir.mkdir("longpaths"); dir.mkdir("longpaths");
dir.cd("longpaths"); dir.cd("longpaths");
QString dirName = "x"; QString dirName = "x";
int n = 0; qsizetype n = 0;
while (dir.exists(dirName) || dir.mkdir(dirName)) { while (dir.exists(dirName) || dir.mkdir(dirName)) {
++n; ++n;
dirName.append('x'); dirName.append('x');
} }
QDirIterator it(dir.absolutePath(), QDir::NoDotAndDotDot|QDir::Dirs, QDirIterator::Subdirectories); QDirListing dirList(dir.absolutePath(), QDir::NoDotAndDotDot|QDir::Dirs, ItFlag::Recursive);
int m = 0; qsizetype m = 0;
while (it.hasNext()) { for (auto it = dirList.begin(); it != dirList.end(); ++it)
++m; ++m;
it.nextFileInfo();
}
QCOMPARE(n, m); QCOMPARE(n, m);
dirName.chop(1); dirName.chop(1);
while (dirName.size() > 0 && dir.exists(dirName) && dir.rmdir(dirName)) { while (dirName.size() > 0 && dir.exists(dirName) && dir.rmdir(dirName))
dirName.chop(1); dirName.chop(1);
}
dir.cdUp(); dir.cdUp();
dir.rmdir("longpaths"); dir.rmdir("longpaths");
} }
void tst_QDirIterator::dirorder() void tst_QDirListing::dirorder()
{ {
QDirIterator iterator("foo", QDirIterator::Subdirectories); QStringList entries;
while (iterator.hasNext() && iterator.next() != "foo/bar") for (const auto &dirEntry : QDirListing(u"foo"_s, ItFlag::Recursive))
{ } entries.append(dirEntry.filePath());
QCOMPARE(iterator.filePath(), QString("foo/bar")); QCOMPARE_GT(entries.indexOf(u"foo/bar"_s), entries.indexOf(u"foo"_s));
QCOMPARE(iterator.fileInfo().filePath(), QString("foo/bar"));
} }
void tst_QDirIterator::relativePaths() void tst_QDirListing::relativePaths()
{ {
QDirIterator iterator("*", QDirIterator::Subdirectories); for (const auto &dirEntry : QDirListing(u"*"_s, ItFlag::Recursive))
while(iterator.hasNext()) { QCOMPARE(dirEntry.filePath(), QDir::cleanPath(dirEntry.filePath()));
QCOMPARE(iterator.filePath(), QDir::cleanPath(iterator.filePath()));
}
} }
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
void tst_QDirIterator::uncPaths_data() void tst_QDirListing::uncPaths_data()
{ {
QTest::addColumn<QString>("dirName"); QTest::addColumn<QString>("dirName");
QTest::newRow("uncserver") QTest::newRow("uncserver")
@ -580,13 +550,13 @@ void tst_QDirIterator::uncPaths_data()
QTest::newRow("uncserver/testshare/tmp") QTest::newRow("uncserver/testshare/tmp")
<<QString("//" + QTest::uncServerName() + "/testshare/tmp"); <<QString("//" + QTest::uncServerName() + "/testshare/tmp");
} }
void tst_QDirIterator::uncPaths() void tst_QDirListing::uncPaths()
{ {
QFETCH(QString, dirName); QFETCH(QString, dirName);
QDirIterator iterator(dirName, QDir::AllEntries|QDir::NoDotAndDotDot, QDirIterator::Subdirectories); constexpr auto dirFilters = QDir::AllEntries | QDir::NoDotAndDotDot;
while(iterator.hasNext()) { for (const auto &dirEntry : QDirListing(dirName, dirFilters, ItFlag::Recursive)) {
iterator.next(); const QString &filePath = dirEntry.filePath();
QCOMPARE(iterator.filePath(), QDir::cleanPath(iterator.filePath())); QCOMPARE(filePath, QDir::cleanPath(filePath));
} }
} }
#endif #endif
@ -596,16 +566,17 @@ void tst_QDirIterator::uncPaths()
// a special call since hidden files need to be "marked" while in Unix // a special call since hidden files need to be "marked" while in Unix
// anything starting by a '.' is a hidden file. // anything starting by a '.' is a hidden file.
// For that reason this test is not run in Windows. // For that reason this test is not run in Windows.
void tst_QDirIterator::hiddenDirs_hiddenFiles() void tst_QDirListing::hiddenDirs_hiddenFiles()
{ {
// Only files // Only files
{ {
int matches = 0; int matches = 0;
int failures = 0; int failures = 0;
QDirIterator di("hiddenDirs_hiddenFiles", QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); constexpr auto filters = QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot;
while (di.hasNext()) { for (const auto &dirEntry : QDirListing(u"hiddenDirs_hiddenFiles"_s, filters,
ItFlag::Recursive)) {
++matches; ++matches;
if (di.nextFileInfo().isDir()) if (dirEntry.isDir())
++failures; // search was only supposed to find files ++failures; // search was only supposed to find files
} }
QCOMPARE(matches, 6); QCOMPARE(matches, 6);
@ -615,10 +586,11 @@ void tst_QDirIterator::hiddenDirs_hiddenFiles()
{ {
int matches = 0; int matches = 0;
int failures = 0; int failures = 0;
QDirIterator di("hiddenDirs_hiddenFiles", QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); constexpr auto filters = QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot;
while (di.hasNext()) { for (const auto &dirEntry : QDirListing(u"hiddenDirs_hiddenFiles"_s, filters,
ItFlag::Recursive)) {
++matches; ++matches;
if (!di.nextFileInfo().isDir()) if (!dirEntry.isDir())
++failures; // search was only supposed to find files ++failures; // search was only supposed to find files
} }
QCOMPARE(matches, 6); QCOMPARE(matches, 6);
@ -627,7 +599,7 @@ void tst_QDirIterator::hiddenDirs_hiddenFiles()
} }
#endif // Q_OS_WIN #endif // Q_OS_WIN
QTEST_MAIN(tst_QDirIterator) QTEST_MAIN(tst_QDirListing)
#include "tst_qdiriterator.moc" #include "tst_qdirlisting.moc"