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.

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