@@ -1,9 +1,10 @@ | |||||
#pragma once | #pragma once | ||||
#include <common.hpp> | |||||
#include <plugin/Plugin.hpp> | |||||
#include <jansson.h> | #include <jansson.h> | ||||
#include <common.hpp> | |||||
#include <plugin/Plugin.hpp> | |||||
#include <list> | |||||
namespace rack { | namespace rack { | ||||
@@ -34,7 +35,7 @@ struct Model { | |||||
/** List of tag IDs representing the function(s) of the module. | /** 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. | 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 */ | /** A one-line summary of the module's purpose */ | ||||
std::string description; | std::string description; | ||||
/** The manual of the module. HTML, PDF, or GitHub readme/wiki are fine. | /** The manual of the module. HTML, PDF, or GitHub readme/wiki are fine. | ||||
@@ -1,9 +1,9 @@ | |||||
#pragma once | #pragma once | ||||
#include <vector> | |||||
#include <common.hpp> | |||||
#include <jansson.h> | #include <jansson.h> | ||||
#include <common.hpp> | |||||
#include <list> | |||||
namespace rack { | namespace rack { | ||||
@@ -15,11 +15,15 @@ struct Model; | |||||
// Subclass this and return a pointer to a new one when init() is called | // Subclass this and return a pointer to a new one when init() is called | ||||
struct Plugin { | 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; | std::string path; | ||||
/** OS-dependent library handle */ | |||||
/** OS-dependent library handle. | |||||
*/ | |||||
void* handle = NULL; | void* handle = NULL; | ||||
/** Must be unique. Used for saving patches. Never change this after releasing your plugin. | /** 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; | 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 | #if defined ARCH_WIN | ||||
/** Performs a Unicode string conversion from UTF-16 to UTF-8. | /** Performs a Unicode string conversion from UTF-16 to UTF-8. | ||||
@@ -54,7 +54,7 @@ static void modelDbInit() { | |||||
for (plugin::Model* model : plugin->models) { | for (plugin::Model* model : plugin->models) { | ||||
// Get search fields for model | // Get search fields for model | ||||
std::string tagStr; | std::string tagStr; | ||||
for (int tagId : model->tags) { | |||||
for (int tagId : model->tagIds) { | |||||
// Add all aliases of a tag | // Add all aliases of a tag | ||||
for (const std::string& tagAlias : tag::tagAliases[tagId]) { | for (const std::string& tagAlias : tag::tagAliases[tagId]) { | ||||
tagStr += tagAlias; | tagStr += tagAlias; | ||||
@@ -249,12 +249,11 @@ struct ModelBox : widget::OpaqueWidget { | |||||
} | } | ||||
// Tags | // Tags | ||||
text += "\n\nTags: "; | 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; | ui::Tooltip* tooltip = new ui::Tooltip; | ||||
tooltip->text = text; | tooltip->text = text; | ||||
return tooltip; | return tooltip; | ||||
@@ -585,8 +584,8 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||||
// Filter tag | // Filter tag | ||||
for (int tagId : tagIds) { | 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; | return false; | ||||
} | } | ||||
@@ -85,11 +85,11 @@ struct ModuleInfoItem : ui::MenuItem { | |||||
} | } | ||||
// tags | // tags | ||||
if (!model->tags.empty()) { | |||||
if (!model->tagIds.empty()) { | |||||
ui::MenuLabel* tagsLabel = new ui::MenuLabel; | ui::MenuLabel* tagsLabel = new ui::MenuLabel; | ||||
tagsLabel->text = "Tags:"; | tagsLabel->text = "Tags:"; | ||||
menu->addChild(tagsLabel); | menu->addChild(tagsLabel); | ||||
for (int tagId : model->tags) { | |||||
for (int tagId : model->tagIds) { | |||||
ui::MenuLabel* tagLabel = new ui::MenuLabel; | ui::MenuLabel* tagLabel = new ui::MenuLabel; | ||||
tagLabel->text = "• " + tag::getTag(tagId); | tagLabel->text = "• " + tag::getTag(tagId); | ||||
menu->addChild(tagLabel); | menu->addChild(tagLabel); | ||||
@@ -148,18 +148,18 @@ static Plugin* loadPlugin(std::string path) { | |||||
plugin->fromJson(rootJ); | plugin->fromJson(rootJ); | ||||
// Reject plugin if slug already exists | // 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()); | 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) { | catch (Exception& e) { | ||||
WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | ||||
delete plugin; | 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; | return plugin; | ||||
} | } | ||||
@@ -26,7 +26,7 @@ void Model::fromJson(json_t* rootJ) { | |||||
description = json_string_value(descriptionJ); | description = json_string_value(descriptionJ); | ||||
// Tags | // Tags | ||||
tags.clear(); | |||||
tagIds.clear(); | |||||
json_t* tagsJ = json_object_get(rootJ, "tags"); | json_t* tagsJ = json_object_get(rootJ, "tags"); | ||||
if (tagsJ) { | if (tagsJ) { | ||||
size_t i; | size_t i; | ||||
@@ -36,12 +36,12 @@ void Model::fromJson(json_t* rootJ) { | |||||
int tagId = tag::findId(tag); | int tagId = tag::findId(tag); | ||||
// Omit duplicates | // 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; | continue; | ||||
if (tagId >= 0) | if (tagId >= 0) | ||||
tags.push_back(tagId); | |||||
tagIds.push_back(tagId); | |||||
} | } | ||||
} | } | ||||
@@ -4,6 +4,8 @@ | |||||
#include <string.hpp> | #include <string.hpp> | ||||
#include <app/common.hpp> | #include <app/common.hpp> | ||||
#include <algorithm> | |||||
namespace rack { | namespace rack { | ||||
namespace plugin { | namespace plugin { | ||||
@@ -24,12 +26,12 @@ void Plugin::addModel(Model* model) { | |||||
} | } | ||||
Model* Plugin::getModel(const std::string& slug) { | 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) { | void Plugin::fromJson(json_t* rootJ) { | ||||
@@ -106,6 +108,9 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
if (changelogUrlJ) | if (changelogUrlJ) | ||||
changelogUrl = json_string_value(changelogUrlJ); | changelogUrl = json_string_value(changelogUrlJ); | ||||
// Reordered models vector | |||||
std::list<Model*> newModels; | |||||
json_t* modulesJ = json_object_get(rootJ, "modules"); | json_t* modulesJ = json_object_get(rootJ, "modules"); | ||||
if (modulesJ && json_array_size(modulesJ) > 0) { | if (modulesJ && json_array_size(modulesJ) > 0) { | ||||
size_t moduleId; | size_t moduleId; | ||||
@@ -131,11 +136,17 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
} | } | ||||
// Get model | // 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); | model->fromJson(moduleJ); | ||||
} | } | ||||
} | } | ||||
@@ -143,6 +154,17 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
WARN("No modules in plugin manifest %s", slug.c_str()); | 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 | // Remove models without names | ||||
// This is a hacky way of matching JSON models with C++ models. | // This is a hacky way of matching JSON models with C++ models. | ||||
for (auto it = models.begin(); it != models.end();) { | for (auto it = models.begin(); it != models.end();) { | ||||