Browse Source

Make system::archiveFolder and unarchiveToFolder work on Windows with

Unicode. Rename UTF8toUTF16 to U8toU16 and switch back to std::wstring
instead of std::u16string.
tags/v2.0.0
Andrew Belt 4 years ago
parent
commit
8a50d39b7f
12 changed files with 239 additions and 157 deletions
  1. +2
    -1
      Makefile
  2. +10
    -10
      include/common.hpp
  3. +1
    -1
      include/patch.hpp
  4. +5
    -2
      include/string.hpp
  5. +10
    -2
      include/system.hpp
  6. +7
    -7
      src/asset.cpp
  7. +6
    -6
      src/common.cpp
  8. +51
    -12
      src/patch.cpp
  9. +2
    -2
      src/plugin.cpp
  10. +14
    -14
      src/string.cpp
  11. +129
    -99
      src/system.cpp
  12. +2
    -1
      standalone/main.cpp

+ 2
- 1
Makefile View File

@@ -61,10 +61,11 @@ ifdef ARCH_WIN
TARGET := libRack.dll TARGET := libRack.dll


SOURCES += dep/osdialog/osdialog_win.c SOURCES += dep/osdialog/osdialog_win.c
LDFLAGS += -municode
LDFLAGS += -Wl,--export-all-symbols LDFLAGS += -Wl,--export-all-symbols
LDFLAGS += -Wl,--out-implib,$(TARGET).a LDFLAGS += -Wl,--out-implib,$(TARGET).a
LDFLAGS += -Wl,-Bstatic -Wl,--whole-archive 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 += -Wl,-Bdynamic -Wl,--no-whole-archive
LDFLAGS += -lpthread -lopengl32 -lgdi32 -lws2_32 -lcomdlg32 -lole32 -ldsound -lwinmm -lksuser -lshlwapi -lmfplat -lmfuuid -lwmcodecdspuuid -ldbghelp LDFLAGS += -lpthread -lopengl32 -lgdi32 -lws2_32 -lcomdlg32 -lole32 -ldsound -lwinmm -lksuser -lshlwapi -lmfplat -lmfuuid -lwmcodecdspuuid -ldbghelp




+ 10
- 10
include/common.hpp View File

@@ -201,22 +201,22 @@ extern const std::string API_VERSION;


#if defined ARCH_WIN #if defined ARCH_WIN
// wchar_t on Windows should be 2 bytes // 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 // 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" { 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 { namespace std {
using ::fopen_utf8;
using ::remove_utf8;
using ::rename_utf8;
using ::fopen_u8;
using ::remove_u8;
using ::rename_u8;
} }
#endif #endif

+ 1
- 1
include/patch.hpp View File

@@ -29,7 +29,7 @@ struct PatchManager {
/** Loads a patch and nothing else. /** Loads a patch and nothing else.
Returns whether the patch was loaded successfully. Returns whether the patch was loaded successfully.
*/ */
bool load(std::string path);
void load(std::string path);
/** Loads the template patch. */ /** Loads the template patch. */
void loadTemplate(); void loadTemplate();
void loadTemplateDialog(); void loadTemplateDialog();


+ 5
- 2
include/string.hpp View File

@@ -89,9 +89,12 @@ struct CaseInsensitiveCompare {
#if defined ARCH_WIN #if defined ARCH_WIN
/** Performs a Unicode string conversion from UTF-16 to UTF-8. /** 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). 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 #endif


} // namespace string } // namespace string


+ 10
- 2
include/system.hpp View File

@@ -69,11 +69,19 @@ The launched process will continue running if the current process is closed.
*/ */
void runProcessDetached(const std::string& path); void runProcessDetached(const std::string& path);
std::string getOperatingSystemInfo(); 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) 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); 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 } // namespace system


+ 7
- 7
src/asset.cpp View File

