@@ -8,6 +8,7 @@ | |||||
#include <assert.h> | #include <assert.h> | ||||
#include <string> | #include <string> | ||||
#include <vector> | |||||
#include <condition_variable> | #include <condition_variable> | ||||
#include <mutex> | #include <mutex> | ||||
@@ -15,23 +16,28 @@ | |||||
// Handy macros | // Handy macros | ||||
//////////////////// | //////////////////// | ||||
/** Surrounds raw text with quotes | |||||
/** Concatenates two literals or two macros | |||||
Example: | Example: | ||||
printf("Hello " STRINGIFY(world)) | |||||
will expand to | |||||
printf("Hello " "world") | |||||
and of course the C++ lexer/parser will then concatenate the string literals | |||||
#define COUNT 42 | |||||
CONCAT(myVariable, COUNT) | |||||
expands to | |||||
myVariable42 | |||||
*/ | */ | ||||
#define STRINGIFY(x) #x | |||||
/** Converts a macro to a string literal | |||||
#define CONCAT_LITERAL(x, y) x ## y | |||||
#define CONCAT(x, y) CONCAT_LITERAL(x, y) | |||||
/** Surrounds raw text with quotes | |||||
Example: | Example: | ||||
#define NAME "world" | #define NAME "world" | ||||
printf("Hello " TOSTRING(NAME)) | printf("Hello " TOSTRING(NAME)) | ||||
will expand to | |||||
expands to | |||||
printf("Hello " "world") | printf("Hello " "world") | ||||
and of course the C++ lexer/parser then concatenates the string literals. | |||||
*/ | */ | ||||
#define TOSTRING(x) STRINGIFY(x) | |||||
#define TOSTRING_LITERAL(x) #x | |||||
#define TOSTRING(x) TOSTRING_LITERAL(x) | |||||
/** Produces the length of a static array in number of elements */ | |||||
#define LENGTHOF(arr) (sizeof(arr) / sizeof((arr)[0])) | #define LENGTHOF(arr) (sizeof(arr) / sizeof((arr)[0])) | ||||
/** Reserve space for `count` enums starting with `name`. | /** Reserve space for `count` enums starting with `name`. | ||||
@@ -95,10 +101,7 @@ DeferWrapper<F> deferWrapper(F f) { | |||||
return DeferWrapper<F>(f); | return DeferWrapper<F>(f); | ||||
} | } | ||||
#define DEFER_1(x, y) x##y | |||||
#define DEFER_2(x, y) DEFER_1(x, y) | |||||
#define DEFER_3(x) DEFER_2(x, __COUNTER__) | |||||
#define defer(code) auto DEFER_3(_defer_) = deferWrapper([&]() code) | |||||
#define defer(code) auto CONCAT(x, __COUNTER__) = deferWrapper([&]() code) | |||||
//////////////////// | //////////////////// | ||||
// Random number generator | // Random number generator | ||||
@@ -129,6 +132,7 @@ std::string uppercase(std::string s); | |||||
/** Truncates and adds "..." to a string, not exceeding `len` characters */ | /** Truncates and adds "..." to a string, not exceeding `len` characters */ | ||||
std::string ellipsize(std::string s, size_t len); | std::string ellipsize(std::string s, size_t len); | ||||
bool startsWith(std::string str, std::string prefix); | bool startsWith(std::string str, std::string prefix); | ||||
bool endsWith(std::string str, std::string suffix); | |||||
std::string extractDirectory(std::string path); | std::string extractDirectory(std::string path); | ||||
std::string extractFilename(std::string path); | std::string extractFilename(std::string path); | ||||
@@ -139,11 +143,13 @@ std::string extractExtension(std::string path); | |||||
// system.cpp | // system.cpp | ||||
//////////////////// | //////////////////// | ||||
std::vector<std::string> systemListDirectory(std::string path); | |||||
/** 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. | ||||
May block, so open in a new thread. | May block, so open in a new thread. | ||||
*/ | */ | ||||
void openBrowser(std::string url); | |||||
void systemOpenBrowser(std::string url); | |||||
//////////////////// | //////////////////// | ||||
// Debug logger | // Debug logger | ||||
@@ -62,7 +62,9 @@ PluginManagerWidget::PluginManagerWidget() { | |||||
struct RegisterButton : Button { | struct RegisterButton : Button { | ||||
void onAction(EventAction &e) override { | void onAction(EventAction &e) override { | ||||
std::thread t(openBrowser, "https://vcvrack.com/"); | |||||
std::thread t([&]() { | |||||
systemOpenBrowser("https://vcvrack.com/"); | |||||
}); | |||||
t.detach(); | t.detach(); | ||||
} | } | ||||
}; | }; | ||||
@@ -126,7 +128,9 @@ PluginManagerWidget::PluginManagerWidget() { | |||||
struct ManageButton : Button { | struct ManageButton : Button { | ||||
void onAction(EventAction &e) override { | void onAction(EventAction &e) override { | ||||
std::thread t(openBrowser, "https://vcvrack.com/"); | |||||
std::thread t([&]() { | |||||
systemOpenBrowser("https://vcvrack.com/"); | |||||
}); | |||||
t.detach(); | t.detach(); | ||||
} | } | ||||
}; | }; | ||||
@@ -1,7 +1,6 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "window.hpp" | #include "window.hpp" | ||||
#include "util/request.hpp" | #include "util/request.hpp" | ||||
#include "osdialog.h" | |||||
#include <string.h> | #include <string.h> | ||||
#include <thread> | #include <thread> | ||||
@@ -10,35 +10,11 @@ | |||||
#include <unistd.h> | #include <unistd.h> | ||||
#include <dirent.h> | |||||
using namespace rack; | using namespace rack; | ||||
std::vector<std::string> filesystemListDirectory(std::string path) { | |||||
std::vector<std::string> filenames; | |||||
DIR *dir = opendir(path.c_str()); | |||||
if (dir) { | |||||
struct dirent *d; | |||||
while ((d = readdir(dir))) { | |||||
std::string filename = d->d_name; | |||||
if (filename == "." || filename == "..") | |||||
continue; | |||||
filenames.push_back(path + "/" + filename); | |||||
} | |||||
closedir(dir); | |||||
} | |||||
return filenames; | |||||
} | |||||
int main(int argc, char* argv[]) { | int main(int argc, char* argv[]) { | ||||
for (std::string filename : filesystemListDirectory("plugins")) { | |||||
debug("%s", filename.c_str()); | |||||
} | |||||
return 0; | |||||
randomInit(); | randomInit(); | ||||
loggerInit(); | loggerInit(); | ||||
@@ -1,5 +1,10 @@ | |||||
#include <stdio.h> | |||||
#include "plugin.hpp" | |||||
#include "app.hpp" | |||||
#include "asset.hpp" | |||||
#include "util/request.hpp" | |||||
#include "osdialog.h" | |||||
#include <stdio.h> | |||||
#include <assert.h> | #include <assert.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
@@ -13,7 +18,7 @@ | |||||
#include <zip.h> | #include <zip.h> | ||||
#include <jansson.h> | #include <jansson.h> | ||||
#if defined(ARCH_WIN) | |||||
#if ARCH_WIN | |||||
#include <windows.h> | #include <windows.h> | ||||
#include <direct.h> | #include <direct.h> | ||||
#define mkdir(_dir, _perms) _mkdir(_dir) | #define mkdir(_dir, _perms) _mkdir(_dir) | ||||
@@ -22,14 +27,10 @@ | |||||
#endif | #endif | ||||
#include <dirent.h> | #include <dirent.h> | ||||
#include "plugin.hpp" | |||||
#include "app.hpp" | |||||
#include "asset.hpp" | |||||
#include "util/request.hpp" | |||||
namespace rack { | namespace rack { | ||||
std::list<Plugin*> gPlugins; | std::list<Plugin*> gPlugins; | ||||
std::string gToken; | std::string gToken; | ||||
@@ -73,7 +74,7 @@ static int loadPlugin(std::string path) { | |||||
warn("Failed to load library %s: %d", libraryFilename.c_str(), error); | warn("Failed to load library %s: %d", libraryFilename.c_str(), error); | ||||
return -1; | return -1; | ||||
} | } | ||||
#elif ARCH_LIN || ARCH_MAC | |||||
#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()); | ||||
@@ -86,7 +87,7 @@ static int loadPlugin(std::string path) { | |||||
InitCallback initCallback; | InitCallback initCallback; | ||||
#if ARCH_WIN | #if ARCH_WIN | ||||
initCallback = (InitCallback) GetProcAddress(handle, "init"); | initCallback = (InitCallback) GetProcAddress(handle, "init"); | ||||
#elif ARCH_LIN || ARCH_MAC | |||||
#else | |||||
initCallback = (InitCallback) dlsym(handle, "init"); | initCallback = (InitCallback) dlsym(handle, "init"); | ||||
#endif | #endif | ||||
if (!initCallback) { | if (!initCallback) { | ||||
@@ -117,80 +118,6 @@ static int loadPlugin(std::string path) { | |||||
return 0; | return 0; | ||||
} | } | ||||
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); | |||||
} | |||||
closedir(dir); | |||||
} | |||||
} | |||||
//////////////////// | |||||
// plugin helpers | |||||
//////////////////// | |||||
static int extractZipHandle(zip_t *za, const char *dir) { | |||||
int err = 0; | |||||
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) | |||||
return err; | |||||
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 && errno != EEXIST) | |||||
return err; | |||||
} | |||||
else { | |||||
zip_file_t *zf = zip_fopen_index(za, i, 0); | |||||
if (!zf) | |||||
return 1; | |||||
FILE *outFile = fopen(path, "wb"); | |||||
if (!outFile) | |||||
continue; | |||||
while (1) { | |||||
char buffer[4096]; | |||||
int len = zip_fread(zf, buffer, sizeof(buffer)); | |||||
if (len <= 0) | |||||
break; | |||||
fwrite(buffer, 1, len, outFile); | |||||
} | |||||
err = zip_fclose(zf); | |||||
if (err) | |||||
return err; | |||||
fclose(outFile); | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
static int extractZip(const char *filename, const char *dir) { | |||||
int err = 0; | |||||
zip_t *za = zip_open(filename, 0, &err); | |||||
if (!za) | |||||
return 1; | |||||
if (!err) { | |||||
err = extractZipHandle(za, dir); | |||||
} | |||||
zip_close(za); | |||||
return err; | |||||
} | |||||
static bool syncPlugin(json_t *pluginJ, bool dryRun) { | static bool syncPlugin(json_t *pluginJ, bool dryRun) { | ||||
json_t *slugJ = json_object_get(pluginJ, "slug"); | json_t *slugJ = json_object_get(pluginJ, "slug"); | ||||
if (!slugJ) | if (!slugJ) | ||||
@@ -223,11 +150,11 @@ static bool syncPlugin(json_t *pluginJ, bool dryRun) { | |||||
json_t *downloadsJ = json_object_get(pluginJ, "downloads"); | json_t *downloadsJ = json_object_get(pluginJ, "downloads"); | ||||
if (downloadsJ) { | if (downloadsJ) { | ||||
#if defined(ARCH_WIN) | |||||
#if ARCH_WIN | |||||
#define DOWNLOADS_ARCH "win" | #define DOWNLOADS_ARCH "win" | ||||
#elif defined(ARCH_MAC) | |||||
#elif ARCH_MAC | |||||
#define DOWNLOADS_ARCH "mac" | #define DOWNLOADS_ARCH "mac" | ||||
#elif defined(ARCH_LIN) | |||||
#elif ARCH_LIN | |||||
#define DOWNLOADS_ARCH "lin" | #define DOWNLOADS_ARCH "lin" | ||||
#endif | #endif | ||||
json_t *archJ = json_object_get(downloadsJ, DOWNLOADS_ARCH); | json_t *archJ = json_object_get(downloadsJ, DOWNLOADS_ARCH); | ||||
@@ -281,15 +208,6 @@ static bool syncPlugin(json_t *pluginJ, bool dryRun) { | |||||
} | } | ||||
} | } | ||||
// Unzip file | |||||
int err = extractZip(zipPath.c_str(), pluginsDir.c_str()); | |||||
if (!err) { | |||||
// Delete zip | |||||
remove(zipPath.c_str()); | |||||
// Load plugin | |||||
// loadPlugin(pluginPath); | |||||
} | |||||
downloadName = ""; | downloadName = ""; | ||||
return true; | return true; | ||||
} | } | ||||
@@ -378,6 +296,95 @@ bool pluginSync(bool dryRun) { | |||||
return available; | 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); | |||||
} | |||||
closedir(dir); | |||||
} | |||||
} | |||||
static int extractZipHandle(zip_t *za, const char *dir) { | |||||
int err = 0; | |||||
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) | |||||
return err; | |||||
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 && errno != EEXIST) | |||||
return err; | |||||
} | |||||
else { | |||||
zip_file_t *zf = zip_fopen_index(za, i, 0); | |||||
if (!zf) | |||||
return 1; | |||||
FILE *outFile = fopen(path, "wb"); | |||||
if (!outFile) | |||||
continue; | |||||
while (1) { | |||||
char buffer[4096]; | |||||
int len = zip_fread(zf, buffer, sizeof(buffer)); | |||||
if (len <= 0) | |||||
break; | |||||
fwrite(buffer, 1, len, outFile); | |||||
} | |||||
err = zip_fclose(zf); | |||||
if (err) | |||||
return err; | |||||
fclose(outFile); | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
static int extractZip(const char *filename, const char *dir) { | |||||
int err = 0; | |||||
zip_t *za = zip_open(filename, 0, &err); | |||||
if (!za) | |||||
return 1; | |||||
if (!err) { | |||||
err = extractZipHandle(za, dir); | |||||
} | |||||
zip_close(za); | |||||
return err; | |||||
} | |||||
static void extractPackages(std::string path) { | |||||
std::string message; | |||||
for (std::string packagePath : systemListDirectory(path)) { | |||||
if (endsWith(packagePath, ".zip")) { | |||||
// Extract package | |||||
if (!extractZip(packagePath.c_str(), path.c_str())) { | |||||
message += stringf("Could not extract package %s\n", path); | |||||
continue; | |||||
} | |||||
// Remove package | |||||
remove(packagePath.c_str()); | |||||
} | |||||
} | |||||
if (!message.empty()) { | |||||
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); | |||||
} | |||||
} | |||||
//////////////////// | //////////////////// | ||||
// plugin API | // plugin API | ||||
//////////////////// | //////////////////// | ||||
@@ -401,6 +408,8 @@ void pluginInit() { | |||||
// Load plugins from local directory | // Load plugins from local 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()); | |||||
extractPackages(localPlugins); | |||||
info("Loading plugins from %s", localPlugins.c_str()); | info("Loading plugins from %s", localPlugins.c_str()); | ||||
loadPlugins(localPlugins); | loadPlugins(localPlugins); | ||||
} | } | ||||
@@ -408,10 +417,10 @@ void pluginInit() { | |||||
void pluginDestroy() { | void pluginDestroy() { | ||||
for (Plugin *plugin : gPlugins) { | for (Plugin *plugin : gPlugins) { | ||||
// Free library handle | // Free library handle | ||||
#if defined(ARCH_WIN) | |||||
#if ARCH_WIN | |||||
if (plugin->handle) | if (plugin->handle) | ||||
FreeLibrary((HINSTANCE)plugin->handle); | FreeLibrary((HINSTANCE)plugin->handle); | ||||
#elif defined(ARCH_LIN) || defined(ARCH_MAC) | |||||
#else | |||||
if (plugin->handle) | if (plugin->handle) | ||||
dlclose(plugin->handle); | dlclose(plugin->handle); | ||||
#endif | #endif | ||||
@@ -11,16 +11,16 @@ static auto startTime = std::chrono::high_resolution_clock::now(); | |||||
void loggerInit() { | void loggerInit() { | ||||
#ifdef RELEASE | |||||
std::string logFilename = assetLocal("log.txt"); | |||||
gLogFile = fopen(logFilename.c_str(), "w"); | |||||
#endif | |||||
#ifdef RELEASE | |||||
std::string logFilename = assetLocal("log.txt"); | |||||
logFile = fopen(logFilename.c_str(), "w"); | |||||
#endif | |||||
} | } | ||||
void loggerDestroy() { | void loggerDestroy() { | ||||
#ifdef RELEASE | |||||
fclose(gLogFile); | |||||
#endif | |||||
#ifdef RELEASE | |||||
fclose(logFile); | |||||
#endif | |||||
} | } | ||||
static void printTimestamp() { | static void printTimestamp() { | ||||
@@ -45,6 +45,10 @@ bool startsWith(std::string str, std::string prefix) { | |||||
return str.substr(0, prefix.size()) == prefix; | return str.substr(0, prefix.size()) == prefix; | ||||
} | } | ||||
bool endsWith(std::string str, std::string suffix) { | |||||
return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; | |||||
} | |||||
std::string extractDirectory(std::string path) { | std::string extractDirectory(std::string path) { | ||||
char *pathDup = strdup(path.c_str()); | char *pathDup = strdup(path.c_str()); | ||||
std::string directory = dirname(pathDup); | std::string directory = dirname(pathDup); | ||||
@@ -1,18 +1,36 @@ | |||||
#include "util/common.hpp" | #include "util/common.hpp" | ||||
#include <dirent.h> | |||||
#if ARCH_WIN | #if ARCH_WIN | ||||
#include <windows.h> | |||||
#include <shellapi.h> | |||||
#include <windows.h> | |||||
#include <shellapi.h> | |||||
#endif | #endif | ||||
namespace rack { | namespace rack { | ||||
void openBrowser(std::string url) { | |||||
std::vector<std::string> systemListDirectory(std::string path) { | |||||
std::vector<std::string> filenames; | |||||
DIR *dir = opendir(path.c_str()); | |||||
if (dir) { | |||||
struct dirent *d; | |||||
while ((d = readdir(dir))) { | |||||
std::string filename = d->d_name; | |||||
if (filename == "." || filename == "..") | |||||
continue; | |||||
filenames.push_back(path + "/" + filename); | |||||
} | |||||
closedir(dir); | |||||
} | |||||
return filenames; | |||||
} | |||||
void systemOpenBrowser(std::string url) { | |||||
#if ARCH_LIN | #if ARCH_LIN | ||||
std::string command = "xdg-open " + url; | std::string command = "xdg-open " + url; | ||||
(void)system(command.c_str()); | |||||
(void) system(command.c_str()); | |||||
#endif | #endif | ||||
#if ARCH_MAC | #if ARCH_MAC | ||||
std::string command = "open " + url; | std::string command = "open " + url; | ||||