Improve Splash screen info, Refactor WalletFacade (user executor, gui defines Platform.runlater as executor)

This commit is contained in:
Manfred Karrer 2014-11-07 16:03:14 +01:00
parent 7dd61b35f6
commit 9e69822a40
5 changed files with 151 additions and 132 deletions

View file

@ -26,6 +26,7 @@ import io.bitsquare.persistence.Persistence;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.DownloadListener;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
@ -61,11 +62,13 @@ import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -74,7 +77,6 @@ import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.application.Platform;
import javafx.util.Pair; import javafx.util.Pair;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -101,7 +103,6 @@ public class WalletFacade {
private final CryptoFacade cryptoFacade; private final CryptoFacade cryptoFacade;
private final Persistence persistence; private final Persistence persistence;
private final String appName; private final String appName;
// private final List<DownloadListener> downloadListeners = new CopyOnWriteArrayList<>();
private final List<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArrayList<>(); private final List<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArrayList<>();
private final List<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArrayList<>(); private final List<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArrayList<>();
private final List<BalanceListener> balanceListeners = new CopyOnWriteArrayList<>(); private final List<BalanceListener> balanceListeners = new CopyOnWriteArrayList<>();
@ -132,12 +133,13 @@ public class WalletFacade {
// Public Methods // 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 // 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 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 // we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in
// a future version. // a future version.
Threading.USER_THREAD = Platform::runLater; Threading.USER_THREAD = executor;
// If seed is non-null it means we are restoring from backup. // If seed is non-null it means we are restoring from backup.
walletAppKit = new WalletAppKit(params, AppDirectory.dir(appName).toFile(), appName) { walletAppKit = new WalletAppKit(params, AppDirectory.dir(appName).toFile(), appName) {
@ -150,7 +152,7 @@ public class WalletFacade {
walletAppKit.peerGroup().setMaxConnections(11); walletAppKit.peerGroup().setMaxConnections(11);
walletAppKit.peerGroup().setBloomFilterFalsePositiveRate(0.00001); walletAppKit.peerGroup().setBloomFilterFalsePositiveRate(0.00001);
initWallet(); 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 // 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.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet"));
//walletAppKit.useTor(); //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) walletAppKit.setDownloadListener(downloadListener)
.setBlockingStartup(false) .setBlockingStartup(false)
.setUserAgent("Bitsquare", "0.1"); .setUserAgent(appName, "0.1");
/* /*
// TODO restore from DeterministicSeed // TODO restore from DeterministicSeed
@ -190,24 +208,15 @@ public class WalletFacade {
@Override @Override
public void failed(@NotNull Service.State from, @NotNull Throwable failure) { public void failed(@NotNull Service.State from, @NotNull Throwable failure) {
walletAppKit = null; walletAppKit = null;
// TODO show error popup startupListener.failed(failure);
//crashAlert(failure);
} }
}, Threading.SAME_THREAD); }, Threading.USER_THREAD);
walletAppKit.startAsync(); walletAppKit.startAsync();
} }
private void initWallet() { private void initWallet() {
wallet = walletAppKit.wallet(); wallet = walletAppKit.wallet();
//walletAppKit.peerGroup().setMaxConnections(11);
/* if (params == RegTestParams.get())
walletAppKit.peerGroup().setMinBroadcastConnections(1);
else
walletAppKit.peerGroup().setMinBroadcastConnections(3);*/
walletEventListener = new WalletEventListener() { walletEventListener = new WalletEventListener() {
@Override @Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) { public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
@ -282,15 +291,6 @@ public class WalletFacade {
// Listener // Listener
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
/* public DownloadListener addDownloadListener(DownloadListener listener) {
downloadListeners.add(listener);
return listener;
}
public void removeDownloadListener(DownloadListener listener) {
downloadListeners.remove(listener);
}*/
public AddressConfidenceListener addAddressConfidenceListener(AddressConfidenceListener listener) { public AddressConfidenceListener addAddressConfidenceListener(AddressConfidenceListener listener) {
addressConfidenceListeners.add(listener); addressConfidenceListeners.add(listener);
return listener; return listener;
@ -1148,38 +1148,14 @@ public class WalletFacade {
public static interface StartupListener { public static interface StartupListener {
void completed(); void completed();
void failed(Throwable failure);
} }
public static interface DownloadListener { public static interface BlockchainDownloadListener {
void progress(double percent); 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();
}
}
}*/
} }

View file

@ -23,7 +23,8 @@ lower gradient color on tab: dddddd
.root { .root {
-bs-grey: #666666; -bs-grey: #666666;
-bs-bg-grey: #dddddd; -bs-bg-grey: #dddddd;
-bs-error-red: #dd0000;
-fx-accent: #0f87c3; -fx-accent: #0f87c3;
-fx-default-button: derive(-fx-accent,95%); -fx-default-button: derive(-fx-accent,95%);
-fx-focus-color: -fx-accent; -fx-focus-color: -fx-accent;
@ -36,7 +37,9 @@ lower gradient color on tab: dddddd
#splash { #splash {
-fx-background-color: #ffffff; -fx-background-color: #ffffff;
} }
#splash-error-state-msg {
-fx-text-fill: -bs-error-red;
}
/* Main UI */ /* Main UI */
#base-content-container { #base-content-container {
@ -52,18 +55,6 @@ lower gradient color on tab: dddddd
-fx-font-size: 18; -fx-font-size: 18;
} }
#info-label {
-fx-font-size: 14;
}
#online-label {
-fx-fill: green;
}
#offline-label {
-fx-fill: red;
}
/* Main navigation */ /* Main navigation */
#nav-button { #nav-button {

View file

@ -29,12 +29,8 @@ import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.TradeManager;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import org.bitcoinj.core.DownloadListener;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.util.Date;
import javax.inject.Named; import javax.inject.Named;
import javafx.application.Platform; import javafx.application.Platform;
@ -45,8 +41,7 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.MapChangeListener; import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@ -70,7 +65,8 @@ class MainModel extends UIModel {
final BooleanProperty backendReady = new SimpleBooleanProperty(); final BooleanProperty backendReady = new SimpleBooleanProperty();
final DoubleProperty networkSyncProgress = new SimpleDoubleProperty(-1); final DoubleProperty networkSyncProgress = new SimpleDoubleProperty(-1);
final IntegerProperty numPendingTrades = new SimpleIntegerProperty(0); final IntegerProperty numPendingTrades = new SimpleIntegerProperty(0);
final StringProperty bootstrapState = new SimpleStringProperty(); final ObjectProperty<BootstrapState> bootstrapState = new SimpleObjectProperty<>();
final ObjectProperty walletFacadeException = new SimpleObjectProperty<Throwable>();
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -130,41 +126,46 @@ class MainModel extends UIModel {
@Override @Override
public void onBootstrapStateChanged(BootstrapState bootstrapState) { public void onBootstrapStateChanged(BootstrapState bootstrapState) {
MainModel.this.bootstrapState.set(bootstrapState.getMessage()); MainModel.this.bootstrapState.set(bootstrapState);
} }
}); });
Profiler.printMsgWithTime("MainModel.initFacades"); Profiler.printMsgWithTime("MainModel.initFacades");
DownloadListener downloadListener = new DownloadListener() { WalletFacade.BlockchainDownloadListener blockchainDownloadListener = new WalletFacade
.BlockchainDownloadListener() {
@Override @Override
protected void progress(double percent, int blocksLeft, Date date) { public void progress(double percentage) {
super.progress(percent, blocksLeft, date); networkSyncProgress.set(percentage / 100.0);
Platform.runLater(() -> {
networkSyncProgress.set(percent / 100.0);
if (facadesInitialised && percent >= 100.0) if (facadesInitialised && percentage >= 100.0)
backendReady.set(true); backendReady.set(true);
});
} }
@Override @Override
protected void doneDownload() { public void doneDownload() {
super.doneDownload(); networkSyncProgress.set(1.0);
Platform.runLater(() -> {
networkSyncProgress.set(1.0);
if (facadesInitialised) if (facadesInitialised)
backendReady.set(true); backendReady.set(true);
});
} }
}; };
walletFacade.initialize(downloadListener, () -> { WalletFacade.StartupListener startupListener = new WalletFacade.StartupListener() {
walletFacadeInited = true; @Override
if (messageFacadeInited) public void completed() {
onFacadesInitialised(); walletFacadeInited = true;
}); if (messageFacadeInited)
onFacadesInitialised();
}
@Override
public void failed(final Throwable failure) {
walletFacadeException.set(failure);
}
};
walletFacade.initialize(Platform::runLater, blockchainDownloadListener, startupListener);
} }

