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.

486 lines
12KB

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