@@ -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 | |||