| @@ -1,9 +1,10 @@ | |||
| #pragma once | |||
| #include <common.hpp> | |||
| #include <plugin/Plugin.hpp> | |||
| #include <jansson.h> | |||
| #include <common.hpp> | |||
| #include <plugin/Plugin.hpp> | |||
| #include <list> | |||
| namespace rack { | |||
| @@ -34,7 +35,7 @@ struct Model { | |||
| /** List of tag IDs representing the function(s) of the module. | |||
| Tag IDs are not part of the ABI and may change at any time. | |||
| */ | |||
| std::vector<int> tags; | |||
| std::list<int> tagIds; | |||
| /** A one-line summary of the module's purpose */ | |||
| std::string description; | |||
| /** The manual of the module. HTML, PDF, or GitHub readme/wiki are fine. | |||
| @@ -1,9 +1,9 @@ | |||
| #pragma once | |||
| #include <vector> | |||
| #include <common.hpp> | |||
| #include <jansson.h> | |||
| #include <common.hpp> | |||
| #include <list> | |||
| namespace rack { | |||
| @@ -15,11 +15,15 @@ struct Model; | |||
| // Subclass this and return a pointer to a new one when init() is called | |||
| struct Plugin { | |||
| /** A list of the models available by this plugin, add with addModel() */ | |||
| std::vector<Model*> models; | |||
| /** The file path to the plugin's directory */ | |||
| /** List of models contained in this plugin. | |||
| Add with addModel(). | |||
| */ | |||
| std::list<Model*> models; | |||
| /** The file path to the plugin's directory. | |||
| */ | |||
| std::string path; | |||
| /** OS-dependent library handle */ | |||
| /** OS-dependent library handle. | |||
| */ | |||
| void* handle = NULL; | |||
| /** Must be unique. Used for saving patches. Never change this after releasing your plugin. | |||
| @@ -52,6 +52,21 @@ struct CaseInsensitiveCompare { | |||
| bool operator()(const std::string& a, const std::string& b) const; | |||
| }; | |||
| /** Joins an container (vector, list, etc) of std::strings with an optional separator string. | |||
| */ | |||
| template <typename TContainer> | |||
| std::string join(const TContainer& container, std::string seperator = "") { | |||
| std::string s; | |||
| bool first = true; | |||
| for (const auto& c : container) { | |||
| if (!first) | |||
| s += seperator; | |||
| first = false; | |||
| s += c; | |||
| } | |||
| return s; | |||
| } | |||
| #if defined ARCH_WIN | |||
| /** Performs a Unicode string conversion from UTF-16 to UTF-8. | |||
| @@ -54,7 +54,7 @@ static void modelDbInit() { | |||
| for (plugin::Model* model : plugin->models) { | |||
| // Get search fields for model | |||
| std::string tagStr; | |||
| for (int tagId : model->tags) { | |||
| for (int tagId : model->tagIds) { | |||
| // Add all aliases of a tag | |||
| for (const std::string& tagAlias : tag::tagAliases[tagId]) { | |||
| tagStr += tagAlias; | |||
| @@ -249,12 +249,11 @@ struct ModelBox : widget::OpaqueWidget { | |||
| } | |||
| // Tags | |||
| text += "\n\nTags: "; | |||
| for (size_t i = 0; i < model->tags.size(); i++) { | |||
| if (i > 0) | |||
| text += ", "; | |||
| int tagId = model->tags[i]; | |||
| text += tag::getTag(tagId); | |||
| std::vector<std::string> tags; | |||
| for (int tagId : model->tagIds) { | |||
| tags.push_back(tag::getTag(tagId)); | |||
| } | |||
| text += string::join(tags, ", "); | |||
| ui::Tooltip* tooltip = new ui::Tooltip; | |||
| tooltip->text = text; | |||
| return tooltip; | |||
| @@ -585,8 +584,8 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
| // Filter tag | |||
| for (int tagId : tagIds) { | |||
| auto it = std::find(model->tags.begin(), model->tags.end(), tagId); | |||
| if (it == model->tags.end()) | |||
| auto it = std::find(model->tagIds.begin(), model->tagIds.end(), tagId); | |||
| if (it == model->tagIds.end()) | |||
| return false; | |||
| } | |||
| @@ -85,11 +85,11 @@ struct ModuleInfoItem : ui::MenuItem { | |||
| } | |||
| // tags | |||
| if (!model->tags.empty()) { | |||
| if (!model->tagIds.empty()) { | |||
| ui::MenuLabel* tagsLabel = new ui::MenuLabel; | |||
| tagsLabel->text = "Tags:"; | |||
| menu->addChild(tagsLabel); | |||
| for (int tagId : model->tags) { | |||
| for (int tagId : model->tagIds) { | |||
| ui::MenuLabel* tagLabel = new ui::MenuLabel; | |||
| tagLabel->text = "• " + tag::getTag(tagId); | |||
| menu->addChild(tagLabel); | |||
| @@ -148,18 +148,18 @@ static Plugin* loadPlugin(std::string path) { | |||
| plugin->fromJson(rootJ); | |||
| // Reject plugin if slug already exists | |||
| Plugin* oldPlugin = getPlugin(plugin->slug); | |||
| if (oldPlugin) | |||
| Plugin* existingPlugin = getPlugin(plugin->slug); | |||
| if (existingPlugin) | |||
| throw Exception("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str()); | |||
| plugins.push_back(plugin); | |||
| INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), plugin->path.c_str()); | |||
| } | |||
| catch (Exception& e) { | |||
| WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | |||
| delete plugin; | |||
| plugin = NULL; | |||
| return NULL; | |||
| } | |||
| plugins.push_back(plugin); | |||
| INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), plugin->path.c_str()); | |||
| return plugin; | |||
| } | |||
| @@ -26,7 +26,7 @@ void Model::fromJson(json_t* rootJ) { | |||
| description = json_string_value(descriptionJ); | |||
| // Tags | |||
| tags.clear(); | |||
| tagIds.clear(); | |||
| json_t* tagsJ = json_object_get(rootJ, "tags"); | |||
| if (tagsJ) { | |||
| size_t i; | |||
| @@ -36,12 +36,12 @@ void Model::fromJson(json_t* rootJ) { | |||
| int tagId = tag::findId(tag); | |||
| // Omit duplicates | |||
| auto it = std::find(tags.begin(), tags.end(), tagId); | |||
| if (it != tags.end()) | |||
| auto it = std::find(tagIds.begin(), tagIds.end(), tagId); | |||
| if (it != tagIds.end()) | |||
| continue; | |||
| if (tagId >= 0) | |||
| tags.push_back(tagId); | |||
| tagIds.push_back(tagId); | |||
| } | |||
| } | |||
| @@ -4,6 +4,8 @@ | |||
| #include <string.hpp> | |||
| #include <app/common.hpp> | |||
| #include <algorithm> | |||
| namespace rack { | |||
| namespace plugin { | |||
| @@ -24,12 +26,12 @@ void Plugin::addModel(Model* model) { | |||
| } | |||
| Model* Plugin::getModel(const std::string& slug) { | |||
| for (Model* model : models) { | |||
| if (model->slug == slug) { | |||
| return model; | |||
| } | |||
| } | |||
| return NULL; | |||
| auto it = std::find_if(models.begin(), models.end(), [&](Model* m) { | |||
| return m->slug == slug; | |||
| }); | |||
| if (it == models.end()) | |||
| return NULL; | |||
| return *it; | |||
| } | |||
| void Plugin::fromJson(json_t* rootJ) { | |||
| @@ -106,6 +108,9 @@ void Plugin::fromJson(json_t* rootJ) { | |||
| if (changelogUrlJ) | |||
| changelogUrl = json_string_value(changelogUrlJ); | |||
| // Reordered models vector | |||
| std::list<Model*> newModels; | |||
| json_t* modulesJ = json_object_get(rootJ, "modules"); | |||
| if (modulesJ && json_array_size(modulesJ) > 0) { | |||
| size_t moduleId; | |||
| @@ -131,11 +136,17 @@ void Plugin::fromJson(json_t* rootJ) { | |||
| } | |||
| // Get model | |||
| Model* model = getModel(modelSlug); | |||
| if (!model) { | |||
| throw Exception("Manifest contains module %s but it is not defined in the plugin", modelSlug.c_str()); | |||
| auto it = std::find_if(models.begin(), models.end(), [&](Model* m) { | |||
| return m->slug == modelSlug; | |||
| }); | |||
| if (it == models.end()) { | |||
| throw Exception("Manifest contains module %s but it is not defined in plugin", modelSlug.c_str()); | |||
| } | |||
| Model* model = *it; | |||
| models.erase(it); | |||
| newModels.push_back(model); | |||
| model->fromJson(moduleJ); | |||
| } | |||
| } | |||
| @@ -143,6 +154,17 @@ void Plugin::fromJson(json_t* rootJ) { | |||
| WARN("No modules in plugin manifest %s", slug.c_str()); | |||
| } | |||
| if (!models.empty()) { | |||
| std::vector<std::string> slugs; | |||
| for (Model* model : models) { | |||
| slugs.push_back(model->slug); | |||
| delete model; | |||
| } | |||
| throw Exception("Plugin defines module %s but it is not defined in manifest", string::join(slugs, ", ").c_str()); | |||
| } | |||
| models = newModels; | |||
| // Remove models without names | |||
| // This is a hacky way of matching JSON models with C++ models. | |||
| for (auto it = models.begin(); it != models.end();) { | |||