Alsa head corruption in error handling.
This commit is contained in:
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -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...";
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user