Integrate UpdateFX with MockUpdateProcess

This commit is contained in:
Manfred Karrer 2014-12-17 17:41:58 +01:00
parent 2e97e900e5
commit 426b38180c
10 changed files with 605 additions and 131 deletions

View file

@ -45,7 +45,9 @@ public class BitsquareEnvironment extends StandardEnvironment {
public static final String APP_VERSION_KEY = "app.version";
// TODO what is the difference to APP_DATA_DIR ?
public static final String USER_DATA_DIR_KEY = "user.data.dir";
public static final String DEFAULT_USER_DATA_DIR = defaultUserDataDir();
public static final String APP_NAME_KEY = "app.name";

View file

@ -49,12 +49,17 @@ import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.util.FileSystemUtils;
import static io.bitsquare.app.BitsquareEnvironment.*;
public class BitsquareApp extends Application {
private static final Logger log = LoggerFactory.getLogger(BitsquareAppMain.class);
private static Environment env;
private BitsquareAppModule bitsquareAppModule;
@ -66,6 +71,9 @@ public class BitsquareApp extends Application {
@Override
public void start(Stage primaryStage) throws IOException {
// For some reason the JavaFX launch process results in us losing the thread context class loader: reset it.
Thread.currentThread().setContextClassLoader(BitsquareApp.class.getClassLoader());
bitsquareAppModule = new BitsquareAppModule(env, primaryStage);
injector = Guice.createInjector(bitsquareAppModule);
injector.getInstance(InjectorViewFactory.class).setInjector(injector);
@ -139,8 +147,7 @@ public class BitsquareApp extends Application {
else
iconPath = "/images/task_bar_icon_linux.png";
if (iconPath != null)
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream(iconPath)));
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream(iconPath)));
// make the UI visible

View file

@ -24,6 +24,12 @@ import io.bitsquare.network.BootstrapNodes;
import io.bitsquare.network.Node;
import io.bitsquare.util.joptsimple.EnumValueConverter;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vinumeris.updatefx.UpdateFX;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
@ -33,8 +39,17 @@ import static io.bitsquare.network.Node.*;
import static java.util.Arrays.asList;
public class BitsquareAppMain extends BitsquareExecutable {
private static final Logger log = LoggerFactory.getLogger(BitsquareAppMain.class);
public static void main(String[] args) throws Exception {
// We don't want to do the whole arg parsing/setup here as that might easily change in update versions
// So we only handle the absolute minimum which is APP_NAME and USER_DATA_DIR
// TODO Not impl. yet, just use default for first testings
UpdateFX.bootstrap(BitsquareAppMain.class, new File(BitsquareEnvironment.DEFAULT_APP_DATA_DIR).toPath(), args);
}
// That will be called from UpdateFX after updates are checked
public static void realMain(String[] args) throws Exception {
new BitsquareAppMain().execute(args);
}

View file

@ -61,6 +61,11 @@ class BitsquareAppModule extends BitsquareModule {
bindConstant().annotatedWith(named(Persistence.PREFIX_KEY)).to(env.getRequiredProperty(Persistence.PREFIX_KEY));
bind(Persistence.class).asEagerSingleton();
// TODO UpdateFXHelper needs Environment. Should we just expose the 2 properties needed?
bind(Environment.class).toInstance(env);
// for temp testing with mock
bind(UpdateProcess.class).to(MockUpdateProcess.class).asEagerSingleton();
install(messageModule());
install(bitcoinModule());
install(cryptoModule());

View file

@ -0,0 +1,154 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app.gui;
import org.bitcoinj.utils.BriefLogFormatter;
import com.google.common.util.concurrent.Uninterruptibles;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.FileHandler;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vinumeris.updatefx.AppDirectory;
import com.vinumeris.updatefx.Crypto;
import com.vinumeris.updatefx.UpdateFX;
import com.vinumeris.updatefx.UpdateSummary;
import com.vinumeris.updatefx.Updater;
import org.bouncycastle.math.ec.ECPoint;
// TODO remove it after we have impl. UpdateFX.
// Let it here for reference and for easier test setup for the moment.
public class ExampleApp extends Application {
private static final Logger log = LoggerFactory.getLogger(ExampleApp.class);
public static int VERSION = 3;
public static void main(String[] args) throws IOException {
// We want to store updates in our app dir so must init that here.
AppDirectory.initAppDir("UpdateFX Example App");
setupLogging();
// re-enter at realMain, but possibly running a newer version of the software i.e. after this point the
// rest of this code may be ignored.
UpdateFX.bootstrap(ExampleApp.class, AppDirectory.dir(), args);
}
public static void realMain(String[] args) {
launch(args);
}
private static java.util.logging.Logger logger;
private static void setupLogging() throws IOException {
logger = java.util.logging.Logger.getLogger("");
logger.getHandlers()[0].setFormatter(new BriefLogFormatter());
FileHandler handler = new FileHandler(AppDirectory.dir().resolve("log.txt").toString(), true);
handler.setFormatter(new BriefLogFormatter());
logger.addHandler(handler);
}
@Override
public void start(Stage primaryStage) throws Exception {
// For some reason the JavaFX launch process results in us losing the thread context class loader: reset it.
Thread.currentThread().setContextClassLoader(ExampleApp.class.getClassLoader());
// Must be done twice for the times when we come here via realMain.
AppDirectory.initAppDir("UpdateFX Example App");
log.info("Hello World! This is version " + VERSION);
ProgressIndicator indicator = showGiantProgressWheel(primaryStage);
List<ECPoint> pubkeys = Crypto.decode("028B41BDDCDCAD97B6AE088FEECA16DC369353B717E13319370C729CB97D677A11",
// wallet_1
"031E3D80F21A4D10D385A32ABEDC300DACBEDBC839FBA58376FBD5D791D806BA68"); // wallet
Updater updater = new Updater("http://localhost:8000/", "ExampleApp/" + VERSION, VERSION,
AppDirectory.dir(), UpdateFX.findCodePath(ExampleApp.class),
pubkeys, 1) {
@Override
protected void updateProgress(long workDone, long max) {
super.updateProgress(workDone, max);
// Give UI a chance to show.
Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
}
};
indicator.progressProperty().bind(updater.progressProperty());
log.info("Checking for updates!");
updater.setOnSucceeded(event -> {
try {
UpdateSummary summary = updater.get();
if (summary.descriptions.size() > 0) {
log.info("One liner: {}", summary.descriptions.get(0).getOneLiner());
log.info("{}", summary.descriptions.get(0).getDescription());
}
if (summary.highestVersion > VERSION) {
log.info("Restarting to get version " + summary.highestVersion);
if (UpdateFX.getVersionPin(AppDirectory.dir()) == 0)
UpdateFX.restartApp();
}
} catch (Throwable e) {
log.error("oops", e);
}
});
updater.setOnFailed(event -> {
log.error("Update error: {}", updater.getException());
updater.getException().printStackTrace();
});
indicator.setOnMouseClicked(ev -> UpdateFX.restartApp());
new Thread(updater, "UpdateFX Thread").start();
primaryStage.show();
}
private ProgressIndicator showGiantProgressWheel(Stage stage) {
ProgressIndicator indicator = new ProgressIndicator();
BorderPane borderPane = new BorderPane(indicator);
borderPane.setMinWidth(640);
borderPane.setMinHeight(480);
Button pinButton = new Button();
pinButton.setText("Pin to version 1");
pinButton.setOnAction(event -> {
UpdateFX.pinToVersion(AppDirectory.dir(), 1);
UpdateFX.restartApp();
});
HBox box = new HBox(new Label("Version " + VERSION), pinButton);
box.setSpacing(10);
box.setAlignment(Pos.CENTER_LEFT);
box.setPadding(new Insets(10));
borderPane.setTop(box);
Scene scene = new Scene(borderPane);
stage.setScene(scene);
return indicator;
}
}

View file

@ -0,0 +1,56 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app.gui;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
public class MockUpdateProcess extends UpdateProcess {
private static final Logger log = LoggerFactory.getLogger(MockUpdateProcess.class);
@Inject
public MockUpdateProcess(Environment environment) {
super(environment);
}
@Override
protected void init(Environment environment) {
/* timeoutTimer.stop();
state.set(State.UPDATE_AVAILABLE);*/
state.set(State.UP_TO_DATE);
timeoutTimer.stop();
process.onCompleted();
/* state.set(State.FAILURE);
errorMessage = "dummy exc.";
timeoutTimer.stop();
process.onCompleted();*/
}
@Override
public void restart() {
log.debug("restart requested");
}
}

View file

@ -0,0 +1,176 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app.gui;
import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.util.Utilities;
import com.google.inject.Inject;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Function;
import javafx.animation.AnimationTimer;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vinumeris.updatefx.Crypto;
import com.vinumeris.updatefx.UpdateFX;
import com.vinumeris.updatefx.UpdateSummary;
import com.vinumeris.updatefx.Updater;
import org.bouncycastle.math.ec.ECPoint;
import org.springframework.core.env.Environment;
import rx.Observable;
import rx.subjects.BehaviorSubject;
import rx.subjects.Subject;
public class UpdateProcess {
private static final Logger log = LoggerFactory.getLogger(UpdateProcess.class);
private static final int VERSION = 1;
private static final List<ECPoint> UPDATE_SIGNING_KEYS = Crypto.decode(
"028B41BDDCDCAD97B6AE088FEECA16DC369353B717E13319370C729CB97D677A11",
"031E3D80F21A4D10D385A32ABEDC300DACBEDBC839FBA58376FBD5D791D806BA68"
);
private static final int UPDATE_SIGNING_THRESHOLD = 1;
private static final String UPDATES_BASE_URL = "http://localhost:8000/";
private static final Path ROOT_CLASS_PATH = UpdateFX.findCodePath(BitsquareAppMain.class);
public enum State {
CHECK_FOR_UPDATES,
UPDATE_AVAILABLE,
UP_TO_DATE,
FAILURE
}
public final ObjectProperty<State> state = new SimpleObjectProperty<>(State.CHECK_FOR_UPDATES);
protected String errorMessage;
protected final Subject<State, State> process = BehaviorSubject.create();
protected final AnimationTimer timeoutTimer;
@Inject
public UpdateProcess(Environment environment) {
// process.timeout() will cause an error state back but we dont want to break startup in case of an update
// timeout
timeoutTimer = Utilities.setTimeout(10000, new Function<AnimationTimer, Void>() {
@Override
public Void apply(AnimationTimer animationTimer) {
process.onCompleted();
return null;
}
});
timeoutTimer.start();
init(environment);
}
public void restart() {
UpdateFX.restartApp();
}
public Observable<State> getProcess() {
return process.asObservable();
}
public String getErrorMessage() {
return errorMessage;
}
protected void init(Environment environment) {
log.info("version " + VERSION);
String agent = environment.getProperty(BitsquareEnvironment.APP_NAME_KEY) + VERSION;
Path dataDirPath = new File(environment.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY)).toPath();
Updater updater = new Updater(UPDATES_BASE_URL, agent, VERSION, dataDirPath, ROOT_CLASS_PATH,
UPDATE_SIGNING_KEYS, UPDATE_SIGNING_THRESHOLD) {
@Override
protected void updateProgress(long workDone, long max) {
log.debug("updateProgress " + workDone + "/" + max);
super.updateProgress(workDone, max);
}
};
updater.progressProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {
log.trace("progressProperty newValue = " + newValue);
}
});
log.info("Checking for updates!");
updater.setOnSucceeded(event -> {
try {
UpdateSummary summary = updater.get();
if (summary.descriptions.size() > 0) {
log.info("One liner: {}", summary.descriptions.get(0).getOneLiner());
log.info("{}", summary.descriptions.get(0).getDescription());
}
if (summary.highestVersion > VERSION) {
state.set(State.UPDATE_AVAILABLE);
}
else if (summary.highestVersion == VERSION) {
state.set(State.UP_TO_DATE);
timeoutTimer.stop();
process.onCompleted();
}
/* if (summary.highestVersion > VERSION) {
log.info("Restarting to get version " + summary.highestVersion);
if (UpdateFX.getVersionPin(dataDirPath) == 0)
UpdateFX.restartApp();
}*/
} catch (Throwable e) {
log.error("Exception at processing UpdateSummary: " + e.getMessage());
// we treat errors as update not as critical errors to prevent startup,
// so we use state.onCompleted() instead of state.onError()
errorMessage = "Exception at processing UpdateSummary: " + e.getMessage();
state.set(State.FAILURE);
timeoutTimer.stop();
process.onCompleted();
}
});
updater.setOnFailed(event -> {
log.error("Update failed: " + updater.getException());
updater.getException().printStackTrace();
// we treat errors as update not as critical errors to prevent startup,
// so we use state.onCompleted() instead of state.onError()
errorMessage = "Update failed: " + updater.getException();
state.set(State.FAILURE);
timeoutTimer.stop();
process.onCompleted();
});
Thread thread = new Thread(updater, "Online update check");
thread.setDaemon(true);
thread.start();
}
}

