The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
5.7KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #pragma once
  19. #include "jucer_ModuleDescription.h"
  20. #include <future>
  21. //==============================================================================
  22. class AvailableModulesList final : private AsyncUpdater
  23. {
  24. public:
  25. using ModuleIDAndFolder = std::pair<String, File>;
  26. using ModuleIDAndFolderList = std::vector<ModuleIDAndFolder>;
  27. AvailableModulesList() = default;
  28. //==============================================================================
  29. void scanPaths (const Array<File>& paths)
  30. {
  31. scanPathsAsync (paths);
  32. scanner = {};
  33. }
  34. void scanPathsAsync (const Array<File>& paths)
  35. {
  36. scanner = std::async (std::launch::async, [this, paths]
  37. {
  38. ModuleIDAndFolderList list;
  39. for (auto& p : paths)
  40. addAllModulesInFolder (p, list);
  41. std::sort (list.begin(), list.end(), [] (const ModuleIDAndFolder& m1,
  42. const ModuleIDAndFolder& m2)
  43. {
  44. return m1.first.compareIgnoreCase (m2.first) < 0;
  45. });
  46. {
  47. const ScopedLock swapLock (lock);
  48. if (list == modulesList)
  49. return;
  50. modulesList.swap (list);
  51. }
  52. triggerAsyncUpdate();
  53. });
  54. }
  55. //==============================================================================
  56. ModuleIDAndFolderList getAllModules() const
  57. {
  58. const ScopedLock readLock (lock);
  59. return modulesList;
  60. }
  61. ModuleIDAndFolder getModuleWithID (const String& id) const
  62. {
  63. const ScopedLock readLock (lock);
  64. for (auto& mod : modulesList)
  65. if (mod.first == id)
  66. return mod;
  67. return {};
  68. }
  69. //==============================================================================
  70. void removeDuplicates (const ModuleIDAndFolderList& other)
  71. {
  72. const ScopedLock readLock (lock);
  73. const auto predicate = [&] (const ModuleIDAndFolder& entry)
  74. {
  75. return std::find (other.begin(), other.end(), entry) != other.end();
  76. };
  77. modulesList.erase (std::remove_if (modulesList.begin(), modulesList.end(), predicate),
  78. modulesList.end());
  79. }
  80. //==============================================================================
  81. struct Listener
  82. {
  83. virtual ~Listener() = default;
  84. virtual void availableModulesChanged (AvailableModulesList* listThatHasChanged) = 0;
  85. };
  86. void addListener (Listener* listenerToAdd) { listeners.add (listenerToAdd); }
  87. void removeListener (Listener* listenerToRemove) { listeners.remove (listenerToRemove); }
  88. private:
  89. //==============================================================================
  90. static bool tryToAddModuleFromFolder (const File& path, ModuleIDAndFolderList& list)
  91. {
  92. ModuleDescription m (path);
  93. if (m.isValid()
  94. && std::none_of (list.begin(), list.end(),
  95. [&m] (const ModuleIDAndFolder& element) { return element.first == m.getID(); }))
  96. {
  97. list.push_back ({ m.getID(), path });
  98. return true;
  99. }
  100. return false;
  101. }
  102. static void addAllModulesInFolder (const File& topLevelPath, ModuleIDAndFolderList& list)
  103. {
  104. struct FileAndDepth
  105. {
  106. File file;
  107. int depth;
  108. };
  109. std::queue<FileAndDepth> pathsToCheck;
  110. pathsToCheck.push ({ topLevelPath, 0 });
  111. while (! pathsToCheck.empty())
  112. {
  113. const auto path = pathsToCheck.front();
  114. pathsToCheck.pop();
  115. if (tryToAddModuleFromFolder (path.file, list) || path.depth == 3)
  116. continue;
  117. for (const auto& iter : RangedDirectoryIterator (path.file, false, "*", File::findDirectories))
  118. {
  119. if (auto* job = ThreadPoolJob::getCurrentThreadPoolJob())
  120. if (job->shouldExit())
  121. return;
  122. pathsToCheck.push ({ iter.getFile(), path.depth + 1 });
  123. }
  124. }
  125. }
  126. //==============================================================================
  127. void handleAsyncUpdate() override
  128. {
  129. listeners.call ([this] (Listener& l) { l.availableModulesChanged (this); });
  130. }
  131. //==============================================================================
  132. ModuleIDAndFolderList modulesList;
  133. ListenerList<Listener> listeners;
  134. CriticalSection lock;
  135. std::future<void> scanner;
  136. //==============================================================================
  137. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AvailableModulesList)
  138. };