|
|
@@ -7,11 +7,12 @@ |
|
|
|
#include <sys/stat.h> |
|
|
|
#include <sys/param.h> // for MAXPATHLEN |
|
|
|
#include <fcntl.h> |
|
|
|
#include <thread> |
|
|
|
|
|
|
|
#include <zip.h> |
|
|
|
#include <jansson.h> |
|
|
|
|
|
|
|
#if ARCH_WIN |
|
|
|
#if defined(ARCH_WIN) |
|
|
|
#include <windows.h> |
|
|
|
#include <direct.h> |
|
|
|
#define mkdir(_dir, _perms) _mkdir(_dir) |
|
|
@@ -225,35 +226,55 @@ static int extractZip(const char *filename, const char *dir) { |
|
|
|
return err; |
|
|
|
} |
|
|
|
|
|
|
|
static void refreshPurchase(json_t *pluginJ) { |
|
|
|
static void syncPlugin(json_t *pluginJ) { |
|
|
|
json_t *slugJ = json_object_get(pluginJ, "slug"); |
|
|
|
if (!slugJ) return; |
|
|
|
std::string slug = json_string_value(slugJ); |
|
|
|
info("Syncing plugin %s", slug.c_str()); |
|
|
|
|
|
|
|
json_t *nameJ = json_object_get(pluginJ, "name"); |
|
|
|
if (!nameJ) return; |
|
|
|
std::string name = json_string_value(nameJ); |
|
|
|
|
|
|
|
json_t *versionJ = json_object_get(pluginJ, "version"); |
|
|
|
if (!versionJ) return; |
|
|
|
std::string version = json_string_value(versionJ); |
|
|
|
|
|
|
|
// Check whether the plugin is already loaded |
|
|
|
for (Plugin *plugin : gPlugins) { |
|
|
|
if (plugin->slug == slug && plugin->version == version) { |
|
|
|
return; |
|
|
|
std::string download; |
|
|
|
std::string sha256; |
|
|
|
|
|
|
|
json_t *downloadsJ = json_object_get(pluginJ, "downloads"); |
|
|
|
if (downloadsJ) { |
|
|
|
#if defined(ARCH_WIN) |
|
|
|
#define DOWNLOADS_ARCH "win" |
|
|
|
#elif defined(ARCH_MAC) |
|
|
|
#define DOWNLOADS_ARCH "mac" |
|
|
|
#elif defined(ARCH_LIN) |
|
|
|
#define DOWNLOADS_ARCH "lin" |
|
|
|
#endif |
|
|
|
json_t *archJ = json_object_get(downloadsJ, DOWNLOADS_ARCH); |
|
|
|
if (archJ) { |
|
|
|
// Get download URL |
|
|
|
json_t *downloadJ = json_object_get(archJ, "download"); |
|
|
|
if (downloadJ) |
|
|
|
download = json_string_value(downloadJ); |
|
|
|
// Get SHA256 hash |
|
|
|
json_t *sha256J = json_object_get(archJ, "sha256"); |
|
|
|
if (sha256J) |
|
|
|
sha256 = json_string_value(sha256J); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Append token and version to download URL |
|
|
|
std::string url = gApiHost; |
|
|
|
url += "/download"; |
|
|
|
url += "?product="; |
|
|
|
url += slug; |
|
|
|
url += "&version="; |
|
|
|
url += requestEscape(gApplicationVersion); |
|
|
|
url += "&token="; |
|
|
|
url += requestEscape(gToken); |
|
|
|
json_t *productIdJ = json_object_get(pluginJ, "productId"); |
|
|
|
if (productIdJ) { |
|
|
|
download = gApiHost; |
|
|
|
download += "/download"; |
|
|
|
download += "?slug="; |
|
|
|
download += slug; |
|
|
|
download += "&token="; |
|
|
|
download += requestEscape(gToken); |
|
|
|
} |
|
|
|
|
|
|
|
if (download.empty()) { |
|
|
|
warn("Could not get download URL for plugin %s", slug.c_str()); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// If plugin is not loaded, download the zip file to /plugins |
|
|
|
downloadName = name; |
|
|
@@ -263,21 +284,127 @@ static void refreshPurchase(json_t *pluginJ) { |
|
|
|
std::string pluginsDir = assetLocal("plugins"); |
|
|
|
std::string pluginPath = pluginsDir + "/" + slug; |
|
|
|
std::string zipPath = pluginPath + ".zip"; |
|
|
|
bool success = requestDownload(url, zipPath, &downloadProgress); |
|
|
|
bool success = requestDownload(download, zipPath, &downloadProgress); |
|
|
|
if (success) { |
|
|
|
if (!sha256.empty()) { |
|
|
|
// Check SHA256 hash |
|
|
|
std::string actualSha256 = requestSHA256File(zipPath); |
|
|
|
if (actualSha256 != sha256) { |
|
|
|
warn("Plugin %s does not match expected SHA256 checksum", slug.c_str()); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Unzip file |
|
|
|
int err = extractZip(zipPath.c_str(), pluginsDir.c_str()); |
|
|
|
if (!err) { |
|
|
|
// Delete zip |
|
|
|
remove(zipPath.c_str()); |
|
|
|
// Load plugin |
|
|
|
loadPlugin(pluginPath); |
|
|
|
// loadPlugin(pluginPath); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
downloadName = ""; |
|
|
|
} |
|
|
|
|
|
|
|
static bool trySyncPlugin(json_t *pluginJ, json_t *communityPluginsJ, bool dryRun) { |
|
|
|
std::string slug = json_string_value(pluginJ); |
|
|
|
|
|
|
|
// Find community plugin |
|
|
|
size_t communityIndex; |
|
|
|
json_t *communityPluginJ = NULL; |
|
|
|
json_array_foreach(communityPluginsJ, communityIndex, communityPluginJ) { |
|
|
|
json_t *communitySlugJ = json_object_get(communityPluginJ, "slug"); |
|
|
|
if (communitySlugJ) { |
|
|
|
std::string communitySlug = json_string_value(communitySlugJ); |
|
|
|
if (slug == communitySlug) |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
if (communityIndex == json_array_size(communityPluginsJ)) { |
|
|
|
warn("Plugin sync error: %s not found in community", slug.c_str()); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// Get community version |
|
|
|
std::string version; |
|
|
|
json_t *versionJ = json_object_get(communityPluginJ, "version"); |
|
|
|
if (versionJ) { |
|
|
|
version = json_string_value(versionJ); |
|
|
|
} |
|
|
|
|
|
|
|
// Check whether we already have a plugin with the same slug and version |
|
|
|
for (Plugin *plugin : gPlugins) { |
|
|
|
if (plugin->slug == slug) { |
|
|
|
// plugin->version might be blank, so adding a version of the manifest will update the plugin |
|
|
|
if (plugin->version == version) |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!dryRun) |
|
|
|
syncPlugin(communityPluginJ); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
bool pluginSync(bool dryRun) { |
|
|
|
if (gToken.empty()) |
|
|
|
return false; |
|
|
|
|
|
|
|
bool available = false; |
|
|
|
|
|
|
|
// Download my plugins |
|
|
|
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())); |
|
|
|
json_t *resJ = requestJson(METHOD_GET, gApiHost + "/plugins", reqJ); |
|
|
|
json_decref(reqJ); |
|
|
|
|
|
|
|
// Download community plugins |
|
|
|
json_t *communityResJ = requestJson(METHOD_GET, gApiHost + "/community/plugins", NULL); |
|
|
|
|
|
|
|
if (!dryRun) { |
|
|
|
isDownloading = true; |
|
|
|
downloadProgress = 0.0; |
|
|
|
downloadName = ""; |
|
|
|
} |
|
|
|
|
|
|
|
if (resJ && communityResJ) { |
|
|
|
json_t *errorJ = json_object_get(resJ, "error"); |
|
|
|
json_t *communityErrorJ = json_object_get(resJ, "error"); |
|
|
|
if (errorJ) { |
|
|
|
warn("Plugin sync error: %s", json_string_value(errorJ)); |
|
|
|
} |
|
|
|
else if (communityErrorJ) { |
|
|
|
warn("Plugin sync error: %s", json_string_value(communityErrorJ)); |
|
|
|
} |
|
|
|
else { |
|
|
|
// Check each plugin in list of my plugins |
|
|
|
json_t *pluginsJ = json_object_get(resJ, "plugins"); |
|
|
|
json_t *communityPluginsJ = json_object_get(communityResJ, "plugins"); |
|
|
|
size_t index; |
|
|
|
json_t *pluginJ; |
|
|
|
json_array_foreach(pluginsJ, index, pluginJ) { |
|
|
|
if (trySyncPlugin(pluginJ, communityPluginsJ, dryRun)) |
|
|
|
available = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (resJ) |
|
|
|
json_decref(resJ); |
|
|
|
|
|
|
|
if (communityResJ) |
|
|
|
json_decref(communityResJ); |
|
|
|
|
|
|
|
if (!dryRun) { |
|
|
|
isDownloading = false; |
|
|
|
} |
|
|
|
|
|
|
|
return available; |
|
|
|
} |
|
|
|
|
|
|
|
//////////////////// |
|
|
|
// plugin API |
|
|
|
//////////////////// |
|
|
@@ -306,10 +433,10 @@ void pluginInit() { |
|
|
|
void pluginDestroy() { |
|
|
|
for (Plugin *plugin : gPlugins) { |
|
|
|
// Free library handle |
|
|
|
#if ARCH_WIN |
|
|
|
#if defined(ARCH_WIN) |
|
|
|
if (plugin->handle) |
|
|
|
FreeLibrary((HINSTANCE)plugin->handle); |
|
|
|
#elif ARCH_LIN || ARCH_MAC |
|
|
|
#elif defined(ARCH_LIN) || defined(ARCH_MAC) |
|
|
|
if (plugin->handle) |
|
|
|
dlclose(plugin->handle); |
|
|
|
#endif |
|
|
@@ -350,40 +477,6 @@ void pluginLogOut() { |
|
|
|
gToken = ""; |
|
|
|
} |
|
|
|
|
|
|
|
void pluginRefresh() { |
|
|
|
if (gToken.empty()) |
|
|
|
return; |
|
|
|
|
|
|
|
isDownloading = true; |
|
|
|
downloadProgress = 0.0; |
|
|
|
downloadName = ""; |
|
|
|
|
|
|
|
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())); |
|
|
|
json_t *resJ = requestJson(METHOD_GET, gApiHost + "/purchases", reqJ); |
|
|
|
json_decref(reqJ); |
|
|
|
|
|
|
|
if (resJ) { |
|
|
|
json_t *errorJ = json_object_get(resJ, "error"); |
|
|
|
if (errorJ) { |
|
|
|
const char *errorStr = json_string_value(errorJ); |
|
|
|
warn("Plugin refresh error: %s", errorStr); |
|
|
|
} |
|
|
|
else { |
|
|
|
json_t *purchasesJ = json_object_get(resJ, "purchases"); |
|
|
|
size_t index; |
|
|
|
json_t *purchaseJ; |
|
|
|
json_array_foreach(purchasesJ, index, purchaseJ) { |
|
|
|
refreshPurchase(purchaseJ); |
|
|
|
} |
|
|
|
} |
|
|
|
json_decref(resJ); |
|
|
|
} |
|
|
|
|
|
|
|
isDownloading = false; |
|
|
|
} |
|
|
|
|
|
|
|
void pluginCancelDownload() { |
|
|
|
// TODO |
|
|
|
} |
|
|
|