@@ -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 | // 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 fopen fopen_u8 | ||||
#define remove remove_u8 | |||||
#define rename rename_u8 | |||||
extern "C" { | extern "C" { | ||||
FILE* fopen_u8(const char* filename, const char* mode); | 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_u8; | using ::fopen_u8; | ||||
using ::remove_u8; | |||||
using ::rename_u8; | |||||
} | } | ||||
#endif | #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 startsWith(const std::string& str, const std::string& prefix); | ||||
bool endsWith(const std::string& str, const std::string& suffix); | 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. | /** Scores how well a query matches a string. | ||||
A score of 0 means no match. | A score of 0 means no match. | ||||
The score is arbitrary and is only meaningful for sorting. | 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); | std::wstring U8toU16(const std::string& s); | ||||
#endif | #endif | ||||
} // namespace string | } // namespace string | ||||
} // namespace rack | } // namespace rack |
@@ -2,54 +2,117 @@ | |||||
#include <list> | #include <list> | ||||
#include <common.hpp> | #include <common.hpp> | ||||
#include <experimental/filesystem> | |||||
namespace rack { | namespace rack { | ||||
// In C++17, this will be `std::filesystem` | |||||
namespace filesystem = std::experimental::filesystem; | |||||
/** Cross-platform functions for operating systems routines | /** Cross-platform functions for operating systems routines | ||||
*/ | */ | ||||
namespace system { | 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. */ | /** Returns whether the given path is a file. */ | ||||
bool isFile(const std::string& path); | bool isFile(const std::string& path); | ||||
/** Returns whether the given path is a directory. */ | /** Returns whether the given path is a directory. */ | ||||
bool isDirectory(const std::string& path); | 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. | /** Creates a directory. | ||||
The parent directory must exist. | The parent directory must exist. | ||||
*/ | */ | ||||
void createDirectory(const std::string& path); | |||||
bool createDirectory(const std::string& path); | |||||
/** Creates all directories up to the 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(); | std::string getWorkingDirectory(); | ||||
void setWorkingDirectory(const std::string& path); | 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. */ | /** Returns the number of logical simultaneous multithreading (SMT) (e.g. Intel Hyperthreaded) threads on the CPU. */ | ||||
int getLogicalCoreCount(); | int getLogicalCoreCount(); | ||||
/** Sets a name of the current thread for debuggers and OS-specific process viewers. */ | /** Sets a name of the current thread for debuggers and OS-specific process viewers. */ | ||||
void setThreadName(const std::string& name); | void setThreadName(const std::string& name); | ||||
// Querying | |||||
/** Returns the caller's human-readable stack trace with "\n"-separated lines. */ | /** Returns the caller's human-readable stack trace with "\n"-separated lines. */ | ||||
std::string getStackTrace(); | std::string getStackTrace(); | ||||
/** Returns the current number of nanoseconds since the epoch. | /** 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. | The epoch is undefined. Do not use this function to get absolute time, as it is different on each OS. | ||||
*/ | */ | ||||
int64_t getNanoseconds(); | int64_t getNanoseconds(); | ||||
std::string getOperatingSystemInfo(); | |||||
// Applications | |||||
/** Opens a URL, also happens to work with PDFs and folders. | /** 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. | Shell injection is possible, so make sure the URL is trusted or hard coded. | ||||
May block, so open in a new thread. | 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. | 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(); | |||||
/** 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 | } // namespace system | ||||
@@ -105,7 +105,7 @@ struct OpenRecentItem : ui::MenuItem { | |||||
for (const std::string& path : settings::recentPatchPaths) { | for (const std::string& path : settings::recentPatchPaths) { | ||||
OpenPathItem* item = new OpenPathItem; | OpenPathItem* item = new OpenPathItem; | ||||
item->text = string::filename(path); | |||||
item->text = system::getFilename(path); | |||||
item->path = path; | item->path = path; | ||||
menu->addChild(item); | menu->addChild(item); | ||||
} | } | ||||
@@ -265,12 +265,11 @@ struct ModulePresetItem : ui::MenuItem { | |||||
bool hasPresets = false; | bool hasPresets = false; | ||||
// Note: This is not cached, so opening this menu each time might have a bit of latency. | // 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)) { | 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; | continue; | ||||
hasPresets = true; | 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 | // Remove "1_", "42_", "001_", etc at the beginning of preset filenames | ||||
std::regex r("^\\d*_"); | std::regex r("^\\d*_"); | ||||
presetName = std::regex_replace(presetName, r, ""); | presetName = std::regex_replace(presetName, r, ""); | ||||
@@ -690,25 +689,22 @@ void ModuleWidget::loadDialog() { | |||||
// Delete directories if empty | // Delete directories if empty | ||||
DEFER({ | DEFER({ | ||||
system::removeDirectories(presetDir); | |||||
system::remove(presetDir); | |||||
system::remove(system::getDirectory(presetDir)); | |||||
}); | }); | ||||
osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS); | 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 | // No path selected | ||||
return; | return; | ||||
} | } | ||||
DEFER({ | |||||
free(path); | |||||
}); | |||||
DEFER({free(pathC);}); | |||||
try { | try { | ||||
loadAction(path); | |||||
loadAction(pathC); | |||||
} | } | ||||
catch (Exception& e) { | catch (Exception& e) { | ||||
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what()); | osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what()); | ||||
@@ -749,30 +745,26 @@ void ModuleWidget::saveDialog() { | |||||
// Delete directories if empty | // Delete directories if empty | ||||
DEFER({ | 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); | 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 | // No path selected | ||||
return; | 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> | template <class T, typename F> | ||||
@@ -182,7 +182,7 @@ void Scene::onHoverKey(const event::HoverKey& e) { | |||||
void Scene::onPathDrop(const event::PathDrop& e) { | void Scene::onPathDrop(const event::PathDrop& e) { | ||||
if (e.paths.size() >= 1) { | if (e.paths.size() >= 1) { | ||||
const std::string& path = e.paths[0]; | const std::string& path = e.paths[0]; | ||||
if (string::filenameExtension(string::filename(path)) == "vcv") { | |||||
if (system::getExtension(path) == ".vcv") { | |||||
APP->patch->loadPathDialog(path); | APP->patch->loadPathDialog(path); | ||||
e.consume(this); | e.consume(this); | ||||
return; | 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()); | 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 | #endif |
@@ -62,7 +62,7 @@ void PatchManager::save(std::string path) { | |||||
INFO("Saving patch %s", path.c_str()); | INFO("Saving patch %s", path.c_str()); | ||||
saveAutosave(); | saveAutosave(); | ||||
system::archiveFolder(filesystem::u8path(path), asset::autosavePath); | |||||
system::archiveFolder(path, asset::autosavePath); | |||||
} | } | ||||
@@ -89,30 +89,26 @@ void PatchManager::saveAsDialog() { | |||||
std::string filename; | std::string filename; | ||||
if (this->path == "") { | if (this->path == "") { | ||||
dir = asset::user("patches"); | dir = asset::user("patches"); | ||||
system::createDirectory(dir); | |||||
system::createDirectories(dir); | |||||
} | } | ||||
else { | 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); | 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); | char* pathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters); | ||||
if (!pathC) { | if (!pathC) { | ||||
// Fail silently | |||||
// Cancel silently | |||||
return; | return; | ||||
} | } | ||||
DEFER({ | |||||
std::free(pathC); | |||||
}); | |||||
DEFER({std::free(pathC);}); | |||||
// Append .vcv extension if no extension was given. | // Append .vcv extension if no extension was given. | ||||
std::string path = pathC; | std::string path = pathC; | ||||
if (string::filenameExtension(string::filename(path)) == "") { | |||||
if (system::getExtension(path) == "") { | |||||
path += ".vcv"; | path += ".vcv"; | ||||
} | } | ||||
@@ -150,9 +146,7 @@ void PatchManager::saveAutosave() { | |||||
json_t* rootJ = toJson(); | json_t* rootJ = toJson(); | ||||
if (!rootJ) | if (!rootJ) | ||||
return; | return; | ||||
DEFER({ | |||||
json_decref(rootJ); | |||||
}); | |||||
DEFER({json_decref(rootJ);}); | |||||
// Write to temporary path and then rename it to the correct path | // Write to temporary path and then rename it to the correct path | ||||
system::createDirectories(asset::autosavePath); | system::createDirectories(asset::autosavePath); | ||||
@@ -166,7 +160,8 @@ void PatchManager::saveAutosave() { | |||||
json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | ||||
std::fclose(file); | 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) { | 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::create_directories(asset::autosavePath); | |||||
system::removeRecursively(asset::autosavePath); | |||||
system::createDirectories(asset::autosavePath); | |||||
if (isPatchLegacyPre2(path)) { | 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 { | else { | ||||
// Extract the .vcv file as a .tar.zst archive. | // Extract the .vcv file as a .tar.zst archive. | ||||
system::unarchiveToFolder(filesystem::u8path(path), asset::autosavePath); | |||||
system::unarchiveToFolder(path, asset::autosavePath); | |||||
} | } | ||||
loadAutosave(); | loadAutosave(); | ||||
@@ -241,7 +235,7 @@ void PatchManager::loadAutosave() { | |||||
FILE* file = std::fopen(patchPath.c_str(), "r"); | FILE* file = std::fopen(patchPath.c_str(), "r"); | ||||
if (!file) { | if (!file) { | ||||
// Exit silently | // Exit silently | ||||
// TODO Load template | |||||
// TODO Load template without causing infinite recursion | |||||
return; | return; | ||||
} | } | ||||
DEFER({ | DEFER({ | ||||
@@ -255,9 +249,7 @@ void PatchManager::loadAutosave() { | |||||
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); | osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); | ||||
return; | return; | ||||
} | } | ||||
DEFER({ | |||||
json_decref(rootJ); | |||||
}); | |||||
DEFER({json_decref(rootJ);}); | |||||
fromJson(rootJ); | fromJson(rootJ); | ||||
} | } | ||||
@@ -288,13 +280,11 @@ void PatchManager::loadDialog() { | |||||
system::createDirectory(dir); | system::createDirectory(dir); | ||||
} | } | ||||
else { | else { | ||||
dir = string::directory(this->path); | |||||
dir = system::getDirectory(this->path); | |||||
} | } | ||||
osdialog_filters* filters = osdialog_filters_parse(PATCH_FILTERS); | 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); | char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters); | ||||
if (!pathC) { | if (!pathC) { | ||||
@@ -44,30 +44,27 @@ namespace plugin { | |||||
//////////////////// | //////////////////// | ||||
static void* loadLibrary(std::string libraryPath) { | 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*); | typedef void (*InitCallback)(Plugin*); | ||||
@@ -85,9 +82,8 @@ static InitCallback loadPluginCallback(Plugin* plugin) { | |||||
std::string libraryPath = plugin->path + "/plugin." + libraryExt; | std::string libraryPath = plugin->path + "/plugin." + libraryExt; | ||||
// Check file existence | // 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 | // Load dynamic/shared library | ||||
plugin->handle = loadLibrary(libraryPath); | plugin->handle = loadLibrary(libraryPath); | ||||
@@ -99,9 +95,8 @@ static InitCallback loadPluginCallback(Plugin* plugin) { | |||||
#else | #else | ||||
initCallback = (InitCallback) dlsym(plugin->handle, "init"); | initCallback = (InitCallback) dlsym(plugin->handle, "init"); | ||||
#endif | #endif | ||||
if (!initCallback) { | |||||
if (!initCallback) | |||||
throw Exception(string::f("Failed to read init() symbol in %s", libraryPath.c_str())); | throw Exception(string::f("Failed to read init() symbol in %s", libraryPath.c_str())); | ||||
} | |||||
return initCallback; | return initCallback; | ||||
} | } | ||||
@@ -130,22 +125,16 @@ static Plugin* loadPlugin(std::string path) { | |||||
// Load plugin.json | // Load plugin.json | ||||
std::string manifestFilename = (path == "") ? asset::system("Core.json") : (path + "/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())); | 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_error_t error; | ||||
json_t* rootJ = json_loadf(file, 0, &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)); | 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 | // Call init callback | ||||
InitCallback initCallback; | InitCallback initCallback; | ||||
@@ -162,12 +151,11 @@ static Plugin* loadPlugin(std::string path) { | |||||
// Reject plugin if slug already exists | // Reject plugin if slug already exists | ||||
Plugin* oldPlugin = getPlugin(plugin->slug); | 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())); | 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); | 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) { | catch (Exception& e) { | ||||
WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | 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; | std::string message; | ||||
for (std::string packagePath : system::getEntries(path)) { | for (std::string packagePath : system::getEntries(path)) { | ||||
if (string::filenameExtension(string::filename(packagePath)) != "zip") | |||||
if (system::getExtension(packagePath) != ".zip") | |||||
continue; | continue; | ||||
INFO("Extracting package %s", packagePath.c_str()); | INFO("Extracting package %s", packagePath.c_str()); | ||||
// Extract package | // Extract package | ||||
@@ -201,14 +189,12 @@ static void extractPackages(std::string path) { | |||||
system::unarchiveToFolder(packagePath, path); | system::unarchiveToFolder(packagePath, path); | ||||
} | } | ||||
catch (Exception& e) { | 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; | continue; | ||||
} | } | ||||
// Remove package | // 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()) { | if (!message.empty()) { | ||||
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); | 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) { | float fuzzyScore(const std::string& s, const std::string& query) { | ||||
size_t pos = s.find(query); | size_t pos = s.find(query); | ||||
if (pos == std::string::npos) | if (pos == std::string::npos) | ||||
@@ -1,6 +1,7 @@ | |||||
#include <thread> | #include <thread> | ||||
#include <regex> | #include <regex> | ||||
#include <chrono> | #include <chrono> | ||||
#include <experimental/filesystem> | |||||
#include <dirent.h> | #include <dirent.h> | ||||
#include <sys/stat.h> | #include <sys/stat.h> | ||||
@@ -33,39 +34,31 @@ | |||||
#include <string.hpp> | #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); | 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. | 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"); | 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"); | 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 | // Based on minitar.c create() in libarchive examples | ||||
int r; | int r; | ||||
@@ -388,24 +183,24 @@ void archiveFolder(const filesystem::path& archivePath, const filesystem::path& | |||||
archive_write_set_format_ustar(a); | archive_write_set_format_ustar(a); | ||||
archive_write_add_filter_zstd(a); | archive_write_add_filter_zstd(a); | ||||
#if defined ARCH_WIN | #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 | #else | ||||
r = archive_write_open_filename(a, archivePath.generic_u8string().c_str()); | |||||
r = archive_write_open_filename(a, archivePath.c_str()); | |||||
#endif | #endif | ||||
if (r < ARCHIVE_OK) | 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);}); | DEFER({archive_write_close(a);}); | ||||
// Open folder for reading | // Open folder for reading | ||||
struct archive* disk = archive_read_disk_new(); | struct archive* disk = archive_read_disk_new(); | ||||
DEFER({archive_read_free(disk);}); | DEFER({archive_read_free(disk);}); | ||||
#if defined ARCH_WIN | #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 | #else | ||||
r = archive_read_disk_open(disk, folderPath.generic_u8string().c_str()); | |||||
r = archive_read_disk_open(disk, folderPath.c_str()); | |||||
#endif | #endif | ||||
if (r < ARCHIVE_OK) | 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);}); | DEFER({archive_read_close(a);}); | ||||
// Iterate folder | // Iterate folder | ||||
@@ -423,17 +218,18 @@ void archiveFolder(const filesystem::path& archivePath, const filesystem::path& | |||||
archive_read_disk_descend(disk); | archive_read_disk_descend(disk); | ||||
// Convert absolute path to relative path | // Convert absolute path to relative path | ||||
filesystem::path entryPath = | |||||
std::string entryPath; | |||||
#if defined ARCH_WIN | #if defined ARCH_WIN | ||||
archive_entry_pathname_w(entry); | |||||
entryPath = string::U16toU8(archive_entry_pathname_w(entry)); | |||||
#else | #else | ||||
archive_entry_pathname(entry); | |||||
entryPath = archive_entry_pathname(entry); | |||||
#endif | #endif | ||||
entryPath = getRelativePath(entryPath, folderPath); | entryPath = getRelativePath(entryPath, folderPath); | ||||
#if defined ARCH_WIN | #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 | #else | ||||
archive_entry_set_pathname(entry, entryPath.generic_u8string().c_str()); | |||||
archive_entry_set_pathname(entry, entryPath.c_str()); | |||||
#endif | #endif | ||||
// Write file to archive | // Write file to archive | ||||
@@ -443,11 +239,11 @@ void archiveFolder(const filesystem::path& archivePath, const filesystem::path& | |||||
// Manually copy data | // Manually copy data | ||||
#if defined ARCH_WIN | #if defined ARCH_WIN | ||||
filesystem::path entrySourcePath = archive_entry_sourcepath_w(entry); | |||||
std::string entrySourcePath = string::U16toU8(archive_entry_sourcepath_w(entry)); | |||||
#else | #else | ||||
filesystem::path entrySourcePath = archive_entry_sourcepath(entry); | |||||
std::string entrySourcePath = archive_entry_sourcepath(entry); | |||||
#endif | #endif | ||||
FILE* f = std::fopen(entrySourcePath.generic_u8string().c_str(), "rb"); | |||||
FILE* f = std::fopen(entrySourcePath.c_str(), "rb"); | |||||
DEFER({std::fclose(f);}); | DEFER({std::fclose(f);}); | ||||
char buf[1 << 14]; | char buf[1 << 14]; | ||||
ssize_t len; | 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 | // Based on minitar.c extract() in libarchive examples | ||||
int r; | 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_tar(a); | ||||
// archive_read_support_format_all(a); | // archive_read_support_format_all(a); | ||||
#if defined ARCH_WIN | #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 | #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 | #endif | ||||
if (r < ARCHIVE_OK) | 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);}); | DEFER({archive_read_close(a);}); | ||||
// Open folder for writing | // 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))); | throw Exception(string::f("unzipToFolder() could not read entry from archive: %s", archive_error_string(a))); | ||||
// Convert relative pathname to absolute based on folderPath | // 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 | #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 | #else | ||||
archive_entry_set_pathname(entry, entryPath.generic_u8string().c_str()); | |||||
archive_entry_set_pathname(entry, entryPath.c_str()); | |||||
#endif | #endif | ||||
// Write entry to disk | // 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 system | ||||
} // namespace rack | } // namespace rack |
@@ -64,7 +64,8 @@ void update() { | |||||
return; | return; | ||||
// Download update | // 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); | std::string path = asset::user(filename); | ||||
INFO("Downloading update %s to %s", downloadUrl.c_str(), path.c_str()); | INFO("Downloading update %s to %s", downloadUrl.c_str(), path.c_str()); | ||||
network::requestDownload(downloadUrl, path, &progress); | network::requestDownload(downloadUrl, path, &progress); | ||||
@@ -374,7 +374,7 @@ void Window::run() { | |||||
windowTitle += " - "; | windowTitle += " - "; | ||||
if (!APP->history->isSaved()) | if (!APP->history->isSaved()) | ||||
windowTitle += "*"; | windowTitle += "*"; | ||||
windowTitle += string::filename(APP->patch->path); | |||||
windowTitle += system::getFilename(APP->patch->path); | |||||
} | } | ||||
if (windowTitle != internal->lastWindowTitle) { | if (windowTitle != internal->lastWindowTitle) { | ||||
glfwSetWindowTitle(win, windowTitle.c_str()); | glfwSetWindowTitle(win, windowTitle.c_str()); | ||||