mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
add software update check
This commit is contained in:
parent
b9db4421df
commit
f22312e04f
7 changed files with 256 additions and 3 deletions
|
@ -19,6 +19,7 @@ import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.io.*;
|
import com.sparrowwallet.sparrow.io.*;
|
||||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||||
|
import com.sparrowwallet.sparrow.net.VersionCheckService;
|
||||||
import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
|
import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
|
||||||
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
||||||
import com.sparrowwallet.sparrow.transaction.TransactionData;
|
import com.sparrowwallet.sparrow.transaction.TransactionData;
|
||||||
|
@ -70,8 +71,8 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
private static final int SERVER_PING_PERIOD = 10 * 1000;
|
private static final int SERVER_PING_PERIOD = 10 * 1000;
|
||||||
private static final int ENUMERATE_HW_PERIOD = 30 * 1000;
|
private static final int ENUMERATE_HW_PERIOD = 30 * 1000;
|
||||||
|
|
||||||
private static final int RATES_PERIOD = 5 * 60 * 1000;
|
private static final int RATES_PERIOD = 5 * 60 * 1000;
|
||||||
|
private static final int VERSION_CHECK_PERIOD_HOURS = 24;
|
||||||
private static final ExchangeSource DEFAULT_EXCHANGE_SOURCE = ExchangeSource.COINGECKO;
|
private static final ExchangeSource DEFAULT_EXCHANGE_SOURCE = ExchangeSource.COINGECKO;
|
||||||
private static final Currency DEFAULT_FIAT_CURRENCY = Currency.getInstance("USD");
|
private static final Currency DEFAULT_FIAT_CURRENCY = Currency.getInstance("USD");
|
||||||
|
|
||||||
|
@ -125,6 +126,8 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
private Hwi.ScheduledEnumerateService deviceEnumerateService;
|
private Hwi.ScheduledEnumerateService deviceEnumerateService;
|
||||||
|
|
||||||
|
private VersionCheckService versionCheckService;
|
||||||
|
|
||||||
private static Integer currentBlockHeight;
|
private static Integer currentBlockHeight;
|
||||||
|
|
||||||
public static boolean showTxHexProperty;
|
public static boolean showTxHexProperty;
|
||||||
|
@ -247,9 +250,18 @@ public class AppController implements Initializable {
|
||||||
if(!ratesService.isRunning() && ratesService.getExchangeSource() != ExchangeSource.NONE) {
|
if(!ratesService.isRunning() && ratesService.getExchangeSource() != ExchangeSource.NONE) {
|
||||||
ratesService.start();
|
ratesService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(versionCheckService.getState() == Worker.State.CANCELLED) {
|
||||||
|
versionCheckService.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!versionCheckService.isRunning() && Config.get().isCheckNewVersions()) {
|
||||||
|
versionCheckService.start();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
connectionService.cancel();
|
connectionService.cancel();
|
||||||
ratesService.cancel();
|
ratesService.cancel();
|
||||||
|
versionCheckService.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -268,10 +280,15 @@ public class AppController implements Initializable {
|
||||||
ExchangeSource source = config.getExchangeSource() != null ? config.getExchangeSource() : DEFAULT_EXCHANGE_SOURCE;
|
ExchangeSource source = config.getExchangeSource() != null ? config.getExchangeSource() : DEFAULT_EXCHANGE_SOURCE;
|
||||||
Currency currency = config.getFiatCurrency() != null ? config.getFiatCurrency() : DEFAULT_FIAT_CURRENCY;
|
Currency currency = config.getFiatCurrency() != null ? config.getFiatCurrency() : DEFAULT_FIAT_CURRENCY;
|
||||||
ratesService = createRatesService(source, currency);
|
ratesService = createRatesService(source, currency);
|
||||||
if (config.getMode() == Mode.ONLINE && source != ExchangeSource.NONE) {
|
if(config.getMode() == Mode.ONLINE && source != ExchangeSource.NONE) {
|
||||||
ratesService.start();
|
ratesService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
versionCheckService = createVersionCheckService();
|
||||||
|
if(config.getMode() == Mode.ONLINE && config.isCheckNewVersions()) {
|
||||||
|
versionCheckService.start();
|
||||||
|
}
|
||||||
|
|
||||||
openTransactionIdItem.disableProperty().bind(onlineProperty.not());
|
openTransactionIdItem.disableProperty().bind(onlineProperty.not());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,6 +340,22 @@ public class AppController implements Initializable {
|
||||||
return ratesService;
|
return ratesService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private VersionCheckService createVersionCheckService() {
|
||||||
|
VersionCheckService versionCheckService = new VersionCheckService();
|
||||||
|
versionCheckService.setDelay(Duration.seconds(10));
|
||||||
|
versionCheckService.setPeriod(Duration.hours(VERSION_CHECK_PERIOD_HOURS));
|
||||||
|
versionCheckService.setRestartOnFailure(true);
|
||||||
|
|
||||||
|
versionCheckService.setOnSucceeded(successEvent -> {
|
||||||
|
VersionUpdatedEvent event = versionCheckService.getValue();
|
||||||
|
if(event != null) {
|
||||||
|
EventManager.get().post(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return versionCheckService;
|
||||||
|
}
|
||||||
|
|
||||||
private Hwi.ScheduledEnumerateService createDeviceEnumerateService() {
|
private Hwi.ScheduledEnumerateService createDeviceEnumerateService() {
|
||||||
Hwi.ScheduledEnumerateService enumerateService = new Hwi.ScheduledEnumerateService(null);
|
Hwi.ScheduledEnumerateService enumerateService = new Hwi.ScheduledEnumerateService(null);
|
||||||
enumerateService.setPeriod(new Duration(ENUMERATE_HW_PERIOD));
|
enumerateService.setPeriod(new Duration(ENUMERATE_HW_PERIOD));
|
||||||
|
@ -1171,6 +1204,20 @@ public class AppController implements Initializable {
|
||||||
wait.play();
|
wait.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void versionUpdated(VersionUpdatedEvent event) {
|
||||||
|
Hyperlink versionUpdateLabel = new Hyperlink("Sparrow " + event.getVersion() + " available");
|
||||||
|
versionUpdateLabel.setOnAction(event1 -> {
|
||||||
|
application.getHostServices().showDocument("https://www.sparrowwallet.com/download");
|
||||||
|
});
|
||||||
|
|
||||||
|
if(statusBar.getRightItems().size() > 0 && statusBar.getRightItems().get(0) instanceof Hyperlink) {
|
||||||
|
statusBar.getRightItems().remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
statusBar.getRightItems().add(0, versionUpdateLabel);
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void timedWorker(TimedEvent event) {
|
public void timedWorker(TimedEvent event) {
|
||||||
if(event.getTimeMills() == 0) {
|
if(event.getTimeMills() == 0) {
|
||||||
|
@ -1214,7 +1261,7 @@ public class AppController implements Initializable {
|
||||||
} else {
|
} else {
|
||||||
if(usbStatus == null) {
|
if(usbStatus == null) {
|
||||||
usbStatus = new UsbStatusButton();
|
usbStatus = new UsbStatusButton();
|
||||||
statusBar.getRightItems().add(0, usbStatus);
|
statusBar.getRightItems().add(Math.max(statusBar.getRightItems().size() - 1, 0), usbStatus);
|
||||||
} else {
|
} else {
|
||||||
usbStatus.getItems().remove(0, usbStatus.getItems().size());
|
usbStatus.getItems().remove(0, usbStatus.getItems().size());
|
||||||
}
|
}
|
||||||
|
@ -1285,6 +1332,16 @@ public class AppController implements Initializable {
|
||||||
fiatCurrencyExchangeRate = event.getCurrencyRate();
|
fiatCurrencyExchangeRate = event.getCurrencyRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void versionCheckStatus(VersionCheckStatusEvent event) {
|
||||||
|
versionCheckService.cancel();
|
||||||
|
|
||||||
|
if(Config.get().getMode() != Mode.OFFLINE && event.isEnabled()) {
|
||||||
|
versionCheckService = createVersionCheckService();
|
||||||
|
versionCheckService.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void openWallets(OpenWalletsEvent event) {
|
public void openWallets(OpenWalletsEvent event) {
|
||||||
List<File> walletFiles = event.getWalletsMap().values().stream().map(storage -> storage.getWalletFile()).collect(Collectors.toList());
|
List<File> walletFiles = event.getWalletsMap().values().stream().map(storage -> storage.getWalletFile()).collect(Collectors.toList());
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
public class VersionCheckStatusEvent {
|
||||||
|
private final boolean enabled;
|
||||||
|
|
||||||
|
public VersionCheckStatusEvent(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
public class VersionUpdatedEvent {
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
public VersionUpdatedEvent(String version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ public class Config {
|
||||||
private boolean groupByAddress = true;
|
private boolean groupByAddress = true;
|
||||||
private boolean includeMempoolChange = true;
|
private boolean includeMempoolChange = true;
|
||||||
private boolean notifyNewTransactions = true;
|
private boolean notifyNewTransactions = true;
|
||||||
|
private boolean checkNewVersions = true;
|
||||||
private List<File> recentWalletFiles;
|
private List<File> recentWalletFiles;
|
||||||
private Integer keyDerivationPeriod;
|
private Integer keyDerivationPeriod;
|
||||||
private File hwi;
|
private File hwi;
|
||||||
|
@ -135,6 +136,15 @@ public class Config {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCheckNewVersions() {
|
||||||
|
return checkNewVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCheckNewVersions(boolean checkNewVersions) {
|
||||||
|
this.checkNewVersions = checkNewVersions;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
public List<File> getRecentWalletFiles() {
|
public List<File> getRecentWalletFiles() {
|
||||||
return recentWalletFiles;
|
return recentWalletFiles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
package com.sparrowwallet.sparrow.net;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.sparrowwallet.drongo.address.Address;
|
||||||
|
import com.sparrowwallet.drongo.address.InvalidAddressException;
|
||||||
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
|
import com.sparrowwallet.sparrow.MainApp;
|
||||||
|
import com.sparrowwallet.sparrow.event.VersionUpdatedEvent;
|
||||||
|
import javafx.concurrent.ScheduledService;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class VersionCheckService extends ScheduledService<VersionUpdatedEvent> {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(VersionCheckService.class);
|
||||||
|
private static final String VERSION_CHECK_URL = "https://www.sparrowwallet.com/version";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<VersionUpdatedEvent> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
protected VersionUpdatedEvent call() {
|
||||||
|
try {
|
||||||
|
VersionCheck versionCheck = getVersionCheck();
|
||||||
|
if(isNewer(versionCheck) && verifySignature(versionCheck)) {
|
||||||
|
return new VersionUpdatedEvent(versionCheck.version);
|
||||||
|
}
|
||||||
|
} catch(IOException e) {
|
||||||
|
log.error("Error retrieving version check file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private VersionCheck getVersionCheck() throws IOException {
|
||||||
|
URL url = new URL(VERSION_CHECK_URL);
|
||||||
|
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
|
||||||
|
|
||||||
|
try(InputStreamReader reader = new InputStreamReader(conn.getInputStream())) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
return gson.fromJson(reader, VersionCheck.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean verifySignature(VersionCheck versionCheck) {
|
||||||
|
try {
|
||||||
|
for(String addressString : versionCheck.signatures.keySet()) {
|
||||||
|
String signature = versionCheck.signatures.get(addressString);
|
||||||
|
ECKey signedMessageKey = ECKey.signedMessageToKey(versionCheck.version, signature, false);
|
||||||
|
Address providedAddress = Address.fromString(addressString);
|
||||||
|
Address signedMessageAddress = ScriptType.P2PKH.getAddress(signedMessageKey);
|
||||||
|
|
||||||
|
if(providedAddress.equals(signedMessageAddress)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.warn("Invalid signature for version check " + signature + " from address " + addressString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(SignatureException e) {
|
||||||
|
log.error("Error in version check signature", e);
|
||||||
|
} catch(InvalidAddressException e) {
|
||||||
|
log.error("Error in version check address", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNewer(VersionCheck versionCheck) {
|
||||||
|
try {
|
||||||
|
Version versionCheckVersion = new Version(versionCheck.version);
|
||||||
|
Version currentVersion = new Version(MainApp.APP_VERSION);
|
||||||
|
return versionCheckVersion.compareTo(currentVersion) > 0;
|
||||||
|
} catch(IllegalArgumentException e) {
|
||||||
|
log.error("Invalid versions to compare: " + versionCheck.version + " to " + MainApp.APP_VERSION, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class VersionCheck {
|
||||||
|
public String version;
|
||||||
|
public Map<String, String> signatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Version implements Comparable<Version> {
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
public final String get() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Version(String version) {
|
||||||
|
if(version == null) {
|
||||||
|
throw new IllegalArgumentException("Version can not be null");
|
||||||
|
}
|
||||||
|
if(!version.matches("[0-9]+(\\.[0-9]+)*")) {
|
||||||
|
throw new IllegalArgumentException("Invalid version format");
|
||||||
|
}
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Version that) {
|
||||||
|
if(that == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
String[] thisParts = this.get().split("\\.");
|
||||||
|
String[] thatParts = that.get().split("\\.");
|
||||||
|
int length = Math.max(thisParts.length, thatParts.length);
|
||||||
|
for(int i = 0; i < length; i++) {
|
||||||
|
int thisPart = i < thisParts.length ? Integer.parseInt(thisParts[i]) : 0;
|
||||||
|
int thatPart = i < thatParts.length ? Integer.parseInt(thatParts[i]) : 0;
|
||||||
|
if(thisPart < thatPart) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(thisPart > thatPart) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object that) {
|
||||||
|
if(this == that) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(that == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(this.getClass() != that.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.compareTo((Version)that) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
|
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch;
|
||||||
import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent;
|
import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.FiatCurrencySelectedEvent;
|
import com.sparrowwallet.sparrow.event.FiatCurrencySelectedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.VersionCheckStatusEvent;
|
||||||
import com.sparrowwallet.sparrow.io.Config;
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
import com.sparrowwallet.sparrow.io.ExchangeSource;
|
import com.sparrowwallet.sparrow.io.ExchangeSource;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
|
@ -38,6 +39,9 @@ public class GeneralPreferencesController extends PreferencesDetailController {
|
||||||
@FXML
|
@FXML
|
||||||
private UnlabeledToggleSwitch notifyNewTransactions;
|
private UnlabeledToggleSwitch notifyNewTransactions;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private UnlabeledToggleSwitch checkNewVersions;
|
||||||
|
|
||||||
private final ChangeListener<Currency> fiatCurrencyListener = new ChangeListener<Currency>() {
|
private final ChangeListener<Currency> fiatCurrencyListener = new ChangeListener<Currency>() {
|
||||||
@Override
|
@Override
|
||||||
public void changed(ObservableValue<? extends Currency> observable, Currency oldValue, Currency newValue) {
|
public void changed(ObservableValue<? extends Currency> observable, Currency oldValue, Currency newValue) {
|
||||||
|
@ -86,6 +90,12 @@ public class GeneralPreferencesController extends PreferencesDetailController {
|
||||||
notifyNewTransactions.selectedProperty().addListener((observableValue, oldValue, newValue) -> {
|
notifyNewTransactions.selectedProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||||
config.setNotifyNewTransactions(newValue);
|
config.setNotifyNewTransactions(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
checkNewVersions.setSelected(config.isCheckNewVersions());
|
||||||
|
checkNewVersions.selectedProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||||
|
config.setCheckNewVersions(newValue);
|
||||||
|
EventManager.get().post(new VersionCheckStatusEvent(newValue));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCurrencies(ExchangeSource exchangeSource) {
|
private void updateCurrencies(ExchangeSource exchangeSource) {
|
||||||
|
|
|
@ -73,6 +73,10 @@
|
||||||
<UnlabeledToggleSwitch fx:id="notifyNewTransactions" />
|
<UnlabeledToggleSwitch fx:id="notifyNewTransactions" />
|
||||||
<HelpLabel helpText="Show system notifications on new wallet transactions"/>
|
<HelpLabel helpText="Show system notifications on new wallet transactions"/>
|
||||||
</Field>
|
</Field>
|
||||||
|
<Field text="Software updates:">
|
||||||
|
<UnlabeledToggleSwitch fx:id="checkNewVersions" />
|
||||||
|
<HelpLabel helpText="Check for updates to Sparrow"/>
|
||||||
|
</Field>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</Form>
|
</Form>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
|
|
Loading…
Reference in a new issue