| @@ -126,6 +126,8 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||||
| OwnedArray <PluginDescription>& typesFound, | OwnedArray <PluginDescription>& typesFound, | ||||
| AudioPluginFormat& format) | AudioPluginFormat& format) | ||||
| { | { | ||||
| const ScopedLock sl (scanLock); | |||||
| if (dontRescanIfAlreadyInList | if (dontRescanIfAlreadyInList | ||||
| && getTypeForFile (fileOrIdentifier) != nullptr) | && getTypeForFile (fileOrIdentifier) != nullptr) | ||||
| { | { | ||||
| @@ -153,14 +155,17 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||||
| OwnedArray <PluginDescription> found; | OwnedArray <PluginDescription> found; | ||||
| if (scanner != nullptr) | |||||
| { | { | ||||
| if (! scanner->findPluginTypesFor (format, found, fileOrIdentifier)) | |||||
| addToBlacklist (fileOrIdentifier); | |||||
| } | |||||
| else | |||||
| { | |||||
| format.findAllTypesForFile (found, fileOrIdentifier); | |||||
| const ScopedUnlock sl (scanLock); | |||||
| if (scanner != nullptr) | |||||
| { | |||||
| if (! scanner->findPluginTypesFor (format, found, fileOrIdentifier)) | |||||
| addToBlacklist (fileOrIdentifier); | |||||
| } | |||||
| else | |||||
| { | |||||
| format.findAllTypesForFile (found, fileOrIdentifier); | |||||
| } | |||||
| } | } | ||||
| for (int i = 0; i < found.size(); ++i) | for (int i = 0; i < found.size(); ++i) | ||||
| @@ -201,6 +201,7 @@ private: | |||||
| OwnedArray <PluginDescription> types; | OwnedArray <PluginDescription> types; | ||||
| StringArray blacklist; | StringArray blacklist; | ||||
| ScopedPointer<CustomScanner> scanner; | ScopedPointer<CustomScanner> scanner; | ||||
| CriticalSection scanLock; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) | ||||
| }; | }; | ||||
| @@ -39,7 +39,6 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, | |||||
| : list (listToAddTo), | : list (listToAddTo), | ||||
| format (formatToLookFor), | format (formatToLookFor), | ||||
| deadMansPedalFile (deadMansPedal), | deadMansPedalFile (deadMansPedal), | ||||
| nextIndex (0), | |||||
| progress (0) | progress (0) | ||||
| { | { | ||||
| directoriesToSearch.removeRedundantPaths(); | directoriesToSearch.removeRedundantPaths(); | ||||
| @@ -60,6 +59,7 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, | |||||
| } | } | ||||
| applyBlacklistingsFromDeadMansPedal (listToAddTo, deadMansPedalFile); | applyBlacklistingsFromDeadMansPedal (listToAddTo, deadMansPedalFile); | ||||
| nextIndex.set (filesOrIdentifiersToScan.size()); | |||||
| } | } | ||||
| PluginDirectoryScanner::~PluginDirectoryScanner() | PluginDirectoryScanner::~PluginDirectoryScanner() | ||||
| @@ -69,43 +69,51 @@ PluginDirectoryScanner::~PluginDirectoryScanner() | |||||
| //============================================================================== | //============================================================================== | ||||
| String PluginDirectoryScanner::getNextPluginFileThatWillBeScanned() const | String PluginDirectoryScanner::getNextPluginFileThatWillBeScanned() const | ||||
| { | { | ||||
| return format.getNameOfPluginFromIdentifier (filesOrIdentifiersToScan [nextIndex]); | |||||
| return format.getNameOfPluginFromIdentifier (filesOrIdentifiersToScan [nextIndex.get() - 1]); | |||||
| } | |||||
| void PluginDirectoryScanner::updateProgress() | |||||
| { | |||||
| progress = (1.0f - nextIndex.get() / (float) filesOrIdentifiersToScan.size()); | |||||
| } | } | ||||
| bool PluginDirectoryScanner::scanNextFile (const bool dontRescanIfAlreadyInList) | bool PluginDirectoryScanner::scanNextFile (const bool dontRescanIfAlreadyInList) | ||||
| { | { | ||||
| String file (filesOrIdentifiersToScan [nextIndex]); | |||||
| const int index = --nextIndex; | |||||
| if (file.isNotEmpty() && ! list.isListingUpToDate (file)) | |||||
| if (index >= 0) | |||||
| { | { | ||||
| OwnedArray <PluginDescription> typesFound; | |||||
| String file (filesOrIdentifiersToScan [index]); | |||||
| // Add this plugin to the end of the dead-man's pedal list in case it crashes... | |||||
| StringArray crashedPlugins (readDeadMansPedalFile (deadMansPedalFile)); | |||||
| crashedPlugins.removeString (file); | |||||
| crashedPlugins.add (file); | |||||
| setDeadMansPedalFile (crashedPlugins); | |||||
| if (file.isNotEmpty() && ! list.isListingUpToDate (file)) | |||||
| { | |||||
| OwnedArray <PluginDescription> typesFound; | |||||
| list.scanAndAddFile (file, dontRescanIfAlreadyInList, typesFound, format); | |||||
| // Add this plugin to the end of the dead-man's pedal list in case it crashes... | |||||
| StringArray crashedPlugins (readDeadMansPedalFile (deadMansPedalFile)); | |||||
| crashedPlugins.removeString (file); | |||||
| crashedPlugins.add (file); | |||||
| setDeadMansPedalFile (crashedPlugins); | |||||
| // Managed to load without crashing, so remove it from the dead-man's-pedal.. | |||||
| crashedPlugins.removeString (file); | |||||
| setDeadMansPedalFile (crashedPlugins); | |||||
| list.scanAndAddFile (file, dontRescanIfAlreadyInList, typesFound, format); | |||||
| if (typesFound.size() == 0 && ! list.getBlacklistedFiles().contains (file)) | |||||
| failedFiles.add (file); | |||||
| // Managed to load without crashing, so remove it from the dead-man's-pedal.. | |||||
| crashedPlugins.removeString (file); | |||||
| setDeadMansPedalFile (crashedPlugins); | |||||
| if (typesFound.size() == 0 && ! list.getBlacklistedFiles().contains (file)) | |||||
| failedFiles.add (file); | |||||
| } | |||||
| } | } | ||||
| return skipNextFile(); | |||||
| updateProgress(); | |||||
| return index > 0; | |||||
| } | } | ||||
| bool PluginDirectoryScanner::skipNextFile() | bool PluginDirectoryScanner::skipNextFile() | ||||
| { | { | ||||
| if (nextIndex >= filesOrIdentifiersToScan.size()) | |||||
| return false; | |||||
| progress = ++nextIndex / (float) filesOrIdentifiersToScan.size(); | |||||
| return nextIndex < filesOrIdentifiersToScan.size(); | |||||
| updateProgress(); | |||||
| return --nextIndex > 0; | |||||
| } | } | ||||
| void PluginDirectoryScanner::setDeadMansPedalFile (const StringArray& newContents) | void PluginDirectoryScanner::setDeadMansPedalFile (const StringArray& newContents) | ||||
| @@ -115,9 +115,10 @@ private: | |||||
| StringArray filesOrIdentifiersToScan; | StringArray filesOrIdentifiersToScan; | ||||
| File deadMansPedalFile; | File deadMansPedalFile; | ||||
| StringArray failedFiles; | StringArray failedFiles; | ||||
| int nextIndex; | |||||
| Atomic<int> nextIndex; | |||||
| float progress; | float progress; | ||||
| void updateProgress(); | |||||
| void setDeadMansPedalFile (const StringArray& newContents); | void setDeadMansPedalFile (const StringArray& newContents); | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginDirectoryScanner) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginDirectoryScanner) | ||||
| @@ -32,7 +32,7 @@ PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, | |||||
| deadMansPedalFile (deadMansPedal), | deadMansPedalFile (deadMansPedal), | ||||
| optionsButton ("Options..."), | optionsButton ("Options..."), | ||||
| propertiesToUse (properties), | propertiesToUse (properties), | ||||
| scanOnBackgroundThread (false) | |||||
| numThreads (0) | |||||
| { | { | ||||
| listBox.setModel (this); | listBox.setModel (this); | ||||
| addAndMakeVisible (&listBox); | addAndMakeVisible (&listBox); | ||||
| @@ -60,9 +60,9 @@ void PluginListComponent::setOptionsButtonText (const String& newText) | |||||
| resized(); | resized(); | ||||
| } | } | ||||
| void PluginListComponent::setScansOnMessageThread (bool useMessageThread) noexcept | |||||
| void PluginListComponent::setNumberOfThreadsForScanning (int num) | |||||
| { | { | ||||
| scanOnBackgroundThread = ! useMessageThread; | |||||
| numThreads = num; | |||||
| } | } | ||||
| void PluginListComponent::resized() | void PluginListComponent::resized() | ||||
| @@ -244,20 +244,18 @@ void PluginListComponent::filesDropped (const StringArray& files, int, int) | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| class PluginListComponent::Scanner : private Timer, | |||||
| private Thread | |||||
| class PluginListComponent::Scanner : private Timer | |||||
| { | { | ||||
| public: | public: | ||||
| Scanner (PluginListComponent& plc, | Scanner (PluginListComponent& plc, | ||||
| AudioPluginFormat& format, | AudioPluginFormat& format, | ||||
| PropertiesFile* properties, | PropertiesFile* properties, | ||||
| bool useThread) | |||||
| : Thread ("plugin_scan"), | |||||
| owner (plc), formatToScan (format), propertiesToUse (properties), | |||||
| int threads) | |||||
| : owner (plc), formatToScan (format), propertiesToUse (properties), | |||||
| pathChooserWindow (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon), | pathChooserWindow (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon), | ||||
| progressWindow (TRANS("Scanning for plug-ins..."), | progressWindow (TRANS("Scanning for plug-ins..."), | ||||
| TRANS("Searching for all possible plug-in files..."), AlertWindow::NoIcon), | TRANS("Searching for all possible plug-in files..."), AlertWindow::NoIcon), | ||||
| progress (0.0), shouldUseThread (useThread), finished (false) | |||||
| progress (0.0), numThreads (threads), finished (false) | |||||
| { | { | ||||
| FileSearchPath path (formatToScan.getDefaultLocationsToSearch()); | FileSearchPath path (formatToScan.getDefaultLocationsToSearch()); | ||||
| @@ -285,7 +283,11 @@ public: | |||||
| ~Scanner() | ~Scanner() | ||||
| { | { | ||||
| stopThread (10000); | |||||
| if (pool != nullptr) | |||||
| { | |||||
| pool->removeAllJobs (true, 60000); | |||||
| pool = nullptr; | |||||
| } | |||||
| } | } | ||||
| private: | private: | ||||
| @@ -317,8 +319,13 @@ private: | |||||
| progressWindow.addProgressBarComponent (progress); | progressWindow.addProgressBarComponent (progress); | ||||
| progressWindow.enterModalState(); | progressWindow.enterModalState(); | ||||
| if (shouldUseThread) | |||||
| startThread(); | |||||
| if (numThreads > 0) | |||||
| { | |||||
| pool = new ThreadPool (numThreads); | |||||
| for (int i = numThreads; --i >= 0;) | |||||
| pool->addJob (new ScanJob (*this), true); | |||||
| } | |||||
| startTimer (20); | startTimer (20); | ||||
| } | } | ||||
| @@ -331,7 +338,7 @@ private: | |||||
| void timerCallback() | void timerCallback() | ||||
| { | { | ||||
| if (! isThreadRunning()) | |||||
| if (pool == nullptr) | |||||
| { | { | ||||
| if (doNextScan()) | if (doNextScan()) | ||||
| startTimer (20); | startTimer (20); | ||||
| @@ -346,12 +353,6 @@ private: | |||||
| progressWindow.setMessage (progressMessage); | progressWindow.setMessage (progressMessage); | ||||
| } | } | ||||
| void run() | |||||
| { | |||||
| while (doNextScan() && ! threadShouldExit()) | |||||
| {} | |||||
| } | |||||
| bool doNextScan() | bool doNextScan() | ||||
| { | { | ||||
| progressMessage = TRANS("Testing:\n\n") + scanner->getNextPluginFileThatWillBeScanned(); | progressMessage = TRANS("Testing:\n\n") + scanner->getNextPluginFileThatWillBeScanned(); | ||||
| @@ -374,15 +375,36 @@ private: | |||||
| FileSearchPathListComponent pathList; | FileSearchPathListComponent pathList; | ||||
| String progressMessage; | String progressMessage; | ||||
| double progress; | double progress; | ||||
| bool shouldUseThread, finished; | |||||
| int numThreads; | |||||
| bool finished; | |||||
| ScopedPointer<ThreadPool> pool; | |||||
| struct ScanJob : public ThreadPoolJob | |||||
| { | |||||
| ScanJob (Scanner& s) : ThreadPoolJob ("pluginscan"), scanner (s) {} | |||||
| JobStatus runJob() | |||||
| { | |||||
| while (scanner.doNextScan() && ! shouldExit()) | |||||
| {} | |||||
| return jobHasFinished; | |||||
| } | |||||
| Scanner& scanner; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScanJob) | |||||
| }; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Scanner) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Scanner) | ||||
| }; | }; | ||||
| void PluginListComponent::scanFor (AudioPluginFormat* format) | void PluginListComponent::scanFor (AudioPluginFormat* format) | ||||
| { | { | ||||
| if (format != nullptr) | if (format != nullptr) | ||||
| currentScanner = new Scanner (*this, *format, propertiesToUse, scanOnBackgroundThread); | |||||
| currentScanner = new Scanner (*this, *format, propertiesToUse, numThreads); | |||||
| } | } | ||||
| void PluginListComponent::scanFinished (const StringArray& failedFiles) | void PluginListComponent::scanFinished (const StringArray& failedFiles) | ||||
| @@ -61,8 +61,10 @@ public: | |||||
| /** Changes the text in the panel's button. */ | /** Changes the text in the panel's button. */ | ||||
| void setOptionsButtonText (const String& newText); | void setOptionsButtonText (const String& newText); | ||||
| /** Chooses whether to use the message thread or a background thread for scanning. */ | |||||
| void setScansOnMessageThread (bool useMessageThread) noexcept; | |||||
| /** Sets how many threads to simultaneously scan for plugins. | |||||
| If this is 0, then all scanning happens on the message thread (this is the default) | |||||
| */ | |||||
| void setNumberOfThreadsForScanning (int numThreads); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** @internal */ | /** @internal */ | ||||
| @@ -86,7 +88,7 @@ private: | |||||
| ListBox listBox; | ListBox listBox; | ||||
| TextButton optionsButton; | TextButton optionsButton; | ||||
| PropertiesFile* propertiesToUse; | PropertiesFile* propertiesToUse; | ||||
| bool scanOnBackgroundThread; | |||||
| int numThreads; | |||||
| class Scanner; | class Scanner; | ||||
| friend class Scanner; | friend class Scanner; | ||||