diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index a88da2850d..27cfd051b6 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -25,11 +25,17 @@ import io.bitsquare.crypto.CryptoService; import org.bitcoinj.core.AbstractWalletEventListener; import org.bitcoinj.core.Address; import org.bitcoinj.core.AddressFormatException; +import org.bitcoinj.core.Block; import org.bitcoinj.core.Coin; import org.bitcoinj.core.DownloadProgressTracker; +import org.bitcoinj.core.FilteredBlock; +import org.bitcoinj.core.GetDataMessage; import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.core.Message; import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Peer; import org.bitcoinj.core.PeerAddress; +import org.bitcoinj.core.PeerEventListener; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.core.TransactionInput; @@ -63,12 +69,17 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import javax.annotation.Nullable; + import javax.inject.Inject; import javax.inject.Named; import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; import org.jetbrains.annotations.NotNull; @@ -107,6 +118,7 @@ public class WalletService { private Wallet wallet; private AddressEntry registrationAddressEntry; private AddressEntry arbitratorDepositAddressEntry; + private final IntegerProperty numPeers = new SimpleIntegerProperty(0); /////////////////////////////////////////////////////////////////////////////////////////// @@ -184,12 +196,9 @@ public class WalletService { e.printStackTrace(); log.error(e.toString()); } - // As an example! - // walletAppKit.useTor(); } else if (params == TestNet3Params.get()) { walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet")); - //walletAppKit.useTor(); } walletAppKit.setDownloadListener(downloadListener) @@ -210,7 +219,6 @@ public class WalletService { } }, Threading.USER_THREAD); walletAppKit.startAsync(); - return status.timeout(30, TimeUnit.SECONDS); } @@ -220,6 +228,47 @@ public class WalletService { addressEntryList.onWalletReady(wallet); registrationAddressEntry = addressEntryList.getRegistrationAddressEntry(); + + walletAppKit.peerGroup().addEventListener(new PeerEventListener() { + @Override + public void onPeersDiscovered(Set peerAddresses) { + } + + @Override + public void onBlocksDownloaded(Peer peer, Block block, FilteredBlock filteredBlock, int blocksLeft) { + } + + @Override + public void onChainDownloadStarted(Peer peer, int blocksLeft) { + } + + @Override + public void onPeerConnected(Peer peer, int peerCount) { + log.trace("onPeerConnected " + peerCount); + Threading.USER_THREAD.execute(() -> numPeers.set(peerCount)); + } + + @Override + public void onPeerDisconnected(Peer peer, int peerCount) { + log.trace("onPeerDisconnected " + peerCount); + Threading.USER_THREAD.execute(() -> numPeers.set(peerCount)); + } + + @Override + public Message onPreMessageReceived(Peer peer, Message m) { + return null; + } + + @Override + public void onTransaction(Peer peer, Transaction t) { + } + + @Nullable + @Override + public List getData(Peer peer, GetDataMessage m) { + return null; + } + }); } public void shutDown() { @@ -534,6 +583,14 @@ public class WalletService { } } + public int getNumPeers() { + return numPeers.get(); + } + + public ReadOnlyIntegerProperty numPeersProperty() { + return numPeers; + } + /////////////////////////////////////////////////////////////////////////////////////////// // Inner classes /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/io/bitsquare/p2p/BaseP2PService.java b/core/src/main/java/io/bitsquare/p2p/BaseP2PService.java index 999794e77b..3402a94ba9 100644 --- a/core/src/main/java/io/bitsquare/p2p/BaseP2PService.java +++ b/core/src/main/java/io/bitsquare/p2p/BaseP2PService.java @@ -29,6 +29,10 @@ public class BaseP2PService implements P2PService { BaseP2PService.userThread = userThread; } + public static Executor getUserThread() { + return userThread; + } + protected Executor executor; protected PeerDHT peerDHT; diff --git a/core/src/main/java/io/bitsquare/p2p/ClientNode.java b/core/src/main/java/io/bitsquare/p2p/ClientNode.java index 4f6e4a5b4f..2b13a3b829 100644 --- a/core/src/main/java/io/bitsquare/p2p/ClientNode.java +++ b/core/src/main/java/io/bitsquare/p2p/ClientNode.java @@ -21,6 +21,8 @@ import io.bitsquare.p2p.tomp2p.BootstrappedPeerBuilder; import java.security.KeyPair; +import javafx.beans.property.ReadOnlyIntegerProperty; + import rx.Observable; public interface ClientNode { @@ -31,4 +33,6 @@ public interface ClientNode { Node getBootstrapNodeAddress(); Observable bootstrap(KeyPair keyPair); + + ReadOnlyIntegerProperty numPeersProperty(); } diff --git a/core/src/main/java/io/bitsquare/p2p/tomp2p/TomP2PNode.java b/core/src/main/java/io/bitsquare/p2p/tomp2p/TomP2PNode.java index a8dc512a67..f06122c5af 100644 --- a/core/src/main/java/io/bitsquare/p2p/tomp2p/TomP2PNode.java +++ b/core/src/main/java/io/bitsquare/p2p/tomp2p/TomP2PNode.java @@ -19,6 +19,7 @@ package io.bitsquare.p2p.tomp2p; import io.bitsquare.BitsquareException; import io.bitsquare.common.handlers.ResultHandler; +import io.bitsquare.p2p.BaseP2PService; import io.bitsquare.p2p.ClientNode; import io.bitsquare.p2p.Node; @@ -35,8 +36,16 @@ import javax.annotation.Nullable; import javax.inject.Inject; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ReadOnlyIntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; + +import net.tomp2p.connection.PeerConnection; +import net.tomp2p.connection.PeerException; import net.tomp2p.dht.PeerDHT; import net.tomp2p.peers.PeerAddress; +import net.tomp2p.peers.PeerStatusListener; +import net.tomp2p.peers.RTT; import org.jetbrains.annotations.NotNull; @@ -54,7 +63,8 @@ public class TomP2PNode implements ClientNode { private BootstrappedPeerBuilder bootstrappedPeerBuilder; private final Subject bootstrapStateSubject; private final List resultHandlers = new CopyOnWriteArrayList<>(); - + private final IntegerProperty numPeers = new SimpleIntegerProperty(0); + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -92,6 +102,20 @@ public class TomP2PNode implements ClientNode { public void onSuccess(@Nullable PeerDHT peerDHT) { if (peerDHT != null) { TomP2PNode.this.peerDHT = peerDHT; + + peerDHT.peerBean().addPeerStatusListener(new PeerStatusListener() { + @Override + public boolean peerFailed(PeerAddress peerAddress, PeerException e) { + return false; + } + + @Override + public boolean peerFound(PeerAddress peerAddress, PeerAddress peerAddress1, PeerConnection peerConnection, RTT rtt) { + BaseP2PService.getUserThread().execute(() -> numPeers.set(peerDHT.peerBean().peerMap().size())); + return false; + } + }); + resultHandlers.stream().forEach(ResultHandler::handleResult); bootstrapStateSubject.onCompleted(); } @@ -141,4 +165,12 @@ public class TomP2PNode implements ClientNode { public void removeResultHandler(ResultHandler resultHandler) { resultHandlers.remove(resultHandler); } + + public int getNumPeers() { + return numPeers.get(); + } + + public ReadOnlyIntegerProperty numPeersProperty() { + return numPeers; + } } diff --git a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java index 8f4c66a64b..28df08249f 100644 --- a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java +++ b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java @@ -22,6 +22,7 @@ import io.bitsquare.gui.common.view.CachingViewLoader; import io.bitsquare.gui.common.view.View; import io.bitsquare.gui.common.view.ViewLoader; import io.bitsquare.gui.common.view.guice.InjectorViewFactory; +import io.bitsquare.gui.components.Popups; import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.debug.DebugView; import io.bitsquare.gui.util.ImageUtil; @@ -46,8 +47,6 @@ import javafx.stage.StageStyle; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Logger; -import com.vinumeris.crashfx.CrashFX; -import com.vinumeris.crashfx.CrashWindow; import org.springframework.core.env.Environment; import static io.bitsquare.app.BitsquareEnvironment.APP_NAME_KEY; @@ -80,7 +79,6 @@ public class BitsquareApp extends Application { Paths.get(env.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY), "crashes"), URI.create("http://188.226.179.109/crashfx/upload"));*/ // Server not setup yet, so we use client side only support - CrashFX.setup(); // Guice bitsquareAppModule = new BitsquareAppModule(env, primaryStage); @@ -89,9 +87,10 @@ public class BitsquareApp extends Application { // load the main view and create the main scene CachingViewLoader viewLoader = injector.getInstance(CachingViewLoader.class); - View view = viewLoader.load(MainView.class); + MainView view = (MainView) viewLoader.load(MainView.class); + view.setExitHandler(this::stop); - scene = new Scene((Parent) view.getRoot(), 1000, 650); + scene = new Scene(view.getRoot(), 1000, 650); scene.getStylesheets().setAll( "/io/bitsquare/gui/bitsquare.css", "/io/bitsquare/gui/images.css"); @@ -137,7 +136,7 @@ public class BitsquareApp extends Application { //TODO just temp. //showDebugWindow(); } catch (Throwable t) { - CrashWindow.open(t); + Popups.openExceptionPopup(t); } } diff --git a/gui/src/main/java/io/bitsquare/app/UpdateProcess.java b/gui/src/main/java/io/bitsquare/app/UpdateProcess.java index 2189e83999..5fe86a509e 100644 --- a/gui/src/main/java/io/bitsquare/app/UpdateProcess.java +++ b/gui/src/main/java/io/bitsquare/app/UpdateProcess.java @@ -105,9 +105,9 @@ public class UpdateProcess { } }; - updater.progressProperty().addListener((observableValue, oldValue, newValue) -> { + /* updater.progressProperty().addListener((observableValue, oldValue, newValue) -> { log.trace("progressProperty newValue = " + newValue); - }); + });*/ log.info("Checking for updates!"); updater.setOnSucceeded(event -> { diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainView.java b/gui/src/main/java/io/bitsquare/gui/main/MainView.java index 4b277447cd..8d490c47af 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainView.java @@ -42,6 +42,7 @@ import javax.inject.Inject; import javax.inject.Named; import javafx.application.Platform; +import javafx.beans.value.ChangeListener; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.*; @@ -66,6 +67,19 @@ public class MainView extends InitializableView { private final OverlayManager overlayManager; private final Transitions transitions; private final String title; + private ChangeListener walletServiceErrorMsgListener; + private ChangeListener blockchainSyncIconIdListener; + private ChangeListener bootstrapErrorMsgListener; + private ChangeListener bootstrapIconIdListener; + private ChangeListener bootstrapProgressListener; + private ChangeListener updateIconIdListener; + private Button restartButton; + private ProgressIndicator bootstrapIndicator; + private Label bootstrapStateLabel; + private ProgressBar blockchainSyncIndicator; + private Label blockchainSyncLabel; + private Label updateInfoLabel; + private Runnable exitHandler; @Inject public MainView(MainViewModel model, CachingViewLoader viewLoader, Navigation navigation, OverlayManager overlayManager, Transitions transitions, @@ -156,7 +170,7 @@ public class MainView extends InitializableView { navigation.navigateToPreviousVisitedView(); - transitions.fadeOutAndRemove(splashScreen, 1500); + transitions.fadeOutAndRemove(splashScreen, 1500, actionEvent -> disposeSplashScreen()); } }); @@ -164,6 +178,253 @@ public class MainView extends InitializableView { Platform.runLater(model::initBackend); } + public void setExitHandler(Runnable exitHandler) { + this.exitHandler = exitHandler; + } + + private VBox createSplashScreen() { + VBox vBox = new VBox(); + vBox.setAlignment(Pos.CENTER); + vBox.setSpacing(0); + vBox.setId("splash"); + + ImageView logo = new ImageView(); + logo.setId("image-splash-logo"); + + + // createBitcoinInfoBox + blockchainSyncLabel = new Label(); + blockchainSyncLabel.textProperty().bind(model.blockchainSyncInfo); + walletServiceErrorMsgListener = (ov, oldValue, newValue) -> { + blockchainSyncLabel.setId("splash-error-state-msg"); + Popups.openErrorPopup("Error", "Connecting to the bitcoin network failed. \n" + newValue + + "\nPlease check our internet connection and restart the application."); + exitHandler.run(); + }; + model.walletServiceErrorMsg.addListener(walletServiceErrorMsgListener); + + blockchainSyncIndicator = new ProgressBar(-1); + blockchainSyncIndicator.setPrefWidth(120); + blockchainSyncIndicator.progressProperty().bind(model.blockchainSyncProgress); + + ImageView blockchainSyncIcon = new ImageView(); + blockchainSyncIcon.setVisible(false); + blockchainSyncIcon.setManaged(false); + + blockchainSyncIconIdListener = (ov, oldValue, newValue) -> { + blockchainSyncIcon.setId(newValue); + blockchainSyncIcon.setVisible(true); + blockchainSyncIcon.setManaged(true); + + blockchainSyncIndicator.setVisible(false); + blockchainSyncIndicator.setManaged(false); + }; + model.blockchainSyncIconId.addListener(blockchainSyncIconIdListener); + + Label bitcoinNetworkLabel = new Label(); + bitcoinNetworkLabel.setText(model.bitcoinNetworkAsString); + bitcoinNetworkLabel.setId("splash-bitcoin-network-label"); + + HBox blockchainSyncBox = new HBox(); + blockchainSyncBox.setSpacing(10); + blockchainSyncBox.setAlignment(Pos.CENTER); + blockchainSyncBox.setPadding(new Insets(40, 0, 0, 0)); + blockchainSyncBox.setPrefHeight(50); + blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator, + blockchainSyncIcon, bitcoinNetworkLabel); + + + // createP2PNetworkBox + bootstrapStateLabel = new Label(); + bootstrapStateLabel.setWrapText(true); + bootstrapStateLabel.setMaxWidth(500); + bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER); + bootstrapStateLabel.textProperty().bind(model.bootstrapInfo); + + bootstrapIndicator = new ProgressIndicator(); + bootstrapIndicator.setMaxSize(24, 24); + bootstrapIndicator.progressProperty().bind(model.bootstrapProgress); + + bootstrapErrorMsgListener = (ov, oldValue, newValue) -> { + bootstrapStateLabel.setId("splash-error-state-msg"); + bootstrapIndicator.setVisible(false); + + Popups.openErrorPopup("Error", "Connecting to the Bitsquare network failed. \n" + model.bootstrapErrorMsg.get() + + "\nPlease check our internet connection and restart the application."); + exitHandler.run(); + }; + model.bootstrapErrorMsg.addListener(bootstrapErrorMsgListener); + + ImageView bootstrapIcon = new ImageView(); + bootstrapIcon.setVisible(false); + bootstrapIcon.setManaged(false); + + bootstrapIconIdListener = (ov, oldValue, newValue) -> { + bootstrapIcon.setId(newValue); + bootstrapIcon.setVisible(true); + bootstrapIcon.setManaged(true); + }; + model.bootstrapIconId.addListener(bootstrapIconIdListener); + + bootstrapProgressListener = (ov, oldValue, newValue) -> { + if ((double) newValue >= 1) { + bootstrapIndicator.setVisible(false); + bootstrapIndicator.setManaged(false); + } + }; + model.bootstrapProgress.addListener(bootstrapProgressListener); + + HBox bootstrapBox = new HBox(); + bootstrapBox.setSpacing(10); + bootstrapBox.setAlignment(Pos.CENTER); + bootstrapBox.setPrefHeight(50); + bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator, bootstrapIcon); + + + // createUpdateBox + updateInfoLabel = new Label(); + updateInfoLabel.setTextAlignment(TextAlignment.RIGHT); + updateInfoLabel.textProperty().bind(model.updateInfo); + + restartButton = new Button("Restart"); + restartButton.setDefaultButton(true); + restartButton.visibleProperty().bind(model.showRestartButton); + restartButton.managedProperty().bind(model.showRestartButton); + restartButton.setOnAction(e -> model.restart()); + + ImageView updateIcon = new ImageView(); + updateIcon.setId(model.updateIconId.get()); + + updateIconIdListener = (ov, oldValue, newValue) -> { + updateIcon.setId(newValue); + updateIcon.setVisible(true); + updateIcon.setManaged(true); + }; + model.updateIconId.addListener(updateIconIdListener); + + HBox updateBox = new HBox(); + updateBox.setSpacing(10); + updateBox.setAlignment(Pos.CENTER); + updateBox.setPrefHeight(20); + updateBox.getChildren().addAll(updateInfoLabel, restartButton, updateIcon); + + vBox.getChildren().addAll(logo, blockchainSyncBox, bootstrapBox, updateBox); + return vBox; + } + + private void disposeSplashScreen() { + model.walletServiceErrorMsg.removeListener(walletServiceErrorMsgListener); + model.blockchainSyncIconId.removeListener(blockchainSyncIconIdListener); + model.bootstrapErrorMsg.removeListener(bootstrapErrorMsgListener); + model.bootstrapIconId.removeListener(bootstrapIconIdListener); + model.bootstrapProgress.removeListener(bootstrapProgressListener); + model.updateIconId.removeListener(updateIconIdListener); + + blockchainSyncLabel.textProperty().unbind(); + blockchainSyncIndicator.progressProperty().unbind(); + bootstrapStateLabel.textProperty().unbind(); + bootstrapIndicator.progressProperty().unbind(); + updateInfoLabel.textProperty().unbind(); + restartButton.visibleProperty().unbind(); + restartButton.managedProperty().unbind(); + } + + + private AnchorPane createFooter() { + // line + Separator separator = new Separator(); + separator.setId("footer-pane-line"); + separator.setPrefHeight(1); + setLeftAnchor(separator, 0d); + setRightAnchor(separator, 0d); + setTopAnchor(separator, 0d); + + // BTC + Label blockchainSyncLabel = new Label(); + blockchainSyncLabel.setId("footer-pane"); + blockchainSyncLabel.textProperty().bind(model.blockchainSyncInfoFooter); + + ProgressBar blockchainSyncIndicator = new ProgressBar(-1); + blockchainSyncIndicator.setPrefWidth(120); + blockchainSyncIndicator.setMaxHeight(10); + blockchainSyncIndicator.progressProperty().bind(model.blockchainSyncProgress); + + Label bitcoinNetworkLabel = new Label(); + bitcoinNetworkLabel.setId("footer-bitcoin-network-label"); + bitcoinNetworkLabel.setText(model.bitcoinNetworkAsString); + + model.walletServiceErrorMsg.addListener((ov, oldValue, newValue) -> { + bitcoinNetworkLabel.setId("splash-error-state-msg"); + bitcoinNetworkLabel.textProperty().unbind(); + bitcoinNetworkLabel.setText("Not connected"); + Popups.openErrorPopup("Error", "Connecting to the bitcoin network failed. \n" + newValue + + "\nPlease check our internet connection and restart the application."); + exitHandler.run(); + }); + + model.blockchainSyncProgress.addListener((ov, oldValue, newValue) -> { + if ((double) newValue >= 1) { + blockchainSyncIndicator.setVisible(false); + blockchainSyncIndicator.setManaged(false); + blockchainSyncLabel.setVisible(false); + blockchainSyncLabel.setManaged(false); + } + }); + + HBox blockchainSyncBox = new HBox(); + blockchainSyncBox.setSpacing(10); + blockchainSyncBox.setAlignment(Pos.CENTER); + blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator, bitcoinNetworkLabel); + setLeftAnchor(blockchainSyncBox, 10d); + setBottomAnchor(blockchainSyncBox, 7d); + + // version + Label versionLabel = new Label(); + versionLabel.setId("footer-pane"); + versionLabel.setTextAlignment(TextAlignment.CENTER); + versionLabel.setAlignment(Pos.BASELINE_CENTER); + versionLabel.setText(model.version); + root.widthProperty().addListener((ov, oldValue, newValue) -> { + versionLabel.setLayoutX(((double) newValue - versionLabel.getWidth()) / 2); + }); + setBottomAnchor(versionLabel, 7d); + + + // P2P + Label bootstrapLabel = new Label(); + bootstrapLabel.setId("footer-pane"); + setRightAnchor(bootstrapLabel, 100d); + setBottomAnchor(bootstrapLabel, 7d); + bootstrapLabel.textProperty().bind(model.bootstrapInfoFooter); + + ImageView bootstrapIcon = new ImageView(); + setRightAnchor(bootstrapIcon, 60d); + setBottomAnchor(bootstrapIcon, 9d); + bootstrapIcon.idProperty().bind(model.bootstrapIconId); + + Label numPeersLabel = new Label(); + numPeersLabel.setId("footer-num-peers"); + setRightAnchor(numPeersLabel, 10d); + setBottomAnchor(numPeersLabel, 7d); + numPeersLabel.textProperty().bind(model.numDHTPeers); + model.bootstrapErrorMsg.addListener((ov, oldValue, newValue) -> { + bootstrapLabel.setId("splash-error-state-msg"); + bootstrapLabel.textProperty().unbind(); + bootstrapLabel.setText("Not connected"); + Popups.openErrorPopup("Error", "Connecting to the P2P network failed. \n" + newValue + + "\nPlease check our internet connection and restart the application."); + exitHandler.run(); + }); + + AnchorPane footerContainer = new AnchorPane(separator, blockchainSyncBox, versionLabel, bootstrapLabel, bootstrapIcon, numPeersLabel) {{ + setId("footer-pane"); + setMinHeight(30); + setMaxHeight(30); + }}; + + return footerContainer; + } + private void setupNotificationIcon(Pane portfolioButtonHolder) { Label numPendingTradesLabel = new Label(); numPendingTradesLabel.textProperty().bind(model.numPendingTradesAsString); @@ -188,308 +449,6 @@ public class MainView extends InitializableView { }); } - private AnchorPane createFooter() { - // BTC - Label blockchainSyncLabel = new Label(); - blockchainSyncLabel.setId("footer-pane"); - blockchainSyncLabel.textProperty().bind(model.blockchainSyncInfoFooter); - model.walletServiceErrorMsg.addListener((ov, oldValue, newValue) -> { - blockchainSyncLabel.setId("splash-error-state-msg"); - Popups.openErrorPopup("Error", "Connecting to the bitcoin network failed. \n\nReason: " + - newValue); - }); - - ProgressBar blockchainSyncIndicator = new ProgressBar(-1); - blockchainSyncIndicator.setPrefWidth(120); - blockchainSyncIndicator.setMaxHeight(10); - blockchainSyncIndicator.progressProperty().bind(model.blockchainSyncProgress); - - Label bitcoinNetworkLabel = new Label(); - bitcoinNetworkLabel.setId("footer-bitcoin-network-label"); - bitcoinNetworkLabel.setText(model.bitcoinNetworkAsString); - - model.blockchainSyncProgress.addListener((ov, oldValue, newValue) -> { - if ((double) newValue >= 1) { - blockchainSyncIndicator.setVisible(false); - blockchainSyncIndicator.setManaged(false); - blockchainSyncLabel.setVisible(false); - blockchainSyncLabel.setManaged(false); - } - }); - - HBox blockchainSyncBox = new HBox(); - blockchainSyncBox.setSpacing(10); - blockchainSyncBox.setAlignment(Pos.CENTER); - blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator, bitcoinNetworkLabel); - setLeftAnchor(blockchainSyncBox, 20d); - setBottomAnchor(blockchainSyncBox, 7d); - - // version - Label versionLabel = new Label(); - versionLabel.setId("footer-pane"); - versionLabel.setTextAlignment(TextAlignment.CENTER); - versionLabel.setAlignment(Pos.BASELINE_CENTER); - versionLabel.setText(model.version); - root.widthProperty().addListener((ov, oldValue, newValue) -> { - versionLabel.setLayoutX(((double) newValue - versionLabel.getWidth()) / 2); - }); - setBottomAnchor(versionLabel, 7d); - - - // P2P - Label bootstrapLabel = new Label(); - bootstrapLabel.setId("footer-pane"); - setRightAnchor(bootstrapLabel, 60d); - setBottomAnchor(bootstrapLabel, 7d); - bootstrapLabel.textProperty().bind(model.bootstrapInfoFooter); - - ImageView bootstrapIcon = new ImageView(); - setRightAnchor(bootstrapIcon, 20d); - setBottomAnchor(bootstrapIcon, 9d); - bootstrapIcon.idProperty().bind(model.bootstrapIconId); - - // line - Separator separator = new Separator(); - separator.setId("footer-pane-line"); - separator.setPrefHeight(1); - setLeftAnchor(separator, 0d); - setRightAnchor(separator, 0d); - setTopAnchor(separator, 0d); - - AnchorPane footerContainer = new AnchorPane(separator, blockchainSyncBox, versionLabel, bootstrapLabel, bootstrapIcon) {{ - setId("footer-pane"); - setMinHeight(30); - setMaxHeight(30); - }}; - - return footerContainer; - } - - private HBox createBitcoinInfoBox() { - Label blockchainSyncLabel = new Label(); - blockchainSyncLabel.textProperty().bind(model.blockchainSyncInfo); - model.walletServiceErrorMsg.addListener((ov, oldValue, newValue) -> { - blockchainSyncLabel.setId("splash-error-state-msg"); - Popups.openErrorPopup("Error", "Connecting to the bitcoin network failed. \n\nReason: " + - newValue); - }); - - ProgressBar blockchainSyncIndicator = new ProgressBar(-1); - blockchainSyncIndicator.setPrefWidth(120); - blockchainSyncIndicator.progressProperty().bind(model.blockchainSyncProgress); - - ImageView blockchainSyncIcon = new ImageView(); - blockchainSyncIcon.setVisible(false); - blockchainSyncIcon.setManaged(false); - - model.blockchainSyncIconId.addListener((ov, oldValue, newValue) -> { - blockchainSyncIcon.setId(newValue); - blockchainSyncIcon.setVisible(true); - blockchainSyncIcon.setManaged(true); - - blockchainSyncIndicator.setVisible(false); - blockchainSyncIndicator.setManaged(false); - }); - - Label bitcoinNetworkLabel = new Label(); - bitcoinNetworkLabel.setText(model.bitcoinNetworkAsString); - bitcoinNetworkLabel.setId("splash-bitcoin-network-label"); - - HBox blockchainSyncBox = new HBox(); - blockchainSyncBox.setSpacing(10); - blockchainSyncBox.setAlignment(Pos.CENTER); - blockchainSyncBox.setPadding(new Insets(40, 0, 0, 0)); - blockchainSyncBox.setPrefHeight(50); - blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator, - blockchainSyncIcon, bitcoinNetworkLabel); - return blockchainSyncBox; - } - - private HBox createP2PNetworkBox() { - Label bootstrapStateLabel = new Label(); - bootstrapStateLabel.setWrapText(true); - bootstrapStateLabel.setMaxWidth(500); - bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER); - bootstrapStateLabel.textProperty().bind(model.bootstrapInfo); - - ProgressIndicator bootstrapIndicator = new ProgressIndicator(); - bootstrapIndicator.setMaxSize(24, 24); - bootstrapIndicator.progressProperty().bind(model.bootstrapProgress); - - model.bootstrapErrorMsg.addListener((ov, oldValue, newValue) -> { - bootstrapStateLabel.setId("splash-error-state-msg"); - bootstrapIndicator.setVisible(false); - - Popups.openErrorPopup("Error", "Connecting to the Bitsquare network failed. \n\nReason: " + - model.bootstrapErrorMsg.get()); - }); - - ImageView bootstrapIcon = new ImageView(); - bootstrapIcon.setVisible(false); - bootstrapIcon.setManaged(false); - - model.bootstrapIconId.addListener((ov, oldValue, newValue) -> { - bootstrapIcon.setId(newValue); - bootstrapIcon.setVisible(true); - bootstrapIcon.setManaged(true); - }); - model.bootstrapProgress.addListener((ov, oldValue, newValue) -> { - if ((double) newValue >= 1) { - bootstrapIndicator.setVisible(false); - bootstrapIndicator.setManaged(false); - } - }); - - HBox bootstrapBox = new HBox(); - bootstrapBox.setSpacing(10); - bootstrapBox.setAlignment(Pos.CENTER); - bootstrapBox.setPrefHeight(50); - bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator, bootstrapIcon); - return bootstrapBox; - } - - private HBox createUpdateBox() { - Label updateInfoLabel = new Label(); - updateInfoLabel.setTextAlignment(TextAlignment.RIGHT); - updateInfoLabel.textProperty().bind(model.updateInfo); - - Button restartButton = new Button("Restart"); - restartButton.setDefaultButton(true); - restartButton.visibleProperty().bind(model.showRestartButton); - restartButton.managedProperty().bind(model.showRestartButton); - restartButton.setOnAction(e -> model.restart()); - - ImageView updateIcon = new ImageView(); - updateIcon.setId(model.updateIconId.get()); - model.updateIconId.addListener((ov, oldValue, newValue) -> { - updateIcon.setId(newValue); - updateIcon.setVisible(true); - updateIcon.setManaged(true); - }); - - HBox updateBox = new HBox(); - updateBox.setSpacing(10); - updateBox.setAlignment(Pos.CENTER); - updateBox.setPrefHeight(20); - updateBox.getChildren().addAll(updateInfoLabel, restartButton, updateIcon); - return updateBox; - } - - private VBox createSplashScreen() { - VBox vBox = new VBox(); - vBox.setAlignment(Pos.CENTER); - vBox.setSpacing(0); - vBox.setId("splash"); - - ImageView logo = new ImageView(); - logo.setId("image-splash-logo"); - - /*Label blockchainSyncLabel = new Label(); - blockchainSyncLabel.textProperty().bind(model.blockchainSyncInfo); - model.walletServiceErrorMsg.addListener((ov, oldValue, newValue) -> { - blockchainSyncLabel.setId("splash-error-state-msg"); - Popups.openErrorPopup("Error", "Connecting to the bitcoin network failed. \n\nReason: " + - newValue); - }); - - ProgressBar blockchainSyncIndicator = new ProgressBar(-1); - blockchainSyncIndicator.setPrefWidth(120); - blockchainSyncIndicator.progressProperty().bind(model.blockchainSyncProgress); - - ImageView blockchainSyncIcon = new ImageView(); - blockchainSyncIcon.setVisible(false); - blockchainSyncIcon.setManaged(false); - - model.blockchainSyncIconId.addListener((ov, oldValue, newValue) -> { - blockchainSyncIcon.setId(newValue); - blockchainSyncIcon.setVisible(true); - blockchainSyncIcon.setManaged(true); - - blockchainSyncIndicator.setVisible(false); - blockchainSyncIndicator.setManaged(false); - }); - - Label bitcoinNetworkLabel = new Label(); - bitcoinNetworkLabel.setText(model.bitcoinNetworkAsString); - bitcoinNetworkLabel.setId("splash-bitcoin-network-label"); - - HBox blockchainSyncBox = new HBox(); - blockchainSyncBox.setSpacing(10); - blockchainSyncBox.setAlignment(Pos.CENTER); - blockchainSyncBox.setPadding(new Insets(40, 0, 0, 0)); - blockchainSyncBox.setPrefHeight(50); - blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator, - blockchainSyncIcon, bitcoinNetworkLabel);*/ - - /* Label bootstrapStateLabel = new Label(); - bootstrapStateLabel.setWrapText(true); - bootstrapStateLabel.setMaxWidth(500); - bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER); - bootstrapStateLabel.textProperty().bind(model.bootstrapInfo); - - ProgressIndicator bootstrapIndicator = new ProgressIndicator(); - bootstrapIndicator.setMaxSize(24, 24); - bootstrapIndicator.progressProperty().bind(model.bootstrapProgress); - - model.bootstrapErrorMsg.addListener((ov, oldValue, newValue) -> { - bootstrapStateLabel.setId("splash-error-state-msg"); - bootstrapIndicator.setVisible(false); - - Popups.openErrorPopup("Error", "Connecting to the Bitsquare network failed. \n\nReason: " + - model.bootstrapErrorMsg.get()); - }); - - ImageView bootstrapIcon = new ImageView(); - bootstrapIcon.setVisible(false); - bootstrapIcon.setManaged(false); - - model.bootstrapIconId.addListener((ov, oldValue, newValue) -> { - bootstrapIcon.setId(newValue); - bootstrapIcon.setVisible(true); - bootstrapIcon.setManaged(true); - }); - model.bootstrapProgress.addListener((ov, oldValue, newValue) -> { - if ((double) newValue >= 1) { - bootstrapIndicator.setVisible(false); - bootstrapIndicator.setManaged(false); - } - }); - - HBox bootstrapBox = new HBox(); - bootstrapBox.setSpacing(10); - bootstrapBox.setAlignment(Pos.CENTER); - bootstrapBox.setPrefHeight(50); - bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator, bootstrapIcon);*/ - - // software update - /* Label updateInfoLabel = new Label(); - updateInfoLabel.setTextAlignment(TextAlignment.RIGHT); - updateInfoLabel.textProperty().bind(model.updateInfo); - - Button restartButton = new Button("Restart"); - restartButton.setDefaultButton(true); - restartButton.visibleProperty().bind(model.showRestartButton); - restartButton.managedProperty().bind(model.showRestartButton); - restartButton.setOnAction(e -> model.restart()); - - ImageView updateIcon = new ImageView(); - updateIcon.setId(model.updateIconId.get()); - model.updateIconId.addListener((ov, oldValue, newValue) -> { - updateIcon.setId(newValue); - updateIcon.setVisible(true); - updateIcon.setManaged(true); - }); - - HBox updateBox = new HBox(); - updateBox.setSpacing(10); - updateBox.setAlignment(Pos.CENTER); - updateBox.setPrefHeight(20); - updateBox.getChildren().addAll(updateInfoLabel, restartButton, updateIcon);*/ - - vBox.getChildren().addAll(logo, createBitcoinInfoBox(), createP2PNetworkBox(), createUpdateBox()); - return vBox; - } - private VBox createBankAccountComboBox() { final ComboBox comboBox = new ComboBox<>(model.getBankAccounts()); comboBox.setLayoutY(12); @@ -523,7 +482,6 @@ public class MainView extends InitializableView { return vBox; } - private void configureBlurring(Node node) { Popups.setOverlayManager(overlayManager); @@ -540,7 +498,6 @@ public class MainView extends InitializableView { }); } - private class NavButton extends ToggleButton { private final Class viewClass; diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index aec2bd639c..b20782363a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -35,8 +35,12 @@ import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.user.User; +import org.bitcoinj.utils.Threading; + import com.google.inject.Inject; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.TimeoutException; import javafx.application.Platform; @@ -60,12 +64,15 @@ import rx.Observable; class MainViewModel implements ViewModel { private static final Logger log = LoggerFactory.getLogger(MainViewModel.class); + private static final long BLOCKCHAIN_SYNC_TIMEOUT = 30000; + // BTC network final StringProperty blockchainSyncInfo = new SimpleStringProperty("Initializing"); final StringProperty blockchainSyncInfoFooter = new SimpleStringProperty("Initializing"); final DoubleProperty blockchainSyncProgress = new SimpleDoubleProperty(-1); final StringProperty walletServiceErrorMsg = new SimpleStringProperty(); final StringProperty blockchainSyncIconId = new SimpleStringProperty(); + final StringProperty numBTCPeers = new SimpleStringProperty(); // P2P network final StringProperty bootstrapInfo = new SimpleStringProperty("Connecting to P2P network..."); @@ -73,6 +80,7 @@ class MainViewModel implements ViewModel { final DoubleProperty bootstrapProgress = new SimpleDoubleProperty(-1); final StringProperty bootstrapErrorMsg = new SimpleStringProperty(); final StringProperty bootstrapIconId = new SimpleStringProperty(); + final StringProperty numDHTPeers = new SimpleStringProperty(); // software update final StringProperty updateInfo = new SimpleStringProperty(); @@ -100,6 +108,8 @@ class MainViewModel implements ViewModel { private OpenOfferManager openOfferManager; private final UpdateProcess updateProcess; private final BSFormatter formatter; + private Timer blockchainSyncTimeoutTimer; + @Inject public MainViewModel(User user, KeyRing keyRing, WalletService walletService, ArbitrationRepository arbitrationRepository, ClientNode clientNode, @@ -136,20 +146,35 @@ class MainViewModel implements ViewModel { public void initBackend() { Platform.runLater(updateProcess::init); + startBlockchainSyncTimeout(); + walletService.downloadPercentageProperty().addListener((ov, oldValue, newValue) -> { setBitcoinNetworkSyncProgress((double) newValue); }); setBitcoinNetworkSyncProgress(walletService.downloadPercentageProperty().get()); + walletService.numPeersProperty().addListener((observable, oldValue, newValue) -> { + numBTCPeers.set(String.valueOf(newValue) + " peers"); + if ((int) newValue < 1) { + walletServiceErrorMsg.set("We lost connection to the last peer."); + } + }); + // Set executor for all P2PServices BaseP2PService.setUserThread(Platform::runLater); + clientNode.numPeersProperty().addListener((observable, oldValue, newValue) -> { + numDHTPeers.set(String.valueOf(newValue) + " peers"); + if ((int) newValue < 1) { + bootstrapErrorMsg.set("We lost connection to the last peer."); + } + }); + Observable bootstrapStateAsObservable = clientNode.bootstrap(keyRing.getDhtSignatureKeyPair()); bootstrapStateAsObservable.publish(); bootstrapStateAsObservable.subscribe( state -> Platform.runLater(() -> setBootstrapState(state)), error -> Platform.runLater(() -> { - log.error(error.toString()); bootstrapErrorMsg.set(error.getMessage()); bootstrapInfo.set("Connecting to the P2P network failed."); bootstrapProgress.set(0); @@ -163,7 +188,6 @@ class MainViewModel implements ViewModel { //log.trace("wallet next"); }, error -> Platform.runLater(() -> { - log.trace("wallet error"); setWalletServiceException(error); }), () -> { @@ -175,7 +199,6 @@ class MainViewModel implements ViewModel { //log.trace("updateProcess next"); }, error -> { - log.trace("updateProcess error"); }, () -> { log.trace("updateProcess completed"); @@ -185,7 +208,8 @@ class MainViewModel implements ViewModel { allServices.subscribe( next -> { }, - error -> log.error(error.toString()), + error -> { + }, () -> Platform.runLater(this::onAllServicesInitialized) ); } @@ -234,7 +258,6 @@ class MainViewModel implements ViewModel { updateIconId.set("image-update-up-to-date"); break; case FAILURE: - log.error(updateProcess.getErrorMessage()); updateInfo.set("Check for updates failed. "); updateIconId.set("image-update-failed"); break; @@ -330,6 +353,7 @@ class MainViewModel implements ViewModel { private void setBitcoinNetworkSyncProgress(double value) { blockchainSyncProgress.set(value); if (value >= 1) { + stopBlockchainSyncTimeout(); blockchainSyncInfo.set("Blockchain synchronization complete."); blockchainSyncIconId.set("image-connection-synced"); } @@ -343,4 +367,30 @@ class MainViewModel implements ViewModel { } } + private void startBlockchainSyncTimeout() { + log.trace("startBlockchainSyncTimeout"); + stopBlockchainSyncTimeout(); + + blockchainSyncTimeoutTimer = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + Threading.USER_THREAD.execute(() -> { + log.trace("Timeout reached"); + Platform.runLater(() -> setWalletServiceException(new TimeoutException())); + }); + } + }; + + blockchainSyncTimeoutTimer.schedule(task, BLOCKCHAIN_SYNC_TIMEOUT); + } + + private void stopBlockchainSyncTimeout() { + log.trace("stopBlockchainSyncTimeout"); + if (blockchainSyncTimeoutTimer != null) { + blockchainSyncTimeoutTimer.cancel(); + blockchainSyncTimeoutTimer = null; + } + } + } diff --git a/gui/src/main/java/io/bitsquare/gui/util/Transitions.java b/gui/src/main/java/io/bitsquare/gui/util/Transitions.java index e8067a6bc8..e876410ead 100644 --- a/gui/src/main/java/io/bitsquare/gui/util/Transitions.java +++ b/gui/src/main/java/io/bitsquare/gui/util/Transitions.java @@ -27,6 +27,8 @@ import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; import javafx.scene.*; import javafx.scene.effect.*; import javafx.scene.layout.*; @@ -82,6 +84,10 @@ public class Transitions { } public void fadeOutAndRemove(Node node, int duration) { + fadeOutAndRemove(node, duration, null); + } + + public void fadeOutAndRemove(Node node, int duration, EventHandler handler) { if (!preferences.getUseEffects()) duration = 1; @@ -90,6 +96,8 @@ public class Transitions { fade.setOnFinished(actionEvent -> { ((Pane) (node.getParent())).getChildren().remove(node); Profiler.printMsgWithTime("fadeOutAndRemove"); + if (handler != null) + handler.handle(actionEvent); }); }