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.

370 lines
9.5KB

  1. #include <thread>
  2. #include <mutex>
  3. #include <condition_variable>
  4. #include <library.hpp>
  5. #include <settings.hpp>
  6. #include <app/common.hpp>
  7. #include <network.hpp>
  8. #include <system.hpp>
  9. #include <context.hpp>
  10. #include <window/Window.hpp>
  11. #include <asset.hpp>
  12. #include <settings.hpp>
  13. #include <plugin.hpp>
  14. namespace rack {
  15. namespace library {
  16. static std::mutex updatesLoopMutex;
  17. static std::condition_variable updatesLoopCv;
  18. static bool updatesLoopRunning = false;
  19. static void checkUpdatesLoop() {
  20. updatesLoopRunning = true;
  21. while (updatesLoopRunning) {
  22. checkUpdates();
  23. // Sleep a few seconds, or wake up when destroy() is called
  24. std::unique_lock<std::mutex> lock(updatesLoopMutex);
  25. auto duration = std::chrono::seconds(60);
  26. if (!updatesLoopRunning)
  27. break;
  28. updatesLoopCv.wait_for(lock, duration, []() {return !updatesLoopRunning;});
  29. }
  30. }
  31. void init() {
  32. if (settings::autoCheckUpdates && !settings::devMode) {
  33. // std::thread t([&]() {
  34. // checkAppUpdate();
  35. // });
  36. // t.detach();
  37. std::thread t2([&] {
  38. checkUpdatesLoop();
  39. });
  40. t2.detach();
  41. }
  42. }
  43. void destroy() {
  44. // Stop checkUpdatesLoop thread if it's running
  45. {
  46. std::lock_guard<std::mutex> lock(updatesLoopMutex);
  47. updatesLoopRunning = false;
  48. updatesLoopCv.notify_all();
  49. }
  50. }
  51. void checkAppUpdate() {
  52. std::string versionUrl = API_URL + "/version";
  53. json_t* reqJ = json_object();
  54. json_object_set(reqJ, "edition", json_string(APP_EDITION.c_str()));
  55. DEFER({json_decref(reqJ);});
  56. json_t* resJ = network::requestJson(network::METHOD_GET, versionUrl, reqJ);
  57. if (!resJ) {
  58. WARN("Request for version failed");
  59. return;
  60. }
  61. DEFER({json_decref(resJ);});
  62. json_t* versionJ = json_object_get(resJ, "version");
  63. if (versionJ)
  64. appVersion = json_string_value(versionJ);
  65. json_t* changelogUrlJ = json_object_get(resJ, "changelogUrl");
  66. if (changelogUrlJ)
  67. appChangelogUrl = json_string_value(changelogUrlJ);
  68. json_t* downloadUrlsJ = json_object_get(resJ, "downloadUrls");
  69. if (downloadUrlsJ) {
  70. json_t* downloadUrlJ = json_object_get(downloadUrlsJ, APP_ARCH.c_str());
  71. if (downloadUrlJ)
  72. appDownloadUrl = json_string_value(downloadUrlJ);
  73. }
  74. }
  75. bool isAppUpdateAvailable() {
  76. return (appVersion != "") && (appVersion != APP_VERSION);
  77. }
  78. bool isLoggedIn() {
  79. return settings::token != "";
  80. }
  81. void logIn(const std::string& email, const std::string& password) {
  82. loginStatus = "Logging in...";
  83. json_t* reqJ = json_object();
  84. json_object_set(reqJ, "email", json_string(email.c_str()));
  85. json_object_set(reqJ, "password", json_string(password.c_str()));
  86. std::string url = API_URL + "/token";
  87. json_t* resJ = network::requestJson(network::METHOD_POST, url, reqJ);
  88. json_decref(reqJ);
  89. if (!resJ) {
  90. loginStatus = "No response from server";
  91. return;
  92. }
  93. DEFER({json_decref(resJ);});
  94. json_t* errorJ = json_object_get(resJ, "error");
  95. if (errorJ) {
  96. const char* errorStr = json_string_value(errorJ);
  97. loginStatus = errorStr;
  98. return;
  99. }
  100. json_t* tokenJ = json_object_get(resJ, "token");
  101. if (!tokenJ) {
  102. loginStatus = "No token in response";
  103. return;
  104. }
  105. const char* tokenStr = json_string_value(tokenJ);
  106. settings::token = tokenStr;
  107. loginStatus = "";
  108. checkUpdates();
  109. }
  110. void logOut() {
  111. settings::token = "";
  112. updateInfos.clear();
  113. }
  114. static network::CookieMap getTokenCookies() {
  115. network::CookieMap cookies;
  116. cookies["token"] = settings::token;
  117. return cookies;
  118. }
  119. void checkUpdates() {
  120. if (settings::token.empty())
  121. return;
  122. // Refuse to check for updates while updating plugins
  123. if (isSyncing)
  124. return;
  125. updateStatus = "Querying for updates...";
  126. // Get user's plugins list
  127. std::string pluginsUrl = API_URL + "/plugins";
  128. json_t* pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, NULL, getTokenCookies());
  129. if (!pluginsResJ) {
  130. WARN("Request for user's plugins failed");
  131. updateStatus = "Could not query plugins";
  132. return;
  133. }
  134. DEFER({json_decref(pluginsResJ);});
  135. json_t* errorJ = json_object_get(pluginsResJ, "error");
  136. if (errorJ) {
  137. WARN("Request for user's plugins returned an error: %s", json_string_value(errorJ));
  138. updateStatus = "Could not query plugins";
  139. return;
  140. }
  141. // Get library manifests
  142. std::string manifestsUrl = API_URL + "/library/manifests";
  143. json_t* manifestsReq = json_object();
  144. json_object_set(manifestsReq, "version", json_string(APP_VERSION_MAJOR.c_str()));
  145. json_t* manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, manifestsReq);
  146. json_decref(manifestsReq);
  147. if (!manifestsResJ) {
  148. WARN("Request for library manifests failed");
  149. updateStatus = "Could not query updates";
  150. return;
  151. }
  152. DEFER({json_decref(manifestsResJ);});
  153. json_t* manifestsJ = json_object_get(manifestsResJ, "manifests");
  154. json_t* pluginsJ = json_object_get(pluginsResJ, "plugins");
  155. size_t pluginIndex;
  156. json_t* pluginJ;
  157. json_array_foreach(pluginsJ, pluginIndex, pluginJ) {
  158. // Get plugin manifest
  159. std::string slug = json_string_value(pluginJ);
  160. json_t* manifestJ = json_object_get(manifestsJ, slug.c_str());
  161. if (!manifestJ) {
  162. WARN("VCV account has plugin %s but no manifest was found", slug.c_str());
  163. continue;
  164. }
  165. // Don't replace existing UpdateInfo, even if version is newer.
  166. // This keeps things sane and ensures that only one version of each plugin is downloaded to `plugins/` at a time.
  167. auto it = updateInfos.find(slug);
  168. if (it != updateInfos.end()) {
  169. continue;
  170. }
  171. UpdateInfo update;
  172. // Get plugin name
  173. json_t* nameJ = json_object_get(manifestJ, "name");
  174. if (nameJ)
  175. update.name = json_string_value(nameJ);
  176. // Get version
  177. json_t* versionJ = json_object_get(manifestJ, "version");
  178. if (!versionJ) {
  179. // WARN("Plugin %s has no version in manifest", slug.c_str());
  180. continue;
  181. }
  182. update.version = json_string_value(versionJ);
  183. // Reject plugins with ABI mismatch
  184. if (!string::startsWith(update.version, APP_VERSION_MAJOR + ".")) {
  185. continue;
  186. }
  187. // Check if update is needed
  188. plugin::Plugin* p = plugin::getPlugin(slug);
  189. if (p && p->version == update.version)
  190. continue;
  191. // Require that plugin is available
  192. json_t* availableJ = json_object_get(manifestJ, "available");
  193. if (!json_boolean_value(availableJ))
  194. continue;
  195. // Get changelog URL
  196. json_t* changelogUrlJ = json_object_get(manifestJ, "changelogUrl");
  197. if (changelogUrlJ)
  198. update.changelogUrl = json_string_value(changelogUrlJ);
  199. // Add update to updates map
  200. updateInfos[slug] = update;
  201. }
  202. // Get module whitelist
  203. // TODO
  204. // {
  205. // std::string whitelistUrl = API_URL + "/modules";
  206. // json_t* whitelistResJ = network::requestJson(network::METHOD_GET, whitelistUrl, NULL, getTokenCookies());
  207. // if (!whitelistResJ) {
  208. // WARN("Request for module whitelist failed");
  209. // updateStatus = "Could not query updates";
  210. // return;
  211. // }
  212. // DEFER({json_decref(whitelistResJ);});
  213. // std::map<std::string, std::set<std::string>> moduleWhitelist;
  214. // json_t* pluginsJ = json_object_get(whitelistResJ, "plugins");
  215. // // Iterate plugins
  216. // const char* pluginSlug;
  217. // json_t* modulesJ;
  218. // json_object_foreach(pluginsJ, pluginSlug, modulesJ) {
  219. // // Iterate modules in plugin
  220. // size_t moduleIndex;
  221. // json_t* moduleSlugJ;
  222. // json_array_foreach(modulesJ, moduleIndex, moduleSlugJ) {
  223. // std::string moduleSlug = json_string_value(moduleSlugJ);
  224. // // Insert module in whitelist
  225. // moduleWhitelist[pluginSlug].insert(moduleSlug);
  226. // }
  227. // }
  228. // settings::moduleWhitelist = moduleWhitelist;
  229. // }
  230. updateStatus = "";
  231. }
  232. bool hasUpdates() {
  233. for (auto& pair : updateInfos) {
  234. if (!pair.second.downloaded)
  235. return true;
  236. }
  237. return false;
  238. }
  239. void syncUpdate(const std::string& slug) {
  240. if (settings::token.empty())
  241. return;
  242. isSyncing = true;
  243. DEFER({isSyncing = false;});
  244. // Get the UpdateInfo object
  245. auto it = updateInfos.find(slug);
  246. if (it == updateInfos.end())
  247. return;
  248. UpdateInfo update = it->second;
  249. updateSlug = slug;
  250. DEFER({updateSlug = "";});
  251. // Set progress to 0%
  252. updateProgress = 0.f;
  253. DEFER({updateProgress = 0.f;});
  254. INFO("Downloading plugin %s v%s for %s", slug.c_str(), update.version.c_str(), APP_ARCH.c_str());
  255. // Get download URL
  256. std::string downloadUrl = API_URL + "/download";
  257. downloadUrl += "?slug=" + network::encodeUrl(slug);
  258. downloadUrl += "&version=" + network::encodeUrl(update.version);
  259. downloadUrl += "&arch=" + network::encodeUrl(APP_ARCH);
  260. // Get file path
  261. std::string packageFilename = slug + "-" + update.version + "-" + APP_ARCH + ".vcvplugin";
  262. std::string packagePath = system::join(plugin::pluginsPath, packageFilename);
  263. // Download plugin package
  264. if (!network::requestDownload(downloadUrl, packagePath, &updateProgress, getTokenCookies())) {
  265. WARN("Plugin %s download was unsuccessful", slug.c_str());
  266. return;
  267. }
  268. // updateInfos could possibly change in the checkUpdates() thread, so re-get the UpdateInfo to modify it.
  269. it = updateInfos.find(slug);
  270. if (it == updateInfos.end())
  271. return;
  272. it->second.downloaded = true;
  273. }
  274. void syncUpdates() {
  275. if (settings::token.empty())
  276. return;
  277. // updateInfos could possibly change in the checkUpdates() thread, but checkUpdates() will not execute if syncUpdate() is running, so the chance of the updateInfos map being modified while iterating is rare.
  278. auto updateInfosClone = updateInfos;
  279. for (auto& pair : updateInfosClone) {
  280. syncUpdate(pair.first);
  281. }
  282. restartRequested = true;
  283. }
  284. std::string appVersion;
  285. std::string appDownloadUrl;
  286. std::string appChangelogUrl;
  287. std::string loginStatus;
  288. std::map<std::string, UpdateInfo> updateInfos;
  289. std::string updateStatus;
  290. std::string updateSlug;
  291. float updateProgress = 0.f;
  292. bool isSyncing = false;
  293. bool restartRequested = false;
  294. } // namespace library
  295. } // namespace rack