Clean shutdown, stability

This commit is contained in:
Robin Davies
2024-09-21 13:18:00 -04:00
parent 951a7a73de
commit 2b13dc1c96
30 changed files with 820 additions and 358 deletions
+7
View File
@@ -10,6 +10,7 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Edge",
"port": 9222,
@@ -148,7 +149,13 @@
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Add alsa source directories.",
"text": "directory $cd:$cwd:/usr/src/alsa-lib-1.2.8",
"ignoreFailures": true
}
]
},
+4 -3
View File
@@ -11,7 +11,7 @@ set (CMAKE_INSTALL_PREFIX "/usr/")
include(CTest)
enable_testing()
add_subdirectory("submodules/pipedal_p2pd")
#add_subdirectory("submodules/pipedal_p2pd")
add_subdirectory("PiPedalCommon")
@@ -82,8 +82,9 @@ install(CODE
EXECUTABLES
${CMAKE_INSTALL_PREFIX}/sbin/pipedaladmind
${CMAKE_INSTALL_PREFIX}/sbin/pipedald
${CMAKE_INSTALL_PREFIX}/sbin/pipedal_nm_p2pd
${CMAKE_INSTALL_PREFIX}/sbin/pipedal_p2pd
${CMAKE_INSTALL_PREFIX}/sbin/pipedal_update
#${CMAKE_INSTALL_PREFIX}/sbin/pipedal_nm_p2pd
#${CMAKE_INSTALL_PREFIX}/sbin/pipedal_p2pd
${CMAKE_INSTALL_PREFIX}/bin/pipedalconfig
)
]]
+2
View File
@@ -6,6 +6,7 @@
#include "ss.hpp"
#include <chrono>
#include <cassert>
#include "util.hpp"
DBusDispatcher::DBusDispatcher(bool systemBus)
{
@@ -87,6 +88,7 @@ void DBusDispatcher::WakeThread()
}
void DBusDispatcher::ThreadProc()
{
pipedal::SetThreadName("dbusd");
try
{
+1 -11
View File
@@ -30,7 +30,7 @@
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <memory>
#include <unistd.h> // for gethostname()
#include "util.hpp"
using namespace pipedal;
using namespace std;
@@ -89,16 +89,6 @@ bool WifiConfigSettings::ValidateCountryCode(const std::string &text)
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()
{
+1 -1
View File
@@ -150,7 +150,7 @@ namespace pipedal
static void error(const char *format, ...)
{
if (Lv2Log::log_level_ >= LogLevel::Warning)
if (Lv2Log::log_level_ >= LogLevel::Error)
{
va_list arglist;
va_start(arglist, format);
+3
View File
@@ -35,4 +35,7 @@ namespace pipedal {
std::u32string ToUtf32(const std::string &s);
inline bool isPathSeperator(char c) { return c == '/' || c == std::filesystem::path::preferred_separator;}
std::string GetHostName();
}
+12
View File
@@ -100,3 +100,15 @@ std::u32string pipedal::ToUtf32(const std::string &s)
}
return result.str();
}
std::string pipedal::GetHostName()
{
char buffer[1024];
if (gethostname(buffer,1024) != 0)
{
buffer[0] = '\0';
}
buffer[1023] = '\0';
return buffer;
}
Binary file not shown.
Binary file not shown.
+46 -22
View File
@@ -6,7 +6,7 @@ import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Divider from '@mui/material/Divider';
import ArrowBackIcon from '@mui/icons-material//ArrowBack';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import JackHostStatus from './JackHostStatus';
import { PiPedalError } from './PiPedalError';
@@ -16,12 +16,9 @@ import { Theme, createStyles } from '@mui/material/styles';
import { withStyles, WithStyles } from '@mui/styles';
import Slide, { SlideProps } from '@mui/material/Slide';
interface AboutDialogProps extends WithStyles<typeof styles> {
open: boolean;
onClose: () => void;
};
interface AboutDialogState {
@@ -76,6 +73,15 @@ const styles = (theme: Theme) => createStyles({
},
secondaryItem: {
},
can_select: {
userSelect: "text",
cursor: "text"
},
no_select: {
userSelect: "none",
cursor: "text"
}
@@ -173,7 +179,7 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
this.updateNotifications();
}
componentDidUpdate(prevProps: Readonly<AboutDialogProps>, prevState: Readonly<AboutDialogState>, snapshot: any): void {
super.componentDidUpdate?.(prevProps,prevState,snapshot);
super.componentDidUpdate?.(prevProps, prevState, snapshot);
this.updateNotifications();
}
@@ -185,6 +191,11 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
render() {
let classes = this.props.classes;
let addressKey = 0;
let serverVersion = this.model.serverVersion?.serverVersion ?? "";
let nPos = serverVersion.indexOf(' ');
if (nPos !== -1) {
serverVersion = serverVersion.substring(nPos + 1);
}
return (
<DialogEx tag="about" fullScreen open={this.props.open}
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}
@@ -210,13 +221,16 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
overflowX: "hidden", overflowY: "auto", userSelect: "text"
}}
>
<div style={{ margin: 24 }}>
<Typography noWrap display="block" variant="h6" color="textPrimary">
PiPedal <span style={{ fontSize: "0.7em" }}>
{(this.model.serverVersion ? this.model.serverVersion.serverVersion : "")
+ (this.model.serverVersion?.debug ? " (Debug)" : "")}
</span>
</Typography>
<div id="debug_info" className={classes.can_select} style={{ margin: 24 }}>
<div style={{ display: "flex", flexFlow: "row nowrap" }}>
<Typography noWrap display="block" variant="h6" color="textPrimary" style={{flexGrow: 1, flexShrink: 1}}>
PiPedal <span style={{ fontSize: "0.7em" }}>
{serverVersion
+ (this.model.serverVersion?.debug ? " (Debug)" : "")}
</span>
</Typography>
</div>
<Typography noWrap display="block" variant="body2" style={{ marginBottom: 12 }} >
Copyright &#169; 2022-2024 Robin Davies.
</Typography>
@@ -235,24 +249,34 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
<Typography noWrap display="block" variant="caption" >
ADDRESSES
</Typography>
<div style={{marginBottom: 16}}>
{
this.model.serverVersion?.webAddresses.map((address) =>
(
<Typography key={addressKey++} display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
{address}
</Typography>
))
<div style={{ marginBottom: 16 }}>
{
this.model.serverVersion?.webAddresses.map((address) =>
(
<Typography key={addressKey++} display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
{address}
</Typography>
))
}
}
</div>
<Divider />
<Typography noWrap display="block" variant="caption" >
SERVER OS
</Typography>
<div style={{ marginBottom: 16 }}>
<Typography display="block" variant="body2" style={{ marginBottom: 0, marginLeft: 24 }} >
{this.model.serverVersion?.osVersion ?? ""}
</Typography>
</div>
</div><div>
<Divider />
<Typography display="block" variant="caption" >
LEGAL NOTICES
</Typography>
<div dangerouslySetInnerHTML={{ __html: this.state.openSourceNotices }} style={{ fontSize: "0.8em",maxWidth: 400 }}>
<div dangerouslySetInnerHTML={{ __html: this.state.openSourceNotices }} style={{ fontSize: "0.8em", maxWidth: 400 }}>
</div>
</div>
+8 -4
View File
@@ -59,11 +59,13 @@ const theme = createTheme(
styleOverrides: {
containedPrimary: {
borderRadius: '9999px',
paddingLeft: "14px", paddingRight: "16px"
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
},
containedSecondary: {
borderRadius: '9999px',
paddingLeft: "14px", paddingRight: "16px"
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
}
},
@@ -103,11 +105,13 @@ const theme = createTheme(
styleOverrides: {
containedPrimary: {
borderRadius: '9999px',
paddingLeft: "14px", paddingRight: "16px"
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
},
containedSecondary: {
borderRadius: '9999px',
paddingLeft: "14px", paddingRight: "16px"
paddingLeft: "16px", paddingRight: "16px",
textTransform: "none"
}
},
+92 -31
View File
@@ -48,7 +48,7 @@ import SettingsDialog from './SettingsDialog';
import AboutDialog from './AboutDialog';
import BankDialog from './BankDialog';
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo,wantsLoadingScreen } from './PiPedalModel';
import { PiPedalModelFactory, PiPedalModel, State, ZoomedControlInfo, wantsLoadingScreen } from './PiPedalModel';
import ZoomedUiControl from './ZoomedUiControl'
import MainPage from './MainPage';
import DialogContent from '@mui/material/DialogContent';
@@ -67,7 +67,7 @@ import { ReactComponent as SaveBankAsIcon } from './svg/ic_save_bank_as.svg';
import { ReactComponent as EditBanksIcon } from './svg/ic_edit_banks.svg';
import { ReactComponent as SettingsIcon } from './svg/ic_settings.svg';
import { ReactComponent as HelpOutlineIcon } from './svg/ic_help_outline.svg';
import DialogEx from './DialogEx';
import DialogEx, { DialogStackState } from './DialogEx';
const appStyles = (theme: Theme) => createStyles({
@@ -139,10 +139,11 @@ const appStyles = (theme: Theme) => createStyles({
marginTop: 0,
fontWeight: 500,
fontSize: "13pt",
maxWidth: 350,
opacity: 1,
zIndex: 2010,
paddingTop: 12,
gravity: "center",
textAlign: "center"
},
errorMessageBox: {
@@ -164,8 +165,6 @@ const appStyles = (theme: Theme) => createStyles({
position: "relative",
top: "20%",
color: isDarkMode() ? theme.palette.text.secondary : "#888",
marginLeft: "auto",
marginRight: "auto",
// border: "3px solid #888",
borderRadius: "12px",
padding: "12px",
@@ -351,9 +350,40 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
this.handleCloseAlert = this.handleCloseAlert.bind(this);
this.banksChangedHandler = this.banksChangedHandler.bind(this);
this.showStatusMonitorHandler = this.showStatusMonitorHandler.bind(this);
this.onPopStateHandler = this.onPopStateHandler.bind(this);
}
showDrawer() {
if (!this.state.isDrawerOpen) {
this.setState({ isDrawerOpen: true })
this.pushOpenMenuState();
}
}
hideDrawer(loadingDialog: boolean = false) {
if (!loadingDialog && window.location.hash === "#menu") {
window.history.back();
} else {
this.setState({ isDrawerOpen: false })
}
}
pushOpenMenuState() {
let stackState = { tag: "OpenMenu", previousState: null };
window.history.replaceState(stackState, "", window.location.href);
window.history.pushState({}, "", "#menu");
}
onPopStateHandler(ev: PopStateEvent) {
let stackState: DialogStackState | null = ev.state as (DialogStackState | null);
if (stackState && stackState.tag === "OpenMenu") {
if (this.state.isDrawerOpen) {
this.setState({ isDrawerOpen: false });
}
ev.stopPropagation();
window.history.replaceState(stackState.previousState, "", window.location.href);
return true;
}
return false;
}
onOpenBank(bankId: number) {
this.model_.openBank(bankId)
@@ -524,11 +554,9 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
return undefined;
}
promptForUpdateHandler(newValue: boolean) {
if (this.model_.enableAutoUpdate)
{
if (this.state.updateDialogOpen !== newValue)
{
this.setState({updateDialogOpen: newValue});
if (this.model_.enableAutoUpdate) {
if (this.state.updateDialogOpen !== newValue) {
this.setState({ updateDialogOpen: newValue });
}
}
}
@@ -537,6 +565,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
super.componentDidMount();
window.addEventListener("beforeunload", this.beforeUnloadListener);
window.addEventListener("unload", this.unloadListener);
window.addEventListener("popstate", this.onPopStateHandler);
this.model_.errorMessage.addOnChangedHandler(this.errorChangeHandler_);
this.model_.state.addOnChangedHandler(this.stateChangeHandler_);
@@ -546,6 +575,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
this.model_.promptForUpdate.addOnChangedHandler(this.promptForUpdateHandler);
this.alertMessageChangedHandler();
}
@@ -568,6 +598,9 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
super.componentWillUnmount();
window.removeEventListener("beforeunload", this.beforeUnloadListener);
window.removeEventListener("popstate", this.onPopStateHandler);
this.model_.promptForUpdate.removeOnChangedHandler(this.promptForUpdateHandler);
this.model_.errorMessage.removeOnChangedHandler(this.errorChangeHandler_);
this.model_.state.removeOnChangedHandler(this.stateChangeHandler_);
@@ -634,12 +667,6 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
}
}
showDrawer() {
this.setState({ isDrawerOpen: true })
}
hideDrawer() {
this.setState({ isDrawerOpen: false })
}
shortBankList(banks: BankIndex): BankIndexEntry[] {
let n = this.state.bankDisplayItems;
let entries = banks.entries;
@@ -796,7 +823,11 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
{
showBankSelectDialog && (
<ListItem button key={'bankDOTDOTDOT'} selected={false}
onClick={() => this.handleDrawerSelectBank()}
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerSelectBank();
}}
>
<ListItemText primary={"..."} />
@@ -808,19 +839,34 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</List>
<Divider />
<List>
<ListItem button key='RenameBank' onClick={() => { this.handleDrawerRenameBank() }}>
<ListItem button key='RenameBank'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerRenameBank()
}}>
<ListItemIcon >
<RenameOutlineIcon color='inherit' className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='Rename bank' />
</ListItem>
<ListItem button key='SaveBank' onClick={() => { this.handleDrawerSaveBankAs() }} >
<ListItem button key='SaveBank'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerSaveBankAs();
}} >
<ListItemIcon>
<SaveBankAsIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='Save as new bank' />
</ListItem>
<ListItem button key='EditBanks' onClick={() => { this.handleDrawerManageBanks(); }}>
<ListItem button key='EditBanks'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerManageBanks();
}}>
<ListItemIcon>
<EditBanksIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
@@ -829,19 +875,34 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</List>
<Divider />
<List>
<ListItem button key='Settings' onClick={() => { this.handleDrawerSettingsClick() }}>
<ListItem button key='Settings'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerSettingsClick()
}}>
<ListItemIcon>
<SettingsIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='Settings' />
</ListItem>
<ListItem button key='About' onClick={() => { this.handleDrawerAboutClick() }}>
<ListItem button key='About'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerAboutClick();
}}>
<ListItemIcon>
<HelpOutlineIcon color="inherit" className={classes.menuIcon} />
</ListItemIcon>
<ListItemText primary='About' />
</ListItem>
<ListItem button key='Donations' onClick={() => { this.handleDrawerDonationClick() }}>
<ListItem button key='Donations'
onClick={(ev) => {
ev.stopPropagation();
this.hideDrawer(true);
this.handleDrawerDonationClick();
}}>
<ListItemIcon >
<VolunteerActivismIcon className={classes.menuIcon} color="inherit" />
</ListItemIcon>
@@ -865,10 +926,10 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</div>
</main>
<BankDialog show={this.state.bankDialogOpen} isEditDialog={this.state.editBankDialogOpen} onDialogClose={() => this.setState({ bankDialogOpen: false })} />
{ (this.state.aboutDialogOpen)&&
(
<AboutDialog open={this.state.aboutDialogOpen} onClose={() => this.setState({ aboutDialogOpen: false })} />
)}
{(this.state.aboutDialogOpen) &&
(
<AboutDialog open={this.state.aboutDialogOpen} onClose={() => this.setState({ aboutDialogOpen: false })} />
)}
<SettingsDialog
open={this.state.isSettingsDialogOpen}
onboarding={this.state.onboarding}
@@ -931,7 +992,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<div className={classes.errorContent} style={{
display: (
wantsLoadingScreen(this.state.displayState)
? "block" : "none"
? "block" : "none"
)
}}
>
@@ -941,7 +1002,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<div className={classes.loadingBoxItem}>
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
</div>
<Typography noWrap variant="body2" className={classes.progressText}>
<Typography display="block" noWrap variant="body2" className={classes.progressText}>
{this.getReloadingMessage()}
</Typography>
</div>
@@ -961,7 +1022,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</p>
</div>
<div style={{ paddingTop: 50, paddingLeft: 36, textAlign: "left" }}>
<Button variant='contained' color="primary"
<Button variant='contained' color="primary"
onClick={() => this.handleReload()} >
Reload
</Button>
+45 -23
View File
@@ -31,21 +31,17 @@ interface DialogExState {
}
class DialogStackEntry {
constructor(tag: string)
constructor(dialog: DialogEx)
{
this.tag = tag;
this.tag = dialog.props.tag;
this.dialog = dialog;
}
tag: string;
dialog: DialogEx;
};
let dialogStack: DialogStackEntry[] = [];
function peekDialogStack(): string {
if (dialogStack.length !== 0) {
return dialogStack[dialogStack.length-1].tag;
}
return "";
}
function popDialogStack(): void {
if (dialogStack.length !== 0)
@@ -53,12 +49,33 @@ function popDialogStack(): void {
dialogStack.splice(dialogStack.length-1,1);
}
}
function pushDialogStack(tag: string): void {
dialogStack.push(new DialogStackEntry(tag));
function pushDialogStack(dialog: DialogEx): void {
dialogStack.push(new DialogStackEntry(dialog));
}
export interface DialogStackState{
tag: String;
previousState: DialogStackState | null;
};
// Close all dialogs higher in the dialog stack than "tag".
export function removeDialogStackEntriesAbove(tag: string)
{
for (let i = dialogStack.length-1; i >= 0; --i)
{
let entry = dialogStack[i];
if (entry.tag === tag)
{
return;
}
popDialogStack();
if (entry.dialog.props.open && entry.dialog.props.onClose)
{
entry.dialog.props.onClose({}, "backdropClick");
}
}
}
class DialogEx extends React.Component<DialogExProps,DialogExState> {
constructor(props: DialogExProps)
{
@@ -75,22 +92,28 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
hasHooks: boolean = false;
stateWasPopped: boolean = false;
handlePopState(e: any): any
handlePopState(ev: PopStateEvent): any
{
let shouldClose = (peekDialogStack() === this.props.tag);
if (shouldClose)
{
let evTag: DialogStackState | null = ev.state as DialogStackState | null;
if (evTag && evTag.tag === this.props.tag) {
removeDialogStackEntriesAbove(this.props.tag);
if (!this.stateWasPopped)
{
this.stateWasPopped = true;
popDialogStack();
if (this.props.open) {
this.props.onClose?.(e,"backdropClick");
this.props.onClose?.(ev,"backdropClick");
}
}
window.history.replaceState(evTag.previousState,"",window.location.href);
window.removeEventListener("popstate",this.handlePopState);
e.stopPropagation();
if (window.location.hash === "#menu") // The menu popstate is next?
{
window.history.back(); // then clear off the menu popstate too.
}
ev.stopPropagation();
}
}
@@ -105,19 +128,18 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> {
this.stateWasPopped = false;
window.addEventListener("popstate",this.handlePopState);
pushDialogStack(this.props.tag);
let state: {tag: string} = {tag: this.props.tag};
// eslint-disable-next-line no-restricted-globals
history.pushState(
state,
pushDialogStack(this);
let dialogStackState: DialogStackState = {tag: this.props.tag,previousState: window.history.state as DialogStackState | null};
window.history.replaceState(dialogStackState,"",window.location.href);
window.history.pushState(
null,
"",
"#" + this.props.tag
);
} else {
if (!this.stateWasPopped)
{
// eslint-disable-next-line no-restricted-globals
history.back();
window.history.back();
}
}
}
+31 -1
View File
@@ -793,17 +793,47 @@ export class PiPedalModel //implements PiPedalModel
return this.webSocket;
}
androidReconnectTimeout?: NodeJS.Timeout = undefined;
cancelAndroidReconnectTimer()
{
if (this.androidReconnectTimeout) {
clearTimeout(this.androidReconnectTimeout);
this.androidReconnectTimeout = undefined;
}
}
startAndroidReconnectTimer()
{
this.cancelAndroidReconnectTimer();
this.androidReconnectTimeout = setTimeout(()=>{
this.androidReconnectTimeout = undefined;
this.androidHost?.setDisconnected(true);
},20*1000);
}
onSocketConnectionLost() {
// remove all the events and subscriptions we have.
if (this.isClosed)
{
return; // page unloading. do NOT change the UI.
}
this.vuSubscriptions = [];
this.monitorPatchPropertyListeners = [];
if (this.isAndroidHosted()) {
this.androidHost?.setDisconnected(true);
// if unexpected, go back to the device browser immediately.
if (this.reconnectReason === ReconnectReason.Disconnected)
{
this.androidHost?.setDisconnected(true);
} else {
this.startAndroidReconnectTimer();
}
}
}
onSocketReconnected() {
this.cancelOnNetworkChanging();
this.cancelAndroidReconnectTimer();
if (this.isAndroidHosted()) {
this.androidHost?.setDisconnected(false);
}
+1 -1
View File
@@ -278,7 +278,7 @@ class PiPedalSocket {
ws.onerror = null;
reject("Connection closed unexpectedly.");
};
ws.onerror = (evWheent: Event) => {
ws.onerror = (evt: Event) => {
ws.onclose = null;
ws.onerror = null;
reject("Failed to connect.");
+3 -2
View File
@@ -451,7 +451,6 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
InputLabelProps={{
shrink: true
}}
disabled={!enabled}
/>
<div style={{ marginBottom: 8, flexGrow: 1, flexBasis: 1 }}>
<form autoComplete='off' onSubmit={() => false}> {/*Prevents chrome from saving passwords */}
@@ -477,7 +476,9 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
}}
InputProps={{
startAdornment:
this.props.wifiConfigSettings.hasSavedPassword && !this.state.hasPassword && !this.state.showPassword ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : ""
!this.state.hasPassword && !this.state.showPassword ?
(this.props.wifiConfigSettings.hasSavedPassword? "(Unchanged)" : "(Required)")
: ""
,
endAdornment: (
<IconButton size="small"
+11 -13
View File
@@ -256,17 +256,15 @@ namespace pipedal
std::mutex terminateSync;
bool terminateAudio_ = false;
std::atomic<bool> terminateAudio_ = false;
void terminateAudio(bool terminate)
{
std::lock_guard lock{terminateSync};
this->terminateAudio_ = terminate;
}
bool terminateAudio()
{
std::lock_guard lock{terminateSync};
return this->terminateAudio_;
}
@@ -1297,10 +1295,10 @@ namespace pipedal
FillOutputBuffer();
err = snd_pcm_start(handle);
// if (err < 0)
// {
// throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
// }
if (err < 0)
{
throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
}
return;
}
else if (err == -ESTRPIPE)
@@ -1320,7 +1318,7 @@ namespace pipedal
}
return;
}
throw PiPedalStateException(SS("ALSA error:" << snd_strerror(err)));
throw PiPedalStateException(SS("ALSA error: " << snd_strerror(err)));
}
std::jthread *audioThread = nullptr;
@@ -1663,16 +1661,16 @@ namespace pipedal
}
void Close()
{
if (hInParams)
{
snd_rawmidi_params_free(hInParams);
hInParams = 0;
}
if (hIn)
{
snd_rawmidi_close(hIn);
hIn = nullptr;
}
if (hInParams)
{
snd_rawmidi_params_free(hInParams);
hInParams = 0;
}
}
int GetDataLength(uint8_t cc)
+8 -10
View File
@@ -349,17 +349,14 @@ private:
virtual void Close()
{
std::lock_guard guard{mutex};
if (!isOpen)
return;
isOpen = false;
if (realtimeMonitorPortSubscriptions != nullptr)
{
delete realtimeMonitorPortSubscriptions;
realtimeMonitorPortSubscriptions = nullptr;
std::lock_guard guard{mutex};
if (!isOpen)
return;
isOpen = false;
}
if (active)
{
audioDriver->Deactivate();
@@ -371,6 +368,7 @@ private:
StopReaderThread();
// release any pdealboards owned by the process thread.
this->activePedalboards.resize(0);
this->realtimeActivePedalboard = nullptr;
@@ -815,7 +813,7 @@ private:
virtual void OnAudioTerminated() override
{
this->active = false;
Lv2Log::info("Audio stopped.");
Lv2Log::info("Audio thread terminated.");
}
+277 -94
View File
@@ -1,18 +1,18 @@
/*
* MIT License
*
*
* Copyright (c) 2022 Robin E. R. 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
@@ -24,6 +24,7 @@
#include "AvahiService.hpp"
#include <string.h>
#include <thread>
#include <avahi-client/client.h>
#include <avahi-client/publish.h>
#include <avahi-common/alternative.h>
@@ -35,6 +36,7 @@
#include <unistd.h>
#include "Lv2Log.hpp"
#include "ss.hpp"
#include <chrono>
using namespace pipedal;
@@ -43,43 +45,122 @@ void AvahiService::Announce(
const std::string &name,
const std::string &instanceId,
const std::string &mdnsName,
bool addTestGroup)
bool wait)
{
if (terminated)
return;
// We should update an existing group if the name doesn't change.
// but in practice, the name is the only thing that's going to change without restarting the server.
if (this->name && strcmp(name.c_str(),this->name) == 0)
// but in practice, the name is the only thing that's going to change without restarting the pipedal server.
if (this->serviceName == name)
{
return;
}
Unannounce();
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->addTestGroup = addTestGroup;
this->name = avahi_strdup(name.c_str());
Start();
}
void AvahiService::Unannounce()
{
Stop();
if (name)
// this->name = avahi_strdup(name.c_str());
if (!started)
{
avahi_free(name);
this->name = nullptr;
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->serviceName = name;
started = true;
makeAnnouncement = true;
createPending = true;
Start();
if (wait) {
Wait();
}
}
else
{
// already started, so we have to use poll_lock instead.
avahi_threaded_poll_lock(threadedPoll);
// all state that we have to protect with the lock now that the avahi serv3ce is running.
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->serviceName = name;
this->makeAnnouncement = true;
// we've requested a start but have not created the group.
if (this->createPending)
{
// the first create will use our freshly updated parameters.
// no action required.
}
else
{
// otherwise we have to use a lock to effect the update.
if (group)
{
avahi_entry_group_reset(group); // unannounce the previous
SetState(ServiceState::Reset);
create_group(client);
}
else
{
Lv2Log::error("Failed to update mDNS service because of a synch problem.");
}
}
avahi_threaded_poll_unlock(threadedPoll);
if (wait)
{
Wait();
}
}
this->group = nullptr;
this->client = nullptr;
this->threadedPoll = nullptr;
}
void AvahiService::Unannounce(bool wait)
{
if (terminated)
return;
if (this->threadedPoll)
{
avahi_threaded_poll_lock(threadedPoll);
if (started)
{
if (this->createPending)
{
// if service is starting up, and we haven't yet made our first announcement,
// just signal that we don't want to announc.
this->makeAnnouncement = false;
}
else
{
this->makeAnnouncement = false;
// we have previously made a successful announcement. Retract it.
if (group)
{
int rc = avahi_entry_group_reset(group);
if (rc < 0)
{
Lv2Log::error("Avahi: failed to un-announce.");
}
}
}
}
avahi_threaded_poll_unlock(threadedPoll);
if (wait)
{
WaitForUnannounce();
}
}
}
void AvahiService::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userData)
{
((AvahiService *)userData)->EntryGroupCallback(g, state);
}
void AvahiService::SetState(ServiceState serviceState)
{
this->serviceState = serviceState;
}
void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState state)
{
group = g;
@@ -87,29 +168,39 @@ void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState s
switch (state)
{
case AVAHI_ENTRY_GROUP_ESTABLISHED:
SetState(ServiceState::Established);
Lv2Log::info(SS("DNS/SD group established."));
/* The entry group has been established successfully */
Lv2Log::debug(SS("Service " << name << " successfully established."));
break;
case AVAHI_ENTRY_GROUP_COLLISION:
{
char *n;
/* A service name collision with a remote service
* happened. Let's pick a new name */
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
Lv2Log::error(SS("Service name collision, renaming service to '" << name << "'\n"));
* happened. Let's pick a new name */
SetState(ServiceState::Collision);
char *n = avahi_alternative_service_name(avahiNameString);
avahi_free(avahiNameString);
avahiNameString = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'\n"));
/* And recreate the services */
create_group(avahi_entry_group_get_client(g));
break;
}
case AVAHI_ENTRY_GROUP_FAILURE:
Lv2Log::error(SS("Entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) << "\n"));
Lv2Log::error(SS("DNS/SD service shutting down."));
/* Some kind of failure happened while we were registering our services */
terminated = true;
avahi_threaded_poll_quit(threadedPoll);
SetState(ServiceState::Failed);
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
SetState(ServiceState::Uncommited);
break;
case AVAHI_ENTRY_GROUP_REGISTERING:;
SetState(ServiceState::Registering);
break;
}
}
@@ -142,11 +233,14 @@ static int toRawDns(char *rawResult, size_t size, const std::string &name)
}
void AvahiService::create_group(AvahiClient *c)
{
this->createPending = false;
char *n;
int ret;
assert(c);
/* If this is the first time we're called, let's create a new
* entry group if necessary */
SetState(ServiceState::Requested);
if (!group)
{
if (!(group = avahi_entry_group_new(c, entry_group_callback, (void *)this)))
@@ -157,20 +251,26 @@ void AvahiService::create_group(AvahiClient *c)
}
/* If the group is empty (either because it was just created, or
* because it was reset previously, add our entries. */
if (avahi_entry_group_is_empty(group))
if (this->makeAnnouncement && avahi_entry_group_is_empty(group))
{
Lv2Log::debug(SS("Adding service '" << name << "'"));
Lv2Log::debug(SS("Adding service '" << avahiNameString << "'"));
std::string instanceTxtRecord = SS("id=" << this->instanceId);
#define PIPEDAL_SERVICE_TYPE "_pipedal._tcp"
if (avahiNameString)
{
avahi_free(avahiNameString);
avahiNameString = nullptr;
}
avahiNameString = avahi_strdup(serviceName.c_str());
if ((ret = avahi_entry_group_add_service(
group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
(AvahiPublishFlags)0,
name,
avahiNameString,
PIPEDAL_SERVICE_TYPE,
NULL,
NULL,
@@ -186,55 +286,31 @@ void AvahiService::create_group(AvahiClient *c)
goto fail;
}
if (this->addTestGroup)
{
Lv2Log::info("Added tests DNS/SD service.");
std::string instanceTxtRecord = SS("id=" << "0a6045b0-1753-4104-b3e4-b9713b9cc360");
if ((ret = avahi_entry_group_add_service(
group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
(AvahiPublishFlags)0,
"Ed's PiPedal",
PIPEDAL_SERVICE_TYPE,
NULL,
NULL,
portNumber,
// txt records.
instanceTxtRecord.c_str(),
NULL)) < 0)
{
if (ret == AVAHI_ERR_COLLISION)
goto collision;
Lv2Log::error(SS("Failed to add _pipedal._tcp service: " << avahi_strerror(ret)));
goto fail;
}
}
/* Tell the server to register the service */
if ((ret = avahi_entry_group_commit(group)) < 0)
{
Lv2Log::error(SS("Failed to commit entry group: " << avahi_strerror(ret)));
goto fail;
}
Lv2Log::info(SS("DNS/SD service announced."));
Lv2Log::info(SS("DNS/SD service announced. (" << this->avahiNameString << ")"));
}
return;
collision:
/* A service name collision with a local service happened. Let's
* pick a new name */
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << name << "'"));
* pick a new avahiNameString */
n = avahi_alternative_service_name(avahiNameString);
avahi_free(avahiNameString);
avahiNameString = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'"));
avahi_entry_group_reset(group);
create_group(c);
return;
fail:
terminated = true;
avahi_threaded_poll_quit(threadedPoll);
Lv2Log::error("DNS/SD service shutting down.");
SetState(ServiceState::Failed);
}
void AvahiService::client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata)
@@ -250,7 +326,7 @@ void AvahiService::ClientCallback(AvahiClient *c, AvahiClientState state)
{
case AVAHI_CLIENT_S_RUNNING:
/* The server has startup successfully and registered its host
* name on the network, so it's time to create our services */
* name on the network, so it's time to create our services */
create_group(c);
break;
case AVAHI_CLIENT_FAILURE:
@@ -259,11 +335,11 @@ void AvahiService::ClientCallback(AvahiClient *c, AvahiClientState state)
if (this->clientErrno == AVAHI_ERR_DISCONNECTED)
{
// tear the client down and restart it
Lv2Log::info("Avahi connection lost. Reconnecting.");
Lv2Log::info("DNS/SD connection lost. Reconnecting.");
if (group)
{
avahi_entry_group_reset(group);
avahi_entry_group_free(group);
group = nullptr;
}
@@ -278,28 +354,35 @@ void AvahiService::ClientCallback(AvahiClient *c, AvahiClientState state)
if (!client)
{
Lv2Log::error(SS("Failed to create client: " << avahi_strerror(error)));
terminated = true;
avahi_threaded_poll_quit(threadedPoll);
SetState(ServiceState::Failed);
}
}
else
{
Lv2Log::error(SS("Client failure: " << avahi_strerror(avahi_client_errno(c))));
terminated = true;
Lv2Log::error(SS("DNS/SD client failure: " << avahi_strerror(avahi_client_errno(c))));
avahi_threaded_poll_quit(threadedPoll);
SetState(ServiceState::Failed);
}
break;
case AVAHI_CLIENT_S_COLLISION:
/* Let's drop our registered services. When the server is back
* in AVAHI_SERVER_RUNNING state we will register them
* again with the new host name. */
* in AVAHI_SERVER_RUNNING state we will register them
* again with the new host name. */
case AVAHI_CLIENT_S_REGISTERING:
/* The server records are now being established. This
* might be caused by a host name change. We need to wait
* for our own records to register until the host name is
* properly esatblished. */
* might be caused by a host name change. We need to wait
* for our own records to register until the host name is
* properly esatblished. */
if (group)
avahi_entry_group_reset(group);
SetState(ServiceState::Settling);
break;
case AVAHI_CLIENT_CONNECTING:
SetState(ServiceState::Settling);
break;
case AVAHI_CLIENT_CONNECTING:;
}
}
@@ -310,7 +393,8 @@ void AvahiService::Start()
int ret = 1;
struct timeval tv;
this->clientErrno = 0;
while (true)
SetState(ServiceState::Initializing);
for (int retry = 0; retry < 3; ++retry)
{
/* Allocate main loop object */
if (!this->threadedPoll)
@@ -339,30 +423,129 @@ void AvahiService::Start()
return;
fail:
Stop();
sleep(1);
SetState(ServiceState::Failed);
}
return;
}
void AvahiService::Stop()
{
if (stopped)
return;
this->stopped = true;
/* Cleanup things */
if (group)
{
avahi_entry_group_reset(group);
avahi_entry_group_free(group);
group = nullptr;
}
if (client)
{
avahi_client_free(client);
client = nullptr;
}
Unannounce(true);
sleep(1); // let traffic setting. avahi doens't seem to shut down cleany otherwise.
if (threadedPoll)
{
avahi_threaded_poll_lock(threadedPoll);
if (group)
{
avahi_entry_group_free(group);
group = nullptr;
}
avahi_threaded_poll_unlock(threadedPoll);
avahi_threaded_poll_lock(threadedPoll);
if (client)
{
avahi_client_free(client);
client = nullptr;
}
avahi_threaded_poll_unlock(threadedPoll);
avahi_threaded_poll_stop(threadedPoll);
avahi_threaded_poll_free(threadedPoll);
SetState(ServiceState::Closed);
threadedPoll = nullptr;
}
/* Cleanup things */
if (avahiNameString)
{
avahi_free(avahiNameString);
avahiNameString = nullptr;
}
Lv2Log::info("DNS/SD service stopped.");
}
void AvahiService::Wait()
{
using clock = std::chrono::steady_clock;
auto start = clock::now();
auto waitTime = start + std::chrono::duration_cast<clock::duration>(std::chrono::seconds(5));
while (true)
{
bool done;
switch (serviceState)
{
default:
case ServiceState::Unitialized:
throw std::runtime_error("Invalid state");
case ServiceState::Initializing:
case ServiceState::Settling:
case ServiceState::Requested:
case ServiceState::Uncommited:
case ServiceState::Registering:
case ServiceState::Collision:
done = false;
break;
case ServiceState::Reset:
case ServiceState::Established:
case ServiceState::Failed:
case ServiceState::Closed:
done = true;
break;
};
if (done) break;
if (clock::now() > waitTime)
{
Lv2Log::error("DNS/SD announcement timed out.");
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
void AvahiService::WaitForUnannounce()
{
using clock = std::chrono::steady_clock;
auto start = clock::now();
auto waitTime = start + std::chrono::duration_cast<clock::duration>(std::chrono::seconds(5));
while (true)
{
bool done;
switch (serviceState)
{
default:
throw std::runtime_error("Invalid state");
case ServiceState::Initializing:
case ServiceState::Settling:
case ServiceState::Requested:
case ServiceState::Registering:
case ServiceState::Collision:
done = false;
break;
case ServiceState::Uncommited:
case ServiceState::Unitialized:
case ServiceState::Reset:
case ServiceState::Established:
case ServiceState::Failed:
case ServiceState::Closed:
done = true;
break;
};
if (done) break;
if (clock::now() > waitTime)
{
Lv2Log::error("DNS/SD unannounce timed out.");
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
+32 -5
View File
@@ -26,10 +26,12 @@
#include <thread>
#include <string>
#include <atomic>
// forward declarations.
class AvahiEntryGroup;
class AvahiThreadedPoll;
#include <atomic>
#include <avahi-client/client.h>
@@ -38,12 +40,37 @@ namespace pipedal {
class AvahiService {
public:
~AvahiService() { Unannounce(); }
~AvahiService() { Stop(); }
void Announce(
int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName,bool addTestGroup = false);
int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName, bool wait);
void Unannounce();
void Unannounce(bool wait);
private:
enum class ServiceState {
Unitialized,
Initializing,
Settling,
Requested,
Uncommited,
Registering,
Collision,
Reset,
Established,
Failed,
Closed
};
std::atomic<ServiceState> serviceState;
void Wait();
void WaitForUnannounce();
void SetState(ServiceState serviceState);
std::atomic<bool> terminated = false;
bool stopped = false;
bool started = false;
bool createPending = false;
bool makeAnnouncement = false;
void Start();
void Stop();
static void entry_group_callback(AvahiEntryGroup*g, AvahiEntryGroupState state, void *userData);
@@ -59,13 +86,13 @@ namespace pipedal {
int portNumber = -1;
std::string instanceId;
std::string serviceName;
std::string mdnsName;
bool addTestGroup = false;
AvahiClient *client = NULL;
AvahiEntryGroup *group = NULL;
AvahiThreadedPoll *threadedPoll = NULL;
char*name = NULL;
char*avahiNameString = NULL;
};
+1 -1
View File
@@ -96,7 +96,7 @@ endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set (USE_SANITIZE False)
set (USE_SANITIZE true)
if (!ENABLE_VST3)
+1
View File
@@ -1295,6 +1295,7 @@ void RequireNetworkManager()
throw std::runtime_error("The current OS is not using NetworkManager.");
}
}
int main(int argc, char **argv)
{
CommandLineParser parser;
+97 -79
View File
@@ -54,13 +54,12 @@ namespace pipedal::impl
std::vector<std::string> GetKnownWifiNetworks();
virtual PostHandle Post(PostCallback&&fn) override;
virtual PostHandle PostDelayed(const clock::duration&delay,PostCallback&&fn) override;
virtual PostHandle Post(PostCallback &&fn) override;
virtual PostHandle PostDelayed(const clock::duration &delay, PostCallback &&fn) override;
virtual bool CancelPost(PostHandle handle) override;
virtual void SetNetworkChangingListener(NetworkChangingListener &&listener) override;
private:
enum class State
{
@@ -104,8 +103,7 @@ namespace pipedal::impl
void UpdateKnownNetworks(
std::vector<ssid_t> &knownSsids,
std::vector<AccessPoint::ptr> &allAccessPoints
);
std::vector<AccessPoint::ptr> &allAccessPoints);
void FireNetworkChanging();
@@ -151,7 +149,6 @@ using namespace pipedal::impl;
HotspotManagerImpl::HotspotManagerImpl()
{
SetDBusLogLevel(DBusLogLevel::None);
}
void HotspotManagerImpl::Open()
@@ -167,7 +164,24 @@ void HotspotManagerImpl::Open()
void HotspotManagerImpl::onClose()
{
StopHotspot();
this->closed = true; // avoids a memory barrier probelm.
CancelDeviceChangedTimer();
CancelWaitForNetworkManagerTimer();
CancelAccessPointsChangedTimer();
if (networkManager && activeConnection)
{
try {
networkManager->DeactivateConnection(activeConnection->getObjectPath());
activeConnection = nullptr;
} catch (const std::exception&e)
{
// nothrow.
}
}
ReleaseNetworkManager();
Lv2Log::debug("HotspotManager: state=Closed");
@@ -250,6 +264,7 @@ void HotspotManagerImpl::UpdateNetworkManagerStatus()
this->StartWaitForNetworkManagerTimer();
}
}
void HotspotManagerImpl::CancelDeviceChangedTimer()
{
if (devicesChangedTimerHandle)
@@ -386,7 +401,6 @@ void HotspotManagerImpl::onStartMonitoring()
StartScanTimer();
onAccessPointChanged();
}
catch (const std::exception &e)
{
@@ -397,6 +411,7 @@ void HotspotManagerImpl::onStartMonitoring()
void HotspotManagerImpl::StartWaitForNetworkManagerTimer()
{
CancelWaitForNetworkManagerTimer();
networkManagerTimerHandle = dbusDispatcher.PostDelayed(
std::chrono::seconds(5),
@@ -408,6 +423,7 @@ void HotspotManagerImpl::StartWaitForNetworkManagerTimer()
}
void HotspotManagerImpl::CancelWaitForNetworkManagerTimer()
{
if (networkManagerTimerHandle)
{
dbusDispatcher.CancelPost(networkManagerTimerHandle);
@@ -436,7 +452,7 @@ void HotspotManagerImpl::onReload()
case State::Error:
// ignore.
return;
default:
default:
MaybeStartHotspot();
return;
}
@@ -473,8 +489,6 @@ void HotspotManagerImpl::Close()
}
}
HotspotManager::ptr HotspotManager::Create()
{
return std::make_unique<HotspotManagerImpl>();
@@ -496,8 +510,8 @@ public:
}
};
struct NetworkSortRecord {
struct NetworkSortRecord
{
std::string ssid;
bool connected = false;
bool knownNetwork = false;
@@ -506,12 +520,11 @@ struct NetworkSortRecord {
};
void HotspotManagerImpl::UpdateKnownNetworks(
std::vector<ssid_t> &knownSsids,
std::vector<AccessPoint::ptr> & allAccessPoints
)
std::vector<AccessPoint::ptr> &allAccessPoints)
{
std::map<std::string,NetworkSortRecord> map;
std::map<std::string, NetworkSortRecord> map;
for (auto &knownSsid: knownSsids)
for (auto &knownSsid : knownSsids)
{
std::string ssid = ssidToString(knownSsid);
if (ssid.length() != 0)
@@ -521,17 +534,22 @@ void HotspotManagerImpl::UpdateKnownNetworks(
record.knownNetwork = true;
}
}
for (auto&accessPoint: allAccessPoints)
for (auto &accessPoint : allAccessPoints)
{
uint8_t strength = accessPoint->Strength();
auto vSsid = accessPoint->Ssid();
std::string ssid = ssidToString(vSsid);
if (ssid.length() != 0)
try {
uint8_t strength = accessPoint->Strength();
auto vSsid = accessPoint->Ssid();
std::string ssid = ssidToString(vSsid);
if (ssid.length() != 0)
{
NetworkSortRecord &record = map[ssid];
record.ssid = ssid;
record.visibleNetwork = true;
record.strength = strength;
}
} catch (const std::exception&ignored)
{
NetworkSortRecord&record = map[ssid];
record.ssid = ssid;
record.visibleNetwork = true;
record.strength = strength;
// race to get the info before it changes. np.
}
}
if (this->wlanDevice)
@@ -539,24 +557,23 @@ void HotspotManagerImpl::UpdateKnownNetworks(
auto activeConnectionPath = this->wlanDevice->ActiveConnection();
if (activeConnectionPath.length() > 2) // "/" -> no connection. Be paranoid about "".
{
auto activeConnection = ActiveConnection::Create(dbusDispatcher,activeConnectionPath);
auto activeConnection = ActiveConnection::Create(dbusDispatcher, activeConnectionPath);
std::string activeSsid = activeConnection->Id();
auto it = map.find(activeSsid);
if (it != map.end())
{
it->second.connected = true;
}
}
}
std::vector<NetworkSortRecord> records;
records.reserve(map.size());
for (auto & mapEntry: map)
for (auto &mapEntry : map)
{
records.push_back(mapEntry.second);
}
std::sort(records.begin(),records.end(), [](const NetworkSortRecord&left, const NetworkSortRecord &right)
{
std::sort(records.begin(), records.end(), [](const NetworkSortRecord &left, const NetworkSortRecord &right)
{
if (left.connected != right.connected)
{
return left.connected > right.connected;
@@ -573,27 +590,25 @@ void HotspotManagerImpl::UpdateKnownNetworks(
{
return left.strength > right.strength;
}
return false;
});
return false; });
if (records.size() > 10)
{
records.resize(10);
}
std::vector<std::string> result;
result.reserve(records.size());
for (auto &record: records)
for (auto &record : records)
{
result.push_back(std::move(record.ssid));
}
{
std::lock_guard lock { this->knownWifiNetworksMutex };
std::lock_guard lock{this->knownWifiNetworksMutex};
this->knownWifiNetworks = std::move(result);
}
}
std::vector<AccessPoint::ptr> HotspotManagerImpl::GetAllAccessPoints() {
std::vector<AccessPoint::ptr> HotspotManagerImpl::GetAllAccessPoints()
{
std::vector<AccessPoint::ptr> accessPoints;
for (const auto &accessPointPath : wlanWirelessDevice->AccessPoints())
@@ -602,15 +617,14 @@ std::vector<AccessPoint::ptr> HotspotManagerImpl::GetAllAccessPoints() {
accessPoints.push_back(std::move(accessPoint));
}
return accessPoints;
}
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetKnownVisibleAccessPoints(const std::vector<ssid_t>&allAccessPoints)
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetKnownVisibleAccessPoints(const std::vector<ssid_t> &allAccessPoints)
{
std::vector<std::vector<uint8_t>> knownSsids = this->GetAllAutoConnectSsids();
std::unordered_set<std::vector<uint8_t>, VectorHash<uint8_t>> index{knownSsids.begin(), knownSsids.end()};
std::vector<ssid_t> result;
std::vector<ssid_t> result;
std::vector<AccessPoint::ptr> accessPoints;
for (const auto &accessPoint : allAccessPoints)
@@ -684,14 +698,16 @@ void HotspotManagerImpl::onAccessPointChanged()
}
}
static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessPoint::ptr>&accessPoints)
static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessPoint::ptr> &accessPoints)
{
std::vector<std::vector<uint8_t>> result;
for (const auto&accessPoint: accessPoints)
for (const auto &accessPoint : accessPoints)
{
try {
try
{
result.push_back(accessPoint->Ssid());
} catch (const std::exception&ignored)
}
catch (const std::exception &ignored)
{
// race to get a disappearing ssid. Ignore the error.
}
@@ -700,8 +716,10 @@ static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessP
}
void HotspotManagerImpl::MaybeStartHotspot()
{
if (this->state == State::Error) return;
if (this->closed) return;
if (this->state == State::Error)
return;
if (this->closed)
return;
if (!wlanDevice || !wlanWirelessDevice)
{
@@ -711,12 +729,12 @@ void HotspotManagerImpl::MaybeStartHotspot()
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.
this->UpdateKnownNetworks(connectableSsids,allAccessPoints);
bool wantsHotspot = this->wifiConfigSettings.WantsHotspot(
this->UpdateKnownNetworks(connectableSsids, allAccessPoints);
this->ethernetConnected,connectableSsids,allAccessPointSsids);
bool wantsHotspot = this->wifiConfigSettings.WantsHotspot(
this->ethernetConnected, connectableSsids, allAccessPointSsids);
if (this->state == State::Monitoring && wantsHotspot)
{
@@ -764,7 +782,7 @@ Connection::ptr HotspotManagerImpl::FindExistingConnection()
}
}
}
return nullptr;
}
@@ -874,28 +892,26 @@ void HotspotManagerImpl::StopHotspot()
{
if (activeConnection)
{
FireNetworkChanging();
networkManager->DeactivateConnection(activeConnection->getObjectPath());
activeConnection = nullptr;
FireNetworkChanging();
}
}
catch (const std::exception &e)
{
Lv2Log::error("HotspotManager: Failed to deactivate hotspot.");
Lv2Log::error(SS("HotspotManager: Failed to deactivate hotspot. " << e.what()));
activeConnection = nullptr;
}
if (this->state == State::HotspotConnected || this->state == State::HotspotConnecting)
{
Lv2Log::info("HotspotManager: state=HotspotMonitoring");
Lv2Log::debug("HotspotManager: state=HotspotMonitoring");
SetState(State::Monitoring);
Lv2Log::info("HotspotManager: PiPedal hotspot disabled.");
}
}
static const std::chrono::seconds scanInterval { 60};
static const std::chrono::seconds scanInterval{60};
void HotspotManagerImpl::StartScanTimer()
{
@@ -904,7 +920,8 @@ void HotspotManagerImpl::StartScanTimer()
}
void HotspotManagerImpl::StopScanTimer()
{
if (this->scanTimerHandle) {
if (this->scanTimerHandle)
{
dbusDispatcher.CancelPost(this->scanTimerHandle);
this->scanTimerHandle = 0;
}
@@ -913,14 +930,17 @@ void HotspotManagerImpl::ScanNow()
{
this->scanTimerHandle = 0;
if (wlanWirelessDevice) {
if (wlanWirelessDevice)
{
std::map<std::string, sdbus::Variant> options;
try {
try
{
Lv2Log::debug("Scanning");
wlanWirelessDevice->RequestScan(options);
} catch (const std::exception &e)
}
catch (const std::exception &e)
{
Lv2Log::error(SS("HotspotMonitor: Wi-Fi RequestScan failed." << e.what()));
return;
@@ -928,44 +948,42 @@ void HotspotManagerImpl::ScanNow()
this->scanTimerHandle = this->dbusDispatcher.PostDelayed(
std::chrono::duration_cast<std::chrono::steady_clock::duration>(scanInterval),
[this]() {
[this]()
{
ScanNow();
}
);
});
}
}
HotspotManagerImpl::PostHandle HotspotManagerImpl::Post(PostCallback&&fn)
HotspotManagerImpl::PostHandle HotspotManagerImpl::Post(PostCallback &&fn)
{
return dbusDispatcher.Post(std::move(fn));
}
HotspotManagerImpl::PostHandle HotspotManagerImpl::PostDelayed(const clock::duration&delay,PostCallback&&fn)
HotspotManagerImpl::PostHandle HotspotManagerImpl::PostDelayed(const clock::duration &delay, PostCallback &&fn)
{
return dbusDispatcher.PostDelayed(delay,std::move(fn));
return dbusDispatcher.PostDelayed(delay, std::move(fn));
}
bool HotspotManagerImpl::CancelPost(PostHandle handle) {
bool HotspotManagerImpl::CancelPost(PostHandle handle)
{
return dbusDispatcher.CancelPost(handle);
}
void HotspotManagerImpl::SetNetworkChangingListener(NetworkChangingListener &&listener)
void HotspotManagerImpl::SetNetworkChangingListener(NetworkChangingListener &&listener)
{
std::lock_guard<std::recursive_mutex> lock { this->networkChangingListenerMutex};
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
this->networkChangingListener = std::move(listener);
}
void HotspotManagerImpl::FireNetworkChanging() {
std::lock_guard<std::recursive_mutex> lock { this->networkChangingListenerMutex};
void HotspotManagerImpl::FireNetworkChanging()
{
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
if (this->networkChangingListener)
{
this->networkChangingListener(this->ethernetConnected,!!activeConnection);
this->networkChangingListener(this->ethernetConnected, !!activeConnection);
}
}
std::vector<std::string> HotspotManagerImpl::GetKnownWifiNetworks()
std::vector<std::string> HotspotManagerImpl::GetKnownWifiNetworks()
{
std::lock_guard lock(knownWifiNetworksMutex); // pushed off the service thread.
return this->knownWifiNetworks;
}
+84 -30
View File
@@ -39,6 +39,9 @@
#include "DBusToLv2Log.hpp"
#include "SysExec.hpp"
#include "Updater.hpp"
#include "util.hpp"
#include "DBusLog.hpp"
#include "AvahiService.hpp"
#ifndef NO_MLOCK
#include <sys/mman.h>
@@ -88,6 +91,7 @@ PiPedalModel::PiPedalModel()
DbusLogToLv2Log();
SetDBusLogLevel(DBusLogLevel::Info);
hotspotManager = HotspotManager::Create();
hotspotManager->SetNetworkChangingListener(
@@ -100,24 +104,41 @@ PiPedalModel::PiPedalModel()
void PiPedalModel::Close()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
std::unique_ptr<AudioHost> oldAudioHost;
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (closed)
{
return;
}
closed = true;
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->Close();
}
delete[] t;
if (avahiService) {
this->avahiService = nullptr; // and close.
}
if (audioHost)
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->Close();
}
delete[] t;
this->subscribers.resize(0);
oldAudioHost = std::move(this->audioHost);
} // end lock.
// lockless to avoid deadlocks while shutting down the audio thread.
if (oldAudioHost)
{
audioHost->Close();
oldAudioHost->Close();
}
}
@@ -977,11 +998,11 @@ void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSet
#if NEW_WIFI_CONFIG
if (this->storage.SetWifiConfigSettings(wifiConfigSettings))
{
this->UpdateDnsSd();
if (this->hotspotManager)
{
this->hotspotManager->Reload();
}
this->UpdateDnsSd();
}
#else
this->storage.SetWifiConfigSettings(wifiConfigSettings);
@@ -1022,20 +1043,28 @@ static std::string GetP2pdName()
void PiPedalModel::UpdateDnsSd()
{
// avahiService.Unannounce(); let Announce decide whether it wants to unannounce or update.
if (!avahiService)
{
throw std::runtime_error("Not ready.");
}
ServiceConfiguration deviceIdFile;
deviceIdFile.Load();
std::string p2pdName = GetP2pdName();
if (p2pdName != "")
WifiConfigSettings wifiSettings;
wifiSettings.Load();
std::string serviceName = wifiSettings.hotspotName_;
if (serviceName == "")
{
deviceIdFile.deviceName = p2pdName;
serviceName = deviceIdFile.deviceName;
}
if (serviceName == "")
{
serviceName = "pipedal";
}
if (deviceIdFile.deviceName != "" && deviceIdFile.uuid != "")
std::string hostName = GetHostName();
if (serviceName != "" && deviceIdFile.uuid != "")
{
avahiService.Announce(webPort, deviceIdFile.deviceName, deviceIdFile.uuid, "pipedal");
avahiService->Announce(webPort, serviceName, deviceIdFile.uuid, hostName,true);
}
else
{
@@ -1376,12 +1405,19 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
addedInstances.insert(activeVuSubscriptions[i].instanceid);
}
}
std::vector<int64_t> instanceids(addedInstances.begin(), addedInstances.end());
audioHost->SetVuSubscriptions(instanceids);
if (audioHost)
{
std::vector<int64_t> instanceids(addedInstances.begin(), addedInstances.end());
audioHost->SetVuSubscriptions(instanceids);
}
}
void PiPedalModel::UpdateRealtimeMonitorPortSubscriptions()
{
if (!audioHost)
{
return;
}
audioHost->SetMonitorPortSubscriptions(this->activeMonitorPortSubscriptions);
}
@@ -1484,7 +1520,10 @@ void PiPedalModel::SendSetPatchProperty(
clientId, instanceId, urid, atomValue, nullptr, onError);
outstandingParameterRequests.push_back(request);
this->audioHost->sendRealtimeParameterRequest(request);
if (this->audioHost)
{
this->audioHost->sendRealtimeParameterRequest(request);
}
}
void PiPedalModel::SendGetPatchProperty(
@@ -1545,7 +1584,10 @@ void PiPedalModel::SendGetPatchProperty(
std::lock_guard<std::recursive_mutex> lock(mutex);
outstandingParameterRequests.push_back(request);
this->audioHost->sendRealtimeParameterRequest(request);
if (this->audioHost)
{
this->audioHost->sendRealtimeParameterRequest(request);
}
}
BankIndex PiPedalModel::GetBankIndex() const
@@ -1802,7 +1844,10 @@ void PiPedalModel::DeleteAtomOutputListeners(int64_t clientId)
--i;
}
}
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
if (audioHost)
{
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
}
}
void PiPedalModel::DeleteMidiListeners(int64_t clientId)
@@ -1816,7 +1861,10 @@ void PiPedalModel::DeleteMidiListeners(int64_t clientId)
--i;
}
}
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
if (audioHost)
{
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
}
}
void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue)
@@ -2248,6 +2296,12 @@ static bool HasAlsaDevice(const std::vector<AlsaDeviceInfo> devices, const std::
void PiPedalModel::StartHotspotMonitoring()
{
this->avahiService = std::make_unique<AvahiService>();
SetThreadName("avahi"); // hack to name the avahi service thread.
UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement.
SetThreadName("main");
this->hotspotManager->Open();
}
+3 -2
View File
@@ -36,7 +36,6 @@
#include "WifiConfigSettings.hpp"
#include "WifiDirectConfigSettings.hpp"
#include "AdminClient.hpp"
#include "AvahiService.hpp"
#include <thread>
#include "Promise.hpp"
#include "AtomConverter.hpp"
@@ -49,6 +48,7 @@ namespace pipedal
struct RealtimeNextMidiProgramRequest;
class Lv2PluginChangeMonitor;
class Updater;
class AvahiService;
class IPiPedalModelSubscriber
{
@@ -113,7 +113,7 @@ namespace pipedal
std::vector<MidiBinding> systemMidiBindings;
AvahiService avahiService;
std::unique_ptr<AvahiService> avahiService;
uint16_t webPort;
PiPedalAlsaDevices alsaDevices;
@@ -197,6 +197,7 @@ namespace pipedal
std::vector<RealtimePatchPropertyRequest *> outstandingParameterRequests;
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
std::atomic<bool> closed = false;
private: // IAudioHostCallbacks
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override;
+25
View File
@@ -516,26 +516,47 @@ private:
std::mutex activePortMonitorsMutex;
std::vector<std::shared_ptr<PortMonitorSubscription>> activePortMonitors;
std::atomic<bool> closed = false;
public:
virtual int64_t GetClientId() { return clientId; }
virtual ~PiPedalSocketHandler()
{
if (!closed)
{
FinalCleanup();
}
}
bool finalCleanup = false;
void FinalCleanup()
{
if (finalCleanup) return;
finalCleanup = true;
// avoid use after free.
for (int i = 0; i < this->activePortMonitors.size(); ++i)
{
model.UnmonitorPort(activePortMonitors[i]->subscriptionHandle);
}
activePortMonitors.resize(0);
for (int i = 0; i < this->activeVuSubscriptions.size(); ++i)
{
model.RemoveVuSubscription(activeVuSubscriptions[i].subscriptionHandle);
}
activeVuSubscriptions.resize(0);
model.RemoveNotificationSubsription(this);
}
virtual void Close()
{
if (closed) return;
closed = true;
FinalCleanup(); // do it while we can. &model will no longer be valid after this. ( :-( )
SocketHandler::Close();
}
@@ -935,6 +956,10 @@ public:
}
return;
}
if (closed)
{
this->SendError(replyTo, "Server has shut down.");
}
if (message == "setControl")
{
ControlChangedBody message;
+4 -1
View File
@@ -1181,7 +1181,7 @@ void Updater::ValidateSignature(const std::filesystem::path &file, const std::fi
<< " --verify "
<< signatureFile << " " << file;
Lv2Log::info(SS("/usr/bin/gpg " << ss.str()));
Lv2Log::debug(SS("/usr/bin/gpg " << ss.str()));
auto gpgOutput = sysExecForOutput("/usr/bin/gpg", ss.str());
if (gpgOutput.exitCode != EXIT_SUCCESS)
{
@@ -1191,17 +1191,20 @@ void Updater::ValidateSignature(const std::filesystem::path &file, const std::fi
if (!IsSignatureGood(gpgText))
{
Lv2Log::error(gpgOutput.output);
throw std::runtime_error("Update signature is not valid.");
}
std::string keyId = getFingerprint(gpgText);
if (keyId != UPDATE_GPG_FINGERPRINT && keyId != UPDATE_GPG_FINGERPRINT2)
{
Lv2Log::error(gpgOutput.output);
throw std::runtime_error(SS("Update signature has the wrong id: " << keyId));
}
std::string origin = getAddress(gpgText);
if (origin != UPDATE_GPG_ADDRESS && origin != UPDATE_GPG_ADDRESS2)
{
Lv2Log::error(gpgOutput.output);
throw std::runtime_error(SS("Update signature has an incorrect address." << origin));
}
}
+2 -12
View File
@@ -557,16 +557,6 @@ pipedal::last_modified(const std::filesystem::path &path)
}
}
static std::string getHostName()
{
char buff[512];
if (gethostname(buff, sizeof(buff)) == 0)
{
buff[511] = '\0';
return buff;
}
return "";
}
static std::string getIpv4Address(const std::string interface)
{
@@ -1165,7 +1155,7 @@ namespace pipedal
std::stringstream ss;
ss << port;
// m_endpoint.listen(this->address, ss.str());
//m_endpoint.listen(this->address, ss.str());
m_endpoint.listen(tcp::v6(), (uint16_t)port);
m_endpoint.start_accept();
@@ -1305,7 +1295,7 @@ std::shared_ptr<WebServer> pipedal::WebServer::create(
void WebServerImpl::DisplayIpAddresses()
{
std::string hostName = getHostName();
std::string hostName = GetHostName();
if (hostName.length() != 0)
{
std::stringstream ss;
+7 -7
View File
@@ -117,7 +117,7 @@ public:
/// Write a string message to the given channel
/**
* @param channel The channel to write to
* @param channel The channel to write tosu
* @param msg The message to write
*/
void write(level channel, std::string const & msg) {
@@ -126,19 +126,19 @@ public:
{
case elevel::devel:
case elevel::library:
Lv2Log::debug(msg);
Lv2Log::debug("WebServer: %s",msg.c_str());
break;
case elevel::info:
Lv2Log::info(msg);
Lv2Log::info("WebServer: %s",msg.c_str());
break;
case elevel::warn:
Lv2Log::warning(msg);
Lv2Log::warning("WebServer: %s",msg.c_str());
break;
case elevel::rerror:
Lv2Log::error(msg);
Lv2Log::error("WebServer: %s",msg.c_str());
break;
case elevel::fatal:
Lv2Log::error("Fatal error: " + msg);
Lv2Log::error("WebServer fatal error: %s",msg.c_str());
break;
default:
break;
@@ -159,7 +159,7 @@ public:
}
bool dynamic_test(level channel) {
return (m_channels) & channel != 0;
return (m_channels & channel) != 0;
}
protected:
+12 -5
View File
@@ -81,10 +81,18 @@ static bool isJackServiceRunning()
return std::filesystem::exists(path);
}
static void AsanCheck()
{
char *t = new char[5];
t[5] = 'x';
delete t;
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
#ifndef WIN32
umask(002); // newly created files in /var/pipedal get 775-ish permissions, which improves debugging/live-service interaction.
#endif
@@ -269,7 +277,6 @@ int main(int argc, char *argv[])
(unsigned long)getpid());
}
model.StartHotspotMonitoring();
model.WaitForAudioDeviceToComeOnline();
@@ -317,9 +324,9 @@ int main(int argc, char *argv[])
{
server->RunInBackground(-1);
SetThreadName("avahi");
model.UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement.
SetThreadName("main");
model.StartHotspotMonitoring();
{
sigwait(&sigSet, &sig);