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.

595 lines
14KB

  1. #include <algorithm>
  2. #include <fstream>
  3. #include <osdialog.h>
  4. #include <patch.hpp>
  5. #include <asset.hpp>
  6. #include <system.hpp>
  7. #include <engine/Engine.hpp>
  8. #include <context.hpp>
  9. #include <app/common.hpp>
  10. #include <app/Scene.hpp>
  11. #include <app/RackWidget.hpp>
  12. #include <history.hpp>
  13. #include <settings.hpp>
  14. #include <plugin.hpp>
  15. namespace rack {
  16. namespace patch {
  17. static const char PATCH_FILTERS[] = "VCV Rack patch (.vcv):vcv";
  18. struct Manager::Internal {
  19. bool disableAutosaveOnClose = false;
  20. };
  21. Manager::Manager() {
  22. internal = new Internal;
  23. autosavePath = asset::user("autosave");
  24. // Use a different temporary autosave dir when safe mode is enabled, to avoid altering normal autosave.
  25. if (settings::safeMode) {
  26. autosavePath = asset::user("autosave-safe");
  27. clearAutosave();
  28. }
  29. templatePath = asset::user("template.vcv");
  30. factoryTemplatePath = asset::system("template.vcv");
  31. }
  32. Manager::~Manager() {
  33. // In safe mode, delete autosave dir.
  34. if (settings::safeMode) {
  35. clearAutosave();
  36. return;
  37. }
  38. if (!internal->disableAutosaveOnClose) {
  39. // Dispatch onSave to all Modules so they save their patch storage, etc.
  40. APP->engine->prepareSave();
  41. // Save autosave if not headless
  42. if (!settings::headless) {
  43. APP->patch->saveAutosave();
  44. }
  45. cleanAutosave();
  46. }
  47. delete internal;
  48. }
  49. void Manager::launch(std::string pathArg) {
  50. // Don't load any patches if safe mode is enabled
  51. if (settings::safeMode)
  52. return;
  53. // Load the argument if exists
  54. if (pathArg != "") {
  55. loadAction(pathArg);
  56. return;
  57. }
  58. // Try loading the autosave patch
  59. if (hasAutosave()) {
  60. try {
  61. loadAutosave();
  62. // Keep path and save state as it was stored in patch.json
  63. }
  64. catch (Exception& e) {
  65. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
  66. }
  67. return;
  68. }
  69. // Try loading the template patch
  70. loadTemplate();
  71. }
  72. void Manager::clear() {
  73. path = "";
  74. if (APP->scene) {
  75. APP->scene->rack->clear();
  76. APP->scene->rackScroll->reset();
  77. }
  78. if (APP->history) {
  79. APP->history->clear();
  80. }
  81. APP->engine->clear();
  82. }
  83. static bool promptClear(std::string text) {
  84. if (APP->history->isSaved())
  85. return true;
  86. if (APP->scene->rack->hasModules())
  87. return true;
  88. return osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, text.c_str());
  89. }
  90. void Manager::save(std::string path) {
  91. INFO("Saving patch %s", path.c_str());
  92. // Dispatch SaveEvent to modules
  93. APP->engine->prepareSave();
  94. // Save patch.json
  95. saveAutosave();
  96. // Clean up autosave directory (e.g. removed modules)
  97. cleanAutosave();
  98. // Take screenshot (disabled because there is currently no way to quickly view them on any OS or website.)
  99. // APP->window->screenshot(system::join(autosavePath, "screenshot.png"));
  100. double startTime = system::getTime();
  101. // Set compression level to 1 so that a 500MB/s SSD is almost bottlenecked
  102. system::archiveDirectory(path, autosavePath, 1);
  103. double endTime = system::getTime();
  104. INFO("Archived patch in %lf seconds", (endTime - startTime));
  105. }
  106. void Manager::saveDialog() {
  107. if (path == "") {
  108. saveAsDialog();
  109. return;
  110. }
  111. // Note: If save() fails below, this should probably be reset. But we need it so toJson() doesn't set the "unsaved" property.
  112. APP->history->setSaved();
  113. try {
  114. save(path);
  115. }
  116. catch (Exception& e) {
  117. std::string message = string::f("Could not save patch: %s", e.what());
  118. osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str());
  119. return;
  120. }
  121. }
  122. void Manager::saveAsDialog(bool setPath) {
  123. std::string dir;
  124. std::string filename;
  125. if (this->path == "") {
  126. dir = asset::user("patches");
  127. system::createDirectories(dir);
  128. filename = "Untitled.vcv";
  129. }
  130. else {
  131. dir = system::getDirectory(this->path);
  132. filename = system::getFilename(this->path);
  133. }
  134. osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS);
  135. DEFER({osdialog_filters_free(filters);});
  136. char* pathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters);
  137. if (!pathC) {
  138. // Cancel silently
  139. return;
  140. }
  141. DEFER({std::free(pathC);});
  142. // Automatically append .vcv extension
  143. std::string path = pathC;
  144. if (system::getExtension(path) != ".vcv") {
  145. path += ".vcv";
  146. }
  147. APP->history->setSaved();
  148. if (setPath) {
  149. this->path = path;
  150. }
  151. try {
  152. save(path);
  153. }
  154. catch (Exception& e) {
  155. std::string message = string::f("Could not save patch: %s", e.what());
  156. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  157. return;
  158. }
  159. pushRecentPath(path);
  160. }
  161. void Manager::saveTemplateDialog() {
  162. // Even if <user>/template.vcv doesn't exist, this message is still valid because it overrides the <system>/template.vcv patch.
  163. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?"))
  164. return;
  165. try {
  166. save(templatePath);
  167. }
  168. catch (Exception& e) {
  169. std::string message = string::f("Could not save template patch: %s", e.what());
  170. osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str());
  171. return;
  172. }
  173. }
  174. void Manager::saveAutosave() {
  175. std::string patchPath = system::join(autosavePath, "patch.json");
  176. INFO("Saving autosave %s", patchPath.c_str());
  177. json_t* rootJ = toJson();
  178. if (!rootJ)
  179. return;
  180. DEFER({json_decref(rootJ);});
  181. // Write to temporary path and then rename it to the correct path
  182. system::createDirectories(autosavePath);
  183. std::string tmpPath = patchPath + ".tmp";
  184. FILE* file = std::fopen(tmpPath.c_str(), "w");
  185. if (!file) {
  186. // Fail silently
  187. return;
  188. }
  189. json_dumpf(rootJ, file, JSON_INDENT(2));
  190. std::fclose(file);
  191. system::remove(patchPath);
  192. system::rename(tmpPath, patchPath);
  193. }
  194. void Manager::clearAutosave() {
  195. system::removeRecursively(autosavePath);
  196. }
  197. void Manager::cleanAutosave() {
  198. // Remove files and directories in the `autosave/modules` directory that doesn't match a module in the rack.
  199. std::string modulesDir = system::join(autosavePath, "modules");
  200. if (system::isDirectory(modulesDir)) {
  201. for (const std::string& entry : system::getEntries(modulesDir)) {
  202. try {
  203. int64_t moduleId = std::stoll(system::getFilename(entry));
  204. // Ignore modules that exist in the rack
  205. if (APP->engine->getModule(moduleId))
  206. continue;
  207. }
  208. catch (std::invalid_argument& e) {}
  209. catch (std::out_of_range& e) {}
  210. // Remove the entry.
  211. system::removeRecursively(entry);
  212. }
  213. }
  214. }
  215. static bool isPatchLegacyV1(std::string path) {
  216. FILE* f = std::fopen(path.c_str(), "rb");
  217. if (!f)
  218. return false;
  219. DEFER({std::fclose(f);});
  220. // All Zstandard frames start with this magic number.
  221. char zstdMagic[] = "\x28\xb5\x2f\xfd";
  222. char buf[4] = {};
  223. std::fread(buf, 1, sizeof(buf), f);
  224. // If the patch file doesn't begin with the magic number, it's a legacy patch.
  225. return std::memcmp(buf, zstdMagic, sizeof(buf)) != 0;
  226. }
  227. void Manager::load(std::string path) {
  228. INFO("Loading patch %s", path.c_str());
  229. clear();
  230. clearAutosave();
  231. system::createDirectories(autosavePath);
  232. if (isPatchLegacyV1(path)) {
  233. // Copy the .vcv file directly to "patch.json".
  234. system::copy(path, system::join(autosavePath, "patch.json"));
  235. }
  236. else {
  237. // Extract the .vcv file as a .tar.zst archive.
  238. double startTime = system::getTime();
  239. system::unarchiveToDirectory(path, autosavePath);
  240. double endTime = system::getTime();
  241. INFO("Unarchived patch in %lf seconds", (endTime - startTime));
  242. }
  243. loadAutosave();
  244. }
  245. void Manager::loadTemplate() {
  246. try {
  247. load(templatePath);
  248. }
  249. catch (Exception& e) {
  250. // Try loading the system template patch
  251. try {
  252. load(factoryTemplatePath);
  253. }
  254. catch (Exception& e) {
  255. std::string message = string::f("Could not load system template patch, clearing rack: %s", e.what());
  256. osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str());
  257. clear();
  258. clearAutosave();
  259. }
  260. }
  261. // load() sets the patch's original patch, but we don't want to use that.
  262. this->path = "";
  263. APP->history->setSaved();
  264. }
  265. void Manager::loadTemplateDialog() {
  266. if (!promptClear("The current patch is unsaved. Clear it and start a new patch?")) {
  267. return;
  268. }
  269. loadTemplate();
  270. }
  271. bool Manager::hasAutosave() {
  272. std::string patchPath = system::join(autosavePath, "patch.json");
  273. FILE* file = std::fopen(patchPath.c_str(), "r");
  274. if (!file)
  275. return false;
  276. std::fclose(file);
  277. return true;
  278. }
  279. void Manager::loadAutosave() {
  280. std::string patchPath = system::join(autosavePath, "patch.json");
  281. INFO("Loading autosave %s", patchPath.c_str());
  282. FILE* file = std::fopen(patchPath.c_str(), "r");
  283. if (!file)
  284. throw Exception("Could not open autosave patch %s", patchPath.c_str());
  285. DEFER({std::fclose(file);});
  286. json_error_t error;
  287. json_t* rootJ = json_loadf(file, 0, &error);
  288. if (!rootJ)
  289. throw Exception("Failed to load patch. JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  290. DEFER({json_decref(rootJ);});
  291. if (checkUnavailableModulesJson(rootJ))
  292. return;
  293. fromJson(rootJ);
  294. }
  295. void Manager::loadAction(std::string path) {
  296. try {
  297. load(path);
  298. }
  299. catch (Exception& e) {
  300. std::string message = string::f("Could not load patch: %s", e.what());
  301. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  302. return;
  303. }
  304. this->path = path;
  305. APP->history->setSaved();
  306. pushRecentPath(path);
  307. }
  308. void Manager::loadDialog() {
  309. if (!promptClear("The current patch is unsaved. Clear it and open a new patch?"))
  310. return;
  311. std::string dir;
  312. if (this->path == "") {
  313. dir = asset::user("patches");
  314. system::createDirectory(dir);
  315. }
  316. else {
  317. dir = system::getDirectory(this->path);
  318. }
  319. osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS);
  320. DEFER({osdialog_filters_free(filters);});
  321. char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
  322. if (!pathC) {
  323. // Fail silently
  324. return;
  325. }
  326. std::string path = pathC;
  327. std::free(pathC);
  328. loadAction(path);
  329. }
  330. void Manager::loadPathDialog(std::string path) {
  331. if (!promptClear("The current patch is unsaved. Clear it and open the new patch?"))
  332. return;
  333. loadAction(path);
  334. }
  335. void Manager::revertDialog() {
  336. if (path == "")
  337. return;
  338. if (!promptClear("Revert patch to the last saved state?"))
  339. return;
  340. loadAction(path);
  341. }
  342. void Manager::pushRecentPath(std::string path) {
  343. auto& recent = settings::recentPatchPaths;
  344. // Remove path from recent patches (if exists)
  345. recent.remove(path);
  346. // Add path to top of recent patches
  347. recent.push_front(path);
  348. // Limit recent patches size
  349. recent.resize(std::min((int) recent.size(), 10));
  350. }
  351. void Manager::disconnectDialog() {
  352. APP->scene->rack->clearCablesAction();
  353. }
  354. json_t* Manager::toJson() {
  355. // root
  356. json_t* rootJ = json_object();
  357. // version
  358. json_t* versionJ = json_string(APP_VERSION.c_str());
  359. json_object_set_new(rootJ, "version", versionJ);
  360. // path
  361. if (path != "") {
  362. json_t* pathJ = json_string(path.c_str());
  363. json_object_set_new(rootJ, "path", pathJ);
  364. }
  365. // unsaved
  366. if (!APP->history->isSaved())
  367. json_object_set_new(rootJ, "unsaved", json_boolean(true));
  368. if (APP->scene) {
  369. // zoom
  370. float zoom = APP->scene->rackScroll->getZoom();
  371. json_object_set_new(rootJ, "zoom", json_real(zoom));
  372. // gridOffset
  373. math::Vec gridOffset = APP->scene->rackScroll->getGridOffset();
  374. json_t* gridOffsetJ = json_pack("[f, f]", gridOffset.x, gridOffset.y);
  375. json_object_set_new(rootJ, "gridOffset", gridOffsetJ);
  376. }
  377. // Merge with Engine JSON
  378. json_t* engineJ = APP->engine->toJson();
  379. json_object_update(rootJ, engineJ);
  380. json_decref(engineJ);
  381. // Merge with RackWidget JSON
  382. if (APP->scene) {
  383. APP->scene->rack->mergeJson(rootJ);
  384. }
  385. return rootJ;
  386. }
  387. void Manager::fromJson(json_t* rootJ) {
  388. clear();
  389. // version
  390. std::string version;
  391. json_t* versionJ = json_object_get(rootJ, "version");
  392. if (versionJ)
  393. version = json_string_value(versionJ);
  394. if (version != APP_VERSION) {
  395. INFO("Patch was made with Rack %s, current Rack version is %s", version.c_str(), APP_VERSION.c_str());
  396. }
  397. // path
  398. json_t* pathJ = json_object_get(rootJ, "path");
  399. if (pathJ)
  400. path = json_string_value(pathJ);
  401. else
  402. path = "";
  403. // unsaved
  404. json_t* unsavedJ = json_object_get(rootJ, "unsaved");
  405. if (!unsavedJ)
  406. APP->history->setSaved();
  407. if (APP->scene) {
  408. // zoom
  409. json_t* zoomJ = json_object_get(rootJ, "zoom");
  410. if (zoomJ)
  411. APP->scene->rackScroll->setZoom(json_number_value(zoomJ));
  412. // gridOffset
  413. json_t* gridOffsetJ = json_object_get(rootJ, "gridOffset");
  414. if (gridOffsetJ) {
  415. double x, y;
  416. json_unpack(gridOffsetJ, "[F, F]", &x, &y);
  417. APP->scene->rackScroll->setGridOffset(math::Vec(x, y));
  418. }
  419. }
  420. // Pass JSON to Engine and RackWidget
  421. try {
  422. APP->engine->fromJson(rootJ);
  423. if (APP->scene) {
  424. APP->scene->rack->fromJson(rootJ);
  425. }
  426. }
  427. catch (Exception& e) {
  428. WARN("Cannot load patch: %s", e.what());
  429. }
  430. // At this point, ModuleWidgets and CableWidgets should own all Modules and Cables.
  431. // TODO Assert this
  432. }
  433. bool Manager::checkUnavailableModulesJson(json_t* rootJ) {
  434. std::set<std::string> pluginModuleSlugs;
  435. json_t* modulesJ = json_object_get(rootJ, "modules");
  436. if (!modulesJ)
  437. return false;
  438. size_t moduleIndex;
  439. json_t* moduleJ;
  440. json_array_foreach(modulesJ, moduleIndex, moduleJ) {
  441. // Get model
  442. try {
  443. plugin::modelFromJson(moduleJ);
  444. }
  445. catch (Exception& e) {
  446. // Get plugin and module slugs
  447. json_t* pluginSlugJ = json_object_get(moduleJ, "plugin");
  448. if (!pluginSlugJ)
  449. continue;
  450. std::string pluginSlug = json_string_value(pluginSlugJ);
  451. json_t* modelSlugJ = json_object_get(moduleJ, "model");
  452. if (!modelSlugJ)
  453. continue;
  454. std::string modelSlug = json_string_value(modelSlugJ);
  455. // Add to list
  456. pluginModuleSlugs.insert(pluginSlug + "/" + modelSlug);
  457. }
  458. }
  459. if (!pluginModuleSlugs.empty()) {
  460. std::string msg = "This patch includes modules that are not installed. Close Rack and show missing modules on the VCV Library?";
  461. if (osdialog_message(OSDIALOG_WARNING, OSDIALOG_YES_NO, msg.c_str())) {
  462. std::string url = "https://library.vcvrack.com/?modules=";
  463. url += string::join(pluginModuleSlugs, ",");
  464. system::openBrowser(url);
  465. // Close Rack
  466. APP->window->close();
  467. // Don't save autosave when closing
  468. // Possible bug: The autosave internal could still occur before the window closes
  469. internal->disableAutosaveOnClose = true;
  470. return true;
  471. }
  472. }
  473. return false;
  474. }
  475. } // namespace patch
  476. } // namespace rack