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