diff --git a/src/main/java/io/bitsquare/btc/WalletFacade.java b/src/main/java/io/bitsquare/btc/WalletFacade.java index 967340d7ce..e403024df1 100644 --- a/src/main/java/io/bitsquare/btc/WalletFacade.java +++ b/src/main/java/io/bitsquare/btc/WalletFacade.java @@ -26,6 +26,7 @@ import io.bitsquare.persistence.Persistence; import org.bitcoinj.core.Address; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; +import org.bitcoinj.core.DownloadListener; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.NetworkParameters; @@ -61,11 +62,13 @@ import java.io.Serializable; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @@ -74,7 +77,6 @@ import javax.annotation.concurrent.GuardedBy; import javax.inject.Inject; import javax.inject.Named; -import javafx.application.Platform; import javafx.util.Pair; import org.jetbrains.annotations.NotNull; @@ -101,7 +103,6 @@ public class WalletFacade { private final CryptoFacade cryptoFacade; private final Persistence persistence; private final String appName; - // private final List downloadListeners = new CopyOnWriteArrayList<>(); private final List addressConfidenceListeners = new CopyOnWriteArrayList<>(); private final List txConfidenceListeners = new CopyOnWriteArrayList<>(); private final List balanceListeners = new CopyOnWriteArrayList<>(); @@ -132,12 +133,13 @@ public class WalletFacade { // Public Methods /////////////////////////////////////////////////////////////////////////////////////////// - public void initialize(org.bitcoinj.core.DownloadListener downloadListener, StartupListener startupListener) { + public void initialize(Executor executor, BlockchainDownloadListener blockchainDownloadListener, + StartupListener startupListener) { // Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means // we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener // we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in // a future version. - Threading.USER_THREAD = Platform::runLater; + Threading.USER_THREAD = executor; // If seed is non-null it means we are restoring from backup. walletAppKit = new WalletAppKit(params, AppDirectory.dir(appName).toFile(), appName) { @@ -150,7 +152,7 @@ public class WalletFacade { walletAppKit.peerGroup().setMaxConnections(11); walletAppKit.peerGroup().setBloomFilterFalsePositiveRate(0.00001); initWallet(); - Platform.runLater(startupListener::completed); + executor.execute(() -> startupListener.completed()); } }; // Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen @@ -176,9 +178,25 @@ public class WalletFacade { walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet")); //walletAppKit.useTor(); } + + // DownloadListener does not run yet in a user thread, so we map it our self + DownloadListener downloadListener = new DownloadListener() { + @Override + protected void progress(double percentage, int blocksLeft, Date date) { + super.progress(percentage, blocksLeft, date); + executor.execute(() -> blockchainDownloadListener.progress(percentage)); + } + + @Override + protected void doneDownload() { + super.doneDownload(); + executor.execute(() -> blockchainDownloadListener.doneDownload()); + } + }; + walletAppKit.setDownloadListener(downloadListener) .setBlockingStartup(false) - .setUserAgent("Bitsquare", "0.1"); + .setUserAgent(appName, "0.1"); /* // TODO restore from DeterministicSeed @@ -190,24 +208,15 @@ public class WalletFacade { @Override public void failed(@NotNull Service.State from, @NotNull Throwable failure) { walletAppKit = null; - // TODO show error popup - //crashAlert(failure); + startupListener.failed(failure); } - }, Threading.SAME_THREAD); + }, Threading.USER_THREAD); walletAppKit.startAsync(); } private void initWallet() { wallet = walletAppKit.wallet(); - //walletAppKit.peerGroup().setMaxConnections(11); - - /* if (params == RegTestParams.get()) - walletAppKit.peerGroup().setMinBroadcastConnections(1); - else - walletAppKit.peerGroup().setMinBroadcastConnections(3);*/ - - walletEventListener = new WalletEventListener() { @Override public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) { @@ -282,15 +291,6 @@ public class WalletFacade { // Listener /////////////////////////////////////////////////////////////////////////////////////////// - /* public DownloadListener addDownloadListener(DownloadListener listener) { - downloadListeners.add(listener); - return listener; - } - - public void removeDownloadListener(DownloadListener listener) { - downloadListeners.remove(listener); - }*/ - public AddressConfidenceListener addAddressConfidenceListener(AddressConfidenceListener listener) { addressConfidenceListeners.add(listener); return listener; @@ -1148,38 +1148,14 @@ public class WalletFacade { public static interface StartupListener { void completed(); + + void failed(Throwable failure); } - public static interface DownloadListener { - void progress(double percent); + public static interface BlockchainDownloadListener { + void progress(double percentage); - void downloadComplete(); + void doneDownload(); } - /* private class BlockChainDownloadListener extends org.bitcoinj.core.DownloadListener { - @Override - protected void progress(double percent, int blocksSoFar, Date date) { - super.progress(percent, blocksSoFar, date); - Platform.runLater(() -> onProgressInUserThread(percent)); - } - - @Override - protected void doneDownload() { - super.doneDownload(); - Platform.runLater(this::onDoneDownloadInUserThread); - } - - private void onProgressInUserThread(double percent) { - for (DownloadListener downloadListener : downloadListeners) { - downloadListener.progress(percent); - } - } - - private void onDoneDownloadInUserThread() { - for (DownloadListener downloadListener : downloadListeners) { - downloadListener.downloadComplete(); - } - } - }*/ - } diff --git a/src/main/java/io/bitsquare/gui/bitsquare.css b/src/main/java/io/bitsquare/gui/bitsquare.css index e2ef17d39a..67434f86f5 100644 --- a/src/main/java/io/bitsquare/gui/bitsquare.css +++ b/src/main/java/io/bitsquare/gui/bitsquare.css @@ -23,7 +23,8 @@ lower gradient color on tab: dddddd .root { -bs-grey: #666666; -bs-bg-grey: #dddddd; - + -bs-error-red: #dd0000; + -fx-accent: #0f87c3; -fx-default-button: derive(-fx-accent,95%); -fx-focus-color: -fx-accent; @@ -36,7 +37,9 @@ lower gradient color on tab: dddddd #splash { -fx-background-color: #ffffff; } - +#splash-error-state-msg { + -fx-text-fill: -bs-error-red; +} /* Main UI */ #base-content-container { @@ -52,18 +55,6 @@ lower gradient color on tab: dddddd -fx-font-size: 18; } -#info-label { - -fx-font-size: 14; -} - -#online-label { - -fx-fill: green; -} - -#offline-label { - -fx-fill: red; -} - /* Main navigation */ #nav-button { diff --git a/src/main/java/io/bitsquare/gui/main/MainModel.java b/src/main/java/io/bitsquare/gui/main/MainModel.java index 2ccec1f9b2..4e61501cd4 100644 --- a/src/main/java/io/bitsquare/gui/main/MainModel.java +++ b/src/main/java/io/bitsquare/gui/main/MainModel.java @@ -29,12 +29,8 @@ import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.user.User; -import org.bitcoinj.core.DownloadListener; - import com.google.inject.Inject; -import java.util.Date; - import javax.inject.Named; import javafx.application.Platform; @@ -45,8 +41,7 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.MapChangeListener; import javafx.collections.ObservableList; @@ -70,7 +65,8 @@ class MainModel extends UIModel { final BooleanProperty backendReady = new SimpleBooleanProperty(); final DoubleProperty networkSyncProgress = new SimpleDoubleProperty(-1); final IntegerProperty numPendingTrades = new SimpleIntegerProperty(0); - final StringProperty bootstrapState = new SimpleStringProperty(); + final ObjectProperty bootstrapState = new SimpleObjectProperty<>(); + final ObjectProperty walletFacadeException = new SimpleObjectProperty(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -130,41 +126,46 @@ class MainModel extends UIModel { @Override public void onBootstrapStateChanged(BootstrapState bootstrapState) { - MainModel.this.bootstrapState.set(bootstrapState.getMessage()); + MainModel.this.bootstrapState.set(bootstrapState); } }); Profiler.printMsgWithTime("MainModel.initFacades"); - DownloadListener downloadListener = new DownloadListener() { + WalletFacade.BlockchainDownloadListener blockchainDownloadListener = new WalletFacade + .BlockchainDownloadListener() { @Override - protected void progress(double percent, int blocksLeft, Date date) { - super.progress(percent, blocksLeft, date); - Platform.runLater(() -> { - networkSyncProgress.set(percent / 100.0); + public void progress(double percentage) { + networkSyncProgress.set(percentage / 100.0); - if (facadesInitialised && percent >= 100.0) - backendReady.set(true); - }); + if (facadesInitialised && percentage >= 100.0) + backendReady.set(true); } @Override - protected void doneDownload() { - super.doneDownload(); - Platform.runLater(() -> { - networkSyncProgress.set(1.0); + public void doneDownload() { + networkSyncProgress.set(1.0); - if (facadesInitialised) - backendReady.set(true); - }); + if (facadesInitialised) + backendReady.set(true); } }; - walletFacade.initialize(downloadListener, () -> { - walletFacadeInited = true; - if (messageFacadeInited) - onFacadesInitialised(); - }); + WalletFacade.StartupListener startupListener = new WalletFacade.StartupListener() { + @Override + public void completed() { + walletFacadeInited = true; + if (messageFacadeInited) + onFacadesInitialised(); + } + + @Override + public void failed(final Throwable failure) { + walletFacadeException.set(failure); + } + }; + + walletFacade.initialize(Platform::runLater, blockchainDownloadListener, startupListener); } diff --git a/src/main/java/io/bitsquare/gui/main/MainPM.java b/src/main/java/io/bitsquare/gui/main/MainPM.java index f2059b6867..4a7451d879 100644 --- a/src/main/java/io/bitsquare/gui/main/MainPM.java +++ b/src/main/java/io/bitsquare/gui/main/MainPM.java @@ -20,6 +20,7 @@ package io.bitsquare.gui.main; import io.bitsquare.bank.BankAccount; import io.bitsquare.gui.PresentationModel; import io.bitsquare.gui.util.BSFormatter; +import io.bitsquare.network.BootstrapState; import com.google.inject.Inject; @@ -47,10 +48,16 @@ class MainPM extends PresentationModel { final BooleanProperty backendReady = new SimpleBooleanProperty(); final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty(); final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty(); - final StringProperty bootstrapState = new SimpleStringProperty(); - final StringProperty bitcoinSyncState = new SimpleStringProperty("Initializing"); + final StringProperty blockchainSyncState = new SimpleStringProperty("Initializing"); final IntegerProperty numPendingTrades = new SimpleIntegerProperty(); - final DoubleProperty networkSyncProgress = new SimpleDoubleProperty(); + final DoubleProperty blockchainSyncProgress = new SimpleDoubleProperty(); + final BooleanProperty blockchainSyncIndicatorVisible = new SimpleBooleanProperty(true); + final DoubleProperty bootstrapProgress = new SimpleDoubleProperty(-1); + final BooleanProperty bootstrapFailed = new SimpleBooleanProperty(); + final BooleanProperty bootstrapIndicatorVisible = new SimpleBooleanProperty(true); + final StringProperty bootstrapState = new SimpleStringProperty(); + final StringProperty bootstrapErrorMsg = new SimpleStringProperty(); + final StringProperty walletFacadeErrorMsg = new SimpleStringProperty(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -74,16 +81,42 @@ class MainPM extends PresentationModel { super.initialize(); backendReady.bind(model.backendReady); - networkSyncProgress.bind(model.networkSyncProgress); numPendingTrades.bind(model.numPendingTrades); - model.bootstrapState.addListener((ov, oldValue, newValue) -> - bootstrapState.set("Connection to P2P network: " + newValue)); + model.bootstrapState.addListener((ov, oldValue, newValue) -> { + if (newValue == BootstrapState.DIRECT_SUCCESS || + newValue == BootstrapState.NAT_SUCCESS || + newValue == BootstrapState.RELAY_SUCCESS) { + bootstrapState.set("Successfully connected to P2P network: " + newValue.getMessage()); + bootstrapIndicatorVisible.set(false); + bootstrapProgress.set(1); + } + else if (newValue == BootstrapState.PEER_CREATION_FAILED || + newValue == BootstrapState.DIRECT_FAILED || + newValue == BootstrapState.NAT_FAILED || + newValue == BootstrapState.RELAY_FAILED) { - bootstrapState.set(model.bootstrapState.get()); + bootstrapErrorMsg.set(newValue.getMessage()); + bootstrapState.set("Connection to P2P network failed."); + bootstrapIndicatorVisible.set(false); + bootstrapProgress.set(0); + bootstrapFailed.set(true); + } + else { + bootstrapState.set("Connecting to P2P network: " + newValue.getMessage()); + } + } + ); - model.networkSyncProgress.addListener((ov, oldValue, newValue) -> updateBitcoinSyncState((double) newValue)); - updateBitcoinSyncState(model.networkSyncProgress.get()); + model.walletFacadeException.addListener((ov, oldValue, newValue) -> { + blockchainSyncProgress.set(0); + blockchainSyncIndicatorVisible.set(false); + blockchainSyncState.set("Startup failed."); + walletFacadeErrorMsg.set(((Throwable) newValue).getMessage()); + }); + + model.networkSyncProgress.addListener((ov, oldValue, newValue) -> setNetworkSyncProgress((double) newValue)); + setNetworkSyncProgress(model.networkSyncProgress.get()); model.getBankAccounts().addListener((ListChangeListener) change -> { bankAccountsComboBoxDisable.set(change.getList().isEmpty()); @@ -149,14 +182,16 @@ class MainPM extends PresentationModel { // Private /////////////////////////////////////////////////////////////////////////////////////////// - private void updateBitcoinSyncState(double value) { - if (value > 0.0) - bitcoinSyncState.set("Synchronizing with bitcoin network: " + - formatter.formatToPercent(value)); - else if (value == 1) - bitcoinSyncState.set("Synchronizing with bitcoin network completed."); + private void setNetworkSyncProgress(double value) { + blockchainSyncProgress.set(value); + if (value >= 1) + blockchainSyncState.set("Synchronization completed."); + else if (value > 0.0) + blockchainSyncState.set("Synchronizing blockchain: " + formatter.formatToPercent(value)); else - bitcoinSyncState.set("Synchronizing with bitcoin network..."); + blockchainSyncState.set("Connecting to bitcoin network..."); + + blockchainSyncIndicatorVisible.set(value < 1); } } diff --git a/src/main/java/io/bitsquare/gui/main/MainViewCB.java b/src/main/java/io/bitsquare/gui/main/MainViewCB.java index 5c359a1a60..3cd6f5becb 100644 --- a/src/main/java/io/bitsquare/gui/main/MainViewCB.java +++ b/src/main/java/io/bitsquare/gui/main/MainViewCB.java @@ -286,36 +286,52 @@ public class MainViewCB extends ViewCB { ImageView logo = new ImageView(); logo.setId("image-splash-logo"); - Label bitcoinSyncStateLabel = new Label(); - bitcoinSyncStateLabel.textProperty().bind(presentationModel.bitcoinSyncState); + Label blockchainSyncLabel = new Label(); + blockchainSyncLabel.textProperty().bind(presentationModel.blockchainSyncState); + presentationModel.walletFacadeErrorMsg.addListener((ov, oldValue, newValue) -> { + blockchainSyncLabel.setId("splash-error-state-msg"); + Popups.openErrorPopup("Error", "An error occurred at startup. \n\nError message:\n" + + newValue); + }); - ProgressBar btcProgressIndicator = new ProgressBar(-1); - btcProgressIndicator.setPrefWidth(120); - btcProgressIndicator.progressProperty().bind(presentationModel.networkSyncProgress); + ProgressBar blockchainSyncIndicator = new ProgressBar(-1); + blockchainSyncIndicator.setPrefWidth(120); + blockchainSyncIndicator.progressProperty().bind(presentationModel.blockchainSyncProgress); + blockchainSyncIndicator.visibleProperty().bind(presentationModel.blockchainSyncIndicatorVisible); + blockchainSyncIndicator.managedProperty().bind(presentationModel.blockchainSyncIndicatorVisible); - HBox btcBox = new HBox(); - btcBox.setSpacing(10); - btcBox.setAlignment(Pos.CENTER); - btcBox.setPadding(new Insets(60, 0, 0, 0)); - btcBox.getChildren().addAll(bitcoinSyncStateLabel, btcProgressIndicator); + HBox blockchainSyncBox = new HBox(); + blockchainSyncBox.setSpacing(10); + blockchainSyncBox.setAlignment(Pos.CENTER); + blockchainSyncBox.setPadding(new Insets(60, 0, 0, 0)); + blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator); Label bootstrapStateLabel = new Label(); bootstrapStateLabel.setWrapText(true); bootstrapStateLabel.setMaxWidth(500); bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER); bootstrapStateLabel.textProperty().bind(presentationModel.bootstrapState); + presentationModel.bootstrapFailed.addListener((ov, oldValue, newValue) -> { + if (newValue) { + bootstrapStateLabel.setId("splash-error-state-msg"); + Popups.openErrorPopup("Error", "Cannot connect to P2P network. \n\nError message:\n" + + presentationModel.bootstrapErrorMsg.get()); + } + }); - ProgressIndicator p2pProgressIndicator = new ProgressIndicator(-1); - p2pProgressIndicator.setMaxSize(24, 24); + ProgressIndicator bootstrapIndicator = new ProgressIndicator(); + bootstrapIndicator.setMaxSize(24, 24); + bootstrapIndicator.progressProperty().bind(presentationModel.bootstrapProgress); + bootstrapIndicator.visibleProperty().bind(presentationModel.bootstrapIndicatorVisible); + bootstrapIndicator.managedProperty().bind(presentationModel.bootstrapIndicatorVisible); - HBox p2pBox = new HBox(); - p2pBox.setSpacing(10); - p2pBox.setAlignment(Pos.CENTER); - p2pBox.setPadding(new Insets(10, 0, 0, 0)); - p2pBox.getChildren().addAll(bootstrapStateLabel, p2pProgressIndicator); - - vBox.getChildren().addAll(logo, btcBox, p2pBox); + HBox bootstrapBox = new HBox(); + bootstrapBox.setSpacing(10); + bootstrapBox.setAlignment(Pos.CENTER); + bootstrapBox.setPadding(new Insets(10, 0, 0, 0)); + bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator); + vBox.getChildren().addAll(logo, blockchainSyncBox, bootstrapBox); return vBox; }