From 8a50d39b7f1e556beb501e4f9bb16ba762861ea5 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sat, 5 Sep 2020 11:26:16 -0400 Subject: [PATCH] Make system::archiveFolder and unarchiveToFolder work on Windows with Unicode. Rename UTF8toUTF16 to U8toU16 and switch back to std::wstring instead of std::u16string. --- Makefile | 3 +- include/common.hpp | 20 ++-- include/patch.hpp | 2 +- include/string.hpp | 7 +- include/system.hpp | 12 ++- src/asset.cpp | 14 +-- src/common.cpp | 12 +-- src/patch.cpp | 63 +++++++++--- src/plugin.cpp | 4 +- src/string.cpp | 28 +++--- src/system.cpp | 228 +++++++++++++++++++++++++------------------- standalone/main.cpp | 3 +- 12 files changed, 239 insertions(+), 157 deletions(-) diff --git a/Makefile b/Makefile index eec3abde..fb99f882 100644 --- a/Makefile +++ b/Makefile @@ -61,10 +61,11 @@ ifdef ARCH_WIN TARGET := libRack.dll SOURCES += dep/osdialog/osdialog_win.c + LDFLAGS += -municode 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 + 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 += -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/include/common.hpp b/include/common.hpp index 5394efc3..604cdacd 100644 --- a/include/common.hpp +++ b/include/common.hpp @@ -201,22 +201,22 @@ extern const std::string API_VERSION; #if defined ARCH_WIN // wchar_t on Windows should be 2 bytes -static_assert(sizeof(char16_t) == sizeof(wchar_t)); +static_assert(sizeof(wchar_t) == 2); // Windows C standard functions are ASCII-8 instead of UTF-8, so redirect these functions to wrappers which convert to UTF-8 -#define fopen fopen_utf8 -#define remove remove_utf8 -#define rename rename_utf8 +#define fopen fopen_u8 +#define remove remove_u8 +#define rename rename_u8 extern "C" { -FILE* fopen_utf8(const char* filename, const char* mode); -int remove_utf8(const char* path); -int rename_utf8(const char* oldname, const char* newname); +FILE* fopen_u8(const char* filename, const char* mode); +int remove_u8(const char* path); +int rename_u8(const char* oldname, const char* newname); } namespace std { - using ::fopen_utf8; - using ::remove_utf8; - using ::rename_utf8; + using ::fopen_u8; + using ::remove_u8; + using ::rename_u8; } #endif diff --git a/include/patch.hpp b/include/patch.hpp index 8e7850a9..460d10e9 100644 --- a/include/patch.hpp +++ b/include/patch.hpp @@ -29,7 +29,7 @@ struct PatchManager { /** Loads a patch and nothing else. Returns whether the patch was loaded successfully. */ - bool load(std::string path); + void load(std::string path); /** Loads the template patch. */ void loadTemplate(); void loadTemplateDialog(); diff --git a/include/string.hpp b/include/string.hpp index 2a3a2e6c..4346011e 100644 --- a/include/string.hpp +++ b/include/string.hpp @@ -89,9 +89,12 @@ struct CaseInsensitiveCompare { #if defined ARCH_WIN /** Performs a Unicode string conversion from UTF-16 to UTF-8. These are only defined on Windows because the implementation uses Windows' API, and conversion is not needed on other OS's (since everything on Mac and Linux is UTF-8). + +std::string and char* variables are considered UTF-8, anywhere in the program. +See https://utf8everywhere.org/ for more information about VCV Rack's philosophy on string encoding, especially section 10 for rules VCV follows for handling text on Windows. */ -std::string UTF16toUTF8(const std::u16string& s); -std::u16string UTF8toUTF16(const std::string& s); +std::string U16toU8(const std::wstring& w); +std::wstring U8toU16(const std::string& s); #endif } // namespace string diff --git a/include/system.hpp b/include/system.hpp index 12c30002..c915c89d 100644 --- a/include/system.hpp +++ b/include/system.hpp @@ -69,11 +69,19 @@ The launched process will continue running if the current process is closed. */ void runProcessDetached(const std::string& path); std::string getOperatingSystemInfo(); -/** Extracts an archive into a folder. +/** Compresses the contents of a folder (recursively) to an archive. Currently supports the "ustar zstd" format (.tar.zstd) +An equivalent shell command is + + tar -cf archivePath --zstd -C folderPath . */ -void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::path& folderPath); void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath); +/** Extracts an archive into a folder. +An equivalent shell command is + + tar -xf archivePath --zstd -C folderPath +*/ +void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::path& folderPath); } // namespace system diff --git a/src/asset.cpp b/src/asset.cpp index d2c2906b..36be9175 100644 --- a/src/asset.cpp +++ b/src/asset.cpp @@ -61,12 +61,12 @@ static void initSystemDir() { #endif #if defined ARCH_WIN // Get path to executable - char16_t moduleBufU16[MAX_PATH] = u""; - DWORD length = GetModuleFileNameW(NULL, (wchar_t*) moduleBufU16, LENGTHOF(moduleBufU16)); + wchar_t moduleBufW[MAX_PATH] = L""; + DWORD length = GetModuleFileNameW(NULL, moduleBufW, LENGTHOF(moduleBufW)); assert(length > 0); // Get folder of executable - PathRemoveFileSpecW((wchar_t*) moduleBufU16); - systemDir = string::UTF16toUTF8(moduleBufU16); + PathRemoveFileSpecW(moduleBufW); + systemDir = string::U16toU8(moduleBufW); #endif #if defined ARCH_LIN // Use the current working directory as the default path on Linux. @@ -86,10 +86,10 @@ static void initUserDir() { #if defined ARCH_WIN // Get "My Documents" folder - char16_t documentsBufU16[MAX_PATH] = u"."; - HRESULT result = SHGetFolderPathW(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, (wchar_t*) documentsBufU16); + wchar_t documentsBufW[MAX_PATH] = L"."; + HRESULT result = SHGetFolderPathW(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, documentsBufW); assert(result == S_OK); - userDir = string::UTF16toUTF8(documentsBufU16); + userDir = string::U16toU8(documentsBufW); userDir += "/Rack"; #endif #if defined ARCH_MAC diff --git a/src/common.cpp b/src/common.cpp index 8ee9ed98..7a5f638b 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -27,16 +27,16 @@ const std::string API_VERSION = "2"; #if defined ARCH_WIN #include -FILE* fopen_utf8(const char* filename, const char* mode) { - return _wfopen((wchar_t*) rack::string::UTF8toUTF16(filename).c_str(), (wchar_t*) rack::string::UTF8toUTF16(mode).c_str()); +FILE* fopen_u8(const char* filename, const char* mode) { + return _wfopen(rack::string::U8toU16(filename).c_str(), rack::string::U8toU16(mode).c_str()); } -int remove_utf8(const char* path) { - return _wremove((wchar_t*) rack::string::UTF8toUTF16(path).c_str()); +int remove_u8(const char* path) { + return _wremove(rack::string::U8toU16(path).c_str()); } -int rename_utf8(const char* oldname, const char* newname) { - return _wrename((wchar_t*) rack::string::UTF8toUTF16(oldname).c_str(), (wchar_t*) rack::string::UTF8toUTF16(newname).c_str()); +int rename_u8(const char* oldname, const char* newname) { + return _wrename(rack::string::U8toU16(oldname).c_str(), rack::string::U8toU16(newname).c_str()); } #endif diff --git a/src/patch.cpp b/src/patch.cpp index 6eb7192a..1a6d4fc9 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -55,17 +55,24 @@ void PatchManager::save(std::string path) { INFO("Saving patch %s", path.c_str()); saveAutosave(); - system::archiveFolder(path, asset::autosavePath); + system::archiveFolder(filesystem::u8path(path), asset::autosavePath); } void PatchManager::saveDialog() { if (path == "") { saveAsDialog(); + return; } - else { + + try { save(path); - APP->history->setSaved(); } + catch (Exception& e) { + WARN("Could not save patch: %s", e.what()); + return; + } + + APP->history->setSaved(); } void PatchManager::saveAsDialog() { @@ -100,7 +107,14 @@ void PatchManager::saveAsDialog() { path += ".vcv"; } - save(path); + try { + save(path); + } + catch (Exception& e) { + WARN("Could not save patch: %s", e.what()); + return; + } + this->path = path; APP->history->setSaved(); pushRecentPath(path); @@ -111,7 +125,13 @@ void PatchManager::saveTemplateDialog() { if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?")) return; - save(asset::templatePath); + try { + save(asset::templatePath); + } + catch (Exception& e) { + WARN("Could not save template patch: %s", e.what()); + return; + } } void PatchManager::saveAutosave() { @@ -138,28 +158,35 @@ void PatchManager::saveAutosave() { system::moveFile(tmpPath, patchPath); } -bool PatchManager::load(std::string path) { +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(path, asset::autosavePath); + system::unarchiveToFolder(filesystem::u8path(path), asset::autosavePath); loadAutosave(); - return true; } void PatchManager::loadTemplate() { this->path = ""; APP->history->setSaved(); - if (load(asset::templatePath)) { + try { + load(asset::templatePath); return; } + catch (Exception& e) { + INFO("Could not load user template patch, attempting system template patch: %s", e.what()); + } - if (load(asset::system("template.vcv"))) { + try { + load(asset::system("template.vcv")); return; } + catch (Exception& e) { + WARN("Could not load system template patch, clearing rack: %s", e.what()); + } clear(); } @@ -199,9 +226,14 @@ void PatchManager::loadAutosave() { } void PatchManager::loadAction(std::string path) { - if (!load(path)) { + try { + load(path); + } + catch (Exception& e) { + WARN("Could not load patch: %s", e.what()); return; } + this->path = path; APP->history->setSaved(); pushRecentPath(path); @@ -249,7 +281,14 @@ void PatchManager::revertDialog() { if (!promptClear("Revert patch to the last saved state?")) return; - load(path); + try { + load(path); + } + catch (Exception& e) { + WARN("Could not load patch: %s", e.what()); + return; + } + APP->history->setSaved(); } diff --git a/src/plugin.cpp b/src/plugin.cpp index b07acd37..e9286e90 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -46,8 +46,8 @@ namespace plugin { static void* loadLibrary(std::string libraryPath) { #if defined ARCH_WIN SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); - std::u16string libraryFilenameU16 = string::UTF8toUTF16(libraryPath); - HINSTANCE handle = LoadLibraryW((wchar_t*) libraryFilenameU16.c_str()); + std::wstring libraryFilenameW = string::U8toU16(libraryPath); + HINSTANCE handle = LoadLibraryW(libraryFilenameW.c_str()); SetErrorMode(0); if (!handle) { int error = GetLastError(); diff --git a/src/string.cpp b/src/string.cpp index f90abe43..d8b58316 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -127,11 +127,11 @@ std::string absolutePath(const std::string& path) { if (absPathC) return absPathC; #elif defined ARCH_WIN - std::u16string pathU16 = UTF8toUTF16(path); - char16_t buf[PATH_MAX]; - char16_t* absPathC = (char16_t*) _wfullpath((wchar_t*) buf, (wchar_t*) pathU16.c_str(), PATH_MAX); + std::wstring pathW = U8toU16(path); + wchar_t buf[PATH_MAX]; + wchar_t* absPathC = _wfullpath(buf, pathW.c_str(), PATH_MAX); if (absPathC) - return UTF16toUTF8(absPathC); + return U16toU8(absPathC); #endif return ""; } @@ -290,33 +290,33 @@ bool CaseInsensitiveCompare::operator()(const std::string& a, const std::string& #if defined ARCH_WIN -std::string UTF16toUTF8(const std::u16string& sU16) { - if (sU16.empty()) +std::string U16toU8(const std::wstring& w) { + if (w.empty()) return ""; // Compute length of output buffer - int len = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*) &sU16[0], sU16.size(), NULL, 0, NULL, NULL); + int len = WideCharToMultiByte(CP_UTF8, 0, &w[0], w.size(), NULL, 0, NULL, NULL); assert(len > 0); std::string s; // Allocate enough space for null character s.resize(len); - len = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*) &sU16[0], sU16.size(), &s[0], len, 0, 0); + len = WideCharToMultiByte(CP_UTF8, 0, &w[0], w.size(), &s[0], len, 0, 0); assert(len > 0); return s; } -std::u16string UTF8toUTF16(const std::string& s) { +std::wstring U8toU16(const std::string& s) { if (s.empty()) - return u""; + return L""; // Compute length of output buffer int len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), NULL, 0); assert(len > 0); - std::u16string sU16; + std::wstring w; // Allocate enough space for null character - sU16.resize(len); - len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), (wchar_t*) &sU16[0], len); + w.resize(len); + len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), &w[0], len); assert(len > 0); - return sU16; + return w; } #endif diff --git a/src/system.cpp b/src/system.cpp index 88b04655..b842036c 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -132,8 +132,8 @@ void copyFile(const std::string& srcPath, const std::string& destPath) { void createDirectory(const std::string& path) { #if defined ARCH_WIN - std::u16string pathU16 = string::UTF8toUTF16(path); - _wmkdir((wchar_t*) pathU16.c_str()); + std::wstring pathW = string::U8toU16(path); + _wmkdir(pathW.c_str()); #else mkdir(path.c_str(), 0755); #endif @@ -152,8 +152,8 @@ void createDirectories(const std::string& path) { void removeDirectory(const std::string& path) { #if defined ARCH_WIN - std::u16string pathU16 = string::UTF8toUTF16(path); - _wrmdir((wchar_t*) pathU16.c_str()); + std::wstring pathW = string::U8toU16(path); + _wrmdir(pathW.c_str()); #else rmdir(path.c_str()); #endif @@ -172,9 +172,9 @@ void removeDirectories(const std::string& path) { std::string getWorkingDirectory() { #if defined ARCH_WIN - char16_t buf[4096] = u""; - GetCurrentDirectory(sizeof(buf), (wchar_t*) buf); - return string::UTF16toUTF8(buf); + wchar_t buf[4096] = L""; + GetCurrentDirectory(sizeof(buf), buf); + return string::U16toU8(buf); #else char buf[4096] = ""; getcwd(buf, sizeof(buf)); @@ -185,8 +185,8 @@ std::string getWorkingDirectory() { void setWorkingDirectory(const std::string& path) { #if defined ARCH_WIN - std::u16string pathU16 = string::UTF8toUTF16(path); - SetCurrentDirectory((wchar_t*) pathU16.c_str()); + std::wstring pathW = string::U8toU16(path); + SetCurrentDirectory(pathW.c_str()); #else chdir(path.c_str()); #endif @@ -303,8 +303,8 @@ void openBrowser(const std::string& url) { std::system(command.c_str()); #endif #if defined ARCH_WIN - std::u16string urlW = string::UTF8toUTF16(url); - ShellExecuteW(NULL, L"open", (wchar_t*) urlW.c_str(), NULL, NULL, SW_SHOWDEFAULT); + std::wstring urlW = string::U8toU16(url); + ShellExecuteW(NULL, L"open", urlW.c_str(), NULL, NULL, SW_SHOWDEFAULT); #endif } @@ -319,8 +319,8 @@ void openFolder(const std::string& path) { std::system(command.c_str()); #endif #if defined ARCH_WIN - std::u16string pathU16 = string::UTF8toUTF16(path); - ShellExecuteW(NULL, L"explore", (wchar_t*) pathU16.c_str(), NULL, NULL, SW_SHOWDEFAULT); + std::wstring pathW = string::U8toU16(path); + ShellExecuteW(NULL, L"explore", pathW.c_str(), NULL, NULL, SW_SHOWDEFAULT); #endif } @@ -332,8 +332,8 @@ void runProcessDetached(const std::string& path) { shExInfo.cbSize = sizeof(shExInfo); shExInfo.lpVerb = L"runas"; - std::u16string pathU16 = string::UTF8toUTF16(path); - shExInfo.lpFile = (wchar_t*) pathU16.c_str(); + std::wstring pathW = string::U8toU16(path); + shExInfo.lpFile = pathW.c_str(); shExInfo.nShow = SW_SHOW; if (ShellExecuteExW(&shExInfo)) { @@ -430,6 +430,104 @@ int unzipToFolder(const std::string& zipPath, const std::string& dir) { } +/** Behaves like `std::filesystem::relative()`. +Limitation: `p` must be a descendant of `base`. Doesn't support adding `../` to the return path. +*/ +static filesystem::path getRelativePath(filesystem::path p, filesystem::path base = filesystem::current_path()) { + p = filesystem::absolute(p); + base = filesystem::absolute(base); + std::string pStr = p.generic_u8string(); + std::string baseStr = base.generic_u8string(); + if (pStr.size() < baseStr.size()) + throw Exception("getRelativePath() error: path is shorter than base"); + if (!std::equal(baseStr.begin(), baseStr.end(), pStr.begin())) + throw Exception("getRelativePath() error: path does not begin with base"); + + // If p == base, this correctly returns "." + return "." + std::string(pStr.begin() + baseStr.size(), pStr.end()); +} + + +void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath){ + // Based on minitar.c create() in libarchive examples + int r; + + // Open archive for writing + struct archive* a = archive_write_new(); + DEFER({archive_write_free(a);}); + archive_write_set_format_ustar(a); + archive_write_add_filter_zstd(a); +#if defined ARCH_WIN + r = archive_write_open_filename_w(a, archivePath.generic_wstring().c_str()); +#else + r = archive_write_open_filename(a, archivePath.generic_u8string().c_str()); +#endif + if (r < ARCHIVE_OK) + throw Exception(string::f("archiveFolder() could not open archive %s for writing: %s", archivePath.generic_u8string().c_str(), archive_error_string(a))); + DEFER({archive_write_close(a);}); + + // Open folder for reading + struct archive* disk = archive_read_disk_new(); + DEFER({archive_read_free(disk);}); +#if defined ARCH_WIN + r = archive_read_disk_open_w(disk, folderPath.generic_wstring().c_str()); +#else + r = archive_read_disk_open(disk, folderPath.generic_u8string().c_str()); +#endif + if (r < ARCHIVE_OK) + throw Exception(string::f("archiveFolder() could not open folder %s for reading: %s", folderPath.generic_u8string().c_str(), archive_error_string(a))); + DEFER({archive_read_close(a);}); + + // Iterate folder + for (;;) { + struct archive_entry* entry = archive_entry_new(); + DEFER({archive_entry_free(entry);}); + + r = archive_read_next_header2(disk, entry); + if (r == ARCHIVE_EOF) + break; + if (r < ARCHIVE_OK) + throw Exception(string::f("archiveFolder() could not get next entry from archive: %s", archive_error_string(disk))); + + // Recurse dirs + archive_read_disk_descend(disk); + + // Convert absolute path to relative path + filesystem::path entryPath = +#if defined ARCH_WIN + archive_entry_pathname_w(entry); +#else + archive_entry_pathname(entry); +#endif + entryPath = getRelativePath(entryPath, folderPath); +#if defined ARCH_WIN + archive_entry_copy_pathname_w(entry, entryPath.generic_wstring().c_str()); +#else + archive_entry_set_pathname(entry, entryPath.generic_u8string().c_str()); +#endif + + // Write file to archive + r = archive_write_header(a, entry); + if (r < ARCHIVE_OK) + throw Exception(string::f("archiveFolder() could not write entry to archive: %s", archive_error_string(a))); + + // Manually copy data +#if defined ARCH_WIN + filesystem::path entrySourcePath = archive_entry_sourcepath_w(entry); +#else + filesystem::path entrySourcePath = archive_entry_sourcepath(entry); +#endif + FILE* f = std::fopen(entrySourcePath.generic_u8string().c_str(), "rb"); + DEFER({std::fclose(f);}); + char buf[1 << 14]; + ssize_t len; + while ((len = std::fread(buf, 1, sizeof(buf), f)) > 0) { + archive_write_data(a, buf, len); + } + } +} + + void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::path& folderPath) { // Based on minitar.c extract() in libarchive examples int r; @@ -441,10 +539,14 @@ 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); - // TODO Fix unicode filenames on Windows? - r = archive_read_open_filename(a, archivePath.c_str(), 1 << 14); - if (r != ARCHIVE_OK) - throw Exception(string::f("unzipToFolder() could not open archive %s: %s", archivePath.c_str(), archive_error_string(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 + r = archive_read_open_filename(a, archivePath.generic_u8string().c_str(), 1 << 14); +#endif + if (r < ARCHIVE_OK) + throw Exception(string::f("unzipToFolder() could not open archive %s: %s", archivePath.generic_u8string().c_str(), archive_error_string(a))); DEFER({archive_read_close(a);}); // Open folder for writing @@ -465,12 +567,16 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa if (r < ARCHIVE_OK) throw Exception(string::f("unzipToFolder() could not read entry from archive: %s", archive_error_string(a))); - // Set pathname relative to folderPath - filesystem::path entryPath = archive_entry_pathname(entry); + // Convert relative pathname to absolute based on folderPath + filesystem::path entryPath = filesystem::u8path(archive_entry_pathname(entry)); if (!entryPath.is_relative()) - throw Exception(string::f("unzipToFolder() does not support absolute paths: %s", entryPath.c_str())); + throw Exception(string::f("unzipToFolder() does not support absolute paths: %s", entryPath.generic_u8string().c_str())); entryPath = filesystem::absolute(entryPath, folderPath); - archive_entry_set_pathname(entry, entryPath.c_str()); +#if defined ARCH_WIN + archive_entry_copy_pathname_w(entry, entryPath.generic_wstring().c_str()); +#else + archive_entry_set_pathname(entry, entryPath.generic_u8string().c_str()); +#endif // Write entry to disk r = archive_write_header(disk, entry); @@ -503,81 +609,5 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa } -/** Behaves like `std::filesystem::relative()`. -Limitation: `p` must be a descendant of `base`. Doesn't support adding `../` to the return path. -*/ -static filesystem::path getRelativePath(filesystem::path p, filesystem::path base = filesystem::current_path()) { - p = filesystem::absolute(p); - base = filesystem::absolute(base); - std::string pStr = p; - std::string baseStr = base; - if (pStr.size() < baseStr.size()) - throw Exception("getRelativePath() error: path is shorter than base"); - if (!std::equal(baseStr.begin(), baseStr.end(), pStr.begin())) - throw Exception("getRelativePath() error: path does not begin with base"); - - // If p == base, this correctly returns "." - return "." + std::string(pStr.begin() + baseStr.size(), pStr.end()); -} - - -void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath){ - // Based on minitar.c create() in libarchive examples - int r; - - // Open archive for writing - struct archive* a = archive_write_new(); - DEFER({archive_write_free(a);}); - archive_write_set_format_ustar(a); - archive_write_add_filter_zstd(a); - r = archive_write_open_filename(a, archivePath.c_str()); - if (r != ARCHIVE_OK) - throw Exception(string::f("archiveFolder() could not open archive %s for writing: %s", archivePath.c_str(), archive_error_string(a))); - DEFER({archive_write_close(a);}); - - // Open folder for reading - struct archive* disk = archive_read_disk_new(); - DEFER({archive_read_free(disk);}); - r = archive_read_disk_open(disk, folderPath.c_str()); - if (r != ARCHIVE_OK) - throw Exception(string::f("archiveFolder() could not open folder %s for reading: %s", folderPath.c_str(), archive_error_string(a))); - DEFER({archive_read_close(a);}); - - // Iterate folder - for (;;) { - struct archive_entry* entry = archive_entry_new(); - DEFER({archive_entry_free(entry);}); - - r = archive_read_next_header2(disk, entry); - if (r == ARCHIVE_EOF) - break; - if (r != ARCHIVE_OK) - throw Exception(string::f("archiveFolder() could not get next entry from archive: %s", archive_error_string(disk))); - - // Recurse dirs - archive_read_disk_descend(disk); - - // Convert absolute path to relative path - filesystem::path entryPath = archive_entry_pathname(entry); - entryPath = getRelativePath(entryPath, folderPath); - archive_entry_set_pathname(entry, entryPath.c_str()); - - // Write file to archive - r = archive_write_header(a, entry); - if (r != ARCHIVE_OK) - throw Exception("archiveFolder() could not write entry to archive"); - - // Manually copy data - FILE* f = std::fopen(archive_entry_sourcepath(entry), "rb"); - DEFER({std::fclose(f);}); - static char buf[1 << 14]; - ssize_t len; - while ((len = std::fread(buf, 1, sizeof(buf), f)) > 0) { - archive_write_data(a, buf, len); - } - } -} - - } // namespace system } // namespace rack diff --git a/standalone/main.cpp b/standalone/main.cpp index b42238d7..1efc7c9b 100644 --- a/standalone/main.cpp +++ b/standalone/main.cpp @@ -56,11 +56,12 @@ static void fatalSignalHandler(int sig) { } +// TODO Use -municode on Windows and change this to wmain. Convert argv to UTF-8. int main(int argc, char* argv[]) { #if defined ARCH_WIN // Windows global mutex to prevent multiple instances // Handle will be closed by Windows when the process ends - HANDLE instanceMutex = CreateMutexW(NULL, true, (wchar_t*) string::UTF8toUTF16(APP_NAME).c_str()); + HANDLE instanceMutex = CreateMutexW(NULL, true, string::U8toU16(APP_NAME).c_str()); if (GetLastError() == ERROR_ALREADY_EXISTS) { osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported."); exit(1);