@@ -0,0 +1,51 @@ | |||
#pragma once | |||
#include <common.hpp> | |||
#include <vector> | |||
namespace rack { | |||
/** Synchronizes plugins with the VCV Library and updates Rack itself | |||
*/ | |||
namespace library { | |||
struct Update { | |||
std::string pluginSlug; | |||
std::string pluginName; | |||
std::string version; | |||
std::string changelogUrl; | |||
float progress = 0.f; | |||
}; | |||
extern std::string version; | |||
extern std::string changelogUrl; | |||
extern float progress; | |||
void init(); | |||
bool isLoggedIn(); | |||
void logIn(const std::string& email, const std::string& password); | |||
void logOut(); | |||
bool isUpdateAvailable(); | |||
void queryUpdates(); | |||
bool hasUpdates(); | |||
void syncUpdate(Update* update); | |||
void syncUpdates(); | |||
bool isSyncing(); | |||
/** Updates Rack automatically or opens the browser to the URL. | |||
Blocking. Call on a detached thread. | |||
*/ | |||
void update(); | |||
extern std::string loginStatus; | |||
extern std::vector<Update> updates; | |||
extern std::string updateStatus; | |||
extern bool restartRequested; | |||
} // namespace library | |||
} // namespace rack |
@@ -9,30 +9,13 @@ | |||
namespace rack { | |||
/** Plugin loader and VCV Library synchronizer | |||
/** Loads and manages plugins | |||
*/ | |||
namespace plugin { | |||
struct Update { | |||
std::string pluginSlug; | |||
std::string pluginName; | |||
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(); | |||
bool isLoggedIn(); | |||
void queryUpdates(); | |||
bool hasUpdates(); | |||
void syncUpdate(Update* update); | |||
void syncUpdates(); | |||
bool isSyncing(); | |||
Plugin* getPlugin(const std::string& pluginSlug); | |||
Model* getModel(const std::string& pluginSlug, const std::string& modelSlug); | |||
/** Creates a Model from a JSON module object. | |||
@@ -47,11 +30,6 @@ std::string normalizeSlug(const std::string& slug); | |||
extern std::vector<Plugin*> plugins; | |||
extern std::string loginStatus; | |||
extern std::vector<Update> updates; | |||
extern std::string updateStatus; | |||
extern bool restartRequested; | |||
} // namespace plugin | |||
} // namespace rack |
@@ -1,26 +0,0 @@ | |||
#pragma once | |||
#include <common.hpp> | |||
namespace rack { | |||
/** Automatically updates the application. | |||
*/ | |||
namespace updater { | |||
extern std::string version; | |||
extern std::string changelogUrl; | |||
extern float progress; | |||
void init(); | |||
bool isUpdateAvailable(); | |||
/** Updates Rack automatically or opens the browser to the URL. | |||
Blocking. Call on a detached thread. | |||
*/ | |||
void update(); | |||
} // namespace updater | |||
} // namespace rack |
@@ -21,7 +21,7 @@ | |||
#include <system.hpp> | |||
#include <plugin.hpp> | |||
#include <patch.hpp> | |||
#include <updater.hpp> | |||
#include <library.hpp> | |||
namespace rack { | |||
@@ -608,7 +608,7 @@ struct LogInItem : ui::MenuItem { | |||
std::string email = emailField->text; | |||
std::string password = passwordField->text; | |||
std::thread t([ = ] { | |||
plugin::logIn(email, password); | |||
library::logIn(email, password); | |||
isLoggingIn = false; | |||
}); | |||
t.detach(); | |||
@@ -618,7 +618,7 @@ struct LogInItem : ui::MenuItem { | |||
void step() override { | |||
disabled = isLoggingIn; | |||
text = "Log in"; | |||
rightText = plugin::loginStatus; | |||
rightText = library::loginStatus; | |||
MenuItem::step(); | |||
} | |||
}; | |||
@@ -626,13 +626,13 @@ struct LogInItem : ui::MenuItem { | |||
struct SyncItem : ui::MenuItem { | |||
void step() override { | |||
disabled = true; | |||
if (plugin::updateStatus != "") { | |||
text = plugin::updateStatus; | |||
if (library::updateStatus != "") { | |||
text = library::updateStatus; | |||
} | |||
else if (plugin::isSyncing()) { | |||
else if (library::isSyncing()) { | |||
text = "Updating..."; | |||
} | |||
else if (!plugin::hasUpdates()) { | |||
else if (!library::hasUpdates()) { | |||
text = "Up-to-date"; | |||
} | |||
else { | |||
@@ -644,7 +644,7 @@ struct SyncItem : ui::MenuItem { | |||
void onAction(const event::Action& e) override { | |||
std::thread t([ = ] { | |||
plugin::syncUpdates(); | |||
library::syncUpdates(); | |||
}); | |||
t.detach(); | |||
e.consume(NULL); | |||
@@ -652,9 +652,9 @@ struct SyncItem : ui::MenuItem { | |||
}; | |||
struct PluginSyncItem : ui::MenuItem { | |||
plugin::Update* update; | |||
library::Update* update; | |||
void setUpdate(plugin::Update* update) { | |||
void setUpdate(library::Update* update) { | |||
this->update = update; | |||
text = update->pluginName; | |||
plugin::Plugin* p = plugin::getPlugin(update->pluginSlug); | |||
@@ -679,7 +679,7 @@ struct PluginSyncItem : ui::MenuItem { | |||
} | |||
void step() override { | |||
disabled = plugin::isSyncing(); | |||
disabled = library::isSyncing(); | |||
if (update->progress >= 1) { | |||
rightText = CHECKMARK_STRING; | |||
disabled = true; | |||
@@ -692,7 +692,7 @@ struct PluginSyncItem : ui::MenuItem { | |||
void onAction(const event::Action& e) override { | |||
std::thread t([ = ] { | |||
plugin::syncUpdate(update); | |||
library::syncUpdate(update); | |||
}); | |||
t.detach(); | |||
e.consume(NULL); | |||
@@ -701,7 +701,7 @@ struct PluginSyncItem : ui::MenuItem { | |||
struct LogOutItem : ui::MenuItem { | |||
void onAction(const event::Action& e) override { | |||
plugin::logOut(); | |||
library::logOut(); | |||
} | |||
}; | |||
@@ -714,7 +714,7 @@ struct LibraryMenu : ui::Menu { | |||
void step() override { | |||
// Refresh menu when appropriate | |||
if (!loggedIn && plugin::isLoggedIn()) | |||
if (!loggedIn && library::isLoggedIn()) | |||
refresh(); | |||
Menu::step(); | |||
} | |||
@@ -726,7 +726,7 @@ struct LibraryMenu : ui::Menu { | |||
if (settings::devMode) { | |||
addChild(createMenuLabel("Disabled in development mode")); | |||
} | |||
else if (!plugin::isLoggedIn()) { | |||
else if (!library::isLoggedIn()) { | |||
UrlItem* registerItem = new UrlItem; | |||
registerItem->text = "Register VCV account"; | |||
registerItem->url = "https://vcvrack.com/"; | |||
@@ -765,14 +765,14 @@ struct LibraryMenu : ui::Menu { | |||
syncItem->text = "Update all"; | |||
addChild(syncItem); | |||
if (plugin::hasUpdates()) { | |||
if (library::hasUpdates()) { | |||
addChild(new ui::MenuSeparator); | |||
ui::MenuLabel* updatesLabel = new ui::MenuLabel; | |||
updatesLabel->text = "Updates"; | |||
addChild(updatesLabel); | |||
for (plugin::Update& update : plugin::updates) { | |||
for (library::Update& update : library::updates) { | |||
PluginSyncItem* updateItem = new PluginSyncItem; | |||
updateItem->setUpdate(&update); | |||
addChild(updateItem); | |||
@@ -799,11 +799,11 @@ struct LibraryButton : MenuButton { | |||
void step() override { | |||
notification->box.pos = math::Vec(0, 0); | |||
notification->visible = plugin::hasUpdates(); | |||
notification->visible = library::hasUpdates(); | |||
// Popup when updates finish downloading | |||
if (plugin::restartRequested) { | |||
plugin::restartRequested = false; | |||
if (library::restartRequested) { | |||
library::restartRequested = false; | |||
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been downloaded. Close and re-launch Rack to load new updates.")) { | |||
APP->window->close(); | |||
} | |||
@@ -823,22 +823,22 @@ struct UpdateItem : ui::MenuItem { | |||
UrlItem* changelogUrl = new UrlItem; | |||
changelogUrl->text = "Changelog"; | |||
changelogUrl->url = updater::changelogUrl; | |||
changelogUrl->url = library::changelogUrl; | |||
menu->addChild(changelogUrl); | |||
return menu; | |||
} | |||
void step() override { | |||
if (updater::progress > 0) { | |||
rightText = string::f("%.0f%%", updater::progress * 100.f); | |||
if (library::progress > 0) { | |||
rightText = string::f("%.0f%%", library::progress * 100.f); | |||
} | |||
MenuItem::step(); | |||
} | |||
void onAction(const event::Action& e) override { | |||
std::thread t([ = ] { | |||
updater::update(); | |||
library::update(); | |||
}); | |||
t.detach(); | |||
e.consume(NULL); | |||
@@ -859,10 +859,10 @@ struct HelpButton : MenuButton { | |||
menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); | |||
menu->box.size.x = box.size.x; | |||
if (updater::isUpdateAvailable()) { | |||
if (library::isUpdateAvailable()) { | |||
UpdateItem* updateItem = new UpdateItem; | |||
updateItem->text = "Update " + APP_NAME; | |||
updateItem->rightText = APP_VERSION + " → " + updater::version; | |||
updateItem->rightText = APP_VERSION + " → " + library::version; | |||
menu->addChild(updateItem); | |||
} | |||
@@ -885,7 +885,7 @@ struct HelpButton : MenuButton { | |||
void step() override { | |||
notification->box.pos = math::Vec(0, 0); | |||
notification->visible = updater::isUpdateAvailable(); | |||
notification->visible = library::isUpdateAvailable(); | |||
MenuButton::step(); | |||
} | |||
}; | |||
@@ -0,0 +1,359 @@ | |||
#include <thread> | |||
#include <library.hpp> | |||
#include <settings.hpp> | |||
#include <app/common.hpp> | |||
#include <network.hpp> | |||
#include <system.hpp> | |||
#include <context.hpp> | |||
#include <window.hpp> | |||
#include <asset.hpp> | |||
#include <settings.hpp> | |||
#include <plugin.hpp> | |||
namespace rack { | |||
namespace library { | |||
std::string version; | |||
std::string changelogUrl; | |||
float progress = 0.f; | |||
static std::string downloadUrl; | |||
static void checkVersion() { | |||
std::string versionUrl = API_URL + "/version"; | |||
json_t* resJ = network::requestJson(network::METHOD_GET, versionUrl, NULL); | |||
if (!resJ) { | |||
WARN("Request for version failed"); | |||
return; | |||
} | |||
DEFER({json_decref(resJ);}); | |||
json_t* versionJ = json_object_get(resJ, "version"); | |||
if (versionJ) | |||
version = json_string_value(versionJ); | |||
json_t* changelogUrlJ = json_object_get(resJ, "changelogUrl"); | |||
if (changelogUrlJ) | |||
changelogUrl = json_string_value(changelogUrlJ); | |||
json_t* downloadUrlsJ = json_object_get(resJ, "downloadUrls"); | |||
if (downloadUrlsJ) { | |||
json_t* downloadUrlJ = json_object_get(downloadUrlsJ, APP_ARCH.c_str()); | |||
if (downloadUrlJ) | |||
downloadUrl = json_string_value(downloadUrlJ); | |||
} | |||
} | |||
void init() { | |||
if (settings::devMode) | |||
return; | |||
std::thread t([]() { | |||
checkVersion(); | |||
}); | |||
t.detach(); | |||
// Sync in a detached thread | |||
if (!settings::devMode) { | |||
std::thread t([] { | |||
queryUpdates(); | |||
}); | |||
t.detach(); | |||
} | |||
} | |||
bool isLoggedIn() { | |||
return settings::token != ""; | |||
} | |||
void logIn(const std::string& email, const std::string& password) { | |||
loginStatus = "Logging in..."; | |||
json_t* reqJ = json_object(); | |||
json_object_set(reqJ, "email", json_string(email.c_str())); | |||
json_object_set(reqJ, "password", json_string(password.c_str())); | |||
std::string url = API_URL + "/token"; | |||
json_t* resJ = network::requestJson(network::METHOD_POST, url, reqJ); | |||
json_decref(reqJ); | |||
if (!resJ) { | |||
loginStatus = "No response from server"; | |||
return; | |||
} | |||
DEFER({ | |||
json_decref(resJ); | |||
}); | |||
json_t* errorJ = json_object_get(resJ, "error"); | |||
if (errorJ) { | |||
const char* errorStr = json_string_value(errorJ); | |||
loginStatus = errorStr; | |||
return; | |||
} | |||
json_t* tokenJ = json_object_get(resJ, "token"); | |||
if (!tokenJ) { | |||
loginStatus = "No token in response"; | |||
return; | |||
} | |||
const char* tokenStr = json_string_value(tokenJ); | |||
settings::token = tokenStr; | |||
loginStatus = ""; | |||
queryUpdates(); | |||
} | |||
void logOut() { | |||
settings::token = ""; | |||
updates.clear(); | |||
} | |||
void update() { | |||
if (downloadUrl == "") | |||
return; | |||
// Download update | |||
// HACK getFilename is only supposed to be used for filesystem paths, not URLs. | |||
std::string filename = system::getFilename(network::urlPath(downloadUrl)); | |||
std::string path = asset::user(filename); | |||
INFO("Downloading update %s to %s", downloadUrl.c_str(), path.c_str()); | |||
network::requestDownload(downloadUrl, path, &progress); | |||
#if defined ARCH_WIN | |||
// Launch the installer | |||
INFO("Launching update %s", path.c_str()); | |||
system::runProcessDetached(path); | |||
#elif defined ARCH_MAC | |||
std::string cmd; | |||
// std::string appPath = system::join(asset::userDir, "Rack.app"); | |||
// cmd = "rm -rf '" + appPath + "'"; | |||
// std::system(cmd.c_str()); | |||
// // Unzip app using Apple's unzipper, since Rack's unzipper doesn't handle the metadata stuff correctly. | |||
// cmd = "unzip -q '" + path + "' -d '" + asset::userDir + "'"; | |||
// std::system(cmd.c_str()); | |||
// // Open app in Finder | |||
// cmd = "open -R '" + appPath + "'"; | |||
// std::system(cmd.c_str()); | |||
// Open Archive Utility | |||
cmd = "open '" + path + "'"; | |||
std::system(cmd.c_str()); | |||
#elif defined ARCH_LIN | |||
system::openFolder(asset::user("")); | |||
#endif | |||
APP->window->close(); | |||
} | |||
bool isUpdateAvailable() { | |||
return (version != "") && (version != APP_VERSION); | |||
} | |||
void queryUpdates() { | |||
if (settings::token.empty()) | |||
return; | |||
updates.clear(); | |||
updateStatus = "Querying for updates..."; | |||
// Get user's plugins list | |||
std::string pluginsUrl = API_URL + "/plugins"; | |||
network::CookieMap cookies; | |||
cookies["token"] = settings::token; | |||
json_t* pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, NULL, cookies); | |||
if (!pluginsResJ) { | |||
WARN("Request for user's plugins failed"); | |||
updateStatus = "Could not query updates"; | |||
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 updates"; | |||
return; | |||
} | |||
// Get library manifests | |||
std::string manifestsUrl = API_URL + "/library/manifests"; | |||
json_t* manifestsReq = json_object(); | |||
json_object_set(manifestsReq, "version", json_string(API_VERSION.c_str())); | |||
json_t* manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, manifestsReq); | |||
json_decref(manifestsReq); | |||
if (!manifestsResJ) { | |||
WARN("Request for library manifests failed"); | |||
updateStatus = "Could not query updates"; | |||
return; | |||
} | |||
DEFER({ | |||
json_decref(manifestsResJ); | |||
}); | |||
json_t* manifestsJ = json_object_get(manifestsResJ, "manifests"); | |||
json_t* pluginsJ = json_object_get(pluginsResJ, "plugins"); | |||
size_t pluginIndex; | |||
json_t* pluginJ; | |||
json_array_foreach(pluginsJ, pluginIndex, pluginJ) { | |||
Update update; | |||
// Get plugin manifest | |||
update.pluginSlug = json_string_value(pluginJ); | |||
json_t* manifestJ = json_object_get(manifestsJ, update.pluginSlug.c_str()); | |||
if (!manifestJ) { | |||
WARN("VCV account has plugin %s but no manifest was found", update.pluginSlug.c_str()); | |||
continue; | |||
} | |||
// Get plugin name | |||
json_t* nameJ = json_object_get(manifestJ, "name"); | |||
if (nameJ) | |||
update.pluginName = json_string_value(nameJ); | |||
// Get version | |||
json_t* versionJ = json_object_get(manifestJ, "version"); | |||
if (!versionJ) { | |||
WARN("Plugin %s has no version in manifest", update.pluginSlug.c_str()); | |||
continue; | |||
} | |||
update.version = json_string_value(versionJ); | |||
// Check if update is needed | |||
plugin::Plugin* p = plugin::getPlugin(update.pluginSlug); | |||
if (p && p->version == update.version) | |||
continue; | |||
// Check status | |||
json_t* statusJ = json_object_get(manifestJ, "status"); | |||
if (!statusJ) | |||
continue; | |||
std::string status = json_string_value(statusJ); | |||
if (status != "available") | |||
continue; | |||
// Get changelog URL | |||
json_t* changelogUrlJ = json_object_get(manifestJ, "changelogUrl"); | |||
if (changelogUrlJ) { | |||
update.changelogUrl = json_string_value(changelogUrlJ); | |||
} | |||
updates.push_back(update); | |||
} | |||
// Get module whitelist | |||
{ | |||
std::string whitelistUrl = API_URL + "/moduleWhitelist"; | |||
json_t* whitelistResJ = network::requestJson(network::METHOD_GET, whitelistUrl, NULL, cookies); | |||
if (!whitelistResJ) { | |||
WARN("Request for module whitelist failed"); | |||
updateStatus = "Could not query updates"; | |||
return; | |||
} | |||
DEFER({ | |||
json_decref(whitelistResJ); | |||
}); | |||
std::map<std::string, std::set<std::string>> moduleWhitelist; | |||
json_t* pluginsJ = json_object_get(whitelistResJ, "plugins"); | |||
// Iterate plugins | |||
const char* pluginSlug; | |||
json_t* modulesJ; | |||
json_object_foreach(pluginsJ, pluginSlug, modulesJ) { | |||
// 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); | |||
} | |||
} | |||
settings::moduleWhitelist = moduleWhitelist; | |||
} | |||
updateStatus = ""; | |||
} | |||
bool hasUpdates() { | |||
for (Update& update : updates) { | |||
if (update.progress < 1.f) | |||
return true; | |||
} | |||
return false; | |||
} | |||
static bool isSyncingUpdate = false; | |||
static bool isSyncingUpdates = false; | |||
void syncUpdate(Update* update) { | |||
isSyncingUpdate = true; | |||
DEFER({ | |||
isSyncingUpdate = false; | |||
}); | |||
std::string downloadUrl = API_URL + "/download"; | |||
downloadUrl += "?slug=" + network::encodeUrl(update->pluginSlug); | |||
downloadUrl += "&version=" + network::encodeUrl(update->version); | |||
downloadUrl += "&arch=" + network::encodeUrl(APP_ARCH); | |||
network::CookieMap cookies; | |||
cookies["token"] = settings::token; | |||
INFO("Downloading plugin %s %s %s", update->pluginSlug.c_str(), update->version.c_str(), APP_ARCH.c_str()); | |||
// 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()); | |||
return; | |||
} | |||
} | |||
void syncUpdates() { | |||
isSyncingUpdates = true; | |||
DEFER({ | |||
isSyncingUpdates = false; | |||
}); | |||
if (settings::token.empty()) | |||
return; | |||
for (Update& update : updates) { | |||
if (update.progress < 1.f) | |||
syncUpdate(&update); | |||
} | |||
restartRequested = true; | |||
} | |||
bool isSyncing() { | |||
return isSyncingUpdate || isSyncingUpdates; | |||
} | |||
std::string loginStatus; | |||
std::vector<Update> updates; | |||
std::string updateStatus; | |||
bool restartRequested = false; | |||
} // namespace library | |||
} // namespace rack |
@@ -11,7 +11,7 @@ | |||
#include <windows.h> | |||
#include <direct.h> | |||
#else | |||
#include <dlfcn.h> | |||
#include <dlfcn.h> // for dlopen | |||
#endif | |||
#include <dirent.h> | |||
@@ -20,14 +20,11 @@ | |||
#include <plugin.hpp> | |||
#include <system.hpp> | |||
#include <network.hpp> | |||
#include <asset.hpp> | |||
#include <string.hpp> | |||
#include <context.hpp> | |||
#include <app/common.hpp> | |||
#include <plugin/callbacks.hpp> | |||
#include <settings.hpp> | |||
#include <engine/Module.hpp> | |||
namespace rack { | |||
@@ -232,14 +229,6 @@ void init() { | |||
system::unarchiveToFolder(fundamentalSrc.c_str(), asset::pluginsPath.c_str()); | |||
loadPlugin(fundamentalDir); | |||
} | |||
// Sync in a detached thread | |||
if (!settings::devMode) { | |||
std::thread t([] { | |||
queryUpdates(); | |||
}); | |||
t.detach(); | |||
} | |||
} | |||
@@ -262,245 +251,6 @@ void destroy() { | |||
} | |||
void logIn(const std::string& email, const std::string& password) { | |||
loginStatus = "Logging in..."; | |||
json_t* reqJ = json_object(); | |||
json_object_set(reqJ, "email", json_string(email.c_str())); | |||
json_object_set(reqJ, "password", json_string(password.c_str())); | |||
std::string url = API_URL + "/token"; | |||
json_t* resJ = network::requestJson(network::METHOD_POST, url, reqJ); | |||
json_decref(reqJ); | |||
if (!resJ) { | |||
loginStatus = "No response from server"; | |||
return; | |||
} | |||
DEFER({ | |||
json_decref(resJ); | |||
}); | |||
json_t* errorJ = json_object_get(resJ, "error"); | |||
if (errorJ) { | |||
const char* errorStr = json_string_value(errorJ); | |||
loginStatus = errorStr; | |||
return; | |||
} | |||
json_t* tokenJ = json_object_get(resJ, "token"); | |||
if (!tokenJ) { | |||
loginStatus = "No token in response"; | |||
return; | |||
} | |||
const char* tokenStr = json_string_value(tokenJ); | |||
settings::token = tokenStr; | |||
loginStatus = ""; | |||
queryUpdates(); | |||
} | |||
void logOut() { | |||
settings::token = ""; | |||
updates.clear(); | |||
} | |||
bool isLoggedIn() { | |||
return settings::token != ""; | |||
} | |||
void queryUpdates() { | |||
if (settings::token.empty()) | |||
return; | |||
updates.clear(); | |||
updateStatus = "Querying for updates..."; | |||
// Get user's plugins list | |||
std::string pluginsUrl = API_URL + "/plugins"; | |||
network::CookieMap cookies; | |||
cookies["token"] = settings::token; | |||
json_t* pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, NULL, cookies); | |||
if (!pluginsResJ) { | |||
WARN("Request for user's plugins failed"); | |||
updateStatus = "Could not query updates"; | |||
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 updates"; | |||
return; | |||
} | |||
// Get library manifests | |||
std::string manifestsUrl = API_URL + "/library/manifests"; | |||
json_t* manifestsReq = json_object(); | |||
json_object_set(manifestsReq, "version", json_string(API_VERSION.c_str())); | |||
json_t* manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, manifestsReq); | |||
json_decref(manifestsReq); | |||
if (!manifestsResJ) { | |||
WARN("Request for library manifests failed"); | |||
updateStatus = "Could not query updates"; | |||
return; | |||
} | |||
DEFER({ | |||
json_decref(manifestsResJ); | |||
}); | |||
json_t* manifestsJ = json_object_get(manifestsResJ, "manifests"); | |||
json_t* pluginsJ = json_object_get(pluginsResJ, "plugins"); | |||
size_t pluginIndex; | |||
json_t* pluginJ; | |||
json_array_foreach(pluginsJ, pluginIndex, pluginJ) { | |||
Update update; | |||
// Get plugin manifest | |||
update.pluginSlug = json_string_value(pluginJ); | |||
json_t* manifestJ = json_object_get(manifestsJ, update.pluginSlug.c_str()); | |||
if (!manifestJ) { | |||
WARN("VCV account has plugin %s but no manifest was found", update.pluginSlug.c_str()); | |||
continue; | |||
} | |||
// Get plugin name | |||
json_t* nameJ = json_object_get(manifestJ, "name"); | |||
if (nameJ) | |||
update.pluginName = json_string_value(nameJ); | |||
// Get version | |||
json_t* versionJ = json_object_get(manifestJ, "version"); | |||
if (!versionJ) { | |||
WARN("Plugin %s has no version in manifest", update.pluginSlug.c_str()); | |||
continue; | |||
} | |||
update.version = json_string_value(versionJ); | |||
// Check if update is needed | |||
Plugin* p = getPlugin(update.pluginSlug); | |||
if (p && p->version == update.version) | |||
continue; | |||
// Check status | |||
json_t* statusJ = json_object_get(manifestJ, "status"); | |||
if (!statusJ) | |||
continue; | |||
std::string status = json_string_value(statusJ); | |||
if (status != "available") | |||
continue; | |||
// Get changelog URL | |||
json_t* changelogUrlJ = json_object_get(manifestJ, "changelogUrl"); | |||
if (changelogUrlJ) { | |||
update.changelogUrl = json_string_value(changelogUrlJ); | |||
} | |||
updates.push_back(update); | |||
} | |||
// Get module whitelist | |||
{ | |||
std::string whitelistUrl = API_URL + "/moduleWhitelist"; | |||
json_t* whitelistResJ = network::requestJson(network::METHOD_GET, whitelistUrl, NULL, cookies); | |||
if (!whitelistResJ) { | |||
WARN("Request for module whitelist failed"); | |||
updateStatus = "Could not query updates"; | |||
return; | |||
} | |||
DEFER({ | |||
json_decref(whitelistResJ); | |||
}); | |||
std::map<std::string, std::set<std::string>> moduleWhitelist; | |||
json_t* pluginsJ = json_object_get(whitelistResJ, "plugins"); | |||
// Iterate plugins | |||
const char* pluginSlug; | |||
json_t* modulesJ; | |||
json_object_foreach(pluginsJ, pluginSlug, modulesJ) { | |||
// 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); | |||
} | |||
} | |||
settings::moduleWhitelist = moduleWhitelist; | |||
} | |||
updateStatus = ""; | |||
} | |||
bool hasUpdates() { | |||
for (Update& update : updates) { | |||
if (update.progress < 1.f) | |||
return true; | |||
} | |||
return false; | |||
} | |||
static bool isSyncingUpdate = false; | |||
static bool isSyncingUpdates = false; | |||
void syncUpdate(Update* update) { | |||
isSyncingUpdate = true; | |||
DEFER({ | |||
isSyncingUpdate = false; | |||
}); | |||
std::string downloadUrl = API_URL + "/download"; | |||
downloadUrl += "?slug=" + network::encodeUrl(update->pluginSlug); | |||
downloadUrl += "&version=" + network::encodeUrl(update->version); | |||
downloadUrl += "&arch=" + network::encodeUrl(APP_ARCH); | |||
network::CookieMap cookies; | |||
cookies["token"] = settings::token; | |||
INFO("Downloading plugin %s %s %s", update->pluginSlug.c_str(), update->version.c_str(), APP_ARCH.c_str()); | |||
// 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()); | |||
return; | |||
} | |||
} | |||
void syncUpdates() { | |||
isSyncingUpdates = true; | |||
DEFER({ | |||
isSyncingUpdates = false; | |||
}); | |||
if (settings::token.empty()) | |||
return; | |||
for (Update& update : updates) { | |||
if (update.progress < 1.f) | |||
syncUpdate(&update); | |||
} | |||
restartRequested = true; | |||
} | |||
bool isSyncing() { | |||
return isSyncingUpdate || isSyncingUpdates; | |||
} | |||
Plugin* getPlugin(const std::string& pluginSlug) { | |||
for (Plugin* plugin : plugins) { | |||
if (plugin->slug == pluginSlug) { | |||
@@ -566,11 +316,6 @@ std::string normalizeSlug(const std::string& slug) { | |||
std::vector<Plugin*> plugins; | |||
std::string loginStatus; | |||
std::vector<Update> updates; | |||
std::string updateStatus; | |||
bool restartRequested = false; | |||
} // namespace plugin | |||
} // namespace rack |
@@ -1,106 +0,0 @@ | |||
#include <thread> | |||
#include <updater.hpp> | |||
#include <settings.hpp> | |||
#include <app/common.hpp> | |||
#include <network.hpp> | |||
#include <system.hpp> | |||
#include <context.hpp> | |||
#include <window.hpp> | |||
#include <asset.hpp> | |||
namespace rack { | |||
namespace updater { | |||
std::string version; | |||
std::string changelogUrl; | |||
float progress = 0.f; | |||
static std::string downloadUrl; | |||
static void checkVersion() { | |||
std::string versionUrl = API_URL + "/version"; | |||
json_t* resJ = network::requestJson(network::METHOD_GET, versionUrl, NULL); | |||
if (!resJ) { | |||
WARN("Request for version failed"); | |||
return; | |||
} | |||
DEFER({ | |||
json_decref(resJ); | |||
}); | |||
json_t* versionJ = json_object_get(resJ, "version"); | |||
if (versionJ) | |||
version = json_string_value(versionJ); | |||
json_t* changelogUrlJ = json_object_get(resJ, "changelogUrl"); | |||
if (changelogUrlJ) | |||
changelogUrl = json_string_value(changelogUrlJ); | |||
json_t* downloadUrlsJ = json_object_get(resJ, "downloadUrls"); | |||
if (downloadUrlsJ) { | |||
json_t* downloadUrlJ = json_object_get(downloadUrlsJ, APP_ARCH.c_str()); | |||
if (downloadUrlJ) | |||
downloadUrl = json_string_value(downloadUrlJ); | |||
} | |||
} | |||
void init() { | |||
if (settings::devMode) | |||
return; | |||
std::thread t([]() { | |||
checkVersion(); | |||
}); | |||
t.detach(); | |||
} | |||
void update() { | |||
if (downloadUrl == "") | |||
return; | |||
// Download update | |||
// HACK getFilename is only supposed to be used for filesystem paths, not URLs. | |||
std::string filename = system::getFilename(network::urlPath(downloadUrl)); | |||
std::string path = asset::user(filename); | |||
INFO("Downloading update %s to %s", downloadUrl.c_str(), path.c_str()); | |||
network::requestDownload(downloadUrl, path, &progress); | |||
#if defined ARCH_WIN | |||
// Launch the installer | |||
INFO("Launching update %s", path.c_str()); | |||
system::runProcessDetached(path); | |||
#elif defined ARCH_MAC | |||
std::string cmd; | |||
// std::string appPath = system::join(asset::userDir, "Rack.app"); | |||
// cmd = "rm -rf '" + appPath + "'"; | |||
// std::system(cmd.c_str()); | |||
// // Unzip app using Apple's unzipper, since Rack's unzipper doesn't handle the metadata stuff correctly. | |||
// cmd = "unzip -q '" + path + "' -d '" + asset::userDir + "'"; | |||
// std::system(cmd.c_str()); | |||
// // Open app in Finder | |||
// cmd = "open -R '" + appPath + "'"; | |||
// std::system(cmd.c_str()); | |||
// Open Archive Utility | |||
cmd = "open '" + path + "'"; | |||
std::system(cmd.c_str()); | |||
#elif defined ARCH_LIN | |||
system::openFolder(asset::user("")); | |||
#endif | |||
APP->window->close(); | |||
} | |||
bool isUpdateAvailable() { | |||
return (version != "") && (version != APP_VERSION); | |||
} | |||
} // namespace updater | |||
} // namespace rack |
@@ -19,7 +19,7 @@ | |||
#include <ui.hpp> | |||
#include <system.hpp> | |||
#include <string.hpp> | |||
#include <updater.hpp> | |||
#include <library.hpp> | |||
#include <network.hpp> | |||
#include <osdialog.h> | |||
@@ -168,7 +168,7 @@ int main(int argc, char* argv[]) { | |||
keyboard::init(); | |||
gamepad::init(); | |||
plugin::init(); | |||
updater::init(); | |||
library::init(); | |||
if (!settings::headless) { | |||
ui::init(); | |||
windowInit(); | |||