diff --git a/src/main/java/io/bitsquare/app/BitsquareEnvironment.java b/src/main/java/io/bitsquare/app/BitsquareEnvironment.java
index 13669fd88e..5c80aea540 100644
--- a/src/main/java/io/bitsquare/app/BitsquareEnvironment.java
+++ b/src/main/java/io/bitsquare/app/BitsquareEnvironment.java
@@ -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";
diff --git a/src/main/java/io/bitsquare/app/gui/BitsquareApp.java b/src/main/java/io/bitsquare/app/gui/BitsquareApp.java
index fd2681d80c..a36d32bb40 100644
--- a/src/main/java/io/bitsquare/app/gui/BitsquareApp.java
+++ b/src/main/java/io/bitsquare/app/gui/BitsquareApp.java
@@ -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
diff --git a/src/main/java/io/bitsquare/app/gui/BitsquareAppMain.java b/src/main/java/io/bitsquare/app/gui/BitsquareAppMain.java
index 8d71b1ad30..a9e660c036 100644
--- a/src/main/java/io/bitsquare/app/gui/BitsquareAppMain.java
+++ b/src/main/java/io/bitsquare/app/gui/BitsquareAppMain.java
@@ -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);
}
diff --git a/src/main/java/io/bitsquare/app/gui/BitsquareAppModule.java b/src/main/java/io/bitsquare/app/gui/BitsquareAppModule.java
index 245e72572d..a3ed2b1739 100644
--- a/src/main/java/io/bitsquare/app/gui/BitsquareAppModule.java
+++ b/src/main/java/io/bitsquare/app/gui/BitsquareAppModule.java
@@ -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());
diff --git a/src/main/java/io/bitsquare/app/gui/ExampleApp.java b/src/main/java/io/bitsquare/app/gui/ExampleApp.java
new file mode 100644
index 0000000000..5bf508d61a
--- /dev/null
+++ b/src/main/java/io/bitsquare/app/gui/ExampleApp.java
@@ -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 .
+ */
+
+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 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;
+ }
+}
diff --git a/src/main/java/io/bitsquare/app/gui/MockUpdateProcess.java b/src/main/java/io/bitsquare/app/gui/MockUpdateProcess.java
new file mode 100644
index 0000000000..8314bcb5c3
--- /dev/null
+++ b/src/main/java/io/bitsquare/app/gui/MockUpdateProcess.java
@@ -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 .
+ */
+
+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");
+ }
+}
diff --git a/src/main/java/io/bitsquare/app/gui/UpdateProcess.java b/src/main/java/io/bitsquare/app/gui/UpdateProcess.java
new file mode 100644
index 0000000000..827157336b
--- /dev/null
+++ b/src/main/java/io/bitsquare/app/gui/UpdateProcess.java
@@ -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 .
+ */
+
+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 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 = new SimpleObjectProperty<>(State.CHECK_FOR_UPDATES);
+
+ protected String errorMessage;
+ protected final Subject 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() {
+ @Override
+ public Void apply(AnimationTimer animationTimer) {
+ process.onCompleted();
+ return null;
+ }
+ });
+ timeoutTimer.start();
+
+ init(environment);
+ }
+
+ public void restart() {
+ UpdateFX.restartApp();
+ }
+
+ public Observable 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() {
+ @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();
+ }
+
+}
diff --git a/src/main/java/io/bitsquare/gui/main/MainView.java b/src/main/java/io/bitsquare/gui/main/MainView.java
index 7c7acc45b8..2ba94dd112 100644
--- a/src/main/java/io/bitsquare/gui/main/MainView.java
+++ b/src/main/java/io/bitsquare/gui/main/MainView.java
@@ -152,7 +152,7 @@ public class MainView extends InitializableView {
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 {
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 {
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 {
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 {
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 {
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;
}
diff --git a/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/src/main/java/io/bitsquare/gui/main/MainViewModel.java
index eb4e63d5fe..c68f2a3ab7 100644
--- a/src/main/java/io/bitsquare/gui/main/MainViewModel.java
+++ b/src/main/java/io/bitsquare/gui/main/MainViewModel.java
@@ -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 = new SimpleObjectProperty<>();
- final StringProperty bootstrapStateText = new SimpleStringProperty();
- final ObjectProperty walletServiceException = new SimpleObjectProperty();
-
- 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 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) 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 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