You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

471 lines
11KB

  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <sys/param.h> // for MAXPATHLEN
  8. #include <fcntl.h>
  9. #include <thread>
  10. #include <zip.h>
  11. #include <jansson.h>
  12. #if defined(ARCH_WIN)
  13. #include <windows.h>
  14. #include <direct.h>
  15. #define mkdir(_dir, _perms) _mkdir(_dir)
  16. #else
  17. #include <dlfcn.h>
  18. #endif
  19. #include <dirent.h>
  20. #include "plugin.hpp"
  21. #include "app.hpp"
  22. #include "asset.hpp"
  23. #include "util/request.hpp"
  24. namespace rack {
  25. std::list<Plugin*> gPlugins;
  26. std::string gToken;
  27. static bool isDownloading = false;
  28. static float downloadProgress = 0.0;
  29. static std::string downloadName;
  30. static std::string loginStatus;
  31. Plugin::~Plugin() {
  32. for (Model *model : models) {
  33. delete model;
  34. }
  35. }
  36. void Plugin::addModel(Model *model) {
  37. assert(!model->plugin);
  38. model->plugin = this;
  39. models.push_back(model);
  40. }
  41. static int loadPlugin(std::string path) {
  42. std::string libraryFilename;
  43. #if ARCH_LIN
  44. libraryFilename = path + "/" + "plugin.so";
  45. #elif ARCH_WIN
  46. libraryFilename = path + "/" + "plugin.dll";
  47. #elif ARCH_MAC
  48. libraryFilename = path + "/" + "plugin.dylib";
  49. #endif
  50. // Load dynamic/shared library
  51. #if ARCH_WIN
  52. SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
  53. HINSTANCE handle = LoadLibrary(libraryFilename.c_str());
  54. SetErrorMode(0);
  55. if (!handle) {
  56. int error = GetLastError();
  57. warn("Failed to load library %s: %d", libraryFilename.c_str(), error);
  58. return -1;
  59. }
  60. #elif ARCH_LIN || ARCH_MAC
  61. void *handle = dlopen(libraryFilename.c_str(), RTLD_NOW);
  62. if (!handle) {
  63. warn("Failed to load library %s: %s", libraryFilename.c_str(), dlerror());
  64. return -1;
  65. }
  66. #endif
  67. // Call plugin's init() function
  68. typedef void (*InitCallback)(Plugin *);
  69. InitCallback initCallback;
  70. #if ARCH_WIN
  71. initCallback = (InitCallback) GetProcAddress(handle, "init");
  72. #elif ARCH_LIN || ARCH_MAC
  73. initCallback = (InitCallback) dlsym(handle, "init");
  74. #endif
  75. if (!initCallback) {
  76. warn("Failed to read init() symbol in %s", libraryFilename.c_str());
  77. return -2;
  78. }
  79. // Construct and initialize Plugin instance
  80. Plugin *plugin = new Plugin();
  81. plugin->path = path;
  82. plugin->handle = handle;
  83. initCallback(plugin);
  84. // Reject plugin if slug already exists
  85. for (Plugin *p : gPlugins) {
  86. if (plugin->slug == p->slug) {
  87. warn("Plugin \"%s\" is already loaded, not attempting to load it again");
  88. // TODO
  89. // Fix memory leak with `plugin` here
  90. return -1;
  91. }
  92. }
  93. // Add plugin to list
  94. gPlugins.push_back(plugin);
  95. info("Loaded plugin %s", libraryFilename.c_str());
  96. return 0;
  97. }
  98. static void loadPlugins(std::string path) {
  99. DIR *dir = opendir(path.c_str());
  100. if (dir) {
  101. struct dirent *d;
  102. while ((d = readdir(dir))) {
  103. if (d->d_name[0] == '.')
  104. continue;
  105. loadPlugin(path + "/" + d->d_name);
  106. }
  107. closedir(dir);
  108. }
  109. }
  110. ////////////////////
  111. // plugin helpers
  112. ////////////////////
  113. static int extractZipHandle(zip_t *za, const char *dir) {
  114. int err = 0;
  115. for (int i = 0; i < zip_get_num_entries(za, 0); i++) {
  116. zip_stat_t zs;
  117. err = zip_stat_index(za, i, 0, &zs);
  118. if (err)
  119. return err;
  120. int nameLen = strlen(zs.name);
  121. char path[MAXPATHLEN];
  122. snprintf(path, sizeof(path), "%s/%s", dir, zs.name);
  123. if (zs.name[nameLen - 1] == '/') {
  124. err = mkdir(path, 0755);
  125. if (err && errno != EEXIST)
  126. return err;
  127. }
  128. else {
  129. zip_file_t *zf = zip_fopen_index(za, i, 0);
  130. if (!zf)
  131. return 1;
  132. FILE *outFile = fopen(path, "wb");
  133. if (!outFile)
  134. continue;
  135. while (1) {
  136. char buffer[4096];
  137. int len = zip_fread(zf, buffer, sizeof(buffer));
  138. if (len <= 0)
  139. break;
  140. fwrite(buffer, 1, len, outFile);
  141. }
  142. err = zip_fclose(zf);
  143. if (err)
  144. return err;
  145. fclose(outFile);
  146. }
  147. }
  148. return 0;
  149. }
  150. static int extractZip(const char *filename, const char *dir) {
  151. int err = 0;
  152. zip_t *za = zip_open(filename, 0, &err);
  153. if (!za)
  154. return 1;
  155. if (!err) {
  156. err = extractZipHandle(za, dir);
  157. }
  158. zip_close(za);
  159. return err;
  160. }
  161. static void syncPlugin(json_t *pluginJ) {
  162. json_t *slugJ = json_object_get(pluginJ, "slug");
  163. if (!slugJ) return;
  164. std::string slug = json_string_value(slugJ);
  165. info("Syncing plugin %s", slug.c_str());
  166. json_t *nameJ = json_object_get(pluginJ, "name");
  167. if (!nameJ) return;
  168. std::string name = json_string_value(nameJ);
  169. std::string download;
  170. std::string sha256;
  171. json_t *downloadsJ = json_object_get(pluginJ, "downloads");
  172. if (downloadsJ) {
  173. #if defined(ARCH_WIN)
  174. #define DOWNLOADS_ARCH "win"
  175. #elif defined(ARCH_MAC)
  176. #define DOWNLOADS_ARCH "mac"
  177. #elif defined(ARCH_LIN)
  178. #define DOWNLOADS_ARCH "lin"
  179. #endif
  180. json_t *archJ = json_object_get(downloadsJ, DOWNLOADS_ARCH);
  181. if (archJ) {
  182. // Get download URL
  183. json_t *downloadJ = json_object_get(archJ, "download");
  184. if (downloadJ)
  185. download = json_string_value(downloadJ);
  186. // Get SHA256 hash
  187. json_t *sha256J = json_object_get(archJ, "sha256");
  188. if (sha256J)
  189. sha256 = json_string_value(sha256J);
  190. }
  191. }
  192. json_t *productIdJ = json_object_get(pluginJ, "productId");
  193. if (productIdJ) {
  194. download = gApiHost;
  195. download += "/download";
  196. download += "?slug=";
  197. download += slug;
  198. download += "&token=";
  199. download += requestEscape(gToken);
  200. }
  201. if (download.empty()) {
  202. warn("Could not get download URL for plugin %s", slug.c_str());
  203. return;
  204. }
  205. // If plugin is not loaded, download the zip file to /plugins
  206. downloadName = name;
  207. downloadProgress = 0.0;
  208. // Download zip
  209. std::string pluginsDir = assetLocal("plugins");
  210. std::string pluginPath = pluginsDir + "/" + slug;
  211. std::string zipPath = pluginPath + ".zip";
  212. bool success = requestDownload(download, zipPath, &downloadProgress);
  213. if (success) {
  214. if (!sha256.empty()) {
  215. // Check SHA256 hash
  216. std::string actualSha256 = requestSHA256File(zipPath);
  217. if (actualSha256 != sha256) {
  218. warn("Plugin %s does not match expected SHA256 checksum", slug.c_str());
  219. return;
  220. }
  221. }
  222. // Unzip file
  223. int err = extractZip(zipPath.c_str(), pluginsDir.c_str());
  224. if (!err) {
  225. // Delete zip
  226. remove(zipPath.c_str());
  227. // Load plugin
  228. // loadPlugin(pluginPath);
  229. }
  230. }
  231. downloadName = "";
  232. }
  233. static bool trySyncPlugin(json_t *pluginJ, json_t *communityPluginsJ, bool dryRun) {
  234. std::string slug = json_string_value(pluginJ);
  235. // Find community plugin
  236. size_t communityIndex;
  237. json_t *communityPluginJ = NULL;
  238. json_array_foreach(communityPluginsJ, communityIndex, communityPluginJ) {
  239. json_t *communitySlugJ = json_object_get(communityPluginJ, "slug");
  240. if (communitySlugJ) {
  241. std::string communitySlug = json_string_value(communitySlugJ);
  242. if (slug == communitySlug)
  243. break;
  244. }
  245. }
  246. if (communityIndex == json_array_size(communityPluginsJ)) {
  247. warn("Plugin sync error: %s not found in community", slug.c_str());
  248. return false;
  249. }
  250. // Get community version
  251. std::string version;
  252. json_t *versionJ = json_object_get(communityPluginJ, "version");
  253. if (versionJ) {
  254. version = json_string_value(versionJ);
  255. }
  256. // Check whether we already have a plugin with the same slug and version
  257. for (Plugin *plugin : gPlugins) {
  258. if (plugin->slug == slug) {
  259. // plugin->version might be blank, so adding a version of the manifest will update the plugin
  260. if (plugin->version == version)
  261. return false;
  262. }
  263. }
  264. if (!dryRun)
  265. syncPlugin(communityPluginJ);
  266. return true;
  267. }
  268. bool pluginSync(bool dryRun) {
  269. if (gToken.empty())
  270. return false;
  271. bool available = false;
  272. // Download my plugins
  273. json_t *reqJ = json_object();
  274. json_object_set(reqJ, "version", json_string(gApplicationVersion.c_str()));
  275. json_object_set(reqJ, "token", json_string(gToken.c_str()));
  276. json_t *resJ = requestJson(METHOD_GET, gApiHost + "/plugins", reqJ);
  277. json_decref(reqJ);
  278. // Download community plugins
  279. json_t *communityResJ = requestJson(METHOD_GET, gApiHost + "/community/plugins", NULL);
  280. if (!dryRun) {
  281. isDownloading = true;
  282. downloadProgress = 0.0;
  283. downloadName = "";
  284. }
  285. if (resJ && communityResJ) {
  286. json_t *errorJ = json_object_get(resJ, "error");
  287. json_t *communityErrorJ = json_object_get(resJ, "error");
  288. if (errorJ) {
  289. warn("Plugin sync error: %s", json_string_value(errorJ));
  290. }
  291. else if (communityErrorJ) {
  292. warn("Plugin sync error: %s", json_string_value(communityErrorJ));
  293. }
  294. else {
  295. // Check each plugin in list of my plugins
  296. json_t *pluginsJ = json_object_get(resJ, "plugins");
  297. json_t *communityPluginsJ = json_object_get(communityResJ, "plugins");
  298. size_t index;
  299. json_t *pluginJ;
  300. json_array_foreach(pluginsJ, index, pluginJ) {
  301. if (trySyncPlugin(pluginJ, communityPluginsJ, dryRun))
  302. available = true;
  303. }
  304. }
  305. }
  306. if (resJ)
  307. json_decref(resJ);
  308. if (communityResJ)
  309. json_decref(communityResJ);
  310. if (!dryRun) {
  311. isDownloading = false;
  312. }
  313. return available;
  314. }
  315. ////////////////////
  316. // plugin API
  317. ////////////////////
  318. void pluginInit() {
  319. tagsInit();
  320. // Load core
  321. // This function is defined in core.cpp
  322. Plugin *coreManufacturer = new Plugin();
  323. init(coreManufacturer);
  324. gPlugins.push_back(coreManufacturer);
  325. // Load plugins from global directory
  326. std::string globalPlugins = assetGlobal("plugins");
  327. info("Loading plugins from %s", globalPlugins.c_str());
  328. loadPlugins(globalPlugins);
  329. // Load plugins from local directory
  330. std::string localPlugins = assetLocal("plugins");
  331. if (globalPlugins != localPlugins) {
  332. mkdir(localPlugins.c_str(), 0755);
  333. info("Loading plugins from %s", localPlugins.c_str());
  334. loadPlugins(localPlugins);
  335. }
  336. }
  337. void pluginDestroy() {
  338. for (Plugin *plugin : gPlugins) {
  339. // Free library handle
  340. #if defined(ARCH_WIN)
  341. if (plugin->handle)
  342. FreeLibrary((HINSTANCE)plugin->handle);
  343. #elif defined(ARCH_LIN) || defined(ARCH_MAC)
  344. if (plugin->handle)
  345. dlclose(plugin->handle);
  346. #endif
  347. // For some reason this segfaults.
  348. // It might be best to let them leak anyway, because "crash on exit" issues would occur with badly-written plugins.
  349. // delete plugin;
  350. }
  351. gPlugins.clear();
  352. }
  353. void pluginLogIn(std::string email, std::string password) {
  354. json_t *reqJ = json_object();
  355. json_object_set(reqJ, "email", json_string(email.c_str()));
  356. json_object_set(reqJ, "password", json_string(password.c_str()));
  357. json_t *resJ = requestJson(METHOD_POST, gApiHost + "/token", reqJ);
  358. json_decref(reqJ);
  359. if (resJ) {
  360. json_t *errorJ = json_object_get(resJ, "error");
  361. if (errorJ) {
  362. const char *errorStr = json_string_value(errorJ);
  363. loginStatus = errorStr;
  364. }
  365. else {
  366. json_t *tokenJ = json_object_get(resJ, "token");
  367. if (tokenJ) {
  368. const char *tokenStr = json_string_value(tokenJ);
  369. gToken = tokenStr;
  370. loginStatus = "";
  371. }
  372. }
  373. json_decref(resJ);
  374. }
  375. }
  376. void pluginLogOut() {
  377. gToken = "";
  378. }
  379. void pluginCancelDownload() {
  380. // TODO
  381. }
  382. bool pluginIsLoggedIn() {
  383. return gToken != "";
  384. }
  385. bool pluginIsDownloading() {
  386. return isDownloading;
  387. }
  388. float pluginGetDownloadProgress() {
  389. return downloadProgress;
  390. }
  391. std::string pluginGetDownloadName() {
  392. return downloadName;
  393. }
  394. std::string pluginGetLoginStatus() {
  395. return loginStatus;
  396. }
  397. } // namespace rack