Browse Source

Move Core metadata from Core/plugin.cpp to Core.json manifest. Finish most of Plugin menu functionality. Rewrite plugin loading code.

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
ba2d5f8e2d
9 changed files with 349 additions and 262 deletions
  1. +113
    -0
      Core.json
  2. +2
    -1
      include/app/common.hpp
  3. +9
    -10
      include/plugin.hpp
  4. +0
    -55
      src/Core/plugin.cpp
  5. +85
    -62
      src/app/MenuBar.cpp
  6. +4
    -2
      src/app/common.cpp
  7. +132
    -127
      src/plugin.cpp
  8. +3
    -4
      src/plugin/Plugin.cpp
  9. +1
    -1
      src/window.cpp

+ 113
- 0
Core.json View File

@@ -0,0 +1,113 @@
{
"slug": "Core",
"name": "Core",
"version": "1.0.0",
"license": "GPL-3.0-only",
"author": "VCV",
"brand": "VCV",
"authorEmail": "contact@vcvrack.com",
"authorUrl": "https://vcvrack.com/",
"pluginUrl": "https://vcvrack.com/",
"manualUrl": "https://vcvrack.com/manual/Core.html",
"sourceUrl": "https://github.com/VCVRack/Rack",
"donateUrl": "",
"modules": [
{
"slug": "AudioInterface",
"name": "Audio-8",
"description": "Sends audio and CV to/from an audio device",
"tags": [
"External"
]
},
{
"slug": "AudioInterface16",
"name": "Audio-16",
"description": "Sends audio and CV to/from an audio device",
"tags": [
"External"
]
},
{
"slug": "MIDIToCVInterface",
"name": "MIDI-CV",
"description": "Converts MIDI from an external device to CV and gates",
"tags": [
"External",
"MIDI",
"Polyphonic"
]
},
{
"slug": "MIDICCToCVInterface",
"name": "MIDI-CC",
"description": "Converts MIDI CC from an external device to CV",
"tags": [
"External",
"MIDI"
]
},
{
"slug": "MIDITriggerToCVInterface",
"name": "MIDI-Gate",
"description": "Converts MIDI notes from an external device to gates",
"tags": [
"External",
"MIDI"
]
},
{
"slug": "MIDI-Map",
"name": "MIDI-Map",
"description": "Controls parameters (knobs, sliders, switches) directly with MIDI CC",
"tags": [
"External",
"MIDI"
]
},
{
"slug": "CV-MIDI",
"name": "CV-MIDI",
"description": "Converts CV to MIDI and sends to an external device",
"tags": [
"External",
"MIDI",
"Polyphonic"
]
},
{
"slug": "CV-CC",
"name": "CV-CC",
"description": "Converts CV to MIDI CC and sends to an external device",
"tags": [
"External",
"MIDI"
]
},
{
"slug": "CV-Gate",
"name": "CV-Gate",
"description": "Converts gates to MIDI notes and sends to an external device",
"tags": [
"External",
"MIDI"
]
},
{
"slug": "Blank",
"name": "Blank",
"description": "A resizable blank panel",
"tags": [
"Blank"
]
},
{
"slug": "Notes",
"name": "Notes",
"description": "Write text for patch notes or artist attribution",
"tags": [
"Blank"
]
}
]
}

+ 2
- 1
include/app/common.hpp View File

