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.

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