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.

489 lines
11KB

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