diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java index 91b853e7..b4b9dc92 100644 --- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java +++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java @@ -333,7 +333,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe stopped = true; p2PService.getPeerManager().removeListener(this); p2PService.removeDecryptedDirectMessageListener(this); - signedOfferKeyImagePoller.clearKeyImages(); + if (signedOfferKeyImagePoller != null) signedOfferKeyImagePoller.clearKeyImages(); stopPeriodicRefreshOffersTimer(); stopPeriodicRepublishOffersTimer(); diff --git a/core/src/main/java/haveno/core/trade/Trade.java b/core/src/main/java/haveno/core/trade/Trade.java index a16d2b97..05447704 100644 --- a/core/src/main/java/haveno/core/trade/Trade.java +++ b/core/src/main/java/haveno/core/trade/Trade.java @@ -54,10 +54,13 @@ import haveno.core.xmr.wallet.XmrWalletService; import haveno.network.p2p.AckMessage; import haveno.network.p2p.NodeAddress; import haveno.network.p2p.P2PService; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; @@ -333,6 +336,10 @@ public abstract class Trade implements Tradable, Model { private long amount; @Setter private long price; + private int initStep = 0; + private static final int TOTAL_INIT_STEPS = 15; // total estimated steps + @Getter + private double initProgress = 0; @Nullable @Getter private State state = State.PREPARATION; @@ -368,6 +375,7 @@ public abstract class Trade implements Tradable, Model { @Getter transient final private XmrWalletService xmrWalletService; + transient final private DoubleProperty initProgressProperty = new SimpleDoubleProperty(0.0); transient final private ObjectProperty stateProperty = new SimpleObjectProperty<>(state); transient final private ObjectProperty phaseProperty = new SimpleObjectProperty<>(state.phase); transient final private ObjectProperty payoutStateProperty = new SimpleObjectProperty<>(payoutState); @@ -1167,7 +1175,10 @@ public abstract class Trade implements Tradable, Model { isInitialized = false; isShutDown = true; synchronized (walletLock) { - if (wallet != null) closeWallet(); + if (wallet != null) { + saveWallet(); + stopWallet(); + } } if (tradePhaseSubscription != null) tradePhaseSubscription.unsubscribe(); if (payoutStateSubscription != null) payoutStateSubscription.unsubscribe(); @@ -1206,6 +1217,11 @@ public abstract class Trade implements Tradable, Model { } } + public void addInitProgressStep() { + initProgress = Math.min(1.0, (double) ++initStep / TOTAL_INIT_STEPS); + UserThread.execute(() -> initProgressProperty.set(initProgress)); + } + public void setState(State state) { if (isInitialized) { // We don't want to log at startup the setState calls from all persisted trades @@ -1551,6 +1567,10 @@ public abstract class Trade implements Tradable, Model { return getPayoutState().ordinal() >= PayoutState.PAYOUT_UNLOCKED.ordinal(); } + public ReadOnlyDoubleProperty initProgressProperty() { + return initProgressProperty; + } + public ReadOnlyObjectProperty stateProperty() { return stateProperty; } diff --git a/core/src/main/java/haveno/core/trade/TradeManager.java b/core/src/main/java/haveno/core/trade/TradeManager.java index b565f0c1..d3efd2b9 100644 --- a/core/src/main/java/haveno/core/trade/TradeManager.java +++ b/core/src/main/java/haveno/core/trade/TradeManager.java @@ -834,6 +834,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi trade.getMaker().setPubKeyRing(trade.getOffer().getPubKeyRing()); trade.getSelf().setPubKeyRing(model.getPubKeyRing()); trade.getSelf().setPaymentAccountId(paymentAccountId); + trade.addInitProgressStep(); // ensure trade is not already open Optional tradeOptional = getOpenTrade(offer.getId()); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java index 7d581e45..6b404b7f 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeSendSignContractRequest.java @@ -72,6 +72,7 @@ public class MaybeSendSignContractRequest extends TradeTask { } // create deposit tx and freeze inputs + trade.addInitProgressStep(); MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade); // collect reserved key images @@ -142,6 +143,7 @@ public class MaybeSendSignContractRequest extends TradeTask { private void completeAux() { trade.setState(State.CONTRACT_SIGNATURE_REQUESTED); + trade.addInitProgressStep(); processModel.getTradeManager().requestPersistence(); complete(); } diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositResponse.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositResponse.java index 37eb3aee..b5d98ec5 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositResponse.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositResponse.java @@ -44,6 +44,7 @@ public class ProcessDepositResponse extends TradeTask { // set success state trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS); + trade.addInitProgressStep(); processModel.getTradeManager().requestPersistence(); complete(); } catch (Throwable t) { diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java index 15ee01b2..816b85d7 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java @@ -76,6 +76,7 @@ public class ProcessInitMultisigRequest extends TradeTask { // prepare multisig if applicable boolean updateParticipants = false; if (trade.getSelf().getPreparedMultisigHex() == null) { + trade.addInitProgressStep(); log.info("Preparing multisig wallet for {} {}", trade.getClass().getSimpleName(), trade.getId()); multisigWallet = trade.createWallet(); trade.getSelf().setPreparedMultisigHex(multisigWallet.prepareMultisig()); @@ -217,6 +218,7 @@ public class ProcessInitMultisigRequest extends TradeTask { } private void completeAux() { + trade.addInitProgressStep(); complete(); } } diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitTradeRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitTradeRequest.java index 42931bb0..d369e68d 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitTradeRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitTradeRequest.java @@ -133,6 +133,7 @@ public class ProcessInitTradeRequest extends TradeTask { checkArgument(request.getTradeAmount() == trade.getAmount().longValueExact(), "Trade amount does not match request's trade amount"); // persist trade + trade.addInitProgressStep(); processModel.getTradeManager().requestPersistence(); complete(); } catch (Throwable t) { diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessSignContractRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessSignContractRequest.java index cabe5ca5..13b6ad9b 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessSignContractRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessSignContractRequest.java @@ -162,6 +162,7 @@ public class ProcessSignContractRequest extends TradeTask { } private void completeAux() { + trade.addInitProgressStep(); trade.setState(State.CONTRACT_SIGNED); processModel.getTradeManager().requestPersistence(); complete(); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessSignContractResponse.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessSignContractResponse.java index befb9e99..8727f685 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessSignContractResponse.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessSignContractResponse.java @@ -104,6 +104,7 @@ public class ProcessSignContractResponse extends TradeTask { // deposit is requested trade.setState(Trade.State.SENT_PUBLISH_DEPOSIT_TX_REQUEST); + trade.addInitProgressStep(); processModel.getTradeManager().requestPersistence(); } else { log.info("Waiting for another contract signatures to send deposit request"); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/TakerReserveTradeFunds.java b/core/src/main/java/haveno/core/trade/protocol/tasks/TakerReserveTradeFunds.java index 46038c4a..a274237b 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/TakerReserveTradeFunds.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/TakerReserveTradeFunds.java @@ -54,6 +54,7 @@ public class TakerReserveTradeFunds extends TradeTask { processModel.setReserveTx(reserveTx); processModel.getTaker().setReserveTxKeyImages(reservedKeyImages); processModel.getTradeManager().requestPersistence(); + trade.addInitProgressStep(); complete(); } catch (Throwable t) { trade.setErrorMessage("An error occurred.\n" + diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/TakerSendInitTradeRequestToArbitrator.java b/core/src/main/java/haveno/core/trade/protocol/tasks/TakerSendInitTradeRequestToArbitrator.java index 810688d8..df962beb 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/TakerSendInitTradeRequestToArbitrator.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/TakerSendInitTradeRequestToArbitrator.java @@ -47,6 +47,7 @@ public class TakerSendInitTradeRequestToArbitrator extends TradeTask { // send request to signing arbitrator then least used arbitrators until success sendInitTradeRequests(trade.getOffer().getOfferPayload().getArbitratorSigner(), new HashSet(), () -> { + trade.addInitProgressStep(); complete(); }, (errorMessage) -> { log.warn("Cannot initialize trade with arbitrators: " + errorMessage); diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java index 0f55fde8..b2887b57 100644 --- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java +++ b/desktop/src/main/java/haveno/desktop/main/overlays/windows/OfferDetailsWindow.java @@ -18,7 +18,6 @@ package haveno.desktop.main.overlays.windows; import com.google.common.base.Joiner; -import haveno.common.UserThread; import haveno.common.crypto.KeyRing; import haveno.common.util.Tuple2; import haveno.common.util.Tuple4; @@ -31,7 +30,6 @@ import haveno.core.payment.PaymentAccount; import haveno.core.payment.payload.PaymentMethod; import haveno.core.trade.HavenoUtils; import haveno.core.trade.Trade; -import haveno.core.trade.Trade.State; import haveno.core.trade.TradeManager; import haveno.core.user.User; import haveno.core.util.FormattingUtils; @@ -85,7 +83,8 @@ public class OfferDetailsWindow extends Overlay { private Optional takeOfferHandlerOptional = Optional.empty(); private BusyAnimation busyAnimation; private TradeManager tradeManager; - private Subscription tradeStateSubscription; + private Subscription numTradesSubscription; + private Subscription initProgressSubscription; /////////////////////////////////////////////////////////////////////////////////////////// // Public API @@ -145,6 +144,16 @@ public class OfferDetailsWindow extends Overlay { protected void onHidden() { if (busyAnimation != null) busyAnimation.stop(); + + if (numTradesSubscription != null) { + numTradesSubscription.unsubscribe(); + numTradesSubscription = null; + } + + if (initProgressSubscription != null) { + initProgressSubscription.unsubscribe(); + initProgressSubscription = null; + } } @Override @@ -407,31 +416,25 @@ public class OfferDetailsWindow extends Overlay { spinnerInfoLabel.setText(Res.get("createOffer.fundsBox.placeOfferSpinnerInfo")); placeOfferHandlerOptional.ifPresent(Runnable::run); } else { - State lastState = Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS; - spinnerInfoLabel.setText(Res.get("takeOffer.fundsBox.takeOfferSpinnerInfo") + " " + getPercentString(0, lastState.ordinal())); + + // subscribe to trade progress + spinnerInfoLabel.setText(Res.get("takeOffer.fundsBox.takeOfferSpinnerInfo") + " 0%"); + numTradesSubscription = EasyBind.subscribe(tradeManager.getNumPendingTrades(), newNum -> { + subscribeToProgress(spinnerInfoLabel); + }); + takeOfferHandlerOptional.ifPresent(Runnable::run); - - // update trade state progress - UserThread.runAfter(() -> { - Trade trade = tradeManager.getTrade(offer.getId()); - if (trade == null) return; - tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newState -> { - String progress = getPercentString(newState.ordinal(), lastState.ordinal()); - spinnerInfoLabel.setText(Res.get("takeOffer.fundsBox.takeOfferSpinnerInfo") + " " + progress); - - // unsubscribe when done - if (newState == lastState) { - tradeStateSubscription.unsubscribe(); - tradeStateSubscription = null; - } - }); - }, 1); } } }); } - private static String getPercentString(int newOrdinal, int lastOrdinal) { - return (int) ((double) newOrdinal / (double) lastOrdinal * 100) + "%"; + private void subscribeToProgress(Label spinnerInfoLabel) { + Trade trade = tradeManager.getTrade(offer.getId()); + if (trade == null || initProgressSubscription != null) return; + initProgressSubscription = EasyBind.subscribe(trade.initProgressProperty(), newProgress -> { + String progress = (int) (newProgress.doubleValue() * 100.0) + "%"; + spinnerInfoLabel.setText(Res.get("takeOffer.fundsBox.takeOfferSpinnerInfo") + " " + progress); + }); } }