// Copyright (c) 2022 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" #include "Storage.hpp" #include "PiPedalException.hpp" #include #include "json.hpp" #include #include "Lv2Log.hpp" #include using namespace pipedal; const char *BANK_EXTENSION = ".bank"; const char *BANKS_FILENAME = "index.banks"; #define WIFI_CONFIG_SETTINGS_FILENAME "wifiConfigSettings.json"; #define GOVERNOR_SETTINGS_FILENAME "governorSettings.json"; Storage::Storage() { SetDataRoot("~/var/PiPedal"); } inline bool isSafeCharacter(char c) { return (c >= '0' && c <= '9') | (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '-'; } static int fromhexStrict(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10; // lowercase letters won't round-trip properly. return -1; } std::string Storage::SafeDecodeName(const std::string &name) { std::stringstream s; for (int i = 0; i < name.length(); /**/) { char c = name[i++]; if (c < ' ') throw PiPedalArgumentException("Unsafe file name."); if (c == '+') { s << ' '; } else if (c == '%') { if (i + 2 > name.length()) { throw PiPedalArgumentException("Unsafe file name."); } int v1 = fromhexStrict(name[i]); int v2 = fromhexStrict(name[i + 1]); i += 2; if (v1 == -1 || v2 == -1) throw PiPedalArgumentException("Unsafe file name."); char c = (char)((v1 << 4) + v2); if (isSafeCharacter(c)) { // name won't round-trip because the escape will not be re-encoded. throw PiPedalArgumentException("Unsafe file name."); } s << c; } else { if (isSafeCharacter(c)) { s << c; } else { throw PiPedalArgumentException("Unsafe file name."); } } } return s.str(); } static const char *hex = "0123456789ABCDEF"; std::string Storage::SafeEncodeName(const std::string &name) { std::stringstream s; for (char c : name) { if (c < ' ') c = ' '; bool safe = isSafeCharacter(c); if (safe) { s << c; } else if (c == ' ') { s << '+'; } else { s << '%' << hex[(c >> 4) & 0x0F] << hex[(c)&0x0F]; } } return s.str(); } std::filesystem::path ResolveHomePath(std::filesystem::path path) { if (path.begin() == path.end()) return path; // is the first element "~"? auto it = path.begin(); auto el = (*it); if (el != "~") return path; const char *homeDirectory = nullptr; homeDirectory = getenv("HOME"); if (homeDirectory == nullptr) { homeDirectory = getenv("USERPROFILE"); } std::filesystem::path result = homeDirectory; bool first = true; for (auto i = path.begin(); i != path.end(); ++i) { if (!first) { result /= *i; } first = false; } return result; } void Storage::SetDataRoot(const char *path) { this->dataRoot = ResolveHomePath(path); } void Storage::Initialize() { try { std::filesystem::create_directories(this->GetPresetsDirectory()); std::filesystem::create_directories(this->GetPluginPresetsDirectory()); } catch (const std::exception &e) { throw PiPedalStateException("Can't create presets directory. (" + (std::string)this->GetPresetsDirectory() + ") (" + e.what() + ")"); } LoadPluginPresetIndex(); LoadBankIndex(); LoadCurrentBank(); try { LoadChannelSelection(); } catch (const std::exception &) { } LoadWifiConfigSettings(); LoadGovernorSettings(); } void Storage::LoadBank(int64_t instanceId) { auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId); LoadBankFile(indexEntry.name(), &(this->currentBank)); if (this->bankIndex.selectedBank() != instanceId) { this->bankIndex.selectedBank(instanceId); SaveBankIndex(); } this->LoadPreset(this->currentBank.selectedPreset()); } void Storage::LoadCurrentBank() { LoadBank(bankIndex.selectedBank()); } std::filesystem::path Storage::GetPresetsDirectory() const { return this->dataRoot / "presets"; } std::filesystem::path Storage::GetPluginPresetsDirectory() const { return this->dataRoot / "plugin_presets"; } std::filesystem::path Storage::GetCurrentPresetPath() const { return this->dataRoot / "currentPreset.json"; } std::filesystem::path Storage::GetChannelSelectionFileName() { return this->dataRoot / "JackChannelSelection.json"; } std::filesystem::path Storage::GetIndexFileName() const { return this->GetPresetsDirectory() / BANKS_FILENAME; } std::filesystem::path Storage::GetBankFileName(const std::string &name) const { std::string fileName = SafeEncodeName(name) + BANK_EXTENSION; return this->GetPresetsDirectory() / fileName; } void Storage::LoadBankIndex() { try { auto path = GetIndexFileName(); std::ifstream s; s.open(path); json_reader reader(s); reader.read(&bankIndex); } catch (const std::exception &) { bankIndex.clear(); ReIndex(); if (bankIndex.entries().size() == 0) { currentBank.clear(); PedalBoard defaultPedalBoard = PedalBoard::MakeDefault(); int64_t instanceId = currentBank.addPreset(defaultPedalBoard); currentBank.selectedPreset(instanceId); std::string name = "Default Bank"; SaveBankFile(name, currentBank); int64_t selectedBank = bankIndex.addBank(-1,name); bankIndex.selectedBank(selectedBank); } SaveBankIndex(); } } void Storage::LoadPluginPresetIndex() { try { auto path = GetPluginPresetsDirectory() / "index.json"; std::ifstream s; if (std::filesystem::exists(path)) { s.open(path); json_reader reader(s); reader.read(&pluginPresetIndex); } } catch (const std::exception &) { } } void Storage::SavePluginPresetIndex() { if (pluginPresetIndexChanged) { pluginPresetIndexChanged = false; std::ofstream os; auto path = GetPluginPresetsDirectory() / "index.json"; os.open(path, std::ios_base::trunc); if (os.fail()) { throw PiPedalException(SS("Can't write to " << path)); } json_writer writer(os); writer.write(this->pluginPresetIndex); } } void Storage::SaveBankIndex() { std::ofstream os; os.open(GetIndexFileName(), std::ios_base::trunc); json_writer writer(os); writer.write(this->bankIndex); } void Storage::ReIndex() { for (const auto &dirEntry : std::filesystem::directory_iterator(GetPresetsDirectory())) { if (!dirEntry.is_directory()) { auto path = dirEntry.path(); if (path.extension() == BANK_EXTENSION) { std::string name = SafeDecodeName(path.stem()); bankIndex.addBank(-1,name); } } } if (bankIndex.entries().size() == 0) { CreateBank("Default Bank"); } bankIndex.selectedBank(bankIndex.entries()[0].instanceId()); } void Storage::CreateBank(const std::string &name) { BankFile bankFile; PedalBoard defaultPreset = PedalBoard::MakeDefault(); defaultPreset.name(std::string("Default Preset")); bankFile.addPreset(defaultPreset); int64_t instanceId = bankFile.presets()[0]->instanceId(); bankFile.selectedPreset(instanceId); SaveBankFile(name, bankFile); this->bankIndex.addBank(-1,name); this->SaveBankIndex(); } void Storage::GetBankFile(int64_t instanceId,BankFile *pBank) const { auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId); auto name = indexEntry.name(); std::filesystem::path fileName = GetBankFileName(name); std::ifstream is(fileName); json_reader reader(is); reader.read(pBank); pBank->name(indexEntry.name()); } void Storage::LoadBankFile(const std::string &name, BankFile *pBank) { std::filesystem::path fileName = GetBankFileName(name); std::ifstream is(fileName); json_reader reader(is); reader.read(pBank); } void Storage::SaveBankFile(const std::string &name, const BankFile &bankFile) { std::filesystem::path fileName = GetBankFileName(name); std::filesystem::path backupFile = ((std::string)fileName) + ".$$$"; if (std::filesystem::exists(backupFile)) { std::filesystem::remove(backupFile); } if (std::filesystem::exists(fileName)) { std::filesystem::rename(fileName, backupFile); } try { std::ofstream s; s.open(fileName, std::ios_base::trunc); json_writer writer(s); writer.write(bankFile); if (std::filesystem::exists(backupFile)) { std::filesystem::remove(backupFile); } } catch (const std::exception &e) { std::filesystem::remove(fileName); if (std::filesystem::exists(backupFile)) { std::filesystem::rename(backupFile, fileName); } throw; } } void Storage::SaveCurrentBank() { auto indexEntry = this->bankIndex.getBankIndexEntry(this->bankIndex.selectedBank()); SaveBankFile(indexEntry.name(), this->currentBank); } const PedalBoard &Storage::GetCurrentPreset() { auto &item = currentBank.getItem(currentBank.selectedPreset()); return item.preset(); } bool Storage::LoadPreset(int64_t instanceId) { if (!currentBank.hasItem(instanceId)) return false; if (instanceId != currentBank.selectedPreset()) { currentBank.selectedPreset(instanceId); SaveCurrentBank(); } return true; } void Storage::SaveCurrentPreset(const PedalBoard &pedalBoard) { auto &item = currentBank.getItem(currentBank.selectedPreset()); item.preset(pedalBoard); SaveCurrentBank(); } int64_t Storage::saveCurrentPresetAs(const PedalBoard &pedalBoard, const std::string &name, int64_t saveAfterInstanceId) { PedalBoard newPedalBoard = pedalBoard; newPedalBoard.name(name); int64_t newInstanceId = currentBank.addPreset(newPedalBoard, saveAfterInstanceId); currentBank.selectedPreset(newInstanceId); SaveCurrentBank(); return newInstanceId; } void Storage::SetPresetIndex(const PresetIndex &presets) { // painful because we must move unique_ptrs. std::map *> entries; for (int i = 0; i < this->currentBank.presets().size(); ++i) { auto & preset = this->currentBank.presets()[i]; entries[preset->instanceId()] = &(this->currentBank.presets()[i]); } std::vector*> newPresets; for (size_t i = 0; i < presets.presets().size(); ++i) { std::unique_ptr* p = entries[presets.presets()[i].instanceId()]; if (p == nullptr) { throw PiPedalStateException("Presets do not match the currently loaded bank."); } newPresets.push_back(p); } // we made it this far. So now we know we can rebuild a new BankFile. BankFile bankFile; bankFile.presets().resize(newPresets.size()); for (int i = 0; i < presets.presets().size(); ++i) { std::unique_ptr *oldBankFilePreset = newPresets[i]; bankFile.presets()[i] = std::move((*oldBankFilePreset)); } bankFile.nextInstanceId(currentBank.nextInstanceId()); bankFile.selectedPreset(currentBank.selectedPreset()); this->currentBank = std::move(bankFile); // deleting any stray presets while we're at it. this->SaveCurrentBank(); } void Storage::GetPresetIndex(PresetIndex *pResult) { *pResult = PresetIndex(); pResult->selectedInstanceId(this->currentBank.selectedPreset()); for (auto &item : this->currentBank.presets()) { PresetIndexEntry entry; entry.instanceId(item->instanceId()); entry.name(item->preset().name()); pResult->presets().push_back(entry); } } PedalBoard Storage::GetPreset(int64_t instanceId) const { for (size_t i = 0; i < currentBank.presets().size(); ++i) { if (currentBank.presets()[i]->instanceId() == instanceId) { return currentBank.presets()[i]->preset(); } } throw PiPedalException("Not found."); } int64_t Storage::DeletePreset(int64_t presetId) { int64_t newSelection = currentBank.deletePreset(presetId); SaveCurrentBank(); return newSelection; } bool Storage::RenamePreset(int64_t presetId, const std::string &name) { if (this->currentBank.renamePreset(presetId, name)) { SaveCurrentBank(); return true; } return false; } static int lastIndexOf(const char *sz, char c) { size_t len = strlen(sz); for (size_t i = len - 1; i >= 0; --i) { if (sz[i] == c) { if (i > 0 && sz[i - 1] == ' ') --i; return i; } } return -1; } std::string Storage::GetPresetCopyName(const std::string &name) { const char *str = name.c_str(); std::string stem; if (name.length() != 0 && name[name.length() - 1] == ')') { const char *str = name.c_str(); size_t pos = lastIndexOf(str, '('); if (pos == -1) { stem = name; } else { stem = name.substr(0, pos); } } else { stem = name; } int copyNumber = 1; while (true) { std::string candidate; if (copyNumber == 1) { candidate = stem + " (Copy)"; } else { std::stringstream s; s << stem << " (Copy " << copyNumber << ")"; candidate = s.str(); } if (!this->currentBank.hasName(candidate)) { return candidate; } ++copyNumber; } } int64_t Storage::CopyPreset(int64_t fromId, int64_t toId) { auto &fromItem = this->currentBank.getItem(fromId); if (toId == -1) { PedalBoard newPedalBoard = fromItem.preset(); std::string name = GetPresetCopyName(fromItem.preset().name()); newPedalBoard.name(name); return this->currentBank.addPreset(newPedalBoard, fromId); } else { auto &toItem = this->currentBank.getItem(toId); toItem.preset(fromItem.preset()); return toId; } } void Storage::SetJackChannelSelection(const JackChannelSelection &channelSelection) { this->jackChannelSelection = channelSelection; this->isJackChannelSelectionValid = true; SaveChannelSelection(); } const JackChannelSelection &Storage::GetJackChannelSelection(const JackConfiguration &jackConfiguration) { if (!this->isJackChannelSelectionValid) { if (jackConfiguration.isValid()) { auto defaultSelection = JackChannelSelection::MakeDefault(jackConfiguration); SetJackChannelSelection(defaultSelection); return this->jackChannelSelection; } } return jackChannelSelection; } void Storage::LoadChannelSelection() { auto fileName = this->GetChannelSelectionFileName(); if (std::filesystem::exists(fileName)) { try { std::ifstream s(fileName); json_reader reader(s); reader.read(&this->jackChannelSelection); this->isJackChannelSelectionValid = true; } catch (const std::exception &e) { Lv2Log::error("I/O error reading %s: %s", fileName.c_str(), e.what()); throw PiPedalStateException("Unexpected error reading Jack settings file."); } } } void Storage::SaveChannelSelection() { auto fileName = this->GetChannelSelectionFileName(); try { std::ofstream s(fileName); json_writer writer(s); writer.write(this->jackChannelSelection); } catch (const std::exception &e) { Lv2Log::error("I/O error writing %s: %s", fileName.c_str(), e.what()); throw PiPedalStateException("Unexpected error writing Jack settings file."); } } void Storage::RenameBank(int64_t bankId, const std::string &newName) { auto existingBank = this->bankIndex.getEntryByName(newName); if (existingBank != nullptr) { throw PiPedalStateException("A bank by that name already exists."); } auto &entry = this->bankIndex.getBankIndexEntry(bankId); std::filesystem::path oldPath = this->GetBankFileName(entry.name()); std::filesystem::path newPath = this->GetBankFileName(newName); try { std::filesystem::rename(oldPath, newPath); } catch (std::exception &e) { std::stringstream s; s << "Unable to rename the bank. (" << e.what() << ")"; throw PiPedalException(s.str()); } entry.name(newName); SaveBankIndex(); } int64_t Storage::SaveBankAs(int64_t bankId, const std::string &newName) { auto existingBank = this->bankIndex.getEntryByName(newName); if (existingBank != nullptr) { throw PiPedalStateException("A bank by that name already exists."); } auto &entry = this->bankIndex.getBankIndexEntry(bankId); std::filesystem::path oldPath = this->GetBankFileName(entry.name()); std::filesystem::path newPath = this->GetBankFileName(newName); try { std::filesystem::copy(oldPath,newPath); } catch (std::exception &e) { std::stringstream s; s << "Unable to save the bank. (" << e.what() << ")"; throw PiPedalException(s.str()); } int64_t newId = this->bankIndex.addBank(bankId,newName); SaveBankIndex(); return newId; } void Storage::MoveBank(int from, int to) { this->bankIndex.move(from,to); this->SaveBankIndex(); } int64_t Storage::DeleteBank(int64_t bankId) { auto & entries = this->bankIndex.entries(); for (size_t i = 0; i < entries.size(); ++i) { auto & entry = entries[i]; if (entry.instanceId() == bankId) { std::filesystem::path fileName = this->GetBankFileName(entry.name()); entries.erase(entries.begin()+i); int64_t newSelection; if (i < entries.size()) { newSelection = entries[i].instanceId(); } else if (entries.size() > 0) { newSelection = entries[entries.size()-1].instanceId(); } else { // zero entries? // Create a default empty bank. BankIndexEntry newEntry; BankFile defaultBank; PedalBoard defaultPedalBoard = PedalBoard::MakeDefault(); int64_t instanceId = defaultBank.addPreset(defaultPedalBoard); defaultBank.selectedPreset(instanceId); std::string name = "Default Bank"; SaveBankFile(name, defaultBank); int64_t selectedBank = bankIndex.addBank(-1,name); newSelection = selectedBank; } if (this->bankIndex.selectedBank() == bankId) { this->bankIndex.selectedBank(newSelection); } this->SaveBankIndex(); std::filesystem::remove(fileName); return newSelection; } } throw PiPedalStateException("Bank not found."); } int64_t Storage::UploadPreset(const BankFile&bankFile,int64_t uploadAfter) { int64_t lastPreset = this->currentBank.selectedPreset(); if (uploadAfter != -1) { lastPreset = uploadAfter; } if (bankFile.presets().size() == 0) { throw PiPedalException("Invalid preset."); } for (size_t i = 0; i < bankFile.presets().size(); ++i) { PedalBoard preset = bankFile.presets()[i]->preset(); int n = 2; std::string baseName = preset.name(); while (this->currentBank.hasName(preset.name())) { std::stringstream s; s << baseName << "(" << n++ << ")"; preset.name(s.str()); } lastPreset = this->currentBank.addPreset(preset,lastPreset); } this->SaveCurrentBank(); return lastPreset; } int64_t Storage::UploadBank(BankFile&bankFile,int64_t uploadAfter) { int64_t lastBank = this->bankIndex.selectedBank(); if (uploadAfter != -1) { lastBank = uploadAfter; } if (bankFile.presets().size() == 0) { throw PiPedalException("Invalid bank."); } bankFile.updateNextIndex(); int n = 2; std::string baseName = bankFile.name(); while (this->bankIndex.hasName(bankFile.name())) { std::stringstream s; s << baseName << "(" << n++ << ")"; bankFile.name(s.str()); } std::filesystem::path path = this->GetBankFileName(bankFile.name()); std::ofstream f(path); if (!f.is_open()) { throw PiPedalException("Can't write to bank file."); } json_writer writer(f); writer.write(bankFile); lastBank = this->bankIndex.addBank(lastBank,bankFile.name()); this->SaveBankIndex(); return lastBank; } void Storage::SetGovernorSettings(const std::string & governor) { std::filesystem::path path = this->dataRoot / GOVERNOR_SETTINGS_FILENAME; { std::ofstream f(path); if (!f.is_open()) { throw PiPedalException("Unable to write to " + ((std::string)path)); } json_writer writer(f); writer.write(governor); this->governorSettings = governor; } } std::string Storage::GetGovernorSettings() const { return this->governorSettings; } void Storage::SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings) { WifiConfigSettings copyToSave = wifiConfigSettings; copyToSave.rebootRequired_ = false; if (!copyToSave.enable_) { copyToSave.hasPassword_ = false; } copyToSave.password_ = ""; std::filesystem::path path = this->dataRoot / WIFI_CONFIG_SETTINGS_FILENAME; { std::ofstream f(path); if (!f.is_open()) { throw PiPedalException("Unable to write to " + ((std::string)path)); } json_writer writer(f); writer.write(©ToSave); } WifiConfigSettings copyToStore = wifiConfigSettings; if (copyToStore.enable_) { copyToStore.hasPassword_ = copyToStore.password_.length() != 0 || this->wifiConfigSettings.hasPassword_; } else { copyToStore.hasPassword_ = false; } copyToStore.password_ = ""; copyToStore.rebootRequired_ = false; if (copyToStore.enable_ && !copyToStore.hasPassword_) { copyToStore.hasPassword_ = this->wifiConfigSettings.hasPassword_; } this->wifiConfigSettings = copyToStore; } void Storage::LoadGovernorSettings() { std::filesystem::path path = this->dataRoot / GOVERNOR_SETTINGS_FILENAME; try { if (std::filesystem::is_regular_file(path)) { std::ifstream f(path); if (!f.is_open()) { throw PiPedalException("Unable to write to " + ((std::string)path)); } json_reader reader(f); std::string governorSettings; reader.read(&governorSettings); this->governorSettings = governorSettings; } } catch (const std::exception&) { this->governorSettings = "performance"; } this->wifiConfigSettings.valid_ = true; } void Storage::LoadWifiConfigSettings() { std::filesystem::path path = this->dataRoot / WIFI_CONFIG_SETTINGS_FILENAME; try { if (std::filesystem::is_regular_file(path)) { std::ifstream f(path); if (!f.is_open()) { throw PiPedalException("Unable to write to " + ((std::string)path)); } json_reader reader(f); WifiConfigSettings wifiConfigSettings; reader.read(&wifiConfigSettings); this->wifiConfigSettings = wifiConfigSettings; } } catch (const std::exception&) { } this->wifiConfigSettings.valid_ = true; } WifiConfigSettings Storage::GetWifiConfigSettings() { return this->wifiConfigSettings; } void Storage::SaveCurrentPreset(const CurrentPreset ¤tPreset) { try { std::filesystem::path path = GetCurrentPresetPath(); std::ofstream f(path); json_writer writer(f); writer.write(currentPreset); } catch (std::exception&) { // called from destructor. Must be nothrow(). } } bool Storage::RestoreCurrentPreset(CurrentPreset*pResult) { std::filesystem::path path = GetCurrentPresetPath(); if (std::filesystem::exists(path)) { try { std::ifstream f(path); json_reader reader(f); reader.read(pResult); std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown. } catch (const std::exception&) { return false; } return true; } else { return false; } } bool Storage::HasPluginPresets(const std::string &pluginUri) const { for (const auto &entry: this->pluginPresetIndex.entries_) { if (entry.pluginUri_ == pluginUri) { return true; } } return false; } std::filesystem::path Storage::GetPluginPresetPath(const std::string &pluginUri) const { for (const auto &entry: this->pluginPresetIndex.entries_) { if (entry.pluginUri_ == pluginUri) { return this->GetPluginPresetsDirectory() / entry.fileName_; } } throw PiPedalArgumentException("Plugin preset file not found."); } void Storage::SavePluginPresets(const std::string&pluginUri, const PluginPresets&presets) { std::string name; std::filesystem::path path; bool presetAdded = false; if (!HasPluginPresets(pluginUri)) { name = SS(pluginPresetIndex.nextInstanceId_ << ".json"); path = GetPluginPresetsDirectory() / name; presetAdded = true; } else { path = GetPluginPresetPath(pluginUri); } auto tempPath = path.string()+".$$$"; { std::ofstream os; os.open(tempPath, std::ios_base::trunc); if (os.fail()) { throw PiPedalException(SS("Can't write to " << path)); } json_writer writer(os); writer.write(presets); } if (std::filesystem::exists(path)) { std::filesystem::remove(path); } std::filesystem::rename(tempPath,path); if (presetAdded) { pluginPresetIndex.entries_.push_back( PluginPresetIndexEntry( pluginUri,name ) ); pluginPresetIndex.nextInstanceId_++; this->pluginPresetIndexChanged = true; SavePluginPresetIndex(); } } PluginPresets Storage::GetPluginPresets(const std::string&pluginUri) const { PluginPresets result; if (!HasPluginPresets(pluginUri)) { result.pluginUri_ = pluginUri; return result; } std::filesystem::path path = GetPluginPresetPath(pluginUri); std::ifstream s; s.open(path); if (s.fail()) { return result; } json_reader reader(s); reader.read(&result); return result; } PluginUiPresets Storage::GetPluginUiPresets(const std::string&pluginUri) const { PluginPresets presets = GetPluginPresets(pluginUri); PluginUiPresets result; result.pluginUri_ = presets.pluginUri_; for (size_t i = 0; i < presets.presets_.size(); ++i) { const auto& preset = presets.presets_[i]; result.presets_.push_back( PluginUiPreset { preset.label_, preset.instanceId_ } ); } return result; } std::vector Storage::GetPluginPresetValues(const std::string&pluginUri, uint64_t instanceId) { auto presets = GetPluginPresets(pluginUri); for (const auto & preset: presets.presets_) { if (preset.instanceId_ == instanceId) { std::vector result; for (const auto &valuePair: preset.controlValues_) { result.push_back(ControlValue(valuePair.first.c_str(),valuePair.second)); } return result; } } throw PiPedalException("Plugin preset not found."); } uint64_t Storage::SavePluginPreset( const std::string&pluginUri, const std::string&name, const std::map & values) { auto presets = GetPluginPresets(pluginUri); uint64_t result = -1; bool existing = false; for (size_t i = 0; i < presets.presets_.size(); ++i) { auto & preset = presets.presets_[i]; if (preset.label_ == name) { preset.controlValues_ = values; existing = true; result = preset.instanceId_; break; } } if (!existing) { result = presets.nextInstanceId_++; presets.presets_.push_back( PluginPreset( result, name, values )); } this->SavePluginPresets(pluginUri,presets); return result; } void Storage::UpdatePluginPresets(const PluginUiPresets &pluginPresets) { // handles deletions, renaming, and reordering only. // If you need to add a preset, you neet to call SavePluginPreset or DulicatePluginPreset instead. PluginPresets presets = this->GetPluginPresets(pluginPresets.pluginUri_); PluginPresets newPresets; newPresets.pluginUri_ = pluginPresets.pluginUri_; newPresets.nextInstanceId_ = presets.nextInstanceId_; for (const auto&preset: pluginPresets.presets_) { PluginPreset newPreset = presets.GetPreset(preset.instanceId_); newPreset.label_ = preset.label_; newPresets.presets_.push_back(std::move(newPreset)); } SavePluginPresets(newPresets.pluginUri_,newPresets); } static std::string stripCopySuffix(const std::string &s) { int pos = s.length()-1; if (pos >= 0 && s[pos] == ')') { --pos; while (pos >= 0 && s[pos] >= '0' && s[pos] <= '9') { --pos; } if (pos >= 0 && s[pos] == '(') { --pos; } while (pos >= 0 && s[pos] == ' ') { --pos; } return s.substr(0,pos+1); } return s; } uint64_t Storage::CopyPluginPreset(const std::string&pluginUri,uint64_t presetId) { PluginPresets presets = this->GetPluginPresets(pluginUri); size_t pos = presets.Find(presetId); if (pos == -1) { throw PiPedalException("Perset not found."); } PluginPreset t = presets.presets_[pos]; t.instanceId_ = presets.nextInstanceId_++; std::string baseName = stripCopySuffix(t.label_); std::string name = baseName; if (presets.Find(name) != -1) { int nCopy = 1; while (true) { name = SS(baseName << " (" << nCopy << ")"); if (presets.Find(name) == -1) { break; } ++nCopy; } } t.label_ = name; presets.presets_.insert(presets.presets_.begin()+pos+1,t); SavePluginPresets(presets.pluginUri_,presets); return t.instanceId_; } JSON_MAP_BEGIN(CurrentPreset) JSON_MAP_REFERENCE(CurrentPreset,modified) JSON_MAP_REFERENCE(CurrentPreset,preset) JSON_MAP_END()