View file

@ -20,6 +20,7 @@ package io.bitsquare.gui.main;
import io.bitsquare.bank.BankAccount; import io.bitsquare.bank.BankAccount;
import io.bitsquare.gui.PresentationModel; import io.bitsquare.gui.PresentationModel;
import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.network.BootstrapState;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -47,10 +48,16 @@ class MainPM extends PresentationModel<MainModel> {
final BooleanProperty backendReady = new SimpleBooleanProperty(); final BooleanProperty backendReady = new SimpleBooleanProperty();
final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty(); final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty();
final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty(); final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty();
final StringProperty bootstrapState = new SimpleStringProperty(); final StringProperty blockchainSyncState = new SimpleStringProperty("Initializing");
final StringProperty bitcoinSyncState = new SimpleStringProperty("Initializing");
final IntegerProperty numPendingTrades = new SimpleIntegerProperty(); 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<MainModel> {
super.initialize(); super.initialize();
backendReady.bind(model.backendReady); backendReady.bind(model.backendReady);
networkSyncProgress.bind(model.networkSyncProgress);
numPendingTrades.bind(model.numPendingTrades); numPendingTrades.bind(model.numPendingTrades);
model.bootstrapState.addListener((ov, oldValue, newValue) -> model.bootstrapState.addListener((ov, oldValue, newValue) -> {
bootstrapState.set("Connection to P2P network: " + 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)); model.walletFacadeException.addListener((ov, oldValue, newValue) -> {
updateBitcoinSyncState(model.networkSyncProgress.get()); 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<BankAccount>) change -> { model.getBankAccounts().addListener((ListChangeListener<BankAccount>) change -> {
bankAccountsComboBoxDisable.set(change.getList().isEmpty()); bankAccountsComboBoxDisable.set(change.getList().isEmpty());
@ -149,14 +182,16 @@ class MainPM extends PresentationModel<MainModel> {
// Private // Private
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void updateBitcoinSyncState(double value) { private void setNetworkSyncProgress(double value) {
if (value > 0.0) blockchainSyncProgress.set(value);
bitcoinSyncState.set("Synchronizing with bitcoin network: " + if (value >= 1)
formatter.formatToPercent(value)); blockchainSyncState.set("Synchronization completed.");
else if (value == 1) else if (value > 0.0)
bitcoinSyncState.set("Synchronizing with bitcoin network completed."); blockchainSyncState.set("Synchronizing blockchain: " + formatter.formatToPercent(value));
else else
bitcoinSyncState.set("Synchronizing with bitcoin network..."); blockchainSyncState.set("Connecting to bitcoin network...");
blockchainSyncIndicatorVisible.set(value < 1);
} }
} }

View file

@ -286,36 +286,52 @@ public class MainViewCB extends ViewCB<MainPM> {
ImageView logo = new ImageView(); ImageView logo = new ImageView();
logo.setId("image-splash-logo"); logo.setId("image-splash-logo");
Label bitcoinSyncStateLabel = new Label(); Label blockchainSyncLabel = new Label();
bitcoinSyncStateLabel.textProperty().bind(presentationModel.bitcoinSyncState); 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); ProgressBar blockchainSyncIndicator = new ProgressBar(-1);
btcProgressIndicator.setPrefWidth(120); blockchainSyncIndicator.setPrefWidth(120);
btcProgressIndicator.progressProperty().bind(presentationModel.networkSyncProgress); blockchainSyncIndicator.progressProperty().bind(presentationModel.blockchainSyncProgress);
blockchainSyncIndicator.visibleProperty().bind(presentationModel.blockchainSyncIndicatorVisible);
blockchainSyncIndicator.managedProperty().bind(presentationModel.blockchainSyncIndicatorVisible);
HBox btcBox = new HBox(); HBox blockchainSyncBox = new HBox();
btcBox.setSpacing(10); blockchainSyncBox.setSpacing(10);
btcBox.setAlignment(Pos.CENTER); blockchainSyncBox.setAlignment(Pos.CENTER);
btcBox.setPadding(new Insets(60, 0, 0, 0)); blockchainSyncBox.setPadding(new Insets(60, 0, 0, 0));
btcBox.getChildren().addAll(bitcoinSyncStateLabel, btcProgressIndicator); blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator);
Label bootstrapStateLabel = new Label(); Label bootstrapStateLabel = new Label();
bootstrapStateLabel.setWrapText(true); bootstrapStateLabel.setWrapText(true);
bootstrapStateLabel.setMaxWidth(500); bootstrapStateLabel.setMaxWidth(500);
bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER); bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER);
bootstrapStateLabel.textProperty().bind(presentationModel.bootstrapState); 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); ProgressIndicator bootstrapIndicator = new ProgressIndicator();
p2pProgressIndicator.setMaxSize(24, 24); bootstrapIndicator.setMaxSize(24, 24);
bootstrapIndicator.progressProperty().bind(presentationModel.bootstrapProgress);
bootstrapIndicator.visibleProperty().bind(presentationModel.bootstrapIndicatorVisible);
bootstrapIndicator.managedProperty().bind(presentationModel.bootstrapIndicatorVisible);
HBox p2pBox = new HBox(); HBox bootstrapBox = new HBox();
p2pBox.setSpacing(10); bootstrapBox.setSpacing(10);
p2pBox.setAlignment(Pos.CENTER); bootstrapBox.setAlignment(Pos.CENTER);
p2pBox.setPadding(new Insets(10, 0, 0, 0)); bootstrapBox.setPadding(new Insets(10, 0, 0, 0));
p2pBox.getChildren().addAll(bootstrapStateLabel, p2pProgressIndicator); bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator);
vBox.getChildren().addAll(logo, btcBox, p2pBox);
vBox.getChildren().addAll(logo, blockchainSyncBox, bootstrapBox);
return vBox; return vBox;
} }