Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago Move string::absolute, directory, filename, filenameBase, and filenameExtension to system::getAbsolute, getDirectory, getFilename, getStem, and getExtension. Reimplement most system:: functions using std::experimental::filesystem. Add system::doesExist, getFileSize, and getTempDir.
4 years ago |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- #include <algorithm>
-
- #include <osdialog.h>
-
- #include <patch.hpp>
- #include <asset.hpp>
- #include <system.hpp>
- #include <engine/Engine.hpp>
- #include <context.hpp>
- #include <app/common.hpp>
- #include <app/Scene.hpp>
- #include <app/RackWidget.hpp>
- #include <history.hpp>
- #include <settings.hpp>
-
- #include <fstream>
-
-
- namespace rack {
-
-
- static const char PATCH_FILTERS[] = "VCV Rack patch (.vcv):vcv";
-
-
- PatchManager::PatchManager() {
- if (settings::devMode) {
- autosavePath = asset::user("autosave");
- }
- else {
- autosavePath = asset::user("autosave-v" + ABI_VERSION);
- }
- }
-
-
- PatchManager::~PatchManager() {
- cleanAutosave();
- }
-
-
- void PatchManager::launch(std::string pathArg) {
- // Load the argument if exists
- if (pathArg != "") {
- loadAction(pathArg);
- return;
- }
-
- // Try loading the autosave patch
- if (hasAutosave()) {
- try {
- loadAutosave();
- // Keep path and save state as it was stored in patch.json
- }
- catch (Exception& e) {
- osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
- }
- return;
- }
-
- // Try loading the template patch
- loadTemplate();
- }
-
-
- void PatchManager::clear() {
- path = "";
- if (APP->scene) {
- APP->scene->rack->clear();
- APP->scene->rackScroll->reset();
- }
- if (APP->history) {
- APP->history->clear();
- }
- APP->engine->clear();
- }
-
-
- static bool promptClear(std::string text) {
- if (APP->history->isSaved())
- return true;
- if (APP->scene->rack->isEmpty())
- return true;
- return osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, text.c_str());
- }
-
-
- void PatchManager::save(std::string path) {
- INFO("Saving patch %s", path.c_str());
- // Save patch.json
- saveAutosave();
- // Clean up autosave directory (e.g. removed modules)
- cleanAutosave();
-
- // Take screenshot (disabled because there is currently no way to quickly view them on any OS or website.)
- // APP->window->screenshot(system::join(autosavePath, "screenshot.png"));
-
- double startTime = system::getTime();
- // Set compression level to 1 so that a 500MB/s SSD is almost bottlenecked
- system::archiveFolder(path, autosavePath, 1);
- double endTime = system::getTime();
- INFO("Archived patch in %lf seconds", (endTime - startTime));
- }
-
-
- void PatchManager::saveDialog() {
- if (path == "") {
- saveAsDialog();
- return;
- }
-
- // Note: If save() fails below, this should probably be reset. But we need it so toJson() doesn't set the "unsaved" property.
- APP->history->setSaved();
-
- try {
- save(path);
- }
- catch (Exception& e) {
- std::string message = string::f("Could not save patch: %s", e.what());
- osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str());
- return;
- }
- }
-
-
- void PatchManager::saveAsDialog() {
- std::string dir;
- std::string filename;
- if (this->path == "") {
- dir = asset::user("patches");
- system::createDirectories(dir);
- filename = "Untitled.vcv";
- }
- else {
- dir = system::getDirectory(this->path);
- filename = system::getFilename(this->path);
- }
-
- osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS);
- DEFER({osdialog_filters_free(filters);});
-
- char* pathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters);
- if (!pathC) {
- // Cancel silently
- return;
- }
- DEFER({std::free(pathC);});
-
- // Append .vcv extension if no extension was given.
- std::string path = pathC;
- if (system::getExtension(path) == "") {
- path += ".vcv";
- }
-
- APP->history->setSaved();
- this->path = path;
-
- try {
- save(path);
- }
- catch (Exception& e) {
- std::string message = string::f("Could not save patch: %s", e.what());
- osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
- return;
- }
-
- pushRecentPath(path);
- }
-
-
- void PatchManager::saveTemplateDialog() {
- // Even if <user>/template.vcv doesn't exist, this message is still valid because it overrides the <system>/template.vcv patch.
- if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?"))
- return;
-
- try {
- save(asset::templatePath);
- }
- catch (Exception& e) {
- std::string message = string::f("Could not save template patch: %s", e.what());
- osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str());
- return;
- }
- }
-
-
- void PatchManager::saveAutosave() {
- std::string patchPath = system::join(autosavePath, "patch.json");
- INFO("Saving autosave %s", patchPath.c_str());
- json_t* rootJ = toJson();
- if (!rootJ)
- return;
- DEFER({json_decref(rootJ);});
-
- // Write to temporary path and then rename it to the correct path
- system::createDirectories(autosavePath);
- std::string tmpPath = patchPath + ".tmp";
- FILE* file = std::fopen(tmpPath.c_str(), "w");
- if (!file) {
- // Fail silently
- return;
- }
-
- json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
- std::fclose(file);
- system::remove(patchPath);
- system::rename(tmpPath, patchPath);
- }
-
-
- void PatchManager::cleanAutosave() {
- // Remove files and folders in the `autosave/modules` folder that doesn't match a module in the rack.
- std::string modulesDir = system::join(autosavePath, "modules");
- if (system::isDirectory(modulesDir)) {
- for (const std::string& entry : system::getEntries(modulesDir)) {
- try {
- int64_t moduleId = std::stol(system::getFilename(entry));
- // Ignore modules that exist in the rack
- if (APP->engine->getModule(moduleId))
- continue;
- }
- catch (std::invalid_argument& e) {
- }
- // Remove the entry.
- system::removeRecursively(entry);
- }
- }
- }
-
-
- static bool isPatchLegacyV1(std::string path) {
- FILE* f = std::fopen(path.c_str(), "rb");
- if (!f)
- return false;
- DEFER({std::fclose(f);});
- // All Zstandard frames start with this magic number.
- char zstdMagic[] = "\x28\xb5\x2f\xfd";
- char buf[4] = {};
- std::fread(buf, 1, sizeof(buf), f);
- // If the patch file doesn't begin with the magic number, it's a legacy patch.
- return std::memcmp(buf, zstdMagic, sizeof(buf)) != 0;
- }
-
-
- void PatchManager::load(std::string path) {
- INFO("Loading patch %s", path.c_str());
-
- system::removeRecursively(autosavePath);
- system::createDirectories(autosavePath);
-
- if (isPatchLegacyV1(path)) {
- // Copy the .vcv file directly to "patch.json".
- system::copy(path, system::join(autosavePath, "patch.json"));
- }
- else {
- // Extract the .vcv file as a .tar.zst archive.
- double startTime = system::getTime();
- system::unarchiveToFolder(path, autosavePath);
- double endTime = system::getTime();
- INFO("Unarchived patch in %lf seconds", (endTime - startTime));
- }
-
- loadAutosave();
- }
-
-
- void PatchManager::loadTemplate() {
- try {
- load(asset::templatePath);
- }
- catch (Exception& e) {
- // Do nothing because it's okay for the user template to not exist.
- try {
- load(asset::system("template.vcv"));
- }
- catch (Exception& e) {
- std::string message = string::f("Could not load system template patch, clearing rack: %s", e.what());
- osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str());
- clear();
- }
- }
-
- // load() sets the patch's original patch, but we don't want to use that.
- this->path = "";
- APP->history->setSaved();
- }
-
-
- void PatchManager::loadTemplateDialog() {
- if (!promptClear("The current patch is unsaved. Clear it and start a new patch?")) {
- return;
- }
- loadTemplate();
- }
-
-
- bool PatchManager::hasAutosave() {
- std::string patchPath = system::join(autosavePath, "patch.json");
- INFO("Loading autosave %s", patchPath.c_str());
- FILE* file = std::fopen(patchPath.c_str(), "r");
- if (!file)
- return false;
- std::fclose(file);
- return true;
- }
-
-
- void PatchManager::loadAutosave() {
- std::string patchPath = system::join(autosavePath, "patch.json");
- INFO("Loading autosave %s", patchPath.c_str());
- FILE* file = std::fopen(patchPath.c_str(), "r");
- if (!file)
- throw Exception("Could not open autosave patch %s", patchPath.c_str());
- DEFER({std::fclose(file);});
-
- json_error_t error;
- json_t* rootJ = json_loadf(file, 0, &error);
- if (!rootJ)
- throw Exception("Failed to load patch. JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
- DEFER({json_decref(rootJ);});
-
- fromJson(rootJ);
- }
-
-
- void PatchManager::loadAction(std::string path) {
- try {
- load(path);
- }
- catch (Exception& e) {
- std::string message = string::f("Could not load patch: %s", e.what());
- osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
- return;
- }
-
- this->path = path;
- APP->history->setSaved();
- pushRecentPath(path);
- }
-
-
- void PatchManager::loadDialog() {
- if (!promptClear("The current patch is unsaved. Clear it and open a new patch?"))
- return;
-
- std::string dir;
- if (this->path == "") {
- dir = asset::user("patches");
- system::createDirectory(dir);
- }
- else {
- dir = system::getDirectory(this->path);
- }
-
- osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS);
- DEFER({osdialog_filters_free(filters);});
-
- char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
- if (!pathC) {
- // Fail silently
- return;
- }
- std::string path = pathC;
- std::free(pathC);
-
- loadAction(path);
- }
-
-
- void PatchManager::loadPathDialog(std::string path) {
- if (!promptClear("The current patch is unsaved. Clear it and open the new patch?"))
- return;
-
- loadAction(path);
- }
-
-
- void PatchManager::revertDialog() {
- if (path == "")
- return;
- if (!promptClear("Revert patch to the last saved state?"))
- return;
-
- loadAction(path);
- }
-
-
- void PatchManager::pushRecentPath(std::string path) {
- auto& recent = settings::recentPatchPaths;
- // Remove path from recent patches (if exists)
- recent.remove(path);
- // Add path to top of recent patches
- recent.push_front(path);
- // Limit recent patches size
- recent.resize(std::min((int) recent.size(), 10));
- }
-
-
- void PatchManager::disconnectDialog() {
- APP->scene->rack->clearCablesAction();
- }
-
-
- json_t* PatchManager::toJson() {
- // root
- json_t* rootJ = json_object();
-
- // version
- json_t* versionJ = json_string(APP_VERSION.c_str());
- json_object_set_new(rootJ, "version", versionJ);
-
- // path
- if (path != "") {
- json_t* pathJ = json_string(path.c_str());
- json_object_set_new(rootJ, "path", pathJ);
- }
-
- // unsaved
- if (!APP->history->isSaved())
- json_object_set_new(rootJ, "unsaved", json_boolean(true));
-
- // Merge with rootJ
- json_t* engineJ = APP->engine->toJson();
- json_object_update(rootJ, engineJ);
- json_decref(engineJ);
-
- if (APP->scene) {
- APP->scene->rack->mergeJson(rootJ);
- }
-
- return rootJ;
- }
-
-
- void PatchManager::fromJson(json_t* rootJ) {
- clear();
-
- // version
- std::string version;
- json_t* versionJ = json_object_get(rootJ, "version");
- if (versionJ)
- version = json_string_value(versionJ);
- if (version != APP_VERSION) {
- INFO("Patch was made with Rack v%s, current Rack version is v%s", version.c_str(), APP_VERSION.c_str());
- }
-
- // path
- json_t* pathJ = json_object_get(rootJ, "path");
- if (pathJ)
- path = json_string_value(pathJ);
- else
- path = "";
-
- // unsaved
- json_t* unsavedJ = json_object_get(rootJ, "unsaved");
- if (!unsavedJ)
- APP->history->setSaved();
-
- try {
- APP->engine->fromJson(rootJ);
- if (APP->scene) {
- APP->scene->rack->fromJson(rootJ);
- }
- }
- catch (Exception& e) {
- warningLog += "\n";
- warningLog += e.what();
- }
- // At this point, ModuleWidgets and CableWidgets should own all Modules and Cables.
- // TODO Assert this
-
- // Display a message if we have something to say.
- if (warningLog != "") {
- osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, warningLog.c_str());
- }
- warningLog = "";
- }
-
-
- void PatchManager::log(std::string msg) {
- warningLog += msg;
- warningLog += "\n";
- }
-
-
- } // namespace rack
|