From f9087788bc2da843b42720ce29fa235a626b58ab Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Wed, 28 Mar 2018 00:50:30 -0400 Subject: [PATCH] Update plugin manager to 0.6 API --- src/app/PluginManagerWidget.cpp | 174 +++++++++++------------ src/app/app.cpp | 4 +- src/plugin.cpp | 241 ++++++++++++++------------------ src/util/request.cpp | 3 +- 4 files changed, 196 insertions(+), 226 deletions(-) diff --git a/src/app/PluginManagerWidget.cpp b/src/app/PluginManagerWidget.cpp index 7fd7573f..31a60d57 100644 --- a/src/app/PluginManagerWidget.cpp +++ b/src/app/PluginManagerWidget.cpp @@ -8,12 +8,53 @@ namespace rack { +struct RegisterButton : Button { + void onAction(EventAction &e) override { + std::thread t([&]() { + systemOpenBrowser("https://vcvrack.com/"); + }); + t.detach(); + } +}; + + +struct LogInButton : Button { + TextField *emailField; + TextField *passwordField; + void onAction(EventAction &e) override { + std::thread t(pluginLogIn, emailField->text, passwordField->text); + t.detach(); + passwordField->text = ""; + } +}; + + +struct StatusLabel : Label { + void step() override { + text = pluginGetLoginStatus(); + } +}; + + +struct ManageButton : Button { + void onAction(EventAction &e) override { + std::thread t([&]() { + systemOpenBrowser("https://vcvrack.com/"); + }); + t.detach(); + } +}; + + struct SyncButton : Button { bool checked = false; + /** Updates are available */ bool available = false; + /** Plugins have been updated */ bool completed = false; void step() override { + // Check for plugin update on first step() if (!checked) { std::thread t([this]() { if (pluginSync(true)) @@ -22,6 +63,7 @@ struct SyncButton : Button { t.detach(); checked = true; } + // Display message if we've completed updates if (completed) { if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been updated. Close Rack and re-launch it to load new updates.")) { windowClose(); @@ -52,112 +94,83 @@ struct SyncButton : Button { }; +struct LogOutButton : Button { + void onAction(EventAction &e) override { + pluginLogOut(); + } +}; + + +struct DownloadProgressBar : ProgressBar { + void step() override { + label = "Downloading"; + std::string name = pluginGetDownloadName(); + if (name != "") + label += " " + name; + setValue(100.0 * pluginGetDownloadProgress()); + } +}; + + +struct CancelButton : Button { + void onAction(EventAction &e) override { + pluginCancelDownload(); + } +}; + + PluginManagerWidget::PluginManagerWidget() { - box.size.y = BND_WIDGET_HEIGHT; - float margin = 5; { - loginWidget = new Widget(); - Vec pos = Vec(0, 0); - - struct RegisterButton : Button { - void onAction(EventAction &e) override { - std::thread t([&]() { - systemOpenBrowser("https://vcvrack.com/"); - }); - t.detach(); - } - }; + SequentialLayout *layout = Widget::create(Vec(0, 0)); + layout->spacing = 5; + loginWidget = layout; + Button *registerButton = new RegisterButton(); - registerButton->box.pos = pos; registerButton->box.size.x = 75; registerButton->text = "Register"; loginWidget->addChild(registerButton); - pos.x += registerButton->box.size.x; - pos.x += margin; TextField *emailField = new TextField(); - emailField->box.pos = pos; emailField->box.size.x = 175; emailField->placeholder = "Email"; loginWidget->addChild(emailField); - pos.x += emailField->box.size.x; - pos.x += margin; PasswordField *passwordField = new PasswordField(); - passwordField->box.pos = pos; passwordField->box.size.x = 175; passwordField->placeholder = "Password"; loginWidget->addChild(passwordField); - pos.x += passwordField->box.size.x; - - struct LogInButton : Button { - TextField *emailField; - TextField *passwordField; - void onAction(EventAction &e) override { - std::thread t(pluginLogIn, emailField->text, passwordField->text); - t.detach(); - passwordField->text = ""; - } - }; - pos.x += margin; + LogInButton *logInButton = new LogInButton(); - logInButton->box.pos = pos; logInButton->box.size.x = 100; logInButton->text = "Log in"; logInButton->emailField = emailField; logInButton->passwordField = passwordField; loginWidget->addChild(logInButton); - pos.x += logInButton->box.size.x; - struct StatusLabel : Label { - void step() override { - text = pluginGetLoginStatus(); - } - }; Label *label = new StatusLabel(); - label->box.pos = pos; loginWidget->addChild(label); addChild(loginWidget); } { - manageWidget = new Widget(); - Vec pos = Vec(0, 0); - - struct ManageButton : Button { - void onAction(EventAction &e) override { - std::thread t([&]() { - systemOpenBrowser("https://vcvrack.com/"); - }); - t.detach(); - } - }; + SequentialLayout *layout = Widget::create(Vec(0, 0)); + layout->spacing = 5; + manageWidget = layout; + Button *manageButton = new ManageButton(); - manageButton->box.pos = pos; manageButton->box.size.x = 125; manageButton->text = "Manage plugins"; manageWidget->addChild(manageButton); - pos.x += manageButton->box.size.x; - pos.x += margin; Button *syncButton = new SyncButton(); - syncButton->box.pos = pos; syncButton->box.size.x = 125; syncButton->text = "Update plugins"; manageWidget->addChild(syncButton); - pos.x += syncButton->box.size.x; - struct LogOutButton : Button { - void onAction(EventAction &e) override { - pluginLogOut(); - } - }; - pos.x += margin; Button *logOutButton = new LogOutButton(); - logOutButton->box.pos = pos; logOutButton->box.size.x = 100; logOutButton->text = "Log out"; manageWidget->addChild(logOutButton); @@ -166,37 +179,20 @@ PluginManagerWidget::PluginManagerWidget() { } { - downloadWidget = new Widget(); - Vec pos = Vec(0, 0); - - struct DownloadProgressBar : ProgressBar { - void step() override { - label = "Downloading"; - std::string name = pluginGetDownloadName(); - if (name != "") - label += " " + name; - setValue(100.0 * pluginGetDownloadProgress()); - } - }; + SequentialLayout *layout = Widget::create(Vec(0, 0)); + layout->spacing = 5; + downloadWidget = layout; + ProgressBar *downloadProgress = new DownloadProgressBar(); - downloadProgress->box.pos = pos; downloadProgress->box.size.x = 300; downloadProgress->setLimits(0, 100); downloadProgress->unit = "%"; downloadWidget->addChild(downloadProgress); - pos.x += downloadProgress->box.size.x; - - // struct CancelButton : Button { - // void onAction(EventAction &e) override { - // pluginCancelDownload(); - // } - // }; - // pos.x += margin; - // Button *logOutButton = new CancelButton(); - // logOutButton->box.pos = pos; - // logOutButton->box.size.x = 100; - // logOutButton->text = "Cancel"; - // downloadWidget->addChild(logOutButton); + + // Button *cancelButton = new CancelButton(); + // cancelButton->box.size.x = 100; + // cancelButton->text = "Cancel"; + // downloadWidget->addChild(cancelButton); addChild(downloadWidget); } diff --git a/src/app/app.cpp b/src/app/app.cpp index c49ee550..32454b5f 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -5,8 +5,8 @@ namespace rack { std::string gApplicationName = "VCV Rack"; std::string gApplicationVersion = TOSTRING(VERSION); -std::string gApiHost = "https://api.vcvrack.com"; -// std::string gApiHost = "http://localhost:8081"; +// std::string gApiHost = "https://api.vcvrack.com"; +std::string gApiHost = "http://localhost:8081"; RackWidget *gRackWidget = NULL; Toolbar *gToolbar = NULL; diff --git a/src/plugin.cpp b/src/plugin.cpp index 79a7ab5a..1a42ceaf 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -126,98 +126,77 @@ static bool loadPlugin(std::string path) { return true; } -static bool syncPlugin(json_t *pluginJ, bool dryRun) { - json_t *slugJ = json_object_get(pluginJ, "slug"); - if (!slugJ) +static bool syncPlugin(std::string slug, json_t *manifestJ, bool dryRun) { + // Get latest version + json_t *latestVersionJ = json_object_get(manifestJ, "latestVersion"); + if (!latestVersionJ) { + warn("Could not get latest version of plugin %s", slug.c_str()); return false; - std::string slug = json_string_value(slugJ); - - // Get community version - std::string version; - json_t *versionJ = json_object_get(pluginJ, "version"); - if (versionJ) { - version = json_string_value(versionJ); } + std::string latestVersion = json_string_value(latestVersionJ); // Check whether we already have a plugin with the same slug and version - for (Plugin *plugin : gPlugins) { - if (plugin->slug == slug) { - // plugin->version might be blank, so adding a version of the manifest will update the plugin - if (plugin->version == version) - return false; - } - } - - json_t *nameJ = json_object_get(pluginJ, "name"); - if (!nameJ) + Plugin *plugin = pluginGetPlugin(slug); + if (plugin && plugin->version == latestVersion) { return false; - std::string name = json_string_value(nameJ); + } - std::string download; - std::string sha256; + json_t *nameJ = json_object_get(manifestJ, "name"); + std::string name; + if (nameJ) { + name = json_string_value(nameJ); + } + else { + name = slug; + } - json_t *downloadsJ = json_object_get(pluginJ, "downloads"); - if (downloadsJ) { #if ARCH_WIN - #define DOWNLOADS_ARCH "win" + std::string arch = "win"; #elif ARCH_MAC - #define DOWNLOADS_ARCH "mac" + std::string arch = "mac"; #elif ARCH_LIN - #define DOWNLOADS_ARCH "lin" + std::string arch = "lin"; #endif - json_t *archJ = json_object_get(downloadsJ, DOWNLOADS_ARCH); - if (archJ) { - // Get download URL - json_t *downloadJ = json_object_get(archJ, "download"); - if (downloadJ) - download = json_string_value(downloadJ); - // Get SHA256 hash - json_t *sha256J = json_object_get(archJ, "sha256"); - if (sha256J) - sha256 = json_string_value(sha256J); - } - } - - json_t *productIdJ = json_object_get(pluginJ, "productId"); - if (productIdJ) { - download = gApiHost; - download += "/download"; - download += "?slug="; - download += slug; - download += "&token="; - download += requestEscape(gToken); - } - if (download.empty()) { - warn("Could not get download URL for plugin %s", slug.c_str()); - return false; + std::string downloadUrl; + downloadUrl = gApiHost; + downloadUrl += "/download"; + if (dryRun) { + downloadUrl += "/available"; } - - // If plugin is not loaded, download the zip file to /plugins - downloadName = name; - downloadProgress = 0.0; - - // Download zip - std::string pluginsDir = assetLocal("plugins"); - std::string pluginPath = pluginsDir + "/" + slug; - std::string zipPath = pluginPath + ".zip"; - bool success = requestDownload(download, zipPath, &downloadProgress); - if (!success) { - warn("Plugin %s download was unsuccessful", slug.c_str()); - return false; + downloadUrl += "?token=" + requestEscape(gToken); + downloadUrl += "&slug=" + requestEscape(slug); + downloadUrl += "&version=" + requestEscape(latestVersion); + downloadUrl += "&arch=" + requestEscape(arch); + + if (dryRun) { + // Check if available + json_t *availableResJ = requestJson(METHOD_GET, downloadUrl, NULL); + if (!availableResJ) { + warn("Could not check whether download is available"); + return false; + } + defer({ + json_decref(availableResJ); + }); + json_t *successJ = json_object_get(availableResJ, "success"); + return json_boolean_value(successJ); } + else { + downloadName = name; + downloadProgress = 0.0; + info("Downloading plugin %s %s %s", slug.c_str(), latestVersion.c_str(), arch.c_str()); - if (!sha256.empty()) { - // Check SHA256 hash - std::string actualSha256 = requestSHA256File(zipPath); - if (actualSha256 != sha256) { - warn("Plugin %s does not match expected SHA256 checksum", slug.c_str()); + // Download zip + std::string pluginDest = assetLocal("plugins/" + slug + ".zip"); + if (!requestDownload(downloadUrl, pluginDest, &downloadProgress)) { + warn("Plugin %s download was unsuccessful", slug.c_str()); return false; } - } - downloadName = ""; - return true; + downloadName = ""; + return true; + } } static void loadPlugins(std::string path) { @@ -378,7 +357,6 @@ void pluginDestroy() { } bool pluginSync(bool dryRun) { - return false; if (gToken.empty()) return false; @@ -389,74 +367,69 @@ bool pluginSync(bool dryRun) { downloadProgress = 0.0; downloadName = "Updating plugins..."; } + defer({ + isDownloading = false; + }); - json_t *resJ = NULL; - json_t *communityResJ = NULL; + // Get user's plugins list + json_t *pluginsReqJ = json_object(); + json_object_set(pluginsReqJ, "token", json_string(gToken.c_str())); + json_t *pluginsResJ = requestJson(METHOD_GET, gApiHost + "/plugins", pluginsReqJ); + json_decref(pluginsReqJ); + if (!pluginsResJ) { + warn("Request for user's plugins failed"); + return false; + } + defer({ + json_decref(pluginsResJ); + }); - try { - // Download plugin slugs - json_t *reqJ = json_object(); - json_object_set(reqJ, "version", json_string(gApplicationVersion.c_str())); - json_object_set(reqJ, "token", json_string(gToken.c_str())); - resJ = requestJson(METHOD_GET, gApiHost + "/plugins", reqJ); - json_decref(reqJ); - if (!resJ) - throw std::runtime_error("No response from server"); + json_t *errorJ = json_object_get(pluginsResJ, "error"); + if (errorJ) { + warn("Request for user's plugins returned an error: %s", json_string_value(errorJ)); + return false; + } - json_t *errorJ = json_object_get(resJ, "error"); - if (errorJ) - throw std::runtime_error(json_string_value(errorJ)); - - // Download community plugins - communityResJ = requestJson(METHOD_GET, gApiHost + "/community/plugins", NULL); - if (!communityResJ) - throw std::runtime_error("No response from server"); - - json_t *communityErrorJ = json_object_get(communityResJ, "error"); - if (communityErrorJ) - throw std::runtime_error(json_string_value(communityErrorJ)); - - // Check each plugin in list of plugin slugs - json_t *pluginSlugsJ = json_object_get(resJ, "plugins"); - json_t *communityPluginsJ = json_object_get(communityResJ, "plugins"); - - size_t index; - json_t *pluginSlugJ; - json_array_foreach(pluginSlugsJ, index, pluginSlugJ) { - std::string slug = json_string_value(pluginSlugJ); - // Search for plugin slug in community - size_t communityIndex; - json_t *communityPluginJ = NULL; - json_array_foreach(communityPluginsJ, communityIndex, communityPluginJ) { - json_t *communitySlugJ = json_object_get(communityPluginJ, "slug"); - if (!communitySlugJ) - continue; - std::string communitySlug = json_string_value(communitySlugJ); - if (slug == communitySlug) - break; - } + // Get community manifests + json_t *manifestsResJ = requestJson(METHOD_GET, gApiHost + "/community/manifests", NULL); + if (!manifestsResJ) { + warn("Request for community manifests failed"); + return false; + } + defer({ + json_decref(manifestsResJ); + }); - // Sync plugin - if (syncPlugin(communityPluginJ, dryRun)) { - available = true; - } - else { - warn("Plugin %s not found in community", slug.c_str()); - } - } + // Check each plugin in list of plugin slugs + json_t *pluginsJ = json_object_get(pluginsResJ, "plugins"); + if (!pluginsJ) { + warn("No plugins array"); + return false; } - catch (std::runtime_error &e) { - warn("Plugin sync error: %s", e.what()); + json_t *manifestsJ = json_object_get(manifestsResJ, "manifests"); + if (!manifestsJ) { + warn("No manifests object"); + return false; } - if (communityResJ) - json_decref(communityResJ); + size_t slugIndex; + json_t *slugJ; + json_array_foreach(pluginsJ, slugIndex, slugJ) { + std::string slug = json_string_value(slugJ); + // Search for slug in manifests + const char *manifestSlug; + json_t *manifestJ = NULL; + json_object_foreach(manifestsJ, manifestSlug, manifestJ) { + if (slug == std::string(manifestSlug)) + break; + } - if (resJ) - json_decref(resJ); + if (!manifestJ) + continue; - if (!dryRun) { - isDownloading = false; + if (syncPlugin(slug, manifestJ, dryRun)) { + available = true; + } } return available; diff --git a/src/util/request.cpp b/src/util/request.cpp index 9579aea5..09d47c1f 100644 --- a/src/util/request.cpp +++ b/src/util/request.cpp @@ -131,8 +131,9 @@ bool requestDownload(std::string url, std::string filename, float *progress) { curl_easy_setopt(curl, CURLOPT_XFERINFODATA, progress); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); + // Fail on 4xx and 5xx HTTP codes + curl_easy_setopt(curl, CURLOPT_FAILONERROR, true); - info("Downloading %s", url.c_str()); CURLcode res = curl_easy_perform(curl); curl_easy_cleanup(curl);