From 574209c837ff2726c55a8b7986a8ff77c0a0e236 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 11 May 2021 12:31:36 +0200 Subject: [PATCH] ensure file and uri opening on original app instance --- build.gradle | 6 +- src/main/deploy/package/osx/Info.plist | 64 +++++++++++++ src/main/deploy/txn.properties | 3 + .../sparrowwallet/sparrow/AppController.java | 17 ++-- .../sparrowwallet/sparrow/AppServices.java | 93 ++++++++++++++++--- .../com/sparrowwallet/sparrow/MainApp.java | 66 ++++++++----- .../event/RequestTransactionOpenEvent.java | 13 +++ .../sparrow/event/RequestWalletOpenEvent.java | 13 +++ src/main/java/module-info.java | 2 +- 9 files changed, 229 insertions(+), 48 deletions(-) create mode 100644 src/main/deploy/txn.properties diff --git a/build.gradle b/build.gradle index ec8db57e..5908ee76 100644 --- a/build.gradle +++ b/build.gradle @@ -71,6 +71,9 @@ dependencies { } implementation('dev.bwt:bwt-jni:0.1.7') 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') { 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.javafx.application=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) { jvmArgs += "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module" diff --git a/src/main/deploy/package/osx/Info.plist b/src/main/deploy/package/osx/Info.plist index 1ffca736..ce69f8d7 100644 --- a/src/main/deploy/package/osx/Info.plist +++ b/src/main/deploy/package/osx/Info.plist @@ -54,5 +54,69 @@ + UTImportedTypeDeclarations + + + UTTypeIdentifier + org.bitcoin.psbt + UTTypeTagSpecification + + public.filename-extension + + psbt + + + UTTypeDescription + Partially Signed Bitcoin Transaction + UTTypeIconFile + sparrow.icns + + + UTTypeIdentifier + org.bitcoin.txn + UTTypeTagSpecification + + public.filename-extension + + txn + + + UTTypeDescription + Bitcoin Transaction + UTTypeIconFile + sparrow.icns + + + CFBundleDocumentTypes + + + CFBundleTypeRole + Editor + CFBundleTypeIconFile + sparrow.icns + CFBundleTypeExtensions + + psbt + + CFBundleTypeName + Partially Signed Bitcoin Transaction + LSHandlerRank + Default + + + CFBundleTypeRole + Editor + CFBundleTypeIconFile + sparrow.icns + CFBundleTypeExtensions + + txn + + CFBundleTypeName + Bitcoin Transaction + LSHandlerRank + Default + + \ No newline at end of file diff --git a/src/main/deploy/txn.properties b/src/main/deploy/txn.properties new file mode 100644 index 00000000..88f09331 --- /dev/null +++ b/src/main/deploy/txn.properties @@ -0,0 +1,3 @@ +mime-type=application/bitcoin-transaction +extension=txn +description=Bitcoin Transaction \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 2be63450..3a45ba0c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -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) { Tooltip tooltip = new Tooltip(getServerToggleTooltipText(currentBlockHeight)); tooltip.setShowDuration(Duration.seconds(15)); @@ -1808,14 +1803,22 @@ public class AppController implements Initializable { @Subscribe public void requestWalletOpen(RequestWalletOpenEvent event) { if(tabs.getScene().getWindow().equals(event.getWindow())) { - openWallet(true); + if(event.getFile() != null) { + openWalletFile(event.getFile(), true); + } else { + openWallet(true); + } } } @Subscribe public void requestTransactionOpen(RequestTransactionOpenEvent event) { if(tabs.getScene().getWindow().equals(event.getWindow())) { - openTransactionFromFile(null); + if(event.getFile() != null) { + openTransactionFile(event.getFile()); + } else { + openTransactionFromFile(null); + } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index 3037011c..00072953 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -13,10 +13,7 @@ import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.control.TextUtils; import com.sparrowwallet.sparrow.control.TrayManager; import com.sparrowwallet.sparrow.event.*; -import com.sparrowwallet.sparrow.io.Config; -import com.sparrowwallet.sparrow.io.Device; -import com.sparrowwallet.sparrow.io.Hwi; -import com.sparrowwallet.sparrow.io.Storage; +import com.sparrowwallet.sparrow.io.*; import com.sparrowwallet.sparrow.net.*; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; @@ -45,13 +42,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.*; -import java.awt.desktop.OpenURIEvent; +import java.awt.desktop.OpenFilesHandler; import java.awt.desktop.OpenURIHandler; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URI; +import java.net.URISyntaxException; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; @@ -104,6 +102,10 @@ public class AppServices { private static List devices; + private static final List argFiles = new ArrayList<>(); + + private static final List argUris = new ArrayList<>(); + private static final Map payjoinURIs = new HashMap<>(); private final ChangeListener onlineServicesListener = new ChangeListener<>() { @@ -124,12 +126,11 @@ public class AppServices { }; private static final OpenURIHandler openURIHandler = event -> { - URI uri = event.getURI(); - if("bitcoin".equals(uri.getScheme())) { - Platform.runLater(() -> openBitcoinUri(uri)); - } else if("aopp".equals(uri.getScheme())) { - Platform.runLater(() -> openAddressOwnershipProof(uri)); - } + openURI(event.getURI()); + }; + + private static final OpenFilesHandler openFilesHandler = event -> { + openFiles(event.getFiles(), null); }; public AppServices(MainApp application) { @@ -521,6 +522,11 @@ public class AppServices { 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 showWarningDialog(String title, String content, ButtonType... buttons) { return showAlertDialog(title, content, Alert.AlertType.WARNING, buttons); } @@ -611,14 +617,71 @@ public class AppServices { } } - public static void handleURI(URI uri) { - openURIHandler.openURI(new OpenURIEvent(uri)); + static void parseFileUriArguments(List fileUriArguments) { + 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 files, Window window) { + final List 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() { try { - if(Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_URI)) { - Desktop.getDesktop().setOpenURIHandler(openURIHandler); + 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); + } } } catch(Exception e) { log.error("Could not add URI handler", e); diff --git a/src/main/java/com/sparrowwallet/sparrow/MainApp.java b/src/main/java/com/sparrowwallet/sparrow/MainApp.java index e49c37a9..ce1da249 100644 --- a/src/main/java/com/sparrowwallet/sparrow/MainApp.java +++ b/src/main/java/com/sparrowwallet/sparrow/MainApp.java @@ -23,14 +23,15 @@ import org.controlsfx.tools.Platform; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; +import tk.pratanumandal.unique4j.Unique4jList; +import tk.pratanumandal.unique4j.exception.Unique4jException; import java.io.File; -import java.net.URI; -import java.net.URISyntaxException; import java.util.*; import java.util.stream.Collectors; 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_VERSION = "1.4.0"; public static final String APP_HOME_PROPERTY = "sparrow.home"; @@ -38,8 +39,7 @@ public class MainApp extends Application { private Stage mainStage; - private static final List argFiles = new ArrayList<>(); - private static final List argUris = new ArrayList<>(); + private static SparrowUnique sparrowUnique; @Override public void init() throws Exception { @@ -113,13 +113,7 @@ public class MainApp extends Application { } } - for(File argFile : argFiles) { - appController.openFile(argFile); - } - - for(URI argUri : argUris) { - AppServices.handleURI(argUri); - } + AppServices.openFileUriArguments(stage); AppServices.get().start(); } @@ -128,6 +122,9 @@ public class MainApp extends Application { public void stop() throws Exception { AppServices.get().stop(); mainStage.close(); + if(sparrowUnique != null) { + sparrowUnique.freeLock(); + } } public static void main(String[] argv) { @@ -175,22 +172,18 @@ public class MainApp extends Application { getLogger().info("Using " + Network.get() + " configuration"); } - if(!jCommander.getUnknownOptions().isEmpty()) { - for(String fileUri : jCommander.getUnknownOptions()) { + List fileUriArguments = jCommander.getUnknownOptions(); + if(!fileUriArguments.isEmpty()) { + if(args.network == null && args.dir == null) { try { - File file = new File(fileUri); - if(file.exists()) { - argFiles.add(file); - continue; - } - 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 + sparrowUnique = new SparrowUnique(APP_ID, fileUriArguments); + sparrowUnique.acquireLock(); //Will exit app after sending fileUriArguments if lock cannot be acquired + } catch(Unique4jException e) { + getLogger().error("Could not obtain unique lock", e); } } + + AppServices.parseFileUriArguments(fileUriArguments); } SLF4JBridgeHandler.removeHandlersForRootLogger(); @@ -201,4 +194,29 @@ public class MainApp extends Application { private static Logger getLogger() { return LoggerFactory.getLogger(MainApp.class); } + + private static class SparrowUnique extends Unique4jList { + private final List fileUriArguments; + + public SparrowUnique(String APP_ID, List fileUriArguments) { + super(APP_ID + "." + Network.get()); + this.fileUriArguments = fileUriArguments; + } + + @Override + protected void receiveMessageList(List messageList) { + AppServices.parseFileUriArguments(messageList); + AppServices.openFileUriArguments(null); + } + + @Override + protected List sendMessageList() { + return fileUriArguments; + } + + @Override + protected void beforeExit() { + getLogger().info("Opening files/URIs in already running instance, exiting..."); + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java index 88ec5ffa..bb16c0db 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/RequestTransactionOpenEvent.java @@ -2,17 +2,30 @@ package com.sparrowwallet.sparrow.event; import javafx.stage.Window; +import java.io.File; + /** * Event class used to request the transaction open file dialog */ public class RequestTransactionOpenEvent { private final Window window; + private final File file; public RequestTransactionOpenEvent(Window window) { this.window = window; + this.file = null; + } + + public RequestTransactionOpenEvent(Window window, File file) { + this.window = window; + this.file = file; } public Window getWindow() { return window; } + + public File getFile() { + return file; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/RequestWalletOpenEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/RequestWalletOpenEvent.java index eedf6fb5..362880e6 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/RequestWalletOpenEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/RequestWalletOpenEvent.java @@ -2,17 +2,30 @@ package com.sparrowwallet.sparrow.event; import javafx.stage.Window; +import java.io.File; + /** * Event class used to request the wallet open dialog */ public class RequestWalletOpenEvent { private final Window window; + private final File file; public RequestWalletOpenEvent(Window window) { this.window = window; + this.file = null; + } + + public RequestWalletOpenEvent(Window window, File file) { + this.window = window; + this.file = file; } public Window getWindow() { return window; } + + public File getFile() { + return file; + } } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 34e4085d..4317a529 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -11,7 +11,6 @@ open module com.sparrowwallet.sparrow { requires com.sparrowwallet.drongo; requires com.google.common; requires flowless; - requires com.google.gson; requires com.google.zxing; requires com.google.zxing.javase; requires simple.json.rpc.client; @@ -28,6 +27,7 @@ open module com.sparrowwallet.sparrow { requires bwt.jni; requires jtorctl; requires javacsv; + requires unique4j; requires jul.to.slf4j; requires bridj; } \ No newline at end of file