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