Skip to content

Commit

Permalink
Merge pull request #6435 from jmacxx/add_shutdown_guard
Browse files Browse the repository at this point in the history
Prompt to confirm before shutting down.
  • Loading branch information
ripcurlx authored Dec 5, 2022
2 parents c381006 + 0e3314c commit 5dc006e
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 65 deletions.
5 changes: 3 additions & 2 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ shared.readMore=Read more
shared.openHelp=Open Help
shared.warning=Warning
shared.close=Close
shared.closeAnywayDanger=Shut down anyway (DANGER!)
shared.okWait=Ok I'll wait
shared.closeAnywayDanger=Shut down anyway
shared.okWait=Keep Bisq Open
shared.cancel=Cancel
shared.ok=OK
shared.yes=Yes
Expand Down Expand Up @@ -3151,6 +3151,7 @@ popup.info.shutDownWithTradeInit={0}\n\
popup.info.shutDownWithDisputeInit=Bisq is being shut down, but there is a Dispute system message still pending.\n\
Please wait a minute before shutting down.\n\
For further help please contact the Bisq support channel at the Bisq Matrix Space.
popup.info.shutDownQuery=Are you sure you want to exit Bisq?
popup.info.qubesOSSetupInfo=It appears you are running Bisq on Qubes OS. \n\n\
Please make sure your Bisq qube is setup according to our Setup Guide at [HYPERLINK:https://bisq.wiki/Running_Bisq_on_Qubes].
popup.info.p2pStatusIndicator.red={0}\n\n\
Expand Down
109 changes: 46 additions & 63 deletions desktop/src/main/java/bisq/desktop/app/BisqApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import static bisq.common.crypto.Hash.getSha256Hash;
import static bisq.common.util.Utilities.encodeToHex;
import static bisq.desktop.util.Layout.INITIAL_WINDOW_HEIGHT;
import static bisq.desktop.util.Layout.INITIAL_WINDOW_WIDTH;
import static bisq.desktop.util.Layout.MIN_WINDOW_HEIGHT;
Expand Down Expand Up @@ -366,93 +368,74 @@ private void addSceneKeyEventHandler(Scene scene, Injector injector) {
}

private void shutDownByUser() {
// two potential popup prompts to user before shutting down, each step can possibly abort the shutdown
shutDownByUserCheckTrades().thenAccept(shutdownStage1 -> {
if (shutdownStage1) {
shutDownByUserCheckOffers().thenAccept(shutdownStage2 -> {
if (shutdownStage2) {
shutDownByUserCheckDisputes().thenAccept(shutdownStage3 -> {
if (shutdownStage3) {
stop();
}
});
}
});
String potentialIssues = checkTradesAtShutdown() + checkDisputesAtShutdown() + checkOffersAtShutdown();
promptUserAtShutdown(potentialIssues).thenAccept(asyncOkToShutDown -> {
if (asyncOkToShutDown) {
stop();
}
});
}

private CompletableFuture<Boolean> shutDownByUserCheckTrades() {
private String checkTradesAtShutdown() {
log.info("Checking trades at shutdown");
final CompletableFuture<Boolean> asyncStatus = new CompletableFuture<>();
Instant fiveMinutesAgo = Instant.ofEpochSecond(Instant.now().getEpochSecond() - TimeUnit.MINUTES.toSeconds(5));
String tradeIdsWithOperationsPending = "";
for (Trade trade : injector.getInstance(TradeManager.class).getObservableList()) {
if (trade.getTradePhase().equals(Trade.Phase.TAKER_FEE_PUBLISHED) &&
new Date(trade.getTakeOfferDate()).toInstant().isAfter(fiveMinutesAgo)) {
String tradeDateString = DisplayUtils.formatDateTime(new Date(trade.getTakeOfferDate()));
tradeIdsWithOperationsPending += Res.get("shared.tradeId") + ": " + trade.getShortId() + " " +
Res.get("shared.dateTime") + ": " + tradeDateString + System.lineSeparator();
break;
String tradeInfo = Res.get("shared.tradeId") + ": " + trade.getShortId() + " " +
Res.get("shared.dateTime") + ": " + tradeDateString;
return Res.get("popup.info.shutDownWithTradeInit", tradeInfo) + System.lineSeparator() + System.lineSeparator();
}
}
if (tradeIdsWithOperationsPending.length() > 0) {
// We show a popup to inform user that some trades are still being initialised.
new Popup().warning(Res.get("popup.info.shutDownWithTradeInit", tradeIdsWithOperationsPending))
.actionButtonText(Res.get("shared.okWait"))
.onAction(() -> asyncStatus.complete(false))
.closeButtonText(Res.get("shared.closeAnywayDanger"))
.onClose(() -> asyncStatus.complete(true))
.show();
} else {
asyncStatus.complete(true);
return "";
}

private String checkDisputesAtShutdown() {
log.info("Checking disputes at shutdown");
if (injector.getInstance(MediationManager.class).hasPendingMessageAtShutdown() ||
injector.getInstance(RefundManager.class).hasPendingMessageAtShutdown()) {
return Res.get("popup.info.shutDownWithDisputeInit") + System.lineSeparator() + System.lineSeparator();
}
return asyncStatus;
return "";
}

private CompletableFuture<Boolean> shutDownByUserCheckOffers() {
private String checkOffersAtShutdown() {
log.info("Checking offers at shutdown");
final CompletableFuture<Boolean> asyncStatus = new CompletableFuture<>();
boolean hasOpenOffers = false;
for (OpenOffer openOffer : injector.getInstance(OpenOfferManager.class).getObservableList()) {
if (openOffer.getState().equals(OpenOffer.State.AVAILABLE)) {
hasOpenOffers = true;
break;
return Res.get("popup.info.shutDownWithOpenOffers") + System.lineSeparator() + System.lineSeparator();
}
}
if (!hasOpenOffers) {
// No open offers, so no need to show the popup.
asyncStatus.complete(true);
return asyncStatus;
}
return "";
}

// We show a popup to inform user that open offers will be removed if Bisq is not running.
String key = "showOpenOfferWarnPopupAtShutDown";
private CompletableFuture<Boolean> promptUserAtShutdown(String issueInfo) {
final CompletableFuture<Boolean> asyncStatus = new CompletableFuture<>();
if (issueInfo.length() > 0) {
// We maybe show a popup to inform user that some issues are pending
String key = encodeToHex(getSha256Hash(issueInfo));
if (injector.getInstance(Preferences.class).showAgain(key) && !DevEnv.isDevMode()) {
new Popup().warning(issueInfo)
.actionButtonText(Res.get("shared.okWait"))
.onAction(() -> asyncStatus.complete(false))
.closeButtonText(Res.get("shared.closeAnywayDanger"))
.onClose(() -> asyncStatus.complete(true))
.dontShowAgainId(key)
.width(800)
.show();
return asyncStatus;
}
}
// if no warning popup has been shown yet, prompt user if they really intend to shut down
String key = "popup.info.shutDownQuery";
if (injector.getInstance(Preferences.class).showAgain(key) && !DevEnv.isDevMode()) {
new Popup().information(Res.get("popup.info.shutDownWithOpenOffers"))
.dontShowAgainId(key)
.useShutDownButton()
new Popup().headLine(Res.get("popup.info.shutDownQuery"))
.actionButtonText(Res.get("shared.yes"))
.onAction(() -> asyncStatus.complete(true))
.closeButtonText(Res.get("shared.cancel"))
.closeButtonText(Res.get("shared.no"))
.onClose(() -> asyncStatus.complete(false))
.show();
} else {
asyncStatus.complete(true);
}
return asyncStatus;
}

private CompletableFuture<Boolean> shutDownByUserCheckDisputes() {
log.info("Checking disputes at shutdown");
final CompletableFuture<Boolean> asyncStatus = new CompletableFuture<>();
if (injector.getInstance(MediationManager.class).hasPendingMessageAtShutdown() ||
injector.getInstance(RefundManager.class).hasPendingMessageAtShutdown()) {
// We show a popup to inform user that some messages are still being sent.
new Popup().warning(Res.get("popup.info.shutDownWithDisputeInit"))
.actionButtonText(Res.get("shared.okWait"))
.onAction(() -> asyncStatus.complete(false))
.closeButtonText(Res.get("shared.closeAnywayDanger"))
.onClose(() -> asyncStatus.complete(true))
.dontShowAgainId(key)
.show();
} else {
asyncStatus.complete(true);
Expand Down

0 comments on commit 5dc006e

Please sign in to comment.