@@ -14,8 +14,9 @@ namespace app {

extern std::string APP_NAME;
extern std::string APP_VERSION;
extern std::string APP_NEW_VERSION;
extern std::string APP_VERSION_UPDATE;
extern std::string API_URL;
extern std::string API_VERSION;

static const float SVG_DPI = 75.0;
static const float MM_PER_IN = 25.4;


+ 9
- 10
include/plugin.hpp View File

@@ -14,13 +14,21 @@ namespace rack {
namespace plugin {


struct Update {
std::string pluginSlug;
std::string version;
std::string changelogUrl;
float progress = 0.f;
};


void init();
void destroy();
void logIn(const std::string &email, const std::string &password);
void logOut();
void queryUpdates();
void syncUpdate(Update *update);
void syncUpdates();
void cancelDownload();
bool isLoggedIn();
Plugin *getPlugin(const std::string &pluginSlug);
Model *getModel(const std::string &pluginSlug, const std::string &modelSlug);
@@ -31,20 +39,11 @@ bool isSlugValid(const std::string &slug);
std::string normalizeSlug(const std::string &slug);


struct Update {
std::string pluginSlug;
std::string version;
std::string changelogUrl;
};


extern const std::set<std::string> allowedTags;
extern std::vector<Plugin*> plugins;

extern std::string loginStatus;
extern std::vector<Update> updates;
extern float downloadProgress;
extern std::string downloadName;


} // namespace plugin


+ 0
- 55
src/Core/plugin.cpp View File

@@ -2,70 +2,15 @@


void init(rack::Plugin *p) {
p->slug = "Core";
p->version = TOSTRING(VERSION);
p->license = "BSD-3-Clause";
p->name = "Core";
p->brand = "Core";
p->author = "VCV";
p->authorEmail = "contact@vcvrack.com";
p->authorUrl = "https://vcvrack.com/";
p->pluginUrl = "https://vcvrack.com/";
p->manualUrl = "https://vcvrack.com/manual/Core.html";
p->sourceUrl = "https://github.com/VCVRack/Rack";

modelAudioInterface->name = "Audio-8";
modelAudioInterface->description = "Sends audio and CV to/from an audio device";
modelAudioInterface->tags = {"External"};
p->addModel(modelAudioInterface);

modelAudioInterface16->name = "Audio-16";
modelAudioInterface16->description = "Sends audio and CV to/from an audio device";
modelAudioInterface16->tags = {"External"};
p->addModel(modelAudioInterface16);

modelMIDI_CV->name = "MIDI-CV";
modelMIDI_CV->description = "Converts MIDI from an external device to CV and gates";
modelMIDI_CV->tags = {"External", "MIDI"};
p->addModel(modelMIDI_CV);

modelMIDI_CC->name = "MIDI-CC";
modelMIDI_CC->description = "Converts MIDI CC from an external device to CV";
modelMIDI_CC->tags = {"External", "MIDI"};
p->addModel(modelMIDI_CC);

modelMIDI_Gate->name = "MIDI-Gate";
modelMIDI_Gate->description = "Converts MIDI notes from an external device to gates";
modelMIDI_Gate->tags = {"External", "MIDI"};
p->addModel(modelMIDI_Gate);

modelMIDI_Map->name = "MIDI-Map";
modelMIDI_Map->description = "";
modelMIDI_Map->tags = {"External", "MIDI"};
p->addModel(modelMIDI_Map);

modelCV_MIDI->name = "CV-MIDI";
modelCV_MIDI->description = "Converts CV to MIDI and sends to an external device";
modelCV_MIDI->tags = {"External", "MIDI"};
p->addModel(modelCV_MIDI);

modelCV_CC->name = "CV-CC";
modelCV_CC->description = "Converts CV to MIDI CC and sends to an external device";
modelCV_CC->tags = {"External", "MIDI"};
p->addModel(modelCV_CC);

modelCV_Gate->name = "CV-Gate";
modelCV_Gate->description = "Converts gates to MIDI notes and sends to an external device";
modelCV_Gate->tags = {"External", "MIDI"};
p->addModel(modelCV_Gate);

modelBlank->name = "Blank";
modelBlank->description = "A resizable blank panel";
modelBlank->tags = {"Blank"};
p->addModel(modelBlank);

modelNotes->name = "Notes";
modelNotes->description = "Write text for patch notes or artist attribution";
modelNotes->tags = {"Blank"};
p->addModel(modelNotes);
}

+ 85
- 62
src/app/MenuBar.cpp View File

@@ -188,7 +188,7 @@ struct EditButton : MenuButton {
menu->addChild(redoItem);

DisconnectCablesItem *disconnectCablesItem = new DisconnectCablesItem;
disconnectCablesItem->text = "Disconnect cables";
disconnectCablesItem->text = "Clear cables";
menu->addChild(disconnectCablesItem);
}
};
@@ -459,6 +459,7 @@ struct AccountEmailField : ui::TextField {

struct AccountPasswordField : ui::PasswordField {
ui::MenuItem *logInItem;

void onSelectKey(const event::SelectKey &e) override {
if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) {
logInItem->doAction();
@@ -489,6 +490,7 @@ struct LogInItem : ui::MenuItem {
disabled = isLoggingIn;
text = "Log in";
rightText = plugin::loginStatus;
MenuItem::step();
}
};

@@ -498,9 +500,57 @@ struct SyncItem : ui::MenuItem {
plugin::syncUpdates();
});
t.detach();
e.consume(NULL);
}
};

