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.

591 lines
14KB

  1. #include <thread>
  2. #include <map>
  3. #include <stdexcept>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <unistd.h>
  7. #include <sys/param.h> // for MAXPATHLEN
  8. #include <fcntl.h>
  9. #if defined ARCH_WIN
  10. #include <windows.h>
  11. #include <direct.h>
  12. #else
  13. #include <dlfcn.h>
  14. #endif
  15. #include <dirent.h>
  16. #include <osdialog.h>
  17. #include <jansson.h>
  18. #include <plugin.hpp>
  19. #include <system.hpp>
  20. #include <network.hpp>
  21. #include <asset.hpp>
  22. #include <string.hpp>
  23. #include <context.hpp>
  24. #include <app/common.hpp>
  25. #include <plugin/callbacks.hpp>
  26. #include <settings.hpp>
  27. #include <engine/Module.hpp>
  28. namespace rack {
  29. namespace core {
  30. void init(rack::plugin::Plugin* plugin);
  31. } // namespace core
  32. namespace plugin {
  33. ////////////////////
  34. // private API
  35. ////////////////////
  36. static void* loadLibrary(std::string libraryPath) {
  37. #if defined ARCH_WIN
  38. SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
  39. std::wstring libraryFilenameW = string::U8toU16(libraryPath);
  40. HINSTANCE handle = LoadLibraryW(libraryFilenameW.c_str());
  41. SetErrorMode(0);
  42. if (!handle) {
  43. int error = GetLastError();
  44. throw Exception(string::f("Failed to load library %s: code %d", libraryPath.c_str(), error));
  45. }
  46. #else
  47. // Plugin uses -rpath=. so change working directory so it can find libRack.
  48. std::string cwd = system::getWorkingDirectory();
  49. system::setWorkingDirectory(asset::systemDir);
  50. // And then change it back
  51. DEFER({
  52. system::setWorkingDirectory(cwd);
  53. });
  54. // Load library with dlopen
  55. void* handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL);
  56. if (!handle) {
  57. throw Exception(string::f("Failed to load library %s: %s", libraryPath.c_str(), dlerror()));
  58. }
  59. #endif
  60. return handle;
  61. }
  62. typedef void (*InitCallback)(Plugin*);
  63. static InitCallback loadPluginCallback(Plugin* plugin) {
  64. // Load plugin library
  65. std::string libraryExt;
  66. #if defined ARCH_LIN
  67. libraryExt = "so";
  68. #elif defined ARCH_WIN
  69. libraryExt = "dll";
  70. #elif ARCH_MAC
  71. libraryExt = "dylib";
  72. #endif
  73. std::string libraryPath = plugin->path + "/plugin." + libraryExt;
  74. // Check file existence
  75. if (!system::isFile(libraryPath)) {
  76. throw Exception(string::f("Library %s does not exist", libraryPath.c_str()));
  77. }
  78. // Load dynamic/shared library
  79. plugin->handle = loadLibrary(libraryPath);
  80. // Get plugin's init() function
  81. InitCallback initCallback;
  82. #if defined ARCH_WIN
  83. initCallback = (InitCallback) GetProcAddress((HMODULE) plugin->handle, "init");
  84. #else
  85. initCallback = (InitCallback) dlsym(plugin->handle, "init");
  86. #endif
  87. if (!initCallback) {
  88. throw Exception(string::f("Failed to read init() symbol in %s", libraryPath.c_str()));
  89. }
  90. return initCallback;
  91. }
  92. /** If path is blank, loads Core */
  93. static Plugin* loadPlugin(std::string path) {
  94. Plugin* plugin = new Plugin;
  95. try {
  96. // Set plugin path
  97. plugin->path = (path == "") ? asset::systemDir : path;
  98. // Get modified timestamp
  99. if (path != "") {
  100. struct stat statbuf;
  101. if (!stat(path.c_str(), &statbuf)) {
  102. #if defined ARCH_MAC
  103. plugin->modifiedTimestamp = (double) statbuf.st_mtimespec.tv_sec + statbuf.st_mtimespec.tv_nsec * 1e-9;
  104. #elif defined ARCH_WIN
  105. plugin->modifiedTimestamp = (double) statbuf.st_mtime;
  106. #elif defined ARCH_LIN
  107. plugin->modifiedTimestamp = (double) statbuf.st_mtim.tv_sec + statbuf.st_mtim.tv_nsec * 1e-9;
  108. #endif
  109. }
  110. }
  111. // Load plugin.json
  112. std::string manifestFilename = (path == "") ? asset::system("Core.json") : (path + "/plugin.json");
  113. FILE* file = fopen(manifestFilename.c_str(), "r");
  114. if (!file) {
  115. throw Exception(string::f("Manifest file %s does not exist", manifestFilename.c_str()));
  116. }
  117. DEFER({
  118. fclose(file);
  119. });
  120. json_error_t error;
  121. json_t* rootJ = json_loadf(file, 0, &error);
  122. if (!rootJ) {
  123. throw Exception(string::f("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text));
  124. }
  125. DEFER({
  126. json_decref(rootJ);
  127. });
  128. // Call init callback
  129. InitCallback initCallback;
  130. if (path == "") {
  131. initCallback = core::init;
  132. }
  133. else {
  134. initCallback = loadPluginCallback(plugin);
  135. }
  136. initCallback(plugin);
  137. // Load manifest
  138. plugin->fromJson(rootJ);
  139. // Reject plugin if slug already exists
  140. Plugin* oldPlugin = getPlugin(plugin->slug);
  141. if (oldPlugin) {
  142. throw Exception(string::f("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str()));
  143. }
  144. INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), plugin->path.c_str());
  145. plugins.push_back(plugin);
  146. }
  147. catch (Exception& e) {
  148. WARN("Could not load plugin %s: %s", path.c_str(), e.what());
  149. delete plugin;
  150. plugin = NULL;
  151. }
  152. return plugin;
  153. }
  154. static void loadPlugins(std::string path) {
  155. for (std::string pluginPath : system::getEntries(path)) {
  156. if (!system::isDirectory(pluginPath))
  157. continue;
  158. if (!loadPlugin(pluginPath)) {
  159. // Ignore bad plugins. They are reported in the log.
  160. }
  161. }
  162. }
  163. static void extractPackages(std::string path) {
  164. std::string message;
  165. for (std::string packagePath : system::getEntries(path)) {
  166. if (string::filenameExtension(string::filename(packagePath)) != "zip")
  167. continue;
  168. INFO("Extracting package %s", packagePath.c_str());
  169. // Extract package
  170. try {
  171. system::unarchiveToFolder(packagePath, path);
  172. }
  173. catch (Exception& e) {
  174. WARN("Package %s failed to extract: %s", packagePath.c_str(), e.what());
  175. message += string::f("Could not extract package %s\n", packagePath.c_str());
  176. continue;
  177. }
  178. // Remove package
  179. if (remove(packagePath.c_str())) {
  180. WARN("Could not delete file %s: error %d", packagePath.c_str(), errno);
  181. }
  182. }
  183. if (!message.empty()) {
  184. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  185. }
  186. }
  187. ////////////////////
  188. // public API
  189. ////////////////////
  190. void init() {
  191. // Don't re-initialize
  192. if (!plugins.empty())
  193. return;
  194. // Load Core
  195. loadPlugin("");
  196. // Get user plugins directory
  197. system::createDirectory(asset::pluginsPath);
  198. // Extract packages and load plugins
  199. extractPackages(asset::pluginsPath);
  200. loadPlugins(asset::pluginsPath);
  201. // If Fundamental wasn't loaded, copy the bundled Fundamental package and load it
  202. #if defined ARCH_MAC
  203. std::string fundamentalSrc = asset::system("Fundamental.txt");
  204. #else
  205. std::string fundamentalSrc = asset::system("Fundamental.zip");
  206. #endif
  207. std::string fundamentalDir = asset::pluginsPath + "/Fundamental";
  208. if (!settings::devMode && !getPlugin("Fundamental") && system::isFile(fundamentalSrc)) {
  209. INFO("Extracting bundled Fundamental package");
  210. system::unarchiveToFolder(fundamentalSrc.c_str(), asset::pluginsPath.c_str());
  211. loadPlugin(fundamentalDir);
  212. }
  213. // Sync in a detached thread
  214. if (!settings::devMode) {
  215. std::thread t([] {
  216. queryUpdates();
  217. });
  218. t.detach();
  219. }
  220. }
  221. void destroy() {
  222. for (Plugin* plugin : plugins) {
  223. // We must delete the plugin *before* freeing the library, because the vtable of Model subclasses are static in the plugin, and we need it for the virtual destructor.
  224. void* handle = plugin->handle;
  225. delete plugin;
  226. // Free library handle
  227. if (handle) {
  228. #if defined ARCH_WIN
  229. FreeLibrary((HINSTANCE) handle);
  230. #else
  231. dlclose(handle);
  232. #endif
  233. }
  234. }
  235. plugins.clear();
  236. }
  237. void logIn(const std::string& email, const std::string& password) {
  238. loginStatus = "Logging in...";
  239. json_t* reqJ = json_object();
  240. json_object_set(reqJ, "email", json_string(email.c_str()));
  241. json_object_set(reqJ, "password", json_string(password.c_str()));
  242. std::string url = API_URL + "/token";
  243. json_t* resJ = network::requestJson(network::METHOD_POST, url, reqJ);
  244. json_decref(reqJ);
  245. if (!resJ) {
  246. loginStatus = "No response from server";
  247. return;
  248. }
  249. DEFER({
  250. json_decref(resJ);
  251. });
  252. json_t* errorJ = json_object_get(resJ, "error");
  253. if (errorJ) {
  254. const char* errorStr = json_string_value(errorJ);
  255. loginStatus = errorStr;
  256. return;
  257. }
  258. json_t* tokenJ = json_object_get(resJ, "token");
  259. if (!tokenJ) {
  260. loginStatus = "No token in response";
  261. return;
  262. }
  263. const char* tokenStr = json_string_value(tokenJ);
  264. settings::token = tokenStr;
  265. loginStatus = "";
  266. queryUpdates();
  267. }
  268. void logOut() {
  269. settings::token = "";
  270. updates.clear();
  271. }
  272. bool isLoggedIn() {
  273. return settings::token != "";
  274. }
  275. void queryUpdates() {
  276. if (settings::token.empty())
  277. return;
  278. updates.clear();
  279. updateStatus = "Querying for updates...";
  280. // Get user's plugins list
  281. std::string pluginsUrl = API_URL + "/plugins";
  282. network::CookieMap cookies;
  283. cookies["token"] = settings::token;
  284. json_t* pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, NULL, cookies);
  285. if (!pluginsResJ) {
  286. WARN("Request for user's plugins failed");
  287. updateStatus = "Could not query updates";
  288. return;
  289. }
  290. DEFER({
  291. json_decref(pluginsResJ);
  292. });
  293. json_t* errorJ = json_object_get(pluginsResJ, "error");
  294. if (errorJ) {
  295. WARN("Request for user's plugins returned an error: %s", json_string_value(errorJ));
  296. updateStatus = "Could not query updates";
  297. return;
  298. }
  299. // Get library manifests
  300. std::string manifestsUrl = API_URL + "/library/manifests";
  301. json_t* manifestsReq = json_object();
  302. json_object_set(manifestsReq, "version", json_string(API_VERSION.c_str()));
  303. json_t* manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, manifestsReq);
  304. json_decref(manifestsReq);
  305. if (!manifestsResJ) {
  306. WARN("Request for library manifests failed");
  307. updateStatus = "Could not query updates";
  308. return;
  309. }
  310. DEFER({
  311. json_decref(manifestsResJ);
  312. });
  313. json_t* manifestsJ = json_object_get(manifestsResJ, "manifests");
  314. json_t* pluginsJ = json_object_get(pluginsResJ, "plugins");
  315. size_t pluginIndex;
  316. json_t* pluginJ;
  317. json_array_foreach(pluginsJ, pluginIndex, pluginJ) {
  318. Update update;
  319. // Get plugin manifest
  320. update.pluginSlug = json_string_value(pluginJ);
  321. json_t* manifestJ = json_object_get(manifestsJ, update.pluginSlug.c_str());
  322. if (!manifestJ) {
  323. WARN("VCV account has plugin %s but no manifest was found", update.pluginSlug.c_str());
  324. continue;
  325. }
  326. // Get plugin name
  327. json_t* nameJ = json_object_get(manifestJ, "name");
  328. if (nameJ)
  329. update.pluginName = json_string_value(nameJ);
  330. // Get version
  331. json_t* versionJ = json_object_get(manifestJ, "version");
  332. if (!versionJ) {
  333. WARN("Plugin %s has no version in manifest", update.pluginSlug.c_str());
  334. continue;
  335. }
  336. update.version = json_string_value(versionJ);
  337. // Check if update is needed
  338. Plugin* p = getPlugin(update.pluginSlug);
  339. if (p && p->version == update.version)
  340. continue;
  341. // Check status
  342. json_t* statusJ = json_object_get(manifestJ, "status");
  343. if (!statusJ)
  344. continue;
  345. std::string status = json_string_value(statusJ);
  346. if (status != "available")
  347. continue;
  348. // Get changelog URL
  349. json_t* changelogUrlJ = json_object_get(manifestJ, "changelogUrl");
  350. if (changelogUrlJ) {
  351. update.changelogUrl = json_string_value(changelogUrlJ);
  352. }
  353. updates.push_back(update);
  354. }
  355. // Get module whitelist
  356. {
  357. std::string whitelistUrl = API_URL + "/moduleWhitelist";
  358. json_t* whitelistResJ = network::requestJson(network::METHOD_GET, whitelistUrl, NULL, cookies);
  359. if (!whitelistResJ) {
  360. WARN("Request for module whitelist failed");
  361. updateStatus = "Could not query updates";
  362. return;
  363. }
  364. DEFER({
  365. json_decref(whitelistResJ);
  366. });
  367. std::map<std::string, std::set<std::string>> moduleWhitelist;
  368. json_t* pluginsJ = json_object_get(whitelistResJ, "plugins");
  369. // Iterate plugins
  370. const char* pluginSlug;
  371. json_t* modulesJ;
  372. json_object_foreach(pluginsJ, pluginSlug, modulesJ) {
  373. // Iterate modules in plugin
  374. size_t moduleIndex;
  375. json_t* moduleSlugJ;
  376. json_array_foreach(modulesJ, moduleIndex, moduleSlugJ) {
  377. std::string moduleSlug = json_string_value(moduleSlugJ);
  378. // Insert module in whitelist
  379. moduleWhitelist[pluginSlug].insert(moduleSlug);
  380. }
  381. }
  382. settings::moduleWhitelist = moduleWhitelist;
  383. }
  384. updateStatus = "";
  385. }
  386. bool hasUpdates() {
  387. for (Update& update : updates) {
  388. if (update.progress < 1.f)
  389. return true;
  390. }
  391. return false;
  392. }
  393. static bool isSyncingUpdate = false;
  394. static bool isSyncingUpdates = false;
  395. void syncUpdate(Update* update) {
  396. isSyncingUpdate = true;
  397. DEFER({
  398. isSyncingUpdate = false;
  399. });
  400. std::string downloadUrl = API_URL + "/download";
  401. downloadUrl += "?slug=" + network::encodeUrl(update->pluginSlug);
  402. downloadUrl += "&version=" + network::encodeUrl(update->version);
  403. downloadUrl += "&arch=" + network::encodeUrl(APP_ARCH);
  404. network::CookieMap cookies;
  405. cookies["token"] = settings::token;
  406. INFO("Downloading plugin %s %s %s", update->pluginSlug.c_str(), update->version.c_str(), APP_ARCH.c_str());
  407. // Download zip
  408. std::string pluginDest = asset::pluginsPath + "/" + update->pluginSlug + ".zip";
  409. if (!network::requestDownload(downloadUrl, pluginDest, &update->progress, cookies)) {
  410. WARN("Plugin %s download was unsuccessful", update->pluginSlug.c_str());
  411. return;
  412. }
  413. }
  414. void syncUpdates() {
  415. isSyncingUpdates = true;
  416. DEFER({
  417. isSyncingUpdates = false;
  418. });
  419. if (settings::token.empty())
  420. return;
  421. for (Update& update : updates) {
  422. if (update.progress < 1.f)
  423. syncUpdate(&update);
  424. }
  425. restartRequested = true;
  426. }
  427. bool isSyncing() {
  428. return isSyncingUpdate || isSyncingUpdates;
  429. }
  430. Plugin* getPlugin(const std::string& pluginSlug) {
  431. for (Plugin* plugin : plugins) {
  432. if (plugin->slug == pluginSlug) {
  433. return plugin;
  434. }
  435. }
  436. return NULL;
  437. }
  438. Model* getModel(const std::string& pluginSlug, const std::string& modelSlug) {
  439. Plugin* plugin = getPlugin(pluginSlug);
  440. if (!plugin)
  441. return NULL;
  442. Model* model = plugin->getModel(modelSlug);
  443. if (!model)
  444. return NULL;
  445. return model;
  446. }
  447. Model* modelFromJson(json_t* moduleJ) {
  448. // Get slugs
  449. json_t* pluginSlugJ = json_object_get(moduleJ, "plugin");
  450. if (!pluginSlugJ)
  451. throw Exception("\"plugin\" property not found in module JSON");
  452. std::string pluginSlug = json_string_value(pluginSlugJ);
  453. pluginSlug = normalizeSlug(pluginSlug);
  454. json_t* modelSlugJ = json_object_get(moduleJ, "model");
  455. if (!modelSlugJ)
  456. throw Exception("\"model\" property not found in module JSON");
  457. std::string modelSlug = json_string_value(modelSlugJ);
  458. modelSlug = normalizeSlug(modelSlug);
  459. // Get Model
  460. Model* model = getModel(pluginSlug, modelSlug);
  461. if (!model)
  462. throw Exception(string::f("Could not find module \"%s\" of plugin \"%s\"", modelSlug.c_str(), pluginSlug.c_str()));
  463. return model;
  464. }
  465. bool isSlugValid(const std::string& slug) {
  466. for (char c : slug) {
  467. if (!(std::isalnum(c) || c == '-' || c == '_'))
  468. return false;
  469. }
  470. return true;
  471. }
  472. std::string normalizeSlug(const std::string& slug) {
  473. std::string s;
  474. for (char c : slug) {
  475. if (!(std::isalnum(c) || c == '-' || c == '_'))
  476. continue;
  477. s += c;
  478. }
  479. return s;
  480. }
  481. std::vector<Plugin*> plugins;
  482. std::string loginStatus;
  483. std::vector<Update> updates;
  484. std::string updateStatus;
  485. bool restartRequested = false;
  486. } // namespace plugin
  487. } // namespace rack