add config, usb status fixes

This commit is contained in:
Craig Raw 2020-05-21 15:37:13 +02:00
parent fdd8327464
commit 7fc0e9b530
12 changed files with 194 additions and 53 deletions

View file

@ -69,7 +69,7 @@ jlink {
jpackage { jpackage {
imageName = "Sparrow" imageName = "Sparrow"
installerName = "Sparrow" installerName = "Sparrow"
appVersion = "0.51" appVersion = "0.6"
skipInstaller = true skipInstaller = true
imageOptions = [] imageOptions = []
installerOptions = [ installerOptions = [

View file

@ -290,6 +290,7 @@ public class AppController implements Initializable {
throw new IOException("Unsupported file type"); throw new IOException("Unsupported file type");
} }
} catch(Exception e) { } catch(Exception e) {
e.printStackTrace();
showErrorDialog("Error Opening Wallet", e.getMessage()); showErrorDialog("Error Opening Wallet", e.getMessage());
} }
} }
@ -380,7 +381,8 @@ public class AppController implements Initializable {
controller.setWalletForm(walletForm); controller.setWalletForm(walletForm);
if(!storage.getWalletFile().exists() || wallet.containsSource(KeystoreSource.HW_USB)) { if(!storage.getWalletFile().exists() || wallet.containsSource(KeystoreSource.HW_USB)) {
Hwi.EnumerateService enumerateService = new Hwi.EnumerateService(null); Hwi.ScheduledEnumerateService enumerateService = new Hwi.ScheduledEnumerateService(null);
enumerateService.setPeriod(new Duration(30 * 1000));
enumerateService.setOnSucceeded(workerStateEvent -> { enumerateService.setOnSucceeded(workerStateEvent -> {
List<Device> devices = enumerateService.getValue(); List<Device> devices = enumerateService.getValue();
EventManager.get().post(new UsbDeviceEvent(devices)); EventManager.get().post(new UsbDeviceEvent(devices));
@ -536,19 +538,26 @@ public class AppController implements Initializable {
@Subscribe @Subscribe
public void usbDevicesFound(UsbDeviceEvent event) { public void usbDevicesFound(UsbDeviceEvent event) {
if(event.getDevices().isEmpty()) { UsbStatusButton usbStatus = null;
Node usbStatus = null; for(Node node : statusBar.getRightItems()) {
for(Node node : statusBar.getRightItems()) { if(node instanceof UsbStatusButton) {
if(node instanceof UsbStatusButton) { usbStatus = (UsbStatusButton)node;
usbStatus = node;
}
} }
}
if(event.getDevices().isEmpty()) {
if(usbStatus != null) { if(usbStatus != null) {
statusBar.getRightItems().removeAll(usbStatus); statusBar.getRightItems().removeAll(usbStatus);
} }
} else { } else {
UsbStatusButton usbStatusButton = new UsbStatusButton(event.getDevices()); if(usbStatus == null) {
statusBar.getRightItems().add(usbStatusButton); usbStatus = new UsbStatusButton();
statusBar.getRightItems().add(usbStatus);
} else {
usbStatus.getItems().remove(0, usbStatus.getItems().size());
}
usbStatus.setDevices(event.getDevices());
} }
} }
} }

View file

@ -3,7 +3,6 @@ package com.sparrowwallet.sparrow;
import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.keystoreimport.KeystoreImportDialog;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
import javafx.application.Application; import javafx.application.Application;
@ -14,9 +13,6 @@ import javafx.scene.image.Image;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.controlsfx.glyphfont.GlyphFontRegistry; import org.controlsfx.glyphfont.GlyphFontRegistry;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MainApp extends Application { public class MainApp extends Application {
@Override @Override
@ -46,9 +42,6 @@ public class MainApp extends Application {
// KeystoreImportDialog dlg = new KeystoreImportDialog(wallet); // KeystoreImportDialog dlg = new KeystoreImportDialog(wallet);
// dlg.showAndWait(); // dlg.showAndWait();
Path path = Paths.get("").toAbsolutePath();
System.out.println(path.toFile().getAbsolutePath());
stage.show(); stage.show();
} }

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.sparrow.control; package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
import com.sparrowwallet.sparrow.io.Device; import com.sparrowwallet.sparrow.io.Device;
import javafx.geometry.Side; import javafx.geometry.Side;
@ -10,33 +11,41 @@ import org.controlsfx.glyphfont.Glyph;
import java.util.List; import java.util.List;
public class UsbStatusButton extends MenuButton { public class UsbStatusButton extends MenuButton {
private final List<Device> devices; public UsbStatusButton() {
public UsbStatusButton(List<Device> devices) {
super(""); super("");
setGraphic(getIcon()); setGraphic(getIcon());
this.devices = devices;
this.setPopupSide(Side.TOP); getStyleClass().add("status-bar-button");
this.setPopupSide(Side.RIGHT);
}
public void setDevices(List<Device> devices) {
for(Device device : devices) { for(Device device : devices) {
MenuItem deviceItem = new MenuItem(device.getModel().toDisplayString()); MenuItem deviceItem = new MenuItem(device.getModel().toDisplayString());
if(device.getNeedsPinSent()) {
deviceItem.setGraphic(getLockIcon());
} else {
deviceItem.setGraphic(getLockOpenIcon());
}
getItems().add(deviceItem); getItems().add(deviceItem);
} }
} }
private Node getIcon() { private Node getIcon() {
Glyph usb = new Glyph(FontAwesome5Brands.FONT_NAME, FontAwesome5Brands.Glyph.USB); Glyph usb = new Glyph(FontAwesome5Brands.FONT_NAME, FontAwesome5Brands.Glyph.USB);
usb.setFontSize(10); usb.setFontSize(15);
return usb; return usb;
} }
private class UsbStatusContextMenu extends ContextMenu { private Node getLockIcon() {
public UsbStatusContextMenu() { Glyph usb = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.LOCK);
for(Device device : devices) { usb.setFontSize(12);
MenuItem deviceItem = new MenuItem(device.getModel().toDisplayString()); return usb;
getItems().add(deviceItem); }
}
} private Node getLockOpenIcon() {
Glyph usb = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.LOCK_OPEN);
usb.setFontSize(12);
return usb;
} }
} }

View file

@ -1,17 +1,23 @@
package com.sparrowwallet.sparrow.event; package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.sparrow.io.Config;
import java.io.File; import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class StorageEvent extends TimedEvent { public class StorageEvent extends TimedEvent {
private static boolean firstRunDone = false; private static boolean firstRunDone = false;
private static int keyDerivationPeriod = -1;
private static final Map<File, Long> eventTime = new HashMap<>(); private static final Map<File, Long> eventTime = new HashMap<>();
public StorageEvent(File file, Action action, String status) { public StorageEvent(File file, Action action, String status) {
super(action, status); super(action, status);
Integer keyDerivationPeriod = Config.get().getKeyDerivationPeriod();
if(keyDerivationPeriod == null) {
keyDerivationPeriod = -1;
}
if(action == Action.START) { if(action == Action.START) {
eventTime.put(file, System.currentTimeMillis()); eventTime.put(file, System.currentTimeMillis());
timeMills = keyDerivationPeriod; timeMills = keyDerivationPeriod;
@ -19,9 +25,9 @@ public class StorageEvent extends TimedEvent {
long start = eventTime.get(file); long start = eventTime.get(file);
if(firstRunDone) { if(firstRunDone) {
keyDerivationPeriod = (int)(System.currentTimeMillis() - start); keyDerivationPeriod = (int)(System.currentTimeMillis() - start);
Config.get().setKeyDerivationPeriod(keyDerivationPeriod);
} }
firstRunDone = true; firstRunDone = true;
System.out.println(keyDerivationPeriod);
timeMills = 0; timeMills = 0;
} }
} }

View file

@ -20,6 +20,8 @@ public class FontAwesome5 extends GlyphFont {
EYE('\uf06e'), EYE('\uf06e'),
KEY('\uf084'), KEY('\uf084'),
LAPTOP('\uf109'), LAPTOP('\uf109'),
LOCK('\uf023'),
LOCK_OPEN('\uf3c1'),
SD_CARD('\uf7c2'), SD_CARD('\uf7c2'),
WALLET('\uf555'); WALLET('\uf555');

View file

@ -0,0 +1,99 @@
package com.sparrowwallet.sparrow.io;
import com.google.gson.*;
import com.sparrowwallet.drongo.Utils;
import java.io.*;
import java.lang.reflect.Type;
public class Config {
public static final String CONFIG_FILENAME = ".config";
private Integer keyDerivationPeriod;
private File hwi;
private static Config INSTANCE;
private static Gson getGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(File.class, new FileSerializer());
gsonBuilder.registerTypeAdapter(File.class, new FileDeserializer());
return gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create();
}
private static File getConfigFile() {
return new File(Storage.getSparrowDir(), CONFIG_FILENAME);
}
private static Config load() {
File configFile = getConfigFile();
if(configFile.exists()) {
try {
Reader reader = new FileReader(configFile);
Config config = getGson().fromJson(reader, Config.class);
reader.close();
if(config != null) {
return config;
}
} catch (IOException e) {
e.printStackTrace();
//Ignore and assume no config
}
}
return new Config();
}
public static synchronized Config get() {
if(INSTANCE == null) {
INSTANCE = load();
}
return INSTANCE;
}
public Integer getKeyDerivationPeriod() {
return keyDerivationPeriod;
}
public void setKeyDerivationPeriod(Integer keyDerivationPeriod) {
this.keyDerivationPeriod = keyDerivationPeriod;
flush();
}
public File getHwi() {
return hwi;
}
public void setHwi(File hwi) {
this.hwi = hwi;
flush();
}
private void flush() {
Gson gson = getGson();
try {
File configFile = getConfigFile();
Writer writer = new FileWriter(configFile);
gson.toJson(this, writer);
writer.close();
} catch (IOException e) {
//Ignore
}
}
private static class FileSerializer implements JsonSerializer<File> {
@Override
public JsonElement serialize(File src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.getAbsolutePath());
}
}
private static class FileDeserializer implements JsonDeserializer<File> {
@Override
public File deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return new File(json.getAsJsonPrimitive().getAsString());
}
}
}

View file

@ -4,6 +4,7 @@ import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import com.google.gson.*; import com.google.gson.*;
import com.sparrowwallet.drongo.wallet.WalletModel; import com.sparrowwallet.drongo.wallet.WalletModel;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Service; import javafx.concurrent.Service;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream; import org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream;
@ -23,8 +24,6 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
public class Hwi { public class Hwi {
private static File hwiExecutable;
public List<Device> enumerate(String passphrase) throws ImportException { public List<Device> enumerate(String passphrase) throws ImportException {
try { try {
List<String> command; List<String> command;
@ -86,9 +85,10 @@ public class Hwi {
return CharStreams.toString(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); return CharStreams.toString(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
} }
private synchronized File getHwiExecutable() throws IOException { private synchronized File getHwiExecutable() {
try { File hwiExecutable = Config.get().getHwi();
if (hwiExecutable == null) { if(hwiExecutable == null || !hwiExecutable.exists()) {
try {
Platform platform = Platform.getCurrent(); Platform platform = Platform.getCurrent();
Set<PosixFilePermission> ownerExecutableWritable = PosixFilePermissions.fromString("rwxr--r--"); Set<PosixFilePermission> ownerExecutableWritable = PosixFilePermissions.fromString("rwxr--r--");
@ -100,16 +100,15 @@ public class Hwi {
InputStream inputStream = Hwi.class.getResourceAsStream("/external/" + platform.getPlatformId().toLowerCase() + "/hwi-1.1.0-mac-amd64-signed.zip"); InputStream inputStream = Hwi.class.getResourceAsStream("/external/" + platform.getPlatformId().toLowerCase() + "/hwi-1.1.0-mac-amd64-signed.zip");
Path tempHwiDirPath = Files.createTempDirectory("hwi", PosixFilePermissions.asFileAttribute(ownerExecutableWritable)); Path tempHwiDirPath = Files.createTempDirectory("hwi", PosixFilePermissions.asFileAttribute(ownerExecutableWritable));
File tempHwiDir = tempHwiDirPath.toFile(); File tempHwiDir = tempHwiDirPath.toFile();
tempHwiDir.deleteOnExit(); //tempHwiDir.deleteOnExit();
//System.out.println(tempHwiDir.getAbsolutePath());
System.out.println(tempHwiDir.getAbsolutePath());
File tempExec = null; File tempExec = null;
ZipInputStream zis = new ZipInputStream(inputStream); ZipInputStream zis = new ZipInputStream(inputStream);
ZipEntry zipEntry = zis.getNextEntry(); ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) { while (zipEntry != null) {
File newFile = newFile(tempHwiDir, zipEntry, ownerExecutableWritable); File newFile = newFile(tempHwiDir, zipEntry, ownerExecutableWritable);
newFile.deleteOnExit(); //newFile.deleteOnExit();
FileOutputStream fos = new FileOutputStream(newFile); FileOutputStream fos = new FileOutputStream(newFile);
ByteStreams.copy(zis, new FileOutputStream(newFile)); ByteStreams.copy(zis, new FileOutputStream(newFile));
fos.flush(); fos.flush();
@ -129,7 +128,7 @@ public class Hwi {
InputStream inputStream = Hwi.class.getResourceAsStream("/external/" + platform.getPlatformId().toLowerCase() + "/hwi"); InputStream inputStream = Hwi.class.getResourceAsStream("/external/" + platform.getPlatformId().toLowerCase() + "/hwi");
Path tempExecPath = Files.createTempFile("hwi", null, PosixFilePermissions.asFileAttribute(ownerExecutableWritable)); Path tempExecPath = Files.createTempFile("hwi", null, PosixFilePermissions.asFileAttribute(ownerExecutableWritable));
File tempExec = tempExecPath.toFile(); File tempExec = tempExecPath.toFile();
tempExec.deleteOnExit(); //tempExec.deleteOnExit();
OutputStream tempExecStream = new BufferedOutputStream(new FileOutputStream(tempExec)); OutputStream tempExecStream = new BufferedOutputStream(new FileOutputStream(tempExec));
ByteStreams.copy(new FramedLZ4CompressorInputStream(inputStream), tempExecStream); ByteStreams.copy(new FramedLZ4CompressorInputStream(inputStream), tempExecStream);
inputStream.close(); inputStream.close();
@ -138,9 +137,11 @@ public class Hwi {
hwiExecutable = tempExec; hwiExecutable = tempExec;
} }
} catch(Exception e) {
e.printStackTrace();
} }
} catch(Exception e) {
e.printStackTrace(); Config.get().setHwi(hwiExecutable);
} }
return hwiExecutable; return hwiExecutable;
@ -199,6 +200,24 @@ public class Hwi {
} }
} }
public static class ScheduledEnumerateService extends ScheduledService<List<Device>> {
private final String passphrase;
public ScheduledEnumerateService(String passphrase) {
this.passphrase = passphrase;
}
@Override
protected Task<List<Device>> createTask() {
return new Task<>() {
protected List<Device> call() throws ImportException {
Hwi hwi = new Hwi();
return hwi.enumerate(passphrase);
}
};
}
}
public static class PromptPinService extends Service<Boolean> { public static class PromptPinService extends Service<Boolean> {
private Device device; private Device device;

View file

@ -220,11 +220,11 @@ public class Storage {
return walletsDir; return walletsDir;
} }
private static File getSparrowDir() { static File getSparrowDir() {
return new File(getHomeDir(), SPARROW_DIR); return new File(getHomeDir(), SPARROW_DIR);
} }
private static File getHomeDir() { static File getHomeDir() {
return new File(System.getProperty("user.home")); return new File(System.getProperty("user.home"));
} }

View file

@ -19,3 +19,7 @@
.drag-over > .background-text { .drag-over > .background-text {
-fx-fill: #383a42; -fx-fill: #383a42;
} }
.status-bar .right-items {
-fx-padding: 0 0 0 8;
}

View file

@ -41,6 +41,6 @@
<TabPane fx:id="tabs" /> <TabPane fx:id="tabs" />
</StackPane> </StackPane>
<StatusBar fx:id="statusBar" text=""/> <StatusBar fx:id="statusBar" text="" minHeight="36"/>
</children> </children>
</VBox> </VBox>

View file

@ -16,7 +16,7 @@
<ToggleGroup fx:id="walletMenu" /> <ToggleGroup fx:id="walletMenu" />
</toggleGroup> </toggleGroup>
<graphic> <graphic>
<Glyph fontFamily="FontAwesome" icon="BTC" /> <Glyph fontFamily="FontAwesome" icon="BTC" fontSize="20" />
</graphic> </graphic>
<userData> <userData>
<Function fx:constant="TRANSACTIONS"/> <Function fx:constant="TRANSACTIONS"/>
@ -24,7 +24,7 @@
</ToggleButton> </ToggleButton>
<ToggleButton VBox.vgrow="ALWAYS" text="Send" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu"> <ToggleButton VBox.vgrow="ALWAYS" text="Send" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu">
<graphic> <graphic>
<Glyph fontFamily="FontAwesome" icon="SEND" /> <Glyph fontFamily="FontAwesome" icon="SEND" fontSize="20" />
</graphic> </graphic>
<userData> <userData>
<Function fx:constant="SEND"/> <Function fx:constant="SEND"/>
@ -32,7 +32,7 @@
</ToggleButton> </ToggleButton>
<ToggleButton VBox.vgrow="ALWAYS" text="Receive" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu"> <ToggleButton VBox.vgrow="ALWAYS" text="Receive" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu">
<graphic> <graphic>
<Glyph fontFamily="FontAwesome" icon="ARROW_DOWN" /> <Glyph fontFamily="FontAwesome" icon="ARROW_DOWN" fontSize="20" />
</graphic> </graphic>
<userData> <userData>
<Function fx:constant="RECEIVE"/> <Function fx:constant="RECEIVE"/>
@ -40,7 +40,7 @@
</ToggleButton> </ToggleButton>
<ToggleButton VBox.vgrow="ALWAYS" text="Addresses" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu"> <ToggleButton VBox.vgrow="ALWAYS" text="Addresses" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu">
<graphic> <graphic>
<Glyph fontFamily="FontAwesome" icon="TH_LIST" /> <Glyph fontFamily="FontAwesome" icon="TH_LIST" fontSize="20" />
</graphic> </graphic>
<userData> <userData>
<Function fx:constant="ADDRESSES"/> <Function fx:constant="ADDRESSES"/>
@ -48,7 +48,7 @@
</ToggleButton> </ToggleButton>
<ToggleButton VBox.vgrow="ALWAYS" text="Policies" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu"> <ToggleButton VBox.vgrow="ALWAYS" text="Policies" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu">
<graphic> <graphic>
<Glyph fontFamily="FontAwesome" icon="FILE_TEXT" /> <Glyph fontFamily="FontAwesome" icon="FILE_TEXT" fontSize="20" />
</graphic> </graphic>
<userData> <userData>
<Function fx:constant="POLICIES"/> <Function fx:constant="POLICIES"/>
@ -56,7 +56,7 @@
</ToggleButton> </ToggleButton>
<ToggleButton VBox.vgrow="ALWAYS" text="Settings" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu"> <ToggleButton VBox.vgrow="ALWAYS" text="Settings" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$walletMenu">
<graphic> <graphic>
<Glyph fontFamily="FontAwesome" icon="COG" /> <Glyph fontFamily="FontAwesome" icon="COG" fontSize="20" />
</graphic> </graphic>
<userData> <userData>
<Function fx:constant="SETTINGS"/> <Function fx:constant="SETTINGS"/>