struct PluginSyncItem : ui::MenuItem {
plugin::Update *update;

void setUpdate(plugin::Update *update) {
this->update = update;
text = update->pluginSlug;
plugin::Plugin *p = plugin::getPlugin(update->pluginSlug);
if (p) {
rightText += "v" + p->version + " → ";
}
rightText += "v" + update->version;
}

ui::Menu *createChildMenu() override {
if (update->changelogUrl != "") {
ui::Menu *menu = new ui::Menu;

UrlItem *changelogUrl = new UrlItem;
changelogUrl->text = "Changelog";
changelogUrl->url = update->changelogUrl;
menu->addChild(changelogUrl);

return menu;
}
return NULL;
}

void step() override {
if (update->progress >= 1) {
rightText = CHECKMARK_STRING;
}
else if (update->progress > 0) {
rightText = string::f("%.0f%%", update->progress * 100.f);
}
MenuItem::step();
}

void onAction(const event::Action &e) override {
std::thread t([=]() {
plugin::syncUpdate(update);
});
t.detach();
e.consume(NULL);
}
};


#if 0
struct SyncButton : ui::Button {
bool checked = false;
@@ -544,46 +594,48 @@ struct LogOutItem : ui::MenuItem {
}
};

struct DownloadQuantity : Quantity {
float getValue() override {
return plugin::downloadProgress;
}

float getDisplayValue() override {
return getValue() * 100.f;
}

int getDisplayPrecision() override {return 0;}

std::string getLabel() override {
return "Downloading " + plugin::downloadName;
}

std::string getUnit() override {return "%";}
};

