1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

DirectoryIterator: Avoid recursing into previously-visited directories

This commit is contained in:
reuk 2022-01-20 15:17:22 +00:00
parent 4cf036bb8b
commit 00e7fbf1c2
7 changed files with 95 additions and 24 deletions

View file

@ -75,13 +75,26 @@ bool DirectoryIterator::next (bool* isDirResult, bool* isHiddenResult, int64* fi
if (! filename.containsOnly ("."))
{
const auto fullPath = File::createFileWithoutCheckingPath (path + filename);
bool matches = false;
if (isDirectory)
{
if (isRecursive && ((whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden))
subIterator.reset (new DirectoryIterator (File::createFileWithoutCheckingPath (path + filename),
true, wildCard, whatToLookFor));
const auto mayRecurseIntoPossibleHiddenDir = [this, &isHidden]
{
return (whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden;
};
const auto mayRecurseIntoPossibleSymlink = [this, &fullPath]
{
return followSymlinks == File::FollowSymlinks::yes
|| ! fullPath.isSymbolicLink()
|| (followSymlinks == File::FollowSymlinks::noCycles
&& knownPaths->find (fullPath.getLinkedTarget()) == knownPaths->end());
};
if (isRecursive && mayRecurseIntoPossibleHiddenDir() && mayRecurseIntoPossibleSymlink())
subIterator.reset (new DirectoryIterator (fullPath, true, wildCard, whatToLookFor, followSymlinks, knownPaths));
matches = (whatToLookFor & File::findDirectories) != 0;
}
@ -99,7 +112,7 @@ bool DirectoryIterator::next (bool* isDirResult, bool* isHiddenResult, int64* fi
if (matches)
{
currentFile = File::createFileWithoutCheckingPath (path + filename);
currentFile = fullPath;
if (isHiddenResult != nullptr) *isHiddenResult = isHidden;
if (isDirResult != nullptr) *isDirResult = isDirectory;

View file

@ -34,6 +34,10 @@ namespace juce
A DirectoryIterator will search through a directory and its subdirectories using
a wildcard filepattern match.
The iterator keeps track of directories that it has previously traversed, and will
skip any previously-seen directories in the case of cycles caused by symbolic links.
It is also possible to avoid following symbolic links altogether.
If you may be scanning a large number of files, it's usually smarter to use this
class than File::findChildFiles() because it allows you to stop at any time, rather
than having to wait for the entire scan to finish before getting the results.
@ -73,17 +77,10 @@ public:
DirectoryIterator (const File& directory,
bool recursive,
const String& pattern = "*",
int type = File::findFiles)
: wildCards (parseWildcards (pattern)),
fileFinder (directory, (recursive || wildCards.size() > 1) ? "*" : pattern),
wildCard (pattern),
path (File::addTrailingSeparator (directory.getFullPathName())),
whatToLookFor (type),
isRecursive (recursive)
int type = File::findFiles,
File::FollowSymlinks follow = File::FollowSymlinks::yes)
: DirectoryIterator (directory, recursive, pattern, type, follow, nullptr)
{
// you have to specify the type of files you're looking for!
jassert ((whatToLookFor & (File::findFiles | File::findDirectories)) != 0);
jassert (whatToLookFor > 0 && whatToLookFor <= 7);
}
/** Moves the iterator along to the next file.
@ -126,6 +123,39 @@ public:
float getEstimatedProgress() const;
private:
using KnownPaths = std::set<File>;
DirectoryIterator (const File& directory,
bool recursive,
const String& pattern,
int type,
File::FollowSymlinks follow,
KnownPaths* seenPaths)
: wildCards (parseWildcards (pattern)),
fileFinder (directory, (recursive || wildCards.size() > 1) ? "*" : pattern),
wildCard (pattern),
path (File::addTrailingSeparator (directory.getFullPathName())),
whatToLookFor (type),
isRecursive (recursive),
followSymlinks (follow),
knownPaths (seenPaths)
{
// you have to specify the type of files you're looking for!
jassert ((whatToLookFor & (File::findFiles | File::findDirectories)) != 0);
jassert (whatToLookFor > 0 && whatToLookFor <= 7);
if (followSymlinks == File::FollowSymlinks::noCycles)
{
if (knownPaths == nullptr)
{
heapKnownPaths = std::make_unique<KnownPaths>();
knownPaths = heapKnownPaths.get();
}
knownPaths->insert (directory);
}
}
//==============================================================================
struct NativeIterator
{
@ -152,6 +182,9 @@ private:
bool hasBeenAdvanced = false;
std::unique_ptr<DirectoryIterator> subIterator;
File currentFile;
File::FollowSymlinks followSymlinks = File::FollowSymlinks::yes;
KnownPaths* knownPaths = nullptr;
std::unique_ptr<KnownPaths> heapKnownPaths;
static StringArray parseWildcards (const String& pattern);
static bool fileMatches (const StringArray& wildCards, const String& filename);

View file

@ -561,18 +561,18 @@ void File::readLines (StringArray& destLines) const
}
//==============================================================================
Array<File> File::findChildFiles (int whatToLookFor, bool searchRecursively, const String& wildcard) const
Array<File> File::findChildFiles (int whatToLookFor, bool searchRecursively, const String& wildcard, FollowSymlinks followSymlinks) const
{
Array<File> results;
findChildFiles (results, whatToLookFor, searchRecursively, wildcard);
findChildFiles (results, whatToLookFor, searchRecursively, wildcard, followSymlinks);
return results;
}
int File::findChildFiles (Array<File>& results, int whatToLookFor, bool searchRecursively, const String& wildcard) const
int File::findChildFiles (Array<File>& results, int whatToLookFor, bool searchRecursively, const String& wildcard, FollowSymlinks followSymlinks) const
{
int total = 0;
for (const auto& di : RangedDirectoryIterator (*this, searchRecursively, wildcard, whatToLookFor))
for (const auto& di : RangedDirectoryIterator (*this, searchRecursively, wildcard, whatToLookFor, followSymlinks))
{
results.add (di.getFile());
++total;

View file

@ -560,6 +560,23 @@ public:
ignoreHiddenFiles = 4 /**< Add this flag to avoid returning any hidden files in the results. */
};
enum class FollowSymlinks
{
/** Requests that a file system traversal should not follow any symbolic links. */
no,
/** Requests that a file system traversal may follow symbolic links, but should attempt to
skip any symbolic links to directories that may cause a cycle.
*/
noCycles,
/** Requests that a file system traversal follow all symbolic links. Use with care, as this
may produce inconsistent results, or fail to terminate, if the filesystem contains cycles
due to symbolic links.
*/
yes
};
/** Searches this directory for files matching a wildcard pattern.
Assuming that this file is a directory, this method will search it
@ -572,13 +589,15 @@ public:
@param searchRecursively if true, all subdirectories will be recursed into to do
an exhaustive search
@param wildCardPattern the filename pattern to search for, e.g. "*.txt"
@param followSymlinks the method that should be used to handle symbolic links
@returns the set of files that were found
@see getNumberOfChildFiles, RangedDirectoryIterator
*/
Array<File> findChildFiles (int whatToLookFor,
bool searchRecursively,
const String& wildCardPattern = "*") const;
const String& wildCardPattern = "*",
FollowSymlinks followSymlinks = FollowSymlinks::yes) const;
/** Searches inside a directory for files matching a wildcard pattern.
Note that there's a newer, better version of this method which returns the results
@ -586,7 +605,8 @@ public:
mainly for legacy code to use.
*/
int findChildFiles (Array<File>& results, int whatToLookFor,
bool searchRecursively, const String& wildCardPattern = "*") const;
bool searchRecursively, const String& wildCardPattern = "*",
FollowSymlinks followSymlinks = FollowSymlinks::yes) const;
/** Searches inside a directory and counts how many files match a wildcard pattern.

View file

@ -39,11 +39,13 @@ float DirectoryEntry::getEstimatedProgress() const
RangedDirectoryIterator::RangedDirectoryIterator (const File& directory,
bool isRecursive,
const String& wildCard,
int whatToLookFor)
int whatToLookFor,
File::FollowSymlinks followSymlinks)
: iterator (new DirectoryIterator (directory,
isRecursive,
wildCard,
whatToLookFor))
whatToLookFor,
followSymlinks))
{
entry.iterator = iterator;
increment();

View file

@ -118,11 +118,13 @@ public:
separated by a semi-colon or comma, e.g. "*.jpg;*.png"
@param whatToLookFor a value from the File::TypesOfFileToFind enum, specifying
whether to look for files, directories, or both.
@param followSymlinks the policy to use when symlinks are encountered
*/
RangedDirectoryIterator (const File& directory,
bool isRecursive,
const String& wildCard = "*",
int whatToLookFor = File::findFiles);
int whatToLookFor = File::findFiles,
File::FollowSymlinks followSymlinks = File::FollowSymlinks::yes);
/** Returns true if both iterators are in their end/sentinel state,
otherwise returns false.

View file

@ -55,13 +55,14 @@
#include <limits>
#include <list>
#include <map>
#include <unordered_map>
#include <memory>
#include <mutex>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <typeindex>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <set>