#include #include #include #include #include #include #include // for MAXPATHLEN #include #include #include #include #if ARCH_WIN #include #include #include #define mkdir(_dir, _perms) _mkdir(_dir) #else #include #endif #include #include "plugin.hpp" namespace rack { std::list 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