diff --git a/.gitignore b/.gitignore index e1b2a5f5..db399cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ /licenses .DS_Store /autosave.vcv +/autosave /settings.json /screenshots /Fundamental.zip \ No newline at end of file diff --git a/Makefile b/Makefile index fb99f882..13b1691b 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ build/dep/osdialog/osdialog_gtk3.c.o: FLAGS += $(shell pkg-config --cflags gtk+- FLAGS += -fno-gnu-unique LDFLAGS += -Wl,--whole-archive - LDFLAGS += dep/lib/libGLEW.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/libarchive.a dep/lib/libzstd.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/librtmidi.a dep/lib/librtaudio.a -lstdc++fs + LDFLAGS += dep/lib/libGLEW.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/libarchive.a dep/lib/libzstd.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/librtmidi.a dep/lib/librtaudio.a -lstdc++fs LDFLAGS += -Wl,--no-whole-archive LDFLAGS += -lpthread -lGL -ldl -lX11 -lasound -ljack LDFLAGS += $(shell pkg-config --libs gtk+-3.0) @@ -49,7 +49,7 @@ ifdef ARCH_MAC LDFLAGS += -lpthread -ldl LDFLAGS += -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -framework CoreAudio -framework CoreMIDI LDFLAGS += -Wl,-all_load - LDFLAGS += dep/lib/libGLEW.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/libarchive.a dep/lib/libzstd.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/librtmidi.a dep/lib/librtaudio.a + LDFLAGS += dep/lib/libGLEW.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/libarchive.a dep/lib/libzstd.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/librtmidi.a dep/lib/librtaudio.a STANDALONE_TARGET := Rack STANDALONE_LDFLAGS += -stdlib=libc++ @@ -65,7 +65,7 @@ ifdef ARCH_WIN LDFLAGS += -Wl,--export-all-symbols LDFLAGS += -Wl,--out-implib,$(TARGET).a LDFLAGS += -Wl,-Bstatic -Wl,--whole-archive - LDFLAGS += dep/lib/libglew32.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/libarchive.a dep/lib/libzstd.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/librtaudio.a dep/lib/librtmidi.a -lstdc++fs + LDFLAGS += dep/lib/libglew32.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libsamplerate.a dep/lib/libarchive.a dep/lib/libzstd.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/librtaudio.a dep/lib/librtmidi.a -lstdc++fs LDFLAGS += -Wl,-Bdynamic -Wl,--no-whole-archive LDFLAGS += -lpthread -lopengl32 -lgdi32 -lws2_32 -lcomdlg32 -lole32 -ldsound -lwinmm -lksuser -lshlwapi -lmfplat -lmfuuid -lwmcodecdspuuid -ldbghelp diff --git a/dep/Makefile b/dep/Makefile index badc3a66..126c8cb0 100755 --- a/dep/Makefile +++ b/dep/Makefile @@ -14,8 +14,6 @@ ifdef ARCH_LIN libcurl = lib/libcurl.a zstd = lib/libzstd.a libarchive = lib/libarchive.a - libzip = lib/libzip.a - zlib = lib/libz.a libspeexdsp = lib/libspeexdsp.a libsamplerate = lib/libsamplerate.a rtmidi = lib/librtmidi.a @@ -30,8 +28,6 @@ ifdef ARCH_MAC libcurl = lib/libcurl.a zstd = lib/libzstd.a libarchive = lib/libarchive.a - libzip = lib/libzip.a - zlib = lib/libz.a libspeexdsp = lib/libspeexdsp.a libsamplerate = lib/libsamplerate.a rtmidi = lib/librtmidi.a @@ -46,8 +42,6 @@ ifdef ARCH_WIN libcurl = lib/libcurl.a zstd = lib/libzstd.a libarchive = lib/libarchive.a - libzip = lib/libzip.a - zlib = lib/libz.a libspeexdsp = lib/libspeexdsp.a libsamplerate = lib/libsamplerate.a rtmidi = lib/librtmidi.a @@ -64,9 +58,7 @@ DEPS += $(glew) DEPS += $(glfw) DEPS += $(jansson) DEPS += $(libcurl) -DEPS += $(zstd) DEPS += $(libarchive) -DEPS += $(libzip) DEPS += $(libspeexdsp) DEPS += $(libsamplerate) DEPS += $(rtmidi) @@ -165,35 +157,6 @@ $(libarchive): | $(zstd) libarchive-3.4.3 $(MAKE) -C libarchive-3.4.3 $(MAKE) -C libarchive-3.4.3 install -libzip-1.5.2: - $(WGET) "https://libzip.org/download/libzip-1.5.2.tar.gz" - $(SHA256) libzip-1.5.2.tar.gz be694a4abb2ffe5ec02074146757c8b56084dbcebf329123c84b205417435e15 - $(UNTAR) libzip-1.5.2.tar.gz - rm libzip-1.5.2.tar.gz - -$(libzip): | $(zlib) libzip-1.5.2 - cd libzip-1.5.2 && mkdir -p build - cd libzip-1.5.2/build && $(CMAKE) .. -DCMAKE_FIND_ROOT_PATH="$(DEP_PATH)" -DENABLE_COMMONCRYPTO=OFF -DENABLE_GNUTLS=OFF -DENABLE_MBEDTLS=OFF -DENABLE_OPENSSL=OFF -DENABLE_WINDOWS_CRYPTO=OFF -DENABLE_BZIP2=OFF -DBUILD_TOOLS=OFF -DBUILD_REGRESS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_DOC=OFF -DBUILD_SHARED_LIBS=OFF - $(MAKE) -C libzip-1.5.2/build - $(MAKE) -C libzip-1.5.2/build install - -zlib-1.2.11: - $(WGET) "https://www.zlib.net/zlib-1.2.11.tar.gz" - $(SHA256) zlib-1.2.11.tar.gz c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 - $(UNTAR) zlib-1.2.11.tar.gz - rm zlib-1.2.11.tar.gz - -$(zlib): | zlib-1.2.11 -ifdef ARCH_WIN - $(MAKE) -C zlib-1.2.11 -f win32/Makefile.gcc - $(MAKE) -C zlib-1.2.11 -f win32/Makefile.gcc BINARY_PATH="$(DEP_PATH)/bin" INCLUDE_PATH="$(DEP_PATH)/include" LIBRARY_PATH="$(DEP_PATH)/lib" install -else - # Don't use $(CONFIGURE) because this is a handwritten configure script - cd zlib-1.2.11 && ./configure --prefix="$(DEP_PATH)" - $(MAKE) -C zlib-1.2.11 - $(MAKE) -C zlib-1.2.11 install -endif - speexdsp-SpeexDSP-1.2rc3: $(WGET) "https://vcvrack.com/downloads/dep/speexdsp-SpeexDSP-1.2rc3.tgz" $(SHA256) speexdsp-SpeexDSP-1.2rc3.tgz c8dded1454747f65956f981c95e7f89a06abdaa2a53e8aeaa66bab2a3d59cebd @@ -283,7 +246,7 @@ $(pffft): | pffft # Helpers -src: glew-2.1.0 glfw jansson-2.12 speexdsp-SpeexDSP-1.2rc3 openssl-1.1.1d curl-7.66.0 zstd-1.4.5 libarchive-3.4.3 libzip-1.5.2 zlib-1.2.11 rtmidi-4.0.0 rtaudio nanovg nanosvg oui-blendish osdialog pffft +src: glew-2.1.0 glfw jansson-2.12 speexdsp-SpeexDSP-1.2rc3 openssl-1.1.1d curl-7.66.0 zstd-1.4.5 libarchive-3.4.3 rtmidi-4.0.0 rtaudio nanovg nanosvg oui-blendish osdialog pffft clean: git clean -fdx diff --git a/include/string.hpp b/include/string.hpp index 4346011e..3d095277 100644 --- a/include/string.hpp +++ b/include/string.hpp @@ -69,18 +69,6 @@ Throws std::runtime_error if string is invalid. */ std::vector fromBase64(const std::string& str); -/** Compress bytes with zlib. -*/ -std::vector compress(const uint8_t* data, size_t dataLen); -std::vector compress(const std::vector& data); -/** Uncompress bytes with zlib. -Before calling this function, set `dataLen` to the capacity of `data`. -After returning, `dataLen` is set to the actual number of bytes written. -*/ -void uncompress(const uint8_t* compressed, size_t compressedLen, uint8_t* data, size_t* dataLen); -std::vector uncompress(const std::vector& compressed); - - struct CaseInsensitiveCompare { bool operator()(const std::string& a, const std::string& b) const; }; diff --git a/include/system.hpp b/include/system.hpp index c915c89d..bc13894e 100644 --- a/include/system.hpp +++ b/include/system.hpp @@ -70,7 +70,7 @@ The launched process will continue running if the current process is closed. void runProcessDetached(const std::string& path); std::string getOperatingSystemInfo(); /** Compresses the contents of a folder (recursively) to an archive. -Currently supports the "ustar zstd" format (.tar.zstd) +Currently supports the "ustar zstd" format (.tar.zst) An equivalent shell command is tar -cf archivePath --zstd -C folderPath . diff --git a/src/patch.cpp b/src/patch.cpp index 1a6d4fc9..161dc73d 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -13,6 +13,8 @@ #include #include +#include + namespace rack { @@ -23,9 +25,11 @@ static const char PATCH_FILTERS[] = "VCV Rack patch (.vcv):vcv"; PatchManager::PatchManager() { } + PatchManager::~PatchManager() { } + void PatchManager::reset() { if (APP->history) { APP->history->clear(); @@ -35,6 +39,7 @@ void PatchManager::reset() { } } + void PatchManager::clear() { if (APP->scene) { APP->scene->rack->clear(); @@ -43,6 +48,7 @@ void PatchManager::clear() { reset(); } + static bool promptClear(std::string text) { if (APP->history->isSaved()) return true; @@ -51,6 +57,7 @@ static bool promptClear(std::string text) { return osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, text.c_str()); } + void PatchManager::save(std::string path) { INFO("Saving patch %s", path.c_str()); saveAutosave(); @@ -58,6 +65,7 @@ void PatchManager::save(std::string path) { system::archiveFolder(filesystem::u8path(path), asset::autosavePath); } + void PatchManager::saveDialog() { if (path == "") { saveAsDialog(); @@ -75,6 +83,7 @@ void PatchManager::saveDialog() { APP->history->setSaved(); } + void PatchManager::saveAsDialog() { std::string dir; std::string filename; @@ -120,6 +129,7 @@ void PatchManager::saveAsDialog() { pushRecentPath(path); } + void PatchManager::saveTemplateDialog() { // Even if /template.vcv doesn't exist, this message is still valid because it overrides the /template.vcv patch. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?")) @@ -134,6 +144,7 @@ void PatchManager::saveTemplateDialog() { } } + void PatchManager::saveAutosave() { INFO("Saving autosave"); json_t* rootJ = toJson(); @@ -158,16 +169,40 @@ void PatchManager::saveAutosave() { system::moveFile(tmpPath, patchPath); } + +static bool isPatchLegacyPre2(std::string path) { + FILE* f = std::fopen(path.c_str(), "rb"); + if (!f) + return false; + DEFER({std::fclose(f);}); + // Read first byte and check if it's a "{" character. + // TODO Is it possible for .tar.zst files to start with the same character? + char buf[1] = {}; + std::fread(buf, 1, sizeof(buf), f); + return std::memcmp(buf, "{", 1) == 0; +} + + void PatchManager::load(std::string path) { INFO("Loading patch %s", path.c_str()); filesystem::remove_all(asset::autosavePath); filesystem::create_directories(asset::autosavePath); - system::unarchiveToFolder(filesystem::u8path(path), asset::autosavePath); + + if (isPatchLegacyPre2(path)) { + // Move the .vcv file directly to "patch.json". + filesystem::path autosavePath = filesystem::u8path(asset::autosavePath); + filesystem::copy(filesystem::u8path(path), autosavePath / "patch.json"); + } + else { + // Extract the .vcv file as a .tar.zst archive. + system::unarchiveToFolder(filesystem::u8path(path), asset::autosavePath); + } loadAutosave(); } + void PatchManager::loadTemplate() { this->path = ""; APP->history->setSaved(); @@ -191,6 +226,7 @@ void PatchManager::loadTemplate() { clear(); } + void PatchManager::loadTemplateDialog() { if (!promptClear("The current patch is unsaved. Clear it and start a new patch?")) { return; @@ -198,6 +234,7 @@ void PatchManager::loadTemplateDialog() { loadTemplate(); } + void PatchManager::loadAutosave() { INFO("Loading autosave"); std::string patchPath = asset::autosavePath + "/patch.json"; @@ -225,6 +262,7 @@ void PatchManager::loadAutosave() { fromJson(rootJ); } + void PatchManager::loadAction(std::string path) { try { load(path); @@ -239,6 +277,7 @@ void PatchManager::loadAction(std::string path) { pushRecentPath(path); } + void PatchManager::loadDialog() { if (!promptClear("The current patch is unsaved. Clear it and open a new patch?")) return; @@ -268,6 +307,7 @@ void PatchManager::loadDialog() { loadAction(path); } + void PatchManager::loadPathDialog(std::string path) { if (!promptClear("The current patch is unsaved. Clear it and open the new patch?")) return; @@ -275,6 +315,7 @@ void PatchManager::loadPathDialog(std::string path) { loadAction(path); } + void PatchManager::revertDialog() { if (path == "") return; @@ -292,6 +333,7 @@ void PatchManager::revertDialog() { APP->history->setSaved(); } + void PatchManager::pushRecentPath(std::string path) { auto& recent = settings::recentPatchPaths; // Remove path from recent patches (if exists) @@ -302,10 +344,12 @@ void PatchManager::pushRecentPath(std::string path) { 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(); @@ -326,6 +370,7 @@ json_t* PatchManager::toJson() { return rootJ; } + void PatchManager::fromJson(json_t* rootJ) { clear(); legacy = 0; @@ -365,10 +410,12 @@ void PatchManager::fromJson(json_t* rootJ) { warningLog = ""; } + bool PatchManager::isLegacy(int level) { return legacy && legacy <= level; } + void PatchManager::log(std::string msg) { warningLog += msg; warningLog += "\n"; diff --git a/src/string.cpp b/src/string.cpp index d8b58316..af519708 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -1,7 +1,6 @@ #include // for tolower and toupper #include // for transform #include // for dirname and basename -#include #if defined ARCH_WIN #include // for MultiByteToWideChar @@ -229,63 +228,13 @@ std::vector fromBase64(const std::string& str) { } -std::vector compress(const uint8_t* data, size_t dataLen) { - std::vector compressed; - uLongf outCap = ::compressBound(dataLen); - compressed.resize(outCap); - int err = ::compress2(compressed.data(), &outCap, data, dataLen, Z_BEST_COMPRESSION); - if (err) - throw std::runtime_error("Zlib error"); - compressed.resize(outCap); - return compressed; -} - - -std::vector compress(const std::vector& data) { - return compress(data.data(), data.size()); -} - - -void uncompress(const uint8_t* compressed, size_t compressedLen, uint8_t* data, size_t* dataLen) { - uLongf dataLenF = *dataLen; - int err = ::uncompress(data, &dataLenF, compressed, compressedLen); - (void) err; - *dataLen = dataLenF; -} - - -std::vector uncompress(const std::vector& compressed) { - // We don't know the uncompressed size, so we can't use the easy compress/uncompress API. - std::vector data; - - z_stream zs; - std::memset(&zs, 0, sizeof(zs)); - zs.next_in = (Bytef*) &compressed[0]; - zs.avail_in = compressed.size(); - inflateInit(&zs); - - while (true) { - uint8_t buffer[16384]; - zs.next_out = (Bytef*) buffer; - zs.avail_out = sizeof(buffer); - int err = inflate(&zs, Z_NO_FLUSH); - - if (err < 0) - throw Exception(string::f("zlib error %d", err)); - - data.insert(data.end(), buffer, zs.next_out); - if (err == Z_STREAM_END) - break; - } - - inflateEnd(&zs); - return data; -} - - bool CaseInsensitiveCompare::operator()(const std::string& a, const std::string& b) const { - // TODO Make more efficient by iterating characters - return lowercase(a) < lowercase(b); + if (a.size() != b.size()) + return false; + auto f = [](unsigned char a, unsigned char b) { + return std::tolower(a) == std::tolower(b); + }; + return std::equal(a.begin(), a.end(), b.begin(), f); } diff --git a/src/system.cpp b/src/system.cpp index b842036c..4866aeb9 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -26,8 +26,6 @@ #include #endif -#define ZIP_STATIC -#include #include #include @@ -362,74 +360,6 @@ std::string getOperatingSystemInfo() { } -int unzipToFolder(const std::string& zipPath, const std::string& dir) { - int err; - // Open ZIP file - zip_t* za = zip_open(zipPath.c_str(), 0, &err); - if (!za) { - WARN("Could not open ZIP file %s: error %d", zipPath.c_str(), err); - return err; - } - DEFER({ - zip_close(za); - }); - - // Iterate ZIP entries - for (int i = 0; i < zip_get_num_entries(za, 0); i++) { - zip_stat_t zs; - err = zip_stat_index(za, i, 0, &zs); - if (err) { - WARN("zip_stat_index() failed: error %d", err); - return err; - } - - std::string path = dir + "/" + zs.name; - - if (path[path.size() - 1] == '/') { - // Create directory - system::createDirectory(path); - // HACK - // Create and delete file to update the directory's mtime. - std::string tmpPath = path + "/.tmp"; - FILE* tmpFile = fopen(tmpPath.c_str(), "w"); - fclose(tmpFile); - std::remove(tmpPath.c_str()); - } - else { - // Open ZIP entry - zip_file_t* zf = zip_fopen_index(za, i, 0); - if (!zf) { - WARN("zip_fopen_index() failed"); - return -1; - } - DEFER({ - zip_fclose(zf); - }); - - // Create file - FILE* outFile = fopen(path.c_str(), "wb"); - if (!outFile) { - WARN("Could not create file %s", path.c_str()); - return -1; - } - DEFER({ - fclose(outFile); - }); - - // Read buffer and copy to file - while (true) { - char buffer[1 << 15]; - int len = zip_fread(zf, buffer, sizeof(buffer)); - if (len <= 0) - break; - fwrite(buffer, 1, len, outFile); - } - } - } - return 0; -} - - /** Behaves like `std::filesystem::relative()`. Limitation: `p` must be a descendant of `base`. Doesn't support adding `../` to the return path. */ @@ -448,7 +378,7 @@ static filesystem::path getRelativePath(filesystem::path p, filesystem::path bas } -void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath){ +void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath) { // Based on minitar.c create() in libarchive examples int r; @@ -539,7 +469,6 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa // archive_read_support_filter_all(a); archive_read_support_format_tar(a); // archive_read_support_format_all(a); - DEBUG("opening %s %s", archivePath.generic_string().c_str(), archivePath.string().c_str()); #if defined ARCH_WIN r = archive_read_open_filename_w(a, archivePath.generic_wstring().c_str(), 1 << 14); #else @@ -554,7 +483,6 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa DEFER({archive_write_free(disk);}); int flags = ARCHIVE_EXTRACT_TIME; archive_write_disk_set_options(disk, flags); - // archive_write_disk_set_standard_lookup(disk); DEFER({archive_write_close(disk);}); // Iterate archive