/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #pragma once #include "jucer_ModuleDescription.h" //============================================================================== class AvailableModulesList : private AsyncUpdater { public: using ModuleIDAndFolder = std::pair; using ModuleIDAndFolderList = std::vector; AvailableModulesList() = default; //============================================================================== void scanPaths (const Array& paths) { auto job = createScannerJob (paths); auto& ref = *job; removePendingAndAddJob (std::move (job)); scanPool.waitForJobToFinish (&ref, -1); } void scanPathsAsync (const Array& paths) { removePendingAndAddJob (createScannerJob (paths)); } //============================================================================== ModuleIDAndFolderList getAllModules() const { const ScopedLock readLock (lock); return modulesList; } ModuleIDAndFolder getModuleWithID (const String& id) const { const ScopedLock readLock (lock); for (auto& mod : modulesList) if (mod.first == id) return mod; return {}; } //============================================================================== void removeDuplicates (const ModuleIDAndFolderList& other) { const ScopedLock readLock (lock); const auto predicate = [&] (const ModuleIDAndFolder& entry) { return std::find (other.begin(), other.end(), entry) != other.end(); }; modulesList.erase (std::remove_if (modulesList.begin(), modulesList.end(), predicate), modulesList.end()); } //============================================================================== struct Listener { virtual ~Listener() = default; virtual void availableModulesChanged (AvailableModulesList* listThatHasChanged) = 0; }; void addListener (Listener* listenerToAdd) { listeners.add (listenerToAdd); } void removeListener (Listener* listenerToRemove) { listeners.remove (listenerToRemove); } private: //============================================================================== struct ModuleScannerJob : public ThreadPoolJob { ModuleScannerJob (const Array& paths, std::function&& callback) : ThreadPoolJob ("ModuleScannerJob"), pathsToScan (paths), completionCallback (std::move (callback)) { } JobStatus runJob() override { ModuleIDAndFolderList list; for (auto& p : pathsToScan) addAllModulesInFolder (p, list); if (! shouldExit()) { std::sort (list.begin(), list.end(), [] (const ModuleIDAndFolder& m1, const ModuleIDAndFolder& m2) { return m1.first.compareIgnoreCase (m2.first) < 0; }); completionCallback (list); } return jobHasFinished; } static bool tryToAddModuleFromFolder (const File& path, ModuleIDAndFolderList& list) { ModuleDescription m (path); if (m.isValid()) { list.push_back ({ m.getID(), path }); return true; } return false; } static void addAllModulesInSubfoldersRecursively (const File& path, int depth, ModuleIDAndFolderList& list) { if (depth > 0) { for (const auto& iter : RangedDirectoryIterator (path, false, "*", File::findDirectories)) { if (auto* job = ThreadPoolJob::getCurrentThreadPoolJob()) if (job->shouldExit()) return; auto childPath = iter.getFile(); if (! tryToAddModuleFromFolder (childPath, list)) addAllModulesInSubfoldersRecursively (childPath, depth - 1, list); } } } static void addAllModulesInFolder (const File& path, ModuleIDAndFolderList& list) { if (! tryToAddModuleFromFolder (path, list)) { constexpr int subfolders = 3; addAllModulesInSubfoldersRecursively (path, subfolders, list); } } Array pathsToScan; std::function completionCallback; }; //============================================================================== void handleAsyncUpdate() override { listeners.call ([this] (Listener& l) { l.availableModulesChanged (this); }); } std::unique_ptr createScannerJob (const Array& paths) { return std::make_unique (paths, [this] (ModuleIDAndFolderList scannedModulesList) { { const ScopedLock swapLock (lock); modulesList.swap (scannedModulesList); } triggerAsyncUpdate(); }); } void removePendingAndAddJob (std::unique_ptr jobToAdd) { scanPool.removeAllJobs (false, 100); scanPool.addJob (jobToAdd.release(), true); } //============================================================================== ThreadPool scanPool { 1 }; ModuleIDAndFolderList modulesList; ListenerList listeners; CriticalSection lock; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AvailableModulesList) };