| @@ -169,15 +169,15 @@ Does *not* add the default value to the map. | |||||
| Posted to https://stackoverflow.com/a/63683271/272642. | Posted to https://stackoverflow.com/a/63683271/272642. | ||||
| Example: | Example: | ||||
| std::map<std::string, int*> m; | |||||
| int v = getWithDefault(m, "a", 3); | |||||
| std::map<std::string, int> m; | |||||
| int v = get(m, "a", 3); | |||||
| // v is 3 because the key "a" does not exist | // v is 3 because the key "a" does not exist | ||||
| int w = getWithDefault(m, "a"); | |||||
| int w = get(m, "a"); | |||||
| // w is 0 because no default value is given, so it assumes the default int. | // w is 0 because no default value is given, so it assumes the default int. | ||||
| */ | */ | ||||
| template <typename C> | template <typename C> | ||||
| typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def = typename C::mapped_type()) { | |||||
| typename C::mapped_type get(const C& m, const typename C::key_type& key, const typename C::mapped_type& def = typename C::mapped_type()) { | |||||
| typename C::const_iterator it = m.find(key); | typename C::const_iterator it = m.find(key); | ||||
| if (it == m.end()) | if (it == m.end()) | ||||
| return def; | return def; | ||||
| @@ -1,7 +1,7 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <common.hpp> | #include <common.hpp> | ||||
| #include <vector> | |||||
| #include <map> | |||||
| namespace rack { | namespace rack { | ||||
| @@ -13,8 +13,7 @@ namespace library { | |||||
| struct Update { | struct Update { | ||||
| std::string pluginSlug; | |||||
| std::string pluginName; | |||||
| std::string name; | |||||
| std::string version; | std::string version; | ||||
| std::string changelogUrl; | std::string changelogUrl; | ||||
| float progress = 0.f; | float progress = 0.f; | ||||
| @@ -31,7 +30,7 @@ void logIn(const std::string& email, const std::string& password); | |||||
| void logOut(); | void logOut(); | ||||
| void queryUpdates(); | void queryUpdates(); | ||||
| bool hasUpdates(); | bool hasUpdates(); | ||||
| void syncUpdate(Update* update); | |||||
| void syncUpdate(const std::string& slug); | |||||
| void syncUpdates(); | void syncUpdates(); | ||||
| bool isSyncing(); | bool isSyncing(); | ||||
| @@ -41,8 +40,11 @@ extern std::string appDownloadUrl; | |||||
| extern std::string appChangelogUrl; | extern std::string appChangelogUrl; | ||||
| extern std::string loginStatus; | extern std::string loginStatus; | ||||
| extern std::vector<Update> updates; | |||||
| // plugin slug -> Update | |||||
| extern std::map<std::string, Update> updates; | |||||
| extern std::string updateStatus; | extern std::string updateStatus; | ||||
| extern std::string updatingSlug; | |||||
| extern bool updatingPlugins; | |||||
| extern bool restartRequested; | extern bool restartRequested; | ||||
| @@ -25,7 +25,7 @@ enum Method { | |||||
| void init(); | void init(); | ||||
| /** Requests a JSON API URL over HTTP(S), using the data as the query (GET) or the body (POST, etc) | /** Requests a JSON API URL over HTTP(S), using the data as the query (GET) or the body (POST, etc) | ||||
| Caller must json_decref(). | |||||
| Caller must json_decref() if return value is non-NULL. | |||||
| */ | */ | ||||
| json_t* requestJson(Method method, const std::string& url, json_t* dataJ, const CookieMap& cookies = {}); | json_t* requestJson(Method method, const std::string& url, json_t* dataJ, const CookieMap& cookies = {}); | ||||
| /** Returns true if downloaded successfully */ | /** Returns true if downloaded successfully */ | ||||
| @@ -575,6 +575,7 @@ static bool isLoggingIn = false; | |||||
| struct AccountEmailField : ui::TextField { | struct AccountEmailField : ui::TextField { | ||||
| ui::TextField* passwordField; | ui::TextField* passwordField; | ||||
| void onSelectKey(const event::SelectKey& e) override { | void onSelectKey(const event::SelectKey& e) override { | ||||
| if (e.action == GLFW_PRESS && e.key == GLFW_KEY_TAB) { | if (e.action == GLFW_PRESS && e.key == GLFW_KEY_TAB) { | ||||
| APP->event->setSelected(passwordField); | APP->event->setSelected(passwordField); | ||||
| @@ -603,6 +604,7 @@ struct AccountPasswordField : ui::PasswordField { | |||||
| struct LogInItem : ui::MenuItem { | struct LogInItem : ui::MenuItem { | ||||
| ui::TextField* emailField; | ui::TextField* emailField; | ||||
| ui::TextField* passwordField; | ui::TextField* passwordField; | ||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| isLoggingIn = true; | isLoggingIn = true; | ||||
| std::string email = emailField->text; | std::string email = emailField->text; | ||||
| @@ -612,7 +614,7 @@ struct LogInItem : ui::MenuItem { | |||||
| isLoggingIn = false; | isLoggingIn = false; | ||||
| }); | }); | ||||
| t.detach(); | t.detach(); | ||||
| e.consume(NULL); | |||||
| e.unconsume(); | |||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| @@ -623,13 +625,13 @@ struct LogInItem : ui::MenuItem { | |||||
| } | } | ||||
| }; | }; | ||||
| struct SyncItem : ui::MenuItem { | |||||
| struct SyncUpdatesItem : ui::MenuItem { | |||||
| void step() override { | void step() override { | ||||
| disabled = true; | disabled = true; | ||||
| if (library::updateStatus != "") { | if (library::updateStatus != "") { | ||||
| text = library::updateStatus; | text = library::updateStatus; | ||||
| } | } | ||||
| else if (library::isSyncing()) { | |||||
| else if (library::updatingPlugins || library::updatingSlug != "") { | |||||
| text = "Updating..."; | text = "Updating..."; | ||||
| } | } | ||||
| else if (!library::hasUpdates()) { | else if (!library::hasUpdates()) { | ||||
| @@ -643,59 +645,72 @@ struct SyncItem : ui::MenuItem { | |||||
| } | } | ||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| std::thread t([ = ] { | |||||
| std::thread t([=] { | |||||
| library::syncUpdates(); | library::syncUpdates(); | ||||
| }); | }); | ||||
| t.detach(); | t.detach(); | ||||
| e.consume(NULL); | |||||
| e.unconsume(); | |||||
| } | } | ||||
| }; | }; | ||||
| struct PluginSyncItem : ui::MenuItem { | |||||
| library::Update* update; | |||||
| struct SyncUpdateItem : ui::MenuItem { | |||||
| std::string slug; | |||||
| void setUpdate(const std::string& slug) { | |||||
| this->slug = slug; | |||||
| auto it = library::updates.find(slug); | |||||
| if (it == library::updates.end()) | |||||
| return; | |||||
| void setUpdate(library::Update* update) { | |||||
| this->update = update; | |||||
| text = update->pluginName; | |||||
| plugin::Plugin* p = plugin::getPlugin(update->pluginSlug); | |||||
| text = it->second.name; | |||||
| plugin::Plugin* p = plugin::getPlugin(slug); | |||||
| if (p) { | if (p) { | ||||
| rightText += "v" + p->version + " → "; | rightText += "v" + p->version + " → "; | ||||
| } | } | ||||
| rightText += "v" + update->version; | |||||
| rightText += "v" + it->second.version; | |||||
| } | } | ||||
| ui::Menu* createChildMenu() override { | ui::Menu* createChildMenu() override { | ||||
| if (update->changelogUrl != "") { | |||||
| ui::Menu* menu = new ui::Menu; | |||||
| auto it = library::updates.find(slug); | |||||
| if (it == library::updates.end()) | |||||
| return NULL; | |||||
| UrlItem* changelogUrl = new UrlItem; | |||||
| changelogUrl->text = "Changelog"; | |||||
| changelogUrl->url = update->changelogUrl; | |||||
| menu->addChild(changelogUrl); | |||||
| if (it->second.changelogUrl == "") | |||||
| return NULL; | |||||
| return menu; | |||||
| } | |||||
| return NULL; | |||||
| ui::Menu* menu = new ui::Menu; | |||||
| UrlItem* changelogUrl = new UrlItem; | |||||
| changelogUrl->text = "Changelog"; | |||||
| changelogUrl->url = it->second.changelogUrl; | |||||
| menu->addChild(changelogUrl); | |||||
| return menu; | |||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| disabled = library::isSyncing(); | disabled = library::isSyncing(); | ||||
| if (update->progress >= 1) { | |||||
| rightText = CHECKMARK_STRING; | |||||
| disabled = true; | |||||
| } | |||||
| else if (update->progress > 0) { | |||||
| rightText = string::f("%.0f%%", update->progress * 100.f); | |||||
| auto it = library::updates.find(slug); | |||||
| if (it != library::updates.end()) { | |||||
| if (it->second.progress >= 1) { | |||||
| rightText = CHECKMARK_STRING; | |||||
| disabled = true; | |||||
| } | |||||
| else if (it->second.progress > 0) { | |||||
| rightText = string::f("%.0f%%", it->second.progress * 100.f); | |||||
| } | |||||
| } | } | ||||
| MenuItem::step(); | MenuItem::step(); | ||||
| } | } | ||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| std::thread t([ = ] { | |||||
| library::syncUpdate(update); | |||||
| std::thread t([=] { | |||||
| library::syncUpdate(slug); | |||||
| }); | }); | ||||
| t.detach(); | t.detach(); | ||||
| e.consume(NULL); | |||||
| e.unconsume(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -761,20 +776,20 @@ struct LibraryMenu : ui::Menu { | |||||
| logOutItem->text = "Log out"; | logOutItem->text = "Log out"; | ||||
| addChild(logOutItem); | addChild(logOutItem); | ||||
| SyncItem* syncItem = new SyncItem; | |||||
| SyncUpdatesItem* syncItem = new SyncUpdatesItem; | |||||
| syncItem->text = "Update all"; | syncItem->text = "Update all"; | ||||
| addChild(syncItem); | addChild(syncItem); | ||||
| if (library::hasUpdates()) { | |||||
| if (!library::updates.empty()) { | |||||
| addChild(new ui::MenuSeparator); | addChild(new ui::MenuSeparator); | ||||
| ui::MenuLabel* updatesLabel = new ui::MenuLabel; | ui::MenuLabel* updatesLabel = new ui::MenuLabel; | ||||
| updatesLabel->text = "Updates"; | updatesLabel->text = "Updates"; | ||||
| addChild(updatesLabel); | addChild(updatesLabel); | ||||
| for (library::Update& update : library::updates) { | |||||
| PluginSyncItem* updateItem = new PluginSyncItem; | |||||
| updateItem->setUpdate(&update); | |||||
| for (auto& pair : library::updates) { | |||||
| SyncUpdateItem* updateItem = new SyncUpdateItem; | |||||
| updateItem->setUpdate(pair.first); | |||||
| addChild(updateItem); | addChild(updateItem); | ||||
| } | } | ||||
| } | } | ||||
| @@ -832,7 +847,6 @@ struct AppUpdateItem : ui::MenuItem { | |||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| system::openBrowser(library::appDownloadUrl); | system::openBrowser(library::appDownloadUrl); | ||||
| APP->window->close(); | APP->window->close(); | ||||
| e.consume(NULL); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -512,7 +512,7 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||||
| // // Sort by score | // // Sort by score | ||||
| // modelContainer->children.sort([&](Widget *w1, Widget *w2) { | // modelContainer->children.sort([&](Widget *w1, Widget *w2) { | ||||
| // // If score was not computed, scores[w] returns 0, but this doesn't matter because those widgets aren't visible. | // // If score was not computed, scores[w] returns 0, but this doesn't matter because those widgets aren't visible. | ||||
| // return getWithDefault(scores, w1, 0.f) > getWithDefault(scores, w2, 0.f); | |||||
| // return get(scores, w1, 0.f) > get(scores, w2, 0.f); | |||||
| // }); | // }); | ||||
| } | } | ||||
| @@ -90,9 +90,7 @@ void logIn(const std::string& email, const std::string& password) { | |||||
| loginStatus = "No response from server"; | loginStatus = "No response from server"; | ||||
| return; | return; | ||||
| } | } | ||||
| DEFER({ | |||||
| json_decref(resJ); | |||||
| }); | |||||
| DEFER({json_decref(resJ);}); | |||||
| json_t* errorJ = json_object_get(resJ, "error"); | json_t* errorJ = json_object_get(resJ, "error"); | ||||
| if (errorJ) { | if (errorJ) { | ||||
| @@ -137,9 +135,7 @@ void queryUpdates() { | |||||
| updateStatus = "Could not query updates"; | updateStatus = "Could not query updates"; | ||||
| return; | return; | ||||
| } | } | ||||
| DEFER({ | |||||
| json_decref(pluginsResJ); | |||||
| }); | |||||
| DEFER({json_decref(pluginsResJ);}); | |||||
| json_t* errorJ = json_object_get(pluginsResJ, "error"); | json_t* errorJ = json_object_get(pluginsResJ, "error"); | ||||
| if (errorJ) { | if (errorJ) { | ||||
| @@ -159,9 +155,7 @@ void queryUpdates() { | |||||
| updateStatus = "Could not query updates"; | updateStatus = "Could not query updates"; | ||||
| return; | return; | ||||
| } | } | ||||
| DEFER({ | |||||
| json_decref(manifestsResJ); | |||||
| }); | |||||
| DEFER({json_decref(manifestsResJ);}); | |||||
| json_t* manifestsJ = json_object_get(manifestsResJ, "manifests"); | json_t* manifestsJ = json_object_get(manifestsResJ, "manifests"); | ||||
| json_t* pluginsJ = json_object_get(pluginsResJ, "plugins"); | json_t* pluginsJ = json_object_get(pluginsResJ, "plugins"); | ||||
| @@ -171,31 +165,38 @@ void queryUpdates() { | |||||
| json_array_foreach(pluginsJ, pluginIndex, pluginJ) { | json_array_foreach(pluginsJ, pluginIndex, pluginJ) { | ||||
| Update update; | Update update; | ||||
| // Get plugin manifest | // Get plugin manifest | ||||
| update.pluginSlug = json_string_value(pluginJ); | |||||
| json_t* manifestJ = json_object_get(manifestsJ, update.pluginSlug.c_str()); | |||||
| std::string slug = json_string_value(pluginJ); | |||||
| json_t* manifestJ = json_object_get(manifestsJ, slug.c_str()); | |||||
| if (!manifestJ) { | if (!manifestJ) { | ||||
| WARN("VCV account has plugin %s but no manifest was found", update.pluginSlug.c_str()); | |||||
| WARN("VCV account has plugin %s but no manifest was found", slug.c_str()); | |||||
| continue; | continue; | ||||
| } | } | ||||
| // Get plugin name | // Get plugin name | ||||
| json_t* nameJ = json_object_get(manifestJ, "name"); | json_t* nameJ = json_object_get(manifestJ, "name"); | ||||
| if (nameJ) | if (nameJ) | ||||
| update.pluginName = json_string_value(nameJ); | |||||
| update.name = json_string_value(nameJ); | |||||
| // Get version | // Get version | ||||
| json_t* versionJ = json_object_get(manifestJ, "version"); | json_t* versionJ = json_object_get(manifestJ, "version"); | ||||
| if (!versionJ) { | if (!versionJ) { | ||||
| WARN("Plugin %s has no version in manifest", update.pluginSlug.c_str()); | |||||
| WARN("Plugin %s has no version in manifest", slug.c_str()); | |||||
| continue; | continue; | ||||
| } | } | ||||
| update.version = json_string_value(versionJ); | update.version = json_string_value(versionJ); | ||||
| // Check if update is needed | // Check if update is needed | ||||
| plugin::Plugin* p = plugin::getPlugin(update.pluginSlug); | |||||
| plugin::Plugin* p = plugin::getPlugin(slug); | |||||
| if (p && p->version == update.version) | if (p && p->version == update.version) | ||||
| continue; | continue; | ||||
| // Don't add update if it exists already | |||||
| auto it = updates.find(slug); | |||||
| if (it != updates.end()) { | |||||
| if (it->second.version == update.version) | |||||
| continue; | |||||
| } | |||||
| // Check status | // Check status | ||||
| json_t* statusJ = json_object_get(manifestJ, "status"); | json_t* statusJ = json_object_get(manifestJ, "status"); | ||||
| if (!statusJ) | if (!statusJ) | ||||
| @@ -210,7 +211,8 @@ void queryUpdates() { | |||||
| update.changelogUrl = json_string_value(changelogUrlJ); | update.changelogUrl = json_string_value(changelogUrlJ); | ||||
| } | } | ||||
| updates.push_back(update); | |||||
| // Add update to updates map | |||||
| updates[slug] = update; | |||||
| } | } | ||||
| @@ -223,9 +225,7 @@ void queryUpdates() { | |||||
| updateStatus = "Could not query updates"; | updateStatus = "Could not query updates"; | ||||
| return; | return; | ||||
| } | } | ||||
| DEFER({ | |||||
| json_decref(whitelistResJ); | |||||
| }); | |||||
| DEFER({json_decref(whitelistResJ);}); | |||||
| std::map<std::string, std::set<std::string>> moduleWhitelist; | std::map<std::string, std::set<std::string>> moduleWhitelist; | ||||
| json_t* pluginsJ = json_object_get(whitelistResJ, "plugins"); | json_t* pluginsJ = json_object_get(whitelistResJ, "plugins"); | ||||
| @@ -252,73 +252,73 @@ void queryUpdates() { | |||||
| bool hasUpdates() { | bool hasUpdates() { | ||||
| for (Update& update : updates) { | |||||
| if (update.progress < 1.f) | |||||
| for (auto& pair : updates) { | |||||
| if (pair.second.progress < 1.f) | |||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| static bool isSyncingUpdate = false; | |||||
| static bool isSyncingUpdates = false; | |||||
| void syncUpdate(const std::string& slug) { | |||||
| if (settings::token.empty()) | |||||
| return; | |||||
| auto it = updates.find(slug); | |||||
| if (it == updates.end()) | |||||
| return; | |||||
| Update& update = it->second; | |||||
| void syncUpdate(Update* update) { | |||||
| isSyncingUpdate = true; | |||||
| DEFER({ | |||||
| isSyncingUpdate = false; | |||||
| }); | |||||
| updatingSlug = slug; | |||||
| DEFER({updatingSlug = "";}); | |||||
| std::string downloadUrl = API_URL + "/download"; | std::string downloadUrl = API_URL + "/download"; | ||||
| downloadUrl += "?slug=" + network::encodeUrl(update->pluginSlug); | |||||
| downloadUrl += "&version=" + network::encodeUrl(update->version); | |||||
| downloadUrl += "?slug=" + network::encodeUrl(slug); | |||||
| downloadUrl += "&version=" + network::encodeUrl(update.version); | |||||
| downloadUrl += "&arch=" + network::encodeUrl(APP_ARCH); | downloadUrl += "&arch=" + network::encodeUrl(APP_ARCH); | ||||
| network::CookieMap cookies; | network::CookieMap cookies; | ||||
| cookies["token"] = settings::token; | cookies["token"] = settings::token; | ||||
| INFO("Downloading plugin %s %s %s", update->pluginSlug.c_str(), update->version.c_str(), APP_ARCH.c_str()); | |||||
| INFO("Downloading plugin %s v%s for %s", slug.c_str(), update.version.c_str(), APP_ARCH.c_str()); | |||||
| // Download zip | // Download zip | ||||
| std::string pluginDest = system::join(asset::pluginsPath, update->pluginSlug + ".zip"); | |||||
| if (!network::requestDownload(downloadUrl, pluginDest, &update->progress, cookies)) { | |||||
| WARN("Plugin %s download was unsuccessful", update->pluginSlug.c_str()); | |||||
| std::string pluginDest = system::join(asset::pluginsPath, slug + ".zip"); | |||||
| if (!network::requestDownload(downloadUrl, pluginDest, &update.progress, cookies)) { | |||||
| WARN("Plugin %s download was unsuccessful", slug.c_str()); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| void syncUpdates() { | void syncUpdates() { | ||||
| isSyncingUpdates = true; | |||||
| DEFER({ | |||||
| isSyncingUpdates = false; | |||||
| }); | |||||
| if (settings::token.empty()) | if (settings::token.empty()) | ||||
| return; | return; | ||||
| for (Update& update : updates) { | |||||
| if (update.progress < 1.f) | |||||
| syncUpdate(&update); | |||||
| // Iterate by value because the map might change | |||||
| for (auto pair : updates) { | |||||
| syncUpdate(pair.first); | |||||
| } | } | ||||
| restartRequested = true; | restartRequested = true; | ||||
| } | } | ||||
| bool isSyncing() { | bool isSyncing() { | ||||
| return isSyncingUpdate || isSyncingUpdates; | |||||
| return updatingPlugins || (updatingSlug != ""); | |||||
| } | } | ||||
| std::string appVersion; | |||||
| std::string appDownloadUrl; | |||||
| std::string appChangelogUrl; | |||||
| std::string loginStatus; | std::string loginStatus; | ||||
| std::vector<Update> updates; | |||||
| std::map<std::string, Update> updates; | |||||
| std::string updateStatus; | std::string updateStatus; | ||||
| std::string updatingSlug; | |||||
| bool updatingPlugins = false; | |||||
| bool restartRequested = false; | bool restartRequested = false; | ||||
| std::string appVersion; | |||||
| std::string appDownloadUrl; | |||||
| std::string appChangelogUrl; | |||||
| } // namespace library | } // namespace library | ||||
| @@ -65,7 +65,7 @@ json_t* requestJson(Method method, const std::string& url, json_t* dataJ, const | |||||
| // Process data | // Process data | ||||
| if (dataJ) { | if (dataJ) { | ||||
| if (method == METHOD_GET) { | if (method == METHOD_GET) { | ||||
| // Append ?key=value&... to url | |||||
| // Append ?key1=value1&key2=value2&... to url | |||||
| urlS += "?"; | urlS += "?"; | ||||
| bool isFirst = true; | bool isFirst = true; | ||||
| const char* key; | const char* key; | ||||
| @@ -131,17 +131,17 @@ json_t* requestJson(Method method, const std::string& url, json_t* dataJ, const | |||||
| // Cleanup | // Cleanup | ||||
| if (reqStr) | if (reqStr) | ||||
| free(reqStr); | |||||
| std::free(reqStr); | |||||
| curl_easy_cleanup(curl); | curl_easy_cleanup(curl); | ||||
| curl_slist_free_all(headers); | curl_slist_free_all(headers); | ||||
| if (res == CURLE_OK) { | |||||
| // Parse JSON response | |||||
| json_error_t error; | |||||
| json_t* rootJ = json_loads(resText.c_str(), 0, &error); | |||||
| return rootJ; | |||||
| } | |||||
| return NULL; | |||||
| if (res != CURLE_OK) | |||||
| return NULL; | |||||
| // Parse JSON response | |||||
| json_error_t error; | |||||
| json_t* rootJ = json_loads(resText.c_str(), 0, &error); | |||||
| return rootJ; | |||||
| } | } | ||||
| @@ -156,6 +156,7 @@ static int xferInfoCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| bool requestDownload(const std::string& url, const std::string& filename, float* progress, const CookieMap& cookies) { | bool requestDownload(const std::string& url, const std::string& filename, float* progress, const CookieMap& cookies) { | ||||
| CURL* curl = createCurl(); | CURL* curl = createCurl(); | ||||
| @@ -189,21 +190,20 @@ bool requestDownload(const std::string& url, const std::string& filename, float* | |||||
| return res == CURLE_OK; | return res == CURLE_OK; | ||||
| } | } | ||||
| std::string encodeUrl(const std::string& s) { | std::string encodeUrl(const std::string& s) { | ||||
| CURL* curl = curl_easy_init(); | CURL* curl = curl_easy_init(); | ||||
| DEFER({curl_easy_cleanup(curl);}); | |||||
| assert(curl); | assert(curl); | ||||
| char* escaped = curl_easy_escape(curl, s.c_str(), s.size()); | char* escaped = curl_easy_escape(curl, s.c_str(), s.size()); | ||||
| std::string ret = escaped; | |||||
| curl_free(escaped); | |||||
| curl_easy_cleanup(curl); | |||||
| return ret; | |||||
| DEFER({curl_free(escaped);}); | |||||
| return std::string(escaped); | |||||
| } | } | ||||
| std::string urlPath(const std::string& url) { | std::string urlPath(const std::string& url) { | ||||
| CURLU* curl = curl_url(); | CURLU* curl = curl_url(); | ||||
| DEFER({ | |||||
| curl_url_cleanup(curl); | |||||
| }); | |||||
| DEFER({curl_url_cleanup(curl);}); | |||||
| if (curl_url_set(curl, CURLUPART_URL, url.c_str(), 0)) | if (curl_url_set(curl, CURLUPART_URL, url.c_str(), 0)) | ||||
| return ""; | return ""; | ||||
| char* buf; | char* buf; | ||||
| @@ -251,7 +251,7 @@ struct RtMidiDriver : midi::Driver { | |||||
| midi::InputDevice* subscribeInput(int deviceId, midi::Input* input) override { | midi::InputDevice* subscribeInput(int deviceId, midi::Input* input) override { | ||||
| if (!(0 <= deviceId && deviceId < (int) rtMidiIn->getPortCount())) | if (!(0 <= deviceId && deviceId < (int) rtMidiIn->getPortCount())) | ||||
| return NULL; | return NULL; | ||||
| RtMidiInputDevice* device = getWithDefault(inputDevices, deviceId, NULL); | |||||
| RtMidiInputDevice* device = get(inputDevices, deviceId, NULL); | |||||
| if (!device) { | if (!device) { | ||||
| try { | try { | ||||
| inputDevices[deviceId] = device = new RtMidiInputDevice(driverId, deviceId); | inputDevices[deviceId] = device = new RtMidiInputDevice(driverId, deviceId); | ||||
| @@ -314,7 +314,7 @@ struct RtMidiDriver : midi::Driver { | |||||
| midi::OutputDevice* subscribeOutput(int deviceId, midi::Output* output) override { | midi::OutputDevice* subscribeOutput(int deviceId, midi::Output* output) override { | ||||
| if (!(0 <= deviceId && deviceId < (int) rtMidiOut->getPortCount())) | if (!(0 <= deviceId && deviceId < (int) rtMidiOut->getPortCount())) | ||||
| return NULL; | return NULL; | ||||
| RtMidiOutputDevice* device = getWithDefault(outputDevices, deviceId, NULL); | |||||
| RtMidiOutputDevice* device = get(outputDevices, deviceId, NULL); | |||||
| if (!device) { | if (!device) { | ||||
| try { | try { | ||||
| outputDevices[deviceId] = device = new RtMidiOutputDevice(driverId, deviceId); | outputDevices[deviceId] = device = new RtMidiOutputDevice(driverId, deviceId); | ||||