|
- #include <stdio.h>
-
- #include <assert.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <sys/param.h> // for MAXPATHLEN
- #include <fcntl.h>
-
- #include <curl/curl.h>
- #include <zip.h>
- #include <jansson.h>
-
- #if ARCH_WIN
- #include <windows.h>
- #include <shellapi.h>
- #include <direct.h>
- #define mkdir(_dir, _perms) _mkdir(_dir)
- #else
- #include <dlfcn.h>
- #endif
- #include <dirent.h>
-
- #include "plugin.hpp"
-
-
- namespace rack {
-
- std::list<Plugin*> gPlugins;
-
- static const std::string apiUrl = "http://localhost:8081";
- static std::string token;
- static bool isDownloading = false;
- static float downloadProgress = 0.0;
- static std::string downloadName;
-
-
- Plugin::~Plugin() {
- for (Model *model : models) {
- delete model;
- }
- }
-
-
- static int loadPlugin(std::string slug) {
- #if ARCH_LIN
- std::string path = "./plugins/" + slug + "/plugin.so";
- #elif ARCH_WIN
- std::string path = "./plugins/" + slug + "/plugin.dll";
- #elif ARCH_MAC
- std::string path = "./plugins/" + slug + "/plugin.dylib";
- #endif
-
- // Load dynamic/shared library
- #if ARCH_WIN
- HINSTANCE handle = LoadLibrary(path.c_str());
- if (!handle) {
- fprintf(stderr, "Failed to load library %s\n", path.c_str());
- return -1;
- }
- #elif ARCH_LIN || ARCH_MAC
- void *handle = dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL);
- if (!handle) {
- fprintf(stderr, "Failed to load library %s: %s\n", path.c_str(), dlerror());
- return -1;
- }
- #endif
-
- // Call plugin init() function
- typedef Plugin *(*InitCallback)();
- InitCallback initCallback;
- #if ARCH_WIN
- initCallback = (InitCallback) GetProcAddress(handle, "init");
- #elif ARCH_LIN || ARCH_MAC
- initCallback = (InitCallback) dlsym(handle, "init");
- #endif
- if (!initCallback) {
- fprintf(stderr, "Failed to read init() symbol in %s\n", path.c_str());
- return -2;
- }
-
- // Add plugin to map
- Plugin *plugin = initCallback();
- if (!plugin) {
- fprintf(stderr, "Library %s did not return a plugin\n", path.c_str());
- return -3;
- }
- gPlugins.push_back(plugin);
- fprintf(stderr, "Loaded plugin %s\n", path.c_str());
- return 0;
- }
-
- void pluginInit() {
- curl_global_init(CURL_GLOBAL_NOTHING);
-
- // Load core
- // This function is defined in core.cpp
- Plugin *corePlugin = init();
- gPlugins.push_back(corePlugin);
-
- // Search for plugin libraries
- DIR *dir = opendir("plugins");
- if (dir) {
- struct dirent *d;
- while ((d = readdir(dir))) {
- if (d->d_name[0] == '.')
- continue;
- loadPlugin(d->d_name);
- }
- closedir(dir);
- }
- }
-
- void pluginDestroy() {
- for (Plugin *plugin : gPlugins) {
- // TODO free shared library handle with `dlclose` or `FreeLibrary`
- delete plugin;
- }
- gPlugins.clear();
- curl_global_cleanup();
- }
-
- ////////////////////
- // CURL and libzip helpers
- ////////////////////
-
- static size_t write_file_callback(void *data, size_t size, size_t nmemb, void *p) {
- int fd = *((int*)p);
- ssize_t len = write(fd, data, size*nmemb);
- return len;
- }
-
- static int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
- if (dltotal == 0.0)
- return 0;
- float progress = dlnow / dltotal;
- downloadProgress = progress;
- return 0;
- }
-
- static CURLcode download_file(int fd, const char *url) {
- CURL *curl = curl_easy_init();
-
- curl_easy_setopt(curl, CURLOPT_URL, url);
- curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
- curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file_callback);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fd);
- curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
- curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, NULL);
- CURLcode res = curl_easy_perform(curl);
- curl_easy_cleanup(curl);
- return res;
- }
-
- static size_t write_string_callback(void *data, size_t size, size_t nmemb, void *p) {
- std::string &text = *((std::string*)p);
- char *dataStr = (char*) data;
- size_t len = size * nmemb;
- text.append(dataStr, len);
- return len;
- }
-
- static void extract_zip(const char *dir, int zipfd) {
- int err = 0;
- zip_t *za = zip_fdopen(zipfd, 0, &err);
- if (!za) return;
- if (err) goto cleanup;
-
- for (int i = 0; i < zip_get_num_entries(za, 0); i++) {
- zip_stat_t zs;
- err = zip_stat_index(za, i, 0, &zs);
- if (err) goto cleanup;
- int nameLen = strlen(zs.name);
-
- char path[MAXPATHLEN];
- snprintf(path, sizeof(path), "%s/%s", dir, zs.name);
-
- if (zs.name[nameLen - 1] == '/') {
- err = mkdir(path, 0755);
- if (err) goto cleanup;
- }
- else {
- zip_file_t *zf = zip_fopen_index(za, i, 0);
- if (!zf) goto cleanup;
-
- int out = open(path, O_RDWR | O_TRUNC | O_CREAT, 0644);
- assert(out != -1);
-
- while (1) {
- char buffer[4096];
- int len = zip_fread(zf, buffer, sizeof(buffer));
- if (len <= 0)
- break;
- write(out, buffer, len);
- }
-
- err = zip_fclose(zf);
- assert(!err);
- close(out);
- }
- }
-
- cleanup:
- zip_close(za);
- }
-
- ////////////////////
- // plugin manager
- ////////////////////
-
- void pluginOpenBrowser(std::string url) {
- // shell injection is possible, so make sure the URL is trusted
- #if ARCH_LIN
- std::string command = "xdg-open " + url;
- system(command.c_str());
- #endif
- #if ARCH_MAC
- std::string command = "open " + url;
- system(command.c_str());
- #endif
- #if ARCH_WIN
- ShellExecute(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL);
- #endif
- }
-
- void pluginLogIn(std::string email, std::string password) {
- CURL *curl = curl_easy_init();
- assert(curl);
-
- std::string postFields = "email=" + email + "&password=" + password;
- std::string url = apiUrl + "/token";
- std::string resText;
-
- curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_string_callback);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText);
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields.c_str());
- curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postFields.size());
- CURLcode res = curl_easy_perform(curl);
- curl_easy_cleanup(curl);
-
- if (res == CURLE_OK) {
- // Parse JSON response
- json_error_t error;
- json_t *rootJ = json_loads(resText.c_str(), 0, &error);
- if (rootJ) {
- json_t *tokenJ = json_object_get(rootJ, "token");
- if (tokenJ) {
- // Set the token, which logs the user in
- token = json_string_value(tokenJ);
- }
- json_decref(rootJ);
- }
- }
- }
-
- void pluginLogOut() {
- token = "";
- }
-
- static void pluginRefreshPlugin(json_t *pluginJ) {
- json_t *slugJ = json_object_get(pluginJ, "slug");
- if (!slugJ) return;
- std::string slug = json_string_value(slugJ);
-
- json_t *nameJ = json_object_get(pluginJ, "name");
- if (!nameJ) return;
- std::string name = json_string_value(nameJ);
-
- json_t *urlJ = json_object_get(pluginJ, "download");
- if (!urlJ) return;
- std::string url = json_string_value(urlJ);
-
- // Find slug in plugins list
- for (Plugin *p : gPlugins) {
- if (p->slug == slug) {
- return;
- }
- }
-
- // If plugin is not loaded, download the zip file to /plugins
- fprintf(stderr, "Downloading %s from %s\n", name.c_str(), url.c_str());
- downloadName = name;
- downloadProgress = 0.0;
-
- const char *dir = "plugins";
- char path[MAXPATHLEN];
- snprintf(path, sizeof(path), "%s/%s.zip", dir, slug.c_str());
- int zip = open(path, O_RDWR | O_TRUNC | O_CREAT, 0644);
- // Download zip
- download_file(zip, url.c_str());
- // Unzip file
- lseek(zip, 0, SEEK_SET);
- extract_zip(dir, zip);
- // Close file
- close(zip);
- downloadName = "";
-
- // Load plugin
- }
-
- void pluginRefresh() {
- if (token.empty())
- return;
-
- isDownloading = true;
- downloadProgress = 0.0;
- downloadName = "";
-
- // Get plugin list from /plugin
- CURL *curl = curl_easy_init();
- assert(curl);
-
- std::string url = apiUrl + "/plugins?token=" + token;
- std::string resText;
-
- curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_string_callback);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText);
- CURLcode res = curl_easy_perform(curl);
- curl_easy_cleanup(curl);
-
- if (res == CURLE_OK) {
- // Parse JSON response
- json_error_t error;
- json_t *rootJ = json_loads(resText.c_str(), 0, &error);
- if (rootJ) {
- json_t *pluginsJ = json_object_get(rootJ, "plugins");
- if (pluginsJ) {
- // Iterate through each plugin object
- size_t index;
- json_t *pluginJ;
- json_array_foreach(pluginsJ, index, pluginJ) {
- pluginRefreshPlugin(pluginJ);
- }
- }
- json_decref(rootJ);
- }
- }
-
- isDownloading = false;
- }
-
- void pluginCancelDownload() {
- // TODO
- }
-
- bool pluginIsLoggedIn() {
- return token != "";
- }
-
- bool pluginIsDownloading() {
- return isDownloading;
- }
-
- float pluginGetDownloadProgress() {
- return downloadProgress;
- }
-
- std::string pluginGetDownloadName() {
- return downloadName;
- }
-
-
- } // namespace rack
|