web ui Update feature complete.
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ project(pipedal
|
|||||||
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
|
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
|
||||||
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
|
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
|
||||||
)
|
)
|
||||||
set (DISPLAY_VERSION "PiPedal v1.2.41-Release")
|
set (DISPLAY_VERSION "PiPedal v1.2.41-Experimental")
|
||||||
set (PACKAGE_ARCHITECTURE "arm64")
|
set (PACKAGE_ARCHITECTURE "arm64")
|
||||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
## PiPedal 1.2.41 Release
|
## PiPedal 1.2.41` Release
|
||||||
|
|
||||||
This version includes the following new features:
|
This version includes the following new features:
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"socket_server_port": 8080,
|
"socket_server_port": 80,
|
||||||
"socket_server_address": "*",
|
"socket_server_address": "*",
|
||||||
"debug": true,
|
"debug": true,
|
||||||
"max_upload_size": 536870912,
|
"max_upload_size": 536870912,
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// ok in debug builds. File doesn't get placed until install time.
|
// ok in debug builds. File doesn't get placed until install time.
|
||||||
console.log("Failed to fetch open-source notices. " + err);
|
console.log("Failed to fetch open-source notices. " + err.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
|
|
||||||
onOpenBank(bankId: number) {
|
onOpenBank(bankId: number) {
|
||||||
this.model_.openBank(bankId)
|
this.model_.openBank(bankId)
|
||||||
.catch((error) => this.model_.showAlert(error));
|
.catch((error) => this.model_.showAlert(error.toString()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +383,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
});
|
});
|
||||||
this.model_.saveBankAs(this.model_.banks.get().selectedBank, newName)
|
this.model_.saveBankAs(this.model_.banks.get().selectedBank, newName)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.model_.showAlert(error);
|
this.model_.showAlert(error.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -407,7 +407,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
});
|
});
|
||||||
this.model_.renameBank(this.model_.banks.get().selectedBank, newName)
|
this.model_.renameBank(this.model_.banks.get().selectedBank, newName)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.model_.showAlert(error);
|
this.model_.showAlert(error.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -896,12 +896,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
|
|||||||
onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); }
|
onDialogClosed={() => { this.model_.zoomedUiControl.set(undefined); }
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{
|
<UpdateDialog open={this.state.updateDialogOpen} />
|
||||||
(this.state.updateDialogOpen)
|
|
||||||
&& (
|
|
||||||
<UpdateDialog open={true} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{this.state.showStatusMonitor && (<JackStatusView />)}
|
{this.state.showStatusMonitor && (<JackStatusView />)}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
|
|||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
e.target.value = ""; // clear the file list so we can get another change notice.
|
e.target.value = ""; // clear the file list so we can get another change notice.
|
||||||
this.model.showAlert(err);
|
this.model.showAlert(err.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
handleUploadBank() {
|
handleUploadBank() {
|
||||||
@@ -266,7 +266,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.model.showAlert(error);
|
this.model.showAlert(error.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,7 +324,7 @@ const BankDialog = withStyles(styles, { withTheme: true })(
|
|||||||
});
|
});
|
||||||
this.model.moveBank(from, to)
|
this.model.moveBank(from, to)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.model.showAlert(error);
|
this.model.showAlert(error.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
}
|
}
|
||||||
).catch(
|
).catch(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
this.model.showAlert(e + "");
|
this.model.showAlert(e.toString());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-53
@@ -20,7 +20,7 @@
|
|||||||
import { UiPlugin, UiControl, PluginType, UiFileProperty } from './Lv2Plugin';
|
import { UiPlugin, UiControl, PluginType, UiFileProperty } from './Lv2Plugin';
|
||||||
|
|
||||||
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
|
import { PiPedalArgumentError, PiPedalStateError } from './PiPedalError';
|
||||||
import { UpdateStatus, UpdatePolicyT} from './Updater';
|
import { UpdateStatus, UpdatePolicyT } from './Updater';
|
||||||
import { ObservableProperty } from './ObservableProperty';
|
import { ObservableProperty } from './ObservableProperty';
|
||||||
import { Pedalboard, PedalboardItem, ControlValue } from './Pedalboard'
|
import { Pedalboard, PedalboardItem, ControlValue } from './Pedalboard'
|
||||||
import PluginClass from './PluginClass';
|
import PluginClass from './PluginClass';
|
||||||
@@ -54,8 +54,7 @@ export enum State {
|
|||||||
InstallingUpdate,
|
InstallingUpdate,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function wantsLoadingScreen(state: State)
|
export function wantsLoadingScreen(state: State) {
|
||||||
{
|
|
||||||
return state >= State.Reconnecting;
|
return state >= State.Reconnecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,8 +441,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
this.onSocketConnectionLost = this.onSocketConnectionLost.bind(this);
|
this.onSocketConnectionLost = this.onSocketConnectionLost.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
expectDisconnect(reason: ReconnectReason)
|
expectDisconnect(reason: ReconnectReason) {
|
||||||
{
|
|
||||||
this.reconnectReason = reason;
|
this.reconnectReason = reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,38 +645,36 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
private updateLaterTimeout?: NodeJS.Timeout = undefined;
|
private updateLaterTimeout?: NodeJS.Timeout = undefined;
|
||||||
|
|
||||||
private clearPromptForUpdateTimer()
|
private clearPromptForUpdateTimer() {
|
||||||
{
|
if (this.updateLaterTimeout) {
|
||||||
if (this.updateLaterTimeout)
|
|
||||||
{
|
|
||||||
clearTimeout(this.updateLaterTimeout);
|
clearTimeout(this.updateLaterTimeout);
|
||||||
this.updateLaterTimeout = undefined;
|
this.updateLaterTimeout = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setPromptForUpdateTimer(when: Date)
|
private setPromptForUpdateTimer(when: Date) {
|
||||||
{
|
let ms = when.getTime() - Date.now();
|
||||||
let ms = when.getTime()-Date.now();
|
|
||||||
this.updateLaterTimeout = setTimeout(
|
this.updateLaterTimeout = setTimeout(
|
||||||
()=>{
|
() => {
|
||||||
this.updateLaterTimeout = undefined;
|
this.updateLaterTimeout = undefined;
|
||||||
// make the server do a fresh check
|
// make the server do a fresh check
|
||||||
this.getUpdateStatus()
|
this.getUpdateStatus()
|
||||||
.then(
|
.then(
|
||||||
()=>{
|
() => {
|
||||||
this.updatePromptForUpdate();
|
this.updatePromptForUpdate();
|
||||||
})
|
})
|
||||||
.catch((e)=>{
|
.catch((e) => {
|
||||||
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
ms);
|
ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private lastCanUpdateNow: boolean = false;
|
||||||
private updatePromptForUpdate() {
|
private updatePromptForUpdate() {
|
||||||
this.clearPromptForUpdateTimer();
|
this.clearPromptForUpdateTimer();
|
||||||
|
|
||||||
let stateEnabled = true; // must be present to accept alerts. this.state.get() === State.Ready;
|
let stateEnabled = true; // must be present to accept alerts. this.state.get() === State.Ready;
|
||||||
let timeEnabled = false;
|
let timeEnabled = false;
|
||||||
|
|
||||||
@@ -689,36 +685,36 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
let nDate = Date.now();
|
let nDate = Date.now();
|
||||||
|
|
||||||
let now: Date = new Date(nDate);
|
let now: Date = new Date(nDate);
|
||||||
let tomorrow: Date = new Date(nDate + 86400000);
|
let maxDate: Date = new Date(nDate + 86400000*2); // sanity check for systems with unstable system clock
|
||||||
|
|
||||||
timeEnabled = (updateLaterTime < now || updateLaterTime >= tomorrow)
|
timeEnabled = (updateLaterTime < now || updateLaterTime >= maxDate)
|
||||||
if (!timeEnabled)
|
|
||||||
{
|
|
||||||
this.setPromptForUpdateTimer(updateLaterTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let updateStatus = this.updateStatus.get();
|
let updateStatus = this.updateStatus.get();
|
||||||
let statusEnabled = updateStatus.isOnline && updateStatus.isValid && updateStatus.getActiveRelease().updateAvailable;
|
let statusEnabled = updateStatus.isOnline && updateStatus.isValid && updateStatus.getActiveRelease().updateAvailable;
|
||||||
|
|
||||||
if (stateEnabled && timeEnabled && statusEnabled)
|
let canUpdateNow: boolean = (stateEnabled && timeEnabled && statusEnabled);
|
||||||
|
if (updateStatus.updatePolicy === UpdatePolicyT.Disable)
|
||||||
{
|
{
|
||||||
this.showUpdateDialogValue = true;
|
canUpdateNow = false;
|
||||||
}
|
}
|
||||||
this.promptForUpdate.set(this.showUpdateDialogValue);
|
|
||||||
|
|
||||||
if (stateEnabled && statusEnabled && !timeEnabled && updateLaterTime)
|
if (canUpdateNow && canUpdateNow !== this.lastCanUpdateNow)
|
||||||
{
|
{
|
||||||
|
this.showUpdateDialogValue = true; // make the dialog sticky so it can show OK button
|
||||||
|
}
|
||||||
|
this.lastCanUpdateNow = canUpdateNow;
|
||||||
|
this.promptForUpdate.set(this.showUpdateDialogValue || canUpdateNow);
|
||||||
|
|
||||||
|
if (stateEnabled && statusEnabled && !timeEnabled && updateLaterTime) {
|
||||||
this.setPromptForUpdateTimer(updateLaterTime);
|
this.setPromptForUpdateTimer(updateLaterTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private showUpdateDialogValue: boolean = false;
|
private showUpdateDialogValue: boolean = false;
|
||||||
|
|
||||||
showUpdateDialog(show: boolean = true) {
|
showUpdateDialog(show: boolean = true) {
|
||||||
if (this.showUpdateDialogValue !== show)
|
if (this.showUpdateDialogValue !== show) {
|
||||||
{
|
|
||||||
this.showUpdateDialogValue = show;
|
this.showUpdateDialogValue = show;
|
||||||
if (show)
|
if (show) {
|
||||||
{
|
|
||||||
this.forceUpdateCheck();
|
this.forceUpdateCheck();
|
||||||
}
|
}
|
||||||
this.updatePromptForUpdate();
|
this.updatePromptForUpdate();
|
||||||
@@ -732,6 +728,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
if (current.currentVersion.length !== 0 && current.currentVersion !== updateStatus.currentVersion) {
|
if (current.currentVersion.length !== 0 && current.currentVersion !== updateStatus.currentVersion) {
|
||||||
// !! Server has been updated!!!
|
// !! Server has been updated!!!
|
||||||
this.reloadPage();
|
this.reloadPage();
|
||||||
|
throw new Error("Reloading...");
|
||||||
}
|
}
|
||||||
if (updateStatus.getActiveRelease().updateAvailable) {
|
if (updateStatus.getActiveRelease().updateAvailable) {
|
||||||
this.promptForUpdate.set(true);
|
this.promptForUpdate.set(true);
|
||||||
@@ -755,7 +752,6 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
setState(state: State) {
|
setState(state: State) {
|
||||||
if (this.state.get() !== state) {
|
if (this.state.get() !== state) {
|
||||||
this.state.set(state);
|
this.state.set(state);
|
||||||
this.updatePromptForUpdate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -809,7 +805,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return this.getUpdateStatus(); // detects whether server has been upgraded.
|
return this.getUpdateStatus(); // detects whether server has been upgraded.
|
||||||
})
|
})
|
||||||
.then((updateStatus) => {
|
.then((updateStatus) => {
|
||||||
|
|
||||||
return this.getWebSocket().request<any>("plugins");
|
return this.getWebSocket().request<any>("plugins");
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@@ -884,7 +880,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
this.setState(State.Ready);
|
this.setState(State.Ready);
|
||||||
})
|
})
|
||||||
.catch((what) => {
|
.catch((what) => {
|
||||||
this.onError(what);
|
this.onError(what.toString());
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
makeSocketServerUrl(hostName: string, port: number): string {
|
makeSocketServerUrl(hostName: string, port: number): string {
|
||||||
@@ -971,7 +967,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.setError("Failed to connect to server. " + error);
|
this.setError("Failed to connect to server. " + error.toString());
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
.then((succeeded) => {
|
.then((succeeded) => {
|
||||||
@@ -1066,7 +1062,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.setError("Failed to fetch server state.\n\n" + error);
|
this.setError("Failed to fetch server state.\n\n" + error.toString());
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1150,7 +1146,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.setError("Failed to get server state. \n\n" + error);
|
this.setError("Failed to get server state. \n\n" + error.toString());
|
||||||
});
|
});
|
||||||
let t = this.onVisibilityChanged;
|
let t = this.onVisibilityChanged;
|
||||||
|
|
||||||
@@ -1789,28 +1785,31 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
updateNow(): Promise<void> {
|
updateNow(): Promise<void> {
|
||||||
return new Promise<void>(
|
return new Promise<void>(
|
||||||
(accept,reject) => {
|
(accept, reject) => {
|
||||||
let updateStatus = this.updateStatus.get();
|
let updateStatus = this.updateStatus.get();
|
||||||
if (updateStatus.isOnline && updateStatus.isValid && updateStatus.getActiveRelease().updateAvailable)
|
if (updateStatus.isOnline && updateStatus.isValid && updateStatus.getActiveRelease().updateAvailable) {
|
||||||
{
|
|
||||||
this.setState(State.DownloadingUpdate);
|
this.setState(State.DownloadingUpdate);
|
||||||
|
this.expectDisconnect(ReconnectReason.Updating);
|
||||||
let url = updateStatus.getActiveRelease().updateUrl;
|
let url = updateStatus.getActiveRelease().updateUrl;
|
||||||
|
|
||||||
nullCast(this.webSocket)
|
nullCast(this.webSocket)
|
||||||
.request<void>('updateNow', url)
|
.request<void>('updateNow', url)
|
||||||
.then(()=> {
|
.then(() => {
|
||||||
this.setState(State.InstallingUpdate);
|
this.setState(State.InstallingUpdate);
|
||||||
accept();
|
accept();
|
||||||
})
|
})
|
||||||
.catch(
|
.catch(
|
||||||
(e: any)=>{
|
(e: any) => {
|
||||||
|
this.expectDisconnect(ReconnectReason.Disconnected);
|
||||||
|
|
||||||
this.setState(State.Ready); // TODO: hopefully we haven't had an intermediate disconnect.
|
this.setState(State.Ready); // TODO: hopefully we haven't had an intermediate disconnect.
|
||||||
reject(e);
|
reject(e);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
reject(new Error("Invalid update request."));
|
reject(new Error("Invalid update request."));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1896,8 +1895,9 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return this.copyPreset(instanceId, -1);
|
return this.copyPreset(instanceId, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
showAlert(message: string): void {
|
showAlert(message: string| Error): void {
|
||||||
this.alertMessage.set(message);
|
let m = message.toString();
|
||||||
|
this.alertMessage.set(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2009,8 +2009,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
private isClosed = false;
|
private isClosed = false;
|
||||||
close() {
|
close() {
|
||||||
if (!this.isClosed)
|
if (!this.isClosed) {
|
||||||
{
|
|
||||||
this.isClosed = true;
|
this.isClosed = true;
|
||||||
this.webSocket?.close();
|
this.webSocket?.close();
|
||||||
this.webSocket = undefined;
|
this.webSocket = undefined;
|
||||||
@@ -2385,7 +2384,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
resolve(json as number);
|
resolve(json as number);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
reject("Upload failed. " + error);
|
reject("Upload failed. " + error.toString());
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -2775,7 +2774,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setUpdatePolicy(updatePolicy: UpdatePolicyT) : void {
|
setUpdatePolicy(updatePolicy: UpdatePolicyT): void {
|
||||||
let iPolicy = updatePolicy as number;
|
let iPolicy = updatePolicy as number;
|
||||||
if (this.webSocket) {
|
if (this.webSocket) {
|
||||||
this.webSocket.send("setUpdatePolicy", iPolicy);
|
this.webSocket.send("setUpdatePolicy", iPolicy);
|
||||||
|
|||||||
+32
-32
@@ -30,15 +30,14 @@ import DialogEx from './DialogEx';
|
|||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
import { UpdateStatus, UpdateRelease, UpdatePolicyT,intToUpdatePolicyT } from './Updater';
|
import { UpdateStatus, UpdateRelease, UpdatePolicyT, intToUpdatePolicyT } from './Updater';
|
||||||
import { PiPedalModelFactory, PiPedalModel } from './PiPedalModel';
|
import { PiPedalModelFactory, PiPedalModel } from './PiPedalModel';
|
||||||
import Select from '@mui/material/Select';
|
import Select from '@mui/material/Select';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
|
||||||
|
|
||||||
//const UPDATE_CHECK_DELAY = 86400000; // one day in ms.
|
const UPDATE_CHECK_DELAY = 86400000; // one day in ms.
|
||||||
const UPDATE_CHECK_DELAY = 30 * 1000; // testing
|
// const UPDATE_CHECK_DELAY = 30 * 1000; // testing
|
||||||
const CLOSE_DELAY = 100; // ms.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -69,11 +68,9 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
this.onUpdateStatusChanged = this.onUpdateStatusChanged.bind(this);
|
this.onUpdateStatusChanged = this.onUpdateStatusChanged.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateStatusChanged(newValue: UpdateStatus)
|
onUpdateStatusChanged(newValue: UpdateStatus) {
|
||||||
{
|
if (!newValue.equals(this.state.updateStatus)) {
|
||||||
if (!newValue.equals(this.state.updateStatus))
|
this.setState({ updateStatus: newValue });
|
||||||
{
|
|
||||||
this.setState({updateStatus: newValue});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,8 +87,8 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private handleOk() {
|
private handleOK() {
|
||||||
|
this.model.showUpdateDialog(false);
|
||||||
}
|
}
|
||||||
private handleUpdateNow() {
|
private handleUpdateNow() {
|
||||||
this.model.updateNow()
|
this.model.updateNow()
|
||||||
@@ -100,9 +97,7 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
this.showAlert(e.toString());
|
this.showAlert(e.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private updatingLater: boolean = false;
|
|
||||||
private handleUpdateLater() {
|
private handleUpdateLater() {
|
||||||
this.updatingLater = true;
|
|
||||||
this.model.updateLater(UPDATE_CHECK_DELAY);
|
this.model.updateLater(UPDATE_CHECK_DELAY);
|
||||||
this.model.showUpdateDialog(false); // allow close if launched from the settings window.
|
this.model.showUpdateDialog(false); // allow close if launched from the settings window.
|
||||||
}
|
}
|
||||||
@@ -110,10 +105,9 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
private handleClose() {
|
private handleClose() {
|
||||||
// ideally, we'd like to not close at all, but it screws up the nav backstack if we dont.
|
// ideally, we'd like to not close at all, but it screws up the nav backstack if we dont.
|
||||||
// so, close, but do so very briefly
|
// so, close, but do so very briefly
|
||||||
if (!this.updatingLater) {
|
if (this.canUpgrade()) {
|
||||||
this.model.updateLater(CLOSE_DELAY); // close and re-open immediately.
|
this.model.updateLater(UPDATE_CHECK_DELAY); // close and re-open immediately.
|
||||||
}
|
}
|
||||||
this.updatingLater = false;
|
|
||||||
this.model.showUpdateDialog(false);
|
this.model.showUpdateDialog(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +130,12 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
}
|
}
|
||||||
canUpgrade(): boolean {
|
canUpgrade(): boolean {
|
||||||
let updateStatus = this.state.updateStatus;
|
let updateStatus = this.state.updateStatus;
|
||||||
|
if (updateStatus.updatePolicy === UpdatePolicyT.Disable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (updateStatus.errorMessage !== "") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let updateRelease = updateStatus.getActiveRelease();
|
let updateRelease = updateStatus.getActiveRelease();
|
||||||
|
|
||||||
return updateStatus.isValid && updateStatus.isOnline && updateRelease.updateAvailable;
|
return updateStatus.isValid && updateStatus.isOnline && updateRelease.updateAvailable;
|
||||||
@@ -143,7 +143,7 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
|
|
||||||
private onPolicySelected(newValue: string | number): void {
|
private onPolicySelected(newValue: string | number): void {
|
||||||
if (newValue.toString() === "") return;
|
if (newValue.toString() === "") return;
|
||||||
let updatePolicy = intToUpdatePolicyT(newValue as number);
|
let updatePolicy = intToUpdatePolicyT(newValue as number);
|
||||||
this.model.setUpdatePolicy(updatePolicy);
|
this.model.setUpdatePolicy(updatePolicy);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,7 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
let compact = this.state.compactLandscape;
|
let compact = this.state.compactLandscape;
|
||||||
return (
|
return (
|
||||||
<DialogEx tag="update" open={this.props.open} onClose={() => { this.handleClose(); }}
|
<DialogEx tag="update" open={this.props.open} onClose={() => { this.handleClose(); }}
|
||||||
style={{ userSelect: "none" }}
|
style={{ userSelect: "none" }}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
(!compact) &&
|
(!compact) &&
|
||||||
@@ -163,10 +163,11 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<div style={{ display: "flex", flexFlow: "row noWrap", alignItems: "center" }} >
|
<div style={{ display: "flex", flexFlow: "row noWrap", alignItems: "center" }} >
|
||||||
<Typography style={{ flexGrow: 1, flexShrink: 1, marginRight: 20 }} noWrap>PiPedal Updates</Typography>
|
<Typography style={{ flexGrow: 1, flexShrink: 1, marginRight: 20 }} noWrap>PiPedal Updates</Typography>
|
||||||
<Select style={{opacity: 0.6}} variant="standard" value={updateStatus.updatePolicy as number} onChange={(ev) => { this.onPolicySelected(ev.target.value); }}>
|
<Select style={{ opacity: 0.6 }} variant="standard" value={updateStatus.updatePolicy} onChange={(ev) => { this.onPolicySelected(ev.target.value); }}>
|
||||||
<MenuItem value={UpdatePolicyT.ReleaseOnly as number}>Release only</MenuItem>
|
<MenuItem value={UpdatePolicyT.ReleaseOnly}>Release only</MenuItem>
|
||||||
<MenuItem value={UpdatePolicyT.ReleaseOrBeta as number}>Release or Beta</MenuItem>
|
<MenuItem value={UpdatePolicyT.ReleaseOrBeta}>Release or Beta</MenuItem>
|
||||||
<MenuItem value={UpdatePolicyT.Development as number}>Development</MenuItem>
|
<MenuItem value={UpdatePolicyT.Development}>Development</MenuItem>
|
||||||
|
<MenuItem value={UpdatePolicyT.Disable}>Disable</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -181,12 +182,11 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
}
|
}
|
||||||
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{
|
{(upToDate) && (
|
||||||
(upToDate) && (
|
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 12 }}>
|
||||||
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 12 }}>
|
PiPedal is up to date.
|
||||||
PiPedal is up to date.
|
</Typography>
|
||||||
</Typography>
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
{(canUpgrade) && (
|
{(canUpgrade) && (
|
||||||
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 12 }}>
|
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 12 }}>
|
||||||
@@ -202,7 +202,7 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
<Typography noWrap variant="body2" color="textSecondary" >
|
<Typography noWrap variant="body2" color="textSecondary" >
|
||||||
Current version:
|
Current version:
|
||||||
</Typography>
|
</Typography>
|
||||||
{(canUpgrade) && (
|
{(!upToDate) && (
|
||||||
<Typography noWrap variant="body2" color="textSecondary" style={{ marginTop: 4 }} >
|
<Typography noWrap variant="body2" color="textSecondary" style={{ marginTop: 4 }} >
|
||||||
Update version:
|
Update version:
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -213,7 +213,7 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
<Typography noWrap variant="body2" color="textSecondary" >
|
<Typography noWrap variant="body2" color="textSecondary" >
|
||||||
{updateStatus.currentVersionDisplayName}
|
{updateStatus.currentVersionDisplayName}
|
||||||
</Typography>
|
</Typography>
|
||||||
{(canUpgrade) && (
|
{(!upToDate) && (
|
||||||
<Typography noWrap variant="body2" color="textSecondary" style={{ marginTop: 4 }} >
|
<Typography noWrap variant="body2" color="textSecondary" style={{ marginTop: 4 }} >
|
||||||
{updateRelease.upgradeVersionDisplayName}
|
{updateRelease.upgradeVersionDisplayName}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -249,10 +249,10 @@ export default class UpdateDialog extends React.Component<UpdateDialogProps, Upd
|
|||||||
) : (
|
) : (
|
||||||
|
|
||||||
<div style={{ display: "flex", flexFlow: "row noWrap", alignItems: "center" }}
|
<div style={{ display: "flex", flexFlow: "row noWrap", alignItems: "center" }}
|
||||||
onClick={()=>{ this.model.showUpdateDialog(false); }}
|
|
||||||
>
|
>
|
||||||
<div style={{ flexGrow: 1, flexShrink: 1 }} />
|
<div style={{ flexGrow: 1, flexShrink: 1 }} />
|
||||||
<Button variant="dialogPrimary">OK</Button>
|
<Button onClick={() => { this.handleOK(); }} variant="dialogPrimary">OK</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export enum UpdatePolicyT {
|
|||||||
ReleaseOnly = 0,
|
ReleaseOnly = 0,
|
||||||
ReleaseOrBeta = 1,
|
ReleaseOrBeta = 1,
|
||||||
Development = 2,
|
Development = 2,
|
||||||
|
Disable = 3,
|
||||||
|
|
||||||
};
|
};
|
||||||
export function intToUpdatePolicyT(intValue: number): UpdatePolicyT {
|
export function intToUpdatePolicyT(intValue: number): UpdatePolicyT {
|
||||||
@@ -90,6 +91,7 @@ export class UpdateStatus {
|
|||||||
}
|
}
|
||||||
getActiveRelease(): UpdateRelease {
|
getActiveRelease(): UpdateRelease {
|
||||||
switch (this.updatePolicy) {
|
switch (this.updatePolicy) {
|
||||||
|
case UpdatePolicyT.Disable: // show available updates in the settings dialog.
|
||||||
case UpdatePolicyT.ReleaseOnly:
|
case UpdatePolicyT.ReleaseOnly:
|
||||||
return this.releaseOnlyRelease;
|
return this.releaseOnlyRelease;
|
||||||
case UpdatePolicyT.ReleaseOrBeta:
|
case UpdatePolicyT.ReleaseOrBeta:
|
||||||
|
|||||||
+19
-6
@@ -41,7 +41,7 @@ AdminClient::~AdminClient()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AdminClient::CanUseShutdownClient()
|
bool AdminClient::CanUseAdminClient()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ bool AdminClient::SetJackServerConfiguration(const JackServerSettings &jackServe
|
|||||||
|
|
||||||
void AdminClient::SetWifiConfig(const WifiConfigSettings &settings)
|
void AdminClient::SetWifiConfig(const WifiConfigSettings &settings)
|
||||||
{
|
{
|
||||||
if (!CanUseShutdownClient())
|
if (!CanUseAdminClient())
|
||||||
{
|
{
|
||||||
throw PiPedalException("Can't perform this operation when debugging.");
|
throw PiPedalException("Can't perform this operation when debugging.");
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ void AdminClient::SetWifiConfig(const WifiConfigSettings &settings)
|
|||||||
}
|
}
|
||||||
void AdminClient::SetWifiDirectConfig(const WifiDirectConfigSettings &settings)
|
void AdminClient::SetWifiDirectConfig(const WifiDirectConfigSettings &settings)
|
||||||
{
|
{
|
||||||
if (!CanUseShutdownClient())
|
if (!CanUseAdminClient())
|
||||||
{
|
{
|
||||||
throw PiPedalException("Can't perform this operation when debugging.");
|
throw PiPedalException("Can't perform this operation when debugging.");
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ void AdminClient::SetWifiDirectConfig(const WifiDirectConfigSettings &settings)
|
|||||||
|
|
||||||
void AdminClient::SetGovernorSettings(const std::string &settings)
|
void AdminClient::SetGovernorSettings(const std::string &settings)
|
||||||
{
|
{
|
||||||
if (!CanUseShutdownClient())
|
if (!CanUseAdminClient())
|
||||||
{
|
{
|
||||||
throw PiPedalException("Can't use AdminClient when running interactively.");
|
throw PiPedalException("Can't use AdminClient when running interactively.");
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ void AdminClient::SetGovernorSettings(const std::string &settings)
|
|||||||
|
|
||||||
void AdminClient::MonitorGovernor(const std::string &governor)
|
void AdminClient::MonitorGovernor(const std::string &governor)
|
||||||
{
|
{
|
||||||
if (!CanUseShutdownClient())
|
if (!CanUseAdminClient())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ void AdminClient::MonitorGovernor(const std::string &governor)
|
|||||||
}
|
}
|
||||||
void AdminClient::UnmonitorGovernor()
|
void AdminClient::UnmonitorGovernor()
|
||||||
{
|
{
|
||||||
if (!CanUseShutdownClient())
|
if (!CanUseAdminClient())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -183,3 +183,16 @@ void AdminClient::UnmonitorGovernor()
|
|||||||
cmd << '\n';
|
cmd << '\n';
|
||||||
bool ignored = WriteMessage(cmd.str().c_str());
|
bool ignored = WriteMessage(cmd.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AdminClient::InstallUpdate(const std::string&filename)
|
||||||
|
{
|
||||||
|
if (!CanUseAdminClient())
|
||||||
|
{
|
||||||
|
// generally can't do amin operations when running under a debugger.
|
||||||
|
throw std::runtime_error("No admin client.");
|
||||||
|
}
|
||||||
|
std::stringstream cmd;
|
||||||
|
cmd << "InstallUpdate " << filename << '\n';
|
||||||
|
bool ignroed = WriteMessage(cmd.str().c_str());
|
||||||
|
}
|
||||||
|
|||||||
+2
-1
@@ -34,7 +34,7 @@ class AdminClient {
|
|||||||
public:
|
public:
|
||||||
AdminClient();
|
AdminClient();
|
||||||
~AdminClient();
|
~AdminClient();
|
||||||
bool CanUseShutdownClient();
|
bool CanUseAdminClient();
|
||||||
bool RequestShutdown(bool restart);
|
bool RequestShutdown(bool restart);
|
||||||
bool SetJackServerConfiguration(const JackServerSettings & jackServerSettings);
|
bool SetJackServerConfiguration(const JackServerSettings & jackServerSettings);
|
||||||
void SetWifiConfig(const WifiConfigSettings & settings);
|
void SetWifiConfig(const WifiConfigSettings & settings);
|
||||||
@@ -42,6 +42,7 @@ public:
|
|||||||
void SetGovernorSettings(const std::string & governor);
|
void SetGovernorSettings(const std::string & governor);
|
||||||
void MonitorGovernor(const std::string &governor);
|
void MonitorGovernor(const std::string &governor);
|
||||||
void UnmonitorGovernor();
|
void UnmonitorGovernor();
|
||||||
|
void InstallUpdate(const std::string&filename);
|
||||||
private:
|
private:
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
UnixSocket socket;
|
UnixSocket socket;
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
// Copyright (c) 2024Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#include "AdminInstallUpdate.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include "ss.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include "Lv2Log.hpp"
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
static fs::path ROOT_INSTALL_DIRECTORY = "/var/pipedal/updates";
|
||||||
|
static fs::path INSTALLER_LOG_FILE_PATH = ROOT_INSTALL_DIRECTORY / "install.log";
|
||||||
|
|
||||||
|
static fs::path UPDATE_DIRECTORY = ROOT_INSTALL_DIRECTORY / "downloads";
|
||||||
|
|
||||||
|
static void removeSignalHandler(int signal)
|
||||||
|
{
|
||||||
|
struct sigaction sa;
|
||||||
|
sa.sa_handler = SIG_DFL;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
sigaction(signal, &sa, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int exec(const std::string &command)
|
||||||
|
{
|
||||||
|
std::string buffer = command;
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<char *> argv;
|
||||||
|
char *p = const_cast<char *>(buffer.c_str());
|
||||||
|
while (*p)
|
||||||
|
{
|
||||||
|
char *start = p;
|
||||||
|
while (*p != ' ' && *p != '\0')
|
||||||
|
{
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
argv.push_back(start);
|
||||||
|
while (*p == ' ')
|
||||||
|
{
|
||||||
|
*p++ = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argv.push_back(nullptr);
|
||||||
|
|
||||||
|
pid_t pid;
|
||||||
|
|
||||||
|
if ((pid = fork()) == 0)
|
||||||
|
{
|
||||||
|
execv(argv[0], (char *const *)argv.data());
|
||||||
|
write(1,"!\n",2);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
if (pid == -1)
|
||||||
|
{
|
||||||
|
write(1,"*",1);
|
||||||
|
perror("execv");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
int returnValue;
|
||||||
|
waitpid(pid, &returnValue, 0);
|
||||||
|
|
||||||
|
|
||||||
|
int exitStatus = WEXITSTATUS(returnValue);
|
||||||
|
return exitStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateLog(const std::string &message)
|
||||||
|
{
|
||||||
|
write(1,message.c_str(),message.length());
|
||||||
|
write(1,"\n",1);
|
||||||
|
}
|
||||||
|
void pipedal::AdminInstallUpdate(const std::filesystem::path path)
|
||||||
|
{
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fs:create_directories(ROOT_INSTALL_DIRECTORY);
|
||||||
|
Lv2Log::info(SS("Installing " << path));
|
||||||
|
|
||||||
|
if (!path.string().starts_with(UPDATE_DIRECTORY.string()))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Update file path is incorrect.");
|
||||||
|
}
|
||||||
|
if (!fs::exists(path))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("File does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream ssCmd;
|
||||||
|
ssCmd << "/usr/bin/apt-get -q -y install " << path.string();
|
||||||
|
|
||||||
|
std::string cmd = ssCmd.str();
|
||||||
|
|
||||||
|
// direct standard outputs to a log file.
|
||||||
|
close(0);
|
||||||
|
close(1);
|
||||||
|
close(2);
|
||||||
|
|
||||||
|
int fd = open(INSTALLER_LOG_FILE_PATH.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||||
|
dup2(fd, 1); // to stdout
|
||||||
|
dup2(fd, 2); // to stderr
|
||||||
|
close(fd);
|
||||||
|
fd = -1;
|
||||||
|
|
||||||
|
// disconnect std input from parent's stdin.
|
||||||
|
int fdNull = open("/dev/null", O_RDONLY, 0644);
|
||||||
|
dup2(fdNull, 0);
|
||||||
|
close(fdNull);
|
||||||
|
|
||||||
|
updateLog("**Stopping pipedald");
|
||||||
|
|
||||||
|
exec("/usr/bin/systemctl stop pipedald");
|
||||||
|
|
||||||
|
updateLog("** Installing update");
|
||||||
|
int retcode = exec(cmd);
|
||||||
|
|
||||||
|
|
||||||
|
// in case we didn't actually update for some reason.
|
||||||
|
updateLog("** Starting pipedald");
|
||||||
|
exec("/usr/bin/systemctl start pipedald");
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
updateLog(e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) 2024Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace pipedal
|
||||||
|
{
|
||||||
|
void AdminInstallUpdate(const std::filesystem::path path);
|
||||||
|
|
||||||
|
}
|
||||||
+11
-1
@@ -220,6 +220,12 @@ bool setJackConfiguration(JackServerSettings serverSettings)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InstallUpdate(const std::filesystem::path path)
|
||||||
|
{
|
||||||
|
std::string cmd = SS("/usr/bin/systemd-run --unit=pidal_update /usr/sbin/pipedal_update " <<path.string());
|
||||||
|
int rc = system(cmd.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
class AdminServer
|
class AdminServer
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
@@ -259,7 +265,11 @@ private:
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (command == "UnmonitorGovernor")
|
if (command == "InstallUpdate")
|
||||||
|
{
|
||||||
|
std::filesystem::path path = args;
|
||||||
|
InstallUpdate(path);
|
||||||
|
} else if (command == "UnmonitorGovernor")
|
||||||
{
|
{
|
||||||
StopGovernorMonitorThread();
|
StopGovernorMonitorThread();
|
||||||
result = 0;
|
result = 0;
|
||||||
|
|||||||
+10
-2
@@ -611,12 +611,20 @@ target_include_directories(capturepresets PRIVATE ${PIPEDAL_INCLUDES})
|
|||||||
target_link_libraries(capturepresets ${PIPEDAL_LIBS})
|
target_link_libraries(capturepresets ${PIPEDAL_LIBS})
|
||||||
|
|
||||||
|
|
||||||
|
add_executable(pipedal_update
|
||||||
|
UpdateMain.cpp
|
||||||
|
Lv2Log.hpp Lv2Log.cpp
|
||||||
|
Lv2SystemdLogger.cpp Lv2SystemdLogger.hpp
|
||||||
|
AdminInstallUpdate.cpp AdminInstallUpdate.hpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(pipedal_update PRIVATE stdc++fs systemd )
|
||||||
|
|
||||||
|
|
||||||
add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
|
add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
|
||||||
UnixSocket.cpp UnixSocket.hpp
|
UnixSocket.cpp UnixSocket.hpp
|
||||||
|
|
||||||
SetWifiConfig.cpp SetWifiConfig.hpp
|
SetWifiConfig.cpp SetWifiConfig.hpp
|
||||||
|
|
||||||
JackServerSettings.hpp JackServerSettings.cpp
|
JackServerSettings.hpp JackServerSettings.cpp
|
||||||
Lv2SystemdLogger.hpp Lv2SystemdLogger.cpp
|
Lv2SystemdLogger.hpp Lv2SystemdLogger.cpp
|
||||||
|
|
||||||
@@ -671,7 +679,7 @@ add_custom_target (
|
|||||||
install (TARGETS pipedalconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
|
install (TARGETS pipedalconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
|
||||||
EXPORT pipedalTargets)
|
EXPORT pipedalTargets)
|
||||||
|
|
||||||
install (TARGETS pipedald pipedaladmind DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
|
install (TARGETS pipedald pipedaladmind pipedal_update DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
|
||||||
EXPORT pipedalSbinTargets)
|
EXPORT pipedalSbinTargets)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+7
-5
@@ -112,11 +112,11 @@ std::filesystem::path findOnPath(const std::string &command)
|
|||||||
|
|
||||||
void EnableService()
|
void EnableService()
|
||||||
{
|
{
|
||||||
if (sysExec(SYSTEMCTL_BIN " enable " PIPEDALD_SERVICE ".service") != EXIT_SUCCESS)
|
if (silentSysExec(SYSTEMCTL_BIN " enable " PIPEDALD_SERVICE ".service") != EXIT_SUCCESS)
|
||||||
{
|
{
|
||||||
cout << "Error: Failed to enable the " PIPEDALD_SERVICE " service.\n";
|
cout << "Error: Failed to enable the " PIPEDALD_SERVICE " service.\n";
|
||||||
}
|
}
|
||||||
if (sysExec(SYSTEMCTL_BIN " enable " ADMIN_SERVICE ".service") != EXIT_SUCCESS)
|
if (silentSysExec(SYSTEMCTL_BIN " enable " ADMIN_SERVICE ".service") != EXIT_SUCCESS)
|
||||||
{
|
{
|
||||||
cout << "Error: Failed to enable the " ADMIN_SERVICE " service.\n";
|
cout << "Error: Failed to enable the " ADMIN_SERVICE " service.\n";
|
||||||
}
|
}
|
||||||
@@ -178,8 +178,10 @@ void StopService(bool excludeShutdownService = false)
|
|||||||
|
|
||||||
void StartService(bool excludeShutdownService = false)
|
void StartService(bool excludeShutdownService = false)
|
||||||
{
|
{
|
||||||
|
if (!UsingNetworkManager())
|
||||||
silentSysExec("/usr/bin/pulseaudio --kill"); // interferes with Jack audio service startup.
|
{
|
||||||
|
silentSysExec("/usr/bin/pulseaudio --kill"); // interferes with Jack audio service startup.
|
||||||
|
}
|
||||||
if (!excludeShutdownService)
|
if (!excludeShutdownService)
|
||||||
{
|
{
|
||||||
if (sysExec(SYSTEMCTL_BIN " start " ADMIN_SERVICE ".service") != EXIT_SUCCESS)
|
if (sysExec(SYSTEMCTL_BIN " start " ADMIN_SERVICE ".service") != EXIT_SUCCESS)
|
||||||
@@ -187,7 +189,7 @@ void StartService(bool excludeShutdownService = false)
|
|||||||
throw std::runtime_error("Failed to start the " ADMIN_SERVICE " service.");
|
throw std::runtime_error("Failed to start the " ADMIN_SERVICE " service.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sysExec(SYSTEMCTL_BIN " start " PIPEDALD_SERVICE ".service") != EXIT_SUCCESS)
|
if (silentSysExec(SYSTEMCTL_BIN " start " PIPEDALD_SERVICE ".service") != EXIT_SUCCESS)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Failed to start the " PIPEDALD_SERVICE " service.");
|
throw std::runtime_error("Failed to start the " PIPEDALD_SERVICE " service.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,11 @@ void Lv2PluginChangeMonitor::Shutdown()
|
|||||||
if (monitorThread)
|
if (monitorThread)
|
||||||
{
|
{
|
||||||
terminateThread = true;
|
terminateThread = true;
|
||||||
|
uint64_t val = 1;
|
||||||
|
write(shutdown_eventfd,(void*)&val, sizeof(val));
|
||||||
monitorThread->join();
|
monitorThread->join();
|
||||||
monitorThread = nullptr;
|
monitorThread = nullptr;
|
||||||
|
close(shutdown_eventfd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2212,8 +2212,10 @@ UpdateStatus PiPedalModel::GetUpdateStatus() {
|
|||||||
|
|
||||||
void PiPedalModel::UpdateNow(const std::string&updateUrl)
|
void PiPedalModel::UpdateNow(const std::string&updateUrl)
|
||||||
{
|
{
|
||||||
sleep(5);
|
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||||
throw std::runtime_error("Not implemented yet.");
|
auto fileName = updater.DownloadUpdate(updateUrl);
|
||||||
|
|
||||||
|
adminClient.InstallUpdate(fileName);
|
||||||
|
|
||||||
}
|
}
|
||||||
void PiPedalModel::ForceUpdateCheck() {
|
void PiPedalModel::ForceUpdateCheck() {
|
||||||
|
|||||||
@@ -1067,7 +1067,7 @@ public:
|
|||||||
{
|
{
|
||||||
WifiConfigSettings wifiConfigSettings;
|
WifiConfigSettings wifiConfigSettings;
|
||||||
pReader->read(&wifiConfigSettings);
|
pReader->read(&wifiConfigSettings);
|
||||||
if (!GetAdminClient().CanUseShutdownClient())
|
if (!GetAdminClient().CanUseAdminClient())
|
||||||
{
|
{
|
||||||
throw PiPedalException("Can't change server settings when running interactively.");
|
throw PiPedalException("Can't change server settings when running interactively.");
|
||||||
}
|
}
|
||||||
@@ -1088,7 +1088,7 @@ public:
|
|||||||
{
|
{
|
||||||
WifiDirectConfigSettings wifiDirectConfigSettings;
|
WifiDirectConfigSettings wifiDirectConfigSettings;
|
||||||
pReader->read(&wifiDirectConfigSettings);
|
pReader->read(&wifiDirectConfigSettings);
|
||||||
if (!GetAdminClient().CanUseShutdownClient())
|
if (!GetAdminClient().CanUseAdminClient())
|
||||||
{
|
{
|
||||||
throw PiPedalException("Can't change server settings when running interactively.");
|
throw PiPedalException("Can't change server settings when running interactively.");
|
||||||
}
|
}
|
||||||
@@ -1978,7 +1978,7 @@ std::shared_ptr<ISocketFactory> pipedal::MakePiPedalSocketFactory(PiPedalModel &
|
|||||||
|
|
||||||
void PiPedalSocketHandler::RequestShutdown(bool restart)
|
void PiPedalSocketHandler::RequestShutdown(bool restart)
|
||||||
{
|
{
|
||||||
if (GetAdminClient().CanUseShutdownClient())
|
if (GetAdminClient().CanUseAdminClient())
|
||||||
{
|
{
|
||||||
GetAdminClient().RequestShutdown(restart);
|
GetAdminClient().RequestShutdown(restart);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2024Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#include "AdminInstallUpdate.hpp"
|
||||||
|
#include "Lv2Log.hpp"
|
||||||
|
#include "Lv2SystemdLogger.hpp"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
|
||||||
|
int main(int argc, char**argv)
|
||||||
|
{
|
||||||
|
Lv2Log::set_logger(MakeLv2SystemdLogger());
|
||||||
|
|
||||||
|
if (argc != 2)
|
||||||
|
{
|
||||||
|
Lv2Log::error("Invalid arguments.");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
std::filesystem::path path = argv[1];
|
||||||
|
AdminInstallUpdate(path);
|
||||||
|
}
|
||||||
+132
@@ -67,6 +67,9 @@ static UpdateStatus GetCachedUpdateStatus()
|
|||||||
json_reader reader(f);
|
json_reader reader(f);
|
||||||
UpdateStatus status;
|
UpdateStatus status;
|
||||||
reader.read(&status);
|
reader.read(&status);
|
||||||
|
|
||||||
|
// cached curruent version might come from a different version.
|
||||||
|
status.ResetCurrentVersion();
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,6 +555,19 @@ UpdateStatus::UpdateStatus()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateStatus::ResetCurrentVersion()
|
||||||
|
{
|
||||||
|
currentVersion_ = PROJECT_VER;
|
||||||
|
currentVersionDisplayName_ = PROJECT_DISPLAY_VERSION;
|
||||||
|
|
||||||
|
#ifdef TEST_UPDATE
|
||||||
|
// uncomment this line to test upgrading.
|
||||||
|
currentVersion_ = "1.2.39";
|
||||||
|
currentVersionDisplayName_ = "PiPedal 1.2.39-Debug";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
std::chrono::system_clock::time_point UpdateStatus::LastUpdateTime() const
|
std::chrono::system_clock::time_point UpdateStatus::LastUpdateTime() const
|
||||||
{
|
{
|
||||||
std::chrono::system_clock::duration duration{this->lastUpdateTime_};
|
std::chrono::system_clock::duration duration{this->lastUpdateTime_};
|
||||||
@@ -583,10 +599,126 @@ const GithubAsset *GithubRelease::GetDownloadForCurrentArchitecture() const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UpdateRelease::UpdateRelease()
|
UpdateRelease::UpdateRelease()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Updater::GetUpdateFilename(const std::string &url)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
|
||||||
|
// partialy whitelisting, partly avoiding having to parse a URL.
|
||||||
|
if (this->currentResult.releaseOnlyRelease_.UpdateUrl() == url)
|
||||||
|
{
|
||||||
|
return this->currentResult.releaseOnlyRelease_.AssetName();
|
||||||
|
}
|
||||||
|
if (this->currentResult.releaseOrBetaRelease_.UpdateUrl() == url)
|
||||||
|
{
|
||||||
|
return this->currentResult.releaseOrBetaRelease_.AssetName();
|
||||||
|
}
|
||||||
|
if (this->currentResult.devRelease_.UpdateUrl() == url)
|
||||||
|
{
|
||||||
|
return this->currentResult.devRelease_.AssetName();
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Permission denied. Invalid url.");
|
||||||
|
|
||||||
|
}
|
||||||
|
static std::string unCRLF(const std::string &text)
|
||||||
|
{
|
||||||
|
std::ostringstream ss;
|
||||||
|
for (char c : text)
|
||||||
|
{
|
||||||
|
if (c == '\r')
|
||||||
|
continue;
|
||||||
|
if (c == '\n') {
|
||||||
|
ss << '/';
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
ss << c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void removeOldSiblings(int numberToKeep, const std::filesystem::path &fileToKeep)
|
||||||
|
{
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
auto directory = fileToKeep.parent_path();
|
||||||
|
if (directory.empty()) return; // superstition.
|
||||||
|
struct RemoveEntry {
|
||||||
|
fs::path path;
|
||||||
|
fs::file_time_type time;
|
||||||
|
};
|
||||||
|
std::vector<RemoveEntry> entries;
|
||||||
|
for (const auto&dirEntry : fs::directory_iterator(directory))
|
||||||
|
{
|
||||||
|
if (dirEntry.is_regular_file())
|
||||||
|
{
|
||||||
|
if (dirEntry.path() != fileToKeep)
|
||||||
|
{
|
||||||
|
dirEntry.last_write_time();
|
||||||
|
entries.push_back(RemoveEntry { .path = dirEntry.path(), .time = dirEntry.last_write_time()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(
|
||||||
|
entries.begin(),entries.end(),
|
||||||
|
[](const RemoveEntry&left, const RemoveEntry&right)
|
||||||
|
{
|
||||||
|
return left.time > right.time; // by time descending
|
||||||
|
}
|
||||||
|
);
|
||||||
|
for (size_t i = numberToKeep; i < entries.size(); ++i)
|
||||||
|
{
|
||||||
|
fs::remove(entries[i].path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::filesystem::path Updater::DownloadUpdate(const std::string &url)
|
||||||
|
{
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
std::string filename = GetUpdateFilename(url);
|
||||||
|
if (filename.empty())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Permission denied. Invalid url.");
|
||||||
|
}
|
||||||
|
auto downloadDirectory = WORKING_DIRECTORY / "downloads";
|
||||||
|
std::filesystem::create_directories(downloadDirectory);
|
||||||
|
|
||||||
|
auto downloadPath = downloadDirectory / filename;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs::remove(downloadPath);
|
||||||
|
std::string args = SS("-s -L " << url << " -o " << downloadPath << " 2>&1");
|
||||||
|
auto curlOutput = sysExecForOutput("curl", args);
|
||||||
|
if (curlOutput.exitCode != EXIT_SUCCESS)
|
||||||
|
{
|
||||||
|
Lv2Log::error(SS("Update download failed." << unCRLF(curlOutput.output)));
|
||||||
|
throw std::runtime_error("PiPedal server does not have access to the internet.");
|
||||||
|
}
|
||||||
|
if (!fs::exists(downloadPath) || fs::file_size(downloadPath) == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Download failed.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
removeOldSiblings(2, downloadPath);
|
||||||
|
} catch (const std::exception&e)
|
||||||
|
{
|
||||||
|
Lv2Log::error(SS("Can't remove download siblings" << e.what()));
|
||||||
|
// and carry on.
|
||||||
|
}
|
||||||
|
return downloadPath;
|
||||||
|
} catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
std::filesystem::remove(downloadPath);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
JSON_MAP_BEGIN(UpdateRelease)
|
JSON_MAP_BEGIN(UpdateRelease)
|
||||||
JSON_MAP_REFERENCE(UpdateRelease, updateAvailable)
|
JSON_MAP_REFERENCE(UpdateRelease, updateAvailable)
|
||||||
JSON_MAP_REFERENCE(UpdateRelease, upgradeVersion)
|
JSON_MAP_REFERENCE(UpdateRelease, upgradeVersion)
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ namespace pipedal
|
|||||||
std::chrono::system_clock::time_point LastUpdateTime() const;
|
std::chrono::system_clock::time_point LastUpdateTime() const;
|
||||||
void LastUpdateTime(const std::chrono::system_clock::time_point &timePoint);
|
void LastUpdateTime(const std::chrono::system_clock::time_point &timePoint);
|
||||||
|
|
||||||
|
void ResetCurrentVersion();
|
||||||
bool IsValid() const { return isValid_; }
|
bool IsValid() const { return isValid_; }
|
||||||
const std::string &ErrorMessage() const { return errorMessage_; }
|
const std::string &ErrorMessage() const { return errorMessage_; }
|
||||||
bool IsOnline() const { return isOnline_; }
|
bool IsOnline() const { return isOnline_; }
|
||||||
@@ -116,7 +117,10 @@ namespace pipedal
|
|||||||
UpdatePolicyT GetUpdatePolicy();
|
UpdatePolicyT GetUpdatePolicy();
|
||||||
void SetUpdatePolicy(UpdatePolicyT updatePolicy);
|
void SetUpdatePolicy(UpdatePolicyT updatePolicy);
|
||||||
void ForceUpdateCheck();
|
void ForceUpdateCheck();
|
||||||
|
std::filesystem::path DownloadUpdate(const std::string &url);
|
||||||
private:
|
private:
|
||||||
|
std::string GetUpdateFilename(const std::string &url);
|
||||||
|
|
||||||
UpdatePolicyT updatePolicy = UpdatePolicyT::ReleaseOrBeta;
|
UpdatePolicyT updatePolicy = UpdatePolicyT::ReleaseOrBeta;
|
||||||
using UpdateReleasePredicate = std::function<bool(const GithubRelease &githubRelease)>;
|
using UpdateReleasePredicate = std::function<bool(const GithubRelease &githubRelease)>;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Restart=always
|
|||||||
RestartSec=60
|
RestartSec=60
|
||||||
WorkingDirectory=/var/pipedal
|
WorkingDirectory=/var/pipedal
|
||||||
TimeoutStopSec=15
|
TimeoutStopSec=15
|
||||||
|
KillMode=process
|
||||||
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ Restart=always
|
|||||||
TimeoutStartSec=60
|
TimeoutStartSec=60
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
TimeoutStopSec=15
|
TimeoutStopSec=15
|
||||||
|
|
||||||
WorkingDirectory=/var/pipedal
|
WorkingDirectory=/var/pipedal
|
||||||
Environment=JACK_PROMISCUOUS_SERVER=audio
|
Environment=JACK_PROMISCUOUS_SERVER=audio
|
||||||
Environment=JACK_NO_AUDIO_RESERVATION=1
|
Environment=JACK_NO_AUDIO_RESERVATION=1
|
||||||
|
|||||||
Reference in New Issue
Block a user