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.

540 lines
13KB

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