1
/****************************************************************************
2
**
3
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
** All rights reserved.
5
** Contact: Nokia Corporation (qt-info@nokia.com)
6
**
7
** This file is part of the QtCore module of the Qt Toolkit.
8
**
9
** $QT_BEGIN_LICENSE:LGPL$
10
** No Commercial Usage
11
** This file contains pre-release code and may not be distributed.
12
** You may use this file in accordance with the terms and conditions
13
** contained in the Technology Preview License Agreement accompanying
14
** this package.
15
**
16
** GNU Lesser General Public License Usage
17
** Alternatively, this file may be used under the terms of the GNU Lesser
18
** General Public License version 2.1 as published by the Free Software
19
** Foundation and appearing in the file LICENSE.LGPL included in the
20
** packaging of this file.  Please review the following information to
21
** ensure the GNU Lesser General Public License version 2.1 requirements
22
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
**
24
** In addition, as a special exception, Nokia gives you certain additional
25
** rights.  These rights are described in the Nokia Qt LGPL Exception
26
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27
**
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
30
**
31
**
32
**
33
**
34
**
35
**
36
**
37
**
38
** $QT_END_LICENSE$
39
**
40
****************************************************************************/
41
42
/*!
43
    \since 4.3
44
    \class QDirIterator
45
    \brief The QDirIterator class provides an iterator for directory entrylists.
46
47
    You can use QDirIterator to navigate entries of a directory one at a time.
48
    It is similar to QDir::entryList() and QDir::entryInfoList(), but because
49
    it lists entries one at a time instead of all at once, it scales better
50
    and is more suitable for large directories. It also supports listing
51
    directory contents recursively, and following symbolic links. Unlike
52
    QDir::entryList(), QDirIterator does not support sorting.
53
54
    The QDirIterator constructor takes a QDir or a directory as
55
    argument. After construction, the iterator is located before the first
56
    directory entry. Here's how to iterate over all the entries sequentially:
57
58
    \snippet doc/src/snippets/code/src_corelib_io_qdiriterator.cpp 0
59
60
    The next() function returns the path to the next directory entry and
61
    advances the iterator. You can also call filePath() to get the current
62
    file path without advancing the iterator.  The fileName() function returns
63
    only the name of the file, similar to how QDir::entryList() works. You can
64
    also call fileInfo() to get a QFileInfo for the current entry.
65
66
    Unlike Qt's container iterators, QDirIterator is uni-directional (i.e.,
67
    you cannot iterate directories in reverse order) and does not allow random
68
    access.
69
70
    QDirIterator works with all supported file engines, and is implemented
71
    using QAbstractFileEngineIterator.
72
73
    \sa QDir, QDir::entryList(), QAbstractFileEngineIterator
74
*/
75
76
/*! \enum QDirIterator::IteratorFlag
77
78
    This enum describes flags that you can combine to configure the behavior
79
    of QDirIterator.
80
81
    \value NoIteratorFlags The default value, representing no flags. The
82
    iterator will return entries for the assigned path.
83
84
    \value Subdirectories List entries inside all subdirectories as well.
85
86
    \value FollowSymlinks When combined with Subdirectories, this flag
87
    enables iterating through all subdirectories of the assigned path,
88
    following all symbolic links. Symbolic link loops (e.g., "link" => "." or
89
    "link" => "..") are automatically detected and ignored.
90
*/
91
92
#include "qdiriterator.h"
93
94
#include "qabstractfileengine.h"
95
96
#include <QtCore/qset.h>
97
#include <QtCore/qstack.h>
98
#include <QtCore/qvariant.h>
99
100
QT_BEGIN_NAMESPACE
101
102
class QDirIteratorPrivate
103
{
104
public:
105
    QDirIteratorPrivate(const QString &path, const QStringList &nameFilters,
106
                        QDir::Filters filters, QDirIterator::IteratorFlags flags);
107
    ~QDirIteratorPrivate();
108
109
    void pushSubDirectory(const QFileInfo &fileInfo, const QStringList &nameFilters,
110
                          QDir::Filters filters);
111
    void advance();
112
    bool shouldFollowDirectory(const QFileInfo &);
113
    bool matchesFilters(const QAbstractFileEngineIterator *it) const;
114
115
    QSet<QString> visitedLinks;
116
    QAbstractFileEngine *engine;
117
    QStack<QAbstractFileEngineIterator *> fileEngineIterators;
118
    QString path;
119
    QFileInfo nextFileInfo;
120
    //This fileinfo is the current that we will return from the public API
121
    QFileInfo currentFileInfo;
122
    QDirIterator::IteratorFlags iteratorFlags;
123
    QDir::Filters filters;
124
    QStringList nameFilters;
125
    bool followNextDir;
126
    bool first;
127
    bool done;
128
129
    QDirIterator *q;
130
};
131
132
/*!
133
    \internal
134
*/
135
QDirIteratorPrivate::QDirIteratorPrivate(const QString &path, const QStringList &nameFilters,
136
                                         QDir::Filters filters, QDirIterator::IteratorFlags flags)
