@@ -205,18 +205,12 @@ 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_u8 | |||
#define remove remove_u8 | |||
#define rename rename_u8 | |||
extern "C" { | |||
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_u8; | |||
using ::remove_u8; | |||
using ::rename_u8; | |||
} | |||
#endif |
@@ -28,31 +28,6 @@ std::string ellipsizePrefix(const std::string& s, size_t len); | |||
bool startsWith(const std::string& str, const std::string& prefix); | |||
bool endsWith(const std::string& str, const std::string& suffix); | |||
/** Extracts the directory of the path. | |||
Example: directory("dir/file.txt") // "dir" | |||
Calls POSIX dirname(). | |||
*/ | |||
std::string directory(const std::string& path); | |||
/** Extracts the filename of the path. | |||
Example: directory("dir/file.txt") // "file.txt" | |||
Calls POSIX basename(). | |||
*/ | |||
std::string filename(const std::string& path); | |||
/** Extracts the portion of a filename without the extension. | |||
Example: filenameBase("file.txt") // "file" | |||
Note: Only works on filenames. Call filename(path) to get the filename of the path. | |||
*/ | |||
std::string filenameBase(const std::string& filename); | |||
/** Extracts the extension of a filename. | |||
Example: filenameExtension("file.txt") // "txt" | |||
Note: Only works on filenames. Call filename(path) to get the filename of the path. | |||
*/ | |||
std::string filenameExtension(const std::string& filename); | |||
/** Returns the canonicalized absolute path pointed to by `path`, following symlinks. | |||
Returns "" if the symbol is not found. | |||
*/ | |||
std::string absolutePath(const std::string& path); | |||
/** Scores how well a query matches a string. | |||
A score of 0 means no match. | |||
The score is arbitrary and is only meaningful for sorting. | |||
@@ -85,5 +60,6 @@ std::string U16toU8(const std::wstring& w); | |||
std::wstring U8toU16(const std::string& s); | |||
#endif | |||
} // namespace string | |||
} // namespace rack |
@@ -2,54 +2,117 @@ | |||
#include <list> | |||
#include <common.hpp> | |||
#include <experimental/filesystem> | |||
namespace rack { | |||
// In C++17, this will be `std::filesystem` | |||
namespace filesystem = std::experimental::filesystem; | |||
/** Cross-platform functions for operating systems routines | |||
*/ | |||
namespace system { | |||
/** Returns a list of all entries (directories, files, symbols) in a directory. | |||
Sorted alphabetically. | |||
// Filesystem | |||
/** Returns a list of all entries (directories, files, symbolic links, etc) in a directory. | |||
`depth` is the number of directories to recurse. 0 depth does not recurse. -1 depth recurses infinitely. | |||
*/ | |||
std::list<std::string> getEntries(const std::string& path); | |||
std::list<std::string> getEntriesRecursive(const std::string &path, int depth); | |||
std::list<std::string> getEntries(const std::string& dirPath, int depth = 0); | |||
bool doesExist(const std::string& path); | |||
/** Returns whether the given path is a file. */ | |||
bool isFile(const std::string& path); | |||
/** Returns whether the given path is a directory. */ | |||
bool isDirectory(const std::string& path); | |||
/** Moves a file. */ | |||
void moveFile(const std::string& srcPath, const std::string& destPath); | |||
/** Copies a file. */ | |||
void copyFile(const std::string& srcPath, const std::string& destPath); | |||
uint64_t getFileSize(const std::string& path); | |||
/** Moves a file or folder. | |||
Does not overwrite the destination. If this behavior is needed, use remove() or removeRecursively() before moving. | |||
*/ | |||
void rename(const std::string& srcPath, const std::string& destPath); | |||
/** Copies a file or folder recursively. */ | |||
void copy(const std::string& srcPath, const std::string& destPath); | |||
/** Creates a directory. | |||
The parent directory must exist. | |||
*/ | |||
void createDirectory(const std::string& path); | |||
bool createDirectory(const std::string& path); | |||
/** Creates all directories up to the path. | |||
*/ | |||
void createDirectories(const std::string& path); | |||
/** Deletes a directory. | |||
The directory must be empty. Fails silently. | |||
bool createDirectories(const std::string& path); | |||
/** Deletes a file or empty directory. | |||
Returns whether the deletion was successful. | |||
*/ | |||
void removeDirectory(const std::string& path); | |||
/** Deletes a directory if empty and all parent directories that are then empty. | |||
bool remove(const std::string& path); | |||
/** Deletes a file or directory recursively. | |||
Returns the number of files and directories that were deleted. | |||
*/ | |||
void removeDirectories(const std::string& path); | |||
int removeRecursively(const std::string& path); | |||
std::string getWorkingDirectory(); | |||
void setWorkingDirectory(const std::string& path); | |||
std::string getTempDir(); | |||
std::string getAbsolute(const std::string& path); | |||
/** Extracts the parent directory of the path. | |||
Examples: | |||
getDirectory("/var/tmp/example.txt") // "/var/tmp" | |||
getDirectory("/") // "" | |||
getDirectory("/var/tmp/.") // "/var/tmp" | |||
*/ | |||
std::string getDirectory(const std::string& path); | |||
/** Extracts the filename of the path. | |||
Examples: | |||
getFilename("/foo/bar.txt") // "bar.txt" | |||
getFilename("/foo/.bar") // ".bar" | |||
getFilename("/foo/bar/") // "." | |||
getFilename("/foo/.") // "." | |||
getFilename("/foo/..") // ".." | |||
getFilename(".") // "." | |||
getFilename("..") // ".." | |||
getFilename("/") // "/" | |||
*/ | |||
std::string getFilename(const std::string& path); | |||
/** Extracts the portion of a filename without the extension. | |||
Examples: | |||
getExtension("/foo/bar.txt") // "bar" | |||
getExtension("/foo/.bar") // "" | |||
getExtension("/foo/foo.bar.baz.tar") // "foo.bar.baz" | |||
*/ | |||
std::string getStem(const std::string& path); | |||
/** Extracts the extension of a filename, including the dot. | |||
Examples: | |||
getExtension("/foo/bar.txt") // ".txt" | |||
getExtension("/foo/bar.") // "." | |||
getExtension("/foo/bar") // "" | |||
getExtension("/foo/bar.txt/bar.cc") // ".cc" | |||
getExtension("/foo/bar.txt/bar.") // "." | |||
getExtension("/foo/bar.txt/bar") // "" | |||
getExtension("/foo/.") // "" | |||
getExtension("/foo/..") // "" | |||
getExtension("/foo/.hidden") // ".hidden" | |||
*/ | |||
std::string getExtension(const std::string& path); | |||
/** Compresses the contents of a folder (recursively) to an archive. | |||
Currently supports the "ustar zstd" format (.tar.zst) | |||
An equivalent shell command is | |||
tar -cf archivePath --zstd -C folderPath . | |||
*/ | |||
void archiveFolder(const std::string& archivePath, const std::string& folderPath); | |||
/** Extracts an archive into a folder. | |||
An equivalent shell command is | |||
tar -xf archivePath --zstd -C folderPath | |||
*/ | |||
void unarchiveToFolder(const std::string& archivePath, const std::string& folderPath); | |||
// Threading | |||
/** Returns the number of logical simultaneous multithreading (SMT) (e.g. Intel Hyperthreaded) threads on the CPU. */ | |||
int getLogicalCoreCount(); | |||
/** Sets a name of the current thread for debuggers and OS-specific process viewers. */ | |||
void setThreadName(const std::string& name); | |||
// Querying | |||
/** Returns the caller's human-readable stack trace with "\n"-separated lines. */ | |||
std::string getStackTrace(); | |||
/** Returns the current number of nanoseconds since the epoch. | |||
@@ -57,6 +120,10 @@ The goal of this function is to give the most precise (fine-grained) time availa | |||
The epoch is undefined. Do not use this function to get absolute time, as it is different on each OS. | |||
*/ | |||
int64_t getNanoseconds(); | |||
std::string getOperatingSystemInfo(); | |||
// Applications | |||
/** Opens a URL, also happens to work with PDFs and folders. | |||
Shell injection is possible, so make sure the URL is trusted or hard coded. | |||
May block, so open in a new thread. | |||
@@ -68,20 +135,6 @@ void openFolder(const std::string& path); | |||
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.zst) | |||
An equivalent shell command is | |||
tar -cf archivePath --zstd -C 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 | |||
@@ -105,7 +105,7 @@ struct OpenRecentItem : ui::MenuItem { | |||
for (const std::string& path : settings::recentPatchPaths) { | |||
OpenPathItem* item = new OpenPathItem; | |||
item->text = string::filename(path); | |||
item->text = system::getFilename(path); | |||
item->path = path; | |||
menu->addChild(item); | |||
} | |||
@@ -265,12 +265,11 @@ struct ModulePresetItem : ui::MenuItem { | |||
bool hasPresets = false; | |||
// Note: This is not cached, so opening this menu each time might have a bit of latency. | |||
for (const std::string& presetPath : system::getEntries(presetDir)) { | |||
std::string presetFilename = string::filename(presetPath); | |||
if (string::filenameExtension(presetFilename) != "vcvm") | |||
if (system::getExtension(presetPath) != ".vcvm") | |||
continue; | |||
hasPresets = true; | |||
std::string presetName = string::filenameBase(presetFilename); | |||
std::string presetName = system::getStem(presetPath); | |||
// Remove "1_", "42_", "001_", etc at the beginning of preset filenames | |||
std::regex r("^\\d*_"); | |||
presetName = std::regex_replace(presetName, r, ""); | |||
@@ -690,25 +689,22 @@ void ModuleWidget::loadDialog() { | |||
// Delete directories if empty | |||
DEFER({ | |||
system::removeDirectories(presetDir); | |||
system::remove(presetDir); | |||
system::remove(system::getDirectory(presetDir)); | |||
}); | |||
osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS); | |||
DEFER({ | |||
osdialog_filters_free(filters); | |||
}); | |||
DEFER({osdialog_filters_free(filters);}); | |||
char* path = osdialog_file(OSDIALOG_OPEN, presetDir.c_str(), NULL, filters); | |||
if (!path) { | |||
char* pathC = osdialog_file(OSDIALOG_OPEN, presetDir.c_str(), NULL, filters); | |||
if (!pathC) { | |||
// No path selected | |||
return; | |||
} | |||
DEFER({ | |||
free(path); | |||
}); | |||
DEFER({free(pathC);}); | |||
try { | |||
loadAction(path); | |||
loadAction(pathC); | |||
} | |||
catch (Exception& e) { | |||
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what()); | |||
@@ -749,30 +745,26 @@ void ModuleWidget::saveDialog() { | |||
// Delete directories if empty | |||
DEFER({ | |||
system::removeDirectories(presetDir); | |||
// These fail silently if the directories are not empty | |||
system::remove(presetDir); | |||
system::remove(system::getDirectory(presetDir)); | |||
}); | |||
osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS); | |||
DEFER({ | |||
osdialog_filters_free(filters); | |||
}); | |||
DEFER({osdialog_filters_free(filters);}); | |||
char* path = osdialog_file(OSDIALOG_SAVE, presetDir.c_str(), "Untitled.vcvm", filters); | |||
if (!path) { | |||
char* pathC = osdialog_file(OSDIALOG_SAVE, presetDir.c_str(), "Untitled.vcvm", filters); | |||
if (!pathC) { | |||
// No path selected | |||
return; | |||
} | |||
DEFER({ | |||
free(path); | |||
}); | |||
DEFER({free(pathC);}); | |||
std::string pathStr = path; | |||
std::string extension = string::filenameExtension(string::filename(pathStr)); | |||
if (extension == "") { | |||
pathStr += ".vcvm"; | |||
} | |||
std::string path = pathC; | |||
if (system::getExtension(path) == "") | |||
path += ".vcvm"; | |||
save(pathStr); | |||
save(path); | |||
} | |||
template <class T, typename F> | |||
@@ -182,7 +182,7 @@ void Scene::onHoverKey(const event::HoverKey& e) { | |||
void Scene::onPathDrop(const event::PathDrop& e) { | |||
if (e.paths.size() >= 1) { | |||
const std::string& path = e.paths[0]; | |||
if (string::filenameExtension(string::filename(path)) == "vcv") { | |||
if (system::getExtension(path) == ".vcv") { | |||
APP->patch->loadPathDialog(path); | |||
e.consume(this); | |||
return; | |||
@@ -31,12 +31,5 @@ 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_u8(const char* path) { | |||
return _wremove(rack::string::U8toU16(path).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 |
@@ -62,7 +62,7 @@ void PatchManager::save(std::string path) { | |||
INFO("Saving patch %s", path.c_str()); | |||
saveAutosave(); | |||
system::archiveFolder(filesystem::u8path(path), asset::autosavePath); | |||
system::archiveFolder(path, asset::autosavePath); | |||
} | |||
@@ -89,30 +89,26 @@ void PatchManager::saveAsDialog() { | |||
std::string filename; | |||
if (this->path == "") { | |||
dir = asset::user("patches"); | |||
system::createDirectory(dir); | |||
system::createDirectories(dir); | |||
} | |||
else { | |||
dir = string::directory(this->path); | |||
filename = string::filename(this->path); | |||
dir = system::getDirectory(this->path); | |||
filename = system::getFilename(this->path); | |||
} | |||
osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS); | |||
DEFER({ | |||
osdialog_filters_free(filters); | |||
}); | |||
DEFER({osdialog_filters_free(filters);}); | |||
char* pathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters); | |||
if (!pathC) { | |||
// Fail silently | |||
// Cancel silently | |||
return; | |||
} | |||
DEFER({ | |||
std::free(pathC); | |||
}); | |||
DEFER({std::free(pathC);}); | |||
// Append .vcv extension if no extension was given. | |||
std::string path = pathC; | |||
if (string::filenameExtension(string::filename(path)) == "") { | |||
if (system::getExtension(path) == "") { | |||
path += ".vcv"; | |||
} | |||
@@ -150,9 +146,7 @@ void PatchManager::saveAutosave() { | |||
json_t* rootJ = toJson(); | |||
if (!rootJ) | |||
return; | |||
DEFER({ | |||
json_decref(rootJ); | |||
}); | |||
DEFER({json_decref(rootJ);}); | |||
// Write to temporary path and then rename it to the correct path | |||
system::createDirectories(asset::autosavePath); | |||
@@ -166,7 +160,8 @@ void PatchManager::saveAutosave() { | |||
json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
std::fclose(file); | |||
system::moveFile(tmpPath, patchPath); | |||
system::remove(patchPath); | |||
system::rename(tmpPath, patchPath); | |||
} | |||
@@ -186,17 +181,16 @@ static bool isPatchLegacyPre2(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::removeRecursively(asset::autosavePath); | |||
system::createDirectories(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"); | |||
// Copy the .vcv file directly to "patch.json". | |||
system::copy(path, asset::autosavePath + "/patch.json"); | |||
} | |||
else { | |||
// Extract the .vcv file as a .tar.zst archive. | |||
system::unarchiveToFolder(filesystem::u8path(path), asset::autosavePath); | |||
system::unarchiveToFolder(path, asset::autosavePath); | |||
} | |||
loadAutosave(); | |||
@@ -241,7 +235,7 @@ void PatchManager::loadAutosave() { | |||
FILE* file = std::fopen(patchPath.c_str(), "r"); | |||
if (!file) { | |||
// Exit silently | |||
// TODO Load template | |||
// TODO Load template without causing infinite recursion | |||
return; | |||
} | |||
DEFER({ | |||
@@ -255,9 +249,7 @@ void PatchManager::loadAutosave() { | |||
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); | |||
return; | |||
} | |||
DEFER({ | |||
json_decref(rootJ); | |||
}); | |||
DEFER({json_decref(rootJ);}); | |||
fromJson(rootJ); | |||
} | |||
@@ -288,13 +280,11 @@ void PatchManager::loadDialog() { | |||
system::createDirectory(dir); | |||
} | |||
else { | |||
dir = string::directory(this->path); | |||
dir = system::getDirectory(this->path); | |||
} | |||
osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS); | |||
DEFER({ | |||
osdialog_filters_free(filters); | |||
}); | |||
DEFER({osdialog_filters_free(filters);}); | |||
char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters); | |||
if (!pathC) { | |||
@@ -44,30 +44,27 @@ namespace plugin { | |||
//////////////////// | |||
static void* loadLibrary(std::string libraryPath) { | |||
#if defined ARCH_WIN | |||
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); | |||
std::wstring libraryFilenameW = string::U8toU16(libraryPath); | |||
HINSTANCE handle = LoadLibraryW(libraryFilenameW.c_str()); | |||
SetErrorMode(0); | |||
if (!handle) { | |||
int error = GetLastError(); | |||
throw Exception(string::f("Failed to load library %s: code %d", libraryPath.c_str(), error)); | |||
} | |||
#else | |||
// Plugin uses -rpath=. so change working directory so it can find libRack. | |||
std::string cwd = system::getWorkingDirectory(); | |||
system::setWorkingDirectory(asset::systemDir); | |||
// And then change it back | |||
DEFER({ | |||
system::setWorkingDirectory(cwd); | |||
}); | |||
// Load library with dlopen | |||
void* handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL); | |||
if (!handle) { | |||
throw Exception(string::f("Failed to load library %s: %s", libraryPath.c_str(), dlerror())); | |||
} | |||
#endif | |||
return handle; | |||
#if defined ARCH_WIN | |||
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); | |||
std::wstring libraryFilenameW = string::U8toU16(libraryPath); | |||
HINSTANCE handle = LoadLibraryW(libraryFilenameW.c_str()); | |||
SetErrorMode(0); | |||
if (!handle) { | |||
int error = GetLastError(); | |||
throw Exception(string::f("Failed to load library %s: code %d", libraryPath.c_str(), error)); | |||
} | |||
#else | |||
// As of Rack v2.0, plugins are linked with `-rpath=.` so change current directory so it can find libRack. | |||
std::string cwd = system::getWorkingDirectory(); | |||
system::setWorkingDirectory(asset::systemDir); | |||
// Change it back when we're finished | |||
DEFER({system::setWorkingDirectory(cwd);}); | |||
// Load library with dlopen | |||
void* handle = dlopen(libraryPath.c_str(), RTLD_NOW | RTLD_LOCAL); | |||
if (!handle) | |||
throw Exception(string::f("Failed to load library %s: %s", libraryPath.c_str(), dlerror())); | |||
#endif | |||
return handle; | |||
} | |||
typedef void (*InitCallback)(Plugin*); | |||
@@ -85,9 +82,8 @@ static InitCallback loadPluginCallback(Plugin* plugin) { | |||
std::string libraryPath = plugin->path + "/plugin." + libraryExt; | |||
// Check file existence | |||
if (!system::isFile(libraryPath)) { | |||
throw Exception(string::f("Library %s does not exist", libraryPath.c_str())); | |||
} | |||
if (!system::isFile(libraryPath)) | |||
throw Exception(string::f("Plugin binary not found at %s", libraryPath.c_str())); | |||
// Load dynamic/shared library | |||
plugin->handle = loadLibrary(libraryPath); | |||
@@ -99,9 +95,8 @@ static InitCallback loadPluginCallback(Plugin* plugin) { | |||
#else | |||
initCallback = (InitCallback) dlsym(plugin->handle, "init"); | |||
#endif | |||
if (!initCallback) { | |||
if (!initCallback) | |||
throw Exception(string::f("Failed to read init() symbol in %s", libraryPath.c_str())); | |||
} | |||
return initCallback; | |||
} | |||
@@ -130,22 +125,16 @@ static Plugin* loadPlugin(std::string path) { | |||
// Load plugin.json | |||
std::string manifestFilename = (path == "") ? asset::system("Core.json") : (path + "/plugin.json"); | |||
FILE* file = fopen(manifestFilename.c_str(), "r"); | |||
if (!file) { | |||
FILE* file = std::fopen(manifestFilename.c_str(), "r"); | |||
if (!file) | |||
throw Exception(string::f("Manifest file %s does not exist", manifestFilename.c_str())); | |||
} | |||
DEFER({ | |||
fclose(file); | |||
}); | |||
DEFER({std::fclose(file);}); | |||
json_error_t error; | |||
json_t* rootJ = json_loadf(file, 0, &error); | |||
if (!rootJ) { | |||
if (!rootJ) | |||
throw Exception(string::f("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text)); | |||
} | |||
DEFER({ | |||
json_decref(rootJ); | |||
}); | |||
DEFER({json_decref(rootJ);}); | |||
// Call init callback | |||
InitCallback initCallback; | |||
@@ -162,12 +151,11 @@ static Plugin* loadPlugin(std::string path) { | |||
// Reject plugin if slug already exists | |||
Plugin* oldPlugin = getPlugin(plugin->slug); | |||
if (oldPlugin) { | |||
if (oldPlugin) | |||
throw Exception(string::f("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str())); | |||
} | |||
INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), plugin->path.c_str()); | |||
plugins.push_back(plugin); | |||
INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), plugin->path.c_str()); | |||
} | |||
catch (Exception& e) { | |||
WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | |||
@@ -193,7 +181,7 @@ static void extractPackages(std::string path) { | |||
std::string message; | |||
for (std::string packagePath : system::getEntries(path)) { | |||
if (string::filenameExtension(string::filename(packagePath)) != "zip") | |||
if (system::getExtension(packagePath) != ".zip") | |||
continue; | |||
INFO("Extracting package %s", packagePath.c_str()); | |||
// Extract package | |||
@@ -201,14 +189,12 @@ static void extractPackages(std::string path) { | |||
system::unarchiveToFolder(packagePath, path); | |||
} | |||
catch (Exception& e) { | |||
WARN("Package %s failed to extract: %s", packagePath.c_str(), e.what()); | |||
message += string::f("Could not extract package %s\n", packagePath.c_str()); | |||
WARN("Plugin package %s failed to extract: %s", packagePath.c_str(), e.what()); | |||
message += string::f("Could not extract plugin package %s\n", packagePath.c_str()); | |||
continue; | |||
} | |||
// Remove package | |||
if (remove(packagePath.c_str())) { | |||
WARN("Could not delete file %s: error %d", packagePath.c_str(), errno); | |||
} | |||
system::remove(packagePath.c_str()); | |||
} | |||
if (!message.empty()) { | |||
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); | |||
@@ -87,55 +87,6 @@ bool endsWith(const std::string& str, const std::string& suffix) { | |||
} | |||
std::string directory(const std::string& path) { | |||
char* pathDup = strdup(path.c_str()); | |||
std::string directory = dirname(pathDup); | |||
free(pathDup); | |||
return directory; | |||
} | |||
std::string filename(const std::string& path) { | |||
char* pathDup = strdup(path.c_str()); | |||
std::string filename = basename(pathDup); | |||
free(pathDup); | |||
return filename; | |||
} | |||
std::string filenameBase(const std::string& filename) { | |||
size_t pos = filename.rfind('.'); | |||
if (pos == std::string::npos) | |||
return filename; | |||
return std::string(filename, 0, pos); | |||
} | |||
std::string filenameExtension(const std::string& filename) { | |||
size_t pos = filename.rfind('.'); | |||
if (pos == std::string::npos) | |||
return ""; | |||
return std::string(filename, pos + 1); | |||
} | |||
std::string absolutePath(const std::string& path) { | |||
#if defined ARCH_LIN || defined ARCH_MAC | |||
char buf[PATH_MAX]; | |||
char* absPathC = realpath(path.c_str(), buf); | |||
if (absPathC) | |||
return absPathC; | |||
#elif defined ARCH_WIN | |||
std::wstring pathW = U8toU16(path); | |||
wchar_t buf[PATH_MAX]; | |||
wchar_t* absPathC = _wfullpath(buf, pathW.c_str(), PATH_MAX); | |||
if (absPathC) | |||
return U16toU8(absPathC); | |||
#endif | |||
return ""; | |||
} | |||
float fuzzyScore(const std::string& s, const std::string& query) { | |||
size_t pos = s.find(query); | |||
if (pos == std::string::npos) | |||
@@ -1,6 +1,7 @@ | |||
#include <thread> | |||
#include <regex> | |||
#include <chrono> | |||
#include <experimental/filesystem> | |||
#include <dirent.h> | |||
#include <sys/stat.h> | |||
@@ -33,39 +34,31 @@ | |||
#include <string.hpp> | |||
namespace rack { | |||
namespace system { | |||
/* | |||
In C++17, this will be `std::filesystem` | |||
Important: When using `fs::path`, always convert strings to UTF-8 using | |||
fs::path p = fs::u8path(s); | |||
std::list<std::string> getEntries(const std::string& path) { | |||
std::list<std::string> filenames; | |||
DIR* dir = opendir(path.c_str()); | |||
if (dir) { | |||
struct dirent* d; | |||
while ((d = readdir(dir))) { | |||
std::string filename = d->d_name; | |||
if (filename == "." || filename == "..") | |||
continue; | |||
filenames.push_back(path + "/" + filename); | |||
} | |||
closedir(dir); | |||
} | |||
filenames.sort(); | |||
return filenames; | |||
} | |||
In fact, it's best to work only with strings to avoid forgetting to decode a string as UTF-8. | |||
The need to do this is a fatal flaw of `fs::path`, but at least `std::filesystem` has some helpful operations. | |||
*/ | |||
namespace fs = std::experimental::filesystem; | |||
std::list<std::string> getEntriesRecursive(const std::string &path, int depth) { | |||
std::list<std::string> entries = getEntries(path); | |||
if (depth > 0) { | |||
// Don't iterate using iterators because the list will be growing. | |||
size_t limit = entries.size(); | |||
auto it = entries.begin(); | |||
for (size_t i = 0; i < limit; i++) { | |||
const std::string &entry = *it++; | |||
if (isDirectory(entry)) { | |||
std::list<std::string> subEntries = getEntriesRecursive(entry, depth - 1); | |||
// Append subEntries to entries | |||
namespace rack { | |||
namespace system { | |||
std::list<std::string> getEntries(const std::string& dirPath, int depth) { | |||
std::list<std::string> entries; | |||
for (auto& entry : fs::directory_iterator(fs::u8path(dirPath))) { | |||
std::string subEntry = entry.path().u8string(); | |||
entries.push_back(subEntry); | |||
// Recurse if depth > 0 (limited recursion) or depth < 0 (infinite recursion). | |||
if (depth != 0) { | |||
if (fs::is_directory(entry.path())) { | |||
std::list<std::string> subEntries = getEntries(subEntry, depth - 1); | |||
entries.splice(entries.end(), subEntries); | |||
} | |||
} | |||
@@ -74,311 +67,113 @@ std::list<std::string> getEntriesRecursive(const std::string &path, int depth) { | |||
} | |||
bool isFile(const std::string& path) { | |||
struct stat statbuf; | |||
if (stat(path.c_str(), &statbuf)) | |||
return false; | |||
return S_ISREG(statbuf.st_mode); | |||
bool doesExist(const std::string& path) { | |||
return fs::exists(fs::u8path(path)); | |||
} | |||
bool isDirectory(const std::string& path) { | |||
struct stat statbuf; | |||
if (stat(path.c_str(), &statbuf)) | |||
return false; | |||
return S_ISDIR(statbuf.st_mode); | |||
bool isFile(const std::string& path) { | |||
return fs::is_regular_file(fs::u8path(path)); | |||
} | |||
void moveFile(const std::string& srcPath, const std::string& destPath) { | |||
std::remove(destPath.c_str()); | |||
// Whether this overwrites existing files is implementation-defined. | |||
// i.e. Mingw64 fails to overwrite. | |||
// This is why we remove the file above. | |||
std::rename(srcPath.c_str(), destPath.c_str()); | |||
bool isDirectory(const std::string& path) { | |||
return fs::is_directory(fs::u8path(path)); | |||
} | |||
void copyFile(const std::string& srcPath, const std::string& destPath) { | |||
// Open source | |||
FILE* source = fopen(srcPath.c_str(), "rb"); | |||
if (!source) | |||
return; | |||
DEFER({ | |||
fclose(source); | |||
}); | |||
// Open destination | |||
FILE* dest = fopen(destPath.c_str(), "wb"); | |||
if (!dest) | |||
return; | |||
DEFER({ | |||
fclose(dest); | |||
}); | |||
// Copy buffer | |||
const int bufferSize = (1 << 15); | |||
char buffer[bufferSize]; | |||
while (1) { | |||
size_t size = fread(buffer, 1, bufferSize, source); | |||
if (size == 0) | |||
break; | |||
size = fwrite(buffer, 1, size, dest); | |||
if (size == 0) | |||
break; | |||
} | |||
uint64_t getFileSize(const std::string& path) { | |||
return fs::file_size(fs::u8path(path)); | |||
} | |||
void createDirectory(const std::string& path) { | |||
#if defined ARCH_WIN | |||
std::wstring pathW = string::U8toU16(path); | |||
_wmkdir(pathW.c_str()); | |||
#else | |||
mkdir(path.c_str(), 0755); | |||
#endif | |||
void rename(const std::string& srcPath, const std::string& destPath) { | |||
fs::rename(fs::u8path(srcPath), fs::u8path(destPath)); | |||
} | |||
void createDirectories(const std::string& path) { | |||
for (size_t i = 1; i < path.size(); i++) { | |||
char c = path[i]; | |||
if (c == '/' || c == '\\') | |||
createDirectory(path.substr(0, i)); | |||
} | |||
createDirectory(path); | |||
void copy(const std::string& srcPath, const std::string& destPath) { | |||
fs::copy(fs::u8path(srcPath), fs::u8path(destPath), fs::copy_options::recursive); | |||
} | |||
void removeDirectory(const std::string& path) { | |||
#if defined ARCH_WIN | |||
std::wstring pathW = string::U8toU16(path); | |||
_wrmdir(pathW.c_str()); | |||
#else | |||
rmdir(path.c_str()); | |||
#endif | |||
bool createDirectory(const std::string& path) { | |||
return fs::create_directory(fs::u8path(path)); | |||
} | |||
void removeDirectories(const std::string& path) { | |||
removeDirectory(path); | |||
for (size_t i = path.size() - 1; i >= 1; i--) { | |||
char c = path[i]; | |||
if (c == '/' || c == '\\') | |||
removeDirectory(path.substr(0, i)); | |||
} | |||
bool createDirectories(const std::string& path) { | |||
return fs::create_directories(fs::u8path(path)); | |||
} | |||
std::string getWorkingDirectory() { | |||
#if defined ARCH_WIN | |||
wchar_t buf[4096] = L""; | |||
GetCurrentDirectory(sizeof(buf), buf); | |||
return string::U16toU8(buf); | |||
#else | |||
char buf[4096] = ""; | |||
getcwd(buf, sizeof(buf)); | |||
return buf; | |||
#endif | |||
bool remove(const std::string& path) { | |||
return fs::remove(fs::u8path(path)); | |||
} | |||
void setWorkingDirectory(const std::string& path) { | |||
#if defined ARCH_WIN | |||
std::wstring pathW = string::U8toU16(path); | |||
SetCurrentDirectory(pathW.c_str()); | |||
#else | |||
chdir(path.c_str()); | |||
#endif | |||
} | |||
int getLogicalCoreCount() { | |||
return std::thread::hardware_concurrency(); | |||
int removeRecursively(const std::string& path) { | |||
return fs::remove_all(fs::u8path(path)); | |||
} | |||
void setThreadName(const std::string& name) { | |||
#if defined ARCH_LIN | |||
pthread_setname_np(pthread_self(), name.c_str()); | |||
#elif defined ARCH_WIN | |||
// Unsupported on Windows | |||
#endif | |||
std::string getWorkingDirectory() { | |||
return fs::current_path().u8string(); | |||
} | |||
std::string getStackTrace() { | |||
int stackLen = 128; | |||
void* stack[stackLen]; | |||
std::string s; | |||
#if defined ARCH_LIN || defined ARCH_MAC | |||
stackLen = backtrace(stack, stackLen); | |||
char** strings = backtrace_symbols(stack, stackLen); | |||
// Skip the first line because it's this function. | |||
for (int i = 1; i < stackLen; i++) { | |||
s += string::f("%d: ", stackLen - i - 1); | |||
std::string line = strings[i]; | |||
#if 0 | |||
// Parse line | |||
std::regex r(R"((.*)\((.*)\+(.*)\) (.*))"); | |||
std::smatch match; | |||
if (std::regex_search(line, match, r)) { | |||
s += match[1].str(); | |||
s += "("; | |||
std::string symbol = match[2].str(); | |||
// Demangle symbol | |||
char* symbolD = __cxxabiv1::__cxa_demangle(symbol.c_str(), NULL, NULL, NULL); | |||
if (symbolD) { | |||
symbol = symbolD; | |||
free(symbolD); | |||
} | |||
s += symbol; | |||
s += "+"; | |||
s += match[3].str(); | |||
s += ")"; | |||
} | |||
#else | |||
s += line; | |||
#endif | |||
s += "\n"; | |||
} | |||
free(strings); | |||
#elif defined ARCH_WIN | |||
HANDLE process = GetCurrentProcess(); | |||
SymInitialize(process, NULL, true); | |||
stackLen = CaptureStackBackTrace(0, stackLen, stack, NULL); | |||
SYMBOL_INFO* symbol = (SYMBOL_INFO*) calloc(sizeof(SYMBOL_INFO) + 256, 1); | |||
symbol->MaxNameLen = 255; | |||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO); | |||
for (int i = 1; i < stackLen; i++) { | |||
SymFromAddr(process, (DWORD64) stack[i], 0, symbol); | |||
s += string::f("%d: %s 0x%0x\n", stackLen - i - 1, symbol->Name, symbol->Address); | |||
} | |||
free(symbol); | |||
#endif | |||
return s; | |||
void setWorkingDirectory(const std::string& path) { | |||
fs::current_path(fs::u8path(path)); | |||
} | |||
int64_t getNanoseconds() { | |||
#if defined ARCH_WIN | |||
LARGE_INTEGER counter; | |||
QueryPerformanceCounter(&counter); | |||
LARGE_INTEGER frequency; | |||
QueryPerformanceFrequency(&frequency); | |||
// TODO Check if this is always an integer factor on all CPUs | |||
int64_t nsPerTick = 1000000000LL / frequency.QuadPart; | |||
int64_t time = counter.QuadPart * nsPerTick; | |||
return time; | |||
#endif | |||
#if defined ARCH_LIN | |||
struct timespec ts; | |||
clock_gettime(CLOCK_MONOTONIC_RAW, &ts); | |||
int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec; | |||
return time; | |||
#endif | |||
#if defined ARCH_MAC | |||
using clock = std::chrono::high_resolution_clock; | |||
using time_point = std::chrono::time_point<clock>; | |||
time_point now = clock::now(); | |||
using duration = std::chrono::duration<int64_t, std::nano>; | |||
duration d = now.time_since_epoch(); | |||
return d.count(); | |||
#endif | |||
std::string getTempDir() { | |||
return fs::temp_directory_path().u8string(); | |||
} | |||
void openBrowser(const std::string& url) { | |||
#if defined ARCH_LIN | |||
std::string command = "xdg-open \"" + url + "\""; | |||
(void) std::system(command.c_str()); | |||
#endif | |||
#if defined ARCH_MAC | |||
std::string command = "open \"" + url + "\""; | |||
std::system(command.c_str()); | |||
#endif | |||
#if defined ARCH_WIN | |||
std::wstring urlW = string::U8toU16(url); | |||
ShellExecuteW(NULL, L"open", urlW.c_str(), NULL, NULL, SW_SHOWDEFAULT); | |||
#endif | |||
std::string getAbsolutePath(const std::string& path) { | |||
return fs::absolute(fs::u8path(path)).u8string(); | |||
} | |||
void openFolder(const std::string& path) { | |||
#if defined ARCH_LIN | |||
std::string command = "xdg-open \"" + path + "\""; | |||
(void) std::system(command.c_str()); | |||
#endif | |||
#if defined ARCH_MAC | |||
std::string command = "open \"" + path + "\""; | |||
std::system(command.c_str()); | |||
#endif | |||
#if defined ARCH_WIN | |||
std::wstring pathW = string::U8toU16(path); | |||
ShellExecuteW(NULL, L"explore", pathW.c_str(), NULL, NULL, SW_SHOWDEFAULT); | |||
#endif | |||
std::string getDirectory(const std::string& path) { | |||
return fs::u8path(path).parent_path().u8string(); | |||
} | |||
void runProcessDetached(const std::string& path) { | |||
#if defined ARCH_WIN | |||
SHELLEXECUTEINFOW shExInfo; | |||
ZeroMemory(&shExInfo, sizeof(shExInfo)); | |||
shExInfo.cbSize = sizeof(shExInfo); | |||
shExInfo.lpVerb = L"runas"; | |||
std::string getFilename(const std::string& path) { | |||
return fs::u8path(path).filename().u8string(); | |||
} | |||
std::wstring pathW = string::U8toU16(path); | |||
shExInfo.lpFile = pathW.c_str(); | |||
shExInfo.nShow = SW_SHOW; | |||
if (ShellExecuteExW(&shExInfo)) { | |||
// Do nothing | |||
} | |||
#else | |||
// Not implemented on Linux or Mac | |||
assert(0); | |||
#endif | |||
std::string getStem(const std::string& path) { | |||
return fs::u8path(path).stem().u8string(); | |||
} | |||
std::string getOperatingSystemInfo() { | |||
#if defined ARCH_LIN || defined ARCH_MAC | |||
struct utsname u; | |||
uname(&u); | |||
return string::f("%s %s %s %s", u.sysname, u.release, u.version, u.machine); | |||
#elif defined ARCH_WIN | |||
OSVERSIONINFOW info; | |||
ZeroMemory(&info, sizeof(info)); | |||
info.dwOSVersionInfoSize = sizeof(info); | |||
GetVersionExW(&info); | |||
// See https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoa for a list of Windows version numbers. | |||
return string::f("Windows %u.%u", info.dwMajorVersion, info.dwMinorVersion); | |||
#endif | |||
std::string getExtension(const std::string& path) { | |||
return fs::u8path(path).extension().u8string(); | |||
} | |||
/** Behaves like `std::filesystem::relative()`. | |||
/** Returns `p` in relative path form, relative to `base` | |||
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()) | |||
static std::string getRelativePath(std::string path, std::string base) { | |||
path = fs::absolute(fs::u8path(path)).generic_u8string(); | |||
base = fs::absolute(fs::u8path(base)).generic_u8string(); | |||
if (path.size() < base.size()) | |||
throw Exception("getRelativePath() error: path is shorter than base"); | |||
if (!std::equal(baseStr.begin(), baseStr.end(), pStr.begin())) | |||
if (!std::equal(base.begin(), base.end(), path.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()); | |||
// If path == base, this correctly returns "." | |||
return "." + std::string(path.begin() + base.size(), path.end()); | |||
} | |||
void archiveFolder(const filesystem::path& archivePath, const filesystem::path& folderPath) { | |||
void archiveFolder(const std::string& archivePath, const std::string& folderPath) { | |||
// Based on minitar.c create() in libarchive examples | |||
int r; | |||
@@ -388,24 +183,24 @@ void archiveFolder(const filesystem::path& archivePath, const filesystem::path& | |||
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()); | |||
r = archive_write_open_filename_w(a, string::U8toU16(archivePath).c_str()); | |||
#else | |||
r = archive_write_open_filename(a, archivePath.generic_u8string().c_str()); | |||
r = archive_write_open_filename(a, archivePath.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))); | |||
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);}); | |||
#if defined ARCH_WIN | |||
r = archive_read_disk_open_w(disk, folderPath.generic_wstring().c_str()); | |||
r = archive_read_disk_open_w(disk, string::U8toU16(folderPath).c_str()); | |||
#else | |||
r = archive_read_disk_open(disk, folderPath.generic_u8string().c_str()); | |||
r = archive_read_disk_open(disk, folderPath.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))); | |||
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 | |||
@@ -423,17 +218,18 @@ void archiveFolder(const filesystem::path& archivePath, const filesystem::path& | |||
archive_read_disk_descend(disk); | |||
// Convert absolute path to relative path | |||
filesystem::path entryPath = | |||
std::string entryPath; | |||
#if defined ARCH_WIN | |||
archive_entry_pathname_w(entry); | |||
entryPath = string::U16toU8(archive_entry_pathname_w(entry)); | |||
#else | |||
archive_entry_pathname(entry); | |||
entryPath = archive_entry_pathname(entry); | |||
#endif | |||
entryPath = getRelativePath(entryPath, folderPath); | |||
#if defined ARCH_WIN | |||
archive_entry_copy_pathname_w(entry, entryPath.generic_wstring().c_str()); | |||
// FIXME This doesn't seem to set UTF-8 paths on Windows. | |||
archive_entry_copy_pathname_w(entry, string::U8toU16(entryPath).c_str()); | |||
#else | |||
archive_entry_set_pathname(entry, entryPath.generic_u8string().c_str()); | |||
archive_entry_set_pathname(entry, entryPath.c_str()); | |||
#endif | |||
// Write file to archive | |||
@@ -443,11 +239,11 @@ void archiveFolder(const filesystem::path& archivePath, const filesystem::path& | |||
// Manually copy data | |||
#if defined ARCH_WIN | |||
filesystem::path entrySourcePath = archive_entry_sourcepath_w(entry); | |||
std::string entrySourcePath = string::U16toU8(archive_entry_sourcepath_w(entry)); | |||
#else | |||
filesystem::path entrySourcePath = archive_entry_sourcepath(entry); | |||
std::string entrySourcePath = archive_entry_sourcepath(entry); | |||
#endif | |||
FILE* f = std::fopen(entrySourcePath.generic_u8string().c_str(), "rb"); | |||
FILE* f = std::fopen(entrySourcePath.c_str(), "rb"); | |||
DEFER({std::fclose(f);}); | |||
char buf[1 << 14]; | |||
ssize_t len; | |||
@@ -458,7 +254,7 @@ void archiveFolder(const filesystem::path& archivePath, const filesystem::path& | |||
} | |||
void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::path& folderPath) { | |||
void unarchiveToFolder(const std::string& archivePath, const std::string& folderPath) { | |||
// Based on minitar.c extract() in libarchive examples | |||
int r; | |||
@@ -470,12 +266,12 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa | |||
archive_read_support_format_tar(a); | |||
// archive_read_support_format_all(a); | |||
#if defined ARCH_WIN | |||
r = archive_read_open_filename_w(a, archivePath.generic_wstring().c_str(), 1 << 14); | |||
r = archive_read_open_filename_w(a, string::U8toU16(archivePath).c_str(), 1 << 14); | |||
#else | |||
r = archive_read_open_filename(a, archivePath.generic_u8string().c_str(), 1 << 14); | |||
r = archive_read_open_filename(a, archivePath.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))); | |||
throw Exception(string::f("unzipToFolder() could not open archive %s: %s", archivePath.c_str(), archive_error_string(a))); | |||
DEFER({archive_read_close(a);}); | |||
// Open folder for writing | |||
@@ -496,14 +292,14 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa | |||
throw Exception(string::f("unzipToFolder() could not read entry from archive: %s", archive_error_string(a))); | |||
// 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.generic_u8string().c_str())); | |||
entryPath = filesystem::absolute(entryPath, folderPath); | |||
std::string entryPath = archive_entry_pathname(entry); | |||
if (!fs::u8path(entryPath).is_relative()) | |||
throw Exception(string::f("unzipToFolder() does not support absolute paths: %s", entryPath.c_str())); | |||
entryPath = fs::absolute(fs::u8path(entryPath), fs::u8path(folderPath)).u8string(); | |||
#if defined ARCH_WIN | |||
archive_entry_copy_pathname_w(entry, entryPath.generic_wstring().c_str()); | |||
archive_entry_copy_pathname_w(entry, string::U8toU16(entryPath).c_str()); | |||
#else | |||
archive_entry_set_pathname(entry, entryPath.generic_u8string().c_str()); | |||
archive_entry_set_pathname(entry, entryPath.c_str()); | |||
#endif | |||
// Write entry to disk | |||
@@ -537,5 +333,175 @@ void unarchiveToFolder(const filesystem::path& archivePath, const filesystem::pa | |||
} | |||
int getLogicalCoreCount() { | |||
return std::thread::hardware_concurrency(); | |||
} | |||
void setThreadName(const std::string& name) { | |||
#if defined ARCH_LIN | |||
pthread_setname_np(pthread_self(), name.c_str()); | |||
#elif defined ARCH_WIN | |||
// Unsupported on Windows | |||
#endif | |||
} | |||
std::string getStackTrace() { | |||
int stackLen = 128; | |||
void* stack[stackLen]; | |||
std::string s; | |||
#if defined ARCH_LIN || defined ARCH_MAC | |||
stackLen = backtrace(stack, stackLen); | |||
char** strings = backtrace_symbols(stack, stackLen); | |||
// Skip the first line because it's this function. | |||
for (int i = 1; i < stackLen; i++) { | |||
s += string::f("%d: ", stackLen - i - 1); | |||
std::string line = strings[i]; | |||
#if 0 | |||
// Parse line | |||
std::regex r(R"((.*)\((.*)\+(.*)\) (.*))"); | |||
std::smatch match; | |||
if (std::regex_search(line, match, r)) { | |||
s += match[1].str(); | |||
s += "("; | |||
std::string symbol = match[2].str(); | |||
// Demangle symbol | |||
char* symbolD = __cxxabiv1::__cxa_demangle(symbol.c_str(), NULL, NULL, NULL); | |||
if (symbolD) { | |||
symbol = symbolD; | |||
free(symbolD); | |||
} | |||
s += symbol; | |||
s += "+"; | |||
s += match[3].str(); | |||
s += ")"; | |||
} | |||
#else | |||
s += line; | |||
#endif | |||
s += "\n"; | |||
} | |||
free(strings); | |||
#elif defined ARCH_WIN | |||
HANDLE process = GetCurrentProcess(); | |||
SymInitialize(process, NULL, true); | |||
stackLen = CaptureStackBackTrace(0, stackLen, stack, NULL); | |||
SYMBOL_INFO* symbol = (SYMBOL_INFO*) calloc(sizeof(SYMBOL_INFO) + 256, 1); | |||
symbol->MaxNameLen = 255; | |||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO); | |||
for (int i = 1; i < stackLen; i++) { | |||
SymFromAddr(process, (DWORD64) stack[i], 0, symbol); | |||
s += string::f("%d: %s 0x%0x\n", stackLen - i - 1, symbol->Name, symbol->Address); | |||
} | |||
free(symbol); | |||
#endif | |||
return s; | |||
} | |||
int64_t getNanoseconds() { | |||
#if defined ARCH_WIN | |||
LARGE_INTEGER counter; | |||
QueryPerformanceCounter(&counter); | |||
LARGE_INTEGER frequency; | |||
QueryPerformanceFrequency(&frequency); | |||
// TODO Check if this is always an integer factor on all CPUs | |||
int64_t nsPerTick = 1000000000LL / frequency.QuadPart; | |||
int64_t time = counter.QuadPart * nsPerTick; | |||
return time; | |||
#endif | |||
#if defined ARCH_LIN | |||
struct timespec ts; | |||
clock_gettime(CLOCK_MONOTONIC_RAW, &ts); | |||
int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec; | |||
return time; | |||
#endif | |||
#if defined ARCH_MAC | |||
using clock = std::chrono::high_resolution_clock; | |||
using time_point = std::chrono::time_point<clock>; | |||
time_point now = clock::now(); | |||
using duration = std::chrono::duration<int64_t, std::nano>; | |||
duration d = now.time_since_epoch(); | |||
return d.count(); | |||
#endif | |||
} | |||
std::string getOperatingSystemInfo() { | |||
#if defined ARCH_LIN || defined ARCH_MAC | |||
struct utsname u; | |||
uname(&u); | |||
return string::f("%s %s %s %s", u.sysname, u.release, u.version, u.machine); | |||
#elif defined ARCH_WIN | |||
OSVERSIONINFOW info; | |||
ZeroMemory(&info, sizeof(info)); | |||
info.dwOSVersionInfoSize = sizeof(info); | |||
GetVersionExW(&info); | |||
// See https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoa for a list of Windows version numbers. | |||
return string::f("Windows %u.%u", info.dwMajorVersion, info.dwMinorVersion); | |||
#endif | |||
} | |||
void openBrowser(const std::string& url) { | |||
#if defined ARCH_LIN | |||
std::string command = "xdg-open \"" + url + "\""; | |||
(void) std::system(command.c_str()); | |||
#endif | |||
#if defined ARCH_MAC | |||
std::string command = "open \"" + url + "\""; | |||
std::system(command.c_str()); | |||
#endif | |||
#if defined ARCH_WIN | |||
std::wstring urlW = string::U8toU16(url); | |||
ShellExecuteW(NULL, L"open", urlW.c_str(), NULL, NULL, SW_SHOWDEFAULT); | |||
#endif | |||
} | |||
void openFolder(const std::string& path) { | |||
#if defined ARCH_LIN | |||
std::string command = "xdg-open \"" + path + "\""; | |||
(void) std::system(command.c_str()); | |||
#endif | |||
#if defined ARCH_MAC | |||
std::string command = "open \"" + path + "\""; | |||
std::system(command.c_str()); | |||
#endif | |||
#if defined ARCH_WIN | |||
std::wstring pathW = string::U8toU16(path); | |||
ShellExecuteW(NULL, L"explore", pathW.c_str(), NULL, NULL, SW_SHOWDEFAULT); | |||
#endif | |||
} | |||
void runProcessDetached(const std::string& path) { | |||
#if defined ARCH_WIN | |||
SHELLEXECUTEINFOW shExInfo; | |||
ZeroMemory(&shExInfo, sizeof(shExInfo)); | |||
shExInfo.cbSize = sizeof(shExInfo); | |||
shExInfo.lpVerb = L"runas"; | |||
std::wstring pathW = string::U8toU16(path); | |||
shExInfo.lpFile = pathW.c_str(); | |||
shExInfo.nShow = SW_SHOW; | |||
if (ShellExecuteExW(&shExInfo)) { | |||
// Do nothing | |||
} | |||
#else | |||
// Not implemented on Linux or Mac | |||
assert(0); | |||
#endif | |||
} | |||
} // namespace system | |||
} // namespace rack |
@@ -64,7 +64,8 @@ void update() { | |||
return; | |||
// Download update | |||
std::string filename = string::filename(network::urlPath(downloadUrl)); | |||
// HACK getFilename is only supposed to be used for filesystem paths, not URLs. | |||
std::string filename = system::getFilename(network::urlPath(downloadUrl)); | |||
std::string path = asset::user(filename); | |||
INFO("Downloading update %s to %s", downloadUrl.c_str(), path.c_str()); | |||
network::requestDownload(downloadUrl, path, &progress); | |||
@@ -374,7 +374,7 @@ void Window::run() { | |||
windowTitle += " - "; | |||
if (!APP->history->isSaved()) | |||
windowTitle += "*"; | |||
windowTitle += string::filename(APP->patch->path); | |||
windowTitle += system::getFilename(APP->patch->path); | |||
} | |||
if (windowTitle != internal->lastWindowTitle) { | |||
glfwSetWindowTitle(win, windowTitle.c_str()); | |||