diff --git a/PiPedalCommon/src/include/json.hpp b/PiPedalCommon/src/include/json.hpp index f47deba..93e9d03 100644 --- a/PiPedalCommon/src/include/json.hpp +++ b/PiPedalCommon/src/include/json.hpp @@ -20,12 +20,9 @@ #pragma once #include #include -#include -#include #include #include #include -#include "HtmlHelper.hpp" #include #include #include @@ -35,6 +32,9 @@ #include #include #include +#include +#include +#include #define DECLARE_JSON_MAP(CLASSNAME) \ static pipedal::json_map::storage_type jmap @@ -228,7 +228,7 @@ namespace pipedal reference(const char *name, MEMBER_TYPE CLASS::*member_pointer) { return new json_member_reference(name, member_pointer); - }; + } template static json_enum_member_reference * enum_reference( @@ -237,14 +237,20 @@ namespace pipedal const ENUM_CONVERTER *converter) { return new json_enum_member_reference(name, member_pointer, converter); - }; + } template static json_conditional_member_reference * conditional_reference(const char *name, MEMBER_TYPE CLASS::*member_pointer, typename JsonConditionFunction::Pointer condition) { return new json_conditional_member_reference(name, member_pointer, condition); - }; + } + + // for cases where the name is not a valid C++ identifier. + #define JSON_MAP_DICTIONARY_REFERENCE(class,name, variable) \ + json_map::reference(name,&class::variable##_), + + }; //---------------------------------------------------------------------------------- @@ -254,7 +260,7 @@ namespace pipedal { template - static std::true_type test(decltype(TYPE::jmap) *) { return std::true_type(); }; + static std::true_type test(decltype(TYPE::jmap) *) { return std::true_type(); } template static std::false_type test(...); @@ -268,7 +274,7 @@ namespace pipedal { template - static std::true_type test() { return std::true_type(); }; + static std::true_type test() { return std::true_type(); } template static std::false_type test(...); @@ -320,7 +326,7 @@ namespace pipedal { os << text; } - using string_view = boost::string_view; + using string_view = std::string_view; json_writer(std::ostream &os, bool compressed = true, bool allowNaN = false) : os(os), compressed(compressed), allowNaN_(allowNaN), indent_level(0) { @@ -1049,6 +1055,7 @@ namespace pipedal if (peek() == ']') { c = get(); + (void)c; break; } T item; @@ -1057,6 +1064,7 @@ namespace pipedal if (peek() == ',') { c = get(); + (void)c; } } *value = std::move(result); @@ -1068,7 +1076,7 @@ namespace pipedal { template - static std::true_type test(decltype(json_reader(std::cin).read((ARG *)nullptr)) *v) { return std::true_type(); }; + static std::true_type test(decltype(json_reader(std::cin).read((ARG *)nullptr)) *v) { return std::true_type(); } template static std::false_type test(...); diff --git a/src/PiLatencyMain.cpp b/src/PiLatencyMain.cpp index 97c8a73..c7e7147 100644 --- a/src/PiLatencyMain.cpp +++ b/src/PiLatencyMain.cpp @@ -32,6 +32,7 @@ #include "AlsaDriver.hpp" #include #include +#include using namespace pipedal; @@ -213,11 +214,11 @@ public: audioDriver->Activate(); - sleep(3); // let audio stabilize. + std::this_thread::sleep_for(std::chrono::milliseconds(3000)); this->SetXruns(0); - sleep(7); // run for a bit. + std::this_thread::sleep_for(std::chrono::milliseconds(7000)); audioDriver->Deactivate(); audioDriver->Close(); diff --git a/src/PresetBundle.cpp b/src/PresetBundle.cpp index b0c93be..7340ad5 100644 --- a/src/PresetBundle.cpp +++ b/src/PresetBundle.cpp @@ -42,6 +42,45 @@ static std::string ToString(const std::vector &value) return std::string(start, end); } +static std::string ToJsonAtomPath(const std::string &path) { + json_variant v = json_variant::make_object();; + auto obj = v.as_object(); + (*obj)["otype_"] = json_variant("Path"); + (*obj)["value"] = json_variant(path); + std::ostringstream ss; + json_writer writer(ss); + writer.write(v); + return ss.str(); +} +static bool TryGetAtomPath(const std::string &atomJson, std::string *outPath) +{ + if (atomJson.empty()) + return false; + std::stringstream ss(atomJson); + json_reader reader(ss); + json_variant vProperty; + reader.read(&vProperty); + if (vProperty.is_object()) + { + auto obj = vProperty.as_object(); + if (obj->contains("otype_") && + (*obj)["otype_"].as_string() == "Path") + { + if (obj->contains("value")) + { + std::string value = (*obj)["value"].as_string(); + if (!value.empty()) + { + *outPath = value; + return true; + } + } + } + } + return false; +} + + static std::vector ToBinary(const std::string &value) { std::vector result(value.length() + 1); @@ -65,6 +104,9 @@ public: void LoadPresets(PiPedalModel &model, const std::string &presetJson) { + pluginUploadDirectory = model.GetPluginUploadDirectory(); + pluginUploadDirectoryString = pluginUploadDirectory.string(); + BankFile bankFile; std::istringstream s(presetJson); @@ -73,12 +115,13 @@ public: reader.read(&bankFile); GatherMediaPaths(model, bankFile); configFiles["bankFile.json"] = presetJson; - - pluginUploadDirectory = model.GetPluginUploadDirectory(); } void LoadPluginPresets(PiPedalModel &model, const std::string pluginPresetJson) { + pluginUploadDirectory = model.GetPluginUploadDirectory(); + pluginUploadDirectoryString = pluginUploadDirectory.string(); + PluginPresets pluginPresets; std::istringstream s(pluginPresetJson); @@ -88,8 +131,6 @@ public: GatherMediaPaths(model, pluginPresets); configFiles["pluginPresets.json"] = pluginPresetJson; - - pluginUploadDirectory = model.GetPluginUploadDirectory(); } virtual ~PresetBundleWriterImpl() noexcept; @@ -98,6 +139,10 @@ public: private: void AddUsedPlugin(PiPedalModel &model, const std::string &pluginUri, const std::string &name); + std::string UnmapPath(const std::string &path); + std::string MapPath(const std::string &path); + bool IsValidMediaPath(const std::string &path); + void GatherMediaPaths(PiPedalModel &model, BankFile &bankFile); void GatherMediaPaths(PiPedalModel &model, PluginPresets &pluginPreset); @@ -107,6 +152,7 @@ private: std::vector> pluginMetadata; std::filesystem::path pluginUploadDirectory; + std::string pluginUploadDirectoryString; }; void PresetBundleWriterImpl::WriteToFile(const std::filesystem::path &filePath) @@ -134,7 +180,14 @@ void PresetBundleWriterImpl::WriteToFile(const std::filesystem::path &filePath) std::filesystem::path sourcePath = this->pluginUploadDirectory / std::filesystem::path(mediaPath); if (std::filesystem::exists(sourcePath)) { - zipFile->WriteFile(zipName, sourcePath); + if (IsValidMediaPath(sourcePath)) // paranoid guard against exfiltration of non-media files. + { + zipFile->WriteFile(zipName, sourcePath); + } + else + { + Lv2Log::warning(SS("Media file is not valid: " << sourcePath)); + } } else { @@ -145,7 +198,6 @@ void PresetBundleWriterImpl::WriteToFile(const std::filesystem::path &filePath) { zipFile->WriteFile(SS(zipName << ".mdata"), metadataPath); } - } zipFile->Close(); } @@ -171,6 +223,54 @@ void PresetBundleWriterImpl::GatherMediaPaths(PiPedalModel &model, BankFile &ban } } } + for (const auto &property : plugin->pathProperties_) + { + std::string path; + if (TryGetAtomPath(property.second, &path)) + { + path = UnmapPath(path); + if (!mediaPaths.contains(path)) + { + mediaPaths.insert(std::move(path)); + } + } + } + for (const auto &snapshot : pedalboard.snapshots()) + { + if (snapshot) + { + for (const auto &value : snapshot->values_) + { + if (value.isEnabled_) + { + const Lv2PluginState &state = value.lv2State_; + for (const auto &v : state.values_) + { + if (v.second.atomType_ == LV2_ATOM__Path) + { + std::string path = ToString(v.second.value_); + if (!mediaPaths.contains(path)) + { + mediaPaths.insert(std::move(path)); + } + } + } + for (const auto &property : value.pathProperties_) + { + std::string path; + if (TryGetAtomPath(property.second, &path)) + { + path = UnmapPath(path); + if (!mediaPaths.contains(path)) + { + mediaPaths.insert(std::move(path)); + } + } + } + } + } + } + } } } } @@ -246,8 +346,8 @@ private: { return false; } - std::string metadataZipFilename = SS(zipFileName <<".mdata"); - std::filesystem::path metadataFilename = SS(filePath.string() <<".mdata"); + std::string metadataZipFilename = SS(zipFileName << ".mdata"); + std::filesystem::path metadataFilename = SS(filePath.string() << ".mdata"); if (!std::filesystem::exists(metadataFilename)) { @@ -256,7 +356,9 @@ private: return true; } return false; - } else { + } + else + { if (!zipFile->FileExists(metadataZipFilename)) { return false; @@ -267,6 +369,12 @@ private: } void RenameMediaFileProperty(const std::string oldName, const std::string &newName); + void RenameState(Lv2PluginState &state, const std::string oldName, const std::string &newName); + void RenamePedalboardItem(PedalboardItem *item, const std::string oldName, const std::string &newName); + void RenamePedalboard(Pedalboard &pedalboard, const std::string oldName, const std::string &newName); + void RenamePreset(PluginPreset &pedalboard, const std::string oldName, const std::string &newName); + void RenameSnapshot(Snapshot *snapshot, const std::string oldName, const std::string &newName); + std::filesystem::path pluginUploadDirectory; // BankFile bankFile; ZipFileReader::ptr zipFile; @@ -274,75 +382,137 @@ private: BankFile bankFile; PluginPresets pluginPresets; }; - -void PresetBundleReaderImpl::RenameMediaFileProperty(const std::string oldName, const std::string &newName) +void PresetBundleReaderImpl::RenameState(Lv2PluginState &state, const std::string oldName, const std::string &newName) { - for (auto &preset : bankFile.presets()) + for (auto &value : state.values_) { - Pedalboard &pedalboard = preset->preset(); - auto items = pedalboard.GetAllPlugins(); - for (auto plugin : items) + if (value.second.atomType_ == LV2_ATOM__Path) { - Lv2PluginState &state = plugin->lv2State(); - for (auto &value : state.values_) + std::string path = ToString(value.second.value_); + if (path == oldName) { - if (value.second.atomType_ == LV2_ATOM__Path) - { - std::string path = ToString(value.second.value_); - if (path == oldName) - { - state.values_.at(value.first).value_ = ToBinary(newName); - } - } + state.values_.at(value.first).value_ = ToBinary(newName); } - std::string fullOldPath = (pluginUploadDirectory / oldName).string(); - for (auto &property : plugin->pathProperties_) - { - std::stringstream ss(property.second); - json_reader reader(ss); - json_variant vProperty; - reader.read(&vProperty); - if (vProperty.is_object()) - { - auto obj = vProperty.as_object(); - if (obj->contains("otype_") && - (*obj)["otype_"].as_string() == "Path") - { - if (obj->contains("value")) - { - std::string value = (*obj)["value"].as_string(); - if (value == fullOldPath) - { - (*obj)["value"] = (pluginUploadDirectory / newName).string(); - std::ostringstream ss; - json_writer writer(ss); - writer.write(vProperty); - plugin->pathProperties_[property.first] = ss.str(); - break; - } - } - } + } + } +} +void PresetBundleReaderImpl::RenamePedalboardItem(PedalboardItem *plugin, const std::string oldName, const std::string &newName) +{ + Lv2PluginState &state = plugin->lv2State(); + RenameState(state, oldName, newName); + + std::vector> propertiesToUpdate; + std::string fullOldPath = (pluginUploadDirectory / oldName).string(); + for (auto &property : plugin->pathProperties_) + { + std::stringstream ss(property.second); + json_reader reader(ss); + json_variant vProperty; + reader.read(&vProperty); + if (vProperty.is_object()) + { + auto obj = vProperty.as_object(); + if (obj->contains("otype_") && + (*obj)["otype_"].as_string() == "Path") + { + if (obj->contains("value")) + { + std::string value = (*obj)["value"].as_string(); + if (value == fullOldPath) + { + (*obj)["value"] = (pluginUploadDirectory / newName).string(); + std::ostringstream ss; + json_writer writer(ss); + writer.write(vProperty); + propertiesToUpdate.push_back(std::make_pair(property.first, ss.str())); + } } } } } - for (auto preset : pluginPresets.presets_) + for (auto &property : propertiesToUpdate) { - Lv2PluginState state = preset.state_; - for (auto &value : state.values_) + plugin->pathProperties_[property.first] = property.second; + } +} + +void PresetBundleReaderImpl::RenameSnapshot(Snapshot *snapshot, const std::string oldName, const std::string &newName) +{ + std::string fullOldPath = (pluginUploadDirectory / oldName).string(); + for (auto &value : snapshot->values_) + { + if (value.isEnabled_) { - if (value.second.atomType_ == LV2_ATOM__Path) + Lv2PluginState &state = value.lv2State_; + RenameState(state, oldName, newName); + + std::vector> propertiesToUpdate; + // Check the path properties in the snapshot value. + for (auto &property : value.pathProperties_) { - std::string path = ToString(value.second.value_); - if (path == oldName) - { - state.values_.at(value.first).value_ = ToBinary(newName); + std::string path; + if (TryGetAtomPath(property.second,&path)) { + if (path == fullOldPath) { + propertiesToUpdate.push_back(std::make_pair(property.first, + ToJsonAtomPath((pluginUploadDirectory / newName).string()))); + } } } + for (auto &property : propertiesToUpdate) + { + value.pathProperties_[property.first] = property.second; + } } } } +void PresetBundleReaderImpl::RenamePedalboard(Pedalboard &pedalboard, const std::string oldName, const std::string &newName) +{ + auto items = pedalboard.GetAllPlugins(); + for (auto pedalboardItem : items) + { + RenamePedalboardItem(pedalboardItem, oldName, newName); + } + for (std::shared_ptr &snapshot : pedalboard.snapshots()) + { + if (snapshot) + { + RenameSnapshot(snapshot.get(), oldName, newName); + } + } +} + +void PresetBundleReaderImpl::RenamePreset(PluginPreset &preset, const std::string oldName, const std::string &newName) +{ + Lv2PluginState state = preset.state_; + for (auto &value : state.values_) + { + if (value.second.atomType_ == LV2_ATOM__Path) + { + std::string path = ToString(value.second.value_); + if (path == oldName) + { + state.values_.at(value.first).value_ = ToBinary(newName); + } + } + } +} + +void PresetBundleReaderImpl::RenameMediaFileProperty(const std::string oldName, const std::string &newName) +{ + // snapshots. + // there should be a dictionary for remapped media files. + // there should be a set for saved media files. + for (auto &preset : bankFile.presets()) + { + Pedalboard &pedalboard = preset->preset(); + RenamePedalboard(pedalboard, oldName, newName); + } + for (auto &preset : pluginPresets.presets_) + { + RenamePreset(preset, oldName, newName); + } +} PresetBundleReader::ptr PresetBundleReader::LoadPresetsFile(PiPedalModel &model, const std::filesystem::path &filePath) { @@ -420,7 +590,7 @@ void PresetBundleReaderImpl::ExtractMediaFile(const std::string &zipFileName) namespace fs = std::filesystem; if (zipFileName.starts_with("media/")) { - if (zipFileName.ends_with(".mdata")) + if (zipFileName.ends_with(".mdata")) { return; } @@ -530,3 +700,33 @@ void PresetBundleWriterImpl::AddUsedPlugin(PiPedalModel &model, const std::strin } } } +std::string PresetBundleWriterImpl::UnmapPath(const std::string &path) +{ + if (path.starts_with(pluginUploadDirectoryString)) + { + return path.substr(pluginUploadDirectory.string().length() + 1); + } + return path; +} +bool PresetBundleWriterImpl::IsValidMediaPath(const std::string &path) +{ + if (path.starts_with(pluginUploadDirectoryString)) + { + if (path.length() >= pluginUploadDirectoryString.length() + 1 && + path[pluginUploadDirectoryString.length()] == '/') + { + return true; + ; + } + } + return false; +} +std::string PresetBundleWriterImpl::MapPath(const std::string &path) +{ + if (!path.starts_with("/")) + { + return pluginUploadDirectory / path; + } + return path; +} + diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp index 63624da..d53e27e 100644 --- a/src/WebServerConfig.cpp +++ b/src/WebServerConfig.cpp @@ -38,6 +38,7 @@ #include "AudioFileMetadataReader.hpp" #include "AudioFiles.hpp" #include "util.hpp" +#include "HtmlHelper.hpp" #define OLD_PRESET_EXTENSION ".piPreset" #define PRESET_EXTENSION ".piPreset" diff --git a/todo.txt b/todo.txt index e62e535..4e3bc90 100644 --- a/todo.txt +++ b/todo.txt @@ -3,7 +3,6 @@ Numeric up-down buttons blown in light theme. Tooltips on touch ui? -pre-cue next decoder stream in big loops. convolution reverb broken.