ensure file and uri opening on original app instance

This commit is contained in:
Craig Raw 2021-05-11 12:31:36 +02:00
parent 74c83fc5e1
commit 574209c837
9 changed files with 229 additions and 48 deletions

View file

@ -71,6 +71,9 @@ dependencies {
} }
implementation('dev.bwt:bwt-jni:0.1.7') implementation('dev.bwt:bwt-jni:0.1.7')
implementation('net.sourceforge.javacsv:javacsv:2.0') implementation('net.sourceforge.javacsv:javacsv:2.0')
implementation('tk.pratanumandal:unique4j:1.3') {
exclude group: 'com.google.code.gson'
}
implementation('org.slf4j:jul-to-slf4j:1.7.30') { implementation('org.slf4j:jul-to-slf4j:1.7.30') {
exclude group: 'org.slf4j' exclude group: 'org.slf4j'
} }
@ -151,7 +154,8 @@ jlink {
"--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow", "--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow",
"--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow", "--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow",
"--add-opens=java.base/java.net=com.sparrowwallet.sparrow", "--add-opens=java.base/java.net=com.sparrowwallet.sparrow",
"--add-reads=com.sparrowwallet.merged.module=java.desktop"] "--add-reads=com.sparrowwallet.merged.module=java.desktop",
"--add-reads=com.sparrowwallet.merged.module=java.sql"]
if(os.macOsX) { if(os.macOsX) {
jvmArgs += "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module" jvmArgs += "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module"

View file

@ -54,5 +54,69 @@
</array> </array>
</dict> </dict>
</array> </array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.bitcoin.psbt</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>psbt</string>
</array>
</dict>
<key>UTTypeDescription</key>
<string>Partially Signed Bitcoin Transaction</string>
<key>UTTypeIconFile</key>
<string>sparrow.icns</string>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.bitcoin.txn</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>txn</string>
</array>
</dict>
<key>UTTypeDescription</key>
<string>Bitcoin Transaction</string>
<key>UTTypeIconFile</key>
<string>sparrow.icns</string>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeIconFile</key>
<string>sparrow.icns</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>psbt</string>
</array>
<key>CFBundleTypeName</key>
<string>Partially Signed Bitcoin Transaction</string>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeIconFile</key>
<string>sparrow.icns</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>txn</string>
</array>
<key>CFBundleTypeName</key>
<string>Bitcoin Transaction</string>
<key>LSHandlerRank</key>
<string>Default</string>
</dict>
</array>
</dict> </dict>
</plist> </plist>

View file

@ -0,0 +1,3 @@
mime-type=application/bitcoin-transaction
extension=txn
description=Bitcoin Transaction

View file

@ -691,11 +691,6 @@ public class AppController implements Initializable {
} }
} }
private boolean isWalletFile(File file) {
FileType fileType = IOUtils.getFileType(file);
return FileType.JSON.equals(fileType) || FileType.BINARY.equals(fileType);
}
private void setServerToggleTooltip(Integer currentBlockHeight) { private void setServerToggleTooltip(Integer currentBlockHeight) {
Tooltip tooltip = new Tooltip(getServerToggleTooltipText(currentBlockHeight)); Tooltip tooltip = new Tooltip(getServerToggleTooltipText(currentBlockHeight));
tooltip.setShowDuration(Duration.seconds(15)); tooltip.setShowDuration(Duration.seconds(15));
@ -1808,16 +1803,24 @@ public class AppController implements Initializable {
@Subscribe @Subscribe
public void requestWalletOpen(RequestWalletOpenEvent event) { public void requestWalletOpen(RequestWalletOpenEvent event) {
if(tabs.getScene().getWindow().equals(event.getWindow())) { if(tabs.getScene().getWindow().equals(event.getWindow())) {
if(event.getFile() != null) {
openWalletFile(event.getFile(), true);
} else {
openWallet(true); openWallet(true);
} }
} }
}
@Subscribe @Subscribe
public void requestTransactionOpen(RequestTransactionOpenEvent event) { public void requestTransactionOpen(RequestTransactionOpenEvent event) {
if(tabs.getScene().getWindow().equals(event.getWindow())) { if(tabs.getScene().getWindow().equals(event.getWindow())) {
if(event.getFile() != null) {
openTransactionFile(event.getFile());
} else {
openTransactionFromFile(null); openTransactionFromFile(null);
} }
} }
}
@Subscribe @Subscribe
public void requestQRScan(RequestQRScanEvent event) { public void requestQRScan(RequestQRScanEvent event) {

View file

@ -13,10 +13,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.control.TextUtils; import com.sparrowwallet.sparrow.control.TextUtils;
import com.sparrowwallet.sparrow.control.TrayManager; import com.sparrowwallet.sparrow.control.TrayManager;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.*;
import com.sparrowwallet.sparrow.io.Device;
import com.sparrowwallet.sparrow.io.Hwi;
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.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
@ -45,13 +42,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.awt.*; import java.awt.*;
import java.awt.desktop.OpenURIEvent; import java.awt.desktop.OpenFilesHandler;
import java.awt.desktop.OpenURIHandler; import java.awt.desktop.OpenURIHandler;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
@ -104,6 +102,10 @@ public class AppServices {
private static List<Device> devices; private static List<Device> devices;
private static final List<File> argFiles = new ArrayList<>();
private static final List<URI> argUris = new ArrayList<>();
private static final Map<Address, BitcoinURI> payjoinURIs = new HashMap<>(); private static final Map<Address, BitcoinURI> payjoinURIs = new HashMap<>();
private final ChangeListener<Boolean> onlineServicesListener = new ChangeListener<>() { private final ChangeListener<Boolean> onlineServicesListener = new ChangeListener<>() {
@ -124,12 +126,11 @@ public class AppServices {
}; };
private static final OpenURIHandler openURIHandler = event -> { private static final OpenURIHandler openURIHandler = event -> {
URI uri = event.getURI(); openURI(event.getURI());
if("bitcoin".equals(uri.getScheme())) { };
Platform.runLater(() -> openBitcoinUri(uri));
} else if("aopp".equals(uri.getScheme())) { private static final OpenFilesHandler openFilesHandler = event -> {
Platform.runLater(() -> openAddressOwnershipProof(uri)); openFiles(event.getFiles(), null);
}
}; };
public AppServices(MainApp application) { public AppServices(MainApp application) {
@ -521,6 +522,11 @@ public class AppServices {
ElectrumServer.clearRetrievedScriptHashes(wallet); ElectrumServer.clearRetrievedScriptHashes(wallet);
} }
public static boolean isWalletFile(File file) {
FileType fileType = IOUtils.getFileType(file);
return FileType.JSON.equals(fileType) || FileType.BINARY.equals(fileType);
}
public static Optional<ButtonType> showWarningDialog(String title, String content, ButtonType... buttons) { public static Optional<ButtonType> showWarningDialog(String title, String content, ButtonType... buttons) {
return showAlertDialog(title, content, Alert.AlertType.WARNING, buttons); return showAlertDialog(title, content, Alert.AlertType.WARNING, buttons);
} }
@ -611,15 +617,72 @@ public class AppServices {
} }
} }
public static void handleURI(URI uri) { static void parseFileUriArguments(List<String> fileUriArguments) {
openURIHandler.openURI(new OpenURIEvent(uri)); for(String fileUri : fileUriArguments) {
try {
File file = new File(fileUri.replace("~", System.getProperty("user.home")));
if(file.exists()) {
argFiles.add(file);
continue;
}
URI uri = new URI(fileUri);
argUris.add(uri);
} catch(URISyntaxException e) {
log.warn("Could not parse " + fileUri + " as a valid file or URI");
} catch(Exception e) {
//ignore
}
}
}
public static void openFileUriArguments(Window window) {
openFiles(argFiles, window);
argFiles.clear();
for(URI argUri : argUris) {
openURI(argUri);
}
argUris.clear();
}
private static void openFiles(List<File> files, Window window) {
final List<File> openFiles = new ArrayList<>(files);
Platform.runLater(() -> {
Window openWindow = window;
if(openWindow == null) {
openWindow = getActiveWindow();
}
for(File file : openFiles) {
if(isWalletFile(file)) {
EventManager.get().post(new RequestWalletOpenEvent(openWindow, file));
} else {
EventManager.get().post(new RequestTransactionOpenEvent(openWindow, file));
}
}
});
}
private static void openURI(URI uri) {
Platform.runLater(() -> {
if("bitcoin".equals(uri.getScheme())) {
openBitcoinUri(uri);
} else if("aopp".equals(uri.getScheme())) {
openAddressOwnershipProof(uri);
}
});
} }
public static void addURIHandlers() { public static void addURIHandlers() {
try { try {
if(Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_URI)) { if(Desktop.isDesktopSupported()) {
if(Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_FILE)) {
Desktop.getDesktop().setOpenFileHandler(openFilesHandler);
}
if(Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_URI)) {
Desktop.getDesktop().setOpenURIHandler(openURIHandler); Desktop.getDesktop().setOpenURIHandler(openURIHandler);
} }
}
} catch(Exception e) { } catch(Exception e) {
log.error("Could not add URI handler", e); log.error("Could not add URI handler", e);
} }

View file

@ -23,14 +23,15 @@ import org.controlsfx.tools.Platform;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler; import org.slf4j.bridge.SLF4JBridgeHandler;
import tk.pratanumandal.unique4j.Unique4jList;
import tk.pratanumandal.unique4j.exception.Unique4jException;
import java.io.File; import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class MainApp extends Application { public class MainApp extends Application {
public static final String APP_ID = "com.sparrowwallet.sparrow";
public static final String APP_NAME = "Sparrow"; public static final String APP_NAME = "Sparrow";
public static final String APP_VERSION = "1.4.0"; public static final String APP_VERSION = "1.4.0";
public static final String APP_HOME_PROPERTY = "sparrow.home"; public static final String APP_HOME_PROPERTY = "sparrow.home";
@ -38,8 +39,7 @@ public class MainApp extends Application {
private Stage mainStage; private Stage mainStage;
private static final List<File> argFiles = new ArrayList<>(); private static SparrowUnique sparrowUnique;
private static final List<URI> argUris = new ArrayList<>();
@Override @Override
public void init() throws Exception { public void init() throws Exception {
@ -113,13 +113,7 @@ public class MainApp extends Application {
} }
} }
for(File argFile : argFiles) { AppServices.openFileUriArguments(stage);
appController.openFile(argFile);
}
for(URI argUri : argUris) {
AppServices.handleURI(argUri);
}
AppServices.get().start(); AppServices.get().start();
} }
@ -128,6 +122,9 @@ public class MainApp extends Application {
public void stop() throws Exception { public void stop() throws Exception {
AppServices.get().stop(); AppServices.get().stop();
mainStage.close(); mainStage.close();
if(sparrowUnique != null) {
sparrowUnique.freeLock();
}
} }
public static void main(String[] argv) { public static void main(String[] argv) {
@ -175,22 +172,18 @@ public class MainApp extends Application {
getLogger().info("Using " + Network.get() + " configuration"); getLogger().info("Using " + Network.get() + " configuration");
} }
if(!jCommander.getUnknownOptions().isEmpty()) { List<String> fileUriArguments = jCommander.getUnknownOptions();
for(String fileUri : jCommander.getUnknownOptions()) { if(!fileUriArguments.isEmpty()) {
if(args.network == null && args.dir == null) {
try { try {
File file = new File(fileUri); sparrowUnique = new SparrowUnique(APP_ID, fileUriArguments);
if(file.exists()) { sparrowUnique.acquireLock(); //Will exit app after sending fileUriArguments if lock cannot be acquired
argFiles.add(file); } catch(Unique4jException e) {
continue; getLogger().error("Could not obtain unique lock", e);
}
URI uri = new URI(fileUri);
argUris.add(uri);
} catch(URISyntaxException e) {
getLogger().warn("Could not parse " + fileUri + " as a valid file or URI");
} catch(Exception e) {
//ignore
} }
} }
AppServices.parseFileUriArguments(fileUriArguments);
} }
SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.removeHandlersForRootLogger();
@ -201,4 +194,29 @@ public class MainApp extends Application {
private static Logger getLogger() { private static Logger getLogger() {
return LoggerFactory.getLogger(MainApp.class); return LoggerFactory.getLogger(MainApp.class);
} }
private static class SparrowUnique extends Unique4jList {
private final List<String> fileUriArguments;
public SparrowUnique(String APP_ID, List<String> fileUriArguments) {
super(APP_ID + "." + Network.get());
this.fileUriArguments = fileUriArguments;
}
@Override
protected void receiveMessageList(List<String> messageList) {
AppServices.parseFileUriArguments(messageList);
AppServices.openFileUriArguments(null);
}
@Override
protected List<String> sendMessageList() {
return fileUriArguments;
}
@Override
protected void beforeExit() {
getLogger().info("Opening files/URIs in already running instance, exiting...");
}
}
} }

View file

@ -2,17 +2,30 @@ package com.sparrowwallet.sparrow.event;
import javafx.stage.Window; import javafx.stage.Window;
import java.io.File;
/** /**
* Event class used to request the transaction open file dialog * Event class used to request the transaction open file dialog
*/ */
public class RequestTransactionOpenEvent { public class RequestTransactionOpenEvent {
private final Window window; private final Window window;
private final File file;
public RequestTransactionOpenEvent(Window window) { public RequestTransactionOpenEvent(Window window) {
this.window = window; this.window = window;
this.file = null;
}
public RequestTransactionOpenEvent(Window window, File file) {
this.window = window;
this.file = file;
} }
public Window getWindow() { public Window getWindow() {
return window; return window;
} }
public File getFile() {
return file;
}
} }

View file

@ -2,17 +2,30 @@ package com.sparrowwallet.sparrow.event;
import javafx.stage.Window; import javafx.stage.Window;
import java.io.File;
/** /**
* Event class used to request the wallet open dialog * Event class used to request the wallet open dialog
*/ */
public class RequestWalletOpenEvent { public class RequestWalletOpenEvent {
private final Window window; private final Window window;
private final File file;
public RequestWalletOpenEvent(Window window) { public RequestWalletOpenEvent(Window window) {
this.window = window; this.window = window;
this.file = null;
}
public RequestWalletOpenEvent(Window window, File file) {
this.window = window;
this.file = file;
} }
public Window getWindow() { public Window getWindow() {
return window; return window;
} }
public File getFile() {
return file;
}
} }

View file

@ -11,7 +11,6 @@ open module com.sparrowwallet.sparrow {
requires com.sparrowwallet.drongo; requires com.sparrowwallet.drongo;
requires com.google.common; requires com.google.common;
requires flowless; requires flowless;
requires com.google.gson;
requires com.google.zxing; requires com.google.zxing;
requires com.google.zxing.javase; requires com.google.zxing.javase;
requires simple.json.rpc.client; requires simple.json.rpc.client;
@ -28,6 +27,7 @@ open module com.sparrowwallet.sparrow {
requires bwt.jni; requires bwt.jni;
requires jtorctl; requires jtorctl;
requires javacsv; requires javacsv;
requires unique4j;
requires jul.to.slf4j; requires jul.to.slf4j;
requires bridj; requires bridj;
} }