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.

patch.cpp 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. #include <patch.hpp>
  2. #include <asset.hpp>
  3. #include <system.hpp>
  4. #include <app.hpp>
  5. #include <app/common.hpp>
  6. #include <app/Scene.hpp>
  7. #include <app/RackWidget.hpp>
  8. #include <history.hpp>
  9. #include <settings.hpp>
  10. #include <osdialog.h>
  11. namespace rack {
  12. static const char PATCH_FILTERS[] = "VCV Rack patch (.vcv):vcv";
  13. PatchManager::PatchManager() {
  14. path = settings::patchPath;
  15. }
  16. PatchManager::~PatchManager() {
  17. settings::patchPath = path;
  18. }
  19. void PatchManager::init(std::string path) {
  20. if (!path.empty()) {
  21. // Load patch
  22. load(path);
  23. this->path = path;
  24. return;
  25. }
  26. if (!settings::devMode) {
  27. // To prevent launch crashes, if Rack crashes between now and 15 seconds from now, the "skipAutosaveOnLaunch" property will remain in settings.json, so that in the next launch, the broken autosave will not be loaded.
  28. bool oldSkipLoadOnLaunch = settings::skipLoadOnLaunch;
  29. settings::skipLoadOnLaunch = true;
  30. settings::save(asset::settingsPath);
  31. settings::skipLoadOnLaunch = false;
  32. if (oldSkipLoadOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, possibly caused by a faulty module in your patch. Clear your patch and start over?")) {
  33. this->path = "";
  34. return;
  35. }
  36. }
  37. // Load autosave
  38. if (load(asset::autosavePath)) {
  39. return;
  40. }
  41. reset();
  42. }
  43. void PatchManager::reset() {
  44. APP->history->clear();
  45. APP->scene->rack->clear();
  46. APP->scene->rackScroll->reset();
  47. path = "";
  48. if (load(asset::templatePath)) {
  49. return;
  50. }
  51. if (load(asset::system("template.vcv"))) {
  52. return;
  53. }
  54. }
  55. static bool promptClear(std::string text) {
  56. if (APP->history->isSaved())
  57. return true;
  58. if (APP->scene->rack->isEmpty())
  59. return true;
  60. return osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, text.c_str());
  61. }
  62. void PatchManager::resetDialog() {
  63. if (!promptClear("The current patch is unsaved. Clear it and start a new patch?"))
  64. return;
  65. reset();
  66. }
  67. void PatchManager::save(std::string path) {
  68. INFO("Saving patch %s", path.c_str());
  69. json_t* rootJ = toJson();
  70. if (!rootJ)
  71. return;
  72. DEFER({
  73. json_decref(rootJ);
  74. });
  75. // Write to temporary path and then rename it to the correct path
  76. std::string tmpPath = path + ".tmp";
  77. FILE* file = std::fopen(tmpPath.c_str(), "w");
  78. if (!file) {
  79. // Fail silently
  80. return;
  81. }
  82. json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  83. std::fclose(file);
  84. system::moveFile(tmpPath, path);
  85. }
  86. void PatchManager::saveDialog() {
  87. if (!path.empty()) {
  88. save(path);
  89. APP->history->setSaved();
  90. }
  91. else {
  92. saveAsDialog();
  93. }
  94. }
  95. void PatchManager::saveAsDialog() {
  96. std::string dir;
  97. std::string filename;
  98. if (path.empty()) {
  99. dir = asset::user("patches");
  100. system::createDirectory(dir);
  101. }
  102. else {
  103. dir = string::directory(path);
  104. filename = string::filename(path);
  105. }
  106. osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS);
  107. DEFER({
  108. osdialog_filters_free(filters);
  109. });
  110. char* pathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters);
  111. if (!pathC) {
  112. // Fail silently
  113. return;
  114. }
  115. DEFER({
  116. std::free(pathC);
  117. });
  118. // Append .vcv extension if no extension was given.
  119. std::string pathStr = pathC;
  120. if (string::filenameExtension(string::filename(pathStr)) == "") {
  121. pathStr += ".vcv";
  122. }
  123. save(pathStr);
  124. path = pathStr;
  125. APP->history->setSaved();
  126. }
  127. void PatchManager::saveTemplateDialog() {
  128. // Even if <user>/template.vcv doesn't exist, this message is still valid because it overrides the <system>/template.vcv patch.
  129. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?"))
  130. return;
  131. save(asset::templatePath);
  132. }
  133. bool PatchManager::load(std::string path) {
  134. INFO("Loading patch %s", path.c_str());
  135. FILE* file = std::fopen(path.c_str(), "r");
  136. if (!file) {
  137. // Exit silently
  138. return false;
  139. }
  140. DEFER({
  141. std::fclose(file);
  142. });
  143. json_error_t error;
  144. json_t* rootJ = json_loadf(file, 0, &error);
  145. if (!rootJ) {
  146. std::string message = string::f("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  147. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  148. return false;
  149. }
  150. DEFER({
  151. json_decref(rootJ);
  152. });
  153. APP->history->clear();
  154. APP->scene->rack->clear();
  155. APP->scene->rackScroll->reset();
  156. legacy = 0;
  157. fromJson(rootJ);
  158. return true;
  159. }
  160. void PatchManager::loadDialog() {
  161. if (!promptClear("The current patch is unsaved. Clear it and open a new patch?"))
  162. return;
  163. std::string dir;
  164. if (path.empty()) {
  165. dir = asset::user("patches");
  166. system::createDirectory(dir);
  167. }
  168. else {
  169. dir = string::directory(path);
  170. }
  171. osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS);
  172. DEFER({
  173. osdialog_filters_free(filters);
  174. });
  175. char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
  176. if (!pathC) {
  177. // Fail silently
  178. return;
  179. }
  180. DEFER({
  181. std::free(pathC);
  182. });
  183. load(pathC);
  184. path = pathC;
  185. APP->history->setSaved();
  186. }
  187. void PatchManager::loadPathDialog(std::string path) {
  188. if (!promptClear("The current patch is unsaved. Clear it and open the new patch?"))
  189. return;
  190. load(path);
  191. this->path = path;
  192. APP->history->setSaved();
  193. }
  194. void PatchManager::revertDialog() {
  195. if (path.empty())
  196. return;
  197. if (!promptClear("Revert patch to the last saved state?"))
  198. return;
  199. load(path);
  200. APP->history->setSaved();
  201. }
  202. void PatchManager::disconnectDialog() {
  203. APP->scene->rack->clearCablesAction();
  204. }
  205. json_t* PatchManager::toJson() {
  206. // root
  207. json_t* rootJ = json_object();
  208. // version
  209. json_t* versionJ = json_string(app::APP_VERSION.c_str());
  210. json_object_set_new(rootJ, "version", versionJ);
  211. // Merge with RackWidget JSON
  212. json_t* rackJ = APP->scene->rack->toJson();
  213. // Merge with rootJ
  214. json_object_update(rootJ, rackJ);
  215. json_decref(rackJ);
  216. return rootJ;
  217. }
  218. void PatchManager::fromJson(json_t* rootJ) {
  219. legacy = 0;
  220. // version
  221. std::string version;
  222. json_t* versionJ = json_object_get(rootJ, "version");
  223. if (versionJ)
  224. version = json_string_value(versionJ);
  225. if (version != app::APP_VERSION) {
  226. INFO("Patch was made with Rack v%s, current Rack version is v%s", version.c_str(), app::APP_VERSION.c_str());
  227. }
  228. // Detect old patches with ModuleWidget::params/inputs/outputs indices.
  229. // (We now use Module::params/inputs/outputs indices.)
  230. if (string::startsWith(version, "0.3.") || string::startsWith(version, "0.4.") || string::startsWith(version, "0.5.") || version == "" || version == "dev") {
  231. legacy = 1;
  232. }
  233. else if (string::startsWith(version, "0.6.")) {
  234. legacy = 2;
  235. }
  236. if (legacy) {
  237. INFO("Loading patch using legacy mode %d", legacy);
  238. }
  239. APP->scene->rack->fromJson(rootJ);
  240. // Display a message if we have something to say
  241. if (!warningLog.empty()) {
  242. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, warningLog.c_str());
  243. }
  244. warningLog = "";
  245. }
  246. bool PatchManager::isLegacy(int level) {
  247. return legacy && legacy <= level;
  248. }
  249. } // namespace rack