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.

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