add support for configuring server aliases, and switching servers via the tools menu

This commit is contained in:
Craig Raw 2022-09-12 15:44:47 +02:00
parent bacbdb848b
commit 1f67692727
17 changed files with 649 additions and 140 deletions

View file

@ -182,6 +182,9 @@ public class AppController implements Initializable {
@FXML @FXML
private MenuItem showPayNym; private MenuItem showPayNym;
@FXML
private Menu switchServer;
@FXML @FXML
private CheckMenuItem preventSleep; private CheckMenuItem preventSleep;
private static final BooleanProperty preventSleepProperty = new SimpleBooleanProperty(); private static final BooleanProperty preventSleepProperty = new SimpleBooleanProperty();
@ -367,6 +370,7 @@ public class AppController implements Initializable {
findMixingPartner.setDisable(exportWallet.isDisable() || getSelectedWalletForm() == null || !SorobanServices.canWalletMix(getSelectedWalletForm().getWallet()) || !newValue); findMixingPartner.setDisable(exportWallet.isDisable() || getSelectedWalletForm() == null || !SorobanServices.canWalletMix(getSelectedWalletForm().getWallet()) || !newValue);
}); });
configureSwitchServer();
setServerType(Config.get().getServerType()); setServerType(Config.get().getServerType());
serverToggle.setSelected(isConnected()); serverToggle.setSelected(isConnected());
serverToggle.setDisable(Config.get().getServerType() == null); serverToggle.setDisable(Config.get().getServerType() == null);
@ -408,8 +412,7 @@ public class AppController implements Initializable {
WelcomeDialog welcomeDialog = new WelcomeDialog(); WelcomeDialog welcomeDialog = new WelcomeDialog();
Optional<Mode> optionalMode = welcomeDialog.showAndWait(); Optional<Mode> optionalMode = welcomeDialog.showAndWait();
if(optionalMode.isPresent() && optionalMode.get().equals(Mode.ONLINE)) { if(optionalMode.isPresent() && optionalMode.get().equals(Mode.ONLINE)) {
PreferencesDialog preferencesDialog = new PreferencesDialog(PreferenceGroup.SERVER); openPreferences(PreferenceGroup.SERVER);
preferencesDialog.showAndWait();
} }
} }
@ -902,7 +905,7 @@ public class AppController implements Initializable {
private String getServerToggleTooltipText(Integer currentBlockHeight) { private String getServerToggleTooltipText(Integer currentBlockHeight) {
if(AppServices.isConnected()) { if(AppServices.isConnected()) {
return "Connected to " + Config.get().getServerAddress() + (currentBlockHeight != null ? " at height " + currentBlockHeight : "") + return "Connected to " + Config.get().getServerDisplayName() + (currentBlockHeight != null ? " at height " + currentBlockHeight : "") +
(Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? "\nWarning! You are connected to a public server and sharing your transaction data with it.\nFor better privacy, consider using your own Bitcoin Core node or private Electrum server." : ""); (Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? "\nWarning! You are connected to a public server and sharing your transaction data with it.\nFor better privacy, consider using your own Bitcoin Core node or private Electrum server." : "");
} }
@ -1284,13 +1287,17 @@ public class AppController implements Initializable {
} }
public void openPreferences(ActionEvent event) { public void openPreferences(ActionEvent event) {
PreferencesDialog preferencesDialog = new PreferencesDialog(); openPreferences(PreferenceGroup.GENERAL);
preferencesDialog.showAndWait();
} }
public void openServerPreferences(ActionEvent event) { public void openServerPreferences(ActionEvent event) {
PreferencesDialog preferencesDialog = new PreferencesDialog(PreferenceGroup.SERVER); openPreferences(PreferenceGroup.SERVER);
}
private void openPreferences(PreferenceGroup preferenceGroup) {
PreferencesDialog preferencesDialog = new PreferencesDialog(preferenceGroup);
preferencesDialog.showAndWait(); preferencesDialog.showAndWait();
configureSwitchServer();
} }
public void signVerifyMessage(ActionEvent event) { public void signVerifyMessage(ActionEvent event) {
@ -1996,6 +2003,48 @@ public class AppController implements Initializable {
return contextMenu; return contextMenu;
} }
private void configureSwitchServer() {
switchServer.getItems().clear();
Config config = Config.get();
if(config.getServerType() == ServerType.BITCOIN_CORE && config.getRecentCoreServers() != null && config.getRecentCoreServers().size() > 1) {
for(Server server : config.getRecentCoreServers()) {
switchServer.getItems().add(getSwitchServerMenuItem(ServerType.BITCOIN_CORE, server));
}
} else if(config.getServerType() == ServerType.ELECTRUM_SERVER && config.getRecentElectrumServers() != null && config.getRecentElectrumServers().size() > 1) {
for(Server server : config.getRecentElectrumServers()) {
switchServer.getItems().add(getSwitchServerMenuItem(ServerType.ELECTRUM_SERVER, server));
}
}
switchServer.setVisible(!switchServer.getItems().isEmpty());
}
private CheckMenuItem getSwitchServerMenuItem(ServerType serverType, Server server) {
CheckMenuItem checkMenuItem = new CheckMenuItem(server.getDisplayName());
boolean selected = (serverType == ServerType.BITCOIN_CORE ? server.equals(Config.get().getCoreServer()) : server.equals(Config.get().getElectrumServer()));
checkMenuItem.setSelected(selected);
checkMenuItem.setOnAction(event -> {
if(!selected) {
boolean online = onlineProperty().get();
onlineProperty().set(false);
if(serverType == ServerType.BITCOIN_CORE) {
Config.get().setCoreServer(server);
} else if(serverType == ServerType.ELECTRUM_SERVER) {
Config.get().setElectrumServer(server);
}
Platform.runLater(() -> {
onlineProperty().set(online);
configureSwitchServer();
});
} else {
checkMenuItem.setSelected(true);
}
});
return checkMenuItem;
}
public void setServerType(ServerType serverType) { public void setServerType(ServerType serverType) {
if(serverType == ServerType.PUBLIC_ELECTRUM_SERVER && !serverToggle.getStyleClass().contains("public-server")) { if(serverType == ServerType.PUBLIC_ELECTRUM_SERVER && !serverToggle.getStyleClass().contains("public-server")) {
serverToggle.getStyleClass().add("public-server"); serverToggle.getStyleClass().add("public-server");
@ -2424,11 +2473,12 @@ public class AppController implements Initializable {
@Subscribe @Subscribe
public void connection(ConnectionEvent event) { public void connection(ConnectionEvent event) {
String status = "Connected to " + Config.get().getServerAddress() + " at height " + event.getBlockHeight(); String status = "Connected to " + Config.get().getServerDisplayName() + " at height " + event.getBlockHeight();
statusUpdated(new StatusEvent(status)); statusUpdated(new StatusEvent(status));
setServerToggleTooltip(event.getBlockHeight()); setServerToggleTooltip(event.getBlockHeight());
serverToggleStopAnimation(); serverToggleStopAnimation();
setTorIcon(); setTorIcon();
configureSwitchServer();
} }
@Subscribe @Subscribe

View file

@ -203,7 +203,7 @@ public class AppServices {
private void restartServices() { private void restartServices() {
Config config = Config.get(); Config config = Config.get();
if(config.hasServerAddress()) { if(config.hasServer()) {
restartService(connectionService); restartService(connectionService);
} }
@ -268,7 +268,7 @@ public class AppServices {
connectionService.setOnRunning(workerStateEvent -> { connectionService.setOnRunning(workerStateEvent -> {
connectionService.setDelay(Duration.ZERO); connectionService.setDelay(Duration.ZERO);
if(!ElectrumServer.isConnected()) { if(!ElectrumServer.isConnected()) {
EventManager.get().post(new ConnectionStartEvent(Config.get().getServerAddress())); EventManager.get().post(new ConnectionStartEvent(Config.get().getServerDisplayName()));
} }
}); });
connectionService.setOnSucceeded(successEvent -> { connectionService.setOnSucceeded(successEvent -> {
@ -1157,8 +1157,10 @@ public class AppServices {
@Subscribe @Subscribe
public void requestConnect(RequestConnectEvent event) { public void requestConnect(RequestConnectEvent event) {
if(Config.get().hasServer()) {
onlineProperty.set(true); onlineProperty.set(true);
} }
}
@Subscribe @Subscribe
public void requestDisconnect(RequestDisconnectEvent event) { public void requestDisconnect(RequestDisconnectEvent event) {
@ -1209,7 +1211,7 @@ public class AppServices {
public void walletHistoryFailed(WalletHistoryFailedEvent event) { public void walletHistoryFailed(WalletHistoryFailedEvent event) {
if(Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER && isConnected()) { if(Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER && isConnected()) {
onlineProperty.set(false); onlineProperty.set(false);
log.warn("Failed to fetch wallet history from " + Config.get().getServerAddress() + ", reconnecting to another server..."); log.warn("Failed to fetch wallet history from " + Config.get().getServerDisplayName() + ", reconnecting to another server...");
Config.get().changePublicServer(); Config.get().changePublicServer();
onlineProperty.set(true); onlineProperty.set(true);
} }

View file

@ -77,7 +77,7 @@ public class MainApp extends Application {
} else if(Network.get() == Network.MAINNET) { } else if(Network.get() == Network.MAINNET) {
Config.get().setServerType(ServerType.PUBLIC_ELECTRUM_SERVER); Config.get().setServerType(ServerType.PUBLIC_ELECTRUM_SERVER);
List<PublicElectrumServer> servers = PublicElectrumServer.getServers(); List<PublicElectrumServer> servers = PublicElectrumServer.getServers();
Config.get().setPublicElectrumServer(servers.get(new Random().nextInt(servers.size())).getUrl()); Config.get().setPublicElectrumServer(servers.get(new Random().nextInt(servers.size())).getServer());
} }
} }
} }

View file

@ -67,6 +67,7 @@ public class FontAwesome5 extends GlyphFont {
SNOWFLAKE('\uf2dc'), SNOWFLAKE('\uf2dc'),
SORT_NUMERIC_DOWN('\uf162'), SORT_NUMERIC_DOWN('\uf162'),
SUN('\uf185'), SUN('\uf185'),
TAG('\uf02b'),
THEATER_MASKS('\uf630'), THEATER_MASKS('\uf630'),
TIMES_CIRCLE('\uf057'), TIMES_CIRCLE('\uf057'),
TOGGLE_OFF('\uf204'), TOGGLE_OFF('\uf204'),

View file

@ -53,14 +53,14 @@ public class Config {
private Boolean hdCapture; private Boolean hdCapture;
private String webcamDevice; private String webcamDevice;
private ServerType serverType; private ServerType serverType;
private String publicElectrumServer; private Server publicElectrumServer;
private String coreServer; private Server coreServer;
private List<String> recentCoreServers; private List<Server> recentCoreServers;
private CoreAuthType coreAuthType; private CoreAuthType coreAuthType;
private File coreDataDir; private File coreDataDir;
private String coreAuth; private String coreAuth;
private String electrumServer; private Server electrumServer;
private List<String> recentElectrumServers; private List<Server> recentElectrumServers;
private File electrumServerCert; private File electrumServerCert;
private boolean useProxy; private boolean useProxy;
private String proxyServer; private String proxyServer;
@ -77,6 +77,8 @@ public class Config {
GsonBuilder gsonBuilder = new GsonBuilder(); GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(File.class, new FileSerializer()); gsonBuilder.registerTypeAdapter(File.class, new FileSerializer());
gsonBuilder.registerTypeAdapter(File.class, new FileDeserializer()); gsonBuilder.registerTypeAdapter(File.class, new FileDeserializer());
gsonBuilder.registerTypeAdapter(Server.class, new ServerSerializer());
gsonBuilder.registerTypeAdapter(Server.class, new ServerDeserializer());
return gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create(); return gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create();
} }
@ -362,14 +364,18 @@ public class Config {
flush(); flush();
} }
public boolean hasServerAddress() { public boolean hasServer() {
return getServerAddress() != null && !getServerAddress().isEmpty(); return getServer() != null;
} }
public String getServerAddress() { public Server getServer() {
return getServerType() == ServerType.BITCOIN_CORE ? getCoreServer() : (getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? getPublicElectrumServer() : getElectrumServer()); return getServerType() == ServerType.BITCOIN_CORE ? getCoreServer() : (getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? getPublicElectrumServer() : getElectrumServer());
} }
public String getServerDisplayName() {
return getServer() == null ? "server" : getServer().getDisplayName();
}
public boolean requiresInternalTor() { public boolean requiresInternalTor() {
if(isUseProxy()) { if(isUseProxy()) {
return false; return false;
@ -379,57 +385,71 @@ public class Config {
} }
public boolean requiresTor() { public boolean requiresTor() {
if(!hasServerAddress()) { if(!hasServer()) {
return false; return false;
} }
Protocol protocol = Protocol.getProtocol(getServerAddress()); return getServer().isOnionAddress();
if(protocol == null) {
return false;
} }
return protocol.isOnionAddress(protocol.getServerHostAndPort(getServerAddress())); public Server getPublicElectrumServer() {
}
public String getPublicElectrumServer() {
return publicElectrumServer; return publicElectrumServer;
} }
public void setPublicElectrumServer(String publicElectrumServer) { public void setPublicElectrumServer(Server publicElectrumServer) {
this.publicElectrumServer = publicElectrumServer; this.publicElectrumServer = publicElectrumServer;
flush(); flush();
} }
public void changePublicServer() { public void changePublicServer() {
List<String> otherServers = PublicElectrumServer.getServers().stream().map(PublicElectrumServer::getUrl).filter(url -> !url.equals(getPublicElectrumServer())).collect(Collectors.toList()); List<Server> otherServers = PublicElectrumServer.getServers().stream().map(PublicElectrumServer::getServer).filter(server -> !server.equals(getPublicElectrumServer())).collect(Collectors.toList());
if(!otherServers.isEmpty()) { if(!otherServers.isEmpty()) {
setPublicElectrumServer(otherServers.get(new Random().nextInt(otherServers.size()))); setPublicElectrumServer(otherServers.get(new Random().nextInt(otherServers.size())));
} }
} }
public String getCoreServer() { public Server getCoreServer() {
return coreServer; return coreServer;
} }
public void setCoreServer(String coreServer) { public void setCoreServer(Server coreServer) {
this.coreServer = coreServer; this.coreServer = coreServer;
flush(); flush();
} }
public List<String> getRecentCoreServers() { public List<Server> getRecentCoreServers() {
return recentCoreServers; return recentCoreServers == null ? new ArrayList<>() : recentCoreServers;
} }
public void addRecentCoreServer(String coreServer) { public boolean addRecentCoreServer(Server coreServer) {
if(recentCoreServers == null) { if(recentCoreServers == null) {
recentCoreServers = new ArrayList<>(); recentCoreServers = new ArrayList<>();
} }
if(!recentCoreServers.contains(coreServer)) { int index = getRecentCoreServers().indexOf(coreServer);
recentCoreServers.stream().filter(url -> Objects.equals(Protocol.getHost(url), Protocol.getHost(coreServer))) if(index < 0) {
.findFirst().ifPresent(existingUrl -> recentCoreServers.remove(existingUrl)); recentCoreServers.removeIf(server -> server.getHost().equals(coreServer.getHost()) && server.getAlias() == null);
recentCoreServers.add(coreServer); recentCoreServers.add(coreServer);
flush(); flush();
return true;
}
return false;
}
public void removeRecentCoreServer(Server server) {
int index = getRecentCoreServers().indexOf(server);
if(index >= 0) {
recentCoreServers.remove(index);
flush();
}
}
public void setCoreServerAlias(Server server) {
int index = getRecentCoreServers().indexOf(server);
if(index >= 0) {
recentCoreServers.set(index, server);
flush();
} }
} }
@ -460,37 +480,59 @@ public class Config {
flush(); flush();
} }
public String getElectrumServer() { public Server getElectrumServer() {
return electrumServer; return electrumServer;
} }
public void setElectrumServer(String electrumServer) { public void setElectrumServer(Server electrumServer) {
this.electrumServer = electrumServer; this.electrumServer = electrumServer;
flush(); flush();
} }
public List<String> getRecentElectrumServers() { public List<Server> getRecentElectrumServers() {
return recentElectrumServers; return recentElectrumServers == null ? new ArrayList<>() : recentElectrumServers;
} }
public void addRecentServer() { public boolean addRecentServer() {
if(serverType == ServerType.BITCOIN_CORE && coreServer != null) { if(serverType == ServerType.BITCOIN_CORE && coreServer != null) {
addRecentCoreServer(coreServer); return addRecentCoreServer(coreServer);
} else if(serverType == ServerType.ELECTRUM_SERVER && electrumServer != null) { } else if(serverType == ServerType.ELECTRUM_SERVER && electrumServer != null) {
addRecentElectrumServer(electrumServer); return addRecentElectrumServer(electrumServer);
}
} }
public void addRecentElectrumServer(String electrumServer) { return false;
}
public boolean addRecentElectrumServer(Server electrumServer) {
if(recentElectrumServers == null) { if(recentElectrumServers == null) {
recentElectrumServers = new ArrayList<>(); recentElectrumServers = new ArrayList<>();
} }
if(!recentElectrumServers.contains(electrumServer)) { int index = getRecentElectrumServers().indexOf(electrumServer);
recentElectrumServers.stream().filter(url -> Objects.equals(Protocol.getHost(url), Protocol.getHost(electrumServer))) if(index < 0) {
.findFirst().ifPresent(existingUrl -> recentElectrumServers.remove(existingUrl)); recentElectrumServers.removeIf(server -> server.getHost().equals(electrumServer.getHost()) && server.getAlias() == null);
recentElectrumServers.add(electrumServer); recentElectrumServers.add(electrumServer);
flush(); flush();
return true;
}
return false;
}
public void removeRecentElectrumServer(Server server) {
int index = getRecentElectrumServers().indexOf(server);
if(index >= 0) {
recentElectrumServers.remove(index);
flush();
}
}
public void setElectrumServerAlias(Server server) {
int index = getRecentElectrumServers().indexOf(server);
if(index >= 0) {
recentElectrumServers.set(index, server);
flush();
} }
} }
@ -595,4 +637,18 @@ public class Config {
return new File(json.getAsJsonPrimitive().getAsString()); return new File(json.getAsJsonPrimitive().getAsString());
} }
} }
private static class ServerSerializer implements JsonSerializer<Server> {
@Override
public JsonElement serialize(Server src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}
private static class ServerDeserializer implements JsonDeserializer<Server> {
@Override
public Server deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return Server.fromString(json.getAsJsonPrimitive().getAsString());
}
}
} }

View file

@ -0,0 +1,99 @@
package com.sparrowwallet.sparrow.io;
import com.google.common.net.HostAndPort;
import com.sparrowwallet.sparrow.net.Protocol;
import java.util.Arrays;
public class Server {
private final String url;
private final String alias;
public Server(String url) {
this(url, null);
}
public Server(String url, String alias) {
if(url == null) {
throw new IllegalArgumentException("Url cannot be null");
}
if(url.isEmpty()) {
throw new IllegalArgumentException("Url cannot be empty");
}
if(Protocol.getProtocol(url) == null) {
throw new IllegalArgumentException("Unknown protocol for url " + url + ", must be one of " + Arrays.toString(Protocol.values()));
}
if(Protocol.getHost(url) == null) {
throw new IllegalArgumentException("Cannot determine host for url " + url);
}
if(alias != null && alias.isEmpty()) {
throw new IllegalArgumentException("Server alias cannot be an empty string");
}
this.url = url;
this.alias = alias;
}
public String getUrl() {
return url;
}
public Protocol getProtocol() {
return Protocol.getProtocol(url);
}
public HostAndPort getHostAndPort() {
return getProtocol().getServerHostAndPort(url);
}
public String getHost() {
return getHostAndPort().getHost();
}
public boolean isOnionAddress() {
return Protocol.isOnionAddress(getHostAndPort());
}
public String getAlias() {
return alias;
}
public String getDisplayName() {
return alias == null ? url : alias;
}
public String toString() {
return url + (alias == null ? "" : "|" + alias);
}
public static Server fromString(String server) {
String[] parts = server.split("\\|");
if(parts.length >= 2) {
return new Server(parts[0], parts[1]);
}
return new Server(parts[0], null);
}
@Override
public boolean equals(Object o) {
if(this == o) {
return true;
}
if(o == null || getClass() != o.getClass()) {
return false;
}
Server server = (Server)o;
return url.equals(server.url);
}
@Override
public int hashCode() {
return url.hashCode();
}
}

View file

@ -143,8 +143,8 @@ public class Bwt {
bwtConfig.electrumSkipMerkle = true; bwtConfig.electrumSkipMerkle = true;
Config config = Config.get(); Config config = Config.get();
bwtConfig.bitcoindUrl = config.getCoreServer(); if(config.getCoreServer() != null) {
if(bwtConfig.bitcoindUrl != null) { bwtConfig.bitcoindUrl = config.getCoreServer().getUrl();
try { try {
HostAndPort hostAndPort = Protocol.HTTP.getServerHostAndPort(bwtConfig.bitcoindUrl); HostAndPort hostAndPort = Protocol.HTTP.getServerHostAndPort(bwtConfig.bitcoindUrl);
if(hostAndPort.getHost().endsWith(".local")) { if(hostAndPort.getHost().endsWith(".local")) {
@ -308,7 +308,7 @@ public class Bwt {
Bwt.this.shutdown(); Bwt.this.shutdown();
terminating = false; terminating = false;
} else { } else {
Platform.runLater(() -> EventManager.get().post(new BwtBootStatusEvent("Connecting to Bitcoin Core node at " + Config.get().getCoreServer() + "..."))); Platform.runLater(() -> EventManager.get().post(new BwtBootStatusEvent("Connecting to Bitcoin Core node " + Config.get().getServerDisplayName() + "...")));
} }
} }

View file

@ -1,6 +1,5 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.github.arteam.simplejsonrpc.client.Transport;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.KeyPurpose;
@ -15,6 +14,7 @@ import com.sparrowwallet.sparrow.AppServices;
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 com.sparrowwallet.sparrow.io.Server;
import com.sparrowwallet.sparrow.paynym.PayNym; import com.sparrowwallet.sparrow.paynym.PayNym;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
@ -51,7 +51,7 @@ public class ElectrumServer {
private static final Map<String, List<String>> subscribedScriptHashes = Collections.synchronizedMap(new HashMap<>()); private static final Map<String, List<String>> subscribedScriptHashes = Collections.synchronizedMap(new HashMap<>());
private static String previousServerAddress; private static Server previousServer;
private static Map<String, String> retrievedScriptHashes = Collections.synchronizedMap(new HashMap<>()); private static Map<String, String> retrievedScriptHashes = Collections.synchronizedMap(new HashMap<>());
@ -59,14 +59,14 @@ public class ElectrumServer {
private static ElectrumServerRpc electrumServerRpc = new SimpleElectrumServerRpc(); private static ElectrumServerRpc electrumServerRpc = new SimpleElectrumServerRpc();
private static String bwtElectrumServer; private static Server bwtElectrumServer;
private static final Pattern RPC_WALLET_LOADING_PATTERN = Pattern.compile(".*\"(Wallet loading failed:[^\"]*)\".*"); private static final Pattern RPC_WALLET_LOADING_PATTERN = Pattern.compile(".*\"(Wallet loading failed:[^\"]*)\".*");
private static synchronized CloseableTransport getTransport() throws ServerException { private static synchronized CloseableTransport getTransport() throws ServerException {
if(transport == null) { if(transport == null) {
try { try {
String electrumServer = null; Server electrumServer = null;
File electrumServerCert = null; File electrumServerCert = null;
String proxyServer = null; String proxyServer = null;
@ -78,8 +78,8 @@ public class ElectrumServer {
throw new ServerConfigException("Could not connect to Bitcoin Core RPC"); throw new ServerConfigException("Could not connect to Bitcoin Core RPC");
} }
electrumServer = bwtElectrumServer; electrumServer = bwtElectrumServer;
if(previousServerAddress != null && previousServerAddress.contains(Bwt.ELECTRUM_HOST)) { if(previousServer != null && previousServer.getUrl().contains(Bwt.ELECTRUM_HOST)) {
previousServerAddress = bwtElectrumServer; previousServer = bwtElectrumServer;
} }
} else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) { } else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) {
electrumServer = Config.get().getElectrumServer(); electrumServer = Config.get().getElectrumServer();
@ -95,33 +95,30 @@ public class ElectrumServer {
throw new ServerConfigException("Electrum server certificate file not found"); throw new ServerConfigException("Electrum server certificate file not found");
} }
Protocol protocol = Protocol.getProtocol(electrumServer); Protocol protocol = electrumServer.getProtocol();
if(protocol == null) {
throw new ServerConfigException("Electrum server URL must start with " + Protocol.TCP.toUrlString() + " or " + Protocol.SSL.toUrlString());
}
//If changing server, don't rely on previous transaction history //If changing server, don't rely on previous transaction history
if(previousServerAddress != null && !electrumServer.equals(previousServerAddress)) { if(previousServer != null && !electrumServer.equals(previousServer)) {
retrievedScriptHashes.clear(); retrievedScriptHashes.clear();
retrievedTransactions.clear(); retrievedTransactions.clear();
} }
previousServerAddress = electrumServer; previousServer = electrumServer;
HostAndPort server = protocol.getServerHostAndPort(electrumServer); HostAndPort hostAndPort = electrumServer.getHostAndPort();
boolean localNetworkAddress = !protocol.isOnionAddress(server) && IpAddressMatcher.isLocalNetworkAddress(server.getHost()); boolean localNetworkAddress = !protocol.isOnionAddress(hostAndPort) && IpAddressMatcher.isLocalNetworkAddress(hostAndPort.getHost());
if(!localNetworkAddress && Config.get().isUseProxy() && proxyServer != null && !proxyServer.isBlank()) { if(!localNetworkAddress && Config.get().isUseProxy() && proxyServer != null && !proxyServer.isBlank()) {
HostAndPort proxy = HostAndPort.fromString(proxyServer); HostAndPort proxy = HostAndPort.fromString(proxyServer);
if(electrumServerCert != null) { if(electrumServerCert != null) {
transport = protocol.getTransport(server, electrumServerCert, proxy); transport = protocol.getTransport(hostAndPort, electrumServerCert, proxy);
} else { } else {
transport = protocol.getTransport(server, proxy); transport = protocol.getTransport(hostAndPort, proxy);
} }
} else { } else {
if(electrumServerCert != null) { if(electrumServerCert != null) {
transport = protocol.getTransport(server, electrumServerCert); transport = protocol.getTransport(hostAndPort, electrumServerCert);
} else { } else {
transport = protocol.getTransport(server); transport = protocol.getTransport(hostAndPort);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -1251,7 +1248,7 @@ public class ElectrumServer {
@Subscribe @Subscribe
public void bwtElectrumReadyStatus(BwtElectrumReadyStatusEvent event) { public void bwtElectrumReadyStatus(BwtElectrumReadyStatusEvent event) {
if(this.isRunning()) { if(this.isRunning()) {
ElectrumServer.bwtElectrumServer = Protocol.TCP.toUrlString(HostAndPort.fromString(event.getElectrumAddr())); ElectrumServer.bwtElectrumServer = new Server(Protocol.TCP.toUrlString(HostAndPort.fromString(event.getElectrumAddr())));
} }
} }

View file

@ -97,7 +97,7 @@ public final class IpAddressMatcher {
return InetAddress.getByName(address); return InetAddress.getByName(address);
} }
catch (UnknownHostException e) { catch (UnknownHostException e) {
throw new IllegalArgumentException("Failed to parse address" + address, e); throw new IllegalArgumentException("Failed to parse address: " + address, e);
} }
} }

View file

@ -1,7 +1,7 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.github.arteam.simplejsonrpc.client.Transport;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.sparrowwallet.sparrow.io.Server;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -121,6 +121,14 @@ public enum Protocol {
return host != null && host.toLowerCase(Locale.ROOT).endsWith(TorService.TOR_ADDRESS_SUFFIX); return host != null && host.toLowerCase(Locale.ROOT).endsWith(TorService.TOR_ADDRESS_SUFFIX);
} }
public static boolean isOnionAddress(Server server) {
if(server != null) {
return isOnionAddress(server.getHostAndPort());
}
return false;
}
public static boolean isOnionAddress(HostAndPort server) { public static boolean isOnionAddress(HostAndPort server) {
return isOnionHost(server.getHost()); return isOnionHost(server.getHost());
} }

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.sparrow.io.Server;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -15,23 +16,21 @@ public enum PublicElectrumServer {
TESTNET_ARANGUREN_ORG("testnet.aranguren.org", "ssl://testnet.aranguren.org:51002", Network.TESTNET); TESTNET_ARANGUREN_ORG("testnet.aranguren.org", "ssl://testnet.aranguren.org:51002", Network.TESTNET);
PublicElectrumServer(String name, String url, Network network) { PublicElectrumServer(String name, String url, Network network) {
this.name = name; this.server = new Server(url, name);
this.url = url;
this.network = network; this.network = network;
} }
public static final List<Network> SUPPORTED_NETWORKS = List.of(Network.MAINNET, Network.TESTNET); public static final List<Network> SUPPORTED_NETWORKS = List.of(Network.MAINNET, Network.TESTNET);
private final String name; private final Server server;
private final String url;
private final Network network; private final Network network;
public String getName() { public Server getServer() {
return name; return server;
} }
public String getUrl() { public String getUrl() {
return url; return server.getUrl();
} }
public Network getNetwork() { public Network getNetwork() {
@ -46,10 +45,10 @@ public enum PublicElectrumServer {
return SUPPORTED_NETWORKS.contains(Network.get()); return SUPPORTED_NETWORKS.contains(Network.get());
} }
public static PublicElectrumServer fromUrl(String url) { public static PublicElectrumServer fromServer(Server server) {
for(PublicElectrumServer server : values()) { for(PublicElectrumServer publicServer : values()) {
if(server.url.equals(url)) { if(publicServer.getServer().equals(server)) {
return server; return publicServer;
} }
} }
@ -58,6 +57,6 @@ public enum PublicElectrumServer {
@Override @Override
public String toString() { public String toString() {
return name; return server.getAlias();
} }
} }

View file

@ -111,7 +111,8 @@ public class TcpOverTlsTransport extends TcpTransport {
protected boolean shouldSaveCertificate() { protected boolean shouldSaveCertificate() {
//Avoid saving the certificates for blockstream.info public servers - they change too often and encourage approval complacency //Avoid saving the certificates for blockstream.info public servers - they change too often and encourage approval complacency
if(PublicElectrumServer.BLOCKSTREAM_INFO.getName().equals(server.getHost()) || PublicElectrumServer.ELECTRUM_BLOCKSTREAM_INFO.getName().equals(server.getHost())) { if(PublicElectrumServer.BLOCKSTREAM_INFO.getServer().getHost().equals(server.getHost())
|| PublicElectrumServer.ELECTRUM_BLOCKSTREAM_INFO.getServer().getHost().equals(server.getHost())) {
return false; return false;
} }

View file

@ -100,6 +100,10 @@ public class TcpTransport implements CloseableTransport, TimeoutCounter {
log.trace("Sending to electrum server at " + server + ": " + request); log.trace("Sending to electrum server at " + server + ": " + request);
} }
if(socket == null) {
throw new IllegalStateException("Socket connection has not been established.");
}
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))); PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
out.println(request); out.println(request);
out.flush(); out.flush();
@ -207,7 +211,7 @@ public class TcpTransport implements CloseableTransport, TimeoutCounter {
String response = readLine(in); String response = readLine(in);
if(response == null) { if(response == null) {
throw new IOException("Could not connect to server at " + Config.get().getServerAddress()); throw new IOException("Could not connect to server" + (Config.get().hasServer() ? " at " + Config.get().getServer().getUrl() : ""));
} }
return response; return response;

View file

@ -0,0 +1,214 @@
package com.sparrowwallet.sparrow.preferences;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Server;
import com.sparrowwallet.sparrow.net.ServerType;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.event.Event;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.util.converter.DefaultStringConverter;
import org.controlsfx.glyphfont.Glyph;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
public class ServerAliasDialog extends Dialog<Server> {
private final ServerType serverType;
private final TableView<ServerEntry> serverTable;
public ServerAliasDialog(ServerType serverType) {
this.serverType = serverType;
final DialogPane dialogPane = new ServerAliasDialogPane();
setDialogPane(dialogPane);
AppServices.setStageIcon(dialogPane.getScene().getWindow());
setTitle("Server Aliases");
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
dialogPane.setHeaderText("Configure aliases for recently connected servers.\nNew servers are added to this list on successful connections.");
Image image = new Image("/image/sparrow-small.png");
dialogPane.setGraphic(new ImageView(image));
serverTable = new TableView<>();
serverTable.setPlaceholder(new Label("No servers added yet"));
TableColumn<ServerEntry, String> urlColumn = new TableColumn<>("URL");
urlColumn.setMinWidth(300);
urlColumn.setCellValueFactory((TableColumn.CellDataFeatures<ServerEntry, String> param) -> {
return new ReadOnlyObjectWrapper<>(param.getValue().getUrl());
});
TableColumn<ServerEntry, String> aliasColumn = new TableColumn<>("Alias");
aliasColumn.setCellValueFactory((TableColumn.CellDataFeatures<ServerEntry, String> param) -> {
return param.getValue().labelProperty();
});
aliasColumn.setCellFactory(value -> new AliasCell());
aliasColumn.setGraphic(getTagGlyph());
serverTable.getColumns().add(urlColumn);
serverTable.getColumns().add(aliasColumn);
serverTable.setEditable(true);
serverTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
List<Server> servers = serverType == ServerType.BITCOIN_CORE ? Config.get().getRecentCoreServers() : Config.get().getRecentElectrumServers();
List<ServerEntry> serverEntries = servers.stream().map(server -> new ServerEntry(serverType, server)).collect(Collectors.toList());
serverTable.setItems(FXCollections.observableArrayList(serverEntries));
StackPane stackPane = new StackPane();
stackPane.getChildren().add(serverTable);
dialogPane.setContent(stackPane);
ButtonType selectButtonType = new ButtonType("Select", ButtonBar.ButtonData.APPLY);
ButtonType deleteButtonType = new ButtonType("Delete", ButtonBar.ButtonData.LEFT);
dialogPane.getButtonTypes().addAll(deleteButtonType, ButtonType.CLOSE, selectButtonType);
Button selectButton = (Button)dialogPane.lookupButton(selectButtonType);
Button deleteButton = (Button)dialogPane.lookupButton(deleteButtonType);
selectButton.setDefaultButton(true);
selectButton.setDisable(true);
deleteButton.setDisable(true);
serverTable.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
selectButton.setDisable(newValue == null);
deleteButton.setDisable(newValue == null);
});
setResultConverter(buttonType -> buttonType == selectButtonType ? serverTable.getSelectionModel().getSelectedItem().getServer() : null);
dialogPane.setPrefWidth(680);
dialogPane.setPrefHeight(500);
AppServices.setStageIcon(dialogPane.getScene().getWindow());
AppServices.moveToActiveWindowScreen(this);
}
private static Glyph getTagGlyph() {
Glyph glyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.TAG);
glyph.setFontSize(12);
return glyph;
}
private class ServerAliasDialogPane extends DialogPane {
@Override
protected Node createButton(ButtonType buttonType) {
Node button;
if(buttonType.getButtonData() == ButtonBar.ButtonData.LEFT) {
Button deleteButton = new Button(buttonType.getText());
final ButtonBar.ButtonData buttonData = buttonType.getButtonData();
ButtonBar.setButtonData(deleteButton, buttonData);
deleteButton.setOnAction(event -> {
ServerEntry serverEntry = serverTable.getSelectionModel().getSelectedItem();
if(serverEntry != null) {
serverTable.getItems().remove(serverEntry);
if(serverType == ServerType.BITCOIN_CORE) {
Config.get().removeRecentCoreServer(serverEntry.getServer());
} else {
Config.get().removeRecentElectrumServer(serverEntry.getServer());
}
}
});
button = deleteButton;
} else {
button = super.createButton(buttonType);
}
return button;
}
}
private static class ServerEntry {
private final Server server;
private final StringProperty labelProperty = new SimpleStringProperty();
public ServerEntry(ServerType serverType, Server server) {
this.server = server;
labelProperty.set(server.getAlias());
labelProperty.addListener((observable, oldValue, newValue) -> {
String alias = newValue;
if(alias != null && alias.isEmpty()) {
alias = null;
}
Server newServer = new Server(server.getUrl(), alias);
if(serverType == ServerType.BITCOIN_CORE) {
Config.get().setCoreServerAlias(newServer);
} else {
Config.get().setElectrumServerAlias(newServer);
}
});
}
public String getUrl() {
return server.getHostAndPort().toString();
}
public StringProperty labelProperty() {
return labelProperty;
}
public Server getServer() {
return new Server(server.getUrl(), labelProperty.get());
}
}
private static class AliasCell extends TextFieldTableCell<ServerEntry, String> {
public AliasCell() {
super(new DefaultStringConverter());
}
@Override
public void commitEdit(String label) {
if(label != null) {
label = label.trim();
}
// This block is necessary to support commit on losing focus, because
// the baked-in mechanism sets our editing state to false before we can
// intercept the loss of focus. The default commitEdit(...) method
// simply bails if we are not editing...
if (!isEditing() && !label.equals(getItem())) {
TableView<ServerEntry> table = getTableView();
if(table != null) {
TableColumn<ServerEntry, String> column = getTableColumn();
TableColumn.CellEditEvent<ServerEntry, String> event = new TableColumn.CellEditEvent<>(
table, new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), label
);
Event.fireEvent(column, event);
}
}
super.commitEdit(label);
}
@Override
public void startEdit() {
super.startEdit();
try {
Field f = getClass().getSuperclass().getDeclaredField("textField");
f.setAccessible(true);
TextField textField = (TextField)f.get(this);
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (!isNowFocused) {
commitEdit(getConverter().fromString(textField.getText()));
setText(getConverter().fromString(textField.getText()));
}
});
} catch (Exception e) {
//ignore
}
}
}
}

View file

@ -12,11 +12,13 @@ import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Server;
import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.net.*; import com.sparrowwallet.sparrow.net.*;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.text.Font; import javafx.scene.text.Font;
@ -51,6 +53,8 @@ import java.util.Random;
public class ServerPreferencesController extends PreferencesDetailController { public class ServerPreferencesController extends PreferencesDetailController {
private static final Logger log = LoggerFactory.getLogger(ServerPreferencesController.class); private static final Logger log = LoggerFactory.getLogger(ServerPreferencesController.class);
private static final Server MANAGE_ALIASES_SERVER = new Server("tcp://localhost", "Manage Aliases...");
@FXML @FXML
private ToggleGroup serverTypeToggleGroup; private ToggleGroup serverTypeToggleGroup;
@ -79,7 +83,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
private Form coreForm; private Form coreForm;
@FXML @FXML
private ComboBox<String> recentCoreServers; private ComboBox<Server> recentCoreServers;
@FXML @FXML
private ComboBoxTextField coreHost; private ComboBoxTextField coreHost;
@ -121,7 +125,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
private Form electrumForm; private Form electrumForm;
@FXML @FXML
private ComboBox<String> recentElectrumServers; private ComboBox<Server> recentElectrumServers;
@FXML @FXML
private ComboBoxTextField electrumHost; private ComboBoxTextField electrumHost;
@ -267,26 +271,52 @@ public class ServerPreferencesController extends PreferencesDetailController {
} }
}); });
recentCoreServers.setConverter(new UrlHostConverter()); recentCoreServers.setCellFactory(value -> new ServerCell());
recentCoreServers.setItems(FXCollections.observableList(Config.get().getRecentCoreServers() == null ? new ArrayList<>() : Config.get().getRecentCoreServers())); recentCoreServers.setItems(getObservableServerList(Config.get().getRecentCoreServers()));
recentCoreServers.prefWidthProperty().bind(coreHost.widthProperty()); recentCoreServers.prefWidthProperty().bind(coreHost.widthProperty());
recentCoreServers.valueProperty().addListener((observable, oldValue, newValue) -> { recentCoreServers.valueProperty().addListener((observable, oldValue, newValue) -> {
if(newValue != null && Protocol.getProtocol(newValue) != null) { if(newValue != null) {
HostAndPort hostAndPort = Protocol.getProtocol(newValue).getServerHostAndPort(newValue); if(newValue == MANAGE_ALIASES_SERVER) {
ServerAliasDialog serverAliasDialog = new ServerAliasDialog(ServerType.BITCOIN_CORE);
Optional<Server> optServer = serverAliasDialog.showAndWait();
recentCoreServers.setItems(getObservableServerList(Config.get().getRecentCoreServers()));
Server selectedServer = optServer.orElseGet(() -> Config.get().getCoreServer());
Platform.runLater(() -> recentCoreServers.setValue(selectedServer));
} else if(newValue.getHostAndPort() != null) {
HostAndPort hostAndPort = newValue.getHostAndPort();
corePort.setText(hostAndPort.hasPort() ? Integer.toString(hostAndPort.getPort()) : "");
if(newValue.getAlias() != null) {
coreHost.setText(newValue.getAlias());
} else {
coreHost.setText(hostAndPort.getHost()); coreHost.setText(hostAndPort.getHost());
corePort.setText(Integer.toString(hostAndPort.getPort())); }
coreHost.positionCaret(coreHost.getText().length());
}
} }
}); });
recentElectrumServers.setConverter(new UrlHostConverter()); recentElectrumServers.setCellFactory(value -> new ServerCell());
recentElectrumServers.setItems(FXCollections.observableList(Config.get().getRecentElectrumServers() == null ? new ArrayList<>() : Config.get().getRecentElectrumServers())); recentElectrumServers.setItems(getObservableServerList(Config.get().getRecentElectrumServers()));
recentElectrumServers.prefWidthProperty().bind(electrumHost.widthProperty()); recentElectrumServers.prefWidthProperty().bind(electrumHost.widthProperty());
recentElectrumServers.valueProperty().addListener((observable, oldValue, newValue) -> { recentElectrumServers.valueProperty().addListener((observable, oldValue, newValue) -> {
if(newValue != null && Protocol.getProtocol(newValue) != null) { if(newValue != null) {
HostAndPort hostAndPort = Protocol.getProtocol(newValue).getServerHostAndPort(newValue); if(newValue == MANAGE_ALIASES_SERVER) {
ServerAliasDialog serverAliasDialog = new ServerAliasDialog(ServerType.ELECTRUM_SERVER);
Optional<Server> optServer = serverAliasDialog.showAndWait();
recentElectrumServers.setItems(getObservableServerList(Config.get().getRecentElectrumServers()));
Server selectedServer = optServer.orElseGet(() -> Config.get().getElectrumServer());
Platform.runLater(() -> recentElectrumServers.setValue(selectedServer));
} else if(newValue.getHostAndPort() != null) {
HostAndPort hostAndPort = newValue.getHostAndPort();
electrumPort.setText(hostAndPort.hasPort() ? Integer.toString(hostAndPort.getPort()) : "");
electrumUseSsl.setSelected(newValue.getProtocol() == Protocol.SSL);
if(newValue.getAlias() != null) {
electrumHost.setText(newValue.getAlias());
} else {
electrumHost.setText(hostAndPort.getHost()); electrumHost.setText(hostAndPort.getHost());
electrumPort.setText(Integer.toString(hostAndPort.getPort())); }
electrumUseSsl.setSelected(Protocol.getProtocol(newValue) == Protocol.SSL); electrumHost.positionCaret(electrumHost.getText().length());
}
} }
}); });
@ -339,7 +369,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
setTestResultsFont(); setTestResultsFont();
testConnection.setOnAction(event -> { testConnection.setOnAction(event -> {
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null)); testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null));
testResults.setText("Connecting to " + config.getServerAddress() + "..."); testResults.setText("Connecting " + (config.hasServer() ? "to " + config.getServer().getUrl() : "") + "...");
if(Config.get().requiresInternalTor() && Tor.getDefault() == null) { if(Config.get().requiresInternalTor() && Tor.getDefault() == null) {
startTor(); startTor();
@ -358,7 +388,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
testConnection.setVisible(true); testConnection.setVisible(true);
}); });
PublicElectrumServer configPublicElectrumServer = PublicElectrumServer.fromUrl(config.getPublicElectrumServer()); PublicElectrumServer configPublicElectrumServer = PublicElectrumServer.fromServer(config.getPublicElectrumServer());
if(configPublicElectrumServer == null && PublicElectrumServer.supportedNetwork()) { if(configPublicElectrumServer == null && PublicElectrumServer.supportedNetwork()) {
List<PublicElectrumServer> servers = PublicElectrumServer.getServers(); List<PublicElectrumServer> servers = PublicElectrumServer.getServers();
if(!servers.isEmpty()) { if(!servers.isEmpty()) {
@ -368,16 +398,16 @@ public class ServerPreferencesController extends PreferencesDetailController {
publicElectrumServer.setValue(configPublicElectrumServer); publicElectrumServer.setValue(configPublicElectrumServer);
} }
String coreServer = config.getCoreServer(); Server coreServer = config.getCoreServer();
if(coreServer != null) { if(coreServer != null) {
Protocol protocol = Protocol.getProtocol(coreServer); HostAndPort hostAndPort = coreServer.getHostAndPort();
Server server = config.getRecentCoreServers().stream().filter(coreServer::equals).findFirst().orElse(null);
if(protocol != null) { if(server != null) {
HostAndPort server = protocol.getServerHostAndPort(coreServer); coreHost.setLeft(getGlyph(FontAwesome5.Glyph.TAG, null));
coreHost.setText(server.getHost());
if(server.hasPort()) {
corePort.setText(Integer.toString(server.getPort()));
} }
coreHost.setText(server == null || server.getAlias() == null ? hostAndPort.getHost() : server.getAlias());
if(hostAndPort.hasPort()) {
corePort.setText(Integer.toString(hostAndPort.getPort()));
} }
} else { } else {
coreHost.setText("127.0.0.1"); coreHost.setText("127.0.0.1");
@ -396,21 +426,22 @@ public class ServerPreferencesController extends PreferencesDetailController {
} }
} }
String electrumServer = config.getElectrumServer(); Server electrumServer = config.getElectrumServer();
if(electrumServer != null) { if(electrumServer != null) {
Protocol protocol = Protocol.getProtocol(electrumServer); Protocol protocol = electrumServer.getProtocol();
if(protocol != null) {
boolean ssl = protocol.equals(Protocol.SSL); boolean ssl = protocol.equals(Protocol.SSL);
electrumUseSsl.setSelected(ssl); electrumUseSsl.setSelected(ssl);
electrumCertificate.setDisable(!ssl); electrumCertificate.setDisable(!ssl);
electrumCertificateSelect.setDisable(!ssl); electrumCertificateSelect.setDisable(!ssl);
HostAndPort server = protocol.getServerHostAndPort(electrumServer); HostAndPort hostAndPort = electrumServer.getHostAndPort();
electrumHost.setText(server.getHost()); Server server = config.getRecentElectrumServers().stream().filter(electrumServer::equals).findFirst().orElse(null);
if(server.hasPort()) { if(server != null) {
electrumPort.setText(Integer.toString(server.getPort())); electrumHost.setLeft(getGlyph(FontAwesome5.Glyph.TAG, null));
} }
electrumHost.setText(server == null || server.getAlias() == null ? hostAndPort.getHost() : server.getAlias());
if(hostAndPort.hasPort()) {
electrumPort.setText(Integer.toString(hostAndPort.getPort()));
} }
} }
@ -449,7 +480,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
torService.setOnSucceeded(workerStateEvent -> { torService.setOnSucceeded(workerStateEvent -> {
Tor.setDefault(torService.getValue()); Tor.setDefault(torService.getValue());
torService.cancel(); torService.cancel();
testResults.appendText("\nTor running, connecting to " + Config.get().getServerAddress() + "..."); testResults.appendText("\nTor running, connecting to " + Config.get().getServer().getUrl() + "...");
startElectrumConnection(); startElectrumConnection();
}); });
torService.setOnFailed(workerStateEvent -> { torService.setOnFailed(workerStateEvent -> {
@ -495,6 +526,13 @@ public class ServerPreferencesController extends PreferencesDetailController {
Config.get().setMode(Mode.ONLINE); Config.get().setMode(Mode.ONLINE);
connectionService.cancel(); connectionService.cancel();
useProxyOriginal = null; useProxyOriginal = null;
if(Config.get().addRecentServer()) {
if(Config.get().getServerType() == ServerType.BITCOIN_CORE) {
recentCoreServers.setItems(getObservableServerList(Config.get().getRecentCoreServers()));
} else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) {
recentElectrumServers.setItems(getObservableServerList(Config.get().getRecentElectrumServers()));
}
}
}); });
connectionService.setOnFailed(workerStateEvent -> { connectionService.setOnFailed(workerStateEvent -> {
EventManager.get().unregister(connectionService); EventManager.get().unregister(connectionService);
@ -668,24 +706,32 @@ public class ServerPreferencesController extends PreferencesDetailController {
@NotNull @NotNull
private ChangeListener<PublicElectrumServer> getPublicElectrumServerListener(Config config) { private ChangeListener<PublicElectrumServer> getPublicElectrumServerListener(Config config) {
return (observable, oldValue, newValue) -> { return (observable, oldValue, newValue) -> {
config.setPublicElectrumServer(newValue.getUrl()); config.setPublicElectrumServer(newValue.getServer());
}; };
} }
@NotNull @NotNull
private ChangeListener<String> getBitcoinCoreListener(Config config) { private ChangeListener<String> getBitcoinCoreListener(Config config) {
return (observable, oldValue, newValue) -> { return (observable, oldValue, newValue) -> {
Server existingServer = config.getRecentCoreServers().stream().filter(server -> coreHost.getText().equals(server.getAlias())).findFirst().orElse(null);
coreHost.setLeft(existingServer == null ? null : getGlyph(FontAwesome5.Glyph.TAG, null));
setCoreServerInConfig(config); setCoreServerInConfig(config);
}; };
} }
private void setCoreServerInConfig(Config config) { private void setCoreServerInConfig(Config config) {
Server existingServer = config.getRecentCoreServers().stream().filter(server -> coreHost.getText().equals(server.getAlias())).findFirst().orElse(null);
if(existingServer != null) {
config.setCoreServer(existingServer);
return;
}
String hostAsString = getHost(coreHost.getText()); String hostAsString = getHost(coreHost.getText());
Integer portAsInteger = getPort(corePort.getText()); Integer portAsInteger = getPort(corePort.getText());
if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) { if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) {
config.setCoreServer(Protocol.HTTP.toUrlString(hostAsString, portAsInteger)); config.setCoreServer(new Server(Protocol.HTTP.toUrlString(hostAsString, portAsInteger)));
} else if(hostAsString != null) { } else if(hostAsString != null) {
config.setCoreServer(Protocol.HTTP.toUrlString(hostAsString)); config.setCoreServer(new Server(Protocol.HTTP.toUrlString(hostAsString)));
} }
} }
@ -699,17 +745,25 @@ public class ServerPreferencesController extends PreferencesDetailController {
@NotNull @NotNull
private ChangeListener<String> getElectrumServerListener(Config config) { private ChangeListener<String> getElectrumServerListener(Config config) {
return (observable, oldValue, newValue) -> { return (observable, oldValue, newValue) -> {
Server existingServer = config.getRecentElectrumServers().stream().filter(server -> electrumHost.getText().equals(server.getAlias())).findFirst().orElse(null);
electrumHost.setLeft(existingServer == null ? null : getGlyph(FontAwesome5.Glyph.TAG, null));
setElectrumServerInConfig(config); setElectrumServerInConfig(config);
}; };
} }
private void setElectrumServerInConfig(Config config) { private void setElectrumServerInConfig(Config config) {
Server existingServer = config.getRecentElectrumServers().stream().filter(server -> electrumHost.getText().equals(server.getAlias())).findFirst().orElse(null);
if(existingServer != null) {
config.setElectrumServer(existingServer);
return;
}
String hostAsString = getHost(electrumHost.getText()); String hostAsString = getHost(electrumHost.getText());
Integer portAsInteger = getPort(electrumPort.getText()); Integer portAsInteger = getPort(electrumPort.getText());
if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) { if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) {
config.setElectrumServer(getProtocol().toUrlString(hostAsString, portAsInteger)); config.setElectrumServer(new Server(getProtocol().toUrlString(hostAsString, portAsInteger)));
} else if(hostAsString != null) { } else if(hostAsString != null) {
config.setElectrumServer(getProtocol().toUrlString(hostAsString)); config.setElectrumServer(new Server(getProtocol().toUrlString(hostAsString)));
} }
} }
@ -777,7 +831,7 @@ public class ServerPreferencesController extends PreferencesDetailController {
} }
} }
private Glyph getGlyph(FontAwesome5.Glyph glyphName, String styleClass) { private static Glyph getGlyph(FontAwesome5.Glyph glyphName, String styleClass) {
Glyph glyph = new Glyph(FontAwesome5.FONT_NAME, glyphName); Glyph glyph = new Glyph(FontAwesome5.FONT_NAME, glyphName);
glyph.setFontSize(12); glyph.setFontSize(12);
if(styleClass != null) { if(styleClass != null) {
@ -813,6 +867,12 @@ public class ServerPreferencesController extends PreferencesDetailController {
} }
} }
private ObservableList<Server> getObservableServerList(List<Server> servers) {
ObservableList<Server> serverObservableList = FXCollections.observableList(new ArrayList<>(servers));
serverObservableList.add(MANAGE_ALIASES_SERVER);
return serverObservableList;
}
@Subscribe @Subscribe
public void bwtStatus(BwtStatusEvent event) { public void bwtStatus(BwtStatusEvent event) {
if(!(event instanceof BwtSyncStatusEvent)) { if(!(event instanceof BwtSyncStatusEvent)) {
@ -844,15 +904,28 @@ public class ServerPreferencesController extends PreferencesDetailController {
}); });
} }
private static class UrlHostConverter extends StringConverter<String> { private static class ServerCell extends ListCell<Server> {
@Override @Override
public String toString(String serverUrl) { protected void updateItem(Server server, boolean empty) {
return serverUrl == null || Protocol.getProtocol(serverUrl) == null ? "" : Protocol.getProtocol(serverUrl).getServerHostAndPort(serverUrl).getHost(); super.updateItem(server, empty);
} if(server == null || empty) {
setText("");
setGraphic(null);
} else {
String serverAlias = server.getAlias();
@Override if(server == MANAGE_ALIASES_SERVER) {
public String fromString(String string) { setText(serverAlias);
return null; setStyle("-fx-font-style: italic");
setGraphic(null);
} else if(serverAlias != null) {
setText(serverAlias);
setGraphic(getGlyph(FontAwesome5.Glyph.TAG, null));
} else {
setText(server.getHost());
setGraphic(null);
}
}
} }
} }
} }

View file

@ -117,6 +117,7 @@
<MenuItem fx:id="findMixingPartner" mnemonicParsing="false" text="Find Mix Partner" onAction="#findMixingPartner"/> <MenuItem fx:id="findMixingPartner" mnemonicParsing="false" text="Find Mix Partner" onAction="#findMixingPartner"/>
<MenuItem fx:id="showPayNym" mnemonicParsing="false" text="Show PayNym" onAction="#showPayNym"/> <MenuItem fx:id="showPayNym" mnemonicParsing="false" text="Show PayNym" onAction="#showPayNym"/>
<SeparatorMenuItem /> <SeparatorMenuItem />
<Menu fx:id="switchServer" text="Switch Server"/>
<MenuItem styleClass="osxHide,windowsHide" mnemonicParsing="false" text="Install Udev Rules" onAction="#installUdevRules"/> <MenuItem styleClass="osxHide,windowsHide" mnemonicParsing="false" text="Install Udev Rules" onAction="#installUdevRules"/>
<CheckMenuItem fx:id="preventSleep" mnemonicParsing="false" text="Prevent Computer Sleep" onAction="#preventSleep"/> <CheckMenuItem fx:id="preventSleep" mnemonicParsing="false" text="Prevent Computer Sleep" onAction="#preventSleep"/>
<MenuItem fx:id="restart" mnemonicParsing="false" text="Restart" onAction="#restart" /> <MenuItem fx:id="restart" mnemonicParsing="false" text="Restart" onAction="#restart" />

View file

@ -52,3 +52,7 @@
#electrumUseSsl { #electrumUseSsl {
-fx-padding: 4 0 2 0; -fx-padding: 4 0 2 0;
} }
#coreHost .left-pane, #electrumHost .left-pane {
-fx-padding: 0 3 0 6;
}