diff --git a/Makefile b/Makefile
index 42f169fa..db4fe127 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@ ifeq ($(ARCH), lin)
LDFLAGS += -rdynamic \
-lpthread -lGL -ldl \
$(shell pkg-config --libs gtk+-2.0) \
- -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi
+ -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl
TARGET = Rack
endif
@@ -23,7 +23,7 @@ ifeq ($(ARCH), mac)
CXXFLAGS += -DAPPLE -stdlib=libc++
LDFLAGS += -stdlib=libc++ -lpthread -ldl \
-framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo \
- -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi
+ -Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl
TARGET = Rack
BUNDLE = dist/$(TARGET).app
endif
@@ -33,7 +33,7 @@ ifeq ($(ARCH), win)
LDFLAGS += -static-libgcc -static-libstdc++ -lpthread \
-Wl,--export-all-symbols,--out-implib,libRack.a -mwindows \
-lgdi32 -lopengl32 -lcomdlg32 -lole32 \
- -Ldep/lib -lglew32 -lglfw3dll -lcurl -lzip -lrtaudio -lrtmidi \
+ -Ldep/lib -lglew32 -lglfw3dll -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl \
-Wl,-Bstatic -ljansson -lspeexdsp
TARGET = Rack.exe
OBJECTS = Rack.res
@@ -112,8 +112,14 @@ ifeq ($(ARCH), mac)
cp dep/lib/libspeexdsp.1.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libcurl.4.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libzip.5.dylib $(BUNDLE)/Contents/MacOS/
+<<<<<<< HEAD
cp dep/lib/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/librtaudio.dylib $(BUNDLE)/Contents/MacOS/
+=======
+ cp dep/lib/librtaudio.dylib $(BUNDLE)/Contents/MacOS/
+ cp dep/lib/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/
+ cp dep/lib/libcrypto.1.1.dylib $(BUNDLE)/Contents/MacOS/
+>>>>>>> v0.5
install_name_tool -change /usr/local/lib/libGLEW.2.1.0.dylib @executable_path/libGLEW.2.1.0.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change lib/libglfw.3.dylib @executable_path/libglfw.3.dylib $(BUNDLE)/Contents/MacOS/Rack
@@ -121,8 +127,14 @@ ifeq ($(ARCH), mac)
install_name_tool -change $(PWD)/dep/lib/libspeexdsp.1.dylib @executable_path/libspeexdsp.1.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/libcurl.4.dylib @executable_path/libcurl.4.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/libzip.5.dylib @executable_path/libzip.5.dylib $(BUNDLE)/Contents/MacOS/Rack
+<<<<<<< HEAD
install_name_tool -change $(PWD)/dep/lib/librtmidi.4.dylib @executable_path/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change librtaudio.dylib @executable_path/librtaudio.dylib $(BUNDLE)/Contents/MacOS/Rack
+=======
+ install_name_tool -change librtaudio.dylib @executable_path/librtaudio.dylib $(BUNDLE)/Contents/MacOS/Rack
+ install_name_tool -change $(PWD)/dep/lib/librtmidi.4.dylib @executable_path/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/Rack
+ install_name_tool -change $(PWD)/dep/lib/libcrypto.1.1.dylib @executable_path/libcrypto.1.1.dylib $(BUNDLE)/Contents/MacOS/Rack
+>>>>>>> v0.5
otool -L $(BUNDLE)/Contents/MacOS/Rack
@@ -149,6 +161,8 @@ ifeq ($(ARCH), win)
cp dep/bin/libspeexdsp-1.dll dist/Rack/
cp dep/bin/libzip-5.dll dist/Rack/
cp dep/bin/librtaudio.dll dist/Rack/
+ cp dep/bin/libcrypto-1_1-x64.dll dist/Rack/
+ cp dep/bin/libssl-1_1-x64.dll dist/Rack/
mkdir -p dist/Rack/plugins
cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/
# Make ZIP
@@ -169,6 +183,8 @@ ifeq ($(ARCH), lin)
cp dep/lib/libzip.so.5 dist/Rack/
cp dep/lib/librtaudio.so dist/Rack/
cp dep/lib/librtmidi.so.4 dist/Rack/
+ cp dep/lib/libssl.so.1.1 dist/Rack/
+ cp dep/lib/libcrypto.so.1.1 dist/Rack/
mkdir -p dist/Rack/plugins
cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/
# Make ZIP
diff --git a/dep/Makefile b/dep/Makefile
index eff4442e..2066846d 100755
--- a/dep/Makefile
+++ b/dep/Makefile
@@ -31,6 +31,7 @@ ifeq ($(ARCH),lin)
libzip = lib/libzip.so
rtmidi = lib/librtmidi.so
rtaudio = lib/librtaudio.so
+ openssl = lib/libssl.so
endif
ifeq ($(ARCH),mac)
@@ -42,6 +43,7 @@ ifeq ($(ARCH),mac)
libzip = lib/libzip.dylib
rtmidi = lib/librtmidi.dylib
rtaudio = lib/librtaudio.dylib
+ openssl = lib/libssl.dylib
endif
ifeq ($(ARCH),win)
@@ -52,7 +54,8 @@ ifeq ($(ARCH),win)
libcurl = bin/libcurl-4.dll
libzip = bin/libzip-5.dll
rtmidi = bin/librtmidi-4.dll
- rtaudio = bin/librtaudio-6.dll
+ rtaudio = bin/librtaudio.dll
+ openssl = bin/libssl-1_1-x64.dll
endif
# Library configuration
@@ -106,12 +109,20 @@ $(libspeexdsp):
$(MAKE) -C speexdsp-SpeexDSP-1.2rc3
$(MAKE) -C speexdsp-SpeexDSP-1.2rc3 install
-$(libcurl):
+$(openssl):
+ $(WGET) https://www.openssl.org/source/openssl-1.1.0g.tar.gz
+ $(UNTAR) openssl-1.1.0g.tar.gz
+ cd openssl-1.1.0g && ./config --prefix="$(LOCAL)"
+ $(MAKE) -C openssl-1.1.0g
+ $(MAKE) -C openssl-1.1.0g install
+
+$(libcurl): $(openssl)
$(WGET) https://github.com/curl/curl/releases/download/curl-7_56_0/curl-7.56.0.tar.gz
$(UNTAR) curl-7.56.0.tar.gz
cd curl-7.56.0 && ./configure --prefix="$(LOCAL)" \
--disable-ftp --disable-file --disable-ldap --disable-ldaps --disable-rtsp --disable-proxy --disable-dict --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --disable-manual \
- --without-zlib --without-ssl --without-ca-bundle --without-ca-path --without-ca-fallback --without-libpsl --without-libmetalink --without-libssh2 --without-librtmp --without-winidn --without-libidn2 --without-nghttp2
+ --without-zlib --without-libpsl --without-libmetalink --without-libssh2 --without-librtmp --without-winidn --without-libidn2 --without-nghttp2 \
+ --without-ca-bundle --with-ca-fallback --with-ssl="$(LOCAL)"
$(MAKE) -C curl-7.56.0
$(MAKE) -C curl-7.56.0 install
diff --git a/include/components.hpp b/include/components.hpp
index 03854062..cac1cf91 100644
--- a/include/components.hpp
+++ b/include/components.hpp
@@ -461,6 +461,14 @@ struct CKSS : SVGSwitch, ToggleSwitch {
}
};
+struct CKSSThree : SVGSwitch, ToggleSwitch {
+ CKSSThree() {
+ addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_0.svg")));
+ addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_1.svg")));
+ addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_2.svg")));
+ }
+};
+
struct CKD6 : SVGSwitch, MomentarySwitch {
CKD6() {
addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKD6_0.svg")));
diff --git a/include/plugin.hpp b/include/plugin.hpp
index 8e9f612b..9f258352 100644
--- a/include/plugin.hpp
+++ b/include/plugin.hpp
@@ -60,7 +60,8 @@ void pluginInit();
void pluginDestroy();
void pluginLogIn(std::string email, std::string password);
void pluginLogOut();
-void pluginRefresh();
+/** Returns whether a new plugin is available, and downloads it unless doing a dry run */
+bool pluginSync(bool dryRun);
void pluginCancelDownload();
bool pluginIsLoggedIn();
bool pluginIsDownloading();
diff --git a/include/util.hpp b/include/util.hpp
index f7e4732a..3e7b53dc 100644
--- a/include/util.hpp
+++ b/include/util.hpp
@@ -30,6 +30,16 @@ will expand to
#define LENGTHOF(arr) (sizeof(arr) / sizeof((arr)[0]))
+/** Reserve space for _count enums starting with _name.
+Example:
+ enum Foo {
+ ENUMS(BAR, 14)
+ };
+
+ BAR + 0 to BAR + 11 is reserved
+*/
+#define ENUMS(_name, _count) _name, _name ## _LAST = _name + (_count) - 1
+
/** Deprecation notice for GCC */
#define DEPRECATED __attribute__ ((deprecated))
diff --git a/include/util/request.hpp b/include/util/request.hpp
index d0405155..7ae510c7 100644
--- a/include/util/request.hpp
+++ b/include/util/request.hpp
@@ -19,5 +19,6 @@ json_t *requestJson(RequestMethod method, std::string url, json_t *dataJ);
/** Returns the filename, blank if unsuccessful */
bool requestDownload(std::string url, std::string filename, float *progress);
std::string requestEscape(std::string s);
+std::string requestSHA256File(std::string filename);
} // namespace rack
diff --git a/res/ComponentLibrary/CKSSThree_0.svg b/res/ComponentLibrary/CKSSThree_0.svg
new file mode 100644
index 00000000..a8283517
--- /dev/null
+++ b/res/ComponentLibrary/CKSSThree_0.svg
@@ -0,0 +1,321 @@
+
+
+
+
diff --git a/res/ComponentLibrary/CKSSThree_1.svg b/res/ComponentLibrary/CKSSThree_1.svg
new file mode 100644
index 00000000..46656fcf
--- /dev/null
+++ b/res/ComponentLibrary/CKSSThree_1.svg
@@ -0,0 +1,322 @@
+
+
+
+
diff --git a/res/ComponentLibrary/CKSSThree_2.svg b/res/ComponentLibrary/CKSSThree_2.svg
new file mode 100644
index 00000000..cbfd986b
--- /dev/null
+++ b/res/ComponentLibrary/CKSSThree_2.svg
@@ -0,0 +1,322 @@
+
+
+
+
diff --git a/src/app.cpp b/src/app.cpp
index 8b8bade6..84795945 100644
--- a/src/app.cpp
+++ b/src/app.cpp
@@ -10,7 +10,8 @@ std::string gApplicationVersion =
#else
"";
#endif
-std::string gApiHost = "http://api.vcvrack.com";
+std::string gApiHost = "https://api.vcvrack.com";
+// std::string gApiHost = "http://localhost:8081";
RackWidget *gRackWidget = NULL;
Toolbar *gToolbar = NULL;
diff --git a/src/app/AddModuleWindow.cpp b/src/app/AddModuleWindow.cpp
index 48f2d91d..ac99389e 100644
--- a/src/app/AddModuleWindow.cpp
+++ b/src/app/AddModuleWindow.cpp
@@ -70,7 +70,7 @@ struct MetadataMenu : ListMenu {
// Plugin metadata
if (!model->plugin->website.empty()) {
- addChild(construct(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->path));
+ addChild(construct(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->website));
}
if (!model->plugin->manual.empty()) {
addChild(construct(&MenuEntry::text, "Manual", &UrlItem::url, model->plugin->manual));
@@ -120,10 +120,6 @@ struct ModelItem : MenuItem {
sModel = model;
MenuItem::onMouseEnter(e);
}
- void onMouseLeave(EventMouseLeave &e) override {
- sModel = NULL;
- MenuItem::onMouseLeave(e);
- }
};
diff --git a/src/app/PluginManagerWidget.cpp b/src/app/PluginManagerWidget.cpp
index c246c325..096eb071 100644
--- a/src/app/PluginManagerWidget.cpp
+++ b/src/app/PluginManagerWidget.cpp
@@ -1,11 +1,57 @@
#include
#include "app.hpp"
#include "plugin.hpp"
+#include "gui.hpp"
+#include "../ext/osdialog/osdialog.h"
namespace rack {
+struct SyncButton : Button {
+ bool checked = false;
+ bool available = false;
+ bool completed = false;
+
+ void step() override {
+ if (!checked) {
+ std::thread t([this]() {
+ if (pluginSync(true))
+ available = true;
+ });
+ t.detach();
+ checked = true;
+ }
+ 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.")) {
+ guiClose();
+ }
+ completed = false;
+ }
+ }
+ void draw(NVGcontext *vg) override {
+ Button::draw(vg);
+ if (available) {
+ // Notification circle
+ nvgBeginPath(vg);
+ nvgCircle(vg, 3, 3, 4.0);
+ nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0));
+ nvgFill(vg);
+ nvgStrokeColor(vg, nvgRGBf(0.5, 0.0, 0.0));
+ nvgStroke(vg);
+ }
+ }
+ void onAction(EventAction &e) override {
+ available = false;
+ std::thread t([this]() {
+ if (pluginSync(false))
+ completed = true;
+ });
+ t.detach();
+ }
+};
+
+
PluginManagerWidget::PluginManagerWidget() {
box.size.y = BND_WIDGET_HEIGHT;
float margin = 5;
@@ -91,19 +137,13 @@ PluginManagerWidget::PluginManagerWidget() {
manageWidget->addChild(manageButton);
pos.x += manageButton->box.size.x;
- struct RefreshButton : Button {
- void onAction(EventAction &e) override {
- std::thread t(pluginRefresh);
- t.detach();
- }
- };
pos.x += margin;
- Button *refreshButton = new RefreshButton();
- refreshButton->box.pos = pos;
- refreshButton->box.size.x = 125;
- refreshButton->text = "Refresh plugins";
- manageWidget->addChild(refreshButton);
- pos.x += refreshButton->box.size.x;
+ 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 {
diff --git a/src/plugin.cpp b/src/plugin.cpp
index 3b0b1b51..8c5d0b41 100644
--- a/src/plugin.cpp
+++ b/src/plugin.cpp
@@ -7,11 +7,12 @@
#include
#include // for MAXPATHLEN
#include
+#include
#include
#include
-#if ARCH_WIN
+#if defined(ARCH_WIN)
#include
#include
#define mkdir(_dir, _perms) _mkdir(_dir)
@@ -189,35 +190,55 @@ static int extractZip(const char *filename, const char *dir) {
return err;
}
-static void refreshPurchase(json_t *pluginJ) {
+static void syncPlugin(json_t *pluginJ) {
json_t *slugJ = json_object_get(pluginJ, "slug");
if (!slugJ) return;
std::string slug = json_string_value(slugJ);
+ info("Syncing plugin %s", slug.c_str());
json_t *nameJ = json_object_get(pluginJ, "name");
if (!nameJ) return;
std::string name = json_string_value(nameJ);
- json_t *versionJ = json_object_get(pluginJ, "version");
- if (!versionJ) return;
- std::string version = json_string_value(versionJ);
-
- // Check whether the plugin is already loaded
- for (Plugin *plugin : gPlugins) {
- if (plugin->slug == slug && plugin->version == version) {
- return;
+ std::string download;
+ std::string sha256;
+
+ json_t *downloadsJ = json_object_get(pluginJ, "downloads");
+ if (downloadsJ) {
+#if defined(ARCH_WIN)
+ #define DOWNLOADS_ARCH "win"
+#elif defined(ARCH_MAC)
+ #define DOWNLOADS_ARCH "mac"
+#elif defined(ARCH_LIN)
+ #define DOWNLOADS_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);
}
}
- // Append token and version to download URL
- std::string url = gApiHost;
- url += "/download";
- url += "?product=";
- url += slug;
- url += "&version=";
- url += requestEscape(gApplicationVersion);
- url += "&token=";
- url += requestEscape(gToken);
+ 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;
+ }
// If plugin is not loaded, download the zip file to /plugins
downloadName = name;
@@ -227,21 +248,127 @@ static void refreshPurchase(json_t *pluginJ) {
std::string pluginsDir = assetLocal("plugins");
std::string pluginPath = pluginsDir + "/" + slug;
std::string zipPath = pluginPath + ".zip";
- bool success = requestDownload(url, zipPath, &downloadProgress);
+ bool success = requestDownload(download, zipPath, &downloadProgress);
if (success) {
+ 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());
+ return;
+ }
+ }
+
// Unzip file
int err = extractZip(zipPath.c_str(), pluginsDir.c_str());
if (!err) {
// Delete zip
remove(zipPath.c_str());
// Load plugin
- loadPlugin(pluginPath);
+ // loadPlugin(pluginPath);
}
}
downloadName = "";
}
+static bool trySyncPlugin(json_t *pluginJ, json_t *communityPluginsJ, bool dryRun) {
+ std::string slug = json_string_value(pluginJ);
+
+ // Find community plugin
+ size_t communityIndex;
+ json_t *communityPluginJ = NULL;
+ json_array_foreach(communityPluginsJ, communityIndex, communityPluginJ) {
+ json_t *communitySlugJ = json_object_get(communityPluginJ, "slug");
+ if (communitySlugJ) {
+ std::string communitySlug = json_string_value(communitySlugJ);
+ if (slug == communitySlug)
+ break;
+ }
+ }
+ if (communityIndex == json_array_size(communityPluginsJ)) {
+ warn("Plugin sync error: %s not found in community", slug.c_str());
+ return false;
+ }
+
+ // Get community version
+ std::string version;
+ json_t *versionJ = json_object_get(communityPluginJ, "version");
+ if (versionJ) {
+ version = json_string_value(versionJ);
+ }
+
+ // 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;
+ }
+ }
+
+ if (!dryRun)
+ syncPlugin(communityPluginJ);
+ return true;
+}
+
+bool pluginSync(bool dryRun) {
+ if (gToken.empty())
+ return false;
+
+ bool available = false;
+
+ // Download my plugins
+ 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()));
+ json_t *resJ = requestJson(METHOD_GET, gApiHost + "/plugins", reqJ);
+ json_decref(reqJ);
+
+ // Download community plugins
+ json_t *communityResJ = requestJson(METHOD_GET, gApiHost + "/community/plugins", NULL);
+
+ if (!dryRun) {
+ isDownloading = true;
+ downloadProgress = 0.0;
+ downloadName = "";
+ }
+
+ if (resJ && communityResJ) {
+ json_t *errorJ = json_object_get(resJ, "error");
+ json_t *communityErrorJ = json_object_get(resJ, "error");
+ if (errorJ) {
+ warn("Plugin sync error: %s", json_string_value(errorJ));
+ }
+ else if (communityErrorJ) {
+ warn("Plugin sync error: %s", json_string_value(communityErrorJ));
+ }
+ else {
+ // Check each plugin in list of my plugins
+ json_t *pluginsJ = json_object_get(resJ, "plugins");
+ json_t *communityPluginsJ = json_object_get(communityResJ, "plugins");
+ size_t index;
+ json_t *pluginJ;
+ json_array_foreach(pluginsJ, index, pluginJ) {
+ if (trySyncPlugin(pluginJ, communityPluginsJ, dryRun))
+ available = true;
+ }
+ }
+ }
+
+ if (resJ)
+ json_decref(resJ);
+
+ if (communityResJ)
+ json_decref(communityResJ);
+
+ if (!dryRun) {
+ isDownloading = false;
+ }
+
+ return available;
+}
+
////////////////////
// plugin API
////////////////////
@@ -271,10 +398,10 @@ void pluginInit() {
void pluginDestroy() {
for (Plugin *plugin : gPlugins) {
// Free library handle
-#if ARCH_WIN
+#if defined(ARCH_WIN)
if (plugin->handle)
FreeLibrary((HINSTANCE)plugin->handle);
-#elif ARCH_LIN || ARCH_MAC
+#elif defined(ARCH_LIN) || defined(ARCH_MAC)
if (plugin->handle)
dlclose(plugin->handle);
#endif
@@ -315,39 +442,6 @@ void pluginLogOut() {
gToken = "";
}
-void pluginRefresh() {
- if (gToken.empty())
- return;
-
- isDownloading = true;
- downloadProgress = 0.0;
- downloadName = "";
-
- json_t *reqJ = json_object();
- json_object_set(reqJ, "token", json_string(gToken.c_str()));
- json_t *resJ = requestJson(METHOD_GET, gApiHost + "/purchases", reqJ);
- json_decref(reqJ);
-
- if (resJ) {
- json_t *errorJ = json_object_get(resJ, "error");
- if (errorJ) {
- const char *errorStr = json_string_value(errorJ);
- warn("Plugin refresh error: %s", errorStr);
- }
- else {
- json_t *purchasesJ = json_object_get(resJ, "purchases");
- size_t index;
- json_t *purchaseJ;
- json_array_foreach(purchasesJ, index, purchaseJ) {
- refreshPurchase(purchaseJ);
- }
- }
- json_decref(resJ);
- }
-
- isDownloading = false;
-}
-
void pluginCancelDownload() {
// TODO
}
diff --git a/src/util/request.cpp b/src/util/request.cpp
index c046bbb7..72702c04 100644
--- a/src/util/request.cpp
+++ b/src/util/request.cpp
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
namespace rack {
@@ -79,6 +80,7 @@ json_t *requestJson(RequestMethod method, std::string url, json_t *dataJ) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, reqStr);
std::string resText;
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText);
@@ -130,6 +132,8 @@ bool requestDownload(std::string url, std::string filename, float *progress) {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferInfoCallback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, progress);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
info("Downloading %s", url.c_str());
CURLcode res = curl_easy_perform(curl);
@@ -153,5 +157,36 @@ std::string requestEscape(std::string s) {
return ret;
}
+std::string requestSHA256File(std::string filename) {
+ FILE *f = fopen(filename.c_str(), "rb");
+ if (!f)
+ return "";
+
+ uint8_t hash[SHA256_DIGEST_LENGTH];
+ SHA256_CTX sha256;
+ SHA256_Init(&sha256);
+ const int bufferLen = 1 << 15;
+ uint8_t *buffer = new uint8_t[bufferLen];
+ int len = 0;
+ while ((len = fread(buffer, 1, bufferLen, f))) {
+ SHA256_Update(&sha256, buffer, len);
+ }
+ SHA256_Final(hash, &sha256);
+ delete[] buffer;
+ fclose(f);
+
+ // Convert binary hash to hex
+ char hashHex[64];
+ const char hexTable[] = "0123456789abcdef";
+ for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
+ uint8_t h = hash[i];
+ hashHex[2*i + 0] = hexTable[h >> 4];
+ hashHex[2*i + 1] = hexTable[h & 0x0f];
+ }
+
+ std::string str(hashHex, sizeof(hashHex));
+ return str;
+}
+
} // namespace rack