From acbe3370f84feefd580741e9c3af53e3b414ae0e Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 23 Mar 2018 11:33:35 -0400 Subject: [PATCH] Plugin extracter and loader --- Makefile | 9 +- include/ui.hpp | 2 +- include/util/common.hpp | 5 +- src/app/PluginManagerWidget.cpp | 1 + src/asset.cpp | 4 +- src/plugin.cpp | 267 +++++++++++++++++--------------- src/util/system.cpp | 47 +++++- 7 files changed, 196 insertions(+), 139 deletions(-) diff --git a/Makefile b/Makefile index dd25a63c..aa014b79 100644 --- a/Makefile +++ b/Makefile @@ -132,8 +132,7 @@ ifeq ($(ARCH), mac) otool -L $(BUNDLE)/Contents/MacOS/$(TARGET) - mkdir -p $(BUNDLE)/Contents/Resources/plugins - cp -R plugins/Fundamental/dist/Fundamental $(BUNDLE)/Contents/Resources/plugins + cp plugins/Fundamental/dist/Fundamental-*.zip $(BUNDLE)/Contents/Resources/Fundamental.zip # Make DMG image cd dist && ln -s /Applications Applications cd dist && hdiutil create -srcfolder . -volname Rack -ov -format UDZO Rack-$(VERSION)-$(ARCH).dmg @@ -157,8 +156,7 @@ ifeq ($(ARCH), win) 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/ + cp plugins/Fundamental/dist/Fundamental-*.zip dist/Rack/Fundamental.zip # Make ZIP cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack # Make NSIS installer @@ -179,8 +177,7 @@ ifeq ($(ARCH), lin) 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/ + cp plugins/Fundamental/dist/Fundamental-*.zip dist/Rack/Fundamental.zip # Make ZIP cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack endif diff --git a/include/ui.hpp b/include/ui.hpp index 39708aab..981b938e 100644 --- a/include/ui.hpp +++ b/include/ui.hpp @@ -14,7 +14,7 @@ namespace rack { //////////////////// /** Positions children in a row/column based on their widths/heights */ -struct SequentialLayout : virtual Widget { +struct SequentialLayout : VirtualWidget { enum Orientation { HORIZONTAL_ORIENTATION, VERTICAL_ORIENTATION, diff --git a/include/util/common.hpp b/include/util/common.hpp index f593e27c..735e686d 100644 --- a/include/util/common.hpp +++ b/include/util/common.hpp @@ -144,7 +144,10 @@ std::string stringExtension(std::string path); // system.cpp //////////////////// -std::vector systemListDirectory(std::string path); +std::vector systemListEntries(std::string path); +bool systemIsFile(std::string path); +bool systemIsDirectory(std::string path); +void systemCopy(std::string srcPath, std::string destPath); /** Opens a URL, also happens to work with PDFs and folders. Shell injection is possible, so make sure the URL is trusted or hard coded. diff --git a/src/app/PluginManagerWidget.cpp b/src/app/PluginManagerWidget.cpp index 1d3165e7..7fd7573f 100644 --- a/src/app/PluginManagerWidget.cpp +++ b/src/app/PluginManagerWidget.cpp @@ -53,6 +53,7 @@ struct SyncButton : Button { PluginManagerWidget::PluginManagerWidget() { + box.size.y = BND_WIDGET_HEIGHT; float margin = 5; diff --git a/src/asset.cpp b/src/asset.cpp index 25384e65..13ee0e7e 100644 --- a/src/asset.cpp +++ b/src/asset.cpp @@ -25,7 +25,7 @@ namespace rack { std::string assetGlobal(std::string filename) { std::string dir; -#if defined(RELEASE) +#if RELEASE #if ARCH_MAC CFBundleRef bundle = CFBundleGetMainBundle(); assert(bundle); @@ -53,7 +53,7 @@ std::string assetGlobal(std::string filename) { std::string assetLocal(std::string filename) { std::string dir; -#if defined(RELEASE) +#if RELEASE #if ARCH_MAC // Get home directory struct passwd *pw = getpwuid(getuid()); diff --git a/src/plugin.cpp b/src/plugin.cpp index a5af23c7..d9c0237e 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -53,8 +53,11 @@ void Plugin::addModel(Model *model) { models.push_back(model); } +//////////////////// +// private API +//////////////////// -static int loadPlugin(std::string path) { +static bool loadPlugin(std::string path) { std::string libraryFilename; #if ARCH_LIN libraryFilename = path + "/" + "plugin.so"; @@ -64,6 +67,12 @@ static int loadPlugin(std::string path) { libraryFilename = path + "/" + "plugin.dylib"; #endif + // Check file existence + if (!systemIsFile(libraryFilename)) { + warn("Plugin file %s does not exist", libraryFilename.c_str()); + return false; + } + // Load dynamic/shared library #if ARCH_WIN SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); @@ -71,14 +80,14 @@ static int loadPlugin(std::string path) { SetErrorMode(0); if (!handle) { int error = GetLastError(); - warn("Failed to load library %s: %d", libraryFilename.c_str(), error); - return -1; + warn("Failed to load library %s: code %d", libraryFilename.c_str(), error); + return false; } #else void *handle = dlopen(libraryFilename.c_str(), RTLD_NOW); if (!handle) { warn("Failed to load library %s: %s", libraryFilename.c_str(), dlerror()); - return -1; + return false; } #endif @@ -92,7 +101,7 @@ static int loadPlugin(std::string path) { #endif if (!initCallback) { warn("Failed to read init() symbol in %s", libraryFilename.c_str()); - return -2; + return false; } // Construct and initialize Plugin instance @@ -102,20 +111,19 @@ static int loadPlugin(std::string path) { initCallback(plugin); // Reject plugin if slug already exists - for (Plugin *p : gPlugins) { - if (plugin->slug == p->slug) { - warn("Plugin \"%s\" is already loaded, not attempting to load it again", p->slug.c_str()); - // TODO - // Fix memory leak with `plugin` here - return -1; - } + Plugin *oldPlugin = pluginGetPlugin(plugin->slug); + if (oldPlugin) { + warn("Plugin \"%s\" is already loaded, not attempting to load it again", plugin->slug.c_str()); + // TODO + // Fix memory leak with `plugin` here + return false; } // Add plugin to list gPlugins.push_back(plugin); info("Loaded plugin %s", libraryFilename.c_str()); - return 0; + return true; } static bool syncPlugin(json_t *pluginJ, bool dryRun) { @@ -212,100 +220,18 @@ static bool syncPlugin(json_t *pluginJ, bool dryRun) { return true; } -bool pluginSync(bool dryRun) { - if (gToken.empty()) - return false; - - bool available = false; - - if (!dryRun) { - isDownloading = true; - downloadProgress = 0.0; - downloadName = "Updating plugins..."; - } - - json_t *resJ = NULL; - json_t *communityResJ = NULL; - - 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(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; - } - - // Sync plugin - if (syncPlugin(communityPluginJ, dryRun)) { - available = true; - } - else { - warn("Plugin %s not found in community", slug.c_str()); - } - } - } - catch (std::runtime_error &e) { - warn("Plugin sync error: %s", e.what()); - } - - if (communityResJ) - json_decref(communityResJ); - - if (resJ) - json_decref(resJ); - - if (!dryRun) { - isDownloading = false; - } - - return available; -} - static void loadPlugins(std::string path) { - DIR *dir = opendir(path.c_str()); - if (dir) { - struct dirent *d; - while ((d = readdir(dir))) { - if (d->d_name[0] == '.') - continue; - loadPlugin(path + "/" + d->d_name); + std::string message; + for (std::string pluginPath : systemListEntries(path)) { + if (!systemIsDirectory(pluginPath)) + continue; + if (!loadPlugin(pluginPath)) { + message += stringf("Could not load plugin %s\n", pluginPath.c_str()); } - closedir(dir); + } + if (!message.empty()) { + message += "See log for details."; + osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); } } @@ -336,7 +262,7 @@ static int extractZipHandle(zip_t *za, const char *dir) { continue; while (1) { - char buffer[4096]; + char buffer[1<<15]; int len = zip_fread(zf, buffer, sizeof(buffer)); if (len <= 0) break; @@ -352,28 +278,30 @@ static int extractZipHandle(zip_t *za, const char *dir) { return 0; } -static int extractZip(const char *filename, const char *dir) { +static int extractZip(const char *filename, const char *path) { int err = 0; zip_t *za = zip_open(filename, 0, &err); if (!za) return 1; + defer({ + zip_close(za); + }); + if (err) + return err; - if (!err) { - err = extractZipHandle(za, dir); - } - - zip_close(za); + err = extractZipHandle(za, path); return err; } static void extractPackages(std::string path) { std::string message; - for (std::string packagePath : systemListDirectory(path)) { + for (std::string packagePath : systemListEntries(path)) { if (stringExtension(packagePath) == "zip") { + info("Extracting package %s", packagePath.c_str()); // Extract package - if (!extractZip(packagePath.c_str(), path.c_str())) { - message += stringf("Could not extract package %s\n", path); + if (extractZip(packagePath.c_str(), path.c_str())) { + message += stringf("Could not extract package %s\n", packagePath.c_str()); continue; } // Remove package @@ -386,31 +314,32 @@ static void extractPackages(std::string path) { } //////////////////// -// plugin API +// public API //////////////////// void pluginInit() { tagsInit(); - // TODO - // If `/plugins/Fundamental` doesn't exist, unzip global Fundamental.zip package into `/plugins` - - // TODO - // Find all ZIP packages in `/plugins` and unzip them. - // Display error if failure - // Load core // This function is defined in core.cpp Plugin *corePlugin = new Plugin(); init(corePlugin); gPlugins.push_back(corePlugin); - // Load plugins from local directory + // Get local plugins directory std::string localPlugins = assetLocal("plugins"); mkdir(localPlugins.c_str(), 0755); - info("Unzipping plugins from %s", localPlugins.c_str()); + +#if RELEASE + // Copy Fundamental package to plugins directory if folder does not exist + std::string fundamentalDest = localPlugins + "/Fundamental.zip"; + if (!systemIsDirectory(localPlugins + "/Fundamental") && !systemIsFile(fundamentalDest)) { + systemCopy(assetGlobal("Fundamental.zip"), fundamentalDest); + } +#endif + + // Extract packages and load plugins extractPackages(localPlugins); - info("Loading plugins from %s", localPlugins.c_str()); loadPlugins(localPlugins); } @@ -432,6 +361,90 @@ void pluginDestroy() { gPlugins.clear(); } +bool pluginSync(bool dryRun) { + if (gToken.empty()) + return false; + + bool available = false; + + if (!dryRun) { + isDownloading = true; + downloadProgress = 0.0; + downloadName = "Updating plugins..."; + } + + json_t *resJ = NULL; + json_t *communityResJ = NULL; + + 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(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; + } + + // Sync plugin + if (syncPlugin(communityPluginJ, dryRun)) { + available = true; + } + else { + warn("Plugin %s not found in community", slug.c_str()); + } + } + } + catch (std::runtime_error &e) { + warn("Plugin sync error: %s", e.what()); + } + + if (communityResJ) + json_decref(communityResJ); + + if (resJ) + json_decref(resJ); + + if (!dryRun) { + isDownloading = false; + } + + return available; +} + void pluginLogIn(std::string email, std::string password) { json_t *reqJ = json_object(); json_object_set(reqJ, "email", json_string(email.c_str())); @@ -507,6 +520,4 @@ Model *pluginGetModel(std::string pluginSlug, std::string modelSlug) { } - - } // namespace rack diff --git a/src/util/system.cpp b/src/util/system.cpp index 340aef8f..e4ef26ae 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1,6 +1,7 @@ #include "util/common.hpp" #include +#include #if ARCH_WIN #include @@ -11,7 +12,7 @@ namespace rack { -std::vector systemListDirectory(std::string path) { +std::vector systemListEntries(std::string path) { std::vector filenames; DIR *dir = opendir(path.c_str()); if (dir) { @@ -27,6 +28,50 @@ std::vector systemListDirectory(std::string path) { return filenames; } +bool systemExists(std::string path) { + struct stat statbuf; + return (stat(path.c_str(), &statbuf) == 0); +} + +bool systemIsFile(std::string path) { + struct stat statbuf; + if (stat(path.c_str(), &statbuf)) + return false; + return S_ISREG(statbuf.st_mode); +} + +bool systemIsDirectory(std::string path) { + struct stat statbuf; + if (stat(path.c_str(), &statbuf)) + return false; + return S_ISDIR(statbuf.st_mode); +} + +void systemCopy(std::string srcPath, std::string destPath) { + // Open files + FILE *source = fopen(srcPath.c_str(), "rb"); + if (!source) return; + defer({ + fclose(source); + }); + FILE *dest = fopen(destPath.c_str(), "wb"); + if (!dest) return; + defer({ + fclose(dest); + }); + // Copy buffer + const int bufferSize = (1<<15); + char buffer[bufferSize]; + while (1) { + size_t size = fread(buffer, 1, bufferSize, source); + if (size == 0) + break; + size = fwrite(buffer, 1, size, dest); + if (size == 0) + break; + } +} + void systemOpenBrowser(std::string url) { #if ARCH_LIN std::string command = "xdg-open " + url;