| @@ -10,16 +10,37 @@ struct Plugin; | |||
| } // namespace plugin | |||
| namespace engine { | |||
| struct Module; | |||
| } // namespace engine | |||
| namespace asset { | |||
| void init(); | |||
| /** Returns the path of a system resource. Should only read files from this location. */ | |||
| std::string system(std::string filename); | |||
| /** Returns the path of a user resource. Can read and write files to this location. */ | |||
| std::string user(std::string filename); | |||
| /** Returns the path of a resource in the plugin's folder. Should only read files from this location. */ | |||
| std::string plugin(plugin::Plugin* plugin, std::string filename); | |||
| /** Returns the path of a system asset. Should only read files from this location. */ | |||
| std::string system(std::string filename = ""); | |||
| /** Returns the path of a user asset. Can read and write files to this location. */ | |||
| std::string user(std::string filename = ""); | |||
| /** Returns the path of a asset in the plugin's folder. | |||
| Plugin assets should be read-only by plugins. | |||
| Examples: | |||
| asset::plugin(pluginInstance, "samples/00.wav") // "/path/to/Rack/user/folder/plugins/MyPlugin/samples/00.wav" | |||
| */ | |||
| std::string plugin(plugin::Plugin* plugin, std::string filename = ""); | |||
| /** Returns the path to an asset in the module patch folder. | |||
| The module patch folder is *not* created automatically. Before creating files at these paths, call | |||
| system::createDirectories(asset::module(module)) | |||
| Examples: | |||
| asset::module(module, "recordings/00.wav") // "/path/to/Rack/user/folder/autosave/modules/1234/recordings/00.wav" | |||
| */ | |||
| std::string module(engine::Module* module, const std::string& filename = ""); | |||
| // Set these before calling init() to override the default paths | |||
| @@ -73,7 +73,7 @@ struct Plugin { | |||
| ~Plugin(); | |||
| void addModel(Model* model); | |||
| Model* getModel(std::string slug); | |||
| Model* getModel(const std::string& slug); | |||
| void fromJson(json_t* rootJ); | |||
| std::string getBrand(); | |||
| }; | |||
| @@ -14,6 +14,15 @@ namespace system { | |||
| // Filesystem | |||
| /** Joins two paths with a directory separator. | |||
| If `path2` is an empty string, returns `path1`. | |||
| */ | |||
| std::string join(const std::string& path1, const std::string& path2 = ""); | |||
| /** Join an arbitrary number of paths, from left to right. */ | |||
| template <typename... Paths> | |||
| std::string join(const std::string& path1, const std::string& path2, Paths... paths) { | |||
| return join(join(path1, path2), paths...); | |||
| } | |||
| /** 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. | |||
| */ | |||
| @@ -674,7 +674,7 @@ void ModuleWidget::loadAction(std::string filename) { | |||
| } | |||
| void ModuleWidget::loadTemplate() { | |||
| std::string templatePath = model->getUserPresetDir() + "/" + "template.vcvm"; | |||
| std::string templatePath = system::join(model->getUserPresetDir(), "template.vcvm"); | |||
| try { | |||
| load(templatePath); | |||
| } | |||
| @@ -735,7 +735,7 @@ void ModuleWidget::saveTemplate() { | |||
| std::string presetDir = model->getUserPresetDir(); | |||
| system::createDirectories(presetDir); | |||
| std::string templatePath = presetDir + "/" + "template.vcvm"; | |||
| std::string templatePath = system::join(presetDir, "template.vcvm"); | |||
| save(templatePath); | |||
| } | |||
| @@ -21,6 +21,7 @@ | |||
| #include <settings.hpp> | |||
| #include <string.hpp> | |||
| #include <plugin/Plugin.hpp> | |||
| #include <engine/Module.hpp> | |||
| #include <app/common.hpp> | |||
| @@ -29,7 +30,7 @@ namespace asset { | |||
| static void initSystemDir() { | |||
| if (systemDir != "") | |||
| if (!systemDir.empty()) | |||
| return; | |||
| if (settings::devMode) { | |||
| @@ -76,7 +77,7 @@ static void initSystemDir() { | |||
| static void initUserDir() { | |||
| if (userDir != "") | |||
| if (!userDir.empty()) | |||
| return; | |||
| if (settings::devMode) { | |||
| @@ -89,15 +90,13 @@ static void initUserDir() { | |||
| wchar_t documentsBufW[MAX_PATH] = L"."; | |||
| HRESULT result = SHGetFolderPathW(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, documentsBufW); | |||
| assert(result == S_OK); | |||
| userDir = string::U16toU8(documentsBufW); | |||
| userDir += "/Rack"; | |||
| userDir = system::join(string::U16toU8(documentsBufW), "Rack"); | |||
| #endif | |||
| #if defined ARCH_MAC | |||
| // Get home directory | |||
| struct passwd* pw = getpwuid(getuid()); | |||
| assert(pw); | |||
| userDir = pw->pw_dir; | |||
| userDir += "/Documents/Rack"; | |||
| userDir = system::join(pw->pw_dir, "Documents/Rack"); | |||
| #endif | |||
| #if defined ARCH_LIN | |||
| // Get home directory | |||
| @@ -107,8 +106,7 @@ static void initUserDir() { | |||
| assert(pw); | |||
| homeBuf = pw->pw_dir; | |||
| } | |||
| userDir = homeBuf; | |||
| userDir += "/.Rack"; | |||
| userDir = system::join(homeBuf, ".Rack"); | |||
| #endif | |||
| } | |||
| @@ -120,34 +118,40 @@ void init() { | |||
| // Set paths | |||
| if (settings::devMode) { | |||
| pluginsPath = userDir + "/plugins"; | |||
| settingsPath = userDir + "/settings.json"; | |||
| autosavePath = userDir + "/autosave"; | |||
| templatePath = userDir + "/template.vcv"; | |||
| pluginsPath = system::join(userDir, "plugins"); | |||
| settingsPath = system::join(userDir, "settings.json"); | |||
| autosavePath = system::join(userDir, "autosave"); | |||
| templatePath = system::join(userDir, "template.vcv"); | |||
| } | |||
| else { | |||
| logPath = userDir + "/log.txt"; | |||
| pluginsPath = userDir + "/plugins-v" + ABI_VERSION; | |||
| settingsPath = userDir + "/settings-v" + ABI_VERSION + ".json"; | |||
| autosavePath = userDir + "/autosave-v" + ABI_VERSION; | |||
| templatePath = userDir + "/template-v" + ABI_VERSION + ".vcv"; | |||
| logPath = system::join(userDir, "log.txt"); | |||
| pluginsPath = system::join(userDir, "plugins-v" + ABI_VERSION); | |||
| settingsPath = system::join(userDir, "settings-v" + ABI_VERSION + ".json"); | |||
| autosavePath = system::join(userDir, "autosave-v" + ABI_VERSION); | |||
| templatePath = system::join(userDir, "template-v" + ABI_VERSION + ".vcv"); | |||
| } | |||
| } | |||
| std::string system(std::string filename) { | |||
| return systemDir + "/" + filename; | |||
| return system::join(systemDir, filename); | |||
| } | |||
| std::string user(std::string filename) { | |||
| return userDir + "/" + filename; | |||
| return system::join(userDir, filename); | |||
| } | |||
| std::string plugin(plugin::Plugin* plugin, std::string filename) { | |||
| assert(plugin); | |||
| return plugin->path + "/" + filename; | |||
| return system::join(plugin->path, filename); | |||
| } | |||
| std::string module(engine::Module* module, const std::string& filename) { | |||
| assert(module); | |||
| return system::join(autosavePath, "modules", std::to_string(module->id), filename); | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| #include <engine/Module.hpp> | |||
| #include <plugin.hpp> | |||
| #include <asset.hpp> | |||
| namespace rack { | |||
| @@ -153,7 +153,7 @@ void PatchManager::saveAutosave() { | |||
| // Write to temporary path and then rename it to the correct path | |||
| system::createDirectories(asset::autosavePath); | |||
| std::string patchPath = asset::autosavePath + "/patch.json"; | |||
| std::string patchPath = system::join(asset::autosavePath, "patch.json"); | |||
| std::string tmpPath = patchPath + ".tmp"; | |||
| FILE* file = std::fopen(tmpPath.c_str(), "w"); | |||
| if (!file) { | |||
| @@ -189,7 +189,7 @@ void PatchManager::load(std::string path) { | |||
| if (isPatchLegacyPre2(path)) { | |||
| // Copy the .vcv file directly to "patch.json". | |||
| system::copy(path, asset::autosavePath + "/patch.json"); | |||
| system::copy(path, system::join(asset::autosavePath, "patch.json")); | |||
| } | |||
| else { | |||
| // Extract the .vcv file as a .tar.zst archive. | |||
| @@ -237,7 +237,7 @@ void PatchManager::loadTemplateDialog() { | |||
| void PatchManager::loadAutosave() { | |||
| INFO("Loading autosave"); | |||
| std::string patchPath = asset::autosavePath + "/patch.json"; | |||
| std::string patchPath = system::join(asset::autosavePath, "patch.json"); | |||
| FILE* file = std::fopen(patchPath.c_str(), "r"); | |||
| if (!file) { | |||
| // Exit silently | |||
| @@ -79,7 +79,7 @@ static InitCallback loadPluginCallback(Plugin* plugin) { | |||
| #elif ARCH_MAC | |||
| libraryExt = "dylib"; | |||
| #endif | |||
| std::string libraryPath = plugin->path + "/plugin." + libraryExt; | |||
| std::string libraryPath = system::join(plugin->path, "plugin." + libraryExt); | |||
| // Check file existence | |||
| if (!system::isFile(libraryPath)) | |||
| @@ -124,7 +124,7 @@ static Plugin* loadPlugin(std::string path) { | |||
| } | |||
| // Load plugin.json | |||
| std::string manifestFilename = (path == "") ? asset::system("Core.json") : (path + "/plugin.json"); | |||
| std::string manifestFilename = (path == "") ? asset::system("Core.json") : system::join(path, "plugin.json"); | |||
| FILE* file = std::fopen(manifestFilename.c_str(), "r"); | |||
| if (!file) | |||
| throw Exception(string::f("Manifest file %s does not exist", manifestFilename.c_str())); | |||
| @@ -226,7 +226,7 @@ void init() { | |||
| #else | |||
| std::string fundamentalSrc = asset::system("Fundamental.zip"); | |||
| #endif | |||
| std::string fundamentalDir = asset::pluginsPath + "/Fundamental"; | |||
| std::string fundamentalDir = system::join(asset::pluginsPath, "Fundamental"); | |||
| if (!settings::devMode && !getPlugin("Fundamental") && system::isFile(fundamentalSrc)) { | |||
| INFO("Extracting bundled Fundamental package"); | |||
| system::unarchiveToFolder(fundamentalSrc.c_str(), asset::pluginsPath.c_str()); | |||
| @@ -471,7 +471,7 @@ void syncUpdate(Update* update) { | |||
| INFO("Downloading plugin %s %s %s", update->pluginSlug.c_str(), update->version.c_str(), APP_ARCH.c_str()); | |||
| // Download zip | |||
| std::string pluginDest = asset::pluginsPath + "/" + update->pluginSlug + ".zip"; | |||
| std::string pluginDest = system::join(asset::pluginsPath, update->pluginSlug + ".zip"); | |||
| if (!network::requestDownload(downloadUrl, pluginDest, &update->progress, cookies)) { | |||
| WARN("Plugin %s download was unsuccessful", update->pluginSlug.c_str()); | |||
| return; | |||
| @@ -59,12 +59,12 @@ std::string Model::getFullName() { | |||
| std::string Model::getFactoryPresetDir() { | |||
| return asset::plugin(plugin, "presets/" + slug); | |||
| return asset::plugin(plugin, system::join("presets", slug)); | |||
| } | |||
| std::string Model::getUserPresetDir() { | |||
| return asset::user("presets/" + plugin->slug + "/" + slug); | |||
| return asset::user(system::join("presets", plugin->slug, slug)); | |||
| } | |||
| @@ -22,7 +22,7 @@ void Plugin::addModel(Model* model) { | |||
| models.push_back(model); | |||
| } | |||
| Model* Plugin::getModel(std::string slug) { | |||
| Model* Plugin::getModel(const std::string& slug) { | |||
| for (Model* model : models) { | |||
| if (model->slug == slug) { | |||
| return model; | |||
| @@ -50,11 +50,16 @@ namespace rack { | |||
| namespace system { | |||
| std::string join(const std::string& path1, const std::string& path2) { | |||
| return (fs::u8path(path1) / fs::u8path(path2)).generic_u8string(); | |||
| } | |||
| std::list<std::string> getEntries(const std::string& dirPath, int depth) { | |||
| try { | |||
| std::list<std::string> entries; | |||
| for (auto& entry : fs::directory_iterator(fs::u8path(dirPath))) { | |||
| std::string subEntry = entry.path().u8string(); | |||
| std::string subEntry = entry.path().generic_u8string(); | |||
| entries.push_back(subEntry); | |||
| // Recurse if depth > 0 (limited recursion) or depth < 0 (infinite recursion). | |||
| if (depth != 0) { | |||
| @@ -174,7 +179,7 @@ int removeRecursively(const std::string& path) { | |||
| std::string getWorkingDirectory() { | |||
| try { | |||
| return fs::current_path().u8string(); | |||
| return fs::current_path().generic_u8string(); | |||
| } | |||
| catch (fs::filesystem_error& e) { | |||
| throw Exception(e.what()); | |||
| @@ -194,7 +199,7 @@ void setWorkingDirectory(const std::string& path) { | |||
| std::string getTempDir() { | |||
| try { | |||
| return fs::temp_directory_path().u8string(); | |||
| return fs::temp_directory_path().generic_u8string(); | |||
| } | |||
| catch (fs::filesystem_error& e) { | |||
| throw Exception(e.what()); | |||
| @@ -204,7 +209,7 @@ std::string getTempDir() { | |||
| std::string getAbsolute(const std::string& path) { | |||
| try { | |||
| return fs::absolute(fs::u8path(path)).u8string(); | |||
| return fs::absolute(fs::u8path(path)).generic_u8string(); | |||
| } | |||
| catch (fs::filesystem_error& e) { | |||
| throw Exception(e.what()); | |||
| @@ -214,7 +219,7 @@ std::string getAbsolute(const std::string& path) { | |||
| std::string getCanonical(const std::string& path) { | |||
| try { | |||
| return fs::canonical(fs::u8path(path)).u8string(); | |||
| return fs::canonical(fs::u8path(path)).generic_u8string(); | |||
| } | |||
| catch (fs::filesystem_error& e) { | |||
| throw Exception(e.what()); | |||
| @@ -224,7 +229,7 @@ std::string getCanonical(const std::string& path) { | |||
| std::string getDirectory(const std::string& path) { | |||
| try { | |||
| return fs::u8path(path).parent_path().u8string(); | |||
| return fs::u8path(path).parent_path().generic_u8string(); | |||
| } | |||
| catch (fs::filesystem_error& e) { | |||
| throw Exception(e.what()); | |||
| @@ -234,7 +239,7 @@ std::string getDirectory(const std::string& path) { | |||
| std::string getFilename(const std::string& path) { | |||
| try { | |||
| return fs::u8path(path).filename().u8string(); | |||
| return fs::u8path(path).filename().generic_u8string(); | |||
| } | |||
| catch (fs::filesystem_error& e) { | |||
| throw Exception(e.what()); | |||
| @@ -244,7 +249,7 @@ std::string getFilename(const std::string& path) { | |||
| std::string getStem(const std::string& path) { | |||
| try { | |||
| return fs::u8path(path).stem().u8string(); | |||
| return fs::u8path(path).stem().generic_u8string(); | |||
| } | |||
| catch (fs::filesystem_error& e) { | |||
| throw Exception(e.what()); | |||
| @@ -254,7 +259,7 @@ std::string getStem(const std::string& path) { | |||
| std::string getExtension(const std::string& path) { | |||
| try { | |||
| return fs::u8path(path).extension().u8string(); | |||
| return fs::u8path(path).extension().generic_u8string(); | |||
| } | |||
| catch (fs::filesystem_error& e) { | |||
| throw Exception(e.what()); | |||
| @@ -410,7 +415,7 @@ void unarchiveToFolder(const std::string& archivePath, const std::string& folder | |||
| 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(); | |||
| entryPath = fs::absolute(fs::u8path(entryPath), fs::u8path(folderPath)).generic_u8string(); | |||
| #if defined ARCH_WIN | |||
| archive_entry_copy_pathname_w(entry, string::U8toU16(entryPath).c_str()); | |||
| #else | |||
| @@ -76,7 +76,7 @@ void update() { | |||
| system::runProcessDetached(path); | |||
| #elif defined ARCH_MAC | |||
| std::string cmd; | |||
| // std::string appPath = asset::userDir + "/Rack.app"; | |||
| // std::string appPath = system::join(asset::userDir, "Rack.app"); | |||
| // cmd = "rm -rf '" + appPath + "'"; | |||
| // std::system(cmd.c_str()); | |||
| // // Unzip app using Apple's unzipper, since Rack's unzipper doesn't handle the metadata stuff correctly. | |||
| @@ -434,10 +434,10 @@ void Window::screenshot(float zoom) { | |||
| std::string screenshotsDir = asset::user("screenshots"); | |||
| system::createDirectory(screenshotsDir); | |||
| for (plugin::Plugin* p : plugin::plugins) { | |||
| std::string dir = screenshotsDir + "/" + p->slug; | |||
| std::string dir = system::join(screenshotsDir, p->slug); | |||
| system::createDirectory(dir); | |||
| for (plugin::Model* model : p->models) { | |||
| std::string filename = dir + "/" + model->slug + ".png"; | |||
| std::string filename = system::join(dir, model->slug + ".png"); | |||
| // Skip model if screenshot already exists | |||
| if (system::isFile(filename)) | |||
| continue; | |||
| @@ -468,9 +468,9 @@ void Window::screenshot(float zoom) { | |||
| for (int y = 0; y < height / 2; y++) { | |||
| int flipY = height - y - 1; | |||
| uint8_t tmp[width * 4]; | |||
| memcpy(tmp, &data[y * width * 4], width * 4); | |||
| memcpy(&data[y * width * 4], &data[flipY * width * 4], width * 4); | |||
| memcpy(&data[flipY * width * 4], tmp, width * 4); | |||
| std::memcpy(tmp, &data[y * width * 4], width * 4); | |||
| std::memcpy(&data[y * width * 4], &data[flipY * width * 4], width * 4); | |||
| std::memcpy(&data[flipY * width * 4], tmp, width * 4); | |||
| } | |||
| // Write pixels to PNG | |||