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) 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 # Make DMG image
cd dist && ln -s /Applications Applications cd dist && ln -s /Applications Applications
cd dist && hdiutil create -srcfolder . -volname Rack -ov -format UDZO Rack-$(VERSION)-$(ARCH).dmg 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/librtaudio.dll dist/Rack/
cp dep/bin/libcrypto-1_1-x64.dll dist/Rack/ cp dep/bin/libcrypto-1_1-x64.dll dist/Rack/
cp dep/bin/libssl-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 # Make ZIP
cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack
# Make NSIS installer # Make NSIS installer
@@ -179,8 +177,7 @@ ifeq ($(ARCH), lin)
cp dep/lib/librtmidi.so.4 dist/Rack/ cp dep/lib/librtmidi.so.4 dist/Rack/
cp dep/lib/libssl.so.1.1 dist/Rack/ cp dep/lib/libssl.so.1.1 dist/Rack/
cp dep/lib/libcrypto.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 # Make ZIP
cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack
endif 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 */ /** Positions children in a row/column based on their widths/heights */
struct SequentialLayout : virtual Widget {
struct SequentialLayout : VirtualWidget {
enum Orientation { enum Orientation {
HORIZONTAL_ORIENTATION, HORIZONTAL_ORIENTATION,
VERTICAL_ORIENTATION, VERTICAL_ORIENTATION,


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

@@ -144,7 +144,10 @@ std::string stringExtension(std::string path);
// system.cpp // 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. /** 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. 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() { PluginManagerWidget::PluginManagerWidget() {
box.size.y = BND_WIDGET_HEIGHT; box.size.y = BND_WIDGET_HEIGHT;
float margin = 5; float margin = 5;




+ 2
- 2
src/asset.cpp View File

@@ -25,7 +25,7 @@ namespace rack {


std::string assetGlobal(std::string filename) { std::string assetGlobal(std::string filename) {
std::string dir; std::string dir;
#if defined(RELEASE)
#if RELEASE
#if ARCH_MAC #if ARCH_MAC
CFBundleRef bundle = CFBundleGetMainBundle(); CFBundleRef bundle = CFBundleGetMainBundle();
assert(bundle); assert(bundle);
@@ -53,7 +53,7 @@ std::string assetGlobal(std::string filename) {


std::string assetLocal(std::string filename) { std::string assetLocal(std::string filename) {
std::string dir; std::string dir;
#if defined(RELEASE)
#if RELEASE
#if ARCH_MAC #if ARCH_MAC
// Get home directory // Get home directory
struct passwd *pw = getpwuid(getuid()); 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); models.push_back(model);
} }


////////////////////
// private API
////////////////////


static int loadPlugin(std::string path) {
static bool loadPlugin(std::string path) {
std::string libraryFilename; std::string libraryFilename;
#if ARCH_LIN #if ARCH_LIN
libraryFilename = path + "/" + "plugin.so"; libraryFilename = path + "/" + "plugin.so";
@@ -64,6 +67,12 @@ static int loadPlugin(std::string path) {
libraryFilename = path + "/" + "plugin.dylib"; libraryFilename = path + "/" + "plugin.dylib";
#endif #endif


// Check file existence
if (!systemIsFile(libraryFilename)) {
warn("Plugin file %s does not exist", libraryFilename.c_str());
return false;
}

// Load dynamic/shared library // Load dynamic/shared library
#if ARCH_WIN #if ARCH_WIN
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
@@ -71,14 +80,14 @@ static int loadPlugin(std::string path) {
SetErrorMode(0); SetErrorMode(0);
if (!handle) { if (!handle) {
int error = GetLastError(); 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 #else
void *handle = dlopen(libraryFilename.c_str(), RTLD_NOW); void *handle = dlopen(libraryFilename.c_str(), RTLD_NOW);
if (!handle) { if (!handle) {
warn("Failed to load library %s: %s", libraryFilename.c_str(), dlerror()); warn("Failed to load library %s: %s", libraryFilename.c_str(), dlerror());
return -1;
return false;
} }
#endif #endif


@@ -92,7 +101,7 @@ static int loadPlugin(std::string path) {
#endif #endif
if (!initCallback) { if (!initCallback) {
warn("Failed to read init() symbol in %s", libraryFilename.c_str()); warn("Failed to read init() symbol in %s", libraryFilename.c_str());
return -2;
return false;
} }


// Construct and initialize Plugin instance // Construct and initialize Plugin instance
@@ -102,20 +111,19 @@ static int loadPlugin(std::string path) {
initCallback(plugin); initCallback(plugin);


// Reject plugin if slug already exists // 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 // Add plugin to list
gPlugins.push_back(plugin); gPlugins.push_back(plugin);
info("Loaded plugin %s", libraryFilename.c_str()); info("Loaded plugin %s", libraryFilename.c_str());


return 0;
return true;
} }


static bool syncPlugin(json_t *pluginJ, bool dryRun) { static bool syncPlugin(json_t *pluginJ, bool dryRun) {
@@ -212,100 +220,18 @@ static bool syncPlugin(json_t *pluginJ, bool dryRun) {
return true; 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) { 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; continue;


while (1) { while (1) {
char buffer[4096];
char buffer[1<<15];
int len = zip_fread(zf, buffer, sizeof(buffer)); int len = zip_fread(zf, buffer, sizeof(buffer));
if (len <= 0) if (len <= 0)
break; break;
@@ -352,28 +278,30 @@ static int extractZipHandle(zip_t *za, const char *dir) {
return 0; return 0;
} }


static int extractZip(const char *filename, const char *dir) {
static int extractZip(const char *filename, const char *path) {
int err = 0; int err = 0;
zip_t *za = zip_open(filename, 0, &err); zip_t *za = zip_open(filename, 0, &err);
if (!za) if (!za)
return 1; 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; return err;
} }


static void extractPackages(std::string path) { static void extractPackages(std::string path) {
std::string message; std::string message;


for (std::string packagePath : systemListDirectory(path)) {
for (std::string packagePath : systemListEntries(path)) {
if (stringExtension(packagePath) == "zip") { if (stringExtension(packagePath) == "zip") {
info("Extracting package %s", packagePath.c_str());
// Extract package // 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; continue;
} }
// Remove package // Remove package
@@ -386,31 +314,32 @@ static void extractPackages(std::string path) {
} }


//////////////////// ////////////////////
// plugin API
// public API
//////////////////// ////////////////////


void pluginInit() { void pluginInit() {
tagsInit(); 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 // Load core
// This function is defined in core.cpp // This function is defined in core.cpp
Plugin *corePlugin = new Plugin(); Plugin *corePlugin = new Plugin();
init(corePlugin); init(corePlugin);
gPlugins.push_back(corePlugin); gPlugins.push_back(corePlugin);


// Load plugins from local directory
// Get local plugins directory
std::string localPlugins = assetLocal("plugins"); std::string localPlugins = assetLocal("plugins");
mkdir(localPlugins.c_str(), 0755); 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); extractPackages(localPlugins);
info("Loading plugins from %s", localPlugins.c_str());
loadPlugins(localPlugins); loadPlugins(localPlugins);
} }


@@ -432,6 +361,90 @@ void pluginDestroy() {
gPlugins.clear(); 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) { void pluginLogIn(std::string email, std::string password) {
json_t *reqJ = json_object(); json_t *reqJ = json_object();
json_object_set(reqJ, "email", json_string(email.c_str())); json_object_set(reqJ, "email", json_string(email.c_str()));
@@ -507,6 +520,4 @@ Model *pluginGetModel(std::string pluginSlug, std::string modelSlug) {
} }






} // namespace rack } // namespace rack

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

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


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


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




std::vector<std::string> systemListDirectory(std::string path) {
std::vector<std::string> systemListEntries(std::string path) {
std::vector<std::string> filenames; std::vector<std::string> filenames;
DIR *dir = opendir(path.c_str()); DIR *dir = opendir(path.c_str());
if (dir) { if (dir) {
@@ -27,6 +28,50 @@ std::vector<std::string> systemListDirectory(std::string path) {
return filenames; 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) { void systemOpenBrowser(std::string url) {
#if ARCH_LIN #if ARCH_LIN
std::string command = "xdg-open " + url; std::string command = "xdg-open " + url;


Loading…
Cancel
Save