Snapshot file model, Hotspot restart on config change.

This commit is contained in:
Robin Davies
2024-10-08 02:40:05 -04:00
parent 94b2072783
commit ebe56f4df9
19 changed files with 651 additions and 193 deletions
+16 -8
View File
@@ -177,16 +177,24 @@ namespace pipedal
std::istringstream ss(pathProperty.second);
json_reader reader(ss);
reader.read(&vProperty);
vProperty = pluginHost.MapPath(vProperty);
if (!vProperty.is_null())
{
try {
vProperty = pluginHost.MapPath(vProperty);
// now to atom format (what we want on the rt thread0)
AtomConverter atomConverter(pluginHost.GetMapFeature());
LV2_Atom *atomValue = atomConverter.ToAtom(vProperty);
// now to atom format (what we want on the rt thread0)
AtomConverter atomConverter(pluginHost.GetMapFeature());
LV2_Atom *atomValue = atomConverter.ToAtom(vProperty);
size_t atomBufferSize = (atomValue->size + sizeof(LV2_Atom) + 3) / 4 * 4;
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
size_t atomBufferSize = (atomValue->size + sizeof(LV2_Atom) + 3) / 4 * 4;
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
} catch (const std::exception &e)
{
Lv2Log::info(SS("IndexedSnapshotValue: Failed to map path property " << pathProperty.first << ". " << e.what()));
}
}
}
}
}
+55 -14
View File
@@ -37,7 +37,7 @@ using namespace dbus::networkmanager;
namespace pipedal::impl
{
#define PIPEDAL_HOTSPOT_NAME "PiPedal Hotspot"
#define PIPEDAL_HOTSPOT_NAME "PiPedal Hotspot"
class HotspotManagerImpl : public HotspotManager
{
@@ -164,19 +164,20 @@ void HotspotManagerImpl::Open()
void HotspotManagerImpl::onClose()
{
this->closed = true; // avoids a memory barrier probelm.
this->closed = true; // avoids a memory barrier probelm.
CancelDeviceChangedTimer();
CancelWaitForNetworkManagerTimer();
CancelAccessPointsChangedTimer();
if (networkManager && activeConnection)
{
try {
try
{
networkManager->DeactivateConnection(activeConnection->getObjectPath());
activeConnection = nullptr;
} catch (const std::exception&e)
}
catch (const std::exception &e)
{
// nothrow.
}
@@ -443,16 +444,31 @@ void HotspotManagerImpl::onReload()
if (closed)
return;
WifiConfigSettings oldSettings = this->wifiConfigSettings;
;
wifiConfigSettings.Load();
this->wifiConfigSettings.Load();
if (wifiConfigSettings == oldSettings)
{
return;
}
switch (state)
{
case State::Initial:
case State::Error:
// ignore.
Lv2Log::error("NetworkManager is in an error state. Reboot and try again.");
return;
case State::Initial:
case State::WaitingForNetworkManager:
// ignore. new config will be picked up once initialization completes.
return;
case State::Closed:
return;
case State::HotspotConnecting:
case State::HotspotConnected:
default:
// force a reload.
StopHotspot();
SetState(State::Monitoring);
MaybeStartHotspot();
return;
}
@@ -536,7 +552,8 @@ void HotspotManagerImpl::UpdateKnownNetworks(
}
for (auto &accessPoint : allAccessPoints)
{
try {
try
{
uint8_t strength = accessPoint->Strength();
auto vSsid = accessPoint->Ssid();
std::string ssid = ssidToString(vSsid);
@@ -547,7 +564,8 @@ void HotspotManagerImpl::UpdateKnownNetworks(
record.visibleNetwork = true;
record.strength = strength;
}
} catch (const std::exception&ignored)
}
catch (const std::exception &ignored)
{
// race to get the info before it changes. np.
}
@@ -819,8 +837,6 @@ void HotspotManagerImpl::StartHotspot()
std::vector<uint8_t> vSsid{ssid.begin(), ssid.end()};
wireless["ssid"] = vSsid;
wireless["mode"] = "ap";
wireless["band"] = "a";
wireless["band"] = "bg";
uint32_t iChannel = 0;
auto channel = this->wifiConfigSettings.channel_;
@@ -829,9 +845,14 @@ void HotspotManagerImpl::StartHotspot()
std::stringstream ss{channel};
ss >> iChannel;
}
if (iChannel != 0)
if (iChannel <= 1)
{
wireless["channel"] = iChannel;
wireless["band"] = iChannel > 14 ? "a" : "bg";
}
else
{
wireless["band"] = iChannel == 0 ? "bg" : "a";
}
std::map<std::string, sdbus::Variant> &wirelessSecurity = settings["802-11-wireless-security"];
@@ -840,6 +861,26 @@ void HotspotManagerImpl::StartHotspot()
settings["ipv4"]["method"] = "shared";
settings["ipv6"]["method"] = "shared";
////////////////////////////////////////////////////////////////
// Create connection
settings["ipv4"]["method"] = sdbus::Variant(std::string("shared"));
//settings["ipv6"]["method"] = sdbus::Variant(std::string("ignore"));
settings["ipv4"]["address-data"] = sdbus::Variant(std::vector<std::map<std::string, sdbus::Variant>>{{{"address", sdbus::Variant("192.168.60.1")},
{"prefix", sdbus::Variant(uint32_t(24))}}});
settings["ipv4"]["dhcp-send-hostname"] = sdbus::Variant(true);
settings["ipv4"]["dhcp-hostname"] = sdbus::Variant("raspberrypi");
// settings["ipv4"]["dhcp-options"] = sdbus::Variant(std::vector<std::string>{
// "option:classless-static-route,192.168.60.0/24,0.0.0.0,0.0.0.0/0,192.168.60.1"});
settings["ipv4"]["route-data"] = sdbus::Variant(std::vector<std::map<std::string, sdbus::Variant>>{{
{"dest", sdbus::Variant("192.168.4.0")},
{"prefix", sdbus::Variant(uint32_t(24))},
{"metric", sdbus::Variant((uint32_t)99)}
}});
////////////////////////////////////////////////////////////
// settings["ipv6"]["addr-gen-mode"] = "stable-privacy";
std::map<std::string, sdbus::Variant> options;
+28
View File
@@ -385,6 +385,34 @@ static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name
return result;
}
bool pipedal::IsLinkLocalAddress(const std::string fromAddress)
{
std::string address = fromAddress;
std::string result;
if (address[0] != '[')
{
return false;
}
else
{
// ipv6
if (address[0] != '[' || address[address.length() - 1] != ']')
throw std::invalid_argument("Bad address.");
address = address.substr(1, address.length() - 2);
struct in6_addr inetAddr6;
memset(&inetAddr6, 0, sizeof(inetAddr6));
if (inet_pton(AF_INET6, address.c_str(), &inetAddr6) == 1)
{
if (IN6_IS_ADDR_LINKLOCAL(&inetAddr6))
{
return true;
}
}
}
return false;
}
std::string pipedal::GetNonLinkLocalAddress(const std::string fromAddress)
{
std::string address = fromAddress;
+2
View File
@@ -27,6 +27,8 @@ namespace pipedal {
std::string GetInterfaceIpv4Address(const std::string& interfaceName);
bool IsLinkLocalAddress(const std::string fromAddress);
std::string GetNonLinkLocalAddress(const std::string fromAddress);
bool IsOnLocalSubnet(const std::string&fromAddress);
+47
View File
@@ -87,6 +87,18 @@ ControlValue* PedalboardItem::GetControlValue(const std::string&symbol)
return nullptr;
}
const ControlValue* PedalboardItem::GetControlValue(const std::string&symbol) const
{
for (size_t i = 0; i < this->controlValues().size(); ++i)
{
if (this->controlValues()[i].key() == symbol)
{
return &(this->controlValues()[i]);
}
}
return nullptr;
}
bool Pedalboard::SetItemEnabled(int64_t pedalItemId, bool enabled)
{
PedalboardItem*item = GetItem(pedalItemId);
@@ -266,6 +278,17 @@ bool PedalboardItem::IsStructurallyIdentical(const PedalboardItem&other) const
}
if (this->isSplit()) // so is the other by virtue of idential uris.
{
auto myValue = this->GetControlValue("splitType");
auto otherValue = other.GetControlValue("splitType");
if (myValue == nullptr || otherValue == nullptr) // actually an error.
{
return false;
}
// split type changes potentially trigger buffer allocation changes,
// so different split types are not structurally identical.
if (myValue->value() != otherValue->value()) {
return false;
}
if (topChain().size() != other.topChain().size())
{
return false;
@@ -356,6 +379,30 @@ void PedalboardItem::AddResetsForMissingProperties(Snapshot&snapshot, size_t*ind
}
Pedalboard Pedalboard::DeepCopy()
{
Pedalboard result = *this;
for (size_t i= 0; i < snapshots_.size(); ++i)
{
if (snapshots_[i])
{
result.snapshots_[i] = std::make_shared<Snapshot>(*(snapshots_[i]));
}
}
return result;
}
void Pedalboard::SetCurrentSnapshotModified(bool modified)
{
if (selectedSnapshot() != -1)
{
auto& snapshot = snapshots_[selectedSnapshot_];
if (snapshot)
{
snapshot->isModified_ = modified;
}
}
}
Snapshot Pedalboard::MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard)
{
Snapshot snapshot;
+5 -1
View File
@@ -101,6 +101,7 @@ public:
PropertyMap patchProperties;
public:
ControlValue*GetControlValue(const std::string&symbol);
const ControlValue*GetControlValue(const std::string&symbol) const;
bool IsStructurallyIdentical(const PedalboardItem&other) const;
@@ -166,7 +167,7 @@ class Snapshot {
public:
std::string name_;
std::string color_;
bool isModified_ = true;
bool isModified_ = false;
std::vector<SnapshotValue> values_;
DECLARE_JSON_MAP(Snapshot);
@@ -186,10 +187,13 @@ class Pedalboard {
int64_t selectedSnapshot_ = -1;
public:
// deep copy, breaking shared pointers.
Pedalboard DeepCopy();
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
void SetCurrentSnapshotModified(bool modified);
bool IsStructureIdentical(const Pedalboard &other) const; // caan we just send a snapshot-style uddate instead of reloading plugins? All settings are ignored.
Snapshot MakeSnapshotFromCurrentSettings(const Pedalboard &previousPedalboard);
+66 -34
View File
@@ -101,6 +101,26 @@ PiPedalModel::PiPedalModel()
// don't actuall start the hotspotManager until after LV2 is initialized (in order to avoid logging oddities)
}
void PrepareSnapshostsForSave(Pedalboard&pedalboard)
{
if (pedalboard.selectedSnapshot() != -1) {
auto&currentSnapshot = pedalboard.snapshots()[pedalboard.selectedSnapshot()];
if (!currentSnapshot || currentSnapshot->isModified_)
{
pedalboard.selectedSnapshot(-1);
}
}
for (auto &snapshot: pedalboard.snapshots())
{
if (snapshot) {
snapshot->isModified_ = false;
}
}
}
void PiPedalModel::Close()
{
std::unique_ptr<AudioHost> oldAudioHost;
@@ -515,15 +535,19 @@ void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard)
void PiPedalModel::SetSnapshot(int64_t selectedSnapshot)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (this->pedalboard.selectedSnapshot() == selectedSnapshot)
{
return;
}
if (this->pedalboard.ApplySnapshot(selectedSnapshot))
{
this->pedalboard.SetCurrentSnapshotModified(false);
this->FireSnapshotModified(selectedSnapshot,false);
if (this->audioHost)
{
this->audioHost->LoadSnapshot(*(this->pedalboard.snapshots()[selectedSnapshot]), this->pluginHost); // no longer own it.
if (this->previousPedalboardLoaded && this->pedalboard.IsStructureIdentical(this->previousPedalboard))
{
this->audioHost->LoadSnapshot(*(this->pedalboard.snapshots()[selectedSnapshot]), this->pluginHost); // no longer own it.
} else {
LoadCurrentPedalboard();
}
}
SetPresetChanged(-1, true, false);
this->pedalboard.selectedSnapshot(selectedSnapshot);
@@ -535,43 +559,21 @@ void PiPedalModel::SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshot
{
std::lock_guard<std::recursive_mutex> lock(mutex);
// notionally, snapshots are saved whenever they are modified, but pedalboards are not.
// so we must add the current snapshots BOTH to the current pedalbaoard, AND to the
// current saved instance of the pedalboard to create the illusion that the are stored separately.
// In practice, snapshots have to track structure changes (delete pedalboard items that have been deleted,
// and set default settings for pedalboard items that have ben added since the snapshots was last saved.
//
// This is acheived by:
// 1. updating BOTH the current pedalboard, and its saved instance.
// 2. discarding references to dangling instances as plugins are loaded (see UpdateDefaults).
// 3. Setting missing snapshot controls to default values (NOT the current pedalboard values)
// 4. (We currently don't do the right thing with patch properties if the patch property has never been set).
UpdateVst3Settings(pedalboard);
{
// stealth update of the saved snapshots.
auto savedPedalboard = storage.GetCurrentPreset();
savedPedalboard.snapshots(snapshots); // makes a shallow copy
if (selectedSnapshot != -1)
{
// implies that this is a snapshot of the currently running pedalboard. so we can mark it as the selected pedalboard.
this->pedalboard.selectedSnapshot(selectedSnapshot);
}
// UpdateDefaults(&savedPedalboard); // The update is awfully fresh. wait until it gets loaded again before pruning.
storage.SaveCurrentPreset(savedPedalboard);
}
this->pedalboard.snapshots(std::move(snapshots));
if (selectedSnapshot != -1)
{
this->pedalboard.selectedSnapshot(selectedSnapshot);
}
this->FirePedalboardChanged(-1, false); // notify clients (but don't change the running pedalboard, because it's still the same)
// this means that all clients get an up-to-date copy of the snapshots AND the currently selected snapshot if that applies
// (and a fresh copy of the pedalboard settings as well, which is harmless, since they have not changed)
this->SetPresetChanged(-1, true,false);
}
void PiPedalModel::UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalboard)
@@ -634,8 +636,16 @@ void PiPedalModel::SetPresetChanged(int64_t clientId, bool value, bool changeSna
{
if (changeSnapshotSelect && value && this->pedalboard.selectedSnapshot() != -1)
{
this->pedalboard.selectedSnapshot(-1);
FireSelectedSnapshotChanged(-1);
auto & snapshot = this->pedalboard.snapshots()[pedalboard.selectedSnapshot()];
if (snapshot) {
if (!snapshot->isModified_)
{
snapshot->isModified_ = true;
FireSnapshotModified(pedalboard.selectedSnapshot(),true);
}
}
this->pedalboard.SetCurrentSnapshotModified(true);
}
if (value != this->hasPresetChanged)
{
@@ -644,6 +654,20 @@ void PiPedalModel::SetPresetChanged(int64_t clientId, bool value, bool changeSna
}
}
void PiPedalModel::FireSnapshotModified(int64_t snapshotIndex, bool modified)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
for (auto &subscriber : t)
{
subscriber->OnSnapshotModified(snapshotIndex,modified);
}
}
}
void PiPedalModel::FireSelectedSnapshotChanged(int64_t selectedSnapshot)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
@@ -762,6 +786,8 @@ void PiPedalModel::SaveCurrentPreset(int64_t clientId)
UpdateVst3Settings(this->pedalboard);
SyncLv2State();
PrepareSnapshostsForSave(pedalboard);
storage.SaveCurrentPreset(this->pedalboard);
this->SetPresetChanged(clientId, false);
@@ -791,12 +817,15 @@ int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string &
return presetId;
}
int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, const std::string &name, int64_t saveAfterInstanceId)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
SyncLv2State();
auto pedalboard = this->pedalboard;
auto pedalboard = this->pedalboard.DeepCopy();
PrepareSnapshostsForSave(pedalboard);
UpdateVst3Settings(pedalboard);
pedalboard.name(name);
int64_t result = storage.SaveCurrentPresetAs(pedalboard, name, saveAfterInstanceId);
@@ -1280,6 +1309,7 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver)
this->audioHost->SetPedalboard(nullptr);
previousPedalboardLoaded = false;
auto jackServerSettings = this->jackServerSettings;
if (useDummyAudioDriver)
{
@@ -2359,6 +2389,8 @@ bool PiPedalModel::LoadCurrentPedalboard()
// return true if the error messages have changed
CheckForResourceInitialization(this->pedalboard);
audioHost->SetPedalboard(lv2Pedalboard);
previousPedalboard = this->pedalboard;
previousPedalboardLoaded = true;
return true;
}
+2
View File
@@ -68,6 +68,7 @@ namespace pipedal
virtual void OnPedalboardChanged(int64_t clientId, const Pedalboard &pedalboard) = 0;
virtual void OnPresetsChanged(int64_t clientId, const PresetIndex &presets) = 0;
virtual void OnPresetChanged(bool changed) = 0;
virtual void OnSnapshotModified(int64_t selectedSnapshot, bool modified) = 0;
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0;
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
@@ -180,6 +181,7 @@ namespace pipedal
std::vector<std::shared_ptr<IPiPedalModelSubscriber>> subscribers;
void SetPresetChanged(int64_t clientId, bool value, bool changeSnapshotSelect = true);
void FireSnapshotModified(int64_t snapshotIndex, bool modified);
void FireSelectedSnapshotChanged(int64_t selectedSnapshot);
void FirePresetsChanged(int64_t clientId);
void FirePresetChanged(bool changed);
+22
View File
@@ -396,6 +396,19 @@ JSON_MAP_REFERENCE(SetSnapshotsBody, snapshots)
JSON_MAP_REFERENCE(SetSnapshotsBody, selectedSnapshot)
JSON_MAP_END()
class SnapshotModifiedBody {
public:
int64_t snapshotIndex_;
bool modified_;
DECLARE_JSON_MAP(SnapshotModifiedBody);
};
JSON_MAP_BEGIN(SnapshotModifiedBody)
JSON_MAP_REFERENCE(SnapshotModifiedBody, snapshotIndex)
JSON_MAP_REFERENCE(SnapshotModifiedBody, modified)
JSON_MAP_END()
class ChannelSelectionChangedBody
{
public:
@@ -1762,6 +1775,15 @@ private:
body.jackChannelSelection_ = const_cast<JackChannelSelection *>(&channelSelection);
Send("onChannelSelectionChanged", body);
}
virtual void OnSnapshotModified(int64_t snapshotIndex, bool modified)
{
SnapshotModifiedBody body;
body.snapshotIndex_ = snapshotIndex;
body.modified_ = modified;
Send("onSnapshotModified", body);
}
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) override
{
Send("onSelectedSnapshotChanged", selectedSnapshot);
+40
View File
@@ -936,6 +936,20 @@ namespace pipedal
return result.str();
}
static void splitAddressAndPort(const std::string &address, std::string&addrOnly, std::string&port)
{
size_t portPos = address.length();
for (size_t i = address.length(); i > 0; --i)
{
if (address[i-1] == ':')
{
portPos = i-1;
break;
}
}
addrOnly = address.substr(0,portPos);
port = address.substr(portPos);
}
void on_http(connection_hdl hdl)
{
// Upgrade our connection handle to a full connection_ptr
@@ -974,6 +988,31 @@ namespace pipedal
std::string fromAddress = SS(con->get_remote_endpoint());
// redirect requests to IPV6 local connections to a better address.
std::string addrOnly,portStr;
splitAddressAndPort(fromAddress,addrOnly,portStr);
if (IsLinkLocalAddress(addrOnly))
{
try {
uri_builder builder(requestUri);
std::string redirectAddress = GetNonLinkLocalAddress(addrOnly);
builder.set_authority(redirectAddress+portStr);
std::string redirectUri = builder.str();
res.set(HttpField::location,redirectUri.c_str());
Lv2Log::info(SS("Redirecting from " << fromAddress << " to " << redirectUri));
res.keepAlive(false);
con->set_status(websocketpp::http::status_code::temporary_redirect);
return;
} catch (const std::exception&e)
{
ServerError(*con,"Invalid request on link-local address.");
return;
}
}
if (req.method() == HttpVerb::options)
{
res.set(HttpField::access_control_allow_origin, origin);
@@ -984,6 +1023,7 @@ namespace pipedal
return;
}
for (auto requestHandler : this->request_handlers)
{
+1
View File
@@ -70,6 +70,7 @@ public:
constexpr static const char* origin = "Origin";
constexpr static const char* date = "Date";
constexpr static const char* referer = "Referer";
constexpr static const char * location = "Location";
};
+2 -2
View File
@@ -74,12 +74,12 @@ std::vector<WifiChannelSelector> pipedal::getWifiChannelSelectors(const char*cou
if (forCommandline) {
WifiChannelSelector autoSelect;
autoSelect.channelId_ = "0";
autoSelect.channelName_ = "0 Select automatically";
autoSelect.channelName_ = "0 Automatic";
result.push_back(autoSelect);
} else {
WifiChannelSelector autoSelect;
autoSelect.channelId_ = "0";
autoSelect.channelName_ = "Select automatically";
autoSelect.channelName_ = "Automatic";
result.push_back(autoSelect);
}
WifiInfo wifiInfo = getWifiInfo(countryIso3661);