From c0f9073698100c50330197b7d14147b3a5afb1c6 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 13 Mar 2020 15:19:19 -0300 Subject: [PATCH] Improve AvoidStandyModeService 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. --- .../core/app/AvoidStandbyModeService.java | 145 +++++++++++++++++- .../java/bisq/core/app/BisqExecutable.java | 1 + .../bisq/core/app/OSXStandbyModeDisabler.java | 27 ---- .../main/java/bisq/desktop/app/BisqApp.java | 2 - .../settings/preferences/PreferencesView.java | 2 +- 5 files changed, 141 insertions(+), 36 deletions(-) delete mode 100644 core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java diff --git a/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java b/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java index 365e0f9a70c..470f7b3c71a 100644 --- a/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java +++ b/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java @@ -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; @@ -46,6 +58,8 @@ public class AvoidStandbyModeService { private final Preferences preferences; private final Config config; + private final Optional inhibitorPathSpec; + private CountDownLatch stopLinuxInhibitorCountdownLatch; private volatile boolean isStopped; @@ -53,11 +67,12 @@ public class AvoidStandbyModeService { 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(); } @@ -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 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())) { @@ -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 inhibitorPath() { + for (Optional installedInhibitor : installedInhibitors.get()) { + if (installedInhibitor.isPresent()) { + return installedInhibitor; + } + } + return Optional.empty(); // falling back to silent audio file player + } + + private Optional 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 runningInhibitorProcess() { + final ProcessHandle[] inhibitorProc = new ProcessHandle[1]; + inhibitorPathSpec.ifPresent(cmd -> { + Optional 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 isCmdInstalled = (p) -> { + File executable = Paths.get(p).toFile(); + return executable.exists() && executable.canExecute(); + }; + + private final Function> cmdPath = (possiblePaths) -> { + for (String path : possiblePaths) { + if (isCmdInstalled.test(path)) { + return Optional.of(path); + } + } + return Optional.empty(); + }; + + private final Supplier>> 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> gnomeSessionInhibitPathSpec = () -> + cmdPath.apply(new String[]{"/usr/bin/gnome-session-inhibit", "/bin/gnome-session-inhibit"}); + + private final Supplier> systemdInhibitPathSpec = () -> + cmdPath.apply(new String[]{"/usr/bin/systemd-inhibit", "/bin/systemd-inhibit"}); + + private final Supplier> caffeinatePathSec = () -> + cmdPath.apply(new String[]{"/usr/bin/caffeinate"}); } diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index 39a9461b510..6efc7bdd6b1 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -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"); diff --git a/core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java b/core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java deleted file mode 100644 index cf5832abc8e..00000000000 --- a/core/src/main/java/bisq/core/app/OSXStandbyModeDisabler.java +++ /dev/null @@ -1,27 +0,0 @@ -package bisq.core.app; - -import bisq.common.util.Utilities; - -import java.io.IOException; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class OSXStandbyModeDisabler { - public void doIt() { - if (!Utilities.isOSX()) { - return; - } - long pid = ProcessHandle.current().pid(); - try { - String[] params = {"/usr/bin/caffeinate", "-w", "" + pid}; - - // we only start the process. caffeinate blocks until we exit. - new ProcessBuilder(params).start(); - log.info("disabled power management via " + String.join(" ", params)); - } catch (IOException e) { - log.error("could not disable standby mode on osx", e); - } - - } -} diff --git a/desktop/src/main/java/bisq/desktop/app/BisqApp.java b/desktop/src/main/java/bisq/desktop/app/BisqApp.java index 5d5e8857c5a..f389bab7ca6 100644 --- a/desktop/src/main/java/bisq/desktop/app/BisqApp.java +++ b/desktop/src/main/java/bisq/desktop/app/BisqApp.java @@ -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; @@ -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); diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 976b440e638..6fe9ff8fa12 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -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