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.

352 lines
7.5KB

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