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.

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