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.

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