mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
bwt threading and wallet rescanning
This commit is contained in:
parent
a1c65cff75
commit
3c4b25ecee
35 changed files with 536 additions and 142 deletions
|
@ -69,7 +69,7 @@ dependencies {
|
||||||
exclude group: 'org.openjfx', module: 'javafx-web'
|
exclude group: 'org.openjfx', module: 'javafx-web'
|
||||||
exclude group: 'org.openjfx', module: 'javafx-media'
|
exclude group: 'org.openjfx', module: 'javafx-media'
|
||||||
}
|
}
|
||||||
implementation('dev.bwt:bwt-jni:0.1.4')
|
implementation('dev.bwt:bwt-jni:0.1.5')
|
||||||
testImplementation('junit:junit:4.12')
|
testImplementation('junit:junit:4.12')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 6ad3f5373119b65d17b857738b8411ee88cea993
|
Subproject commit 4da2c024d4fbe3ecbca9772a0761def6d5269c38
|
|
@ -110,6 +110,8 @@ public class AppController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private UnlabeledToggleSwitch serverToggle;
|
private UnlabeledToggleSwitch serverToggle;
|
||||||
|
|
||||||
|
private PauseTransition wait;
|
||||||
|
|
||||||
private Timeline statusTimeline;
|
private Timeline statusTimeline;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -170,7 +172,7 @@ public class AppController implements Initializable {
|
||||||
boolean walletAdded = c.getAddedSubList().stream().anyMatch(tab -> ((TabData)tab.getUserData()).getType() == TabData.TabType.WALLET);
|
boolean walletAdded = c.getAddedSubList().stream().anyMatch(tab -> ((TabData)tab.getUserData()).getType() == TabData.TabType.WALLET);
|
||||||
boolean walletRemoved = c.getRemoved().stream().anyMatch(tab -> ((TabData)tab.getUserData()).getType() == TabData.TabType.WALLET);
|
boolean walletRemoved = c.getRemoved().stream().anyMatch(tab -> ((TabData)tab.getUserData()).getType() == TabData.TabType.WALLET);
|
||||||
if(walletAdded || walletRemoved) {
|
if(walletAdded || walletRemoved) {
|
||||||
EventManager.get().post(new OpenWalletsEvent(tabs.getScene().getWindow(), getOpenWallets()));
|
EventManager.get().post(new OpenWalletsEvent(tabs.getScene().getWindow(), getOpenWalletTabData()));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<WalletTabData> closedWalletTabs = c.getRemoved().stream().map(tab -> (TabData)tab.getUserData())
|
List<WalletTabData> closedWalletTabs = c.getRemoved().stream().map(tab -> (TabData)tab.getUserData())
|
||||||
|
@ -194,7 +196,7 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
tabs.getScene().getWindow().setOnCloseRequest(event -> {
|
tabs.getScene().getWindow().setOnCloseRequest(event -> {
|
||||||
EventManager.get().unregister(this);
|
EventManager.get().unregister(this);
|
||||||
EventManager.get().post(new OpenWalletsEvent(tabs.getScene().getWindow(), Collections.emptyMap()));
|
EventManager.get().post(new OpenWalletsEvent(tabs.getScene().getWindow(), Collections.emptyList()));
|
||||||
});
|
});
|
||||||
|
|
||||||
BitcoinUnit unit = Config.get().getBitcoinUnit();
|
BitcoinUnit unit = Config.get().getBitcoinUnit();
|
||||||
|
@ -221,7 +223,7 @@ public class AppController implements Initializable {
|
||||||
showTxHex.setSelected(Config.get().isShowTransactionHex());
|
showTxHex.setSelected(Config.get().isShowTransactionHex());
|
||||||
exportWallet.setDisable(true);
|
exportWallet.setDisable(true);
|
||||||
|
|
||||||
serverToggle.setSelected(isOnline());
|
serverToggle.setSelected(isConnected());
|
||||||
onlineProperty().bindBidirectional(serverToggle.selectedProperty());
|
onlineProperty().bindBidirectional(serverToggle.selectedProperty());
|
||||||
onlineProperty().addListener((observable, oldValue, newValue) -> {
|
onlineProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
Platform.runLater(() -> setServerToggleTooltip(getCurrentBlockHeight()));
|
Platform.runLater(() -> setServerToggleTooltip(getCurrentBlockHeight()));
|
||||||
|
@ -451,21 +453,30 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Wallet, Storage> getOpenWallets() {
|
|
||||||
Map<Wallet, Storage> openWallets = new LinkedHashMap<>();
|
public List<WalletTabData> getOpenWalletTabData() {
|
||||||
|
List<WalletTabData> openWalletTabData = new ArrayList<>();
|
||||||
|
|
||||||
for(Tab tab : tabs.getTabs()) {
|
for(Tab tab : tabs.getTabs()) {
|
||||||
TabData tabData = (TabData)tab.getUserData();
|
TabData tabData = (TabData)tab.getUserData();
|
||||||
if(tabData.getType() == TabData.TabType.WALLET) {
|
if(tabData.getType() == TabData.TabType.WALLET) {
|
||||||
WalletTabData walletTabData = (WalletTabData) tabData;
|
openWalletTabData.add((WalletTabData)tabData);
|
||||||
openWallets.put(walletTabData.getWallet(), walletTabData.getStorage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return openWalletTabData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Wallet, Storage> getOpenWallets() {
|
||||||
|
Map<Wallet, Storage> openWallets = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for(WalletTabData walletTabData : getOpenWalletTabData()){
|
||||||
|
openWallets.put(walletTabData.getWallet(), walletTabData.getStorage());
|
||||||
|
}
|
||||||
|
|
||||||
return openWallets;
|
return openWallets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void selectTab(Wallet wallet) {
|
public void selectTab(Wallet wallet) {
|
||||||
for(Tab tab : tabs.getTabs()) {
|
for(Tab tab : tabs.getTabs()) {
|
||||||
TabData tabData = (TabData) tab.getUserData();
|
TabData tabData = (TabData) tab.getUserData();
|
||||||
|
@ -521,7 +532,7 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setServerToggleTooltip(Integer currentBlockHeight) {
|
private void setServerToggleTooltip(Integer currentBlockHeight) {
|
||||||
serverToggle.setTooltip(new Tooltip(AppServices.isOnline() ? "Connected to " + Config.get().getServerAddress() + (currentBlockHeight != null ? " at height " + currentBlockHeight : "") : "Disconnected"));
|
serverToggle.setTooltip(new Tooltip(AppServices.isConnected() ? "Connected to " + Config.get().getServerAddress() + (currentBlockHeight != null ? " at height " + currentBlockHeight : "") : "Disconnected"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void newWallet(ActionEvent event) {
|
public void newWallet(ActionEvent event) {
|
||||||
|
@ -845,6 +856,8 @@ public class AppController implements Initializable {
|
||||||
tab.setContent(walletLoader.load());
|
tab.setContent(walletLoader.load());
|
||||||
WalletController controller = walletLoader.getController();
|
WalletController controller = walletLoader.getController();
|
||||||
|
|
||||||
|
EventManager.get().post(new WalletOpeningEvent(storage, wallet));
|
||||||
|
|
||||||
//Note that only one WalletForm is created per wallet tab, and registered to listen for events. All wallet controllers (except SettingsController) share this instance.
|
//Note that only one WalletForm is created per wallet tab, and registered to listen for events. All wallet controllers (except SettingsController) share this instance.
|
||||||
WalletForm walletForm = new WalletForm(storage, wallet);
|
WalletForm walletForm = new WalletForm(storage, wallet);
|
||||||
EventManager.get().register(walletForm);
|
EventManager.get().register(walletForm);
|
||||||
|
@ -1162,7 +1175,10 @@ public class AppController implements Initializable {
|
||||||
public void statusUpdated(StatusEvent event) {
|
public void statusUpdated(StatusEvent event) {
|
||||||
statusBar.setText(event.getStatus());
|
statusBar.setText(event.getStatus());
|
||||||
|
|
||||||
PauseTransition wait = new PauseTransition(Duration.seconds(20));
|
if(wait != null && wait.getStatus() == Animation.Status.RUNNING) {
|
||||||
|
wait.stop();
|
||||||
|
}
|
||||||
|
wait = new PauseTransition(Duration.seconds(20));
|
||||||
wait.setOnFinished((e) -> {
|
wait.setOnFinished((e) -> {
|
||||||
if(statusBar.getText().equals(event.getStatus())) {
|
if(statusBar.getText().equals(event.getStatus())) {
|
||||||
statusBar.setText("");
|
statusBar.setText("");
|
||||||
|
@ -1237,14 +1253,14 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
||||||
if(AppServices.isOnline()) {
|
if((AppServices.isConnecting() || AppServices.isConnected()) && !event.isCompleted()) {
|
||||||
statusUpdated(new StatusEvent(event.getStatus()));
|
statusUpdated(new StatusEvent(event.getStatus()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void bwtScanStatus(BwtScanStatusEvent event) {
|
public void bwtScanStatus(BwtScanStatusEvent event) {
|
||||||
if(AppServices.isOnline()) {
|
if((AppServices.isConnecting() || AppServices.isConnected()) && !event.isCompleted()) {
|
||||||
statusUpdated(new StatusEvent(event.getStatus()));
|
statusUpdated(new StatusEvent(event.getStatus()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1302,7 +1318,7 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void requestOpenWallets(RequestOpenWalletsEvent event) {
|
public void requestOpenWallets(RequestOpenWalletsEvent event) {
|
||||||
EventManager.get().post(new OpenWalletsEvent(tabs.getScene().getWindow(), getOpenWallets()));
|
EventManager.get().post(new OpenWalletsEvent(tabs.getScene().getWindow(), getOpenWalletTabData()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|
|
@ -12,10 +12,7 @@ import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.io.Device;
|
import com.sparrowwallet.sparrow.io.Device;
|
||||||
import com.sparrowwallet.sparrow.io.Hwi;
|
import com.sparrowwallet.sparrow.io.Hwi;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
import com.sparrowwallet.sparrow.net.*;
|
||||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
|
||||||
import com.sparrowwallet.sparrow.net.MempoolRateSize;
|
|
||||||
import com.sparrowwallet.sparrow.net.VersionCheckService;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
@ -57,7 +54,7 @@ public class AppServices {
|
||||||
|
|
||||||
private final MainApp application;
|
private final MainApp application;
|
||||||
|
|
||||||
private final Map<Window, Map<Wallet, Storage>> walletWindows = new LinkedHashMap<>();
|
private final Map<Window, List<WalletTabData>> walletWindows = new LinkedHashMap<>();
|
||||||
|
|
||||||
private static final BooleanProperty onlineProperty = new SimpleBooleanProperty(false);
|
private static final BooleanProperty onlineProperty = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
|
@ -146,6 +143,20 @@ public class AppServices {
|
||||||
onlineProperty.addListener(onlineServicesListener);
|
onlineProperty.addListener(onlineServicesListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if(connectionService != null) {
|
||||||
|
connectionService.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ratesService != null) {
|
||||||
|
ratesService.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(versionCheckService != null) {
|
||||||
|
versionCheckService.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ElectrumServer.ConnectionService createConnectionService() {
|
private ElectrumServer.ConnectionService createConnectionService() {
|
||||||
ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService();
|
ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService();
|
||||||
connectionService.setPeriod(new Duration(SERVER_PING_PERIOD));
|
connectionService.setPeriod(new Duration(SERVER_PING_PERIOD));
|
||||||
|
@ -267,24 +278,22 @@ public class AppServices {
|
||||||
|
|
||||||
public Map<Wallet, Storage> getOpenWallets() {
|
public Map<Wallet, Storage> getOpenWallets() {
|
||||||
Map<Wallet, Storage> openWallets = new LinkedHashMap<>();
|
Map<Wallet, Storage> openWallets = new LinkedHashMap<>();
|
||||||
for(Map<Wallet, Storage> walletStorageMap : walletWindows.values()) {
|
for(List<WalletTabData> walletTabDataList : walletWindows.values()) {
|
||||||
openWallets.putAll(walletStorageMap);
|
for(WalletTabData walletTabData : walletTabDataList) {
|
||||||
|
openWallets.put(walletTabData.getWallet(), walletTabData.getStorage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return openWallets;
|
return openWallets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Wallet, Storage> getOpenWallets(Window window) {
|
|
||||||
return walletWindows.get(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Window getWindowForWallet(Storage storage) {
|
public Window getWindowForWallet(Storage storage) {
|
||||||
Optional<Window> optWindow = walletWindows.entrySet().stream().filter(entry -> entry.getValue().values().stream().anyMatch(storage1 -> storage1.getWalletFile().equals(storage.getWalletFile()))).map(Map.Entry::getKey).findFirst();
|
Optional<Window> optWindow = walletWindows.entrySet().stream().filter(entry -> entry.getValue().stream().anyMatch(walletTabData -> walletTabData.getStorage().getWalletFile().equals(storage.getWalletFile()))).map(Map.Entry::getKey).findFirst();
|
||||||
return optWindow.orElse(null);
|
return optWindow.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Window getWindowForPSBT(PSBT psbt) {
|
public Window getWindowForPSBT(PSBT psbt) {
|
||||||
Optional<Window> optWindow = walletWindows.entrySet().stream().filter(entry -> entry.getValue().keySet().stream().anyMatch(wallet -> wallet.canSign(psbt))).map(Map.Entry::getKey).findFirst();
|
Optional<Window> optWindow = walletWindows.entrySet().stream().filter(entry -> entry.getValue().stream().anyMatch(walletTabData -> walletTabData.getWallet().canSign(psbt))).map(Map.Entry::getKey).findFirst();
|
||||||
return optWindow.orElse(null);
|
return optWindow.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,8 +301,12 @@ public class AppServices {
|
||||||
return walletWindows.keySet().stream().mapToDouble(Window::getX).max().orElse(0d);
|
return walletWindows.keySet().stream().mapToDouble(Window::getX).max().orElse(0d);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isOnline() {
|
public static boolean isConnecting() {
|
||||||
return onlineProperty.get();
|
return onlineProperty.get() && get().connectionService.isConnecting();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isConnected() {
|
||||||
|
return onlineProperty.get() && get().connectionService.isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BooleanProperty onlineProperty() {
|
public static BooleanProperty onlineProperty() {
|
||||||
|
@ -440,25 +453,25 @@ public class AppServices {
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void openWallets(OpenWalletsEvent event) {
|
public void openWallets(OpenWalletsEvent event) {
|
||||||
if(event.getWalletsMap().isEmpty()) {
|
if(event.getWalletTabDataList().isEmpty()) {
|
||||||
walletWindows.remove(event.getWindow());
|
walletWindows.remove(event.getWindow());
|
||||||
} else {
|
} else {
|
||||||
walletWindows.put(event.getWindow(), event.getWalletsMap());
|
walletWindows.put(event.getWindow(), event.getWalletTabDataList());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Map.Entry<Wallet, Storage>> allWallets = walletWindows.values().stream().flatMap(map -> map.entrySet().stream()).collect(Collectors.toList());
|
List<WalletTabData> allWallets = walletWindows.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if(!Window.getWindows().isEmpty()) {
|
if(!Window.getWindows().isEmpty()) {
|
||||||
List<File> walletFiles = allWallets.stream().map(entry -> entry.getValue().getWalletFile()).collect(Collectors.toList());
|
List<File> walletFiles = allWallets.stream().map(walletTabData -> walletTabData.getStorage().getWalletFile()).collect(Collectors.toList());
|
||||||
Config.get().setRecentWalletFiles(walletFiles);
|
Config.get().setRecentWalletFiles(walletFiles);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
boolean usbWallet = false;
|
boolean usbWallet = false;
|
||||||
for(Map.Entry<Wallet, Storage> entry : allWallets) {
|
for(WalletTabData walletTabData : allWallets) {
|
||||||
Wallet wallet = entry.getKey();
|
Wallet wallet = walletTabData.getWallet();
|
||||||
Storage storage = entry.getValue();
|
Storage storage = walletTabData.getStorage();
|
||||||
|
|
||||||
if(!storage.getWalletFile().exists() || wallet.containsSource(KeystoreSource.HW_USB)) {
|
if(!storage.getWalletFile().exists() || wallet.containsSource(KeystoreSource.HW_USB)) {
|
||||||
usbWallet = true;
|
usbWallet = true;
|
||||||
|
@ -494,4 +507,28 @@ public class AppServices {
|
||||||
public void requestDisconnect(RequestDisconnectEvent event) {
|
public void requestDisconnect(RequestDisconnectEvent event) {
|
||||||
onlineProperty.set(false);
|
onlineProperty.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void walletSettingsChanged(WalletSettingsChangedEvent event) {
|
||||||
|
restartBwt(event.getWallet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void walletOpening(WalletOpeningEvent event) {
|
||||||
|
restartBwt(event.getWallet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restartBwt(Wallet wallet) {
|
||||||
|
if(Config.get().getServerType() == ServerType.BITCOIN_CORE && isConnected() && wallet.isValid()) {
|
||||||
|
connectionService.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtShutdown(BwtShutdownEvent event) {
|
||||||
|
if(onlineProperty().get() && !connectionService.isRunning()) {
|
||||||
|
connectionService.reset();
|
||||||
|
connectionService.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ public class MainApp extends Application {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() throws Exception {
|
public void stop() throws Exception {
|
||||||
|
AppServices.get().stop();
|
||||||
mainStage.close();
|
mainStage.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,27 @@ package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.BitcoinUnit;
|
import com.sparrowwallet.drongo.BitcoinUnit;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.event.WalletDataChangedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
|
import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.WalletSettingsChangedEvent;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
|
import com.sparrowwallet.sparrow.net.ServerType;
|
||||||
import com.sparrowwallet.sparrow.wallet.Entry;
|
import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Hyperlink;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TreeTableView;
|
import javafx.scene.control.TreeTableView;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class CoinTreeTable extends TreeTableView<Entry> {
|
public class CoinTreeTable extends TreeTableView<Entry> {
|
||||||
private BitcoinUnit bitcoinUnit;
|
private BitcoinUnit bitcoinUnit;
|
||||||
|
@ -51,10 +66,46 @@ public class CoinTreeTable extends TreeTableView<Entry> {
|
||||||
setPlaceholder(new Label("Loading transactions..."));
|
setPlaceholder(new Label("Loading transactions..."));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setPlaceholder(new Label("No transactions"));
|
setPlaceholder(getDefaultPlaceholder(event.getWallet()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Node getDefaultPlaceholder(Wallet wallet) {
|
||||||
|
StackPane stackPane = new StackPane();
|
||||||
|
stackPane.getChildren().add(AppServices.isConnecting() ? new Label("Loading transactions...") : new Label("No transactions"));
|
||||||
|
|
||||||
|
if(Config.get().getServerType() == ServerType.BITCOIN_CORE && !AppServices.isConnecting()) {
|
||||||
|
Hyperlink hyperlink = new Hyperlink();
|
||||||
|
hyperlink.setTranslateY(30);
|
||||||
|
hyperlink.setOnAction(event -> {
|
||||||
|
WalletBirthDateDialog dlg = new WalletBirthDateDialog(wallet.getBirthDate());
|
||||||
|
Optional<Date> optDate = dlg.showAndWait();
|
||||||
|
if(optDate.isPresent()) {
|
||||||
|
wallet.setBirthDate(optDate.get());
|
||||||
|
Storage storage = AppServices.get().getOpenWallets().get(wallet);
|
||||||
|
if(storage != null) {
|
||||||
|
//Trigger background save of birthdate
|
||||||
|
EventManager.get().post(new WalletDataChangedEvent(wallet));
|
||||||
|
//Trigger full wallet rescan
|
||||||
|
wallet.clearHistory();
|
||||||
|
EventManager.get().post(new WalletSettingsChangedEvent(wallet, storage.getWalletFile()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(wallet.getBirthDate() == null) {
|
||||||
|
hyperlink.setText("Scan for previous transactions?");
|
||||||
|
} else {
|
||||||
|
DateFormat dateFormat = new SimpleDateFormat(DateStringConverter.FORMAT_PATTERN);
|
||||||
|
hyperlink.setText("Scan for transactions earlier than " + dateFormat.format(wallet.getBirthDate()) + "?");
|
||||||
|
}
|
||||||
|
|
||||||
|
stackPane.getChildren().add(hyperlink);
|
||||||
|
}
|
||||||
|
|
||||||
|
stackPane.setAlignment(Pos.CENTER);
|
||||||
|
return stackPane;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
public class DateStringConverter extends StringConverter<LocalDate> {
|
||||||
|
public static final String FORMAT_PATTERN = "yyyy/MM/dd";
|
||||||
|
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(FORMAT_PATTERN);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(LocalDate date) {
|
||||||
|
if (date != null) {
|
||||||
|
return DATE_FORMATTER.format(date);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalDate fromString(String string) {
|
||||||
|
if (string != null && !string.isEmpty()) {
|
||||||
|
return LocalDate.parse(string, DATE_FORMATTER);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
import com.sparrowwallet.sparrow.wallet.TransactionEntry;
|
import com.sparrowwallet.sparrow.wallet.TransactionEntry;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletTransactionsEntry;
|
import com.sparrowwallet.sparrow.wallet.WalletTransactionsEntry;
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.TreeTableColumn;
|
import javafx.scene.control.TreeTableColumn;
|
||||||
import javafx.scene.control.TreeTableView;
|
import javafx.scene.control.TreeTableView;
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ public class TransactionsTreeTable extends CoinTreeTable {
|
||||||
balanceCol.setSortable(true);
|
balanceCol.setSortable(true);
|
||||||
getColumns().add(balanceCol);
|
getColumns().add(balanceCol);
|
||||||
|
|
||||||
setPlaceholder(new Label("No transactions"));
|
setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet()));
|
||||||
setEditable(true);
|
setEditable(true);
|
||||||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
dateCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
dateCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
||||||
|
|
|
@ -3,7 +3,6 @@ package com.sparrowwallet.sparrow.control;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletNode;
|
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
import com.sparrowwallet.sparrow.wallet.*;
|
import com.sparrowwallet.sparrow.wallet.*;
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.SelectionMode;
|
import javafx.scene.control.SelectionMode;
|
||||||
import javafx.scene.control.TreeTableColumn;
|
import javafx.scene.control.TreeTableColumn;
|
||||||
import javafx.scene.control.TreeTableView;
|
import javafx.scene.control.TreeTableView;
|
||||||
|
@ -69,7 +68,7 @@ public class UtxosTreeTable extends CoinTreeTable {
|
||||||
getColumns().add(amountCol);
|
getColumns().add(amountCol);
|
||||||
setTreeColumn(amountCol);
|
setTreeColumn(amountCol);
|
||||||
|
|
||||||
setPlaceholder(new Label("No unspent outputs"));
|
setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet()));
|
||||||
setEditable(true);
|
setEditable(true);
|
||||||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
amountCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
amountCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
|
import org.controlsfx.validation.ValidationResult;
|
||||||
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
|
import org.controlsfx.validation.Validator;
|
||||||
|
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class WalletBirthDateDialog extends Dialog<Date> {
|
||||||
|
private final DatePicker birthDatePicker;
|
||||||
|
|
||||||
|
public WalletBirthDateDialog(Date birthDate) {
|
||||||
|
final DialogPane dialogPane = getDialogPane();
|
||||||
|
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||||
|
|
||||||
|
setTitle("Wallet Birth Date");
|
||||||
|
dialogPane.setHeaderText("Select an approximate date earlier than the first wallet transaction:");
|
||||||
|
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||||
|
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL);
|
||||||
|
dialogPane.setPrefWidth(420);
|
||||||
|
dialogPane.setPrefHeight(200);
|
||||||
|
|
||||||
|
Glyph wallet = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.HISTORY);
|
||||||
|
wallet.setFontSize(50);
|
||||||
|
dialogPane.setGraphic(wallet);
|
||||||
|
|
||||||
|
HBox datePickerBox = new HBox(10);
|
||||||
|
Label label = new Label("Start scanning from:");
|
||||||
|
label.setPadding(new Insets(5, 0, 0, 8));
|
||||||
|
datePickerBox.getChildren().add(label);
|
||||||
|
|
||||||
|
birthDatePicker = birthDate == null ? new DatePicker() : new DatePicker(birthDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
|
||||||
|
birthDatePicker.setEditable(false);
|
||||||
|
birthDatePicker.setConverter(new DateStringConverter());
|
||||||
|
|
||||||
|
datePickerBox.getChildren().add(birthDatePicker);
|
||||||
|
|
||||||
|
dialogPane.setContent(datePickerBox);
|
||||||
|
|
||||||
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
Platform.runLater( () -> {
|
||||||
|
validationSupport.registerValidator(birthDatePicker, Validator.combine(
|
||||||
|
(Control c, LocalDate newValue) -> ValidationResult.fromErrorIf( c, "Birth date not specified", newValue == null)
|
||||||
|
));
|
||||||
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
|
});
|
||||||
|
|
||||||
|
final ButtonType okButtonType = new javafx.scene.control.ButtonType("Rescan Wallet", ButtonBar.ButtonData.OK_DONE);
|
||||||
|
dialogPane.getButtonTypes().addAll(okButtonType);
|
||||||
|
Button okButton = (Button) dialogPane.lookupButton(okButtonType);
|
||||||
|
BooleanBinding isInvalid = Bindings.createBooleanBinding(() -> birthDatePicker.getValue() == null, birthDatePicker.valueProperty());
|
||||||
|
okButton.disableProperty().bind(isInvalid);
|
||||||
|
|
||||||
|
setResultConverter(dialogButton -> dialogButton == okButtonType ? Date.from(birthDatePicker.getValue().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()) : null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ public class WalletNameDialog extends Dialog<WalletNameDialog.NameAndBirthDate>
|
||||||
dialogPane.setHeaderText("Enter a name for this wallet:");
|
dialogPane.setHeaderText("Enter a name for this wallet:");
|
||||||
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||||
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL);
|
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL);
|
||||||
dialogPane.setPrefWidth(420);
|
dialogPane.setPrefWidth(460);
|
||||||
dialogPane.setPrefHeight(requestBirthDate ? 250 : 200);
|
dialogPane.setPrefHeight(requestBirthDate ? 250 : 200);
|
||||||
|
|
||||||
Glyph wallet = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WALLET);
|
Glyph wallet = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WALLET);
|
||||||
|
@ -60,12 +60,20 @@ public class WalletNameDialog extends Dialog<WalletNameDialog.NameAndBirthDate>
|
||||||
existingBox.getChildren().add(existingCheck);
|
existingBox.getChildren().add(existingCheck);
|
||||||
|
|
||||||
existingPicker = new DatePicker();
|
existingPicker = new DatePicker();
|
||||||
|
existingPicker.setConverter(new DateStringConverter());
|
||||||
existingPicker.setEditable(false);
|
existingPicker.setEditable(false);
|
||||||
existingPicker.setPrefWidth(130);
|
existingPicker.setPrefWidth(130);
|
||||||
existingPicker.managedProperty().bind(existingPicker.visibleProperty());
|
existingPicker.managedProperty().bind(existingPicker.visibleProperty());
|
||||||
existingPicker.setVisible(false);
|
existingPicker.setVisible(false);
|
||||||
existingBox.getChildren().add(existingPicker);
|
existingBox.getChildren().add(existingPicker);
|
||||||
|
|
||||||
|
HelpLabel helpLabel = new HelpLabel();
|
||||||
|
helpLabel.setHelpText("Select an approximate date earlier than the first wallet transaction.");
|
||||||
|
helpLabel.setTranslateY(5);
|
||||||
|
helpLabel.managedProperty().bind(helpLabel.visibleProperty());
|
||||||
|
helpLabel.visibleProperty().bind(existingPicker.visibleProperty());
|
||||||
|
existingBox.getChildren().add(helpLabel);
|
||||||
|
|
||||||
existingCheck.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
existingCheck.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if(newValue) {
|
if(newValue) {
|
||||||
existingCheck.setText("Has existing transactions starting from");
|
existingCheck.setText("Has existing transactions starting from");
|
||||||
|
|
|
@ -16,6 +16,10 @@ public class BwtScanStatusEvent extends BwtStatusEvent {
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCompleted() {
|
||||||
|
return progress == 100;
|
||||||
|
}
|
||||||
|
|
||||||
public Date getEta() {
|
public Date getEta() {
|
||||||
return eta;
|
return eta;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty class used to notify the bwt has shut down.
|
||||||
|
*/
|
||||||
|
public class BwtShutdownEvent {
|
||||||
|
|
||||||
|
}
|
|
@ -14,6 +14,10 @@ public class BwtSyncStatusEvent extends BwtStatusEvent {
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCompleted() {
|
||||||
|
return progress == 100;
|
||||||
|
}
|
||||||
|
|
||||||
public int getTip() {
|
public int getTip() {
|
||||||
return tip;
|
return tip;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,47 @@
|
||||||
package com.sparrowwallet.sparrow.event;
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.WalletTabData;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class OpenWalletsEvent {
|
public class OpenWalletsEvent {
|
||||||
private final Window window;
|
private final Window window;
|
||||||
private final Map<Wallet, Storage> walletsMap;
|
private final List<WalletTabData> walletTabDataList;
|
||||||
|
|
||||||
public OpenWalletsEvent(Window window, Map<Wallet, Storage> walletsMap) {
|
public OpenWalletsEvent(Window window, List<WalletTabData> walletTabDataList) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.walletsMap = walletsMap;
|
this.walletTabDataList = walletTabDataList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Window getWindow() {
|
public Window getWindow() {
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Wallet> getWallets() {
|
public List<WalletTabData> getWalletTabDataList() {
|
||||||
return new ArrayList<>(walletsMap.keySet());
|
return walletTabDataList;
|
||||||
}
|
|
||||||
|
|
||||||
public Storage getStorage(Wallet wallet) {
|
|
||||||
return walletsMap.get(wallet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Wallet, Storage> getWalletsMap() {
|
public Map<Wallet, Storage> getWalletsMap() {
|
||||||
return walletsMap;
|
Map<Wallet, Storage> openWallets = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for(WalletTabData walletTabData : walletTabDataList){
|
||||||
|
openWallets.put(walletTabData.getWallet(), walletTabData.getStorage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return openWallets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Wallet> getWallets() {
|
||||||
|
return new ArrayList<>(getWalletsMap().keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Storage getStorage(Wallet wallet) {
|
||||||
|
return getWalletsMap().get(wallet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,6 @@ public class SettingsChangedEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
POLICY, SCRIPT_TYPE, MUTLISIG_THRESHOLD, MULTISIG_TOTAL, KEYSTORE_LABEL, KEYSTORE_FINGERPRINT, KEYSTORE_DERIVATION, KEYSTORE_XPUB, GAP_LIMIT;
|
POLICY, SCRIPT_TYPE, MUTLISIG_THRESHOLD, MULTISIG_TOTAL, KEYSTORE_LABEL, KEYSTORE_FINGERPRINT, KEYSTORE_DERIVATION, KEYSTORE_XPUB, GAP_LIMIT, BIRTH_DATE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package com.sparrowwallet.sparrow.event;
|
||||||
import com.sparrowwallet.drongo.KeyPurpose;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletNode;
|
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||||
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -12,13 +14,19 @@ import java.util.stream.Collectors;
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class WalletHistoryChangedEvent extends WalletChangedEvent {
|
public class WalletHistoryChangedEvent extends WalletChangedEvent {
|
||||||
|
private final Storage storage;
|
||||||
private final List<WalletNode> historyChangedNodes;
|
private final List<WalletNode> historyChangedNodes;
|
||||||
|
|
||||||
public WalletHistoryChangedEvent(Wallet wallet, List<WalletNode> historyChangedNodes) {
|
public WalletHistoryChangedEvent(Wallet wallet, Storage storage, List<WalletNode> historyChangedNodes) {
|
||||||
super(wallet);
|
super(wallet);
|
||||||
|
this.storage = storage;
|
||||||
this.historyChangedNodes = historyChangedNodes;
|
this.historyChangedNodes = historyChangedNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File getWalletFile() {
|
||||||
|
return storage.getWalletFile();
|
||||||
|
}
|
||||||
|
|
||||||
public List<WalletNode> getHistoryChangedNodes() {
|
public List<WalletNode> getHistoryChangedNodes() {
|
||||||
return historyChangedNodes;
|
return historyChangedNodes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,27 +4,27 @@ import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
public class WalletHistoryStatusEvent {
|
public class WalletHistoryStatusEvent {
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private final boolean loaded;
|
private final boolean loading;
|
||||||
private final String statusMessage;
|
private final String statusMessage;
|
||||||
private final String errorMessage;
|
private final String errorMessage;
|
||||||
|
|
||||||
public WalletHistoryStatusEvent(Wallet wallet, boolean loaded) {
|
public WalletHistoryStatusEvent(Wallet wallet, boolean loading) {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.loaded = loaded;
|
this.loading = loading;
|
||||||
this.statusMessage = null;
|
this.statusMessage = null;
|
||||||
this.errorMessage = null;
|
this.errorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletHistoryStatusEvent(Wallet wallet, boolean loaded, String statusMessage) {
|
public WalletHistoryStatusEvent(Wallet wallet, boolean loading, String statusMessage) {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.loaded = false;
|
this.loading = loading;
|
||||||
this.statusMessage = statusMessage;
|
this.statusMessage = statusMessage;
|
||||||
this.errorMessage = null;
|
this.errorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletHistoryStatusEvent(Wallet wallet,String errorMessage) {
|
public WalletHistoryStatusEvent(Wallet wallet, String errorMessage) {
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.loaded = false;
|
this.loading = true;
|
||||||
this.statusMessage = null;
|
this.statusMessage = null;
|
||||||
this.errorMessage = errorMessage;
|
this.errorMessage = errorMessage;
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,7 @@ public class WalletHistoryStatusEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLoading() {
|
public boolean isLoading() {
|
||||||
return !loaded;
|
return loading;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLoaded() {
|
|
||||||
return loaded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStatusMessage() {
|
public String getStatusMessage() {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
|
|
||||||
|
public class WalletOpeningEvent {
|
||||||
|
private final Storage storage;
|
||||||
|
private final Wallet wallet;
|
||||||
|
|
||||||
|
public WalletOpeningEvent(Storage storage, Wallet wallet) {
|
||||||
|
this.storage = storage;
|
||||||
|
this.wallet = wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Storage getStorage() {
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Wallet getWallet() {
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ public class FontAwesome5 extends GlyphFont {
|
||||||
EYE('\uf06e'),
|
EYE('\uf06e'),
|
||||||
HAND_HOLDING('\uf4bd'),
|
HAND_HOLDING('\uf4bd'),
|
||||||
HAND_HOLDING_MEDICAL('\ue05c'),
|
HAND_HOLDING_MEDICAL('\ue05c'),
|
||||||
|
HISTORY('\uf1da'),
|
||||||
KEY('\uf084'),
|
KEY('\uf084'),
|
||||||
LAPTOP('\uf109'),
|
LAPTOP('\uf109'),
|
||||||
LOCK('\uf023'),
|
LOCK('\uf023'),
|
||||||
|
|
|
@ -74,7 +74,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
|
||||||
public Map<String, ScriptHashTx[]> getScriptHashHistory(Transport transport, Wallet wallet, Map<String, String> pathScriptHashes, boolean failOnError) {
|
public Map<String, ScriptHashTx[]> getScriptHashHistory(Transport transport, Wallet wallet, Map<String, String> pathScriptHashes, boolean failOnError) {
|
||||||
JsonRpcClient client = new JsonRpcClient(transport);
|
JsonRpcClient client = new JsonRpcClient(transport);
|
||||||
BatchRequestBuilder<String, ScriptHashTx[]> batchRequest = client.createBatchRequest().keysType(String.class).returnType(ScriptHashTx[].class);
|
BatchRequestBuilder<String, ScriptHashTx[]> batchRequest = client.createBatchRequest().keysType(String.class).returnType(ScriptHashTx[].class);
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false, "Loading transactions"));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Loading transactions"));
|
||||||
|
|
||||||
for(String path : pathScriptHashes.keySet()) {
|
for(String path : pathScriptHashes.keySet()) {
|
||||||
batchRequest.add(path, "blockchain.scripthash.get_history", pathScriptHashes.get(path));
|
batchRequest.add(path, "blockchain.scripthash.get_history", pathScriptHashes.get(path));
|
||||||
|
@ -130,7 +130,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
|
||||||
public Map<String, String> subscribeScriptHashes(Transport transport, Wallet wallet, Map<String, String> pathScriptHashes) {
|
public Map<String, String> subscribeScriptHashes(Transport transport, Wallet wallet, Map<String, String> pathScriptHashes) {
|
||||||
JsonRpcClient client = new JsonRpcClient(transport);
|
JsonRpcClient client = new JsonRpcClient(transport);
|
||||||
BatchRequestBuilder<String, String> batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class);
|
BatchRequestBuilder<String, String> batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class);
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false, "Finding transactions"));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Finding transactions"));
|
||||||
|
|
||||||
for(String path : pathScriptHashes.keySet()) {
|
for(String path : pathScriptHashes.keySet()) {
|
||||||
batchRequest.add(path, "blockchain.scripthash.subscribe", pathScriptHashes.get(path));
|
batchRequest.add(path, "blockchain.scripthash.subscribe", pathScriptHashes.get(path));
|
||||||
|
@ -151,7 +151,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
|
||||||
public Map<Integer, String> getBlockHeaders(Transport transport, Wallet wallet, Set<Integer> blockHeights) {
|
public Map<Integer, String> getBlockHeaders(Transport transport, Wallet wallet, Set<Integer> blockHeights) {
|
||||||
JsonRpcClient client = new JsonRpcClient(transport);
|
JsonRpcClient client = new JsonRpcClient(transport);
|
||||||
BatchRequestBuilder<Integer, String> batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(String.class);
|
BatchRequestBuilder<Integer, String> batchRequest = client.createBatchRequest().keysType(Integer.class).returnType(String.class);
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false, "Retrieving blocks"));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Retrieving blocks"));
|
||||||
|
|
||||||
for(Integer height : blockHeights) {
|
for(Integer height : blockHeights) {
|
||||||
batchRequest.add(height, "blockchain.block.header", height);
|
batchRequest.add(height, "blockchain.block.header", height);
|
||||||
|
@ -171,7 +171,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
|
||||||
public Map<String, String> getTransactions(Transport transport, Wallet wallet, Set<String> txids) {
|
public Map<String, String> getTransactions(Transport transport, Wallet wallet, Set<String> txids) {
|
||||||
JsonRpcClient client = new JsonRpcClient(transport);
|
JsonRpcClient client = new JsonRpcClient(transport);
|
||||||
BatchRequestBuilder<String, String> batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class);
|
BatchRequestBuilder<String, String> batchRequest = client.createBatchRequest().keysType(String.class).returnType(String.class);
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false, "Retrieving transactions"));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Retrieving transactions"));
|
||||||
|
|
||||||
for(String txid : txids) {
|
for(String txid : txids) {
|
||||||
batchRequest.add(txid, "blockchain.transaction.get", txid);
|
batchRequest.add(txid, "blockchain.transaction.get", txid);
|
||||||
|
|
|
@ -5,12 +5,13 @@ import com.google.gson.annotations.SerializedName;
|
||||||
import com.sparrowwallet.drongo.KeyPurpose;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.Network;
|
import com.sparrowwallet.drongo.Network;
|
||||||
import com.sparrowwallet.drongo.OutputDescriptor;
|
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||||
|
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import dev.bwt.daemon.CallbackNotifier;
|
import dev.bwt.libbwt.daemon.CallbackNotifier;
|
||||||
import dev.bwt.daemon.NativeBwtDaemon;
|
import dev.bwt.libbwt.daemon.NativeBwtDaemon;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.concurrent.Service;
|
import javafx.concurrent.Service;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
@ -18,20 +19,18 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Bwt {
|
public class Bwt {
|
||||||
private static final Logger log = LoggerFactory.getLogger(Bwt.class);
|
private static final Logger log = LoggerFactory.getLogger(Bwt.class);
|
||||||
private Long shutdownPtr;
|
private Long shutdownPtr;
|
||||||
|
private boolean terminating;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
org.controlsfx.tools.Platform platform = org.controlsfx.tools.Platform.getCurrent();
|
org.controlsfx.tools.Platform platform = org.controlsfx.tools.Platform.getCurrent();
|
||||||
if(platform == org.controlsfx.tools.Platform.OSX) {
|
if(platform == org.controlsfx.tools.Platform.OSX) {
|
||||||
NativeUtils.loadLibraryFromJar("/native/osx/x64/libbwt.dylib");
|
NativeUtils.loadLibraryFromJar("/native/osx/x64/libbwt_jni.dylib");
|
||||||
} else if(platform == org.controlsfx.tools.Platform.WINDOWS) {
|
} else if(platform == org.controlsfx.tools.Platform.WINDOWS) {
|
||||||
NativeUtils.loadLibraryFromJar("/native/windows/x64/bwt.dll");
|
NativeUtils.loadLibraryFromJar("/native/windows/x64/bwt.dll");
|
||||||
} else {
|
} else {
|
||||||
|
@ -43,9 +42,7 @@ public class Bwt {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start(CallbackNotifier callback) {
|
private void start(CallbackNotifier callback) {
|
||||||
List<String> descriptors = List.of("pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)");
|
start(Collections.emptyList(), null, null, null, callback);
|
||||||
Date now = new Date();
|
|
||||||
start(descriptors, (int)(now.getTime() / 1000), null, callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start(Collection<Wallet> wallets, CallbackNotifier callback) {
|
private void start(Collection<Wallet> wallets, CallbackNotifier callback) {
|
||||||
|
@ -57,10 +54,18 @@ public class Bwt {
|
||||||
outputDescriptors.add(changeOutputDescriptor.toString(false, false));
|
outputDescriptors.add(changeOutputDescriptor.toString(false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
int rescanSince = wallets.stream().filter(wallet -> wallet.getBirthDate() != null).mapToInt(wallet -> (int)(wallet.getBirthDate().getTime() / 1000)).min().orElse(0);
|
int rescanSince = wallets.stream().filter(wallet -> wallet.getBirthDate() != null).mapToInt(wallet -> (int)(wallet.getBirthDate().getTime() / 1000)).min().orElse(-1);
|
||||||
int gapLimit = wallets.stream().filter(wallet -> wallet.getGapLimit() > 0).mapToInt(Wallet::getGapLimit).max().orElse(Wallet.DEFAULT_LOOKAHEAD);
|
int gapLimit = wallets.stream().filter(wallet -> wallet.getGapLimit() > 0).mapToInt(Wallet::getGapLimit).max().orElse(Wallet.DEFAULT_LOOKAHEAD);
|
||||||
|
|
||||||
start(outputDescriptors, rescanSince, gapLimit, callback);
|
boolean forceRescan = false;
|
||||||
|
for(Wallet wallet :wallets) {
|
||||||
|
Date txBirthDate = wallet.getTransactions().values().stream().map(BlockTransactionHash::getDate).filter(Objects::nonNull).min(Date::compareTo).orElse(null);
|
||||||
|
if((wallet.getBirthDate() != null && txBirthDate != null && wallet.getBirthDate().before(txBirthDate)) || (txBirthDate == null && wallet.getStoredBlockHeight() == 0)) {
|
||||||
|
forceRescan = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start(outputDescriptors, rescanSince, forceRescan, gapLimit, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,13 +77,24 @@ public class Bwt {
|
||||||
* @param gapLimit desired gap limit beyond last used address
|
* @param gapLimit desired gap limit beyond last used address
|
||||||
* @param callback object receiving notifications
|
* @param callback object receiving notifications
|
||||||
*/
|
*/
|
||||||
private void start(List<String> outputDescriptors, Integer rescanSince, Integer gapLimit, CallbackNotifier callback) {
|
private void start(List<String> outputDescriptors, Integer rescanSince, Boolean forceRescan, Integer gapLimit, CallbackNotifier callback) {
|
||||||
BwtConfig bwtConfig = new BwtConfig();
|
BwtConfig bwtConfig = new BwtConfig();
|
||||||
bwtConfig.network = Network.get() == Network.MAINNET ? "bitcoin" : Network.get().getName();
|
bwtConfig.network = Network.get() == Network.MAINNET ? "bitcoin" : Network.get().getName();
|
||||||
bwtConfig.descriptors = outputDescriptors;
|
|
||||||
bwtConfig.rescanSince = rescanSince;
|
if(!outputDescriptors.isEmpty()) {
|
||||||
bwtConfig.gapLimit = gapLimit;
|
bwtConfig.descriptors = outputDescriptors;
|
||||||
|
bwtConfig.rescanSince = (rescanSince == null || rescanSince < 0 ? "now" : rescanSince);
|
||||||
|
bwtConfig.forceRescan = forceRescan;
|
||||||
|
bwtConfig.gapLimit = gapLimit;
|
||||||
|
} else {
|
||||||
|
bwtConfig.requireAddresses = false;
|
||||||
|
}
|
||||||
|
|
||||||
bwtConfig.verbose = log.isDebugEnabled() ? 2 : 0;
|
bwtConfig.verbose = log.isDebugEnabled() ? 2 : 0;
|
||||||
|
if(!log.isInfoEnabled()) {
|
||||||
|
bwtConfig.setupLogger = false;
|
||||||
|
}
|
||||||
|
|
||||||
bwtConfig.electrumAddr = "127.0.0.1:0";
|
bwtConfig.electrumAddr = "127.0.0.1:0";
|
||||||
bwtConfig.electrumSkipMerkle = true;
|
bwtConfig.electrumSkipMerkle = true;
|
||||||
|
|
||||||
|
@ -95,6 +111,7 @@ public class Bwt {
|
||||||
|
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
String jsonConfig = gson.toJson(bwtConfig);
|
String jsonConfig = gson.toJson(bwtConfig);
|
||||||
|
log.debug("Configuring bwt: " + jsonConfig);
|
||||||
|
|
||||||
NativeBwtDaemon.start(jsonConfig, callback);
|
NativeBwtDaemon.start(jsonConfig, callback);
|
||||||
}
|
}
|
||||||
|
@ -102,16 +119,26 @@ public class Bwt {
|
||||||
/**
|
/**
|
||||||
* Shut down the BWT daemon
|
* Shut down the BWT daemon
|
||||||
*
|
*
|
||||||
* @param shutdownPtr the pointer provided on startup
|
|
||||||
*/
|
*/
|
||||||
private void shutdown(long shutdownPtr) {
|
private void shutdown() {
|
||||||
|
if(shutdownPtr == null) {
|
||||||
|
terminating = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NativeBwtDaemon.shutdown(shutdownPtr);
|
NativeBwtDaemon.shutdown(shutdownPtr);
|
||||||
|
this.shutdownPtr = null;
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtShutdownEvent()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRunning() {
|
public boolean isRunning() {
|
||||||
return shutdownPtr != null;
|
return shutdownPtr != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTerminating() {
|
||||||
|
return terminating;
|
||||||
|
}
|
||||||
|
|
||||||
public ConnectionService getConnectionService(Collection<Wallet> wallets) {
|
public ConnectionService getConnectionService(Collection<Wallet> wallets) {
|
||||||
return wallets != null ? new ConnectionService(wallets) : new ConnectionService();
|
return wallets != null ? new ConnectionService(wallets) : new ConnectionService();
|
||||||
}
|
}
|
||||||
|
@ -146,11 +173,17 @@ public class Bwt {
|
||||||
public String xpubs;
|
public String xpubs;
|
||||||
|
|
||||||
@SerializedName("rescan_since")
|
@SerializedName("rescan_since")
|
||||||
public Integer rescanSince;
|
public Object rescanSince;
|
||||||
|
|
||||||
|
@SerializedName("force_rescan")
|
||||||
|
public Boolean forceRescan;
|
||||||
|
|
||||||
@SerializedName("gap_limit")
|
@SerializedName("gap_limit")
|
||||||
public Integer gapLimit;
|
public Integer gapLimit;
|
||||||
|
|
||||||
|
@SerializedName("initial_import_size")
|
||||||
|
public Integer initialImportSize;
|
||||||
|
|
||||||
@SerializedName("verbose")
|
@SerializedName("verbose")
|
||||||
public Integer verbose;
|
public Integer verbose;
|
||||||
|
|
||||||
|
@ -159,6 +192,12 @@ public class Bwt {
|
||||||
|
|
||||||
@SerializedName("electrum_skip_merkle")
|
@SerializedName("electrum_skip_merkle")
|
||||||
public Boolean electrumSkipMerkle;
|
public Boolean electrumSkipMerkle;
|
||||||
|
|
||||||
|
@SerializedName("require_addresses")
|
||||||
|
public Boolean requireAddresses;
|
||||||
|
|
||||||
|
@SerializedName("setup_logger")
|
||||||
|
public Boolean setupLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ConnectionService extends Service<Void> {
|
public final class ConnectionService extends Service<Void> {
|
||||||
|
@ -179,25 +218,37 @@ public class Bwt {
|
||||||
CallbackNotifier notifier = new CallbackNotifier() {
|
CallbackNotifier notifier = new CallbackNotifier() {
|
||||||
@Override
|
@Override
|
||||||
public void onBooting() {
|
public void onBooting() {
|
||||||
Platform.runLater(() -> EventManager.get().post(new BwtStatusEvent("Starting bwt")));
|
log.debug("Booting bwt");
|
||||||
|
if(!terminating) {
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtStatusEvent("Starting bwt")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSyncProgress(float progress, int tip) {
|
public void onSyncProgress(float progress, int tip) {
|
||||||
int percent = (int) (progress * 100.0);
|
int percent = (int) (progress * 100.0);
|
||||||
Platform.runLater(() -> EventManager.get().post(new BwtSyncStatusEvent("Syncing (" + percent + "%)", percent, tip)));
|
log.debug("Syncing " + percent + "%");
|
||||||
|
if(!terminating) {
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtSyncStatusEvent("Syncing" + (percent < 100 ? " (" + percent + "%)" : ""), percent, tip)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onScanProgress(float progress, int eta) {
|
public void onScanProgress(float progress, int eta) {
|
||||||
int percent = (int) (progress * 100.0);
|
int percent = (int) (progress * 100.0);
|
||||||
Date date = new Date((long) eta * 1000);
|
Date date = new Date((long) eta * 1000);
|
||||||
Platform.runLater(() -> EventManager.get().post(new BwtScanStatusEvent("Scanning (" + percent + "%)", percent, date)));
|
log.debug("Scanning " + percent + "%");
|
||||||
|
if(!terminating) {
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtScanStatusEvent("Scanning" + (percent < 100 ? " (" + percent + "%)" : ""), percent, date)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onElectrumReady(String addr) {
|
public void onElectrumReady(String addr) {
|
||||||
Platform.runLater(() -> EventManager.get().post(new BwtElectrumReadyStatusEvent("Electrum server ready", addr)));
|
log.debug("Electrum ready");
|
||||||
|
if(!terminating) {
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtElectrumReadyStatusEvent("Electrum server ready", addr)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -207,8 +258,14 @@ public class Bwt {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReady(long shutdownPtr) {
|
public void onReady(long shutdownPtr) {
|
||||||
|
log.debug("Bwt ready");
|
||||||
Bwt.this.shutdownPtr = shutdownPtr;
|
Bwt.this.shutdownPtr = shutdownPtr;
|
||||||
Platform.runLater(() -> EventManager.get().post(new BwtReadyStatusEvent("Server ready", shutdownPtr)));
|
if(terminating) {
|
||||||
|
Bwt.this.shutdown();
|
||||||
|
terminating = false;
|
||||||
|
} else {
|
||||||
|
Platform.runLater(() -> EventManager.get().post(new BwtReadyStatusEvent("Server ready", shutdownPtr)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -229,12 +286,7 @@ public class Bwt {
|
||||||
protected Task<Void> createTask() {
|
protected Task<Void> createTask() {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
protected Void call() {
|
protected Void call() {
|
||||||
if(shutdownPtr == null) {
|
Bwt.this.shutdown();
|
||||||
throw new IllegalStateException("Bwt has not been started");
|
|
||||||
}
|
|
||||||
|
|
||||||
Bwt.this.shutdown(shutdownPtr);
|
|
||||||
shutdownPtr = null;
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -795,6 +795,18 @@ public class ElectrumServer {
|
||||||
ElectrumServer electrumServer = new ElectrumServer();
|
ElectrumServer electrumServer = new ElectrumServer();
|
||||||
|
|
||||||
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
|
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
|
||||||
|
if(bwt.isTerminating()) {
|
||||||
|
try {
|
||||||
|
bwtStartLock.lock();
|
||||||
|
bwtStartCondition.await();
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
bwtStartLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!bwt.isRunning()) {
|
if(!bwt.isRunning()) {
|
||||||
Bwt.ConnectionService bwtConnectionService = bwt.getConnectionService(subscribe ? AppServices.get().getOpenWallets().keySet() : null);
|
Bwt.ConnectionService bwtConnectionService = bwt.getConnectionService(subscribe ? AppServices.get().getOpenWallets().keySet() : null);
|
||||||
bwtConnectionService.setOnFailed(workerStateEvent -> {
|
bwtConnectionService.setOnFailed(workerStateEvent -> {
|
||||||
|
@ -817,6 +829,7 @@ public class ElectrumServer {
|
||||||
}
|
}
|
||||||
} catch(InterruptedException e) {
|
} catch(InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
bwtStartLock.unlock();
|
bwtStartLock.unlock();
|
||||||
}
|
}
|
||||||
|
@ -894,6 +907,14 @@ public class ElectrumServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isConnecting() {
|
||||||
|
return isRunning() && Config.get().getServerType() == ServerType.BITCOIN_CORE && !bwt.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return isRunning() && (Config.get().getServerType() != ServerType.BITCOIN_CORE || bwt.isRunning());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean cancel() {
|
public boolean cancel() {
|
||||||
try {
|
try {
|
||||||
|
@ -907,16 +928,14 @@ public class ElectrumServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdownBwt() {
|
private void shutdownBwt() {
|
||||||
if(bwt.isRunning()) {
|
Bwt.DisconnectionService disconnectionService = bwt.getDisconnectionService();
|
||||||
Bwt.DisconnectionService disconnectionService = bwt.getDisconnectionService();
|
disconnectionService.setOnSucceeded(workerStateEvent -> {
|
||||||
disconnectionService.setOnSucceeded(workerStateEvent -> {
|
ElectrumServer.bwtElectrumServer = null;
|
||||||
ElectrumServer.bwtElectrumServer = null;
|
});
|
||||||
});
|
disconnectionService.setOnFailed(workerStateEvent -> {
|
||||||
disconnectionService.setOnFailed(workerStateEvent -> {
|
log.error("Failed to stop BWT", workerStateEvent.getSource().getException());
|
||||||
log.error("Failed to stop BWT", workerStateEvent.getSource().getException());
|
});
|
||||||
});
|
Platform.runLater(disconnectionService::start);
|
||||||
Platform.runLater(disconnectionService::start);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -954,6 +973,16 @@ public class ElectrumServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtShutdown(BwtShutdownEvent event) {
|
||||||
|
try {
|
||||||
|
bwtStartLock.lock();
|
||||||
|
bwtStartCondition.signal();
|
||||||
|
} finally {
|
||||||
|
bwtStartLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public StringProperty statusProperty() {
|
public StringProperty statusProperty() {
|
||||||
return statusProperty;
|
return statusProperty;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
|
||||||
|
|
||||||
Map<String, ScriptHashTx[]> result = new LinkedHashMap<>();
|
Map<String, ScriptHashTx[]> result = new LinkedHashMap<>();
|
||||||
for(String path : pathScriptHashes.keySet()) {
|
for(String path : pathScriptHashes.keySet()) {
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false, "Loading transactions for " + path));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Loading transactions for " + path));
|
||||||
try {
|
try {
|
||||||
ScriptHashTx[] scriptHashTxes = new RetryLogic<ScriptHashTx[]>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
|
ScriptHashTx[] scriptHashTxes = new RetryLogic<ScriptHashTx[]>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
|
||||||
client.createRequest().returnAs(ScriptHashTx[].class).method("blockchain.scripthash.get_history").id(path + "-" + idCounter.incrementAndGet()).params(pathScriptHashes.get(path)).execute());
|
client.createRequest().returnAs(ScriptHashTx[].class).method("blockchain.scripthash.get_history").id(path + "-" + idCounter.incrementAndGet()).params(pathScriptHashes.get(path)).execute());
|
||||||
|
@ -120,7 +120,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
|
||||||
|
|
||||||
Map<String, String> result = new LinkedHashMap<>();
|
Map<String, String> result = new LinkedHashMap<>();
|
||||||
for(String path : pathScriptHashes.keySet()) {
|
for(String path : pathScriptHashes.keySet()) {
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false, "Finding transactions for " + path));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Finding transactions for " + path));
|
||||||
try {
|
try {
|
||||||
String scriptHash = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
|
String scriptHash = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
|
||||||
client.createRequest().returnAs(String.class).method("blockchain.scripthash.subscribe").id(path + "-" + idCounter.incrementAndGet()).params(pathScriptHashes.get(path)).executeNullable());
|
client.createRequest().returnAs(String.class).method("blockchain.scripthash.subscribe").id(path + "-" + idCounter.incrementAndGet()).params(pathScriptHashes.get(path)).executeNullable());
|
||||||
|
@ -140,7 +140,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
|
||||||
|
|
||||||
Map<Integer, String> result = new LinkedHashMap<>();
|
Map<Integer, String> result = new LinkedHashMap<>();
|
||||||
for(Integer blockHeight : blockHeights) {
|
for(Integer blockHeight : blockHeights) {
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false, "Retrieving block at height " + blockHeight));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Retrieving block at height " + blockHeight));
|
||||||
try {
|
try {
|
||||||
String blockHeader = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
|
String blockHeader = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
|
||||||
client.createRequest().returnAs(String.class).method("blockchain.block.header").id(idCounter.incrementAndGet()).params(blockHeight).execute());
|
client.createRequest().returnAs(String.class).method("blockchain.block.header").id(idCounter.incrementAndGet()).params(blockHeight).execute());
|
||||||
|
@ -161,7 +161,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
|
||||||
|
|
||||||
Map<String, String> result = new LinkedHashMap<>();
|
Map<String, String> result = new LinkedHashMap<>();
|
||||||
for(String txid : txids) {
|
for(String txid : txids) {
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false, "Retrieving transaction [" + txid.substring(0, 6) + "]"));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Retrieving transaction [" + txid.substring(0, 6) + "]"));
|
||||||
try {
|
try {
|
||||||
String rawTxHex = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
|
String rawTxHex = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() ->
|
||||||
client.createRequest().returnAs(String.class).method("blockchain.transaction.get").id(idCounter.incrementAndGet()).params(txid).execute());
|
client.createRequest().returnAs(String.class).method("blockchain.transaction.get").id(idCounter.incrementAndGet()).params(txid).execute());
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.TextFieldValidator;
|
import com.sparrowwallet.sparrow.control.TextFieldValidator;
|
||||||
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
|
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
|
||||||
import com.sparrowwallet.sparrow.event.BwtStatusEvent;
|
import com.sparrowwallet.sparrow.event.BwtStatusEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.BwtSyncStatusEvent;
|
||||||
import com.sparrowwallet.sparrow.event.ConnectionEvent;
|
import com.sparrowwallet.sparrow.event.ConnectionEvent;
|
||||||
import com.sparrowwallet.sparrow.event.RequestDisconnectEvent;
|
import com.sparrowwallet.sparrow.event.RequestDisconnectEvent;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
@ -119,6 +120,8 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
|
|
||||||
private final ValidationSupport validationSupport = new ValidationSupport();
|
private final ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
|
||||||
|
private ElectrumServer.ConnectionService connectionService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initializeView(Config config) {
|
public void initializeView(Config config) {
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
|
@ -343,7 +346,11 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startElectrumConnection() {
|
private void startElectrumConnection() {
|
||||||
ElectrumServer.ConnectionService connectionService = new ElectrumServer.ConnectionService(false);
|
if(connectionService != null && connectionService.isRunning()) {
|
||||||
|
connectionService.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionService = new ElectrumServer.ConnectionService(false);
|
||||||
connectionService.setPeriod(Duration.hours(1));
|
connectionService.setPeriod(Duration.hours(1));
|
||||||
EventManager.get().register(connectionService);
|
EventManager.get().register(connectionService);
|
||||||
connectionService.statusProperty().addListener((observable, oldValue, newValue) -> {
|
connectionService.statusProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
@ -365,11 +372,11 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setFieldsEditable(boolean editable) {
|
private void setFieldsEditable(boolean editable) {
|
||||||
|
serverTypeToggleGroup.getToggles().forEach(toggle -> ((ToggleButton)toggle).setDisable(!editable));
|
||||||
|
|
||||||
coreHost.setEditable(editable);
|
coreHost.setEditable(editable);
|
||||||
corePort.setEditable(editable);
|
corePort.setEditable(editable);
|
||||||
for(Toggle toggle : coreAuthToggleGroup.getToggles()) {
|
coreAuthToggleGroup.getToggles().forEach(toggle -> ((ToggleButton)toggle).setDisable(!editable));
|
||||||
((ToggleButton)toggle).setDisable(!editable);
|
|
||||||
}
|
|
||||||
coreDataDir.setEditable(editable);
|
coreDataDir.setEditable(editable);
|
||||||
coreDataDirSelect.setDisable(!editable);
|
coreDataDirSelect.setDisable(!editable);
|
||||||
coreUser.setEditable(editable);
|
coreUser.setEditable(editable);
|
||||||
|
@ -611,6 +618,17 @@ public class ServerPreferencesController extends PreferencesDetailController {
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void bwtStatus(BwtStatusEvent event) {
|
public void bwtStatus(BwtStatusEvent event) {
|
||||||
testResults.appendText("\n" + event.getStatus());
|
if(!(event instanceof BwtSyncStatusEvent)) {
|
||||||
|
testResults.appendText("\n" + event.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
||||||
|
if(connectionService != null && connectionService.isRunning() && event.getProgress() < 100) {
|
||||||
|
testResults.appendText("\nThe connection to the Bitcoin Core node was successful, but it is still syncing to the the blockchain tip at " + event.getTip() + " blocks (" + event.getProgress() + "% completed)");
|
||||||
|
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE, null));
|
||||||
|
connectionService.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -326,7 +326,7 @@ public class TransactionController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchThisAndInputBlockTransactions(int indexStart, int indexEnd) {
|
private void fetchThisAndInputBlockTransactions(int indexStart, int indexEnd) {
|
||||||
if(AppServices.isOnline() && indexStart < getTransaction().getInputs().size()) {
|
if(AppServices.isConnected() && indexStart < getTransaction().getInputs().size()) {
|
||||||
Set<Sha256Hash> references = new HashSet<>();
|
Set<Sha256Hash> references = new HashSet<>();
|
||||||
if(getPSBT() == null) {
|
if(getPSBT() == null) {
|
||||||
references.add(getTransaction().getTxId());
|
references.add(getTransaction().getTxId());
|
||||||
|
@ -378,7 +378,7 @@ public class TransactionController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchOutputBlockTransactions(int indexStart, int indexEnd) {
|
private void fetchOutputBlockTransactions(int indexStart, int indexEnd) {
|
||||||
if(AppServices.isOnline() && getPSBT() == null && indexStart < getTransaction().getOutputs().size()) {
|
if(AppServices.isConnected() && getPSBT() == null && indexStart < getTransaction().getOutputs().size()) {
|
||||||
int maxIndex = Math.min(getTransaction().getOutputs().size(), indexEnd);
|
int maxIndex = Math.min(getTransaction().getOutputs().size(), indexEnd);
|
||||||
ElectrumServer.TransactionOutputsReferenceService transactionOutputsReferenceService = new ElectrumServer.TransactionOutputsReferenceService(getTransaction(), indexStart, maxIndex);
|
ElectrumServer.TransactionOutputsReferenceService transactionOutputsReferenceService = new ElectrumServer.TransactionOutputsReferenceService(getTransaction(), indexStart, maxIndex);
|
||||||
transactionOutputsReferenceService.setOnSucceeded(successEvent -> {
|
transactionOutputsReferenceService.setOnSucceeded(successEvent -> {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.control.DateStringConverter;
|
||||||
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
@ -27,12 +28,14 @@ public class AdvancedController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializeView(Wallet wallet) {
|
public void initializeView(Wallet wallet) {
|
||||||
|
birthDate.setConverter(new DateStringConverter());
|
||||||
if(wallet.getBirthDate() != null) {
|
if(wallet.getBirthDate() != null) {
|
||||||
birthDate.setValue(wallet.getBirthDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
|
birthDate.setValue(wallet.getBirthDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
|
||||||
}
|
}
|
||||||
birthDate.valueProperty().addListener((observable, oldValue, newValue) -> {
|
birthDate.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if(newValue != null) {
|
if(newValue != null) {
|
||||||
wallet.setBirthDate(Date.from(newValue.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
|
wallet.setBirthDate(Date.from(newValue.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()));
|
||||||
|
EventManager.get().post(new SettingsChangedEvent(wallet, SettingsChangedEvent.Type.BIRTH_DATE));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ public class ReceiveController extends WalletFormController implements Initializ
|
||||||
|
|
||||||
private void updateLastUsed() {
|
private void updateLastUsed() {
|
||||||
Set<BlockTransactionHashIndex> currentOutputs = currentEntry.getNode().getTransactionOutputs();
|
Set<BlockTransactionHashIndex> currentOutputs = currentEntry.getNode().getTransactionOutputs();
|
||||||
if(AppServices.isOnline() && currentOutputs.isEmpty()) {
|
if(AppServices.isConnected() && currentOutputs.isEmpty()) {
|
||||||
lastUsed.setText("Never");
|
lastUsed.setText("Never");
|
||||||
lastUsed.setGraphic(getUnusedGlyph());
|
lastUsed.setGraphic(getUnusedGlyph());
|
||||||
} else if(!currentOutputs.isEmpty()) {
|
} else if(!currentOutputs.isEmpty()) {
|
||||||
|
|
|
@ -14,10 +14,7 @@ import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.*;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.event.RequestOpenWalletsEvent;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
|
||||||
import com.sparrowwallet.sparrow.event.StorageEvent;
|
|
||||||
import com.sparrowwallet.sparrow.event.TimedEvent;
|
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
@ -28,17 +25,14 @@ import javafx.fxml.Initializable;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.controlsfx.control.RangeSlider;
|
import org.controlsfx.control.RangeSlider;
|
||||||
import org.controlsfx.tools.Borders;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import tornadofx.control.Fieldset;
|
import tornadofx.control.Fieldset;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class SettingsController extends WalletFormController implements Initializable {
|
public class SettingsController extends WalletFormController implements Initializable {
|
||||||
|
@ -329,6 +323,22 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void walletSettingsChanged(WalletSettingsChangedEvent event) {
|
||||||
|
updateBirthDate(event.getWalletFile(), event.getWallet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void walletHistoryChanged(WalletHistoryChangedEvent event) {
|
||||||
|
updateBirthDate(event.getWalletFile(), event.getWallet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBirthDate(File walletFile, Wallet wallet) {
|
||||||
|
if(walletFile.equals(walletForm.getWalletFile()) && !Objects.equals(wallet.getBirthDate(), walletForm.getWallet().getBirthDate())) {
|
||||||
|
walletForm.getWallet().setBirthDate(wallet.getBirthDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void saveWallet(boolean changePassword) {
|
private void saveWallet(boolean changePassword) {
|
||||||
ECKey existingPubKey = walletForm.getStorage().getEncryptionPubKey();
|
ECKey existingPubKey = walletForm.getStorage().getEncryptionPubKey();
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.sparrowwallet.sparrow.event.WalletSettingsChangedEvent;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class extends WalletForm to allow rollback of wallet changes. It is used exclusively by SettingsController for this purpose.
|
* This class extends WalletForm to allow rollback of wallet changes. It is used exclusively by SettingsController for this purpose.
|
||||||
|
@ -37,7 +38,7 @@ public class SettingsWalletForm extends WalletForm {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveAndRefresh() throws IOException {
|
public void saveAndRefresh() throws IOException {
|
||||||
boolean refreshAll = changesScriptHashes(wallet, walletCopy);
|
boolean refreshAll = isRefreshNecessary(wallet, walletCopy);
|
||||||
if(refreshAll) {
|
if(refreshAll) {
|
||||||
walletCopy.clearNodes();
|
walletCopy.clearNodes();
|
||||||
}
|
}
|
||||||
|
@ -50,7 +51,7 @@ public class SettingsWalletForm extends WalletForm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean changesScriptHashes(Wallet original, Wallet changed) {
|
private boolean isRefreshNecessary(Wallet original, Wallet changed) {
|
||||||
if(!original.isValid() || !changed.isValid()) {
|
if(!original.isValid() || !changed.isValid()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -86,6 +87,10 @@ public class SettingsWalletForm extends WalletForm {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!Objects.equals(original.getBirthDate(), changed.getBirthDate())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,12 +130,17 @@ public class TransactionsController extends WalletFormController implements Init
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
||||||
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false, event.getStatus()));
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), true, event.getStatus()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void bwtScanStatus(BwtScanStatusEvent event) {
|
public void bwtScanStatus(BwtScanStatusEvent event) {
|
||||||
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false, event.getStatus()));
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), true, event.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtShutdown(BwtShutdownEvent event) {
|
||||||
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|
|
@ -136,12 +136,17 @@ public class UtxosController extends WalletFormController implements Initializab
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
public void bwtSyncStatus(BwtSyncStatusEvent event) {
|
||||||
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false, event.getStatus()));
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), true, event.getStatus()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void bwtScanStatus(BwtScanStatusEvent event) {
|
public void bwtScanStatus(BwtScanStatusEvent event) {
|
||||||
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false, event.getStatus()));
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), true, event.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void bwtShutdown(BwtShutdownEvent event) {
|
||||||
|
walletHistoryStatus(new WalletHistoryStatusEvent(walletForm.getWallet(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|
|
@ -75,18 +75,18 @@ public class WalletForm {
|
||||||
|
|
||||||
public void refreshHistory(Integer blockHeight, WalletNode node) {
|
public void refreshHistory(Integer blockHeight, WalletNode node) {
|
||||||
Wallet previousWallet = wallet.copy();
|
Wallet previousWallet = wallet.copy();
|
||||||
if(wallet.isValid() && AppServices.isOnline()) {
|
if(wallet.isValid() && AppServices.isConnected()) {
|
||||||
log.debug(node == null ? wallet.getName() + " refreshing full wallet history" : wallet.getName() + " requesting node wallet history for " + node.getDerivationPath());
|
log.debug(node == null ? wallet.getName() + " refreshing full wallet history" : wallet.getName() + " requesting node wallet history for " + node.getDerivationPath());
|
||||||
ElectrumServer.TransactionHistoryService historyService = new ElectrumServer.TransactionHistoryService(wallet, getWalletTransactionNodes(node));
|
ElectrumServer.TransactionHistoryService historyService = new ElectrumServer.TransactionHistoryService(wallet, getWalletTransactionNodes(node));
|
||||||
historyService.setOnSucceeded(workerStateEvent -> {
|
historyService.setOnSucceeded(workerStateEvent -> {
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false));
|
||||||
updateWallet(previousWallet, blockHeight);
|
updateWallet(previousWallet, blockHeight);
|
||||||
});
|
});
|
||||||
historyService.setOnFailed(workerStateEvent -> {
|
historyService.setOnFailed(workerStateEvent -> {
|
||||||
log.error("Error retrieving wallet history", workerStateEvent.getSource().getException());
|
log.error("Error retrieving wallet history", workerStateEvent.getSource().getException());
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, workerStateEvent.getSource().getException().getMessage()));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, workerStateEvent.getSource().getException().getMessage()));
|
||||||
});
|
});
|
||||||
EventManager.get().post(new WalletHistoryStatusEvent(wallet, false));
|
EventManager.get().post(new WalletHistoryStatusEvent(wallet, true));
|
||||||
historyService.start();
|
historyService.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ public class WalletForm {
|
||||||
|
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
if(!historyChangedNodes.isEmpty()) {
|
if(!historyChangedNodes.isEmpty()) {
|
||||||
Platform.runLater(() -> EventManager.get().post(new WalletHistoryChangedEvent(wallet, historyChangedNodes)));
|
Platform.runLater(() -> EventManager.get().post(new WalletHistoryChangedEvent(wallet, storage, historyChangedNodes)));
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -168,3 +168,7 @@
|
||||||
-fx-border-width: 1px 0px 1px 0px;
|
-fx-border-width: 1px 0px 1px 0px;
|
||||||
-fx-border-color: derive(-fx-background, -10%);
|
-fx-border-color: derive(-fx-background, -10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.root .placeholder .hyperlink {
|
||||||
|
-fx-text-fill: derive(#1e88cf, 20%);
|
||||||
|
}
|
||||||
|
|
BIN
src/main/resources/native/osx/x64/libbwt.dylib → src/main/resources/native/osx/x64/libbwt_jni.dylib
Normal file → Executable file
BIN
src/main/resources/native/osx/x64/libbwt.dylib → src/main/resources/native/osx/x64/libbwt_jni.dylib
Normal file → Executable file
Binary file not shown.
Loading…
Reference in a new issue