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.

488 lines
11KB

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