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.

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