@@ -61,12 +61,12 @@ static void initSystemDir() {
#endif #endif
#if defined ARCH_WIN #if defined ARCH_WIN
// Get path to executable // 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); assert(length > 0);
// Get folder of executable // Get folder of executable
PathRemoveFileSpecW((wchar_t*) moduleBufU16);
systemDir = string::UTF16toUTF8(moduleBufU16);
PathRemoveFileSpecW(moduleBufW);
systemDir = string::U16toU8(moduleBufW);
#endif #endif
#if defined ARCH_LIN #if defined ARCH_LIN
// Use the current working directory as the default path on Linux. // Use the current working directory as the default path on Linux.
@@ -86,10 +86,10 @@ static void initUserDir() {


#if defined ARCH_WIN #if defined ARCH_WIN
// Get "My Documents" folder // 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); assert(result == S_OK);
userDir = string::UTF16toUTF8(documentsBufU16);
userDir = string::U16toU8(documentsBufW);
userDir += "/Rack"; userDir += "/Rack";
#endif #endif
#if defined ARCH_MAC #if defined ARCH_MAC


+ 6
- 6
src/common.cpp View File

@@ -27,16 +27,16 @@ const std::string API_VERSION = "2";
#if defined ARCH_WIN #if defined ARCH_WIN
#include <windows.h> #include <windows.h>


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 #endif

+ 51
- 12
src/patch.cpp View File

@@ -55,17 +55,24 @@ void PatchManager::save(std::string path) {
INFO("Saving patch %s", path.c_str()); INFO("Saving patch %s", path.c_str());
saveAutosave(); saveAutosave();


system::archiveFolder(path, asset::autosavePath);
system::archiveFolder(filesystem::u8path(path), asset::autosavePath);
} }


void PatchManager::saveDialog() { void PatchManager::saveDialog() {
if (path == "") { if (path == "") {
saveAsDialog(); saveAsDialog();
return;
} }
else {

try {
save(path); save(path);
APP->history->setSaved();
} }
catch (Exception& e) {
WARN("Could not save patch: %s", e.what());
return;
}

APP->history->setSaved();
} }


void PatchManager::saveAsDialog() { void PatchManager::saveAsDialog() {
@@ -100,7 +107,14 @@ void PatchManager::saveAsDialog() {
path += ".vcv"; path += ".vcv";
} }


save(path);
try {
save(path);
}
catch (Exception& e) {
WARN("Could not save patch: %s", e.what());
return;
}

this->path = path; this->path = path;
APP->history->setSaved(); APP->history->setSaved();
pushRecentPath(path); pushRecentPath(path);
@@ -111,7 +125,13 @@ void PatchManager::saveTemplateDialog() {
if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?")) if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?"))
return; return;


save(asset::templatePath);
try {
save(asset::templatePath);
}
catch (Exception& e) {
WARN("Could not save template patch: %s", e.what());
return;
}
} }


void PatchManager::saveAutosave() { void PatchManager::saveAutosave() {
@@ -138,28 +158,35 @@ void PatchManager::saveAutosave() {
system::moveFile(tmpPath, patchPath); system::moveFile(tmpPath, patchPath);
} }


bool PatchManager::load(std::string path) {
void PatchManager::load(std::string path) {
INFO("Loading patch %s", path.c_str()); INFO("Loading patch %s", path.c_str());


filesystem::remove_all(asset::autosavePath); filesystem::remove_all(asset::autosavePath);
filesystem::create_directories(asset::autosavePath); filesystem::create_directories(asset::autosavePath);
system::unarchiveToFolder(path, asset::autosavePath);
system::unarchiveToFolder(filesystem::u8path(path), asset::autosavePath);


loadAutosave(); loadAutosave();
return true;
} }


void PatchManager::loadTemplate() { void PatchManager::loadTemplate() {
this->path = ""; this->path = "";
APP->history->setSaved(); APP->history->setSaved();


if (load(asset::templatePath)) {
try {
load(asset::templatePath);
return; 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; return;
} }
catch (Exception& e) {
WARN("Could not load system template patch, clearing rack: %s", e.what());
}


clear(); clear();
} }
@@ -199,9 +226,14 @@ void PatchManager::loadAutosave() {
} }


