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.

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