137
    : engine(0), path(path), iteratorFlags(flags), followNextDir(false), first(true), done(false)
138
{
139
    if (filters == QDir::NoFilter)
140
        filters = QDir::AllEntries;
141
    this->filters = filters;
142
    this->nameFilters = nameFilters;
143
144
    nextFileInfo.setFile(path);
145
    pushSubDirectory(nextFileInfo, nameFilters, filters);
146
}
147
148
/*!
149
    \internal
150
*/
151
QDirIteratorPrivate::~QDirIteratorPrivate()
152
{
153
    delete engine;
154
}
155
156
/*!
157
    \internal
158
*/
159
void QDirIteratorPrivate::pushSubDirectory(const QFileInfo &fileInfo, const QStringList &nameFilters,
160
                                           QDir::Filters filters)
161
{
162
    QString path = fileInfo.filePath();
163
164
#ifdef Q_OS_WIN
165
    if (fileInfo.isSymLink())
166
        path = fileInfo.canonicalFilePath();
167
#endif
168
169
    if (iteratorFlags & QDirIterator::FollowSymlinks)
170
        visitedLinks << fileInfo.canonicalFilePath();
171
172
    if (engine || (engine = QAbstractFileEngine::create(this->path))) {
173
        engine->setFileName(path);
174
        QAbstractFileEngineIterator *it = engine->beginEntryList(filters, nameFilters);
175
        if (it) {
176
            it->setPath(path);
177
            fileEngineIterators << it;
178
        } else {
179
            // No iterator; no entry list.
180
        }
181
    }
182
}
183
184
/*!
185
    \internal
186
*/
187
void QDirIteratorPrivate::advance()
188
{
189
    // Advance to the next entry
190
    if (followNextDir) {
191
        // Start by navigating into the current directory.
192
        QAbstractFileEngineIterator *it = fileEngineIterators.top();
193
        pushSubDirectory(it->currentFileInfo(), it->nameFilters(), it->filters());
194
        followNextDir = false;
195
    }
196
197
    while (!fileEngineIterators.isEmpty()) {
198
        QAbstractFileEngineIterator *it = fileEngineIterators.top();
199
200
        // Find the next valid iterator that matches the filters.
201
        bool foundDirectory = false;
202
        while (it->hasNext()) {
203
            it->next();
204
            if (matchesFilters(it)) {
205
                currentFileInfo = nextFileInfo;
206
                nextFileInfo = it->currentFileInfo();
207
                // Signal that we want to follow this entry.
208
                followNextDir = shouldFollowDirectory(nextFileInfo);
209
                //We found a matching entry.
210
                return;
211
212
            } else if (shouldFollowDirectory(it->currentFileInfo())) {
213
                pushSubDirectory(it->currentFileInfo(), it->nameFilters(), it->filters());
214
                foundDirectory = true;
215
                break;
216
            }
217
        }
218
        if (!foundDirectory)
219
            delete fileEngineIterators.pop();
220
    }
221
    currentFileInfo = nextFileInfo;
222
    done = true;
223
}
224
225
/*!
226
    \internal
227
 */
