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.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<DownloadListener> downloadListeners = new CopyOnWriteArrayList<>();
private final List<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArrayList<>();
private final List<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArrayList<>();
private final List<BalanceListener> 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();
}
}
}*/
}

View File

@ -23,6 +23,7 @@ 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%);
@ -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 {

View File

@ -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> bootstrapState = new SimpleObjectProperty<>();
final ObjectProperty walletFacadeException = new SimpleObjectProperty<Throwable>();
///////////////////////////////////////////////////////////////////////////////////////////
@ -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)
if (facadesInitialised && percentage >= 100.0)
backendReady.set(true);
});
}
@Override
protected void doneDownload() {
super.doneDownload();
Platform.runLater(() -> {
public void doneDownload() {
networkSyncProgress.set(1.0);
if (facadesInitialised)
backendReady.set(true);
});
}
};
walletFacade.initialize(downloadListener, () -> {
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);
}

View File

@ -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<MainModel> {
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<MainModel> {
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<BankAccount>) change -> {
bankAccountsComboBoxDisable.set(change.getList().isEmpty());
@ -149,14 +182,16 @@ class MainPM extends PresentationModel<MainModel> {
// 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);
}
}

View File

@ -286,36 +286,52 @@ public class MainViewCB extends ViewCB<MainPM> {
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;
}