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.

377 lines
8.1KB

  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <sys/param.h> // for MAXPATHLEN
  8. #include <fcntl.h>
  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 "plugin.hpp"
  20. #include "app.hpp"
  21. #include "asset.hpp"
  22. #include "util/request.hpp"
  23. namespace rack {
  24. std::list<Plugin*> gPlugins;
  25. std::string gToken;
  26. static bool isDownloading = false;
  27. static float downloadProgress = 0.0;
  28. static std::string downloadName;
  29. static std::string loginStatus;
  30. Plugin::~Plugin() {
  31. for (Model *model : models) {
  32. delete model;
  33. }
  34. }
  35. void Plugin::addModel(Model *model) {
  36. assert(!model->plugin);
  37. model->plugin = this;
  38. models.push_back(model);
  39. }
  40. static int loadPlugin(std::string path) {
  41. std::string libraryFilename;
  42. #if ARCH_LIN
  43. libraryFilename = path + "/" + "plugin.so";
  44. #elif ARCH_WIN
  45. libraryFilename = path + "/" + "plugin.dll";
  46. #elif ARCH_MAC
  47. libraryFilename = path + "/" + "plugin.dylib";
  48. #endif
  49. // Load dynamic/shared library
  50. #if ARCH_WIN
  51. SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
  52. HINSTANCE handle = LoadLibrary(libraryFilename.c_str());
  53. SetErrorMode(0);
  54. if (!handle) {
  55. int error = GetLastError();
  56. warn("Failed to load library %s: %d", libraryFilename.c_str(), error);
  57. return -1;
  58. }
  59. #elif ARCH_LIN || ARCH_MAC
  60. void *handle = dlopen(libraryFilename.c_str(), RTLD_NOW);
  61. if (!handle) {
  62. warn("Failed to load library %s: %s", libraryFilename.c_str(), dlerror());
  63. return -1;
  64. }
  65. #endif
  66. // Call plugin's init() function
  67. typedef void (*InitCallback)(Plugin *);
  68. InitCallback initCallback;
  69. #if ARCH_WIN
  70. initCallback = (InitCallback) GetProcAddress(handle, "init");
  71. #elif ARCH_LIN || ARCH_MAC
  72. initCallback = (InitCallback) dlsym(handle, "init");
  73. #endif
  74. if (!initCallback) {
  75. warn("Failed to read init() symbol in %s", libraryFilename.c_str());
  76. return -2;
  77. }
  78. // Construct and initialize Plugin instance
  79. Plugin *plugin = new Plugin();
  80. plugin->path = path;
  81. plugin->handle = handle;
  82. initCallback(plugin);
  83. // Reject plugin if slug already exists
  84. for (Plugin *p : gPlugins) {
  85. if (plugin->slug == p->slug) {
  86. warn("Plugin \"%s\" is already loaded, not attempting to load it again");
  87. // TODO
  88. // Fix memory leak with `plugin` here
  89. return -1;
  90. }
  91. }
  92. // Add plugin to list
  93. gPlugins.push_back(plugin);
  94. info("Loaded plugin %s", libraryFilename.c_str());
  95. return 0;
  96. }
  97. static void loadPlugins(std::string path) {
  98. DIR *dir = opendir(path.c_str());
  99. if (dir) {
  100. struct dirent *d;
  101. while ((d = readdir(dir))) {
  102. if (d->d_name[0] == '.')
  103. continue;
  104. loadPlugin(path + "/" + d->d_name);
  105. }
  106. closedir(dir);
  107. }
  108. }
  109. ////////////////////
  110. // plugin helpers
  111. ////////////////////
  112. static int extractZipHandle(zip_t *za, const char *dir) {
  113. int err = 0;
  114. for (int i = 0; i < zip_get_num_entries(za, 0); i++) {
  115. zip_stat_t zs;
  116. err = zip_stat_index(za, i, 0, &zs);
  117. if (err)
  118. return err;
  119. int nameLen = strlen(zs.name);
  120. char path[MAXPATHLEN];
  121. snprintf(path, sizeof(path), "%s/%s", dir, zs.name);
  122. if (zs.name[nameLen - 1] == '/') {
  123. err = mkdir(path, 0755);
  124. if (err && errno != EEXIST)
  125. return err;
  126. }
  127. else {
  128. zip_file_t *zf = zip_fopen_index(za, i, 0);
  129. if (!zf)
  130. return 1;
  131. FILE *outFile = fopen(path, "wb");
  132. if (!outFile)
  133. continue;
  134. while (1) {
  135. char buffer[4096];
  136. int len = zip_fread(zf, buffer, sizeof(buffer));
  137. if (len <= 0)
  138. break;
  139. fwrite(buffer, 1, len, outFile);
  140. }
  141. err = zip_fclose(zf);
  142. if (err)
  143. return err;
  144. fclose(outFile);
  145. }
  146. }
  147. return 0;
  148. }
  149. static int extractZip(const char *filename, const char *dir) {
  150. int err = 0;
  151. zip_t *za = zip_open(filename, 0, &err);
  152. if (!za)
  153. return 1;
  154. if (!err) {
  155. err = extractZipHandle(za, dir);
  156. }
  157. zip_close(za);
  158. return err;
  159. }
  160. static void refreshPurchase(json_t *pluginJ) {
  161. json_t *slugJ = json_object_get(pluginJ, "slug");
  162. if (!slugJ) return;
  163. std::string slug = json_string_value(slugJ);
  164. json_t *nameJ = json_object_get(pluginJ, "name");
  165. if (!nameJ) return;
  166. std::string name = json_string_value(nameJ);
  167. json_t *versionJ = json_object_get(pluginJ, "version");
  168. if (!versionJ) return;
  169. std::string version = json_string_value(versionJ);
  170. // Check whether the plugin is already loaded
  171. for (Plugin *plugin : gPlugins) {
  172. if (plugin->slug == slug && plugin->version == version) {
  173. return;
  174. }
  175. }
  176. // Append token and version to download URL
  177. std::string url = gApiHost;
  178. url += "/download";
  179. url += "?product=";
  180. url += slug;
  181. url += "&version=";
  182. url += requestEscape(gApplicationVersion);
  183. url += "&token=";
  184. url += requestEscape(gToken);
  185. // If plugin is not loaded, download the zip file to /plugins
  186. downloadName = name;
  187. downloadProgress = 0.0;
  188. // Download zip
  189. std::string pluginsDir = assetLocal("plugins");
  190. std::string pluginPath = pluginsDir + "/" + slug;
  191. std::string zipPath = pluginPath + ".zip";
  192. bool success = requestDownload(url, zipPath, &downloadProgress);
  193. if (success) {
  194. // Unzip file
  195. int err = extractZip(zipPath.c_str(), pluginsDir.c_str());
  196. if (!err) {
  197. // Delete zip
  198. remove(zipPath.c_str());
  199. // Load plugin
  200. loadPlugin(pluginPath);
  201. }
  202. }
  203. downloadName = "";
  204. }
  205. ////////////////////
  206. // plugin API
  207. ////////////////////
  208. void pluginInit() {
  209. tagsInit();
  210. // Load core
  211. // This function is defined in core.cpp
  212. Plugin *coreManufacturer = new Plugin();
  213. init(coreManufacturer);
  214. gPlugins.push_back(coreManufacturer);
  215. // Load plugins from global directory
  216. std::string globalPlugins = assetGlobal("plugins");
  217. info("Loading plugins from %s", globalPlugins.c_str());
  218. loadPlugins(globalPlugins);
  219. // Load plugins from local directory
  220. std::string localPlugins = assetLocal("plugins");
  221. if (globalPlugins != localPlugins) {
  222. mkdir(localPlugins.c_str(), 0755);
  223. info("Loading plugins from %s", localPlugins.c_str());
  224. loadPlugins(localPlugins);
  225. }
  226. }
  227. void pluginDestroy() {
  228. for (Plugin *plugin : gPlugins) {
  229. // Free library handle
  230. #if ARCH_WIN
  231. if (plugin->handle)
  232. FreeLibrary((HINSTANCE)plugin->handle);
  233. #elif ARCH_LIN || ARCH_MAC
  234. if (plugin->handle)
  235. dlclose(plugin->handle);
  236. #endif
  237. // For some reason this segfaults.
  238. // It might be best to let them leak anyway, because "crash on exit" issues would occur with badly-written plugins.
  239. // delete plugin;
  240. }
  241. gPlugins.clear();
  242. }
  243. void pluginLogIn(std::string email, std::string password) {
  244. json_t *reqJ = json_object();
  245. json_object_set(reqJ, "email", json_string(email.c_str()));
  246. json_object_set(reqJ, "password", json_string(password.c_str()));
  247. json_t *resJ = requestJson(METHOD_POST, gApiHost + "/token", reqJ);
  248. json_decref(reqJ);
  249. if (resJ) {
  250. json_t *errorJ = json_object_get(resJ, "error");
  251. if (errorJ) {
  252. const char *errorStr = json_string_value(errorJ);
  253. loginStatus = errorStr;
  254. }
  255. else {
  256. json_t *tokenJ = json_object_get(resJ, "token");
  257. if (tokenJ) {
  258. const char *tokenStr = json_string_value(tokenJ);
  259. gToken = tokenStr;
  260. loginStatus = "";
  261. }
  262. }
  263. json_decref(resJ);
  264. }
  265. }
  266. void pluginLogOut() {
  267. gToken = "";
  268. }
  269. void pluginRefresh() {
  270. if (gToken.empty())
  271. return;
  272. isDownloading = true;
  273. downloadProgress = 0.0;
  274. downloadName = "";
  275. json_t *reqJ = json_object();
  276. json_object_set(reqJ, "token", json_string(gToken.c_str()));
  277. json_t *resJ = requestJson(METHOD_GET, gApiHost + "/purchases", reqJ);
  278. json_decref(reqJ);
  279. if (resJ) {
  280. json_t *errorJ = json_object_get(resJ, "error");
  281. if (errorJ) {
  282. const char *errorStr = json_string_value(errorJ);
  283. warn("Plugin refresh error: %s", errorStr);
  284. }
  285. else {
  286. json_t *purchasesJ = json_object_get(resJ, "purchases");
  287. size_t index;
  288. json_t *purchaseJ;
  289. json_array_foreach(purchasesJ, index, purchaseJ) {
  290. refreshPurchase(purchaseJ);
  291. }
  292. }
  293. json_decref(resJ);
  294. }
  295. isDownloading = false;
  296. }
  297. void pluginCancelDownload() {
  298. // TODO
  299. }
  300. bool pluginIsLoggedIn() {
  301. return gToken != "";
  302. }
  303. bool pluginIsDownloading() {
  304. return isDownloading;
  305. }
  306. float pluginGetDownloadProgress() {
  307. return downloadProgress;
  308. }
  309. std::string pluginGetDownloadName() {
  310. return downloadName;
  311. }
  312. std::string pluginGetLoginStatus() {
  313. return loginStatus;
  314. }
  315. } // namespace rack