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 (".")) if (! filename.containsOnly ("."))
{ {
const auto fullPath = File::createFileWithoutCheckingPath (path + filename);
bool matches = false; bool matches = false;
if (isDirectory) if (isDirectory)
{ {
if (isRecursive && ((whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden)) const auto mayRecurseIntoPossibleHiddenDir = [this, &isHidden]
subIterator.reset (new DirectoryIterator (File::createFileWithoutCheckingPath (path + filename), {
true, wildCard, whatToLookFor)); 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; matches = (whatToLookFor & File::findDirectories) != 0;
} }
@ -99,7 +112,7 @@ bool DirectoryIterator::next (bool* isDirResult, bool* isHiddenResult, int64* fi
if (matches) if (matches)
{ {
currentFile = File::createFileWithoutCheckingPath (path + filename); currentFile = fullPath;
if (isHiddenResult != nullptr) *isHiddenResult = isHidden; if (isHiddenResult != nullptr) *isHiddenResult = isHidden;
if (isDirResult != nullptr) *isDirResult = isDirectory; 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 DirectoryIterator will search through a directory and its subdirectories using
a wildcard filepattern match. 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 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 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. than having to wait for the entire scan to finish before getting the results.
@ -73,17 +77,10 @@ public:
DirectoryIterator (const File& directory, DirectoryIterator (const File& directory,
bool recursive, bool recursive,
const String& pattern = "*", const String& pattern = "*",
int type = File::findFiles) int type = File::findFiles,
: wildCards (parseWildcards (pattern)), File::FollowSymlinks follow = File::FollowSymlinks::yes)
fileFinder (directory, (recursive || wildCards.size() > 1) ? "*" : pattern), : DirectoryIterator (directory, recursive, pattern, type, follow, nullptr)
wildCard (pattern),
path (File::addTrailingSeparator (directory.getFullPathName())),
whatToLookFor (type),
isRecursive (recursive)
{ {
// 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. /** Moves the iterator along to the next file.
@ -126,6 +123,39 @@ public:
float getEstimatedProgress() const; float getEstimatedProgress() const;
private: 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 struct NativeIterator
{ {
@ -152,6 +182,9 @@ private:
bool hasBeenAdvanced = false; bool hasBeenAdvanced = false;
std::unique_ptr<DirectoryIterator> subIterator; std::unique_ptr<DirectoryIterator> subIterator;
File currentFile; File currentFile;
File::FollowSymlinks followSymlinks = File::FollowSymlinks::yes;
KnownPaths* knownPaths = nullptr;
std::unique_ptr<KnownPaths> heapKnownPaths;
static StringArray parseWildcards (const String& pattern); static StringArray parseWildcards (const String& pattern);
static bool fileMatches (const StringArray& wildCards, const String& filename); 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; Array<File> results;
findChildFiles (results, whatToLookFor, searchRecursively, wildcard); findChildFiles (results, whatToLookFor, searchRecursively, wildcard, followSymlinks);
return results; 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; 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()); results.add (di.getFile());
++total; ++total;

View file

@ -560,6 +560,23 @@ public:
ignoreHiddenFiles = 4 /**< Add this flag to avoid returning any hidden files in the results. */ 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. /** Searches this directory for files matching a wildcard pattern.
Assuming that this file is a directory, this method will search it 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 @param searchRecursively if true, all subdirectories will be recursed into to do
an exhaustive search an exhaustive search
@param wildCardPattern the filename pattern to search for, e.g. "*.txt" @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 @returns the set of files that were found
@see getNumberOfChildFiles, RangedDirectoryIterator @see getNumberOfChildFiles, RangedDirectoryIterator
*/ */
Array<File> findChildFiles (int whatToLookFor, Array<File> findChildFiles (int whatToLookFor,
bool searchRecursively, bool searchRecursively,
const String& wildCardPattern = "*") const; const String& wildCardPattern = "*",
FollowSymlinks followSymlinks = FollowSymlinks::yes) const;
/** Searches inside a directory for files matching a wildcard pattern. /** 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 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. mainly for legacy code to use.
*/ */
int findChildFiles (Array<File>& results, int whatToLookFor, 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. /** 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, RangedDirectoryIterator::RangedDirectoryIterator (const File& directory,
bool isRecursive, bool isRecursive,
const String& wildCard, const String& wildCard,
int whatToLookFor) int whatToLookFor,
File::FollowSymlinks followSymlinks)
: iterator (new DirectoryIterator (directory, : iterator (new DirectoryIterator (directory,
isRecursive, isRecursive,
wildCard, wildCard,
whatToLookFor)) whatToLookFor,
followSymlinks))
{ {
entry.iterator = iterator; entry.iterator = iterator;
increment(); increment();

View file

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

View file

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