|  | /*
  ==============================================================================
   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"
#include <future>
//==============================================================================
class AvailableModulesList  : private AsyncUpdater
{
public:
    using ModuleIDAndFolder     = std::pair<String, File>;
    using ModuleIDAndFolderList = std::vector<ModuleIDAndFolder>;
    AvailableModulesList() = default;
    //==============================================================================
    void scanPaths (const Array<File>& paths)
    {
        scanPathsAsync (paths);
        scanner = {};
    }
    void scanPathsAsync (const Array<File>& paths)
    {
        scanner = std::async (std::launch::async, [this, paths]
        {
            ModuleIDAndFolderList list;
            for (auto& p : paths)
                addAllModulesInFolder (p, list);
            std::sort (list.begin(), list.end(), [] (const ModuleIDAndFolder& m1,
                                                     const ModuleIDAndFolder& m2)
            {
                return m1.first.compareIgnoreCase (m2.first) < 0;
            });
            {
                const ScopedLock swapLock (lock);
                if (list == modulesList)
                    return;
                modulesList.swap (list);
            }
            triggerAsyncUpdate();
        });
    }
    //==============================================================================
    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:
    //==============================================================================
    static bool tryToAddModuleFromFolder (const File& path, ModuleIDAndFolderList& list)
    {
        ModuleDescription m (path);
        if (m.isValid()
            && std::none_of (list.begin(), list.end(),
                             [&m] (const ModuleIDAndFolder& element) { return element.first == m.getID(); }))
        {
            list.push_back ({ m.getID(), path });
            return true;
        }
        return false;
    }
    static void addAllModulesInFolder (const File& topLevelPath, ModuleIDAndFolderList& list)
    {
        struct FileAndDepth
        {
            File file;
            int depth;
        };
        std::queue<FileAndDepth> pathsToCheck;
        pathsToCheck.push ({ topLevelPath, 0 });
        while (! pathsToCheck.empty())
        {
            const auto path = pathsToCheck.front();
            pathsToCheck.pop();
            if (tryToAddModuleFromFolder (path.file, list) || path.depth == 3)
                continue;
            for (const auto& iter : RangedDirectoryIterator (path.file, false, "*", File::findDirectories))
            {
                if (auto* job = ThreadPoolJob::getCurrentThreadPoolJob())
                    if (job->shouldExit())
                        return;
                pathsToCheck.push({ iter.getFile(), path.depth + 1 });
            }
        }
    }
    //==============================================================================
    void handleAsyncUpdate() override
    {
        listeners.call ([this] (Listener& l) { l.availableModulesChanged (this); });
    }
    //==============================================================================
    ModuleIDAndFolderList modulesList;
    ListenerList<Listener> listeners;
    CriticalSection lock;
    std::future<void> scanner;
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AvailableModulesList)
};
 |