1 /* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License 11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). 12 13 End User License Agreement: www.juce.com/juce-6-licence 14 Privacy Policy: www.juce.com/juce-privacy-policy 15 16 Or: You may also use this code under the terms of the GPL v3 (see 17 www.gnu.org/licenses). 18 19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 21 DISCLAIMED. 22 23 ============================================================================== 24 */ 25 26 #pragma once 27 28 #include "jucer_ModuleDescription.h" 29 30 //============================================================================== 31 class AvailableModulesList : private AsyncUpdater 32 { 33 public: 34 using ModuleIDAndFolder = std::pair<String, File>; 35 using ModuleIDAndFolderList = std::vector<ModuleIDAndFolder>; 36 37 AvailableModulesList() = default; 38 39 //============================================================================== scanPaths(const Array<File> & paths)40 void scanPaths (const Array<File>& paths) 41 { 42 auto job = createScannerJob (paths); 43 auto& ref = *job; 44 45 removePendingAndAddJob (std::move (job)); 46 scanPool.waitForJobToFinish (&ref, -1); 47 } 48 scanPathsAsync(const Array<File> & paths)49 void scanPathsAsync (const Array<File>& paths) 50 { 51 removePendingAndAddJob (createScannerJob (paths)); 52 } 53 54 //============================================================================== getAllModules()55 ModuleIDAndFolderList getAllModules() const 56 { 57 const ScopedLock readLock (lock); 58 return modulesList; 59 } 60 getModuleWithID(const String & id)61 ModuleIDAndFolder getModuleWithID (const String& id) const 62 { 63 const ScopedLock readLock (lock); 64 65 for (auto& mod : modulesList) 66 if (mod.first == id) 67 return mod; 68 69 return {}; 70 } 71 72 //============================================================================== removeDuplicates(const ModuleIDAndFolderList & other)73 void removeDuplicates (const ModuleIDAndFolderList& other) 74 { 75 const ScopedLock readLock (lock); 76 77 const auto predicate = [&] (const ModuleIDAndFolder& entry) 78 { 79 return std::find (other.begin(), other.end(), entry) != other.end(); 80 }; 81 82 modulesList.erase (std::remove_if (modulesList.begin(), modulesList.end(), predicate), 83 modulesList.end()); 84 } 85 86 //============================================================================== 87 struct Listener 88 { 89 virtual ~Listener() = default; 90 virtual void availableModulesChanged (AvailableModulesList* listThatHasChanged) = 0; 91 }; 92 addListener(Listener * listenerToAdd)93 void addListener (Listener* listenerToAdd) { listeners.add (listenerToAdd); } removeListener(Listener * listenerToRemove)94 void removeListener (Listener* listenerToRemove) { listeners.remove (listenerToRemove); } 95 96 private: 97 //============================================================================== 98 struct ModuleScannerJob : public ThreadPoolJob 99 { ModuleScannerJobModuleScannerJob100 ModuleScannerJob (const Array<File>& paths, 101 std::function<void (const ModuleIDAndFolderList&)>&& callback) 102 : ThreadPoolJob ("ModuleScannerJob"), 103 pathsToScan (paths), 104 completionCallback (std::move (callback)) 105 { 106 } 107 runJobModuleScannerJob108 JobStatus runJob() override 109 { 110 ModuleIDAndFolderList list; 111 112 for (auto& p : pathsToScan) 113 addAllModulesInFolder (p, list); 114 115 if (! shouldExit()) 116 { 117 std::sort (list.begin(), list.end(), [] (const ModuleIDAndFolder& m1, 118 const ModuleIDAndFolder& m2) 119 { 120 return m1.first.compareIgnoreCase (m2.first) < 0; 121 }); 122 123 completionCallback (list); 124 } 125 126 return jobHasFinished; 127 } 128 tryToAddModuleFromFolderModuleScannerJob129 static bool tryToAddModuleFromFolder (const File& path, ModuleIDAndFolderList& list) 130 { 131 ModuleDescription m (path); 132 133 if (m.isValid()) 134 { 135 list.push_back ({ m.getID(), path }); 136 return true; 137 } 138 139 return false; 140 } 141 addAllModulesInSubfoldersRecursivelyModuleScannerJob142 static void addAllModulesInSubfoldersRecursively (const File& path, int depth, ModuleIDAndFolderList& list) 143 { 144 if (depth > 0) 145 { 146 for (const auto& iter : RangedDirectoryIterator (path, false, "*", File::findDirectories)) 147 { 148 if (auto* job = ThreadPoolJob::getCurrentThreadPoolJob()) 149 if (job->shouldExit()) 150 return; 151 152 auto childPath = iter.getFile(); 153 154 if (! tryToAddModuleFromFolder (childPath, list)) 155 addAllModulesInSubfoldersRecursively (childPath, depth - 1, list); 156 } 157 } 158 } 159 addAllModulesInFolderModuleScannerJob160 static void addAllModulesInFolder (const File& path, ModuleIDAndFolderList& list) 161 { 162 if (! tryToAddModuleFromFolder (path, list)) 163 { 164 constexpr int subfolders = 3; 165 addAllModulesInSubfoldersRecursively (path, subfolders, list); 166 } 167 } 168 169 Array<File> pathsToScan; 170 std::function<void (const ModuleIDAndFolderList&)> completionCallback; 171 }; 172 173 //============================================================================== handleAsyncUpdate()174 void handleAsyncUpdate() override 175 { 176 listeners.call ([this] (Listener& l) { l.availableModulesChanged (this); }); 177 } 178 createScannerJob(const Array<File> & paths)179 std::unique_ptr<ThreadPoolJob> createScannerJob (const Array<File>& paths) 180 { 181 return std::make_unique<ModuleScannerJob> (paths, [this] (ModuleIDAndFolderList scannedModulesList) 182 { 183 { 184 const ScopedLock swapLock (lock); 185 modulesList.swap (scannedModulesList); 186 } 187 188 triggerAsyncUpdate(); 189 }); 190 } 191 removePendingAndAddJob(std::unique_ptr<ThreadPoolJob> jobToAdd)192 void removePendingAndAddJob (std::unique_ptr<ThreadPoolJob> jobToAdd) 193 { 194 scanPool.removeAllJobs (false, 100); 195 scanPool.addJob (jobToAdd.release(), true); 196 } 197 198 //============================================================================== 199 ThreadPool scanPool { 1 }; 200 201 ModuleIDAndFolderList modulesList; 202 ListenerList<Listener> listeners; 203 CriticalSection lock; 204 205 //============================================================================== 206 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AvailableModulesList) 207 }; 208