228
bool QDirIteratorPrivate::shouldFollowDirectory(const QFileInfo &fileInfo)
229
{
230
    // If we're doing flat iteration, we're done.
231
    if (!(iteratorFlags & QDirIterator::Subdirectories))
232
        return false;
233
234
    // Never follow non-directory entries
235
    if (!fileInfo.isDir())
236
        return false;
237
238
    // Never follow . and ..
239
    if (fileInfo.fileName() == QLatin1String(".") || fileInfo.fileName() == QLatin1String(".."))
240
        return false;
241
242
    // Check symlinks
243
    if (fileInfo.isSymLink() && !(iteratorFlags & QDirIterator::FollowSymlinks)) {
244
        // Follow symlinks only if FollowSymlinks was passed
245
        return false;
246
    }
247
248
    // Stop link loops
249
    if (visitedLinks.contains(fileInfo.canonicalFilePath()))
250
        return false;
251
252
    return true;
253
}
254
255
/*!
256
    \internal
257
258
    This convenience function implements the iterator's filtering logics and
259
    applies then to the current directory entry.
260
261
    It returns true if the current entry matches the filters (i.e., the
262
    current entry will be returned as part of the directory iteration);
263
    otherwise, false is returned.
264
*/
265
bool QDirIteratorPrivate::matchesFilters(const QAbstractFileEngineIterator *it) const
266
{
267
    const bool filterPermissions = ((filters & QDir::PermissionMask)
268
                                    && (filters & QDir::PermissionMask) != QDir::PermissionMask);
269
    const bool skipDirs     = !(filters & (QDir::Dirs | QDir::AllDirs));
270
    const bool skipFiles    = !(filters & QDir::Files);
271
    const bool skipSymlinks = (filters & QDir::NoSymLinks);
272
    const bool doReadable   = !filterPermissions || (filters & QDir::Readable);
273
    const bool doWritable   = !filterPermissions || (filters & QDir::Writable);
274
    const bool doExecutable = !filterPermissions || (filters & QDir::Executable);
275
    const bool includeHidden = (filters & QDir::Hidden);
276
    const bool includeSystem = (filters & QDir::System);
277
278
#ifndef QT_NO_REGEXP
279
    // Prepare name filters
280
    QList<QRegExp> regexps;
281
    bool hasNameFilters = !nameFilters.isEmpty() && !(nameFilters.contains(QLatin1String("*")));
282
    if (hasNameFilters) {
283
        for (int i = 0; i < nameFilters.size(); ++i) {
284
            regexps << QRegExp(nameFilters.at(i),
285
                               (filters & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive,
286
                               QRegExp::Wildcard);
287
        }
288
    }
289
#endif
290
291
    QString fileName = it->currentFileName();
292
    if (fileName.isEmpty()) {
293
        // invalid entry
294
        return false;
295
    }
296
297
    QFileInfo fi = it->currentFileInfo();
298
    QString filePath = it->currentFilePath();
299
300
#ifndef QT_NO_REGEXP
301
    // Pass all entries through name filters, except dirs if the AllDirs
302
    // filter is passed.
303
    if (hasNameFilters && !((filters & QDir::AllDirs) && fi.isDir())) {
304
        bool matched = false;
305
        for (int i = 0; i < regexps.size(); ++i) {
306
            if (regexps.at(i).exactMatch(fileName)) {
307
                matched = true;
308
                break;
309
            }
310
        }
311
        if (!matched)
312
            return false;
313
    }
314
#endif
315
316
    bool dotOrDotDot = (fileName == QLatin1String(".") || fileName == QLatin1String(".."));
317
    if ((filters & QDir::NoDotAndDotDot) && dotOrDotDot)
318
        return false;
319
320
    bool isHidden = !dotOrDotDot && fi.isHidden();
321
    if (!includeHidden && isHidden)
322
        return false;
323
324
    bool isSystem = (!fi.isFile() && !fi.isDir() && !fi.isSymLink())
325
                    || (!fi.exists() && fi.isSymLink());
326
    if (!includeSystem && isSystem)
327
        return false;
328
329
    bool alwaysShow = (filters & QDir::TypeMask) == 0
330
        && ((isHidden && includeHidden)
331
            || (includeSystem && isSystem));
332
333
    // Skip files and directories
334
    if ((filters & QDir::AllDirs) == 0 && skipDirs && fi.isDir()) {
335
        if (!alwaysShow)
336
            return false;
337
    }
338
339
    if ((skipFiles && (fi.isFile() || !fi.exists()))
340
        || (skipSymlinks && fi.isSymLink())) {
341
        if (!alwaysShow)
342
            return false;
343
    }
344
345
    if (filterPermissions
346
        && ((doReadable && !fi.isReadable())
347
            || (doWritable && !fi.isWritable())
348
            || (doExecutable && !fi.isExecutable()))) {
349
        return false;
350
    }
351
352
    if (!includeSystem && !dotOrDotDot && ((fi.exists() && !fi.isFile() && !fi.isDir() && !fi.isSymLink())
353
                                           || (!fi.exists() && fi.isSymLink()))) {
354
        return false;
355
    }
356
357
    return true;
358
}
359
360
/*!
361
    Constructs a QDirIterator that can iterate over \a dir's entrylist, using
362
    \a dir's name filters and regular filters. You can pass options via \a
363
    flags to decide how the directory should be iterated.
364
365
    By default, \a flags is NoIteratorFlags, which provides the same behavior
366
    as in QDir::entryList().
367
368
    The sorting in \a dir is ignored.
369
370
    \note To list symlinks that point to non existing files, QDir::System must be
371
     passed to the flags.
372
373
    \sa hasNext(), next(), IteratorFlags
374
*/
375
QDirIterator::QDirIterator(const QDir &dir, IteratorFlags flags)
376
    : d(new QDirIteratorPrivate(dir.path(), dir.nameFilters(), dir.filter(), flags))
377
{
378
    d->q = this;
379
}
380
381
/*!
382
    Constructs a QDirIterator that can iterate over \a path, with no name
383
    filtering and \a filters for entry filtering. You can pass options via \a
384
    flags to decide how the directory should be iterated.
385
386
    By default, \a filters is QDir::NoFilter, and \a flags is NoIteratorFlags,
387
    which provides the same behavior as in QDir::entryList().
388
389
    \note To list symlinks that point to non existing files, QDir::System must be
390
     passed to the flags.
391
392
    \warning This constructor expects \a flags to be left at its default value. Use
393
             the constructors that do not take the \a filters argument instead.
394
395
    \sa hasNext(), next(), IteratorFlags
396
*/
397
QDirIterator::QDirIterator(const QString &path, QDir::Filters filters, IteratorFlags flags)
398
    : d(new QDirIteratorPrivate(path, QStringList(QLatin1String("*")), filters, flags))
399
{
400
    d->q = this;
401
}
402
403
/*!
404
    Constructs a QDirIterator that can iterate over \a path. You can pass
405
    options via \a flags to decide how the directory should be iterated.
406
407
    By default, \a flags is NoIteratorFlags, which provides the same behavior
408
    as in QDir::entryList().
409
410
    \note To list symlinks that point to non existing files, QDir::System must be
411
     passed to the flags.
412
413
    \sa hasNext(), next(), IteratorFlags
414
*/
415
QDirIterator::QDirIterator(const QString &path, IteratorFlags flags)
416
    : d(new QDirIteratorPrivate(path, QStringList(QLatin1String("*")), QDir::NoFilter, flags))
417
{
418
    d->q = this;
419
}
420
421
/*!
422
    Constructs a QDirIterator that can iterate over \a path, using \a
423
    nameFilters and \a filters. You can pass options via \a flags to decide
424
    how the directory should be iterated.
425
426
    By default, \a flags is NoIteratorFlags, which provides the same behavior
427
    as QDir::entryList().
428
429
    \note To list symlinks that point to non existing files, QDir::System must be
430
     passed to the flags.
431
432
    \warning This constructor expects \c flags to be left at its default value. Use the
433
             constructors that do not take the \a filters argument instead.
434
435
    \sa hasNext(), next(), IteratorFlags
436
*/
437
QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters,
438
                           QDir::Filters filters, IteratorFlags flags)
