From 543b001e9cfe85f981d1266877a10bbe7d806a1e Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 31 Jan 2023 13:00:14 +0000 Subject: [PATCH] FileSearchPath: Allow working with paths that are not necessarily absolute This allows paths that are prefixed with environment variables to behave as expected. This is useful when scanning the default LV2 locations in the AudioPluginHost on Windows. --- .../scanning/juce_PluginListComponent.cpp | 6 +- .../juce_core/files/juce_FileSearchPath.cpp | 86 ++++++++++++++++--- modules/juce_core/files/juce_FileSearchPath.h | 13 ++- .../juce_FileSearchPathListComponent.cpp | 6 +- 4 files changed, 93 insertions(+), 18 deletions(-) diff --git a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 6af4db7640..008c74753b 100644 --- a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -472,9 +472,9 @@ private: { for (int i = 0; i < pathList.getPath().getNumPaths(); ++i) { - auto f = pathList.getPath()[i]; + auto f = pathList.getPath().getRawString (i); - if (isStupidPath (f)) + if (File::isAbsolutePath (f) && isStupidPath (File (f))) { AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon, TRANS("Plugin Scanning"), @@ -483,7 +483,7 @@ private: "attempting to load unsuitable files.") + newLine + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") - .replace ("XYZ", f.getFullPathName()), + .replace ("XYZ", f), TRANS ("Scan"), String(), nullptr, diff --git a/modules/juce_core/files/juce_FileSearchPath.cpp b/modules/juce_core/files/juce_FileSearchPath.cpp index 6b4ad3981b..9b786ac05e 100644 --- a/modules/juce_core/files/juce_FileSearchPath.cpp +++ b/modules/juce_core/files/juce_FileSearchPath.cpp @@ -63,7 +63,12 @@ int FileSearchPath::getNumPaths() const File FileSearchPath::operator[] (int index) const { - return File (directories[index]); + return File (getRawString (index)); +} + +String FileSearchPath::getRawString (int index) const +{ + return directories[index]; } String FileSearchPath::toString() const @@ -110,21 +115,30 @@ void FileSearchPath::addPath (const FileSearchPath& other) void FileSearchPath::removeRedundantPaths() { - for (int i = directories.size(); --i >= 0;) + std::vector reduced; + + for (const auto& directory : directories) { - const File d1 (directories[i]); + const auto checkedIsChildOf = [&] (const auto& a, const auto& b) + { + return File::isAbsolutePath (a) && File::isAbsolutePath (b) && File (a).isAChildOf (b); + }; - for (int j = directories.size(); --j >= 0;) + const auto fContainsDirectory = [&] (const auto& f) { - const File d2 (directories[j]); + return f == directory || checkedIsChildOf (directory, f); + }; - if (i != j && (d1.isAChildOf (d2) || d1 == d2)) - { - directories.remove (i); - break; - } - } + if (std::find_if (reduced.begin(), reduced.end(), fContainsDirectory) != reduced.end()) + continue; + + const auto directoryContainsF = [&] (const auto& f) { return checkedIsChildOf (f, directory); }; + + reduced.erase (std::remove_if (reduced.begin(), reduced.end(), directoryContainsF), reduced.end()); + reduced.push_back (directory); } + + directories = StringArray (reduced.data(), (int) reduced.size()); } void FileSearchPath::removeNonExistentPaths() @@ -172,4 +186,54 @@ bool FileSearchPath::isFileInPath (const File& fileToCheck, return false; } +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class FileSearchPathTests : public UnitTest +{ +public: + FileSearchPathTests() : UnitTest ("FileSearchPath", UnitTestCategories::files) {} + + void runTest() override + { + beginTest ("removeRedundantPaths"); + { + #if JUCE_WINDOWS + const String prefix = "C:"; + #else + const String prefix = ""; + #endif + + { + FileSearchPath fsp { prefix + "/a/b/c/d;" + prefix + "/a/b/c/e;" + prefix + "/a/b/c" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), prefix + "/a/b/c"); + } + + { + FileSearchPath fsp { prefix + "/a/b/c;" + prefix + "/a/b/c/d;" + prefix + "/a/b/c/e" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), prefix + "/a/b/c"); + } + + { + FileSearchPath fsp { prefix + "/a/b/c/d;" + prefix + "/a/b/c;" + prefix + "/a/b/c/e" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), prefix + "/a/b/c"); + } + + { + FileSearchPath fsp { "%FOO%;" + prefix + "/a/b/c;%FOO%;" + prefix + "/a/b/c/d" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), "%FOO%;" + prefix + "/a/b/c"); + } + } + } +}; + +static FileSearchPathTests fileSearchPathTests; + +#endif + } // namespace juce diff --git a/modules/juce_core/files/juce_FileSearchPath.h b/modules/juce_core/files/juce_FileSearchPath.h index 8b48162ba5..d789cf4133 100644 --- a/modules/juce_core/files/juce_FileSearchPath.h +++ b/modules/juce_core/files/juce_FileSearchPath.h @@ -71,10 +71,21 @@ public: /** Returns one of the folders in this search path. The file returned isn't guaranteed to actually be a valid directory. - @see getNumPaths + @see getNumPaths, getRawString */ File operator[] (int index) const; + /** Returns the unaltered text of the folder at the specified index. + + Unlike operator[], this function returns the exact text that was entered. It does not + attempt to convert the path into an absolute path. + + This may be useful if the directory string is expected to understand environment variables + or other placeholders that the File constructor doesn't necessarily understand. + @see operator[] + */ + String getRawString (int index) const; + /** Returns the search path as a semicolon-separated list of directories. */ String toString() const; diff --git a/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp b/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp index 65ceb66a39..1799e7382b 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp @@ -129,7 +129,7 @@ void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, f.setHorizontalScale (0.9f); g.setFont (f); - g.drawText (path[rowNumber].getFullPathName(), + g.drawText (path.getRawString (rowNumber), 4, 0, width - 6, height, Justification::centredLeft, true); } @@ -145,7 +145,7 @@ void FileSearchPathListComponent::deleteKeyPressed (int row) void FileSearchPathListComponent::returnKeyPressed (int row) { - chooser = std::make_unique (TRANS("Change folder..."), path[row], "*"); + chooser = std::make_unique (TRANS("Change folder..."), path.getRawString (row), "*"); auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; chooser->launchAsync (chooserFlags, [this, row] (const FileChooser& fc) @@ -258,7 +258,7 @@ void FileSearchPathListComponent::moveSelection (int delta) if (currentRow != newRow) { - auto f = path[currentRow]; + const auto f = File::createFileWithoutCheckingPath (path.getRawString (currentRow)); path.remove (currentRow); path.add (f, newRow); listBox.selectRow (newRow);