struct PluginsMenu : ui::Menu {
int state = 0;
bool loggedIn = false;

PluginsMenu() {
refresh();
}

void step() override {
if (!loggedIn && plugin::isLoggedIn())
refresh();
Menu::step();
}

void refresh() {
clearChildren();

{
ui::MenuLabel *disabledLable = new ui::MenuLabel;
disabledLable->text = "Server not yet available";
addChild(disabledLable);
return;
if (!plugin::isLoggedIn()) {
UrlItem *registerItem = new UrlItem;
registerItem->text = "Register VCV account";
registerItem->url = "https://vcvrack.com/";
addChild(registerItem);

AccountEmailField *emailField = new AccountEmailField;
emailField->placeholder = "Email";
emailField->box.size.x = 240.0;
addChild(emailField);

AccountPasswordField *passwordField = new AccountPasswordField;
passwordField->placeholder = "Password";
passwordField->box.size.x = 240.0;
emailField->passwordField = passwordField;
addChild(passwordField);

LogInItem *logInItem = new LogInItem;
logInItem->emailField = emailField;
logInItem->passwordField = passwordField;
passwordField->logInItem = logInItem;
addChild(logInItem);
}
else {
loggedIn = true;

if (plugin::isLoggedIn()) {
UrlItem *manageItem = new UrlItem;
manageItem->text = "Manage";
manageItem->url = "https://vcvrack.com/plugins.html";
@@ -602,49 +654,20 @@ struct PluginsMenu : ui::Menu {
addChild(new ui::MenuEntry);

ui::MenuLabel *updatesLabel = new ui::MenuLabel;
updatesLabel->text = "Updates (click for changelog)";
updatesLabel->text = "Updates";
addChild(updatesLabel);

for (const plugin::Update &update : plugin::updates) {
UrlItem *updateItem = new UrlItem;
updateItem->text = update.pluginSlug;
plugin::Plugin *p = plugin::getPlugin(update.pluginSlug);
if (p) {
updateItem->rightText += "v" + p->version + " → ";
}
updateItem->rightText += "v" + update.version;
updateItem->url = update.changelogUrl;
updateItem->disabled = update.changelogUrl.empty();
for (plugin::Update &update : plugin::updates) {
PluginSyncItem *updateItem = new PluginSyncItem;
updateItem->setUpdate(&update);
addChild(updateItem);
}
}
}
else {
UrlItem *registerItem = new UrlItem;
registerItem->text = "Register VCV account";
registerItem->url = "https://vcvrack.com/";
addChild(registerItem);

AccountEmailField *emailField = new AccountEmailField;
emailField->placeholder = "Email";
emailField->box.size.x = 220.0;
addChild(emailField);

AccountPasswordField *passwordField = new AccountPasswordField;
passwordField->placeholder = "Password";
passwordField->box.size.x = 220.0;
emailField->passwordField = passwordField;
addChild(passwordField);

LogInItem *logInItem = new LogInItem;
logInItem->emailField = emailField;
logInItem->passwordField = passwordField;
passwordField->logInItem = logInItem;
addChild(logInItem);
}
}
};


struct PluginsButton : MenuButton {
NotificationIcon *notification;

@@ -704,7 +727,7 @@ struct HelpButton : MenuButton {
if (hasUpdate()) {
UrlItem *updateItem = new UrlItem;
updateItem->text = "Update " + APP_NAME;
updateItem->rightText = APP_VERSION + " → " + APP_NEW_VERSION;
updateItem->rightText = APP_VERSION + " → " + APP_VERSION_UPDATE;
updateItem->url = "https://vcvrack.com/";
menu->addChild(updateItem);
}
@@ -721,7 +744,7 @@ struct HelpButton : MenuButton {
}

bool hasUpdate() {
return !APP_NEW_VERSION.empty() && APP_NEW_VERSION != APP_VERSION;
return !APP_VERSION_UPDATE.empty() && APP_VERSION_UPDATE != APP_VERSION;
}
};



+ 4
- 2
src/app/common.cpp View File

@@ -10,8 +10,10 @@ namespace app {

std::string APP_NAME = "VCV Rack";
std::string APP_VERSION = TOSTRING(VERSION);
std::string APP_NEW_VERSION;
std::string APP_VERSION_UPDATE;
std::string API_URL = "https://api.vcvrack.com";
std::string API_VERSION = "1";


static void checkVersion() {
std::string versionUrl = app::API_URL + "/version";
@@ -26,7 +28,7 @@ static void checkVersion() {

json_t *versionJ = json_object_get(versionResJ, "version");
if (versionJ)
APP_NEW_VERSION = json_string_value(versionJ);
APP_VERSION_UPDATE = json_string_value(versionJ);
}

void init() {


+ 132
- 127
src/plugin.cpp View File

@@ -22,11 +22,11 @@
#include <jansson.h>

#if defined ARCH_WIN
#include <windows.h>
#include <direct.h>
#define mkdir(_dir, _perms) _mkdir(_dir)
#include <windows.h>
#include <direct.h>
#define mkdir(_dir, _perms) _mkdir(_dir)
#else
#include <dlfcn.h>
#include <dlfcn.h>
#endif
#include <dirent.h>
#include <osdialog.h>
@@ -40,42 +40,22 @@ namespace plugin {
// private API
////////////////////

static bool loadPlugin(std::string path) {
// Load plugin.json
std::string metadataFilename = path + "/plugin.json";
FILE *file = fopen(metadataFilename.c_str(), "r");
if (!file) {
WARN("Plugin metadata file %s does not exist", metadataFilename.c_str());
return false;
}
DEFER({
fclose(file);
});

json_error_t error;
json_t *rootJ = json_loadf(file, 0, &error);
if (!rootJ) {
WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
return false;
}
DEFER({
json_decref(rootJ);
});
typedef void (*InitCallback)(Plugin*);

static InitCallback loadLibrary(Plugin *plugin) {
// Load plugin library
std::string libraryFilename;
#if defined ARCH_LIN
libraryFilename = path + "/" + "plugin.so";
libraryFilename = plugin->path + "/" + "plugin.so";
#elif defined ARCH_WIN
libraryFilename = path + "/" + "plugin.dll";
libraryFilename = plugin->path + "/" + "plugin.dll";
#elif ARCH_MAC
libraryFilename = path + "/" + "plugin.dylib";
libraryFilename = plugin->path + "/" + "plugin.dylib";
#endif

// Check file existence
if (!system::isFile(libraryFilename)) {
WARN("Plugin file %s does not exist", libraryFilename.c_str());
return false;
throw UserException(string::f("Library %s does not exist", libraryFilename.c_str()));
}

// Load dynamic/shared library
@@ -85,19 +65,17 @@ static bool loadPlugin(std::string path) {
SetErrorMode(0);
if (!handle) {
int error = GetLastError();
WARN("Failed to load library %s: code %d", libraryFilename.c_str(), error);
return false;
throw UserException(string::f("Failed to load library %s: code %d", libraryFilename.c_str(), error));
}
#else
void *handle = dlopen(libraryFilename.c_str(), RTLD_NOW);
if (!handle) {
WARN("Failed to load library %s: %s", libraryFilename.c_str(), dlerror());
return false;
throw UserException(string::f("Failed to load library %s: %s", libraryFilename.c_str(), dlerror()));
}
#endif
plugin->handle = handle;

// Call plugin's init() function
typedef void (*InitCallback)(Plugin *);
// Get plugin's init() function
InitCallback initCallback;
#if defined ARCH_WIN
initCallback = (InitCallback) GetProcAddress(handle, "init");
@@ -105,95 +83,103 @@ static bool loadPlugin(std::string path) {
initCallback = (InitCallback) dlsym(handle, "init");
#endif
if (!initCallback) {
WARN("Failed to read init() symbol in %s", libraryFilename.c_str());
return false;
throw UserException(string::f("Failed to read init() symbol in %s", libraryFilename.c_str()));
}

// Construct and initialize Plugin instance
Plugin *plugin = new Plugin;
plugin->path = path;
plugin->handle = handle;
initCallback(plugin);
plugin->fromJson(rootJ);

// Check slug
if (!isSlugValid(plugin->slug)) {
WARN("Plugin slug \"%s\" is invalid", plugin->slug.c_str());
// TODO Fix memory leak with `plugin`
return false;
}
return initCallback;
}

// Reject plugin if slug already exists
Plugin *oldPlugin = getPlugin(plugin->slug);
if (oldPlugin) {
WARN("Plugin \"%s\" is already loaded, not attempting to load it again", plugin->slug.c_str());
// TODO Fix memory leak with `plugin`
return false;
}

// Add plugin to list
plugins.push_back(plugin);
INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), libraryFilename.c_str());

// Normalize tags
for (Model *model : plugin->models) {
std::vector<std::string> normalizedTags;
for (const std::string &tag : model->tags) {
std::string normalizedTag = normalizeTag(tag);
if (!normalizedTag.empty())
normalizedTags.push_back(normalizedTag);
/** If path is blank, loads Core */
static Plugin *loadPlugin(std::string path) {
Plugin *plugin = new Plugin;
try {
plugin->path = path;

// Load plugin.json
std::string metadataFilename;
if (path == "")
metadataFilename = asset::system("Core.json");
else
metadataFilename = path + "/plugin.json";
FILE *file = fopen(metadataFilename.c_str(), "r");
if (!file) {
throw UserException(string::f("Metadata file %s does not exist", metadataFilename.c_str()));
}
model->tags = normalizedTags;
}
DEFER({
fclose(file);
});

json_error_t error;
json_t *rootJ = json_loadf(file, 0, &error);
if (!rootJ) {
throw UserException(string::f("JSON parsing error at %s %d:%d %s", metadataFilename.c_str(), error.line, error.column, error.text));
}
DEFER({
json_decref(rootJ);
});

// Call init callback
InitCallback initCallback;
if (path == "") {
initCallback = ::init;
}
else {
initCallback = loadLibrary(plugin);
}
initCallback(plugin);

// Search for presets
for (Model *model : plugin->models) {
std::string presetDir = asset::plugin(plugin, "presets/" + model->slug);
for (const std::string &presetPath : system::getEntries(presetDir)) {
model->presetPaths.push_back(presetPath);
// Load manifest
plugin->fromJson(rootJ);

// Check slug
if (!isSlugValid(plugin->slug)) {
throw UserException(string::f("Plugin slug \"%s\" is invalid", plugin->slug.c_str()));
}
}

return true;
}
// Reject plugin if slug already exists
Plugin *oldPlugin = getPlugin(plugin->slug);
if (oldPlugin) {
throw UserException(string::f("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str()));
}

static bool syncUpdate(const Update &update) {
#if defined ARCH_WIN
std::string arch = "win";
#elif ARCH_MAC
std::string arch = "mac";
#elif defined ARCH_LIN
std::string arch = "lin";
#endif
// Normalize tags
for (Model *model : plugin->models) {
std::vector<std::string> normalizedTags;
for (const std::string &tag : model->tags) {
std::string normalizedTag = normalizeTag(tag);
if (!normalizedTag.empty())
normalizedTags.push_back(normalizedTag);
}
model->tags = normalizedTags;
}

std::string downloadUrl = app::API_URL + "/download";
downloadUrl += "?token=" + network::encodeUrl(settings::token);
downloadUrl += "&slug=" + network::encodeUrl(update.pluginSlug);
downloadUrl += "&version=" + network::encodeUrl(update.version);
downloadUrl += "&arch=" + network::encodeUrl(arch);
// Search for presets
for (Model *model : plugin->models) {
std::string presetDir = asset::plugin(plugin, "presets/" + model->slug);
for (const std::string &presetPath : system::getEntries(presetDir)) {
model->presetPaths.push_back(presetPath);
}
}

// downloadName = name;
downloadProgress = 0.0;
INFO("Downloading plugin %s %s %s", update.pluginSlug.c_str(), update.version.c_str(), arch.c_str());
INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), path.c_str());
plugins.push_back(plugin);

// Download zip
std::string pluginDest = asset::user("plugins/" + update.pluginSlug + ".zip");
if (!network::requestDownload(downloadUrl, pluginDest, &downloadProgress)) {
WARN("Plugin %s download was unsuccessful", update.pluginSlug.c_str());
return false;
return plugin;
}
catch (UserException &e) {
WARN("Could not load plugin %s: %s", path.c_str(), e.what());
delete plugin;
return NULL;
}

// downloadName = "";
return true;
}

static void loadPlugins(std::string path) {
std::string message;
for (std::string pluginPath : system::getEntries(path)) {
if (!system::isDirectory(pluginPath))
continue;
if (!loadPlugin(pluginPath)) {
// Ignore bad plugins. They are reported in log.txt.
// Ignore bad plugins. They are reported in the log.
}
}
}
@@ -233,7 +219,7 @@ static int extractZipHandle(zip_t *za, const char *dir) {
continue;

while (1) {
char buffer[1<<15];
char buffer[1 << 15];
int len = zip_fread(zf, buffer, sizeof(buffer));
if (len <= 0)
break;
@@ -267,7 +253,7 @@ static int extractZip(const char *filename, const char *path) {
return err;
}

static void extractPackages(const std::string &path) {
static void extractPackages(std::string path) {
std::string message;

for (std::string packagePath : system::getEntries(path)) {
@@ -296,10 +282,7 @@ static void extractPackages(const std::string &path) {

void init() {
// Load Core
Plugin *corePlugin = new Plugin;
// This function is defined in Core/plugin.cpp
::init(corePlugin);
plugins.push_back(corePlugin);
loadPlugin("");

// Get user plugins directory
std::string pluginsDir = asset::user("plugins");
@@ -309,7 +292,7 @@ void init() {
extractPackages(pluginsDir);
loadPlugins(pluginsDir);

// Copy Fundamental package to plugins directory if Fundamental is not loaded
// If Fundamental wasn't loaded, copy the bundled Fundamental package and load it
std::string fundamentalSrc = asset::system("Fundamental.zip");
std::string fundamentalDir = asset::user("plugins/Fundamental");
if (!settings::devMode && !getPlugin("Fundamental") && system::isFile(fundamentalSrc)) {
@@ -318,9 +301,8 @@ void init() {
loadPlugin(fundamentalDir);
}

// TEMP
// Sync in a detached thread
std::thread t([]{
std::thread t([] {
queryUpdates();
});
t.detach();
@@ -387,9 +369,9 @@ void queryUpdates() {
updates.clear();

// Get user's plugins list
std::string pluginsUrl = app::API_URL + "/plugins";
json_t *pluginsReqJ = json_object();
json_object_set(pluginsReqJ, "token", json_string(settings::token.c_str()));
std::string pluginsUrl = app::API_URL + "/plugins";
json_t *pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, pluginsReqJ);
json_decref(pluginsReqJ);
if (!pluginsResJ) {
@@ -406,11 +388,14 @@ void queryUpdates() {
return;
}

// Get community manifests
std::string manifestsUrl = app::API_URL + "/community/manifests";
json_t *manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, NULL);
// Get library manifests
std::string manifestsUrl = app::API_URL + "/library/manifests";
json_t *manifestsReq = json_object();
json_object_set(manifestsReq, "version", json_string(app::API_VERSION.c_str()));
json_t *manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, manifestsReq);
json_decref(manifestsReq);
if (!manifestsResJ) {
WARN("Request for community manifests failed");
WARN("Request for library manifests failed");
return;
}
DEFER({
@@ -434,7 +419,7 @@ void queryUpdates() {

// Get version
// TODO Change this to "version" when API changes
json_t *versionJ = json_object_get(manifestJ, "latestVersion");
json_t *versionJ = json_object_get(manifestJ, "version");
if (!versionJ) {
WARN("Plugin %s has no version in manifest", update.pluginSlug.c_str());
continue;
@@ -464,15 +449,37 @@ void queryUpdates() {
}
}

void syncUpdate(Update *update) {
#if defined ARCH_WIN
std::string arch = "win";
#elif ARCH_MAC
std::string arch = "mac";
#elif defined ARCH_LIN
std::string arch = "lin";
#endif

std::string downloadUrl = app::API_URL + "/download";
downloadUrl += "?token=" + network::encodeUrl(settings::token);
downloadUrl += "&slug=" + network::encodeUrl(update->pluginSlug);
downloadUrl += "&version=" + network::encodeUrl(update->version);
downloadUrl += "&arch=" + network::encodeUrl(arch);

INFO("Downloading plugin %s %s %s", update->pluginSlug.c_str(), update->version.c_str(), arch.c_str());

// Download zip
std::string pluginDest = asset::user("plugins/" + update->pluginSlug + ".zip");
if (!network::requestDownload(downloadUrl, pluginDest, &update->progress)) {
WARN("Plugin %s download was unsuccessful", update->pluginSlug.c_str());
return;
}
}

void syncUpdates() {
if (settings::token.empty())
return;

downloadProgress = 0.0;
downloadName = "Updating plugins...";

for (const Update &update : updates) {
syncUpdate(update);
for (Update &update : updates) {
syncUpdate(&update);
}
}

@@ -626,8 +633,6 @@ std::vector<Plugin*> plugins;

std::string loginStatus;
std::vector<Update> updates;
float downloadProgress = 0.f;
std::string downloadName;


} // namespace plugin


+ 3
- 4
src/plugin/Plugin.cpp View File

@@ -1,6 +1,7 @@
#include <plugin/Plugin.hpp>
#include <plugin/Model.hpp>
#include <plugin.hpp>
#include <string.hpp>


namespace rack {
@@ -18,8 +19,7 @@ void Plugin::addModel(Model *model) {
assert(!model->plugin);
// Check model slug
if (!isSlugValid(model->slug)) {
WARN("Module slug \"%s\" is invalid", model->slug.c_str());
return;
throw UserException(string::f("Module slug \"%s\" is invalid", model->slug.c_str()));
}
model->plugin = this;
models.push_back(model);
@@ -99,8 +99,7 @@ void Plugin::fromJson(json_t *rootJ) {

Model *model = getModel(modelSlug);
if (!model) {
WARN("plugin.json of \"%s\" contains module \"%s\" but it is not defined in the plugin", slug.c_str(), modelSlug.c_str());
continue;
throw UserException(string::f("plugin.json of \"%s\" contains module \"%s\" but it is not defined in the plugin", slug.c_str(), modelSlug.c_str()));
}

model->fromJson(moduleJ);


+ 1
- 1
src/window.cpp View File

@@ -164,7 +164,7 @@ static void keyCallback(GLFWwindow *win, int key, int scancode, int action, int
return;

// Keyboard MIDI driver
if ((mods & RACK_MOD_MASK) == 0 && action == GLFW_PRESS) {
if (action == GLFW_PRESS && (mods & RACK_MOD_MASK) == 0) {
keyboard::press(key);
}
if (action == GLFW_RELEASE) {


Loading…
Cancel
Save