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.

429 lines
11KB

  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. #include <string.hpp>
  15. namespace rack {
  16. namespace library {
  17. static std::mutex appUpdateMutex;
  18. static std::mutex updateMutex;
  19. static std::mutex timeoutMutex;
  20. static std::condition_variable updateCv;
  21. void init() {
  22. if (!settings::autoCheckUpdates)
  23. return;
  24. // Dev mode is typically used when Rack or plugins are compiled from source, so updating might overwrite assets.
  25. if (settings::devMode)
  26. return;
  27. // Safe mode disables plugin loading, so Rack will unnecessarily try to sync all plugins.
  28. if (settings::safeMode)
  29. return;
  30. std::thread t([&]() {
  31. system::setThreadName("Library");
  32. // Wait a few seconds before updating in case library is destroyed immediately afterwards
  33. {
  34. std::unique_lock<std::mutex> lock(timeoutMutex);
  35. if (updateCv.wait_for(lock, std::chrono::duration<double>(4.0)) != std::cv_status::timeout)
  36. return;
  37. }
  38. checkAppUpdate();
  39. checkUpdates();
  40. });
  41. t.detach();
  42. }
  43. void destroy() {
  44. // Wait until all library threads are finished
  45. updateCv.notify_all();
  46. std::lock_guard<std::mutex> timeoutLock(timeoutMutex);
  47. std::lock_guard<std::mutex> appUpdateLock(appUpdateMutex);
  48. std::lock_guard<std::mutex> updateLock(updateMutex);
  49. }
  50. void checkAppUpdate() {
  51. if (!appUpdateMutex.try_lock())
  52. return;
  53. DEFER({appUpdateMutex.unlock();});
  54. std::string versionUrl = API_URL + "/version";
  55. json_t* reqJ = json_object();
  56. json_object_set_new(reqJ, "edition", json_string(APP_EDITION.c_str()));
  57. DEFER({json_decref(reqJ);});
  58. json_t* resJ = network::requestJson(network::METHOD_GET, versionUrl, reqJ);
  59. if (!resJ) {
  60. WARN("Request for version failed");
  61. return;
  62. }
  63. DEFER({json_decref(resJ);});
  64. json_t* versionJ = json_object_get(resJ, "version");
  65. if (versionJ) {
  66. std::string appVersion = json_string_value(versionJ);
  67. // Check if app version is more recent than current version
  68. if (string::Version(APP_VERSION) < string::Version(appVersion))
  69. library::appVersion = appVersion;
  70. }
  71. json_t* changelogUrlJ = json_object_get(resJ, "changelogUrl");
  72. if (changelogUrlJ)
  73. appChangelogUrl = json_string_value(changelogUrlJ);
  74. json_t* downloadUrlsJ = json_object_get(resJ, "downloadUrls");
  75. if (downloadUrlsJ) {
  76. std::string arch = APP_OS + "-" + APP_CPU;
  77. json_t* downloadUrlJ = json_object_get(downloadUrlsJ, arch.c_str());
  78. if (downloadUrlJ)
  79. appDownloadUrl = json_string_value(downloadUrlJ);
  80. }
  81. }
  82. bool isAppUpdateAvailable() {
  83. return (appVersion != "");
  84. }
  85. bool isLoggedIn() {
  86. return settings::token != "";
  87. }
  88. void logIn(std::string email, std::string password) {
  89. if (!updateMutex.try_lock())
  90. return;
  91. DEFER({updateMutex.unlock();});
  92. loginStatus = "Logging in...";
  93. json_t* reqJ = json_object();
  94. json_object_set_new(reqJ, "email", json_string(email.c_str()));
  95. json_object_set_new(reqJ, "password", json_string(password.c_str()));
  96. std::string url = API_URL + "/token";
  97. json_t* resJ = network::requestJson(network::METHOD_POST, url, reqJ);
  98. json_decref(reqJ);
  99. if (!resJ) {
  100. loginStatus = "No response from server";
  101. return;
  102. }
  103. DEFER({json_decref(resJ);});
  104. json_t* errorJ = json_object_get(resJ, "error");
  105. if (errorJ) {
  106. const char* errorStr = json_string_value(errorJ);
  107. loginStatus = errorStr;
  108. return;
  109. }
  110. json_t* tokenJ = json_object_get(resJ, "token");
  111. if (!tokenJ) {
  112. loginStatus = "No token in response";
  113. return;
  114. }
  115. const char* tokenStr = json_string_value(tokenJ);
  116. settings::token = tokenStr;
  117. loginStatus = "";
  118. refreshRequested = true;
  119. }
  120. void logOut() {
  121. settings::token = "";
  122. updateInfos.clear();
  123. }
  124. static network::CookieMap getTokenCookies() {
  125. network::CookieMap cookies;
  126. cookies["token"] = settings::token;
  127. return cookies;
  128. }
  129. void checkUpdates() {
  130. if (!updateMutex.try_lock())
  131. return;
  132. DEFER({updateMutex.unlock();});
  133. if (settings::token.empty())
  134. return;
  135. // Refuse to check for updates while updating plugins
  136. if (isSyncing)
  137. return;
  138. updateStatus = "Querying for updates...";
  139. // Check user token
  140. std::string userUrl = API_URL + "/user";
  141. json_t* userResJ = network::requestJson(network::METHOD_GET, userUrl, NULL, getTokenCookies());
  142. if (!userResJ) {
  143. WARN("Request for user account failed");
  144. updateStatus = "Could not query user account";
  145. return;
  146. }
  147. DEFER({json_decref(userResJ);});
  148. json_t* userErrorJ = json_object_get(userResJ, "error");
  149. if (userErrorJ) {
  150. std::string userError = json_string_value(userErrorJ);
  151. WARN("Request for user account error: %s", userError.c_str());
  152. // Unset token
  153. settings::token = "";
  154. refreshRequested = true;
  155. return;
  156. }
  157. // Get library manifests
  158. std::string manifestsUrl = API_URL + "/library/manifests";
  159. json_t* manifestsReq = json_object();
  160. json_object_set_new(manifestsReq, "version", json_string(APP_VERSION_MAJOR.c_str()));
  161. json_t* manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, manifestsReq);
  162. json_decref(manifestsReq);
  163. if (!manifestsResJ) {
  164. WARN("Request for library manifests failed");
  165. updateStatus = "Could not query plugin manifests";
  166. return;
  167. }
  168. DEFER({json_decref(manifestsResJ);});
  169. // Get user's modules
  170. std::string modulesUrl = API_URL + "/modules";
  171. json_t* modulesResJ = network::requestJson(network::METHOD_GET, modulesUrl, NULL, getTokenCookies());
  172. if (!modulesResJ) {
  173. WARN("Request for user's modules failed");
  174. updateStatus = "Could not query user's modules";
  175. return;
  176. }
  177. DEFER({json_decref(modulesResJ);});
  178. json_t* manifestsJ = json_object_get(manifestsResJ, "manifests");
  179. json_t* pluginsJ = json_object_get(modulesResJ, "modules");
  180. const char* modulesKey;
  181. json_t* modulesJ;
  182. json_object_foreach(pluginsJ, modulesKey, modulesJ) {
  183. std::string pluginSlug = modulesKey;
  184. // Get plugin manifest
  185. json_t* manifestJ = json_object_get(manifestsJ, pluginSlug.c_str());
  186. if (!manifestJ) {
  187. // Skip plugin silently
  188. continue;
  189. }
  190. // Don't replace existing UpdateInfo, even if version is newer.
  191. // This keeps things sane and ensures that only one version of each plugin is downloaded to `plugins/` at a time.
  192. auto it = updateInfos.find(pluginSlug);
  193. if (it != updateInfos.end()) {
  194. continue;
  195. }
  196. UpdateInfo update;
  197. // Get plugin name
  198. json_t* nameJ = json_object_get(manifestJ, "name");
  199. if (nameJ)
  200. update.name = json_string_value(nameJ);
  201. // Get version
  202. json_t* versionJ = json_object_get(manifestJ, "version");
  203. if (!versionJ) {
  204. // WARN("Plugin %s has no version in manifest", pluginSlug.c_str());
  205. continue;
  206. }
  207. update.version = json_string_value(versionJ);
  208. // Reject plugins with ABI mismatch
  209. if (!string::startsWith(update.version, APP_VERSION_MAJOR + ".")) {
  210. continue;
  211. }
  212. // Check that update is needed
  213. plugin::Plugin* p = plugin::getPlugin(pluginSlug);
  214. if (p) {
  215. if (update.version == p->version)
  216. continue;
  217. if (string::Version(update.version) < string::Version(p->version))
  218. continue;
  219. }
  220. // Check that plugin is available for this arch
  221. json_t* archesJ = json_object_get(manifestJ, "arches");
  222. if (!archesJ)
  223. continue;
  224. std::string arch = APP_OS + "-" + APP_CPU;
  225. json_t* archJ = json_object_get(archesJ, arch.c_str());
  226. if (!json_boolean_value(archJ))
  227. continue;
  228. // Get changelog URL
  229. json_t* changelogUrlJ = json_object_get(manifestJ, "changelogUrl");
  230. if (changelogUrlJ)
  231. update.changelogUrl = json_string_value(changelogUrlJ);
  232. // Get minRackVersion
  233. json_t* minRackVersionJ = json_object_get(manifestJ, "minRackVersion");
  234. if (minRackVersionJ) {
  235. std::string minRackVersion = json_string_value(minRackVersionJ);
  236. // Check that Rack version is at least minRackVersion
  237. if (string::Version(APP_VERSION) < string::Version(minRackVersion)) {
  238. update.minRackVersion = minRackVersion;
  239. }
  240. }
  241. // Add update to updates map
  242. updateInfos[pluginSlug] = update;
  243. }
  244. // Merge module whitelist
  245. {
  246. // Clone plugin slugs from settings to temporary whitelist.
  247. // This makes existing plugins entirely hidden if removed from user's VCV account.
  248. std::map<std::string, settings::PluginWhitelist> moduleWhitelist;
  249. for (const auto& pluginPair : settings::moduleWhitelist) {
  250. std::string pluginSlug = pluginPair.first;
  251. moduleWhitelist[pluginSlug] = settings::PluginWhitelist();
  252. }
  253. // Iterate plugins
  254. const char* modulesKey;
  255. json_t* modulesJ;
  256. json_object_foreach(pluginsJ, modulesKey, modulesJ) {
  257. std::string pluginSlug = modulesKey;
  258. settings::PluginWhitelist& pw = moduleWhitelist[pluginSlug];
  259. // If value is "true", plugin is subscribed
  260. if (json_is_true(modulesJ)) {
  261. pw.subscribed = true;
  262. continue;
  263. }
  264. // Iterate modules in plugin
  265. size_t moduleIndex;
  266. json_t* moduleSlugJ;
  267. json_array_foreach(modulesJ, moduleIndex, moduleSlugJ) {
  268. std::string moduleSlug = json_string_value(moduleSlugJ);
  269. // Insert module in whitelist
  270. pw.moduleSlugs.insert(moduleSlug);
  271. }
  272. }
  273. settings::moduleWhitelist = moduleWhitelist;
  274. }
  275. updateStatus = "";
  276. refreshRequested = true;
  277. }
  278. bool hasUpdates() {
  279. for (auto& pair : updateInfos) {
  280. if (!pair.second.downloaded)
  281. return true;
  282. }
  283. return false;
  284. }
  285. void syncUpdate(std::string slug) {
  286. if (!updateMutex.try_lock())
  287. return;
  288. DEFER({updateMutex.unlock();});
  289. if (settings::token.empty())
  290. return;
  291. isSyncing = true;
  292. DEFER({isSyncing = false;});
  293. // Get the UpdateInfo object
  294. auto it = updateInfos.find(slug);
  295. if (it == updateInfos.end())
  296. return;
  297. UpdateInfo update = it->second;
  298. // Don't update if not compatible with Rack version
  299. if (update.minRackVersion != "")
  300. return;
  301. updateSlug = slug;
  302. DEFER({updateSlug = "";});
  303. // Set progress to 0%
  304. updateProgress = 0.f;
  305. DEFER({updateProgress = 0.f;});
  306. INFO("Downloading plugin %s v%s for %s-%s", slug.c_str(), update.version.c_str(), APP_OS.c_str(), APP_CPU.c_str());
  307. // Get download URL
  308. std::string downloadUrl = API_URL + "/download";
  309. downloadUrl += "?slug=" + network::encodeUrl(slug);
  310. downloadUrl += "&version=" + network::encodeUrl(update.version);
  311. downloadUrl += "&arch=" + network::encodeUrl(APP_OS + "-" + APP_CPU);
  312. // Get file path
  313. std::string packageFilename = slug + "-" + update.version + "-" + APP_OS + "-" + APP_CPU + ".vcvplugin";
  314. std::string packagePath = system::join(plugin::pluginsPath, packageFilename);
  315. // Download plugin package
  316. if (!network::requestDownload(downloadUrl, packagePath, &updateProgress, getTokenCookies())) {
  317. WARN("Plugin %s download was unsuccessful", slug.c_str());
  318. return;
  319. }
  320. // updateInfos could possibly change in the checkUpdates() thread, so re-get the UpdateInfo to modify it.
  321. it = updateInfos.find(slug);
  322. if (it == updateInfos.end())
  323. return;
  324. it->second.downloaded = true;
  325. }
  326. void syncUpdates() {
  327. if (settings::token.empty())
  328. return;
  329. // 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.
  330. auto updateInfosClone = updateInfos;
  331. for (auto& pair : updateInfosClone) {
  332. syncUpdate(pair.first);
  333. }
  334. restartRequested = true;
  335. }
  336. std::string appVersion;
  337. std::string appDownloadUrl;
  338. std::string appChangelogUrl;
  339. std::string loginStatus;
  340. std::map<std::string, UpdateInfo> updateInfos;
  341. std::string updateStatus;
  342. std::string updateSlug;
  343. float updateProgress = 0.f;
  344. bool isSyncing = false;
  345. bool restartRequested = false;
  346. bool refreshRequested = false;
  347. } // namespace library
  348. } // namespace rack