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.

321 lines
7.7KB

  1. #include <thread>
  2. #include <library.hpp>
  3. #include <settings.hpp>
  4. #include <app/common.hpp>
  5. #include <network.hpp>
  6. #include <system.hpp>
  7. #include <context.hpp>
  8. #include <window.hpp>
  9. #include <asset.hpp>
  10. #include <settings.hpp>
  11. #include <plugin.hpp>
  12. namespace rack {
  13. namespace library {
  14. void init() {
  15. if (settings::autoCheckUpdates && !settings::devMode) {
  16. std::thread t([&]() {
  17. checkAppUpdate();
  18. });
  19. t.detach();
  20. std::thread t2([&] {
  21. checkUpdates();
  22. });
  23. t2.detach();
  24. }
  25. }
  26. void destroy() {
  27. }
  28. void checkAppUpdate() {
  29. std::string versionUrl = API_URL + "/version";
  30. json_t* resJ = network::requestJson(network::METHOD_GET, versionUrl, NULL);
  31. if (!resJ) {
  32. WARN("Request for version failed");
  33. return;
  34. }
  35. DEFER({json_decref(resJ);});
  36. json_t* versionJ = json_object_get(resJ, "version");
  37. if (versionJ)
  38. appVersion = json_string_value(versionJ);
  39. json_t* changelogUrlJ = json_object_get(resJ, "changelogUrl");
  40. if (changelogUrlJ)
  41. appChangelogUrl = json_string_value(changelogUrlJ);
  42. json_t* downloadUrlsJ = json_object_get(resJ, "downloadUrls");
  43. if (downloadUrlsJ) {
  44. json_t* downloadUrlJ = json_object_get(downloadUrlsJ, APP_ARCH.c_str());
  45. if (downloadUrlJ)
  46. appDownloadUrl = json_string_value(downloadUrlJ);
  47. }
  48. }
  49. bool isAppUpdateAvailable() {
  50. return (appVersion != "") && (appVersion != APP_VERSION);
  51. }
  52. bool isLoggedIn() {
  53. return settings::token != "";
  54. }
  55. void logIn(const std::string& email, const std::string& password) {
  56. loginStatus = "Logging in...";
  57. json_t* reqJ = json_object();
  58. json_object_set(reqJ, "email", json_string(email.c_str()));
  59. json_object_set(reqJ, "password", json_string(password.c_str()));
  60. std::string url = API_URL + "/token";
  61. json_t* resJ = network::requestJson(network::METHOD_POST, url, reqJ);
  62. json_decref(reqJ);
  63. if (!resJ) {
  64. loginStatus = "No response from server";
  65. return;
  66. }
  67. DEFER({json_decref(resJ);});
  68. json_t* errorJ = json_object_get(resJ, "error");
  69. if (errorJ) {
  70. const char* errorStr = json_string_value(errorJ);
  71. loginStatus = errorStr;
  72. return;
  73. }
  74. json_t* tokenJ = json_object_get(resJ, "token");
  75. if (!tokenJ) {
  76. loginStatus = "No token in response";
  77. return;
  78. }
  79. const char* tokenStr = json_string_value(tokenJ);
  80. settings::token = tokenStr;
  81. loginStatus = "";
  82. checkUpdates();
  83. }
  84. void logOut() {
  85. settings::token = "";
  86. updates.clear();
  87. }
  88. void checkUpdates() {
  89. if (settings::token.empty())
  90. return;
  91. updates.clear();
  92. updateStatus = "Querying for updates...";
  93. // Get user's plugins list
  94. std::string pluginsUrl = API_URL + "/plugins";
  95. network::CookieMap cookies;
  96. cookies["token"] = settings::token;
  97. json_t* pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, NULL, cookies);
  98. if (!pluginsResJ) {
  99. WARN("Request for user's plugins failed");
  100. updateStatus = "Could not query updates";
  101. return;
  102. }
  103. DEFER({json_decref(pluginsResJ);});
  104. json_t* errorJ = json_object_get(pluginsResJ, "error");
  105. if (errorJ) {
  106. WARN("Request for user's plugins returned an error: %s", json_string_value(errorJ));
  107. updateStatus = "Could not query updates";
  108. return;
  109. }
  110. // Get library manifests
  111. std::string manifestsUrl = API_URL + "/library/manifests";
  112. json_t* manifestsReq = json_object();
  113. json_object_set(manifestsReq, "version", json_string(API_VERSION.c_str()));
  114. json_t* manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, manifestsReq);
  115. json_decref(manifestsReq);
  116. if (!manifestsResJ) {
  117. WARN("Request for library manifests failed");
  118. updateStatus = "Could not query updates";
  119. return;
  120. }
  121. DEFER({json_decref(manifestsResJ);});
  122. json_t* manifestsJ = json_object_get(manifestsResJ, "manifests");
  123. json_t* pluginsJ = json_object_get(pluginsResJ, "plugins");
  124. size_t pluginIndex;
  125. json_t* pluginJ;
  126. json_array_foreach(pluginsJ, pluginIndex, pluginJ) {
  127. Update update;
  128. // Get plugin manifest
  129. std::string slug = json_string_value(pluginJ);
  130. json_t* manifestJ = json_object_get(manifestsJ, slug.c_str());
  131. if (!manifestJ) {
  132. WARN("VCV account has plugin %s but no manifest was found", slug.c_str());
  133. continue;
  134. }
  135. // Get plugin name
  136. json_t* nameJ = json_object_get(manifestJ, "name");
  137. if (nameJ)
  138. update.name = json_string_value(nameJ);
  139. // Get version
  140. json_t* versionJ = json_object_get(manifestJ, "version");
  141. if (!versionJ) {
  142. WARN("Plugin %s has no version in manifest", slug.c_str());
  143. continue;
  144. }
  145. update.version = json_string_value(versionJ);
  146. // Check if update is needed
  147. plugin::Plugin* p = plugin::getPlugin(slug);
  148. if (p && p->version == update.version)
  149. continue;
  150. // Don't add update if it exists already
  151. auto it = updates.find(slug);
  152. if (it != updates.end()) {
  153. if (it->second.version == update.version)
  154. continue;
  155. }
  156. // Check status
  157. json_t* statusJ = json_object_get(manifestJ, "status");
  158. if (!statusJ)
  159. continue;
  160. std::string status = json_string_value(statusJ);
  161. if (status != "available")
  162. continue;
  163. // Get changelog URL
  164. json_t* changelogUrlJ = json_object_get(manifestJ, "changelogUrl");
  165. if (changelogUrlJ) {
  166. update.changelogUrl = json_string_value(changelogUrlJ);
  167. }
  168. // Add update to updates map
  169. updates[slug] = update;
  170. }
  171. // Get module whitelist
  172. {
  173. std::string whitelistUrl = API_URL + "/moduleWhitelist";
  174. json_t* whitelistResJ = network::requestJson(network::METHOD_GET, whitelistUrl, NULL, cookies);
  175. if (!whitelistResJ) {
  176. WARN("Request for module whitelist failed");
  177. updateStatus = "Could not query updates";
  178. return;
  179. }
  180. DEFER({json_decref(whitelistResJ);});
  181. std::map<std::string, std::set<std::string>> moduleWhitelist;
  182. json_t* pluginsJ = json_object_get(whitelistResJ, "plugins");
  183. // Iterate plugins
  184. const char* pluginSlug;
  185. json_t* modulesJ;
  186. json_object_foreach(pluginsJ, pluginSlug, modulesJ) {
  187. // Iterate modules in plugin
  188. size_t moduleIndex;
  189. json_t* moduleSlugJ;
  190. json_array_foreach(modulesJ, moduleIndex, moduleSlugJ) {
  191. std::string moduleSlug = json_string_value(moduleSlugJ);
  192. // Insert module in whitelist
  193. moduleWhitelist[pluginSlug].insert(moduleSlug);
  194. }
  195. }
  196. settings::moduleWhitelist = moduleWhitelist;
  197. }
  198. updateStatus = "";
  199. }
  200. bool hasUpdates() {
  201. for (auto& pair : updates) {
  202. if (pair.second.progress < 1.f)
  203. return true;
  204. }
  205. return false;
  206. }
  207. void syncUpdate(const std::string& slug) {
  208. if (settings::token.empty())
  209. return;
  210. auto it = updates.find(slug);
  211. if (it == updates.end())
  212. return;
  213. Update& update = it->second;
  214. updatingSlug = slug;
  215. DEFER({updatingSlug = "";});
  216. std::string downloadUrl = API_URL + "/download";
  217. downloadUrl += "?slug=" + network::encodeUrl(slug);
  218. downloadUrl += "&version=" + network::encodeUrl(update.version);
  219. downloadUrl += "&arch=" + network::encodeUrl(APP_ARCH);
  220. network::CookieMap cookies;
  221. cookies["token"] = settings::token;
  222. INFO("Downloading plugin %s v%s for %s", slug.c_str(), update.version.c_str(), APP_ARCH.c_str());
  223. // Download plugin package
  224. std::string packageFilename = slug + "-" + update.version + "-" + APP_ARCH + ".vcvplugin";
  225. std::string packagePath = system::join(asset::pluginsPath, packageFilename);
  226. if (!network::requestDownload(downloadUrl, packagePath, &update.progress, cookies)) {
  227. WARN("Plugin %s download was unsuccessful", slug.c_str());
  228. return;
  229. }
  230. }
  231. void syncUpdates() {
  232. if (settings::token.empty())
  233. return;
  234. // Iterate by value because the map might change
  235. for (auto pair : updates) {
  236. syncUpdate(pair.first);
  237. }
  238. restartRequested = true;
  239. }
  240. bool isSyncing() {
  241. return updatingPlugins || (updatingSlug != "");
  242. }
  243. std::string appVersion;
  244. std::string appDownloadUrl;
  245. std::string appChangelogUrl;
  246. std::string loginStatus;
  247. std::map<std::string, Update> updates;
  248. std::string updateStatus;
  249. std::string updatingSlug;
  250. bool updatingPlugins = false;
  251. bool restartRequested = false;
  252. } // namespace library
  253. } // namespace rack