From 2a851dbd4bfb03f7642001b1067b9639d0378f08 Mon Sep 17 00:00:00 2001 From: Robin Davies Date: Sun, 15 Sep 2024 06:30:16 -0400 Subject: [PATCH] Alsa head corruption in error handling. --- PiPedalCommon/src/WifiConfigSettings.cpp | 16 +- .../src/include/WifiConfigSettings.hpp | 2 +- react/public/index.html | 21 +- react/src/AppThemed.tsx | 2 + react/src/BankDialog.tsx | 2 +- react/src/PiPedalModel.tsx | 108 ++++++- react/src/SystemMidiBindingsDialog.tsx | 17 ++ react/src/WifiConfigSettings.tsx | 2 + src/AudioHost.cpp | 53 ++++ src/AudioHost.hpp | 2 + src/CMakeLists.txt | 3 +- src/ConfigMain.cpp | 3 + src/HotspotManager.cpp | 8 +- src/Ipv6Helpers.cpp | 58 ++-- src/Ipv6Helpers.hpp | 2 +- src/PiPedalModel.cpp | 98 ++++++- src/PiPedalModel.hpp | 6 +- src/PiPedalSocket.cpp | 55 +--- src/RealtimeMidiEventType.hpp | 29 ++ src/RingBufferReader.hpp | 12 + src/Storage.cpp | 40 ++- src/TemporaryFile.cpp | 1 + src/TemporaryFile.hpp | 2 + src/Updater.cpp | 267 ++++++++++++++++-- src/Updater.hpp | 15 +- src/WebServer.cpp | 9 +- src/WebServerConfig.cpp | 34 ++- todo.txt | 17 +- 28 files changed, 744 insertions(+), 140 deletions(-) create mode 100644 src/RealtimeMidiEventType.hpp diff --git a/PiPedalCommon/src/WifiConfigSettings.cpp b/PiPedalCommon/src/WifiConfigSettings.cpp index ae525c3..f0249f5 100644 --- a/PiPedalCommon/src/WifiConfigSettings.cpp +++ b/PiPedalCommon/src/WifiConfigSettings.cpp @@ -30,6 +30,7 @@ #include #include #include +#include // 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]; diff --git a/PiPedalCommon/src/include/WifiConfigSettings.hpp b/PiPedalCommon/src/include/WifiConfigSettings.hpp index 3713661..44492aa 100644 --- a/PiPedalCommon/src/include/WifiConfigSettings.hpp +++ b/PiPedalCommon/src/include/WifiConfigSettings.hpp @@ -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; } diff --git a/react/public/index.html b/react/public/index.html index 0e138f2..7383ce2 100644 --- a/react/public/index.html +++ b/react/public/index.html @@ -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); diff --git a/react/src/AppThemed.tsx b/react/src/AppThemed.tsx index fc526dc..acf2d61 100644 --- a/react/src/AppThemed.tsx +++ b/react/src/AppThemed.tsx @@ -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..."; } diff --git a/react/src/BankDialog.tsx b/react/src/BankDialog.tsx index 94ce27b..2b0940f 100644 --- a/react/src/BankDialog.tsx +++ b/react/src/BankDialog.tsx @@ -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 ""; } diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index 738a949..680b5fc 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -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; diff --git a/react/src/SystemMidiBindingsDialog.tsx b/react/src/SystemMidiBindingsDialog.tsx index 33185bc..25112e6 100644 --- a/react/src/SystemMidiBindingsDialog.tsx +++ b/react/src/SystemMidiBindingsDialog.tsx @@ -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)); diff --git a/react/src/WifiConfigSettings.tsx b/react/src/WifiConfigSettings.tsx index 401e22f..7a80a4a 100644 --- a/react/src/WifiConfigSettings.tsx +++ b/react/src/WifiConfigSettings.tsx @@ -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; diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index fb742db..d248fae 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -296,6 +296,10 @@ private: SystemMidiBinding nextMidiBinding; SystemMidiBinding prevMidiBinding; + SystemMidiBinding startHotspotMidiBinding; + SystemMidiBinding stopHotspotMidiBinding; + SystemMidiBinding rebootMidiBinding; + SystemMidiBinding shutdownMidiBinding; JackChannelSelection channelSelection; std::atomic 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 &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 &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())); } } } diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp index 2455f88..d5a6803 100644 --- a/src/AudioHost.hpp +++ b/src/AudioHost.hpp @@ -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; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e9b5047..ba8aede 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 ) diff --git a/src/ConfigMain.cpp b/src/ConfigMain.cpp index bb26e61..423298c 100644 --- a/src/ConfigMain.cpp +++ b/src/ConfigMain.cpp @@ -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) diff --git a/src/HotspotManager.cpp b/src/HotspotManager.cpp index 2783ec7..1d0612c 100644 --- a/src/HotspotManager.cpp +++ b/src/HotspotManager.cpp @@ -725,6 +725,12 @@ static std::vector> GetAccessPointSsids(std::vectorstate == State::Error) return; + + if (!wlanDevice || !wlanWirelessDevice) + { + // devices are transitioning. Do nothing. + } std::vector allAccessPoints = GetAllAccessPoints(); std::vector> allAccessPointSsids = GetAccessPointSsids(allAccessPoints); std::vector> 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) { diff --git a/src/Ipv6Helpers.cpp b/src/Ipv6Helpers.cpp index a758a3a..7634bbd 100644 --- a/src/Ipv6Helpers.cpp +++ b/src/Ipv6Helpers.cpp @@ -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); } diff --git a/src/Ipv6Helpers.hpp b/src/Ipv6Helpers.hpp index 68c788d..abfefed 100644 --- a/src/Ipv6Helpers.hpp +++ b/src/Ipv6Helpers.hpp @@ -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); diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 75f49c7..90005b8 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -37,6 +37,7 @@ #include "Lv2PluginChangeMonitor.hpp" #include "HotspotManager.hpp" #include "DBusToLv2Log.hpp" +#include "SysExec.hpp" #ifndef NO_MLOCK #include @@ -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 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."); + } + } + } +} diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 7f5ed31..0eb3575 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -78,9 +78,11 @@ namespace pipedal virtual void OnFavoritesChanged(const std::map &favorites) = 0; virtual void OnShowStatusMonitorChanged(bool show) = 0; virtual void OnSystemMidiBindingsChanged(const std::vector&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); diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp index 33d7f2e..558602a 100644 --- a/src/PiPedalSocket.cpp +++ b/src/PiPedalSocket.cpp @@ -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 pipedal::MakePiPedalSocketFactory(PiPedalModel & return std::make_shared(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."); - } - } - } -} diff --git a/src/RealtimeMidiEventType.hpp b/src/RealtimeMidiEventType.hpp new file mode 100644 index 0000000..8867ec9 --- /dev/null +++ b/src/RealtimeMidiEventType.hpp @@ -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 + }; + +}; \ No newline at end of file diff --git a/src/RingBufferReader.hpp b/src/RingBufferReader.hpp index be51fe2..2e1cbf1 100644 --- a/src/RingBufferReader.hpp +++ b/src/RingBufferReader.hpp @@ -24,6 +24,7 @@ #include "VuUpdate.hpp" #include "AudioHost.hpp" #include "lv2/atom/atom.h" +#include "RealtimeMidiEventType.hpp" #include 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}; diff --git a/src/Storage.cpp b/src/Storage.cpp index 5d17c2c..9e05424 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -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 &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 Storage::GetSystemMidiBindings() { std::vector 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; } diff --git a/src/TemporaryFile.cpp b/src/TemporaryFile.cpp index bababdd..26ad12f 100644 --- a/src/TemporaryFile.cpp +++ b/src/TemporaryFile.cpp @@ -18,6 +18,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "TemporaryFile.hpp" +#include using namespace pipedal; diff --git a/src/TemporaryFile.hpp b/src/TemporaryFile.hpp index dd0e1a5..7f1f2a9 100644 --- a/src/TemporaryFile.hpp +++ b/src/TemporaryFile.hpp @@ -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; }; diff --git a/src/Updater.cpp b/src/Updater.cpp index 46881f4..3733345 100644 --- a/src/Updater.cpp +++ b/src/Updater.cpp @@ -35,6 +35,9 @@ #include "UpdaterSecurity.hpp" #include "SysExec.hpp" #include "ofstream_synced.hpp" +#include "TemporaryFile.hpp" +#include "ofstream_synced.hpp" +#include 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(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(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(clockDelay).count(); + if (delayMs > std::numeric_limits::max()) // could be as little as 32 seconds on a 16-bit int system. + { + delayMs = std::numeric_limits::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(std::chrono::hours(4))); throw std::runtime_error("Server has no internet access."); } else { + // hard throttling for github. + RetryAfter(std::chrono::duration_cast(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(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(chron::hours(6)); + } + SaveRetryTime(chron::system_clock::now()+std::chrono::duration_cast(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(systemDuration); + if (clockDuration > duration_cast(hours(48))) // is system clock blown? + { + clockDuration = duration_cast(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; diff --git a/src/Updater.hpp b/src/Updater.hpp index e0c3325..38e5c44 100644 --- a/src/Updater.hpp +++ b/src/Updater.hpp @@ -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 + void RetryAfter(std::chrono::duration duration) + { + RetryAfter(std::chrono::duration_cast(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; }; } \ No newline at end of file diff --git a/src/WebServer.cpp b/src/WebServer.cpp index 406b42a..7ffc570 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -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)); + } } } \ No newline at end of file diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp index fbba021..05f085b 100644 --- a/src/WebServerConfig.cpp +++ b/src/WebServerConfig.cpp @@ -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; diff --git a/todo.txt b/todo.txt index e8b25ca..2268cc5 100644 --- a/todo.txt +++ b/todo.txt @@ -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?