439
    : d(new QDirIteratorPrivate(path, nameFilters, filters, flags))
440
{
441
    d->q = this;
442
}
443
444
/*!
445
    Destroys the QDirIterator.
446
*/
447
QDirIterator::~QDirIterator()
448
{
449
    qDeleteAll(d->fileEngineIterators);
450
    delete d;
451
}
452
453
/*!
454
    Advances the iterator to the next entry, and returns the file path of this
455
    new entry. If hasNext() returns false, this function does nothing, and
456
    returns a null QString.
457
458
    You can call fileName() or filePath() to get the current entry file name
459
    or path, or fileInfo() to get a QFileInfo for the current entry.
460
461
    \sa hasNext(), fileName(), filePath(), fileInfo()
462
*/
463
QString QDirIterator::next()
464
{
465
    if (!hasNext())
466
        return QString();
467
    d->advance();
468
    return filePath();
469
}
470
471
/*!
472
    Returns true if there is at least one more entry in the directory;
473
    otherwise, false is returned.
474
475
    \sa next(), fileName(), filePath(), fileInfo()
476
*/
477
bool QDirIterator::hasNext() const
478
{
479
    if (d->first) {
480
        d->first = false;
481
        d->advance();
482
    }
483
    return !d->done;
484
}
485
486
/*!
487
    Returns the file name for the current directory entry, without the path
488
    prepended.
489
490
    This function is convenient when iterating a single directory. When using
491
    the QDirIterator::Subdirectories flag, you can use filePath() to get the
492
    full path.
493
494
    \sa filePath(), fileInfo()
495
*/
496
QString QDirIterator::fileName() const
497
{
498
    return d->currentFileInfo.fileName();
499
}
500
501
/*!
502
    Returns the full file path for the current directory entry.
503
504
    \sa fileInfo(), fileName()
505
*/
506
QString QDirIterator::filePath() const
507
{
508
    return d->currentFileInfo.filePath();
509
}
510
511
/*!
512
    Returns a QFileInfo for the current directory entry.
513
514
    \sa filePath(), fileName()
515
*/
516
QFileInfo QDirIterator::fileInfo() const
517
{
518
    return d->currentFileInfo;
519
}
520
521
/*!
522
    Returns the base directory of the iterator.
523
*/
524
QString QDirIterator::path() const
525
{
526
    return d->path;
527
}
528
529
QT_END_NAMESPACE