View file

@ -152,7 +152,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
root.getChildren().addAll(baseApplicationContainer, splashScreen);
model.isReadyForMainScreen.addListener((ov, oldValue, newValue) -> {
model.showAppScreen.addListener((ov, oldValue, newValue) -> {
if (newValue) {
bankAccountComboBoxHolder.getChildren().setAll(createBankAccountComboBox());
@ -184,15 +184,13 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
Pane notification = new Pane();
notification.relocate(30, 9);
notification.setMouseTransparent(true);
notification.setVisible(model.numPendingTrades.get() > 0);
notification.setEffect(new DropShadow(4, 1, 2, Color.GREY));
notification.getChildren().addAll(icon, numPendingTradesLabel);
notification.visibleProperty().bind(model.showPendingTradesNotification);
portfolioButtonHolder.getChildren().add(notification);
model.numPendingTrades.addListener((ov, oldValue, newValue) -> {
notification.setVisible((int) newValue > 0);
if ((int) newValue > 0)
model.showPendingTradesNotification.addListener((ov, oldValue, newValue) -> {
if (newValue)
SystemNotification.openInfoNotification(title, "You got a new trade message.");
});
}
@ -200,17 +198,17 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
private VBox createSplashScreen() {
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
vBox.setSpacing(10);
vBox.setSpacing(0);
vBox.setId("splash");
ImageView logo = new ImageView();
logo.setId("image-splash-logo");
Label blockchainSyncLabel = new Label();
blockchainSyncLabel.textProperty().bind(model.blockchainSyncState);
blockchainSyncLabel.textProperty().bind(model.blockchainSyncInfo);
model.walletServiceErrorMsg.addListener((ov, oldValue, newValue) -> {
blockchainSyncLabel.setId("splash-error-state-msg");
Popups.openErrorPopup("Error", "An error occurred at startup. \n\nError message:\n" +
Popups.openErrorPopup("Error", "Connecting to the bitcoin network failed. \n\nReason: " +
newValue);
});
@ -238,7 +236,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
HBox blockchainSyncBox = new HBox();
blockchainSyncBox.setSpacing(10);
blockchainSyncBox.setAlignment(Pos.CENTER);
blockchainSyncBox.setPadding(new Insets(60, 0, 0, 0));
blockchainSyncBox.setPadding(new Insets(40, 0, 0, 0));
blockchainSyncBox.setPrefHeight(50);
blockchainSyncBox.getChildren().addAll(blockchainSyncLabel, blockchainSyncIndicator,
blockchainSyncIcon, bitcoinNetworkLabel);
@ -247,20 +245,18 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
bootstrapStateLabel.setWrapText(true);
bootstrapStateLabel.setMaxWidth(500);
bootstrapStateLabel.setTextAlignment(TextAlignment.CENTER);
bootstrapStateLabel.textProperty().bind(model.bootstrapStateText);
bootstrapStateLabel.textProperty().bind(model.bootstrapInfo);
ProgressIndicator bootstrapIndicator = new ProgressIndicator();
bootstrapIndicator.setMaxSize(24, 24);
bootstrapIndicator.progressProperty().bind(model.bootstrapProgress);
model.bootstrapFailed.addListener((ov, oldValue, newValue) -> {
if (newValue) {
bootstrapStateLabel.setId("splash-error-state-msg");
bootstrapIndicator.setVisible(false);
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());
}
Popups.openErrorPopup("Error", "Connecting to the Bitsquare network failed. \n\nReason: " +
model.bootstrapErrorMsg.get());
});
ImageView bootstrapIcon = new ImageView();
@ -279,11 +275,35 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
HBox bootstrapBox = new HBox();
bootstrapBox.setSpacing(10);
bootstrapBox.setAlignment(Pos.CENTER);
bootstrapBox.setPadding(new Insets(10, 0, 0, 0));
bootstrapBox.setPrefHeight(50);
bootstrapBox.getChildren().addAll(bootstrapStateLabel, bootstrapIndicator, bootstrapIcon);
vBox.getChildren().addAll(logo, blockchainSyncBox, bootstrapBox);
// 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, blockchainSyncBox, bootstrapBox, updateBox);
return vBox;
}

View file

@ -18,13 +18,13 @@
package io.bitsquare.gui.main;
import io.bitsquare.account.AccountSettings;
import io.bitsquare.app.gui.UpdateProcess;
import io.bitsquare.arbitrator.Arbitrator;
import io.bitsquare.arbitrator.Reputation;
import io.bitsquare.bank.BankAccount;
import io.bitsquare.bank.BankAccountType;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.WalletService;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.LanguageUtil;
@ -35,7 +35,6 @@ import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.user.User;
import io.bitsquare.util.DSAKeyUtil;
import io.bitsquare.util.Utilities;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
@ -47,18 +46,16 @@ import java.util.ArrayList;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeoutException;
import viewfx.model.ViewModel;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
@ -75,106 +72,63 @@ import rx.Observable;
class MainViewModel implements ViewModel {
private static final Logger log = LoggerFactory.getLogger(MainViewModel.class);
final DoubleProperty networkSyncProgress = new SimpleDoubleProperty(-1);
final IntegerProperty numPendingTrades = new SimpleIntegerProperty(0);
final StringProperty numPendingTradesAsString = new SimpleStringProperty();
final ObjectProperty<BootstrapState> bootstrapState = new SimpleObjectProperty<>();
final StringProperty bootstrapStateText = new SimpleStringProperty();
final ObjectProperty walletServiceException = new SimpleObjectProperty<Throwable>();
final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty();
final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty();
final StringProperty blockchainSyncState = new SimpleStringProperty("Initializing");
// BTC network
final StringProperty blockchainSyncInfo = new SimpleStringProperty("Initializing");
final DoubleProperty blockchainSyncProgress = new SimpleDoubleProperty(-1);
final BooleanProperty blockchainSyncIndicatorVisible = new SimpleBooleanProperty(true);
final StringProperty blockchainSyncIconId = new SimpleStringProperty();
final StringProperty walletServiceErrorMsg = new SimpleStringProperty();
final BooleanProperty isReadyForMainScreen = new SimpleBooleanProperty();
final StringProperty blockchainSyncIconId = new SimpleStringProperty();
// P2P network
final StringProperty bootstrapInfo = new SimpleStringProperty();
final DoubleProperty bootstrapProgress = new SimpleDoubleProperty(-1);
final BooleanProperty bootstrapFailed = new SimpleBooleanProperty();
final StringProperty bootstrapErrorMsg = new SimpleStringProperty();
final StringProperty bootstrapIconId = new SimpleStringProperty();
final StringProperty featureNotImplementedWarning = new SimpleStringProperty();
// software update
final StringProperty updateInfo = new SimpleStringProperty();
final BooleanProperty showRestartButton = new SimpleBooleanProperty(false);
final StringProperty updateIconId = new SimpleStringProperty();
final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty();
final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty();
final ObjectProperty<BankAccount> currentBankAccount = new SimpleObjectProperty<>();
final BooleanProperty showAppScreen = new SimpleBooleanProperty();
final StringProperty featureNotImplementedWarning = new SimpleStringProperty();
final StringProperty numPendingTradesAsString = new SimpleStringProperty();
final BooleanProperty showPendingTradesNotification = new SimpleBooleanProperty();
final String bitcoinNetworkAsString;
private final User user;
private final WalletService walletService;
private final MessageService messageService;
private final TradeManager tradeManager;
private UpdateProcess updateProcess;
private final BSFormatter formatter;
private Persistence persistence;
private AccountSettings accountSettings;
private AnimationTimer bitcoinNetworkTimeout;
@Inject
public MainViewModel(User user, WalletService walletService, MessageService messageService,
TradeManager tradeManager, BitcoinNetwork bitcoinNetwork, BSFormatter formatter,
Persistence persistence, AccountSettings accountSettings) {
TradeManager tradeManager, BitcoinNetwork bitcoinNetwork, UpdateProcess updateProcess,
BSFormatter formatter, Persistence persistence, AccountSettings accountSettings) {
this.user = user;
this.walletService = walletService;
this.messageService = messageService;
this.tradeManager = tradeManager;
this.updateProcess = updateProcess;
this.formatter = formatter;
this.persistence = persistence;
this.accountSettings = accountSettings;
bitcoinNetworkAsString = bitcoinNetwork.toString();
updateProcess.state.addListener((observableValue, oldValue, newValue) -> applyUpdateState(newValue));
applyUpdateState(updateProcess.state.get());
user.getCurrentBankAccount().addListener((observable, oldValue, newValue) -> persistence.write(user));
currentBankAccount.bind(user.currentBankAccountProperty());
bootstrapState.addListener((ov, oldValue, newValue) -> {
if (newValue == BootstrapState.DISCOVERY_DIRECT_SUCCEEDED ||
newValue == BootstrapState.DISCOVERY_MANUAL_PORT_FORWARDING_SUCCEEDED ||
newValue == BootstrapState.DISCOVERY_AUTO_PORT_FORWARDING_SUCCEEDED ||
newValue == BootstrapState.RELAY_SUCCEEDED) {
bootstrapStateText.set("Successfully connected to P2P network: " + newValue.getMessage());
bootstrapProgress.set(1);
if (newValue == BootstrapState.DISCOVERY_DIRECT_SUCCEEDED)
bootstrapIconId.set("image-connection-direct");
else if (newValue == BootstrapState.DISCOVERY_MANUAL_PORT_FORWARDING_SUCCEEDED ||
newValue == BootstrapState.DISCOVERY_AUTO_PORT_FORWARDING_SUCCEEDED)
bootstrapIconId.set("image-connection-nat");
else if (newValue == BootstrapState.RELAY_SUCCEEDED)
bootstrapIconId.set("image-connection-relay");
}
else if (newValue == BootstrapState.PEER_CREATION_FAILED ||
newValue == BootstrapState.DISCOVERY_FAILED ||
newValue == BootstrapState.DISCOVERY_AUTO_PORT_FORWARDING_FAILED ||
newValue == BootstrapState.RELAY_FAILED) {
bootstrapErrorMsg.set(newValue.getMessage());
bootstrapStateText.set("Connecting to the Bitsquare network failed.");
bootstrapProgress.set(0);
bootstrapFailed.set(true);
}
else {
bootstrapStateText.set("Connecting to the Bitsquare network: " + newValue.getMessage());
}
}
);
walletServiceException.addListener((ov, oldValue, newValue) -> {
blockchainSyncIndicatorVisible.set(false);
blockchainSyncState.set("Startup failed.");
walletServiceErrorMsg.set(((Throwable) newValue).getMessage());
});
networkSyncProgress.addListener((ov, oldValue, newValue) -> {
setNetworkSyncProgress((double) newValue);
if ((double) newValue >= 1)
blockchainSyncIconId.set("image-connection-synced");
});
setNetworkSyncProgress(networkSyncProgress.get());
user.getBankAccounts().addListener((ListChangeListener<BankAccount>) change -> {
bankAccountsComboBoxDisable.set(change.getList().isEmpty());
bankAccountsComboBoxPrompt.set(change.getList().isEmpty() ? "No accounts" : "");
@ -182,57 +136,67 @@ class MainViewModel implements ViewModel {
bankAccountsComboBoxDisable.set(user.getBankAccounts().isEmpty());
bankAccountsComboBoxPrompt.set(user.getBankAccounts().isEmpty() ? "No accounts" : "");
tradeManager.featureNotImplementedWarningProperty().addListener((ov, oldValue, newValue) -> {
if (oldValue == null && newValue != null) {
featureNotImplementedWarning.set(newValue);
Popups.openWarningPopup(newValue);
tradeManager.setFeatureNotImplementedWarning(null);
}
});
}
public void initBackend() {
bitcoinNetworkTimeout = Utilities.setTimeout(20000, animationTimer -> {
Platform.runLater(() -> {
networkSyncProgress.set(0);
blockchainSyncState.set("Connecting to the bitcoin network failed.");
Popups.openErrorPopup("Connecting to the bitcoin network failed",
"Please check your network connection.\n\n" +
"You must allow outgoing TCP connections to port 18333 for the bitcoin testnet.\n\n" +
"See https://github.com/bitsquare/bitsquare/wiki for instructions.");
});
return null;
});
public void restart() {
updateProcess.restart();
}
public void initBackend() {
setBitcoinNetworkSyncProgress(-1);
walletService.getDownloadProgress().subscribe(
percentage -> Platform.runLater(() -> {
if (percentage > 0)
networkSyncProgress.set(percentage / 100.0);
setBitcoinNetworkSyncProgress(percentage / 100.0);
}),
error -> log.error(error.toString()),
() -> Platform.runLater(() -> networkSyncProgress.set(1.0)));
() -> Platform.runLater(() -> setBitcoinNetworkSyncProgress(1.0)));
Observable<BootstrapState> message = messageService.init();
message.publish();
message.subscribe(
state -> Platform.runLater(() -> bootstrapState.set(state)),
error -> log.error(error.toString()),
state -> Platform.runLater(() -> setBootstrapState(state)),
error -> Platform.runLater(() -> {
log.error(error.toString());
bootstrapErrorMsg.set(error.getMessage());
bootstrapInfo.set("Connecting to the Bitsquare network failed.");
bootstrapProgress.set(0);
}),
() -> log.trace("message completed"));
Observable<Object> wallet = walletService.initialize(Platform::runLater);
wallet.subscribe(
next -> {
log.trace("wallet next");
},
error -> Platform.runLater(() -> walletServiceException.set(error)),
error -> Platform.runLater(() -> {
log.trace("wallet error");
setWalletServiceException(error);
}),
() -> {
log.trace("wallet completed");
bitcoinNetworkTimeout.stop();
bitcoinNetworkTimeout = null;
});
Observable<?> backend = Observable.merge(message, wallet);
backend.subscribe(
Observable<UpdateProcess.State> updateProcess = this.updateProcess.getProcess();
updateProcess.subscribe(next -> {
log.trace("updateProcess next");
},
error -> {
log.trace("updateProcess error");
},
() -> {
log.trace("updateProcess completed");
});
Observable<?> backEnd = Observable.merge(message, wallet, updateProcess);
backEnd.subscribe(
next -> {
},
error -> log.error(error.toString()),
@ -246,7 +210,7 @@ class MainViewModel implements ViewModel {
tradeManager.getPendingTrades().addListener(
(MapChangeListener<String, Trade>) change -> updateNumPendingTrades());
updateNumPendingTrades();
isReadyForMainScreen.set(true);
showAppScreen.set(true);
// For alpha version
// uses messageService, so don't call it before backend is ready
@ -270,6 +234,77 @@ class MainViewModel implements ViewModel {
}
}
private void applyUpdateState(UpdateProcess.State state) {
switch (state) {
case CHECK_FOR_UPDATES:
updateInfo.set("Checking for updates...");
updateIconId.set("image-update-in-progress");
break;
case UPDATE_AVAILABLE:
updateInfo.set("New update available. Please restart!");
updateIconId.set("image-update-available");
showRestartButton.set(true);
break;
case UP_TO_DATE:
updateInfo.set("Software is up to date.");
updateIconId.set("image-update-up-to-date");
break;
case FAILURE:
updateInfo.set(updateProcess.getErrorMessage());
updateIconId.set("image-update-failed");
break;
}
}
private void setBootstrapState(BootstrapState state) {
switch (state) {
case DISCOVERY_DIRECT_SUCCEEDED:
bootstrapIconId.set("image-connection-direct");
break;
case DISCOVERY_MANUAL_PORT_FORWARDING_SUCCEEDED:
case DISCOVERY_AUTO_PORT_FORWARDING_SUCCEEDED:
bootstrapIconId.set("image-connection-nat");
break;
case RELAY_SUCCEEDED:
bootstrapIconId.set("image-connection-relay");
break;
default:
bootstrapIconId.set(null);
break;
}
switch (state) {
case DISCOVERY_DIRECT_SUCCEEDED:
case DISCOVERY_MANUAL_PORT_FORWARDING_SUCCEEDED:
case DISCOVERY_AUTO_PORT_FORWARDING_SUCCEEDED:
case RELAY_SUCCEEDED:
bootstrapInfo.set("Successfully connected to P2P network: " + state.getMessage());
bootstrapProgress.set(1);
break;
default:
bootstrapInfo.set("Connecting to the Bitsquare network: " + state.getMessage());
bootstrapProgress.set(-1);
break;
}
}
private void setWalletServiceException(Throwable error) {
setBitcoinNetworkSyncProgress(0);
blockchainSyncInfo.set("Connecting to the bitcoin network failed.");
if (error instanceof TimeoutException) {
walletServiceErrorMsg.set("Please check your network connection.\n\n" +
"You must allow outgoing TCP connections to port 18333 for the bitcoin testnet.\n\n" +
"See https://github.com/bitsquare/bitsquare/wiki for instructions.");
}
else if (error.getMessage() != null) {
walletServiceErrorMsg.set(error.getMessage());
}
else {
walletServiceErrorMsg.set(error.toString());
}
}
public StringConverter<BankAccount> getBankAccountsConverter() {
return new StringConverter<BankAccount>() {
@Override
@ -293,21 +328,24 @@ class MainViewModel implements ViewModel {
}
private void updateNumPendingTrades() {
numPendingTrades.set(tradeManager.getPendingTrades().size());
if (numPendingTrades.get() > 0)
numPendingTradesAsString.set(String.valueOf(numPendingTrades.get()));
int numPendingTrades = tradeManager.getPendingTrades().size();
if (numPendingTrades > 0)
numPendingTradesAsString.set(String.valueOf(numPendingTrades));
showPendingTradesNotification.set(numPendingTrades > 0);
}
private void setNetworkSyncProgress(double value) {
private void setBitcoinNetworkSyncProgress(double value) {
blockchainSyncProgress.set(value);
if (value >= 1)
blockchainSyncState.set("Blockchain synchronization complete.");
else if (value > 0.0)
blockchainSyncState.set("Synchronizing blockchain: " + formatter.formatToPercent(value));
else
blockchainSyncState.set("Connecting to the bitcoin network...");
blockchainSyncIndicatorVisible.set(value < 1);
if (value >= 1) {
blockchainSyncInfo.set("Blockchain synchronization complete.");
blockchainSyncIconId.set("image-connection-synced");
}
else if (value > 0.0) {
blockchainSyncInfo.set("Synchronizing blockchain: " + formatter.formatToPercent(value));
}
else {
blockchainSyncInfo.set("Connecting to the bitcoin network...");
}
}
private void addMockArbitrator() {

View file

@ -27,6 +27,7 @@
<logger name="org.bitcoinj" level="INFO"/>
<logger name="net.tomp2p" level="INFO"/>
<logger name="com.vinumeris.updatefx" level="TRACE"/>
<logger name="net.tomp2p.message.Encoder" level="WARN"/>