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.

546 lines
12KB

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