@@ -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();) { | |||