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.io.*;
|
||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||
import com.sparrowwallet.sparrow.net.VersionCheckService;
|
||||
import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
|
||||
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
||||
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 ENUMERATE_HW_PERIOD = 30 * 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 Currency DEFAULT_FIAT_CURRENCY = Currency.getInstance("USD");
|
||||
|
||||
|
@ -125,6 +126,8 @@ public class AppController implements Initializable {
|
|||
|
||||
private Hwi.ScheduledEnumerateService deviceEnumerateService;
|
||||
|
||||
private VersionCheckService versionCheckService;
|
||||
|
||||
private static Integer currentBlockHeight;
|
||||
|
||||
public static boolean showTxHexProperty;
|
||||
|
@ -247,9 +250,18 @@ public class AppController implements Initializable {
|
|||
if(!ratesService.isRunning() && ratesService.getExchangeSource() != ExchangeSource.NONE) {
|
||||
ratesService.start();
|
||||
}
|
||||
|
||||
if(versionCheckService.getState() == Worker.State.CANCELLED) {
|
||||
versionCheckService.reset();
|
||||
}
|
||||
|
||||
if(!versionCheckService.isRunning() && Config.get().isCheckNewVersions()) {
|
||||
versionCheckService.start();
|
||||
}
|
||||
} else {
|
||||
connectionService.cancel();
|
||||
ratesService.cancel();
|
||||
versionCheckService.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -272,6 +284,11 @@ public class AppController implements Initializable {
|
|||
ratesService.start();
|
||||
}
|
||||
|
||||
versionCheckService = createVersionCheckService();
|
||||
if(config.getMode() == Mode.ONLINE && config.isCheckNewVersions()) {
|
||||
versionCheckService.start();
|
||||
}
|
||||
|
||||
openTransactionIdItem.disableProperty().bind(onlineProperty.not());
|
||||
}
|
||||
|
||||
|
@ -323,6 +340,22 @@ public class AppController implements Initializable {
|
|||
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() {
|
||||
Hwi.ScheduledEnumerateService enumerateService = new Hwi.ScheduledEnumerateService(null);
|
||||
enumerateService.setPeriod(new Duration(ENUMERATE_HW_PERIOD));
|
||||
|
@ -1171,6 +1204,20 @@ public class AppController implements Initializable {
|
|||
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
|
||||
public void timedWorker(TimedEvent event) {
|
||||
if(event.getTimeMills() == 0) {
|
||||
|
@ -1214,7 +1261,7 @@ public class AppController implements Initializable {
|
|||
} else {
|
||||
if(usbStatus == null) {
|
||||
usbStatus = new UsbStatusButton();
|
||||
statusBar.getRightItems().add(0, usbStatus);
|
||||
statusBar.getRightItems().add(Math.max(statusBar.getRightItems().size() - 1, 0), usbStatus);
|
||||
} else {
|
||||
usbStatus.getItems().remove(0, usbStatus.getItems().size());
|
||||
}
|
||||
|
@ -1285,6 +1332,16 @@ public class AppController implements Initializable {
|
|||
fiatCurrencyExchangeRate = event.getCurrencyRate();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void versionCheckStatus(VersionCheckStatusEvent event) {
|
||||
versionCheckService.cancel();
|
||||
|
||||
if(Config.get().getMode() != Mode.OFFLINE && event.isEnabled()) {
|
||||
versionCheckService = createVersionCheckService();
|
||||
versionCheckService.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void openWallets(OpenWalletsEvent event) {
|
||||
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 includeMempoolChange = true;
|
||||
private boolean notifyNewTransactions = true;
|
||||
private boolean checkNewVersions = true;
|
||||
private List<File> recentWalletFiles;
|
||||
private Integer keyDerivationPeriod;
|
||||
private File hwi;
|
||||
|
@ -135,6 +136,15 @@ public class Config {
|
|||
flush();
|
||||
}
|
||||
|
||||
public boolean isCheckNewVersions() {
|
||||
return checkNewVersions;
|
||||
}
|
||||
|
||||
public void setCheckNewVersions(boolean checkNewVersions) {
|
||||
this.checkNewVersions = checkNewVersions;
|
||||
flush();
|
||||
}
|
||||
|
||||
public List<File> getRecentWalletFiles() {
|
||||
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.event.BitcoinUnitChangedEvent;
|
||||
import com.sparrowwallet.sparrow.event.FiatCurrencySelectedEvent;
|
||||
import com.sparrowwallet.sparrow.event.VersionCheckStatusEvent;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.ExchangeSource;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
@ -38,6 +39,9 @@ public class GeneralPreferencesController extends PreferencesDetailController {
|
|||
@FXML
|
||||
private UnlabeledToggleSwitch notifyNewTransactions;
|
||||
|
||||
@FXML
|
||||
private UnlabeledToggleSwitch checkNewVersions;
|
||||
|
||||
private final ChangeListener<Currency> fiatCurrencyListener = new ChangeListener<Currency>() {
|
||||
@Override
|
||||
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) -> {
|
||||
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) {
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
<UnlabeledToggleSwitch fx:id="notifyNewTransactions" />
|
||||
<HelpLabel helpText="Show system notifications on new wallet transactions"/>
|
||||
</Field>
|
||||
<Field text="Software updates:">
|
||||
<UnlabeledToggleSwitch fx:id="checkNewVersions" />
|
||||
<HelpLabel helpText="Check for updates to Sparrow"/>
|
||||
</Field>
|
||||
</Fieldset>
|
||||
</Form>
|
||||
</GridPane>
|
||||
|
|
Loading…
Reference in a new issue