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.

522 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. system::removeRecursively(autosavePath);
  204. system::createDirectories(autosavePath);
  205. if (isPatchLegacyV1(path)) {
  206. // Copy the .vcv file directly to "patch.json".
  207. system::copy(path, system::join(autosavePath, "patch.json"));
  208. }
  209. else {
  210. // Extract the .vcv file as a .tar.zst archive.
  211. double startTime = system::getTime();
  212. system::unarchiveToDirectory(path, autosavePath);
  213. double endTime = system::getTime();
  214. INFO("Unarchived patch in %lf seconds", (endTime - startTime));
  215. }
  216. loadAutosave();
  217. }
  218. void Manager::loadTemplate() {
  219. try {
  220. load(templatePath);
  221. }
  222. catch (Exception& e) {
  223. // Try loading the system template patch
  224. try {
  225. load(factoryTemplatePath);
  226. }
  227. catch (Exception& e) {
  228. std::string message = string::f("Could not load system template patch, clearing rack: %s", e.what());
  229. osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str());
  230. system::removeRecursively(autosavePath);
  231. system::createDirectories(autosavePath);
  232. clear();
  233. }
  234. }
  235. // load() sets the patch's original patch, but we don't want to use that.
  236. this->path = "";
  237. APP->history->setSaved();
  238. }
  239. void Manager::loadTemplateDialog() {
  240. if (!promptClear("The current patch is unsaved. Clear it and start a new patch?")) {
  241. return;
  242. }
  243. loadTemplate();
  244. }
  245. bool Manager::hasAutosave() {
  246. std::string patchPath = system::join(autosavePath, "patch.json");
  247. INFO("Loading autosave %s", patchPath.c_str());
  248. FILE* file = std::fopen(patchPath.c_str(), "r");
  249. if (!file)
  250. return false;
  251. std::fclose(file);
  252. return true;
  253. }
  254. void Manager::loadAutosave() {
  255. std::string patchPath = system::join(autosavePath, "patch.json");
  256. INFO("Loading autosave %s", patchPath.c_str());
  257. FILE* file = std::fopen(patchPath.c_str(), "r");
  258. if (!file)
  259. throw Exception("Could not open autosave patch %s", patchPath.c_str());
  260. DEFER({std::fclose(file);});
  261. json_error_t error;
  262. json_t* rootJ = json_loadf(file, 0, &error);
  263. if (!rootJ)
  264. throw Exception("Failed to load patch. JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  265. DEFER({json_decref(rootJ);});
  266. fromJson(rootJ);
  267. }
  268. void Manager::loadAction(std::string path) {
  269. try {
  270. load(path);
  271. }
  272. catch (Exception& e) {
  273. std::string message = string::f("Could not load patch: %s", e.what());
  274. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  275. return;
  276. }
  277. this->path = path;
  278. APP->history->setSaved();
  279. pushRecentPath(path);
  280. }
  281. void Manager::loadDialog() {
  282. if (!promptClear("The current patch is unsaved. Clear it and open a new patch?"))
  283. return;
  284. std::string dir;
  285. if (this->path == "") {
  286. dir = asset::user("patches");
  287. system::createDirectory(dir);
  288. }
  289. else {
  290. dir = system::getDirectory(this->path);
  291. }
  292. osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS);
  293. DEFER({osdialog_filters_free(filters);});
  294. char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
  295. if (!pathC) {
  296. // Fail silently
  297. return;
  298. }
  299. std::string path = pathC;
  300. std::free(pathC);
  301. loadAction(path);
  302. }
  303. void Manager::loadPathDialog(std::string path) {
  304. if (!promptClear("The current patch is unsaved. Clear it and open the new patch?"))
  305. return;
  306. loadAction(path);
  307. }
  308. void Manager::revertDialog() {
  309. if (path == "")
  310. return;
  311. if (!promptClear("Revert patch to the last saved state?"))
  312. return;
  313. loadAction(path);
  314. }
  315. void Manager::pushRecentPath(std::string path) {
  316. auto& recent = settings::recentPatchPaths;
  317. // Remove path from recent patches (if exists)
  318. recent.remove(path);
  319. // Add path to top of recent patches
  320. recent.push_front(path);
  321. // Limit recent patches size
  322. recent.resize(std::min((int) recent.size(), 10));
  323. }
  324. void Manager::disconnectDialog() {
  325. APP->scene->rack->clearCablesAction();
  326. }
  327. json_t* Manager::toJson() {
  328. // root
  329. json_t* rootJ = json_object();
  330. // version
  331. json_t* versionJ = json_string(APP_VERSION.c_str());
  332. json_object_set_new(rootJ, "version", versionJ);
  333. // path
  334. if (path != "") {
  335. json_t* pathJ = json_string(path.c_str());
  336. json_object_set_new(rootJ, "path", pathJ);
  337. }
  338. // unsaved
  339. if (!APP->history->isSaved())
  340. json_object_set_new(rootJ, "unsaved", json_boolean(true));
  341. if (APP->scene) {
  342. // zoom
  343. float zoom = APP->scene->rackScroll->getZoom();
  344. json_object_set_new(rootJ, "zoom", json_real(zoom));
  345. // gridOffset
  346. math::Vec gridOffset = APP->scene->rackScroll->getGridOffset();
  347. json_t* gridOffsetJ = json_pack("[f, f]", gridOffset.x, gridOffset.y);
  348. json_object_set_new(rootJ, "gridOffset", gridOffsetJ);
  349. }
  350. // Merge with Engine JSON
  351. json_t* engineJ = APP->engine->toJson();
  352. json_object_update(rootJ, engineJ);
  353. json_decref(engineJ);
  354. // Merge with RackWidget JSON
  355. if (APP->scene) {
  356. APP->scene->rack->mergeJson(rootJ);
  357. }
  358. return rootJ;
  359. }
  360. void Manager::fromJson(json_t* rootJ) {
  361. clear();
  362. // version
  363. std::string version;
  364. json_t* versionJ = json_object_get(rootJ, "version");
  365. if (versionJ)
  366. version = json_string_value(versionJ);
  367. if (version != APP_VERSION) {
  368. INFO("Patch was made with Rack v%s, current Rack version is v%s", version.c_str(), APP_VERSION.c_str());
  369. }
  370. // path
  371. json_t* pathJ = json_object_get(rootJ, "path");
  372. if (pathJ)
  373. path = json_string_value(pathJ);
  374. else
  375. path = "";
  376. // unsaved
  377. json_t* unsavedJ = json_object_get(rootJ, "unsaved");
  378. if (!unsavedJ)
  379. APP->history->setSaved();
  380. if (APP->scene) {
  381. // zoom
  382. json_t* zoomJ = json_object_get(rootJ, "zoom");
  383. if (zoomJ)
  384. APP->scene->rackScroll->setZoom(json_number_value(zoomJ));
  385. // gridOffset
  386. json_t* gridOffsetJ = json_object_get(rootJ, "gridOffset");
  387. if (gridOffsetJ) {
  388. double x, y;
  389. json_unpack(gridOffsetJ, "[F, F]", &x, &y);
  390. APP->scene->rackScroll->setGridOffset(math::Vec(x, y));
  391. }
  392. }
  393. // Pass JSON to Engine and RackWidget
  394. try {
  395. APP->engine->fromJson(rootJ);
  396. if (APP->scene) {
  397. APP->scene->rack->fromJson(rootJ);
  398. }
  399. }
  400. catch (Exception& e) {
  401. warningLog += "\n";
  402. warningLog += e.what();
  403. }
  404. // At this point, ModuleWidgets and CableWidgets should own all Modules and Cables.
  405. // TODO Assert this
  406. // Display a message if we have something to say.
  407. if (warningLog != "") {
  408. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, warningLog.c_str());
  409. }
  410. warningLog = "";
  411. }
  412. void Manager::log(std::string msg) {
  413. warningLog += msg;
  414. warningLog += "\n";
  415. }
  416. } // namespace patch
  417. } // namespace rack