Skip to content

Commit

Permalink
Improve AvoidStandyModeService
Browse files Browse the repository at this point in the history
This change substitutes use of memory efficient, OS supplied sleep/suspend
inhibitor utilties for the silent audio file player on Linux, merges
OSXStandbyModeDisabler functionality into AvoidStandyModeService, and
removes that class.

This change also stops Bisq from running the audio file on OSX;  it is
currently running both caffeinate and the silent audio file, and the avoid
standby mode button in the the OSX settings view is currently toggling
the audio player on and off, but not caffeinate.  (The only way to shut
down caffeinate is by shutting down Bisq.)

The OSX avoid standby mode button button has not been hidden so a cached
'do not avoid standby mode' preference does not leave the user stuck without
a caffeinate service the next time they start Bisq. (They can use it to
turn caffeinate on, but it can't be used to turn it off without shutting
down Bisq too.)

The avoid standy mode button is now displayed on Linux because the native
inhibitor can be toggled on and off.

There is no change to the avoid shutdown service on Windows and Unix.
  • Loading branch information
ghubstan committed Mar 13, 2020
1 parent 96934d9 commit c0f9073
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 36 deletions.
145 changes: 139 additions & 6 deletions core/src/main/java/bisq/core/app/AvoidStandbyModeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,25 @@
import bisq.common.config.Config;
import bisq.common.storage.FileUtil;
import bisq.common.storage.ResourceNotFoundException;
import bisq.common.util.Utilities;

import javax.inject.Inject;
import javax.inject.Singleton;

import java.nio.file.Paths;

import java.io.File;
import java.io.IOException;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import lombok.extern.slf4j.Slf4j;


