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.

519 lines
12KB

  1. #include <unistd.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <sys/param.h> // for MAXPATHLEN
  5. #include <fcntl.h>
  6. #include <thread>
  7. #include <stdexcept>
  8. #define ZIP_STATIC
  9. #include <zip.h>
  10. #include <jansson.h>
  11. #if ARCH_WIN
  12. #include <windows.h>
  13. #include <direct.h>
  14. #define mkdir(_dir, _perms) _mkdir(_dir)
  15. #else
  16. #include <dlfcn.h>
  17. #endif
  18. #include <dirent.h>
  19. #include "rack.hpp"
  20. #include "osdialog.h"
  21. namespace rack {
  22. std::list<Plugin*> gPlugins;
  23. std::string gToken;
  24. static bool isDownloading = false;
  25. static float downloadProgress = 0.0;
  26. static std::string downloadName;
  27. static std::string loginStatus;
  28. Plugin::~Plugin() {
  29. for (Model *model : models) {
  30. delete model;
  31. }
  32. }
  33. void Plugin::addModel(Model *model) {
  34. assert(!model->plugin);
  35. model->plugin = this;
  36. models.push_back(model);
  37. }
  38. ////////////////////
  39. // private API
  40. ////////////////////
  41. static bool 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. // Check file existence
  51. if (!system::isFile(libraryFilename)) {
  52. WARN("Plugin file %s does not exist", libraryFilename.c_str());
  53. return false;
  54. }
  55. // Load dynamic/shared library
  56. #if ARCH_WIN
  57. SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
  58. HINSTANCE handle = LoadLibrary(libraryFilename.c_str());
  59. SetErrorMode(0);
  60. if (!handle) {
  61. int error = GetLastError();
  62. WARN("Failed to load library %s: code %d", libraryFilename.c_str(), error);
  63. return false;
  64. }
  65. #else
  66. void *handle = dlopen(libraryFilename.c_str(), RTLD_NOW);
  67. if (!handle) {
  68. WARN("Failed to load library %s: %s", libraryFilename.c_str(), dlerror());
  69. return false;
  70. }
  71. #endif
  72. // Call plugin's init() function
  73. typedef void (*InitCallback)(Plugin *);
  74. InitCallback initCallback;
  75. #if ARCH_WIN
  76. initCallback = (InitCallback) GetProcAddress(handle, "init");
  77. #else
  78. initCallback = (InitCallback) dlsym(handle, "init");
  79. #endif
  80. if (!initCallback) {
  81. WARN("Failed to read init() symbol in %s", libraryFilename.c_str());
  82. return false;
  83. }
  84. // Construct and initialize Plugin instance
  85. Plugin *plugin = new Plugin;
  86. plugin->path = path;
  87. plugin->handle = handle;
  88. initCallback(plugin);
  89. // Reject plugin if slug already exists
  90. Plugin *oldPlugin = pluginGetPlugin(plugin->slug);
  91. if (oldPlugin) {
  92. WARN("Plugin \"%s\" is already loaded, not attempting to load it again", plugin->slug.c_str());
  93. // TODO
  94. // Fix memory leak with `plugin` here
  95. return false;
  96. }
  97. // Add plugin to list
  98. gPlugins.push_back(plugin);
  99. INFO("Loaded plugin %s %s from %s", plugin->slug.c_str(), plugin->version.c_str(), libraryFilename.c_str());
  100. return true;
  101. }
  102. static bool syncPlugin(std::string slug, json_t *manifestJ, bool dryRun) {
  103. // Check that "status" is "available"
  104. json_t *statusJ = json_object_get(manifestJ, "status");
  105. if (!statusJ) {
  106. return false;
  107. }
  108. std::string status = json_string_value(statusJ);
  109. if (status != "available") {
  110. return false;
  111. }
  112. // Get latest version
  113. json_t *latestVersionJ = json_object_get(manifestJ, "latestVersion");
  114. if (!latestVersionJ) {
  115. WARN("Could not get latest version of plugin %s", slug.c_str());
  116. return false;
  117. }
  118. std::string latestVersion = json_string_value(latestVersionJ);
  119. // Check whether we already have a plugin with the same slug and version
  120. Plugin *plugin = pluginGetPlugin(slug);
  121. if (plugin && plugin->version == latestVersion) {
  122. return false;
  123. }
  124. json_t *nameJ = json_object_get(manifestJ, "name");
  125. std::string name;
  126. if (nameJ) {
  127. name = json_string_value(nameJ);
  128. }
  129. else {
  130. name = slug;
  131. }
  132. #if ARCH_WIN
  133. std::string arch = "win";
  134. #elif ARCH_MAC
  135. std::string arch = "mac";
  136. #elif ARCH_LIN
  137. std::string arch = "lin";
  138. #endif
  139. std::string downloadUrl;
  140. downloadUrl = gApiHost;
  141. downloadUrl += "/download";
  142. if (dryRun) {
  143. downloadUrl += "/available";
  144. }
  145. downloadUrl += "?token=" + network::encodeUrl(gToken);
  146. downloadUrl += "&slug=" + network::encodeUrl(slug);
  147. downloadUrl += "&version=" + network::encodeUrl(latestVersion);
  148. downloadUrl += "&arch=" + network::encodeUrl(arch);
  149. if (dryRun) {
  150. // Check if available
  151. json_t *availableResJ = network::requestJson(network::METHOD_GET, downloadUrl, NULL);
  152. if (!availableResJ) {
  153. WARN("Could not check whether download is available");
  154. return false;
  155. }
  156. DEFER({
  157. json_decref(availableResJ);
  158. });
  159. json_t *successJ = json_object_get(availableResJ, "success");
  160. return json_boolean_value(successJ);
  161. }
  162. else {
  163. downloadName = name;
  164. downloadProgress = 0.0;
  165. INFO("Downloading plugin %s %s %s", slug.c_str(), latestVersion.c_str(), arch.c_str());
  166. // Download zip
  167. std::string pluginDest = asset::local("plugins/" + slug + ".zip");
  168. if (!network::requestDownload(downloadUrl, pluginDest, &downloadProgress)) {
  169. WARN("Plugin %s download was unsuccessful", slug.c_str());
  170. return false;
  171. }
  172. downloadName = "";
  173. return true;
  174. }
  175. }
  176. static void loadPlugins(std::string path) {
  177. std::string message;
  178. for (std::string pluginPath : system::listEntries(path)) {
  179. if (!system::isDirectory(pluginPath))
  180. continue;
  181. if (!loadPlugin(pluginPath)) {
  182. message += string::f("Could not load plugin %s\n", pluginPath.c_str());
  183. }
  184. }
  185. if (!message.empty()) {
  186. message += "See log for details.";
  187. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  188. }
  189. }
  190. /** Returns 0 if successful */
  191. static int extractZipHandle(zip_t *za, const char *dir) {
  192. int err;
  193. for (int i = 0; i < zip_get_num_entries(za, 0); i++) {
  194. zip_stat_t zs;
  195. err = zip_stat_index(za, i, 0, &zs);
  196. if (err) {
  197. WARN("zip_stat_index() failed: error %d", err);
  198. return err;
  199. }
  200. int nameLen = strlen(zs.name);
  201. char path[MAXPATHLEN];
  202. snprintf(path, sizeof(path), "%s/%s", dir, zs.name);
  203. if (zs.name[nameLen - 1] == '/') {
  204. if (mkdir(path, 0755)) {
  205. if (errno != EEXIST) {
  206. WARN("mkdir(%s) failed: error %d", path, errno);
  207. return errno;
  208. }
  209. }
  210. }
  211. else {
  212. zip_file_t *zf = zip_fopen_index(za, i, 0);
  213. if (!zf) {
  214. WARN("zip_fopen_index() failed");
  215. return -1;
  216. }
  217. FILE *outFile = fopen(path, "wb");
  218. if (!outFile)
  219. continue;
  220. while (1) {
  221. char buffer[1<<15];
  222. int len = zip_fread(zf, buffer, sizeof(buffer));
  223. if (len <= 0)
  224. break;
  225. fwrite(buffer, 1, len, outFile);
  226. }
  227. err = zip_fclose(zf);
  228. if (err) {
  229. WARN("zip_fclose() failed: error %d", err);
  230. return err;
  231. }
  232. fclose(outFile);
  233. }
  234. }
  235. return 0;
  236. }
  237. /** Returns 0 if successful */
  238. static int extractZip(const char *filename, const char *path) {
  239. int err;
  240. zip_t *za = zip_open(filename, 0, &err);
  241. if (!za) {
  242. WARN("Could not open zip %s: error %d", filename, err);
  243. return err;
  244. }
  245. DEFER({
  246. zip_close(za);
  247. });
  248. err = extractZipHandle(za, path);
  249. return err;
  250. }
  251. static void extractPackages(std::string path) {
  252. std::string message;
  253. for (std::string packagePath : system::listEntries(path)) {
  254. if (string::extension(packagePath) != "zip")
  255. continue;
  256. INFO("Extracting package %s", packagePath.c_str());
  257. // Extract package
  258. if (extractZip(packagePath.c_str(), path.c_str())) {
  259. WARN("Package %s failed to extract", packagePath.c_str());
  260. message += string::f("Could not extract package %s\n", packagePath.c_str());
  261. continue;
  262. }
  263. // Remove package
  264. if (remove(packagePath.c_str())) {
  265. WARN("Could not delete file %s: error %d", packagePath.c_str(), errno);
  266. }
  267. }
  268. if (!message.empty()) {
  269. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  270. }
  271. }
  272. ////////////////////
  273. // public API
  274. ////////////////////
  275. void pluginInit(bool devMode) {
  276. tagsInit();
  277. // Load core
  278. // This function is defined in core.cpp
  279. Plugin *corePlugin = new Plugin;
  280. init(corePlugin);
  281. gPlugins.push_back(corePlugin);
  282. // Get local plugins directory
  283. std::string localPlugins = asset::local("plugins");
  284. mkdir(localPlugins.c_str(), 0755);
  285. if (!devMode) {
  286. // Copy Fundamental package to plugins directory if folder does not exist
  287. std::string fundamentalSrc = asset::global("Fundamental.zip");
  288. std::string fundamentalDest = asset::local("plugins/Fundamental.zip");
  289. std::string fundamentalDir = asset::local("plugins/Fundamental");
  290. if (system::isFile(fundamentalSrc) && !system::isFile(fundamentalDest) && !system::isDirectory(fundamentalDir)) {
  291. system::copyFile(fundamentalSrc, fundamentalDest);
  292. }
  293. }
  294. // Extract packages and load plugins
  295. extractPackages(localPlugins);
  296. loadPlugins(localPlugins);
  297. }
  298. void pluginDestroy() {
  299. for (Plugin *plugin : gPlugins) {
  300. // Free library handle
  301. #if ARCH_WIN
  302. if (plugin->handle)
  303. FreeLibrary((HINSTANCE)plugin->handle);
  304. #else
  305. if (plugin->handle)
  306. dlclose(plugin->handle);
  307. #endif
  308. // For some reason this segfaults.
  309. // It might be best to let them leak anyway, because "crash on exit" issues would occur with badly-written plugins.
  310. // delete plugin;
  311. }
  312. gPlugins.clear();
  313. }
  314. bool pluginSync(bool dryRun) {
  315. if (gToken.empty())
  316. return false;
  317. bool available = false;
  318. if (!dryRun) {
  319. isDownloading = true;
  320. downloadProgress = 0.0;
  321. downloadName = "Updating plugins...";
  322. }
  323. DEFER({
  324. isDownloading = false;
  325. });
  326. // Get user's plugins list
  327. json_t *pluginsReqJ = json_object();
  328. json_object_set(pluginsReqJ, "token", json_string(gToken.c_str()));
  329. json_t *pluginsResJ = network::requestJson(network::METHOD_GET, gApiHost + "/plugins", pluginsReqJ);
  330. json_decref(pluginsReqJ);
  331. if (!pluginsResJ) {
  332. WARN("Request for user's plugins failed");
  333. return false;
  334. }
  335. DEFER({
  336. json_decref(pluginsResJ);
  337. });
  338. json_t *errorJ = json_object_get(pluginsResJ, "error");
  339. if (errorJ) {
  340. WARN("Request for user's plugins returned an error: %s", json_string_value(errorJ));
  341. return false;
  342. }
  343. // Get community manifests
  344. json_t *manifestsResJ = network::requestJson(network::METHOD_GET, gApiHost + "/community/manifests", NULL);
  345. if (!manifestsResJ) {
  346. WARN("Request for community manifests failed");
  347. return false;
  348. }
  349. DEFER({
  350. json_decref(manifestsResJ);
  351. });
  352. // Check each plugin in list of plugin slugs
  353. json_t *pluginsJ = json_object_get(pluginsResJ, "plugins");
  354. if (!pluginsJ) {
  355. WARN("No plugins array");
  356. return false;
  357. }
  358. json_t *manifestsJ = json_object_get(manifestsResJ, "manifests");
  359. if (!manifestsJ) {
  360. WARN("No manifests object");
  361. return false;
  362. }
  363. size_t slugIndex;
  364. json_t *slugJ;
  365. json_array_foreach(pluginsJ, slugIndex, slugJ) {
  366. std::string slug = json_string_value(slugJ);
  367. // Search for slug in manifests
  368. const char *manifestSlug;
  369. json_t *manifestJ = NULL;
  370. json_object_foreach(manifestsJ, manifestSlug, manifestJ) {
  371. if (slug == std::string(manifestSlug))
  372. break;
  373. }
  374. if (!manifestJ)
  375. continue;
  376. if (syncPlugin(slug, manifestJ, dryRun)) {
  377. available = true;
  378. }
  379. }
  380. return available;
  381. }
  382. void pluginLogIn(std::string email, std::string password) {
  383. json_t *reqJ = json_object();
  384. json_object_set(reqJ, "email", json_string(email.c_str()));
  385. json_object_set(reqJ, "password", json_string(password.c_str()));
  386. json_t *resJ = network::requestJson(network::METHOD_POST, gApiHost + "/token", reqJ);
  387. json_decref(reqJ);
  388. if (resJ) {
  389. json_t *errorJ = json_object_get(resJ, "error");
  390. if (errorJ) {
  391. const char *errorStr = json_string_value(errorJ);
  392. loginStatus = errorStr;
  393. }
  394. else {
  395. json_t *tokenJ = json_object_get(resJ, "token");
  396. if (tokenJ) {
  397. const char *tokenStr = json_string_value(tokenJ);
  398. gToken = tokenStr;
  399. loginStatus = "";
  400. }
  401. }
  402. json_decref(resJ);
  403. }
  404. }
  405. void pluginLogOut() {
  406. gToken = "";
  407. }
  408. void pluginCancelDownload() {
  409. // TODO
  410. }
  411. bool pluginIsLoggedIn() {
  412. return gToken != "";
  413. }
  414. bool pluginIsDownloading() {
  415. return isDownloading;
  416. }
  417. float pluginGetDownloadProgress() {
  418. return downloadProgress;
  419. }
  420. std::string pluginGetDownloadName() {
  421. return downloadName;
  422. }
  423. std::string pluginGetLoginStatus() {
  424. return loginStatus;
  425. }
  426. Plugin *pluginGetPlugin(std::string pluginSlug) {
  427. for (Plugin *plugin : gPlugins) {
  428. if (plugin->slug == pluginSlug) {
  429. return plugin;
  430. }
  431. }
  432. return NULL;
  433. }
  434. Model *pluginGetModel(std::string pluginSlug, std::string modelSlug) {
  435. Plugin *plugin = pluginGetPlugin(pluginSlug);
  436. if (plugin) {
  437. for (Model *model : plugin->models) {
  438. if (model->slug == modelSlug) {
  439. return model;
  440. }
  441. }
  442. }
  443. return NULL;
  444. }
  445. } // namespace rack