diff --git a/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java b/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java index c1461149..b030de27 100644 --- a/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java +++ b/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java @@ -16,7 +16,7 @@ import java.io.File; import java.util.*; public class SparrowWallet { - public static final String APP_ID = "com.sparrowwallet.sparrow"; + public static final String APP_ID = "sparrow"; public static final String APP_NAME = "Sparrow"; public static final String APP_VERSION = "1.8.5"; public static final String APP_VERSION_SUFFIX = ""; @@ -79,7 +79,7 @@ public class SparrowWallet { try { instance = new Instance(fileUriArguments); - instance.acquireLock(); //If fileUriArguments is not empty, will exit app after sending fileUriArguments if lock cannot be acquired + instance.acquireLock(!fileUriArguments.isEmpty()); //If fileUriArguments is not empty, will exit app after sending fileUriArguments if lock cannot be acquired } catch(InstanceException e) { getLogger().error("Could not access application lock", e); } @@ -130,7 +130,7 @@ public class SparrowWallet { private final List fileUriArguments; public Instance(List fileUriArguments) { - super(SparrowWallet.APP_ID + "." + Network.get(), !fileUriArguments.isEmpty()); + super(SparrowWallet.APP_ID + "." + Network.get(), true); this.fileUriArguments = fileUriArguments; } diff --git a/src/main/java/com/sparrowwallet/sparrow/instance/Instance.java b/src/main/java/com/sparrowwallet/sparrow/instance/Instance.java index 1b6ecffa..939df9b2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/instance/Instance.java +++ b/src/main/java/com/sparrowwallet/sparrow/instance/Instance.java @@ -1,535 +1,232 @@ -/** - * Copyright 2019 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - package com.sparrowwallet.sparrow.instance; +import com.sparrowwallet.sparrow.io.Storage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.RandomAccessFile; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; import java.net.SocketException; -import java.net.UnknownHostException; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.Set; -/** - * The Instance class is the primary logical entry point to the library.
- * It allows to create an application lock or free it and send and receive messages between first and subsequent instances.

- * - *
- *	// unique application ID
- *	String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6";
- *	
- *	// create Instance instance
- *	Instance unique = new Instance(APP_ID) {
- *	    @Override
- *	    protected void receiveMessage(String message) {
- *	        // print received message (timestamp)
- *	        System.out.println(message);
- *	    }
- *	    
- *	    @Override
- *	    protected String sendMessage() {
- *	        // send timestamp as message
- *	        Timestamp ts = new Timestamp(new Date().getTime());
- *	        return "Another instance launch attempted: " + ts.toString();
- *	    }
- *	};
- *	
- *	// try to obtain lock
- *	try {
- *	    unique.acquireLock();
- *	} catch (InstanceException e) {
- *	    e.printStackTrace();
- *	}
- *	
- *	...
- *	
- *	// try to free the lock before exiting program
- *	try {
- *	    unique.freeLock();
- *	} catch (InstanceException e) {
- *	    e.printStackTrace();
- *	}
- * 
- * - * @author Pratanu Mandal - * @since 1.3 - * - */ public abstract class Instance { private static final Logger log = LoggerFactory.getLogger(Instance.class); - - // starting position of port check - private static final int PORT_START = 7221; - - // system temporary directory path - private static final String TEMP_DIR = System.getProperty("java.io.tmpdir"); - - /** - * Unique string representing the application ID.

- * - * The APP_ID must be as unique as possible. - * Avoid generic names like "my_app_id" or "hello_world".
- * A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. - */ - public final String APP_ID; - - // auto exit from application or not - private final boolean AUTO_EXIT; - - // lock server port - private int port; - - // lock server socket - private ServerSocket server; - - // lock file RAF object - private RandomAccessFile lockRAF; - - // file lock for the lock file RAF object - private FileLock fileLock; - /** - * Parameterized constructor.
- * This constructor configures to automatically exit the application for subsequent instances.

- * - * The APP_ID must be as unique as possible. - * Avoid generic names like "my_app_id" or "hello_world".
- * A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. - * - * @param APP_ID Unique string representing the application ID - */ - public Instance(final String APP_ID) { - this(APP_ID, true); - } - - /** - * Parameterized constructor.
- * This constructor allows to explicitly specify the exit strategy for subsequent instances.

- * - * The APP_ID must be as unique as possible. - * Avoid generic names like "my_app_id" or "hello_world".
- * A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. - * - * @since 1.2 - * - * @param APP_ID Unique string representing the application ID - * @param AUTO_EXIT If true, automatically exit the application for subsequent instances - */ - public Instance(final String APP_ID, final boolean AUTO_EXIT) { - this.APP_ID = APP_ID; - this.AUTO_EXIT = AUTO_EXIT; - } - - /** - * Try to obtain lock. If not possible, send data to first instance. - * - * @deprecated Use acquireLock() instead. - * @throws InstanceException throws InstanceException if it is unable to start a server or connect to server - */ - @Deprecated - public void lock() throws InstanceException { - acquireLock(); - } - - /** - * Try to obtain lock. If not possible, send data to first instance. - * - * @since 1.2 - * - * @return true if able to acquire lock, false otherwise - * @throws InstanceException throws InstanceException if it is unable to start a server or connect to server - */ - public boolean acquireLock() throws InstanceException { - // try to obtain port number from lock file - port = lockFile(); - - if (port == -1) { - // failed to fetch port number - // try to start server - startServer(); - } - else { - // port number fetched from lock file - // try to start client - doClient(); - } - - return (server != null); - } - - // start the server - private void startServer() throws InstanceException { - // try to create server - port = PORT_START; - while (true) { - try { - server = new ServerSocket(port, 50, InetAddress.getByName(null)); - break; - } catch (IOException e) { - port++; - } - } - - // try to lock file - lockFile(port); - - // server created successfully; this is the first instance - // keep listening for data from other instances - Thread thread = new Thread() { - @Override - public void run() { - while (!server.isClosed()) { - try { - // establish connection - final Socket socket = server.accept(); - - // handle socket on a different thread to allow parallel connections - Thread thread = new Thread() { - @Override - public void run() { - try { - // open writer - OutputStream os = socket.getOutputStream(); - DataOutputStream dos = new DataOutputStream(os); - - // open reader - InputStream is = socket.getInputStream(); - DataInputStream dis = new DataInputStream(is); - - // read message length from client - int length = dis.readInt(); - - // read message string from client - String message = null; - if (length > -1) { - byte[] messageBytes = new byte[length]; - int bytesRead = dis.read(messageBytes, 0, length); - message = new String(messageBytes, 0, bytesRead, "UTF-8"); - } - - // write response to client - if (APP_ID == null) { - dos.writeInt(-1); - } - else { - byte[] appId = APP_ID.getBytes("UTF-8"); - - dos.writeInt(appId.length); - dos.write(appId); - } - dos.flush(); - - // close writer and reader - dos.close(); - dis.close(); - - // perform user action on message - receiveMessage(message); - - // close socket - socket.close(); - } catch (IOException e) { - handleException(new InstanceException(e)); - } - } - }; - - // start socket thread - thread.start(); - } catch (SocketException e) { - if (!server.isClosed()) { - handleException(new InstanceException(e)); - } - } catch (IOException e) { - handleException(new InstanceException(e)); - } - } - } - }; - - thread.start(); - } - - // do client tasks - private void doClient() throws InstanceException { - // get localhost address - InetAddress address = null; - try { - address = InetAddress.getByName(null); - } catch (UnknownHostException e) { - throw new InstanceException(e); - } - - // try to establish connection to server - Socket socket = null; - try { - socket = new Socket(address, port); - } catch (IOException e) { - // connection failed try to start server - startServer(); - } - - // connection successful try to connect to server - if (socket != null) { - try { - // get message to be sent to first instance - String message = sendMessage(); - - // open writer - OutputStream os = socket.getOutputStream(); - DataOutputStream dos = new DataOutputStream(os); - - // open reader - InputStream is = socket.getInputStream(); - DataInputStream dis = new DataInputStream(is); - - // write message to server - if (message == null) { - dos.writeInt(-1); - } - else { - byte[] messageBytes = message.getBytes("UTF-8"); - - dos.writeInt(messageBytes.length); - dos.write(messageBytes); - } - - dos.flush(); - - // read response length from server - int length = dis.readInt(); - - // read response string from server - String response = null; - if (length > -1) { - byte[] responseBytes = new byte[length]; - int bytesRead = dis.read(responseBytes, 0, length); - response = new String(responseBytes, 0, bytesRead, "UTF-8"); - } - - // close writer and reader - dos.close(); - dis.close(); - - if (response.equals(APP_ID)) { - // validation successful - if (AUTO_EXIT) { - // perform pre-exit tasks - beforeExit(); - // exit this instance - System.exit(0); - } - } - else { - // validation failed, this is the first instance - startServer(); - } - } catch (IOException e) { - throw new InstanceException(e); - } finally { - // close socket - try { - if (socket != null) socket.close(); - } catch (IOException e) { - throw new InstanceException(e); - } - } - } - } - - // try to get port from lock file - private int lockFile() throws InstanceException { - // lock file path - String filePath = TEMP_DIR + File.separator + APP_ID + ".lock"; - File file = new File(filePath); - - // try to get port from lock file - if (file.exists()) { - BufferedReader br = null; - try { - br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); - return Integer.parseInt(br.readLine()); - } catch (IOException e) { - throw new InstanceException(e); - } catch (NumberFormatException e) { - // do nothing - } finally { - try { - if (br != null) br.close(); - } catch (IOException e) { - throw new InstanceException(e); - } - } - } - - return -1; - } - - // try to write port to lock file - private void lockFile(int port) throws InstanceException { - // lock file path - String filePath = TEMP_DIR + File.separator + APP_ID + ".lock"; - File file = new File(filePath); - - // try to write port to lock file - BufferedWriter bw = null; - try { - bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); - bw.write(String.valueOf(port)); - } catch (IOException e) { - throw new InstanceException(e); - } finally { - try { - if (bw != null) bw.close(); - } catch (IOException e) { - throw new InstanceException(e); - } - } - - // try to obtain file lock - try { - lockRAF = new RandomAccessFile(file, "rw"); - FileChannel fc = lockRAF.getChannel(); - fileLock = fc.tryLock(0, Long.MAX_VALUE, true); - if (fileLock == null) { - throw new InstanceException("Failed to obtain file lock"); - } - } catch (FileNotFoundException e) { - throw new InstanceException(e); - } catch (IOException e) { - throw new InstanceException(e); - } - } - - /** - * Free the lock if possible. This is only required to be called from the first instance. - * - * @deprecated Use freeLock() instead. - * @throws InstanceException throws InstanceException if it is unable to stop the server or release file lock - */ - @Deprecated - public void free() throws InstanceException { - freeLock(); - } - - /** - * Free the lock if possible. This is only required to be called from the first instance. - * - * @since 1.2 - * - * @return true if able to release lock, false otherwise - * @throws InstanceException throws InstanceException if it is unable to stop the server or release file lock - */ - public boolean freeLock() throws InstanceException { - try { - // close server socket - if (server != null) { - server.close(); - - // lock file path - String filePath = TEMP_DIR + File.separator + APP_ID + ".lock"; - File file = new File(filePath); - - // try to release file lock - if (fileLock != null) { - fileLock.release(); - } - - // try to close lock file RAF object - if (lockRAF != null) { - lockRAF.close(); - } - - // try to delete lock file - if (file.exists()) { - file.delete(); - } - - return true; - } - - return false; - } catch (IOException e) { - throw new InstanceException(e); - } - } - - /** - * Method used in first instance to receive messages from subsequent instances.

- * - * This method is not synchronized. - * - * @param message message received by first instance from subsequent instances - */ - protected abstract void receiveMessage(String message); - - /** - * Method used in subsequent instances to send message to first instance.

- * - * It is not recommended to perform blocking (long running) tasks here. Use beforeExit() method instead.
- * One exception to this rule is if you intend to perform some user interaction before sending the message.

- * - * This method is not synchronized. - * - * @return message sent from subsequent instances - */ - protected abstract String sendMessage(); - - /** - * Method to receive and handle exceptions occurring while first instance is listening for subsequent instances.

- * - * By default prints stack trace of all exceptions. Override this method to handle exceptions explicitly.

- * - * This method is not synchronized. - * - * @param exception exception occurring while first instance is listening for subsequent instances - */ - protected void handleException(Exception exception) { + public final String applicationId; + private final boolean autoExit; + + private Selector selector; + private ServerSocketChannel serverChannel; + + public Instance(final String applicationId) { + this(applicationId, true); + } + + public Instance(final String applicationId, final boolean autoExit) { + this.applicationId = applicationId; + this.autoExit = autoExit; + } + + /** + * Try to obtain lock. If not possible, send data to first instance. + * + * @throws InstanceException throws InstanceException if it is unable to start a server or connect to server + */ + public void acquireLock(boolean findExisting) throws InstanceException { + Path lockFile = getLockFile(findExisting); + + if(!Files.exists(lockFile)) { + startServer(lockFile); + createSymlink(lockFile); + } else { + doClient(lockFile); + } + } + + private void startServer(Path lockFile) throws InstanceException { + try { + selector = Selector.open(); + UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(lockFile); + serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + serverChannel.bind(socketAddress); + serverChannel.configureBlocking(false); + serverChannel.register(selector, SelectionKey.OP_ACCEPT); + lockFile.toFile().deleteOnExit(); + } catch(Exception e) { + throw new InstanceException("Could not open UNIX socket at " + lockFile.toAbsolutePath(), e); + } + + Thread thread = new Thread(() -> { + while(true) { + try { + selector.select(); + Set selectedKeys = selector.selectedKeys(); + Iterator iter = selectedKeys.iterator(); + while(iter.hasNext()) { + SelectionKey key = iter.next(); + if(key.isAcceptable()) { + SocketChannel client = serverChannel.accept(); + client.configureBlocking(false); + client.register(selector, SelectionKey.OP_READ); + } + if(key.isReadable()) { + try(SocketChannel clientChannel = (SocketChannel)key.channel()) { + String message = readMessage(clientChannel); + clientChannel.write(ByteBuffer.wrap(applicationId.getBytes(StandardCharsets.UTF_8))); + receiveMessage(message); + } + } + iter.remove(); + } + } catch(SocketException e) { + if(serverChannel.isOpen()) { + handleException(new InstanceException(e)); + } + } catch(Exception e) { + handleException(new InstanceException(e)); + } + } + }); + + thread.setDaemon(true); + thread.setName("SparrowInstanceListener"); + thread.start(); + } + + private void doClient(Path lockFile) throws InstanceException { + try(SocketChannel client = SocketChannel.open(UnixDomainSocketAddress.of(lockFile))) { + String message = sendMessage(); + client.write(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8))); + client.shutdownOutput(); + + String response = readMessage(client); + if(response.equals(applicationId) && autoExit) { + beforeExit(); + System.exit(0); + } + } catch(Exception e) { + throw new InstanceException("Could not open client connection to existing instance", e); + } + } + + private static String readMessage(SocketChannel clientChannel) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(1024); + StringBuilder messageBuilder = new StringBuilder(); + while(clientChannel.read(buffer) != -1) { + buffer.flip(); + messageBuilder.append(new String(buffer.array(), 0, buffer.limit())); + buffer.clear(); + } + + return messageBuilder.toString(); + } + + private Path getLockFile(boolean findExisting) { + if(findExisting) { + Path symlink = getSystemSymlinkPath(); + try { + if(Files.exists(symlink)) { + return Files.readSymbolicLink(symlink); + } + } catch(IOException e) { + log.warn("Could not follow symbolic link at " + symlink.toAbsolutePath()); + } catch(Exception e) { + //ignore + } + } + + return Storage.getSparrowDir().toPath().resolve(applicationId + ".lock"); + } + + private void createSymlink(Path lockFile) { + Path symlink = getSystemSymlinkPath(); + try { + if(!Files.exists(symlink, LinkOption.NOFOLLOW_LINKS)) { + Files.createSymbolicLink(symlink, lockFile); + log.warn("Created symlink at " + symlink.toAbsolutePath()); + symlink.toFile().deleteOnExit(); + } + } catch(IOException e) { + log.warn("Could not create symlink " + symlink.toAbsolutePath() + " to lockFile at " + lockFile.toAbsolutePath()); + } catch(Exception e) { + //ignore + } + } + + private Path getSystemSymlinkPath() { + return Path.of(System.getProperty("java.io.tmpdir")).resolve(applicationId + ".link"); + } + + /** + * Free the lock if possible. This is only required to be called from the first instance. + * + * @throws InstanceException throws InstanceException if it is unable to stop the server or release file lock + */ + public void freeLock() throws InstanceException { + try { + if(serverChannel != null && serverChannel.isOpen()) { + serverChannel.close(); + } + + Files.deleteIfExists(getSystemSymlinkPath()); + Files.deleteIfExists(getLockFile(false)); + } catch(Exception e) { + throw new InstanceException(e); + } + } + + /** + * Method used in first instance to receive messages from subsequent instances.

+ * + * This method is not synchronized. + * + * @param message message received by first instance from subsequent instances + */ + protected abstract void receiveMessage(String message); + + /** + * Method used in subsequent instances to send message to first instance.

+ * + * It is not recommended to perform blocking (long running) tasks here. Use beforeExit() method instead.
+ * One exception to this rule is if you intend to perform some user interaction before sending the message.

+ * + * This method is not synchronized. + * + * @return message sent from subsequent instances + */ + protected abstract String sendMessage(); + + /** + * Method to receive and handle exceptions occurring while first instance is listening for subsequent instances.

+ * + * By default prints stack trace of all exceptions. Override this method to handle exceptions explicitly.

+ * + * This method is not synchronized. + * + * @param exception exception occurring while first instance is listening for subsequent instances + */ + protected void handleException(Exception exception) { log.error("Error listening for instances", exception); - } - - /** - * This method is called before exiting from subsequent instances.

- * - * Override this method to perform blocking tasks before exiting from subsequent instances.
- * This method is not invoked if auto exit is turned off.

- * - * This method is not synchronized. - * - * @since 1.2 - */ - protected void beforeExit() {} - + } + + /** + * This method is called before exiting from subsequent instances.

+ * + * Override this method to perform blocking tasks before exiting from subsequent instances.
+ * This method is not invoked if auto exit is turned off.

+ * + * This method is not synchronized. + */ + protected void beforeExit() {} } diff --git a/src/main/java/com/sparrowwallet/sparrow/instance/InstanceList.java b/src/main/java/com/sparrowwallet/sparrow/instance/InstanceList.java index 44cfd8c2..5986d8ea 100644 --- a/src/main/java/com/sparrowwallet/sparrow/instance/InstanceList.java +++ b/src/main/java/com/sparrowwallet/sparrow/instance/InstanceList.java @@ -7,88 +7,14 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonParser; -/** - * The InstanceList class is a logical entry point to the library which extends the functionality of the Instance class.
- * It allows to create an application lock or free it and send and receive messages between first and subsequent instances.

- * - * This class is intended for passing a list of strings instead of a single string from the subsequent instance to the first instance.

- * - *
- *	// unique application ID
- *	String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6";
- *	
- *	// create Instance instance
- *	Instance unique = new InstanceList(APP_ID) {
- *	    @Override
- *	    protected List<String> sendMessageList() {
- *	        List<String> messageList = new ArrayList<String>();
- *	        
- *	        messageList.add("Message 1");
- *	        messageList.add("Message 2");
- *	        messageList.add("Message 3");
- *	        messageList.add("Message 4");
- *	        
- *	        return messageList;
- *	    }
- *
- *	    @Override
- *	    protected void receiveMessageList(List<String> messageList) {
- *	        for (String message : messageList) {
- *	            System.out.println(message);
- *	        }
- *	    }
- *	};
- *	
- *	// try to obtain lock
- *	try {
- *	    unique.acquireLock();
- *	} catch (InstanceException e) {
- *	    e.printStackTrace();
- *	}
- *	
- *	...
- *	
- *	// try to free the lock before exiting program
- *	try {
- *	    unique.freeLock();
- *	} catch (InstanceException e) {
- *	    e.printStackTrace();
- *	}
- * 
- * - * @author Pratanu Mandal - * @since 1.3 - * - */ public abstract class InstanceList extends Instance { - /** - * Parameterized constructor.
- * This constructor configures to automatically exit the application for subsequent instances.

- * - * The APP_ID must be as unique as possible. - * Avoid generic names like "my_app_id" or "hello_world".
- * A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. - * - * @param APP_ID Unique string representing the application ID - */ - public InstanceList(String APP_ID) { - super(APP_ID); + public InstanceList(String applicationId) { + super(applicationId); } - /** - * Parameterized constructor.
- * This constructor allows to explicitly specify the exit strategy for subsequent instances.

- * - * The APP_ID must be as unique as possible. - * Avoid generic names like "my_app_id" or "hello_world".
- * A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. - * - * @param APP_ID Unique string representing the application ID - * @param AUTO_EXIT If true, automatically exit the application for subsequent instances - */ - public InstanceList(String APP_ID, boolean AUTO_EXIT) { - super(APP_ID, AUTO_EXIT); + public InstanceList(String applicationId, boolean autoExit) { + super(applicationId, autoExit); } /** @@ -101,16 +27,15 @@ public abstract class InstanceList extends Instance { */ @Override protected final void receiveMessage(String message) { - if (message == null) { + if(message == null) { receiveMessageList(null); - } - else { + } else { // parse the JSON array string into an array of string arguments JsonArray jsonArgs = JsonParser.parseString(message).getAsJsonArray(); List stringArgs = new ArrayList(jsonArgs.size()); - for (int i = 0; i < jsonArgs.size(); i++) { + for(int i = 0; i < jsonArgs.size(); i++) { JsonElement element = jsonArgs.get(i); stringArgs.add(element.getAsString()); } @@ -137,10 +62,11 @@ public abstract class InstanceList extends Instance { JsonArray jsonArgs = new JsonArray(); List stringArgs = sendMessageList(); + if(stringArgs == null) { + return null; + } - if (stringArgs == null) return null; - - for (String arg : stringArgs) { + for(String arg : stringArgs) { jsonArgs.add(arg); } @@ -168,5 +94,4 @@ public abstract class InstanceList extends Instance { * @return list of messages sent from subsequent instances */ protected abstract List sendMessageList(); - } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java index 89363744..41e4f481 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java @@ -535,7 +535,7 @@ public class Storage { return certsDir; } - static File getSparrowDir() { + public static File getSparrowDir() { File sparrowDir; if(Network.get() != Network.MAINNET) { sparrowDir = new File(getSparrowHome(), Network.get().getName()); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/TorUtils.java b/src/main/java/com/sparrowwallet/sparrow/net/TorUtils.java index 1df3ec09..1f4fa27c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/TorUtils.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/TorUtils.java @@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory; import java.io.*; import java.net.Socket; +import java.net.SocketTimeoutException; import java.nio.file.Files; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,11 +29,14 @@ public class TorUtils { } else { HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1); try(Socket socket = new Socket(control.getHost(), control.getPort())) { + socket.setSoTimeout(1500); if(authenticate(socket)) { writeNewNym(socket); } } catch(TorAuthenticationException e) { log.warn("Error authenticating to Tor at " + control + ", server returned " + e.getMessage()); + } catch(SocketTimeoutException e) { + log.warn("Timeout reading from " + control + ", is this a Tor ControlPort?"); } catch(Exception e) { log.warn("Error connecting to " + control + ", no Tor ControlPort configured?"); }