Browse Source

Plugin extracter and loader

tags/v0.6.0
Andrew Belt 7 years ago
parent
commit
acbe3370f8
7 changed files with 196 additions and 139 deletions
  1. +3
    -6
      Makefile
  2. +1
    -1
      include/ui.hpp
  3. +4
    -1
      include/util/common.hpp
  4. +1
    -0
      src/app/PluginManagerWidget.cpp
  5. +2
    -2
      src/asset.cpp
  6. +139
    -128
      src/plugin.cpp
  7. +46
    -1
      src/util/system.cpp

+ 3
- 6
Makefile View File

@@ -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


+ 1
- 1
include/ui.hpp View File

@@ -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,


+ 4
- 1
include/util/common.hpp View File

@@ -144,7 +144,10 @@ std::string stringExtension(std::string path);
// system.cpp
////////////////////

std::vector<std::string> systemListDirectory(std::string path);
std::vector<std::string> 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.


+ 1
- 0
src/app/PluginManagerWidget.cpp View File

@@ -53,6 +53,7 @@ struct SyncButton : Button {


PluginManagerWidget::PluginManagerWidget() {
box.size.y = BND_WIDGET_HEIGHT;
float margin = 5;



+ 2
- 2
src/asset.cpp View File

@@ -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());


+ 139
- 128
src/plugin.cpp View File

@@ -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 `<local>/plugins/Fundamental` doesn't exist, unzip global Fundamental.zip package into `<local>/plugins`

// TODO
// Find all ZIP packages in `<local>/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

+ 46
- 1
src/util/system.cpp View File

@@ -1,6 +1,7 @@
#include "util/common.hpp"

#include <dirent.h>
#include <sys/stat.h>

#if ARCH_WIN
#include <windows.h>
@@ -11,7 +12,7 @@
namespace rack {


std::vector<std::string> systemListDirectory(std::string path) {
std::vector<std::string> systemListEntries(std::string path) {
std::vector<std::string> filenames;
DIR *dir = opendir(path.c_str());
if (dir) {
@@ -27,6 +28,50 @@ std::vector<std::string> 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;


Loading…
Cancel
Save