diff --git a/include/settings.hpp b/include/settings.hpp index d57eea3d..2b7c1a9b 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -96,8 +96,16 @@ extern std::map> moduleInfos; */ ModuleInfo* getModuleInfo(const std::string& pluginSlug, const std::string& moduleSlug); -/** pluginSlug -> {moduleSlug} */ -extern std::map> moduleWhitelist; +/** The VCV JSON API returns the data structure +{pluginSlug: [moduleSlugs] or true} +where "true" represents that the user is subscribed to the plugin (all modules and future modules). +C++ isn't weakly typed, so we need the PluginWhitelist data structure to store this information. +*/ +struct PluginWhitelist { + bool subscribed = false; + std::set moduleSlugs; +}; +extern std::map moduleWhitelist; bool isModuleWhitelisted(const std::string& pluginSlug, const std::string& moduleSlug); diff --git a/src/library.cpp b/src/library.cpp index 117fd738..0eeea251 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -187,40 +187,33 @@ void checkUpdates() { } DEFER({json_decref(manifestsResJ);}); - // Get user's plugins list - std::string pluginsUrl = API_URL + "/plugins"; - json_t* pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, NULL, getTokenCookies()); - if (!pluginsResJ) { - WARN("Request for user's plugins failed"); - updateStatus = "Could not query user's plugins"; - return; - } - DEFER({json_decref(pluginsResJ);}); - - json_t* errorJ = json_object_get(pluginsResJ, "error"); - if (errorJ) { - WARN("Request for user's plugins returned an error: %s", json_string_value(errorJ)); - updateStatus = "Could not query plugins"; + // Get user's modules + std::string modulesUrl = API_URL + "/modules"; + json_t* modulesResJ = network::requestJson(network::METHOD_GET, modulesUrl, NULL, getTokenCookies()); + if (!modulesResJ) { + WARN("Request for user's modules failed"); + updateStatus = "Could not query user's modules"; return; } + DEFER({json_decref(modulesResJ);}); json_t* manifestsJ = json_object_get(manifestsResJ, "manifests"); - json_t* pluginsJ = json_object_get(pluginsResJ, "plugins"); + json_t* pluginsJ = json_object_get(modulesResJ, "modules"); - size_t pluginIndex; - json_t* pluginJ; - json_array_foreach(pluginsJ, pluginIndex, pluginJ) { + const char* modulesKey; + json_t* modulesJ; + json_object_foreach(pluginsJ, modulesKey, modulesJ) { + std::string pluginSlug = modulesKey; // Get plugin manifest - std::string slug = json_string_value(pluginJ); - json_t* manifestJ = json_object_get(manifestsJ, slug.c_str()); + json_t* manifestJ = json_object_get(manifestsJ, pluginSlug.c_str()); if (!manifestJ) { - WARN("VCV account has plugin %s but no manifest was found", slug.c_str()); + WARN("VCV account has plugin %s but no manifest was found", pluginSlug.c_str()); continue; } // Don't replace existing UpdateInfo, even if version is newer. // This keeps things sane and ensures that only one version of each plugin is downloaded to `plugins/` at a time. - auto it = updateInfos.find(slug); + auto it = updateInfos.find(pluginSlug); if (it != updateInfos.end()) { continue; } @@ -234,7 +227,7 @@ void checkUpdates() { // Get version json_t* versionJ = json_object_get(manifestJ, "version"); if (!versionJ) { - // WARN("Plugin %s has no version in manifest", slug.c_str()); + // WARN("Plugin %s has no version in manifest", pluginSlug.c_str()); continue; } update.version = json_string_value(versionJ); @@ -244,7 +237,7 @@ void checkUpdates() { } // Check if update is needed - plugin::Plugin* p = plugin::getPlugin(slug); + plugin::Plugin* p = plugin::getPlugin(pluginSlug); if (p && p->version == update.version) continue; @@ -259,41 +252,39 @@ void checkUpdates() { update.changelogUrl = json_string_value(changelogUrlJ); // Add update to updates map - updateInfos[slug] = update; + updateInfos[pluginSlug] = update; } - // Get module whitelist + // Merge module whitelist { - std::string whitelistUrl = API_URL + "/modules"; - json_t* whitelistResJ = network::requestJson(network::METHOD_GET, whitelistUrl, NULL, getTokenCookies()); - if (!whitelistResJ) { - WARN("Request for module whitelist failed"); - updateStatus = "Could not query user's modules"; - return; - } - DEFER({json_decref(whitelistResJ);}); - // Clone plugin slugs from settings to temporary whitelist. - // This makes plugins entirely hidden if removed. - std::map> moduleWhitelist; + // This makes existing plugins entirely hidden if removed from user's VCV account. + std::map moduleWhitelist; for (const auto& pluginPair : settings::moduleWhitelist) { - // Create an empty set std::string pluginSlug = pluginPair.first; - moduleWhitelist[pluginSlug]; + moduleWhitelist[pluginSlug] = settings::PluginWhitelist(); } // Iterate plugins - json_t* pluginsJ = json_object_get(whitelistResJ, "plugins"); - const char* pluginSlug; + const char* modulesKey; json_t* modulesJ; - json_object_foreach(pluginsJ, pluginSlug, modulesJ) { + json_object_foreach(pluginsJ, modulesKey, modulesJ) { + std::string pluginSlug = modulesKey; + settings::PluginWhitelist& pw = moduleWhitelist[pluginSlug]; + + // If value is "true", plugin is subscribed + if (json_is_true(modulesJ)) { + pw.subscribed = true; + continue; + } + // Iterate modules in plugin size_t moduleIndex; json_t* moduleSlugJ; json_array_foreach(modulesJ, moduleIndex, moduleSlugJ) { std::string moduleSlug = json_string_value(moduleSlugJ); // Insert module in whitelist - moduleWhitelist[pluginSlug].insert(moduleSlug); + pw.moduleSlugs.insert(moduleSlug); } } diff --git a/src/settings.cpp b/src/settings.cpp index efd1cd1c..073c784c 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -62,7 +62,7 @@ bool discordUpdateActivity = true; BrowserSort browserSort = BROWSER_SORT_UPDATED; float browserZoom = -1.f; std::map> moduleInfos; -std::map> moduleWhitelist; +std::map moduleWhitelist; ModuleInfo* getModuleInfo(const std::string& pluginSlug, const std::string& moduleSlug) { @@ -81,8 +81,13 @@ bool isModuleWhitelisted(const std::string& pluginSlug, const std::string& modul // All modules in a plugin are visible if plugin set is empty. if (pluginIt == moduleWhitelist.end()) return true; - auto moduleIt = pluginIt->second.find(moduleSlug); - if (moduleIt == pluginIt->second.end()) + // All modules in a plugin are visible if plugin set is subscribed. + const PluginWhitelist& plugin = pluginIt->second; + if (plugin.subscribed) + return true; + // Check if plugin whitelist contains module + auto moduleIt = plugin.moduleSlugs.find(moduleSlug); + if (moduleIt == plugin.moduleSlugs.end()) return false; return true; } @@ -204,10 +209,20 @@ json_t* toJson() { // moduleWhitelist json_t* moduleWhitelistJ = json_object(); for (const auto& pluginPair : moduleWhitelist) { - json_t* pluginJ = json_array(); - for (const std::string& moduleSlug : pluginPair.second) { - json_array_append_new(pluginJ, json_stringn(moduleSlug.c_str(), moduleSlug.size())); + const PluginWhitelist& plugin = pluginPair.second; + json_t* pluginJ; + + // If plugin is subscribed, set to true, otherwise an array of module slugs. + if (plugin.subscribed) { + pluginJ = json_true(); } + else { + pluginJ = json_array(); + for (const std::string& moduleSlug : plugin.moduleSlugs) { + json_array_append_new(pluginJ, json_stringn(moduleSlug.c_str(), moduleSlug.size())); + } + } + json_object_set_new(moduleWhitelistJ, pluginPair.first.c_str(), pluginJ); } json_object_set_new(rootJ, "moduleWhitelist", moduleWhitelistJ); @@ -398,15 +413,18 @@ void fromJson(json_t* rootJ) { const char* pluginSlug; json_t* pluginJ; json_object_foreach(moduleWhitelistJ, pluginSlug, pluginJ) { - // Create an empty modules set, even if there are no modules in the whitelist. - // An empty set means no modules will be displayed in the Browser. - auto& modules = moduleWhitelist[pluginSlug]; + auto& plugin = moduleWhitelist[pluginSlug]; + + if (json_is_true(pluginJ)) { + plugin.subscribed = true; + continue; + } size_t moduleIndex; json_t* moduleJ; json_array_foreach(pluginJ, moduleIndex, moduleJ) { std::string moduleSlug = json_string_value(moduleJ); - modules.insert(moduleSlug); + plugin.moduleSlugs.insert(moduleSlug); } } }