Alsa head corruption in error handling.

This commit is contained in:
Robin Davies
2024-09-15 06:30:16 -04:00
parent f762d8b396
commit 2a851dbd4b
28 changed files with 744 additions and 140 deletions
+15 -1
View File
@@ -30,6 +30,7 @@
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <memory>
#include <unistd.h> // for gethostname()
using namespace pipedal;
using namespace std;
@@ -87,6 +88,18 @@ bool WifiConfigSettings::ValidateCountryCode(const std::string &text)
return false;
return std::isalpha(text[0]) && std::isalpha(text[1]);
}
static std::string GetHostName()
{
char buffer[1024];
if (gethostname(buffer,1024) != 0)
{
buffer[0] = '\0';
}
buffer[1023] = '\0';
return buffer;
}
void WifiConfigSettings::Load()
{
try
@@ -103,7 +116,7 @@ void WifiConfigSettings::Load()
}
this->countryCode_ = getWifiCountryCode();
this->enable_ = this->autoStartMode_ != (uint16_t)HotspotAutoStartMode::Never;
this->mdnsName_ = this->hotspotName_;
this->mdnsName_ = GetHostName();
}
catch (const std::exception &e)
{
@@ -689,6 +702,7 @@ void WifiConfigSettings::ParseArguments(
this->autoStartMode_ = (int16_t)startMode;
this->enable_ = startMode != HotspotAutoStartMode::Never;
this->homeNetwork_ = homeNetworkSsid;
this->mdnsName_ = GetHostName();
this->countryCode_ = argv[0];
this->hotspotName_ = argv[1];
@@ -75,12 +75,12 @@ namespace pipedal {
bool hasPassword_ = false;
std::string password_;
std::string channel_ = "";
std::string mdnsName_ = "pipedal";
bool wifiWarningGiven_ = false; // Do not use. Present only for backward compatibility.
bool valid_ = false; // Do not use. Present only for backward compatibility.
private:
bool rebootRequired_ = false; // Do not use. Present only for backward compatibility.
std::string mdnsName_ = "pipedal";
bool enable_ = false; // Do not use. Present only for backward compatibility.
public:
bool IsEnabled() const { return autoStartMode_ != 0; }
+18 -3
View File
@@ -67,10 +67,9 @@
useSystem = true;
break;
}
if (useSystem)
{
if (useSystem) {
darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
}
if (!darkMode) {
let bgStyle = document.getElementById("bgStyle");
@@ -79,6 +78,22 @@
bgStyle.setAttribute('media', "max-width: 1px");
}
}
function removeHashOnLoad() {
if (window.location.hash) {
// Store the hash value (without the '#' symbol)
var hash = window.location.hash.substring(1);
// Replace the current URL without the hash
var newUrl = window.location.href.replace(window.location.hash, '');
// Use HTML5 history API to change the URL without reloading the page
history.replaceState(null, document.title, newUrl);
}
}
// Run the function when the window loads
window.addEventListener('load', removeHashOnLoad);
</script>
</head>
+2
View File
@@ -682,6 +682,8 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
return "Downloading update...";
case State.InstallingUpdate:
return "Installing update....";
case State.HotspotChanging:
return "Network connection changing..."
default:
return "Reconnecting...";
}
+1 -1
View File
@@ -392,7 +392,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
getSelectedBankName() {
try {
return this.model.banks.get().getEntry(this.state.selectedItem)!.name;
return this.model.banks.get()?.getEntry(this.state.selectedItem)?.name??"";
} catch (error) {
return "";
}
+106 -2
View File
@@ -52,6 +52,7 @@ export enum State {
ReloadingPlugins,
DownloadingUpdate,
InstallingUpdate,
HotspotChanging,
};
class UpdatedError extends Error {
@@ -65,6 +66,7 @@ export enum ReconnectReason {
LoadingSettings,
ReloadingPlugins,
Updating,
HotspotChanging
};
export type ControlValueChangedHandler = (key: string, value: number) => void;
@@ -466,6 +468,10 @@ export class PiPedalModel //implements PiPedalModel
case ReconnectReason.Updating:
this.setState(State.InstallingUpdate);
break;
case ReconnectReason.HotspotChanging:
this.setState(State.HotspotChanging);
this.startHotspotReconnectTimer();
break;
}
return true;
@@ -643,9 +649,13 @@ export class PiPedalModel //implements PiPedalModel
} else if (message === "onUpdateStatusChanged") {
let updateStatus = new UpdateStatus().deserialize(body);
this.onUpdateStatusChanged(updateStatus);
} else if (message === "onNetworkChanging")
{
this.onNetworkChanging(body as boolean);
}
}
private updateLaterTimeout?: NodeJS.Timeout = undefined;
private clearPromptForUpdateTimer() {
@@ -789,7 +799,7 @@ export class PiPedalModel //implements PiPedalModel
}
}
onSocketReconnected() {
this.cancelOnNetworkChanging();
if (this.isAndroidHosted()) {
this.androidHost?.setDisconnected(false);
}
@@ -943,7 +953,7 @@ export class PiPedalModel //implements PiPedalModel
return this.webSocket.connect();
})
.catch((error) => {
this.setError("Failed to connect to server. " + this.socketServerUrl);
this.setError("Failed to connect to server. (" + this.socketServerUrl + ")");
return false;
})
.then(() => {
@@ -2894,6 +2904,100 @@ export class PiPedalModel //implements PiPedalModel
window.location.href = url;
//window.location.reload();
}
private networkChanging: boolean = false;
private networkChanging_expectHotspot = false;
private expectNetworkChangeTimeout?: NodeJS.Timeout = undefined;
private hotspotReconnectTimer?: NodeJS.Timeout = undefined;
async detectServer(address: string)
{
let port = window.location.port;
let newUrl = new URL("http://" + address + ":" + port + "/manifest.json");
try {
let response = await fetch(newUrl);
if (response.ok)
{
return "http://" + address +":"+ port;
}
return "";
} catch (error: any)
{
return "";
}
}
async pollForLiveServer() {
let wifiConfigSettings = this.wifiConfigSettings.get();
if (wifiConfigSettings.mdnsName.length !== 0)
{
let newUrl = await this.detectServer(wifiConfigSettings.mdnsName);
if (newUrl.length !== 0)
{
return newUrl;
}
}
if (this.networkChanging_expectHotspot)
{
let newUrl = await this.detectServer("10.40.0.1");
if (newUrl.length !== 0)
{
return newUrl;
}
}
return "";
}
cancelHotspotReconnectTimer()
{
if (this.hotspotReconnectTimer)
{
clearTimeout(this.hotspotReconnectTimer);
}
}
startHotspotReconnectTimer()
{
// poll for access to a running pipedal server
this.hotspotReconnectTimer = setTimeout(
async () => {
let newUrl = await this.pollForLiveServer();
if (newUrl.length === 0)
{
this.startHotspotReconnectTimer();
} else {
this.cancelOnNetworkChanging();
window.location.replace(newUrl);
}
},
5*1000);
}
cancelOnNetworkChanging()
{
this.cancelHotspotReconnectTimer();
if (this.expectNetworkChangeTimeout)
{
clearTimeout(this.expectNetworkChangeTimeout);
this.expectNetworkChangeTimeout = undefined;
}
this.networkChanging = false;
this.expectDisconnect(ReconnectReason.Disconnected);
}
onNetworkChanging(hotspotConnected: boolean)
{
this.cancelOnNetworkChanging();
this.networkChanging = true;
this.networkChanging_expectHotspot = hotspotConnected;
this.expectDisconnect(ReconnectReason.HotspotChanging);
this.expectNetworkChangeTimeout = setTimeout(
() => {
this.cancelOnNetworkChanging();
},
30*1000);
}
};
let instance: PiPedalModel | undefined = undefined;
+17
View File
@@ -131,7 +131,24 @@ export const SystemMidiBindingDialog =
{
displayName = "Next Preset";
instanceId = 2;
} else if (item.symbol === "startHotspot")
{
displayName = "Enable Hotspot";
instanceId = 3;
} else if (item.symbol === "stopHotspot")
{
displayName = "Disable Hotspot";
instanceId = 4;
} else if (item.symbol === "shutdown")
{
displayName = "Shutdown";
instanceId = 5;
} else if (item.symbol === "reboot")
{
displayName = "Reboot";
instanceId = 6;
}
if (instanceId !== -1)
{
result.push(new BindingEntry(displayName,instanceId,item));
+2
View File
@@ -28,6 +28,7 @@ export default class WifiConfigSettings {
this.wifiWarningGiven = input.wifiWarningGiven;
this.hotspotName = input.hotspotName;
this.mdnsName = input.mdnsName;
this.hasPassword = input.hasPassword;
this.password = input.password;
this.countryCode = input.countryCode;
@@ -40,6 +41,7 @@ export default class WifiConfigSettings {
autoStartMode: number = 0;
hasSavedPassword: boolean = false;
homeNetwork: string = "";
mdnsName: string = "";
wifiWarningGiven: boolean = false;
hasPassword: boolean = false;
valid: boolean = false;
+53
View File
@@ -296,6 +296,10 @@ private:
SystemMidiBinding nextMidiBinding;
SystemMidiBinding prevMidiBinding;
SystemMidiBinding startHotspotMidiBinding;
SystemMidiBinding stopHotspotMidiBinding;
SystemMidiBinding rebootMidiBinding;
SystemMidiBinding shutdownMidiBinding;
JackChannelSelection channelSelection;
std::atomic<bool> active = false;
@@ -742,6 +746,34 @@ private:
this->realtimeWriter.OnNextMidiProgram(++(this->midiProgramChangeId), -1);
}
}
else if (this->shutdownMidiBinding.IsMatch(event))
{
if (shutdownMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Shutdown);
}
}
else if (this->rebootMidiBinding.IsMatch(event))
{
if (rebootMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::Reboot);
}
}
else if (this->startHotspotMidiBinding.IsMatch(event))
{
if (startHotspotMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StartHotspot);
}
}
else if (this->stopHotspotMidiBinding.IsMatch(event))
{
if (stopHotspotMidiBinding.IsTriggered(event))
{
this->realtimeWriter.OnRealtimeMidiEvent(RealtimeMidiEventType::StopHotspot);
}
}
else if (midiProgramChangePending)
{
// defer the message for processing after the program change has completed.
@@ -1182,6 +1214,12 @@ public:
hostReader.read(&request);
pNotifyCallbacks->OnNotifyNextMidiProgram(request);
}
else if (command == RingBufferCommand::RealtimeMidiEvent)
{
RealtimeMidiEventRequest request;
hostReader.read(&request);
pNotifyCallbacks->OnNotifyMidiRealtimeEvent(request.eventType);
}
else if (command == RingBufferCommand::Lv2ErrorMessage)
{
size_t size;
@@ -1710,6 +1748,7 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
{
for (auto i = bindings.begin(); i != bindings.end(); ++i)
{
if (i->symbol() == "nextProgram")
{
this->nextMidiBinding.SetBinding(*i);
@@ -1717,6 +1756,20 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
else if (i->symbol() == "prevProgram")
{
this->prevMidiBinding.SetBinding(*i);
} else if (i->symbol() == "startHotspot")
{
this->startHotspotMidiBinding.SetBinding(*i);
} else if (i->symbol() == "stopHotspot")
{
this->stopHotspotMidiBinding.SetBinding(*i);
} else if (i->symbol() == "reboot")
{
this->rebootMidiBinding.SetBinding(*i);
} else if (i->symbol() == "shutdown")
{
this->shutdownMidiBinding.SetBinding(*i);
} else {
Lv2Log::error(SS("Invalid system midi binding: " << i->symbol()));
}
}
}
+2
View File
@@ -30,6 +30,7 @@
#include "PiPedalAlsa.hpp"
#include "Promise.hpp"
#include "json_variant.hpp"
#include "RealtimeMidiEventType.hpp"
namespace pipedal {
@@ -160,6 +161,7 @@ public:
virtual void OnNotifyMidiProgramChange(RealtimeMidiProgramRequest&midiProgramRequest) = 0;
virtual void OnNotifyNextMidiProgram(const RealtimeNextMidiProgramRequest&request) = 0;
virtual void OnNotifyLv2RealtimeError(int64_t instanceId,const std::string &error) = 0;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
};
+2 -1
View File
@@ -146,6 +146,7 @@ else()
endif()
set (PIPEDAL_SOURCES
RealtimeMidiEventType.hpp
DBusToLv2Log.cpp DBusToLv2Log.hpp
HotspotManager.cpp HotspotManager.hpp
UpdateResults.cpp UpdateResults.hpp
@@ -646,7 +647,7 @@ add_executable(pipedal_update
UpdateMain.cpp
UpdateResults.cpp UpdateResults.hpp
Lv2SystemdLogger.cpp Lv2SystemdLogger.hpp
UpdateResults.cpp UpdateResults.hpp
TemporaryFile.cpp TemporaryFile.hpp
AdminInstallUpdate.cpp AdminInstallUpdate.hpp
)
+3
View File
@@ -909,6 +909,9 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
}
try
{
// apply policy changes we dropped into the polkit configuration files (allows pipedal_d to use NetworkManager dbus apis).
silentSysExec(SYSTEMCTL_BIN " restart polkit");
DeployVarConfig();
if (sysExec(GROUPADD_BIN " -f " AUDIO_SERVICE_GROUP_NAME) != EXIT_SUCCESS)
+7 -1
View File
@@ -725,6 +725,12 @@ static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessP
}
void HotspotManagerImpl::MaybeStartHotspot()
{
if (this->state == State::Error) return;
if (!wlanDevice || !wlanWirelessDevice)
{
// devices are transitioning. Do nothing.
}
std::vector<AccessPoint::ptr> allAccessPoints = GetAllAccessPoints();
std::vector<std::vector<uint8_t>> allAccessPointSsids = GetAccessPointSsids(allAccessPoints);
std::vector<std::vector<uint8_t>> connectableSsids = GetKnownVisibleAccessPoints(allAccessPointSsids); // all the ssids currently visible for which we have credentials.
@@ -820,7 +826,7 @@ void HotspotManagerImpl::StartHotspot()
wireless["band"] = "a";
wireless["band"] = "bg";
uint32_t iChannel;
uint32_t iChannel = 0;
auto channel = this->wifiConfigSettings.channel_;
if (channel.length() != 0)
{
+21 -37
View File
@@ -30,7 +30,7 @@
using namespace pipedal;
std::string pipedal::GetLinkLocalAddress(const std::string fromAddress);
std::string pipedal::GetNonLinkLocalAddress(const std::string fromAddress);
static bool IsIpv4MappedAddress(const struct in6_addr &inetAddr6)
{
@@ -302,6 +302,7 @@ static std::string GetInterfaceForIp6Address(const in6_addr inetAddr6)
{ // TODO: Add support for AF_INET6
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
struct sockaddr_in6 *pNetMask = (struct sockaddr_in6 *)(p->ifa_netmask);
if (ipv6NetmaskCompare(inetAddr6, pAddr->sin6_addr, pNetMask->sin6_addr))
{
result = p->ifa_name;
@@ -313,7 +314,7 @@ static std::string GetInterfaceForIp6Address(const in6_addr inetAddr6)
return result;
}
static std::string GetLinkLocalAddressForInterface(const std::string &name)
static std::string GetNonLinkLocalAddressForInterface(const std::string &name)
{
struct ifaddrs *ifap = nullptr;
if (getifaddrs(&ifap) != 0)
@@ -324,7 +325,7 @@ static std::string GetLinkLocalAddressForInterface(const std::string &name)
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
{ // TODO: Add support for AF_INET6
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
if (IN6_IS_ADDR_LINKLOCAL(&(pAddr->sin6_addr)))
if (!IN6_IS_ADDR_LINKLOCAL(&(pAddr->sin6_addr)))
{
if (name == p->ifa_name)
{
@@ -353,7 +354,7 @@ static std::string GetLinkLocalAddressForInterface(const std::string &name)
freeifaddrs(ifap);
return result;
}
static std::string GetLinkLocalAddressForIp4Interface(const std::string &name)
static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name)
{
struct ifaddrs *ifap = nullptr;
if (getifaddrs(&ifap) != 0)
@@ -384,23 +385,13 @@ static std::string GetLinkLocalAddressForIp4Interface(const std::string &name)
return result;
}
std::string pipedal::GetLinkLocalAddress(const std::string fromAddress)
std::string pipedal::GetNonLinkLocalAddress(const std::string fromAddress)
{
std::string address = StripPortNumber(fromAddress);
std::string address = fromAddress;
std::string result;
if (address[0] != '[')
{
// ipv4
struct in_addr inetAddr;
memset(&inetAddr, 0, sizeof(inetAddr));
if (inet_pton(AF_INET, address.c_str(), &inetAddr) == 1)
{
uint32_t remoteAddress = htonl(inetAddr.s_addr);
std::string interfaceName = GetInterfaceForIp4Address(remoteAddress);
result = GetLinkLocalAddressForIp4Interface(interfaceName);
}
return address;
}
else
{
@@ -408,13 +399,14 @@ std::string pipedal::GetLinkLocalAddress(const std::string fromAddress)
if (address[0] != '[' || address[address.length() - 1] != ']')
throw std::invalid_argument("Bad address.");
address = address.substr(1, address.length() - 2);
// strip scope if neccessary.
auto pos = address.find_last_of('%');
if (pos != std::string::npos)
{
address = address.substr(0, pos);
}
auto nPos = address.find('%') ;
if (nPos != std::string::npos)
{
std::string ifName = address.substr(nPos+1);
return GetNonLinkLocalAddressForInterface(ifName);
}
struct in6_addr inetAddr6;
memset(&inetAddr6, 0, sizeof(inetAddr6));
if (inet_pton(AF_INET6, address.c_str(), &inetAddr6) == 1)
@@ -430,33 +422,25 @@ std::string pipedal::GetLinkLocalAddress(const std::string fromAddress)
int8_t *pAddr = (int8_t *)&inetAddr6;
uint32_t remoteAddress = htonl(*(int32_t *)(pAddr + 12));
std::string interfaceName = GetInterfaceForIp4Address(remoteAddress);
result = GetLinkLocalAddressForIp4Interface(interfaceName);
result = GetNonLinkLocalAddressForIp4Interface(interfaceName);
}
else
{
std::string interfaceName;
if (IsIpv4MappedAddress(inetAddr6))
{
uint32_t remoteAddress = GetIpv4MappedAddress(inetAddr6);
interfaceName = GetInterfaceForIp4Address(remoteAddress);
}
else
{
interfaceName = GetInterfaceForIp6Address(inetAddr6);
}
result = GetLinkLocalAddressForInterface(interfaceName);
std::string interfaceName = GetInterfaceForIp6Address(inetAddr6);
result = GetNonLinkLocalAddressForInterface(interfaceName);
}
}
}
if (result == "")
{
result = GetLinkLocalAddressForInterface("");
result = GetNonLinkLocalAddressForInterface("");
}
return result;
}
std::string pipedal::GetInterfaceIpv4Address(const std::string& interfaceName)
{
return GetLinkLocalAddressForIp4Interface(interfaceName);
return GetNonLinkLocalAddressForIp4Interface(interfaceName);
}
+1 -1
View File
@@ -27,7 +27,7 @@ namespace pipedal {
std::string GetInterfaceIpv4Address(const std::string& interfaceName);
std::string GetLinkLocalAddress(const std::string fromAddress);
std::string GetNonLinkLocalAddress(const std::string fromAddress);
bool IsOnLocalSubnet(const std::string&fromAddress);
+96 -2
View File
@@ -37,6 +37,7 @@
#include "Lv2PluginChangeMonitor.hpp"
#include "HotspotManager.hpp"
#include "DBusToLv2Log.hpp"
#include "SysExec.hpp"
#ifndef NO_MLOCK
#include <sys/mman.h>
@@ -1957,7 +1958,7 @@ void PiPedalModel::CancelMonitorPatchProperty(int64_t clientId, int64_t clientHa
atomOutputListeners.erase(atomOutputListeners.begin() + i);
break;
}
}
}
if (midiEventListeners.size() == 0)
{
audioHost->SetListenForMidiEvent(false);
@@ -2343,14 +2344,107 @@ void PiPedalModel::OnNetworkChanging(bool ethernetConnected,bool hotspotConnecte
{
CancelNetworkChangingTimer();
this->networkChangingDelayHandle =
PostDelayed(std::chrono::seconds(5),
PostDelayed(std::chrono::seconds(10), // takes a while for network configuration to be fully applied.
[this,ethernetConnected,hotspotConnected]() {
this->networkChangingDelayHandle = 0;
OnNetworkChanged(ethernetConnected,hotspotConnected);
}
);
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
std::vector<IPiPedalModelSubscriber *> t;
t.reserve(this->subscribers.size());
for (size_t i = 0; i < subscribers.size(); ++i)
{
t.push_back(this->subscribers[i]);
}
for (size_t i = 0; i < t.size(); ++i)
{
t[i]->OnNetworkChanging(hotspotConnected);
}
}
void PiPedalModel::OnNetworkChanged(bool ethernetConnected, bool hotspotConnected)
{
FireNetworkChanged();
}
void PiPedalModel::OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType)
{
try {
switch (eventType)
{
case RealtimeMidiEventType::Shutdown:
{
this->RequestShutdown(false);
}
break;
case RealtimeMidiEventType::Reboot:
{
this->RequestShutdown(true);
}
break;
case RealtimeMidiEventType::StartHotspot:
{
WifiConfigSettings settings = storage.GetWifiConfigSettings();
if (!settings.hasSavedPassword_)
{
throw std::runtime_error("Can't start Wi-Fi hotspot because no password has been configured.");
}
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Always;
this->SetWifiConfigSettings(settings);
}
break;
case RealtimeMidiEventType::StopHotspot:
{
WifiConfigSettings settings = storage.GetWifiConfigSettings();
settings.autoStartMode_ = (uint16_t)HotspotAutoStartMode::Never;
this->SetWifiConfigSettings(settings);
}
break;
default:
break;
}
} catch (const std::exception&e)
{
Lv2Log::error(SS("Failed to process realtime MIDI event. " << e.what()));
}
}
void PiPedalModel::RequestShutdown(bool restart)
{
if (GetAdminClient().CanUseAdminClient())
{
GetAdminClient().RequestShutdown(restart);
}
else
{
// ONLY works when interactively logged in.
std::stringstream s;
s << "/usr/sbin/shutdown ";
if (restart)
{
s << "-r";
}
else
{
s << "-P";
}
s << " now";
if (sysExec(s.str().c_str()) != EXIT_SUCCESS)
{
Lv2Log::error("shutdown failed.");
if (restart)
{
throw new PiPedalStateException("Restart request failed.");
}
else
{
throw new PiPedalStateException("Shutdown request failed.");
}
}
}
}
+5 -1
View File
@@ -78,9 +78,11 @@ namespace pipedal
virtual void OnFavoritesChanged(const std::map<std::string, bool> &favorites) = 0;
virtual void OnShowStatusMonitorChanged(bool show) = 0;
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
//virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
virtual void OnErrorMessage(const std::string&message) = 0;
virtual void OnLv2PluginsChanging() = 0;
virtual void OnNetworkChanging(bool hotspotConnected) = 0;
virtual void Close() = 0;
};
@@ -203,6 +205,8 @@ namespace pipedal
virtual void OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, float value) override;
virtual void OnNotifyMidiListen(bool isNote, uint8_t noteOrControl) override;
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom*atomValue) override;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
void OnNotifyPatchProperty(uint64_t instanceId, LV2_URID outputAtomProperty, const std::string &atomJson);
@@ -227,7 +231,7 @@ namespace pipedal
PiPedalModel();
virtual ~PiPedalModel();
void RequestShutdown(bool restart);
virtual PostHandle Post(PostCallback&&fn);
virtual PostHandle PostDelayed(const clock::duration&delay,PostCallback&&fn);
+14 -41
View File
@@ -459,7 +459,6 @@ private:
{
return model.GetAdminClient();
}
void RequestShutdown(bool restart);
std::recursive_mutex writeMutex;
PiPedalModel &model;
@@ -1246,15 +1245,12 @@ public:
}
else if (message == "shutdown")
{
PresetIndex newIndex;
RequestShutdown(false);
model.RequestShutdown(false);
this->Reply(replyTo, "shutdown");
}
else if (message == "restart")
{
PresetIndex newIndex;
RequestShutdown(true);
model.RequestShutdown(true);
this->Reply(replyTo, "restart");
}
else if (message == "deletePresetItem")
@@ -1627,6 +1623,18 @@ private:
Send("onLv2PluginsChanging",true);
Flush();
}
virtual void OnNetworkChanging(bool hotspotConnected) override {
try {
Send("onNetworkChanging",hotspotConnected);
Flush();
} catch (const std::exception&ignored)
{
}
}
virtual void OnErrorMessage(const std::string&message)
{
@@ -1982,38 +1990,3 @@ std::shared_ptr<ISocketFactory> pipedal::MakePiPedalSocketFactory(PiPedalModel &
return std::make_shared<PiPedalSocketFactory>(model);
}
void PiPedalSocketHandler::RequestShutdown(bool restart)
{
if (GetAdminClient().CanUseAdminClient())
{
GetAdminClient().RequestShutdown(restart);
}
else
{
// ONLY works when interactively logged in.
std::stringstream s;
s << "/usr/sbin/shutdown ";
if (restart)
{
s << "-r";
}
else
{
s << "-P";
}
s << " now";
if (sysExec(s.str().c_str()) != EXIT_SUCCESS)
{
Lv2Log::error("shutdown failed.");
if (restart)
{
throw new PiPedalStateException("Restart request failed.");
}
else
{
throw new PiPedalStateException("Shutdown request failed.");
}
}
}
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright (c) 2024 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.
#pragma once
namespace pipedal {
enum class RealtimeMidiEventType {
Shutdown,
Reboot,
StartHotspot,
StopHotspot
};
};
+12
View File
@@ -24,6 +24,7 @@
#include "VuUpdate.hpp"
#include "AudioHost.hpp"
#include "lv2/atom/atom.h"
#include "RealtimeMidiEventType.hpp"
#include <chrono>
namespace pipedal
{
@@ -65,9 +66,15 @@ namespace pipedal
SetOutputVolume,
Lv2ErrorMessage,
RealtimeMidiEvent,
};
struct RealtimeMidiEventRequest {
RealtimeMidiEventType eventType;
};
struct RealtimeNextMidiProgramRequest {
int64_t requestId;
int32_t direction;
@@ -371,6 +378,11 @@ namespace pipedal
write(RingBufferCommand::MidiProgramChange,msg);
}
void OnRealtimeMidiEvent(RealtimeMidiEventType eventType)
{
RealtimeMidiEventRequest msg { eventType};
write(RingBufferCommand::RealtimeMidiEvent,msg);
}
void OnNextMidiProgram(int64_t requestId,int32_t direction)
{
RealtimeNextMidiProgramRequest msg { requestId: requestId, direction:direction};
+30 -10
View File
@@ -960,7 +960,7 @@ bool Storage::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSettings
if (!copyToSave.hasPassword_)
{
copyToSave.hasPassword_ = previousValue.hasPassword_;
copyToSave.password_ = previousValue.hasPassword_;
copyToSave.password_ = previousValue.password_;
copyToSave.hasSavedPassword_ = previousValue.hasPassword_;
} else {
if (copyToSave.IsEnabled())
@@ -1409,7 +1409,7 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings)
{
std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
std::filesystem::path fileName = this->dataRoot / "config" / "SystemMidiBindings.json";
pipedal::ofstream_synced f;
f.open(fileName);
if (f.is_open())
@@ -1422,19 +1422,39 @@ std::vector<MidiBinding> Storage::GetSystemMidiBindings()
{
std::vector<MidiBinding> result;
std::filesystem::path fileName = this->dataRoot / "SystemMidiBindings.json";
std::filesystem::path fileName = this->dataRoot / "config" / "SystemMidiBindings.json";
if (!std::filesystem::exists(fileName)) {
// pick up from legacy location?
fileName = this->configRoot / "config" / "SystemMidiBindings.json";
}
std::ifstream f;
f.open(fileName);
if (f.is_open())
{
json_reader reader(f);
reader.read(&result);
}
else
{
result.push_back(MidiBinding::SystemBinding("prevProgram"));
result.push_back(MidiBinding::SystemBinding("nextProgram"));
try {
json_reader reader(f);
reader.read(&result);
if (result.size() == 2)
{
result.push_back(MidiBinding::SystemBinding("stopHotspot"));
result.push_back(MidiBinding::SystemBinding("startHotspot"));
result.push_back(MidiBinding::SystemBinding("shutdown"));
result.push_back(MidiBinding::SystemBinding("reboot"));
}
return result;
}
catch (const std::exception&e)
{
Lv2Log::warning(SS("Can't read file " << fileName << ". " << e.what()));
}
}
result.clear();
result.push_back(MidiBinding::SystemBinding("prevProgram"));
result.push_back(MidiBinding::SystemBinding("nextProgram"));
result.push_back(MidiBinding::SystemBinding("stopHotspot"));
result.push_back(MidiBinding::SystemBinding("startHotspot"));
result.push_back(MidiBinding::SystemBinding("shutdown"));
result.push_back(MidiBinding::SystemBinding("reboot"));
return result;
}
+1
View File
@@ -18,6 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "TemporaryFile.hpp"
#include <fstream>
using namespace pipedal;
+2
View File
@@ -28,6 +28,8 @@ namespace pipedal {
TemporaryFile(const std::filesystem::path&parentDirectory);
~TemporaryFile();
const std::filesystem::path&Path()const { return path;}
std::string str() const { return path.c_str(); }
const char*c_str() const { return path.c_str(); }
private:
std::filesystem::path path;
};
+251 -16
View File
@@ -35,6 +35,9 @@
#include "UpdaterSecurity.hpp"
#include "SysExec.hpp"
#include "ofstream_synced.hpp"
#include "TemporaryFile.hpp"
#include "ofstream_synced.hpp"
#include <limits>
using namespace pipedal;
namespace fs = std::filesystem;
@@ -165,6 +168,15 @@ void Updater::SetUpdateListener(UpdateListener &&listener)
}
}
static void LogNextStartTime(const std::chrono::steady_clock::time_point &clockTime) {
using namespace std::chrono;
// convert to system_clock;
auto delay = clockTime-steady_clock::now();
auto systemTime = system_clock::now() + duration_cast<system_clock::duration>(delay);
auto t = system_clock::to_time_t(systemTime);
Lv2Log::info(SS("Updater: Next update check at " << std::put_time(std::localtime(&t),"%Y-%m-%d %H:%M:%S")));
}
void Updater::ThreadProc()
{
SetThreadName("UpdateMonitor");
@@ -172,10 +184,28 @@ void Updater::ThreadProc()
pfd.fd = this->event_reader;
pfd.events = POLLIN;
auto lastRetryTime = clock::time_point();
this->updateRetryTime = LoadRetryTime();
try {
while (true)
{
int ret = poll(&pfd, 1, std::chrono::duration_cast<std::chrono::milliseconds>(updateRate).count()); // 1000 ms timeout
if (updateRetryTime != lastRetryTime) // has retry time changed?
{
lastRetryTime = updateRetryTime;
if (updateRetryTime > clock::now())
{
LogNextStartTime(updateRetryTime);
}
}
auto clockDelay = this->updateRetryTime-clock::now();
auto delayMs = std::chrono::duration_cast<std::chrono::milliseconds>(clockDelay).count();
if (delayMs > std::numeric_limits<int>::max()) // could be as little as 32 seconds on a 16-bit int system.
{
delayMs = std::numeric_limits<int>::max();
}
int ret = poll(&pfd, 1, (int)delayMs); // 1000 ms timeout
if (ret == -1)
{
@@ -184,7 +214,10 @@ void Updater::ThreadProc()
}
else if (ret == 0)
{
CheckForUpdate(true);
if (clock::now() >= this->updateRetryTime)
{
CheckForUpdate(true);
}
}
else
{
@@ -195,6 +228,7 @@ void Updater::ThreadProc()
{
if (value == CHECK_NOW_EVENT)
{
CheckForUpdate(true);
}
else if (value == UNCACHED_CHECK_NOW_EVENT)
@@ -418,31 +452,145 @@ static void CheckUpdateHttpResponse(std::string errorCode)
}
}
static std::chrono::system_clock::time_point http_date_to_time_point(const std::string& http_date) {
std::tm tm = {};
std::istringstream ss(http_date);
ss >> std::get_time(&tm, "%a, %d %b %Y %H:%M:%S GMT");
if (ss.fail()) {
throw std::runtime_error("Failed to parse HTTP date: " + http_date);
}
return std::chrono::system_clock::from_time_t(std::mktime(&tm));
}
class GitResponseHeaders {
public:
GitResponseHeaders(const std::filesystem::path path)
{
// HTTP/2 403
// date: Fri, 13 Sep 2024 17:31:22 GMT
//...
// x-ratelimit-limit: 60
// x-ratelimit-remaining: 0
// x-ratelimit-reset: 1726250807
// x-ratelimit-resource: core
// x-ratelimit-used: 60
std::ifstream f {path};
if (!f.is_open())
{
throw std::runtime_error(SS("Can't open file " << path << "."));
}
std::string http;
f >> http >> code;
std::string line;
std::getline(f,line);
while (true)
{
std::getline(f,line);
auto trimPos = line.find_last_not_of("\r\n");
if (trimPos != std::string::npos) {
line = line.substr(trimPos+1);
}
if (!f) break;
auto pos = line.find(':');
if (pos != std::string::npos)
{
std::string tag = line.substr(0,pos);
++pos;
while (pos < line.length() && line[pos] == ' ')
{
++pos;
}
std::string value = line.substr(pos);
uint64_t *pResult = nullptr;
if (tag =="date")
{
this->date = http_date_to_time_point(value);
} else if (tag == "x-ratelimit-limit")
{
pResult = &ratelimit_limit;
} else if (tag == "x-ratelimit-remaining")
{
pResult = &ratelimit_remaining;
} else if (tag == "x-ratelimit-reset")
{
std::istringstream ss {value};
uint64_t reset = 0;
ss >> reset;
auto secs = std::chrono::seconds(reset); // value is seconds in UTC Unix epoch.
this->ratelimit_reset = std::chrono::system_clock::time_point(secs);
} else if (tag == "x-ratelimit-resource")
{
ratelimit_resource = value;
} else if (tag == "x-ratelimit-used")
{
pResult = &ratelimit_used;
}
if (pResult)
{
std::istringstream ss { value};
ss >> *pResult;
}
}
}
}
int code = -1;
std::chrono::system_clock::time_point date;
std::chrono::system_clock::time_point ratelimit_reset;
uint64_t ratelimit_limit = 0;
uint64_t ratelimit_remaining = 0;
uint64_t ratelimit_used = 0;
std::string ratelimit_resource;
bool limit_exceeded() const { return code != 200 && ratelimit_limit != 0 && ratelimit_limit == ratelimit_used; }
};
void Updater::CheckForUpdate(bool useCache)
{
UpdateStatus updateResult;
if (useCache)
{
std::lock_guard lock{mutex};
if (useCache && IsCacheValid(cachedUpdateStatus))
{
this->currentResult = cachedUpdateStatus;
this->currentResult.UpdatePolicy(this->updatePolicy);
if (listener)
if (IsCacheValid(cachedUpdateStatus))
{
listener(this->currentResult);
this->currentResult = cachedUpdateStatus;
this->currentResult.UpdatePolicy(this->updatePolicy);
if (listener)
{
listener(this->currentResult);
}
return;
}
return;
}
updateResult = this->currentResult;
}
if (clock::now() < this->updateRetryTime)
{
// forced or not, do NOT hit the githup api before we're supposed to.
std::lock_guard lock(mutex);
this->currentResult = cachedUpdateStatus;
this->currentResult.UpdatePolicy(this->updatePolicy);
if (listener)
{
listener(this->currentResult);
}
return;
}
// set default next retry time. github failures may postpone even further.
Lv2Log::info("Checking for updates.");
// const std::string responseOption = "-w \"%{response_code}\"";
#ifdef WIN32
responseOption = "-w \"%%{response_code}\""; // windows shell requires doubling of the %%.
#endif
std::string args = SS("-s -L " << GITHUB_RELEASES_URL);
TemporaryFile headerFile { WORKING_DIRECTORY };
std::string args = SS("-s -L " << GITHUB_RELEASES_URL << " -D " << headerFile.str());
updateResult.errorMessage_ = "";
@@ -451,10 +599,33 @@ void Updater::CheckForUpdate(bool useCache)
auto result = sysExecForOutput("curl", args);
if (result.exitCode != EXIT_SUCCESS)
{
RetryAfter(std::chrono::duration_cast<clock::duration>(std::chrono::hours(4)));
throw std::runtime_error("Server has no internet access.");
}
else
{
// hard throttling for github.
RetryAfter(std::chrono::duration_cast<clock::duration>(std::chrono::hours(8)));
GitResponseHeaders githubHeaders { headerFile.Path()};
if (githubHeaders.code != 200)
{
if (
githubHeaders.ratelimit_limit != 0 &&
githubHeaders.ratelimit_limit == githubHeaders.ratelimit_used)
{
std::time_t time = std::chrono::system_clock::to_time_t(githubHeaders.ratelimit_reset);
std::string strTime = std::ctime(&time);
Lv2Log::warning(SS("Updater: github rate limit exceeded. Retry at " << strTime));
this->RetryAfter(githubHeaders.ratelimit_reset-githubHeaders.date);
return;
}
}
if (result.output.length() == 0)
{
throw std::runtime_error("Server has no internet access.");
@@ -468,11 +639,16 @@ void Updater::CheckForUpdate(bool useCache)
// an HTML error.
updateResult.isOnline_ = false;
auto o = vResult.as_object();
std::string message = o->at("message").as_string();
auto status_code = o->at("status_code").as_int64();
throw std::runtime_error(SS("Service error. ()" << status_code << ": " << message << ")"));
}
else
std::string message = "Unknown error.";
if (o->at("message").is_string())
{
message = o->at("message").as_string();
}
throw std::runtime_error(SS("Github Service error: " << message ));
} else if (!vResult.is_array())
{
throw std::runtime_error("Invalid file format error.");
} else
{
json_variant::array_ptr vArray = vResult.as_array();
@@ -534,7 +710,7 @@ void Updater::CheckForUpdate(bool useCache)
}
catch (const std::exception &e)
{
Lv2Log::error(SS("Failed to fetch update info. " << e.what()));
Lv2Log::error(SS("Updater: Failed to fetch update info. " << e.what()));
updateResult.errorMessage_ = e.what();
updateResult.isValid_ = false;
updateResult.isOnline_ = false;
@@ -897,6 +1073,65 @@ static void checkCurlHttpResponse(std::string errorCode)
throw std::runtime_error(message);
}
}
void Updater::RetryAfter(clock::duration delay)
{
namespace chron = std::chrono;
clock::duration hours24 = chron::duration_cast<clock::duration>(chron::hours(24));
if (delay.count() < 0 || delay >= hours24) // non-synced system clock (as Raspberry Pis are prone to without network connections)
{
delay = chron::duration_cast<clock::duration>(chron::hours(6));
}
SaveRetryTime(chron::system_clock::now()+std::chrono::duration_cast<chron::system_clock::duration>(delay));
clock::time_point myRetryTime = clock::now() + delay;
if (this->updateRetryTime < myRetryTime)
{
this->updateRetryTime = myRetryTime;
}
}
void Updater::SaveRetryTime(const std::chrono::system_clock::time_point &time)
{
fs::path retryTimePath = WORKING_DIRECTORY / "retryTime.json";
pipedal::ofstream_synced f {retryTimePath};
if (f.is_open())
{
json_writer writer(f);
auto rep = time.time_since_epoch().count();
writer.write(rep);
}
}
Updater::clock::time_point Updater::LoadRetryTime()
{
using namespace std::chrono;
fs::path retryTimePath = WORKING_DIRECTORY / "retryTime.json";
std::ifstream f {retryTimePath};
if (f.is_open())
{
system_clock::duration::rep tRep = 0;
json_reader reader(f);
reader.read(&tRep);
system_clock::duration duration(tRep);
system_clock::time_point systemTime = system_clock::time_point(duration);
// now convert to stead_clock time with the understanding that system_clock time may be blown.
system_clock::duration systemDuration = systemTime - system_clock::now();
clock::duration clockDuration = duration_cast<clock::duration>(systemDuration);
if (clockDuration > duration_cast<clock::duration>(hours(48))) // is system clock blown?
{
clockDuration = duration_cast<clock::duration>(hours(6));
}
return clock::now() + clockDuration;
}
return clock::now();
}
void Updater::DownloadUpdate(const std::string &url, std::filesystem::path *file, std::filesystem::path *signatureFile)
{
std::string filename, signatureUrl;
+14 -1
View File
@@ -127,6 +127,19 @@ namespace pipedal
void DownloadUpdate(const std::string &url, std::filesystem::path*file, std::filesystem::path*signatureFile);
UpdateStatus GetCurrentStatus() const { return this->currentResult; }
private:
using clock = std::chrono::steady_clock;
void RetryAfter(clock::duration delay);
template<typename REP,typename PERIOD>
void RetryAfter(std::chrono::duration<REP,PERIOD> duration)
{
RetryAfter(std::chrono::duration_cast<clock::duration>(duration));
}
void SaveRetryTime(const std::chrono::system_clock::time_point &time);
clock::time_point LoadRetryTime();
std::string GetUpdateFilename(const std::string &url);
std::string GetSignatureUrl(const std::string &url);
@@ -140,7 +153,6 @@ namespace pipedal
UpdateStatus cachedUpdateStatus;
bool stopped = false;
using clock = std::chrono::steady_clock;
int event_reader = -1;
int event_writer = -1;
@@ -154,5 +166,6 @@ namespace pipedal
bool hasInfo = false;
UpdateStatus currentResult;
static clock::duration updateRate;
clock::time_point updateRetryTime;
};
}
+8 -1
View File
@@ -1320,6 +1320,13 @@ void WebServerImpl::DisplayIpAddresses()
std::string wifiAddress = getIpv4Address("wlan0");
if (wifiAddress.length() != 0)
{
Lv2Log::info(SS("Listening on Wi-Fi address " << wifiAddress << ":" << this->port));
if (wifiAddress == "10.42.0.1")
{
Lv2Log::info(SS("Listening on Wi-Fi hotspot address " << wifiAddress << ":" << this->port));
}
else
{
Lv2Log::info(SS("Listening on Wi-Fi address " << wifiAddress << ":" << this->port));
}
}
}
+28 -6
View File
@@ -472,6 +472,33 @@ public:
}
};
static std::string StripPortNumber(const std::string &fromAddress)
{
std::string address = fromAddress;
if (address.size() == 0)
return fromAddress;
char lastChar = address[address.size() - 1];
size_t pos = address.find_last_of(':');
// if ipv6, make sure we found an actual port address.
size_t posBracket = address.find_last_of(']');
if (posBracket != std::string::npos && pos != std::string::npos)
{
if (posBracket > pos)
{
pos = std::string::npos;
}
}
if (pos != std::string::npos)
{
address = address.substr(0, pos);
}
return address;
}
/* When hosting a react app, replace /var/config.json with
data that will connect the react app with our socket server.
*/
@@ -491,13 +518,8 @@ public:
}
std::string GetConfig(const std::string &fromAddress)
{
#define LINK_LOCAL_WEB_SOCKET 1
#if LINK_LOCAL_WEB_SOCKET
std::string webSocketAddress = GetLinkLocalAddress(fromAddress);
std::string webSocketAddress = GetNonLinkLocalAddress(StripPortNumber(fromAddress));
Lv2Log::info(SS("Web Socket Address: " << webSocketAddress << ":" << portNumber));
#else
std::string webSocketAddress = "*";
#endif
std::stringstream s;
+3 -14
View File
@@ -1,14 +1,9 @@
Installer:
sudo systemctl restart polkitd.service
- Did I break ALSA device config?be
- Shutdown/reboot midi bindings
- Midi binding for hotspotManager.
- verify that scanning doesn't cause overruns.
- address change handlers in client (and browser?)
- Notify hotspotManager on settings changed.
- Midi binding for hotspotManager.
- Keybaord escape for hotspotManager.
- verify stripping in installer.
- verify update with no keyring.
@@ -17,21 +12,15 @@ Installer:
X Review docs changes once we go live.
- Back button after reloads on Android.
- pidedal_nm_p2p2 service not started from web ui.
- pipedal_nm_p2pd connection not visible.
- hdcpcd spurious ipv6 error messages.
- also stopping on startup, even with big buffers.
- pipedald admin needs to fork in response to a wifi p2p update or do it on a thread.
Localisation: support non-UTF8 code pages.
- unicode commandline arguments.
- unicode filenames.
- review unicode filenames (this is probably ok)
- make app use the same theme settings as the website.
- BUG: gcs when we have an animated output control
- Shutdown/reboot midi bindings
- versioning.
- do we want to take a second crack at establishin multi-p2p connections with the driver_param?