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