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