void PatchManager::loadAction(std::string path) { void PatchManager::loadAction(std::string path) {
if (!load(path)) {
try {
load(path);
}
catch (Exception& e) {
WARN("Could not load patch: %s", e.what());
return; return;
} }

this->path = path; this->path = path;
APP->history->setSaved(); APP->history->setSaved();
pushRecentPath(path); pushRecentPath(path);
@@ -249,7 +281,14 @@ void PatchManager::revertDialog() {
if (!promptClear("Revert patch to the last saved state?")) if (!promptClear("Revert patch to the last saved state?"))
return; return;


load(path);
try {
load(path);
}
catch (Exception& e) {
WARN("Could not load patch: %s", e.what());
return;
}

APP->history->setSaved(); APP->history->setSaved();
} }




+ 2
- 2
src/plugin.cpp View File

@@ -46,8 +46,8 @@ namespace plugin {
static void* loadLibrary(std::string libraryPath) { static void* loadLibrary(std::string libraryPath) {
#if defined ARCH_WIN #if defined ARCH_WIN
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); 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); SetErrorMode(0);
if (!handle) { if (!handle) {
int error = GetLastError(); int error = GetLastError();


+ 14
- 14
src/string.cpp View File

@@ -127,11 +127,11 @@ std::string absolutePath(const std::string& path) {
if (absPathC) if (absPathC)
return absPathC; return absPathC;
#elif defined ARCH_WIN #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) if (absPathC)
return UTF16toUTF8(absPathC);
return U16toU8(absPathC);
#endif #endif
return ""; return "";
} }
@@ -290,33 +290,33 @@ bool CaseInsensitiveCompare::operator()(const std::string& a, const std::string&




#if defined ARCH_WIN #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 ""; return "";
// Compute length of output buffer // 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); assert(len > 0);
std::string s; std::string s;
// Allocate enough space for null character // Allocate enough space for null character
s.resize(len); 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); assert(len > 0);
return s; return s;
} }




std::u16string UTF8toUTF16(const std::string& s) {
std::wstring U8toU16(const std::string& s) {
if (s.empty()) if (s.empty())
return u"";
return L"";
// Compute length of output buffer // Compute length of output buffer
int len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), NULL, 0); int len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), NULL, 0);
assert(len > 0); assert(len > 0);
std::u16string sU16;
std::wstring w;
// Allocate enough space for null character // 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); assert(len > 0);
return sU16;
return w;
} }
#endif #endif




+ 129
- 99
src/system.cpp View File

@@ -132,8 +132,8 @@ void copyFile(const std::string& srcPath, const std::string& destPath) {


void createDirectory(const std::string& path) { void createDirectory(const std::string& path) {
#if defined ARCH_WIN #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 #else
mkdir(path.c_str(), 0755); mkdir(path.c_str(), 0755);
#endif #endif
@@ -152,8 +152,8 @@ void createDirectories(const std::string& path) {


void removeDirectory(const std::string& path) { void removeDirectory(const std::string& path) {
#if defined ARCH_WIN #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 #else
rmdir(path.c_str()); rmdir(path.c_str());
#endif #endif
@@ -172,9 +172,9 @@ void removeDirectories(const std::string& path) {


std::string getWorkingDirectory() { std::string getWorkingDirectory() {
#if defined ARCH_WIN #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 #else
char buf[4096] = ""; char buf[4096] = "";
getcwd(buf, sizeof(buf)); getcwd(buf, sizeof(buf));
@@ -185,8 +185,8 @@ std::string getWorkingDirectory() {


void setWorkingDirectory(const std::string& path) { void setWorkingDirectory(const std::string& path) {
#if defined ARCH_WIN #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 #else
chdir(path.c_str()); chdir(path.c_str());
#endif #endif
@@ -303,8 +303,8 @@ void openBrowser(const std::string& url) {
std::system(command.c_str()); std::system(command.c_str());
#endif #endif
#if defined ARCH_WIN #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 #endif
} }


@@ -319,8 +319,8 @@ void openFolder(const std::string& path) {
std::system(command.c_str()); std::system(command.c_str());
#endif #endif
#if defined ARCH_WIN #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 #endif
} }


@@ -332,8 +332,8 @@ void runProcessDetached(const std::string& path) {
shExInfo.cbSize = sizeof(shExInfo); shExInfo.cbSize = sizeof(shExInfo);
shExInfo.lpVerb = L"runas"; 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; shExInfo.nShow = SW_SHOW;


if (ShellExecuteExW(&shExInfo)) { 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) { void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::path& folderPath) {
// Based on minitar.c extract() in libarchive examples // Based on minitar.c extract() in libarchive examples
int r; int r;
@@ -441,10 +539,14 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa
// archive_read_support_filter_all(a); // archive_read_support_filter_all(a);
archive_read_support_format_tar(a); archive_read_support_format_tar(a);
// archive_read_support_format_all(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);}); DEFER({archive_read_close(a);});


// Open folder for writing // Open folder for writing
@@ -465,12 +567,16 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa
if (r < ARCHIVE_OK) if (r < ARCHIVE_OK)
throw Exception(string::f("unzipToFolder() could not read entry from archive: %s", archive_error_string(a))); 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()) 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); 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 // Write entry to disk
r = archive_write_header(disk, entry); 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 system
} // namespace rack } // namespace rack

+ 2
- 1
standalone/main.cpp View File

@@ -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[]) { int main(int argc, char* argv[]) {
#if defined ARCH_WIN #if defined ARCH_WIN
// Windows global mutex to prevent multiple instances // Windows global mutex to prevent multiple instances
// Handle will be closed by Windows when the process ends // 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) { if (GetLastError() == ERROR_ALREADY_EXISTS) {
osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported."); osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported.");
exit(1); exit(1);


Loading…
Cancel
Save