Expand All @@ -46,18 +58,21 @@ public class AvoidStandbyModeService {

private final Preferences preferences;
private final Config config;
private final Optional<String> inhibitorPathSpec;
private CountDownLatch stopLinuxInhibitorCountdownLatch;

private volatile boolean isStopped;

@Inject
public AvoidStandbyModeService(Preferences preferences, Config config) {
this.preferences = preferences;
this.config = config;

this.inhibitorPathSpec = inhibitorPath();
preferences.getUseStandbyModeProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
isStopped = true;
log.info("AvoidStandbyModeService stopped");
if (Utilities.isLinux() && runningInhibitorProcess().isPresent()) {
Objects.requireNonNull(stopLinuxInhibitorCountdownLatch).countDown();
}
} else {
start();
}
Expand All @@ -73,13 +88,62 @@ public void init() {

private void start() {
isStopped = false;
log.info("AvoidStandbyModeService started");
new Thread(this::play, "AvoidStandbyModeService-thread").start();
if (Utilities.isLinux() || Utilities.isOSX()) {
startInhibitor();
} else {
new Thread(this::playSilentAudioFile, "AvoidStandbyModeService-thread").start();
}
}

public void shutDown() {
isStopped = true;
stopInhibitor();
}

private void startInhibitor() {
try {
if (runningInhibitorProcess().isPresent()) {
log.info("Inhibitor already started");
return;
}
inhibitCommand().ifPresent(cmd -> {
try {
new ProcessBuilder(cmd).start();
log.info("Started -- disabled power management via {}", String.join(" ", cmd));
if (Utilities.isLinux()) {
stopLinuxInhibitorCountdownLatch = new CountDownLatch(1);
new Thread(this::stopInhibitor, "StopAvoidStandbyModeService-thread").start();
}
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
log.error("Cannot avoid standby mode", e);
}
}

private void stopInhibitor() {
try {
// Cannot toggle off osx caffeinate, but it will shutdown with bisq.
if (Utilities.isLinux()) {
if (!isStopped) {
Objects.requireNonNull(stopLinuxInhibitorCountdownLatch).await();
}
Optional<ProcessHandle> runningInhibitor = runningInhibitorProcess();
runningInhibitor.ifPresent(processHandle -> {
processHandle.destroy();
log.info("Stopped");
});
}
} catch (Exception e) {
log.error("Stop inhibitor thread interrupted", e);
}
}

private void play() {
private void playSilentAudioFile() {
try {
log.info("Started");
while (!isStopped) {
try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(getSoundFile());
SourceDataLine sourceDataLine = getSourceDataLine(audioInputStream.getFormat())) {
Expand Down Expand Up @@ -113,4 +177,73 @@ private SourceDataLine getSourceDataLine(AudioFormat audioFormat) throws LineUna
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat);
return (SourceDataLine) AudioSystem.getLine(dataLineInfo);
}

private Optional<String> inhibitorPath() {
for (Optional<String> installedInhibitor : installedInhibitors.get()) {
if (installedInhibitor.isPresent()) {
return installedInhibitor;
}
}
return Optional.empty(); // falling back to silent audio file player
}

private Optional<String[]> inhibitCommand() {
final String[] params;
if (inhibitorPathSpec.isPresent()) {
String cmd = inhibitorPathSpec.get();
if (Utilities.isLinux()) {
params = cmd.contains("gnome-session-inhibit")
? new String[]{cmd, "--app-id", "Bisq", "--inhibit", "suspend", "--reason", "Avoid Standby", "--inhibit-only"}
: new String[]{cmd, "--who", "Bisq", "--what", "sleep", "--why", "Avoid Standby", "--mode", "block", "tail", "-f", "/dev/null"};
} else {
params = Utilities.isOSX() ? new String[]{cmd, "-w", "" + ProcessHandle.current().pid()} : null;
}
} else {
params = null; // fall back to silent audio file player
}
return params == null ? Optional.empty() : Optional.of(params);
}

private Optional<ProcessHandle> runningInhibitorProcess() {
final ProcessHandle[] inhibitorProc = new ProcessHandle[1];
inhibitorPathSpec.ifPresent(cmd -> {
Optional<ProcessHandle> jvmProc = ProcessHandle.of(ProcessHandle.current().pid());
jvmProc.ifPresent(proc -> proc.children().forEach(childProc -> childProc.info().command().ifPresent(command -> {
if (command.equals(cmd) && childProc.isAlive()) {
inhibitorProc[0] = childProc;
}
})));
});
return inhibitorProc[0] == null ? Optional.empty() : Optional.of(inhibitorProc[0]);
}

private final Predicate<String> isCmdInstalled = (p) -> {
File executable = Paths.get(p).toFile();
return executable.exists() && executable.canExecute();
};

private final Function<String[], Optional<String>> cmdPath = (possiblePaths) -> {
for (String path : possiblePaths) {
if (isCmdInstalled.test(path)) {
return Optional.of(path);
}
}
return Optional.empty();
};

private final Supplier<List<Optional<String>>> installedInhibitors = () ->
new ArrayList<>() {{
add(gnomeSessionInhibitPathSpec.get()); // On linux, preferred inhibitor is gnome-session-inhibit,
add(systemdInhibitPathSpec.get()); // then fall back to systemd-inhibit if it is installed.
add(caffeinatePathSec.get()); // On OSX, caffeinate should be installed.
}};

private final Supplier<Optional<String>> gnomeSessionInhibitPathSpec = () ->
cmdPath.apply(new String[]{"/usr/bin/gnome-session-inhibit", "/bin/gnome-session-inhibit"});

private final Supplier<Optional<String>> systemdInhibitPathSpec = () ->
cmdPath.apply(new String[]{"/usr/bin/systemd-inhibit", "/bin/systemd-inhibit"});

private final Supplier<Optional<String>> caffeinatePathSec = () ->
cmdPath.apply(new String[]{"/usr/bin/caffeinate"});
}
1 change: 1 addition & 0 deletions core/src/main/java/bisq/core/app/BisqExecutable.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ public void gracefulShutDown(ResultHandler resultHandler) {
injector.getInstance(BsqWalletService.class).shutDown();
});
});
injector.getInstance(AvoidStandbyModeService.class).shutDown();
// we wait max 20 sec.
UserThread.runAfter(() -> {
log.warn("Timeout triggered resultHandler");
Expand Down
27 changes: 0 additions & 27 deletions core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java

This file was deleted.

2 changes: 0 additions & 2 deletions desktop/src/main/java/bisq/desktop/app/BisqApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import bisq.desktop.util.ImageUtil;

import bisq.core.app.AvoidStandbyModeService;
import bisq.core.app.OSXStandbyModeDisabler;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.dao.governance.voteresult.MissingDataRequestService;
Expand Down Expand Up @@ -133,7 +132,6 @@ public void startApplication(Runnable onUiReadyHandler) {
scene = createAndConfigScene(mainView, injector);
setupStage(scene);

injector.getInstance(OSXStandbyModeDisabler.class).doIt();
injector.getInstance(AvoidStandbyModeService.class).init();

UserThread.runPeriodically(() -> Profiler.printSystemLoad(log), LOG_MEMORY_PERIOD_MIN, TimeUnit.MINUTES);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public PreferencesView(PreferencesViewModel model,
!rpcUser.isEmpty() &&
!rpcPassword.isEmpty() &&
rpcBlockNotificationPort != Config.UNSPECIFIED_PORT;
this.displayStandbyModeFeature = Utilities.isOSX() || Utilities.isWindows();
this.displayStandbyModeFeature = Utilities.isLinux() || Utilities.isOSX() || Utilities.isWindows();
}

@Override
Expand Down

0 comments on commit c0f9073

Please sign in to comment.