/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.settings.network;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.Clock;
import io.bitsquare.common.UserThread;
import io.bitsquare.gui.common.model.Activatable;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.network.Statistic;
import io.bitsquare.user.Preferences;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import org.bitcoinj.core.Peer;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import javax.inject.Inject;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@FxmlView
public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activatable> {
@FXML
InputTextField btcNodes;
@FXML
TextField onionAddress, totalTraffic;
@FXML
TextArea bitcoinPeersTextArea;
@FXML
Label bitcoinPeersLabel, p2PPeersLabel;
@FXML
CheckBox useTorForBtcJCheckBox;
@FXML
TableView<P2pNetworkListItem> tableView;
@FXML
TableColumn<P2pNetworkListItem, String> onionAddressColumn, connectionTypeColumn, creationDateColumn,
roundTripTimeColumn, sentBytesColumn, receivedBytesColumn, peerTypeColumn;
private final WalletService walletService;
private final Preferences preferences;
private Clock clock;
private final BSFormatter formatter;
private final P2PService p2PService;
private Subscription numP2PPeersSubscription;
private Subscription bitcoinPeersSubscription;
private Subscription nodeAddressSubscription;
private ObservableList<P2pNetworkListItem> networkListItems = FXCollections.observableArrayList();
private final SortedList<P2pNetworkListItem> sortedList = new SortedList<>(networkListItems);
private ChangeListener<Boolean> btcNodesFocusListener;
private String btcNodesPreFocusText;
@Inject
public NetworkSettingsView(WalletService walletService, P2PService p2PService, Preferences preferences, Clock clock,
BSFormatter formatter) {
super();
this.walletService = walletService;
this.p2PService = p2PService;
this.preferences = preferences;
this.clock = clock;
this.formatter = formatter;
}
public void initialize() {
GridPane.setMargin(bitcoinPeersLabel, new Insets(4, 0, 0, 0));
GridPane.setValignment(bitcoinPeersLabel, VPos.TOP);
GridPane.setMargin(p2PPeersLabel, new Insets(4, 0, 0, 0));
GridPane.setValignment(p2PPeersLabel, VPos.TOP);
bitcoinPeersTextArea.setPrefRowCount(10);
tableView.setMinHeight(230);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No connections are available"));
tableView.getSortOrder().add(creationDateColumn);
creationDateColumn.setSortType(TableColumn.SortType.ASCENDING);
//TODO sorting needs other NetworkStatisticListItem as columns type
/* creationDateColumn.setComparator((o1, o2) ->
o1.statistic.getCreationDate().compareTo(o2.statistic.getCreationDate()));
sentBytesColumn.setComparator((o1, o2) ->
((Integer) o1.statistic.getSentBytes()).compareTo(((Integer) o2.statistic.getSentBytes())));
receivedBytesColumn.setComparator((o1, o2) ->
((Integer) o1.statistic.getReceivedBytes()).compareTo(((Integer) o2.statistic.getReceivedBytes())));*/
}
@Override
public void activate() {
useTorForBtcJCheckBox.setSelected(preferences.getUseTorForBitcoinJ());
useTorForBtcJCheckBox.setOnAction(event -> {
boolean selected = useTorForBtcJCheckBox.isSelected();
if (selected != preferences.getUseTorForBitcoinJ()) {
new Popup().information("You need to restart the application to apply that change.\n" +
"Do you want to do that now?")
.actionButtonText("Apply and shut down")
.onAction(() -> {
preferences.setUseTorForBitcoinJ(selected);
UserThread.runAfter(BitsquareApp.shutDownHandler::run, 500, TimeUnit.MILLISECONDS);
})
.closeButtonText("Cancel")
.onClose(() -> useTorForBtcJCheckBox.setSelected(!selected))
.show();
}
});
bitcoinPeersSubscription = EasyBind.subscribe(walletService.connectedPeersProperty(), connectedPeers -> updateBitcoinPeersTextArea());
nodeAddressSubscription = EasyBind.subscribe(p2PService.getNetworkNode().nodeAddressProperty(),
nodeAddress -> onionAddress.setText(nodeAddress == null ? "Not known yet..." : p2PService.getAddress().getFullAddress()));
numP2PPeersSubscription = EasyBind.subscribe(p2PService.getNumConnectedPeers(), numPeers -> updateP2PTable());
totalTraffic.textProperty().bind(EasyBind.combine(Statistic.totalSentBytesProperty(), Statistic.totalReceivedBytesProperty(),
(sent, received) -> "Sent: " + formatter.formatBytes((long) sent) + ", received: " + formatter.formatBytes((long) received)));
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
btcNodes.setText(preferences.getBitcoinNodes());
btcNodes.setPromptText("Add comma separated IP addresses");
btcNodesFocusListener = (observable, oldValue, newValue) -> {
if (newValue) {
btcNodesPreFocusText = btcNodes.getText();
}
if (oldValue && !newValue && !btcNodesPreFocusText.equals(btcNodes.getText())) {
new Popup().information("You need to restart the application to apply that change.\n" +
"Do you want to do that now?")
.actionButtonText("Apply and shut down")
.onAction(() -> {
if (btcNodes.getText().isEmpty()) {
preferences.setBitcoinNodes("");
} else {
preferences.setBitcoinNodes(btcNodes.getText());
}
UserThread.runAfter(BitsquareApp.shutDownHandler::run, 500, TimeUnit.MILLISECONDS);
})
.closeButtonText("Cancel")
.onClose(() -> btcNodes.setText(btcNodesPreFocusText))
.show();
}
};
btcNodes.focusedProperty().addListener(btcNodesFocusListener);
}
@Override
public void deactivate() {
useTorForBtcJCheckBox.setOnAction(null);
if (nodeAddressSubscription != null)
nodeAddressSubscription.unsubscribe();
if (bitcoinPeersSubscription != null)
bitcoinPeersSubscription.unsubscribe();
if (numP2PPeersSubscription != null)
numP2PPeersSubscription.unsubscribe();
totalTraffic.textProperty().unbind();
sortedList.comparatorProperty().unbind();
tableView.getItems().forEach(P2pNetworkListItem::cleanup);
btcNodes.focusedProperty().removeListener(btcNodesFocusListener);
}
private void updateP2PTable() {
tableView.getItems().forEach(P2pNetworkListItem::cleanup);
networkListItems.clear();
networkListItems.setAll(p2PService.getNetworkNode().getAllConnections().stream()
.map(connection -> new P2pNetworkListItem(connection, clock, formatter))
.collect(Collectors.toList()));
}
private void updateBitcoinPeersTextArea() {
bitcoinPeersTextArea.clear();
List<Peer> peerList = walletService.connectedPeersProperty().get();
if (peerList != null) {
peerList.stream().forEach(e -> {
if (bitcoinPeersTextArea.getText().length() > 0)
bitcoinPeersTextArea.appendText("\n");
bitcoinPeersTextArea.appendText(e.toString());
});
}
}
}