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.

368 lines
8.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 <curl/curl.h>
  10. #include <zip.h>
  11. #include <jansson.h>
  12. #if ARCH_WIN
  13. #include <windows.h>
  14. #include <shellapi.h>
  15. #include <direct.h>
  16. #define mkdir(_dir, _perms) _mkdir(_dir)
  17. #else
  18. #include <dlfcn.h>
  19. #endif
  20. #include <dirent.h>
  21. #include "plugin.hpp"
  22. namespace rack {
  23. std::list<Plugin*> gPlugins;
  24. static const std::string apiUrl = "http://localhost:8081";
  25. static std::string token;
  26. static bool isDownloading = false;
  27. static float downloadProgress = 0.0;
  28. static std::string downloadName;
  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. fprintf(stderr, "Failed to load library %s\n", path.c_str());
  47. return -1;
  48. }
  49. #elif ARCH_LIN || ARCH_MAC
  50. void *handle = dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL);
  51. if (!handle) {
  52. fprintf(stderr, "Failed to load library %s: %s\n", path.c_str(), dlerror());
  53. return -1;
  54. }
  55. #endif
  56. // Call plugin init() function
  57. typedef Plugin *(*InitCallback)();
  58. InitCallback initCallback;
  59. #if ARCH_WIN
  60. initCallback = (InitCallback) GetProcAddress(handle, "init");
  61. #elif ARCH_LIN || ARCH_MAC
  62. initCallback = (InitCallback) dlsym(handle, "init");
  63. #endif
  64. if (!initCallback) {
  65. fprintf(stderr, "Failed to read init() symbol in %s\n", path.c_str());
  66. return -2;
  67. }
  68. // Add plugin to map
  69. Plugin *plugin = initCallback();
  70. if (!plugin) {
  71. fprintf(stderr, "Library %s did not return a plugin\n", path.c_str());
  72. return -3;
  73. }
  74. gPlugins.push_back(plugin);
  75. fprintf(stderr, "Loaded plugin %s\n", path.c_str());
  76. return 0;
  77. }
  78. void pluginInit() {
  79. curl_global_init(CURL_GLOBAL_NOTHING);
  80. // Load core
  81. // This function is defined in core.cpp
  82. Plugin *corePlugin = init();
  83. gPlugins.push_back(corePlugin);
  84. // Search for plugin libraries
  85. DIR *dir = opendir("plugins");
  86. if (dir) {
  87. struct dirent *d;
  88. while ((d = readdir(dir))) {
  89. if (d->d_name[0] == '.')
  90. continue;
  91. loadPlugin(d->d_name);
  92. }
  93. closedir(dir);
  94. }
  95. }
  96. void pluginDestroy() {
  97. for (Plugin *plugin : gPlugins) {
  98. // TODO free shared library handle with `dlclose` or `FreeLibrary`
  99. delete plugin;
  100. }
  101. gPlugins.clear();
  102. curl_global_cleanup();
  103. }
  104. ////////////////////
  105. // CURL and libzip helpers
  106. ////////////////////
  107. static size_t write_file_callback(void *data, size_t size, size_t nmemb, void *p) {
  108. int fd = *((int*)p);
  109. ssize_t len = write(fd, data, size*nmemb);
  110. return len;
  111. }
  112. static int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
  113. if (dltotal == 0.0)
  114. return 0;
  115. float progress = dlnow / dltotal;
  116. downloadProgress = progress;
  117. return 0;
  118. }
  119. static CURLcode download_file(int fd, const char *url) {
  120. CURL *curl = curl_easy_init();
  121. curl_easy_setopt(curl, CURLOPT_URL, url);
  122. curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
  123. curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
  124. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file_callback);
  125. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fd);
  126. curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
  127. curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, NULL);
  128. CURLcode res = curl_easy_perform(curl);
  129. curl_easy_cleanup(curl);
  130. return res;
  131. }
  132. static size_t write_string_callback(void *data, size_t size, size_t nmemb, void *p) {
  133. std::string &text = *((std::string*)p);
  134. char *dataStr = (char*) data;
  135. size_t len = size * nmemb;
  136. text.append(dataStr, len);
  137. return len;
  138. }
  139. static void extract_zip(const char *dir, int zipfd) {
  140. int err = 0;
  141. zip_t *za = zip_fdopen(zipfd, 0, &err);
  142. if (!za) return;
  143. if (err) goto cleanup;
  144. for (int i = 0; i < zip_get_num_entries(za, 0); i++) {
  145. zip_stat_t zs;
  146. err = zip_stat_index(za, i, 0, &zs);
  147. if (err) goto cleanup;
  148. int nameLen = strlen(zs.name);
  149. char path[MAXPATHLEN];
  150. snprintf(path, sizeof(path), "%s/%s", dir, zs.name);
  151. if (zs.name[nameLen - 1] == '/') {
  152. err = mkdir(path, 0755);
  153. if (err) goto cleanup;
  154. }
  155. else {
  156. zip_file_t *zf = zip_fopen_index(za, i, 0);
  157. if (!zf) goto cleanup;
  158. int out = open(path, O_RDWR | O_TRUNC | O_CREAT, 0644);
  159. assert(out != -1);
  160. while (1) {
  161. char buffer[4096];
  162. int len = zip_fread(zf, buffer, sizeof(buffer));
  163. if (len <= 0)
  164. break;
  165. write(out, buffer, len);
  166. }
  167. err = zip_fclose(zf);
  168. assert(!err);
  169. close(out);
  170. }
  171. }
  172. cleanup:
  173. zip_close(za);
  174. }
  175. ////////////////////
  176. // plugin manager
  177. ////////////////////
  178. void pluginOpenBrowser(std::string url) {
  179. // shell injection is possible, so make sure the URL is trusted
  180. #if ARCH_LIN
  181. std::string command = "xdg-open " + url;
  182. system(command.c_str());
  183. #endif
  184. #if ARCH_MAC
  185. std::string command = "open " + url;
  186. system(command.c_str());
  187. #endif
  188. #if ARCH_WIN
  189. ShellExecute(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL);
  190. #endif
  191. }
  192. void pluginLogIn(std::string email, std::string password) {
  193. CURL *curl = curl_easy_init();
  194. assert(curl);
  195. std::string postFields = "email=" + email + "&password=" + password;
  196. std::string url = apiUrl + "/token";
  197. std::string resText;
  198. curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  199. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_string_callback);
  200. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText);
  201. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields.c_str());
  202. curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postFields.size());
  203. CURLcode res = curl_easy_perform(curl);
  204. curl_easy_cleanup(curl);
  205. if (res == CURLE_OK) {
  206. // Parse JSON response
  207. json_error_t error;
  208. json_t *rootJ = json_loads(resText.c_str(), 0, &error);
  209. if (rootJ) {
  210. json_t *tokenJ = json_object_get(rootJ, "token");
  211. if (tokenJ) {
  212. // Set the token, which logs the user in
  213. token = json_string_value(tokenJ);
  214. }
  215. json_decref(rootJ);
  216. }
  217. }
  218. }
  219. void pluginLogOut() {
  220. token = "";
  221. }
  222. static void pluginRefreshPlugin(json_t *pluginJ) {
  223. json_t *slugJ = json_object_get(pluginJ, "slug");
  224. if (!slugJ) return;
  225. std::string slug = json_string_value(slugJ);
  226. json_t *nameJ = json_object_get(pluginJ, "name");
  227. if (!nameJ) return;
  228. std::string name = json_string_value(nameJ);
  229. json_t *urlJ = json_object_get(pluginJ, "download");
  230. if (!urlJ) return;
  231. std::string url = json_string_value(urlJ);
  232. // Find slug in plugins list
  233. for (Plugin *p : gPlugins) {
  234. if (p->slug == slug) {
  235. return;
  236. }
  237. }
  238. // If plugin is not loaded, download the zip file to /plugins
  239. fprintf(stderr, "Downloading %s from %s\n", name.c_str(), url.c_str());
  240. downloadName = name;
  241. downloadProgress = 0.0;
  242. const char *dir = "plugins";
  243. char path[MAXPATHLEN];
  244. snprintf(path, sizeof(path), "%s/%s.zip", dir, slug.c_str());
  245. int zip = open(path, O_RDWR | O_TRUNC | O_CREAT, 0644);
  246. // Download zip
  247. download_file(zip, url.c_str());
  248. // Unzip file
  249. lseek(zip, 0, SEEK_SET);
  250. extract_zip(dir, zip);
  251. // Close file
  252. close(zip);
  253. downloadName = "";
  254. // Load plugin
  255. }
  256. void pluginRefresh() {
  257. if (token.empty())
  258. return;
  259. isDownloading = true;
  260. downloadProgress = 0.0;
  261. downloadName = "";
  262. // Get plugin list from /plugin
  263. CURL *curl = curl_easy_init();
  264. assert(curl);
  265. std::string url = apiUrl + "/plugins?token=" + token;
  266. std::string resText;
  267. curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  268. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_string_callback);
  269. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText);
  270. CURLcode res = curl_easy_perform(curl);
  271. curl_easy_cleanup(curl);
  272. if (res == CURLE_OK) {
  273. // Parse JSON response
  274. json_error_t error;
  275. json_t *rootJ = json_loads(resText.c_str(), 0, &error);
  276. if (rootJ) {
  277. json_t *pluginsJ = json_object_get(rootJ, "plugins");
  278. if (pluginsJ) {
  279. // Iterate through each plugin object
  280. size_t index;
  281. json_t *pluginJ;
  282. json_array_foreach(pluginsJ, index, pluginJ) {
  283. pluginRefreshPlugin(pluginJ);
  284. }
  285. }
  286. json_decref(rootJ);
  287. }
  288. }
  289. isDownloading = false;
  290. }
  291. void pluginCancelDownload() {
  292. // TODO
  293. }
  294. bool pluginIsLoggedIn() {
  295. return token != "";
  296. }
  297. bool pluginIsDownloading() {
  298. return isDownloading;
  299. }
  300. float pluginGetDownloadProgress() {
  301. return downloadProgress;
  302. }
  303. std::string pluginGetDownloadName() {
  304. return downloadName;
  305. }
  306. } // namespace rack