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.

451 lines
10.0KB

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