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.

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