| @@ -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()); | |||