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.

496 lines
12KB

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