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.

353 lines
7.4KB

  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. json_t *downloadJ = json_object_get(pluginJ, "download");
  161. if (!downloadJ) return;
  162. const char *download = json_string_value(downloadJ);
  163. // Find slug in plugins list
  164. for (Plugin *p : gPlugins) {
  165. if (p->slug == slug) {
  166. return;
  167. }
  168. }
  169. // If plugin is not loaded, download the zip file to /plugins
  170. downloadName = name;
  171. downloadProgress = 0.0;
  172. // Download zip
  173. std::string pluginsDir = assetLocal("plugins");
  174. mkdir(pluginsDir.c_str(), 0755);
  175. std::string filename = pluginsDir + "/" + slug + ".zip";
  176. bool success = requestDownload(download, filename, &downloadProgress);
  177. if (success) {
  178. // Unzip file
  179. int err = extractZip(filename.c_str(), pluginsDir.c_str());
  180. if (!err) {
  181. // Load plugin
  182. loadPlugin(slug);
  183. // Delete zip
  184. remove(filename.c_str());
  185. }
  186. }
  187. downloadName = "";
  188. }
  189. ////////////////////
  190. // plugin API
  191. ////////////////////
  192. void pluginInit() {
  193. // Load core
  194. // This function is defined in core.cpp
  195. Plugin *corePlugin = new Plugin();
  196. init(corePlugin);
  197. gPlugins.push_back(corePlugin);
  198. // Load plugins from global directory
  199. std::string globalPlugins = assetGlobal("plugins");
  200. loadPlugins(globalPlugins);
  201. // Load plugins from local directory
  202. std::string localPlugins = assetLocal("plugins");
  203. if (globalPlugins != localPlugins)
  204. loadPlugins(localPlugins);
  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. // delete plugin;
  218. }
  219. gPlugins.clear();
  220. }
  221. void pluginLogIn(std::string email, std::string password) {
  222. json_t *reqJ = json_object();
  223. json_object_set(reqJ, "email", json_string(email.c_str()));
  224. json_object_set(reqJ, "password", json_string(password.c_str()));
  225. json_t *resJ = requestJson(METHOD_POST, gApiHost + "/token", reqJ);
  226. json_decref(reqJ);
  227. if (resJ) {
  228. json_t *errorJ = json_object_get(resJ, "error");
  229. if (errorJ) {
  230. const char *errorStr = json_string_value(errorJ);
  231. loginStatus = errorStr;
  232. }
  233. else {
  234. json_t *tokenJ = json_object_get(resJ, "token");
  235. if (tokenJ) {
  236. const char *tokenStr = json_string_value(tokenJ);
  237. gToken = tokenStr;
  238. loginStatus = "";
  239. }
  240. }
  241. json_decref(resJ);
  242. }
  243. }
  244. void pluginLogOut() {
  245. gToken = "";
  246. }
  247. void pluginRefresh() {
  248. if (gToken.empty())
  249. return;
  250. isDownloading = true;
  251. downloadProgress = 0.0;
  252. downloadName = "";
  253. json_t *reqJ = json_object();
  254. json_object_set(reqJ, "token", json_string(gToken.c_str()));
  255. json_t *resJ = requestJson(METHOD_GET, gApiHost + "/purchases", reqJ);
  256. json_decref(reqJ);
  257. if (resJ) {
  258. json_t *errorJ = json_object_get(resJ, "error");
  259. if (errorJ) {
  260. const char *errorStr = json_string_value(errorJ);
  261. fprintf(stderr, "Plugin refresh error: %s\n", errorStr);
  262. }
  263. else {
  264. json_t *purchasesJ = json_object_get(resJ, "purchases");
  265. size_t index;
  266. json_t *purchaseJ;
  267. json_array_foreach(purchasesJ, index, purchaseJ) {
  268. refreshPurchase(purchaseJ);
  269. }
  270. }
  271. json_decref(resJ);
  272. }
  273. isDownloading = false;
  274. }
  275. void pluginCancelDownload() {
  276. // TODO
  277. }
  278. bool pluginIsLoggedIn() {
  279. return gToken != "";
  280. }
  281. bool pluginIsDownloading() {
  282. return isDownloading;
  283. }
  284. float pluginGetDownloadProgress() {
  285. return downloadProgress;
  286. }
  287. std::string pluginGetDownloadName() {
  288. return downloadName;
  289. }
  290. std::string pluginGetLoginStatus() {
  291. return loginStatus;
  292. }
  293. } // namespace rack