use unix sockets in sparrow home for instance checks and message passing, with system symlink to find existing instances for files and uris

This commit is contained in:
Craig Raw 2024-03-27 12:45:05 +02:00
parent 08ec158d19
commit d1a353ae53
5 changed files with 238 additions and 612 deletions

View file

@ -16,7 +16,7 @@ import java.io.File;
import java.util.*; import java.util.*;
public class SparrowWallet { 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_NAME = "Sparrow";
public static final String APP_VERSION = "1.8.5"; public static final String APP_VERSION = "1.8.5";
public static final String APP_VERSION_SUFFIX = ""; public static final String APP_VERSION_SUFFIX = "";
@ -79,7 +79,7 @@ public class SparrowWallet {
try { try {
instance = new Instance(fileUriArguments); 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) { } catch(InstanceException e) {
getLogger().error("Could not access application lock", e); getLogger().error("Could not access application lock", e);
} }
@ -130,7 +130,7 @@ public class SparrowWallet {
private final List<String> fileUriArguments; private final List<String> fileUriArguments;
public Instance(List<String> fileUriArguments) { public Instance(List<String> fileUriArguments) {
super(SparrowWallet.APP_ID + "." + Network.get(), !fileUriArguments.isEmpty()); super(SparrowWallet.APP_ID + "." + Network.get(), true);
this.fileUriArguments = fileUriArguments; this.fileUriArguments = fileUriArguments;
} }

View file

@ -1,487 +1,187 @@
/**
* 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; package com.sparrowwallet.sparrow.instance;
import com.sparrowwallet.sparrow.io.Storage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.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.SocketException;
import java.net.UnknownHostException; import java.net.StandardProtocolFamily;
import java.nio.channels.FileChannel; import java.net.UnixDomainSocketAddress;
import java.nio.channels.FileLock; 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 <code>Instance</code> class is the primary logical entry point to the library.<br>
* It allows to create an application lock or free it and send and receive messages between first and subsequent instances.<br><br>
*
* <pre>
* // unique application ID
* String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6";
*
* // create Instance instance
* Instance unique = new Instance(APP_ID) {
* &nbsp;&nbsp;&nbsp;&nbsp;&#64;Override
* &nbsp;&nbsp;&nbsp;&nbsp;protected void receiveMessage(String message) {
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// print received message (timestamp)
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(message);
* &nbsp;&nbsp;&nbsp;&nbsp;}
* &nbsp;&nbsp;&nbsp;&nbsp;
* &nbsp;&nbsp;&nbsp;&nbsp;&#64;Override
* &nbsp;&nbsp;&nbsp;&nbsp;protected String sendMessage() {
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// send timestamp as message
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Timestamp ts = new Timestamp(new Date().getTime());
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return "Another instance launch attempted: " + ts.toString();
* &nbsp;&nbsp;&nbsp;&nbsp;}
* };
*
* // try to obtain lock
* try {
* &nbsp;&nbsp;&nbsp;&nbsp;unique.acquireLock();
* } catch (InstanceException e) {
* &nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();
* }
*
* ...
*
* // try to free the lock before exiting program
* try {
* &nbsp;&nbsp;&nbsp;&nbsp;unique.freeLock();
* } catch (InstanceException e) {
* &nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();
* }
* </pre>
*
* @author Pratanu Mandal
* @since 1.3
*
*/
public abstract class Instance { public abstract class Instance {
private static final Logger log = LoggerFactory.getLogger(Instance.class); private static final Logger log = LoggerFactory.getLogger(Instance.class);
// starting position of port check public final String applicationId;
private static final int PORT_START = 7221; private final boolean autoExit;
// system temporary directory path private Selector selector;
private static final String TEMP_DIR = System.getProperty("java.io.tmpdir"); private ServerSocketChannel serverChannel;
/** public Instance(final String applicationId) {
* Unique string representing the application ID.<br><br> this(applicationId, true);
*
* The APP_ID must be as unique as possible.
* Avoid generic names like "my_app_id" or "hello_world".<br>
* 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.<br>
* This constructor configures to automatically exit the application for subsequent instances.<br><br>
*
* The APP_ID must be as unique as possible.
* Avoid generic names like "my_app_id" or "hello_world".<br>
* 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);
} }
/** public Instance(final String applicationId, final boolean autoExit) {
* Parameterized constructor.<br> this.applicationId = applicationId;
* This constructor allows to explicitly specify the exit strategy for subsequent instances.<br><br> this.autoExit = autoExit;
*
* The APP_ID must be as unique as possible.
* Avoid generic names like "my_app_id" or "hello_world".<br>
* 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. * Try to obtain lock. If not possible, send data to first instance.
* *
* @deprecated Use <code>acquireLock()</code> instead.
* @throws InstanceException throws InstanceException if it is unable to start a server or connect to server * @throws InstanceException throws InstanceException if it is unable to start a server or connect to server
*/ */
@Deprecated public void acquireLock(boolean findExisting) throws InstanceException {
public void lock() throws InstanceException { Path lockFile = getLockFile(findExisting);
acquireLock();
if(!Files.exists(lockFile)) {
startServer(lockFile);
createSymlink(lockFile);
} else {
doClient(lockFile);
}
} }
/** private void startServer(Path lockFile) throws InstanceException {
* 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 { try {
server = new ServerSocket(port, 50, InetAddress.getByName(null)); selector = Selector.open();
break; UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(lockFile);
} catch (IOException e) { serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
port++; 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);
} }
// try to lock file Thread thread = new Thread(() -> {
lockFile(port); while(true) {
// 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 { try {
// establish connection selector.select();
final Socket socket = server.accept(); Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectedKeys.iterator();
// handle socket on a different thread to allow parallel connections while(iter.hasNext()) {
Thread thread = new Thread() { SelectionKey key = iter.next();
@Override if(key.isAcceptable()) {
public void run() { SocketChannel client = serverChannel.accept();
try { client.configureBlocking(false);
// open writer client.register(selector, SelectionKey.OP_READ);
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");
} }
if(key.isReadable()) {
// write response to client try(SocketChannel clientChannel = (SocketChannel)key.channel()) {
if (APP_ID == null) { String message = readMessage(clientChannel);
dos.writeInt(-1); clientChannel.write(ByteBuffer.wrap(applicationId.getBytes(StandardCharsets.UTF_8)));
}
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); receiveMessage(message);
}
// close socket }
socket.close(); iter.remove();
} catch (IOException e) { }
} catch(SocketException e) {
if(serverChannel.isOpen()) {
handleException(new InstanceException(e));
}
} catch(Exception e) {
handleException(new InstanceException(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.setDaemon(true);
thread.setName("SparrowInstanceListener");
thread.start(); thread.start();
} }
// do client tasks private void doClient(Path lockFile) throws InstanceException {
private void doClient() throws InstanceException { try(SocketChannel client = SocketChannel.open(UnixDomainSocketAddress.of(lockFile))) {
// 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(); String message = sendMessage();
client.write(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8)));
client.shutdownOutput();
// open writer String response = readMessage(client);
OutputStream os = socket.getOutputStream(); if(response.equals(applicationId) && autoExit) {
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(); beforeExit();
// exit this instance
System.exit(0); System.exit(0);
} }
} } catch(Exception e) {
else { throw new InstanceException("Could not open client connection to existing instance", e);
// 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 static String readMessage(SocketChannel clientChannel) throws IOException {
private int lockFile() throws InstanceException { ByteBuffer buffer = ByteBuffer.allocate(1024);
// lock file path StringBuilder messageBuilder = new StringBuilder();
String filePath = TEMP_DIR + File.separator + APP_ID + ".lock"; while(clientChannel.read(buffer) != -1) {
File file = new File(filePath); buffer.flip();
messageBuilder.append(new String(buffer.array(), 0, buffer.limit()));
// try to get port from lock file buffer.clear();
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 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 -1; return Storage.getSparrowDir().toPath().resolve(applicationId + ".lock");
} }
// try to write port to lock file private void createSymlink(Path lockFile) {
private void lockFile(int port) throws InstanceException { Path symlink = getSystemSymlinkPath();
// 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 { try {
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); if(!Files.exists(symlink, LinkOption.NOFOLLOW_LINKS)) {
bw.write(String.valueOf(port)); Files.createSymbolicLink(symlink, lockFile);
} catch (IOException e) { log.warn("Created symlink at " + symlink.toAbsolutePath());
throw new InstanceException(e); symlink.toFile().deleteOnExit();
} finally { }
try { } catch(IOException e) {
if (bw != null) bw.close(); log.warn("Could not create symlink " + symlink.toAbsolutePath() + " to lockFile at " + lockFile.toAbsolutePath());
} catch (IOException e) { } catch(Exception e) {
throw new InstanceException(e); //ignore
} }
} }
// try to obtain file lock private Path getSystemSymlinkPath() {
try { return Path.of(System.getProperty("java.io.tmpdir")).resolve(applicationId + ".link");
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. * Free the lock if possible. This is only required to be called from the first instance.
* *
* @deprecated Use <code>freeLock()</code> instead.
* @throws InstanceException throws InstanceException if it is unable to stop the server or release file lock * @throws InstanceException throws InstanceException if it is unable to stop the server or release file lock
*/ */
@Deprecated public void freeLock() throws InstanceException {
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 { try {
// close server socket if(serverChannel != null && serverChannel.isOpen()) {
if (server != null) { serverChannel.close();
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 Files.deleteIfExists(getSystemSymlinkPath());
if (lockRAF != null) { Files.deleteIfExists(getLockFile(false));
lockRAF.close(); } catch(Exception e) {
}
// try to delete lock file
if (file.exists()) {
file.delete();
}
return true;
}
return false;
} catch (IOException e) {
throw new InstanceException(e); throw new InstanceException(e);
} }
} }
@ -527,9 +227,6 @@ public abstract class Instance {
* This method is not invoked if auto exit is turned off.<br><br> * This method is not invoked if auto exit is turned off.<br><br>
* *
* This method is not synchronized. * This method is not synchronized.
*
* @since 1.2
*/ */
protected void beforeExit() {} protected void beforeExit() {}
} }

View file

@ -7,88 +7,14 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
/**
* The <code>InstanceList</code> class is a logical entry point to the library which extends the functionality of the <code>Instance</code> class.<br>
* It allows to create an application lock or free it and send and receive messages between first and subsequent instances.<br><br>
*
* This class is intended for passing a list of strings instead of a single string from the subsequent instance to the first instance.<br><br>
*
* <pre>
* // unique application ID
* String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6";
*
* // create Instance instance
* Instance unique = new InstanceList(APP_ID) {
* &nbsp;&nbsp;&nbsp;&nbsp;&#64;Override
* &nbsp;&nbsp;&nbsp;&nbsp;protected List&lt;String&gt; sendMessageList() {
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List&lt;String&gt; messageList = new ArrayList&lt;String&gt;();
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;messageList.add("Message 1");
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;messageList.add("Message 2");
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;messageList.add("Message 3");
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;messageList.add("Message 4");
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return messageList;
* &nbsp;&nbsp;&nbsp;&nbsp;}
*
* &nbsp;&nbsp;&nbsp;&nbsp;&#64;Override
* &nbsp;&nbsp;&nbsp;&nbsp;protected void receiveMessageList(List&lt;String&gt; messageList) {
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (String message : messageList) {
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(message);
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
* &nbsp;&nbsp;&nbsp;&nbsp;}
* };
*
* // try to obtain lock
* try {
* &nbsp;&nbsp;&nbsp;&nbsp;unique.acquireLock();
* } catch (InstanceException e) {
* &nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();
* }
*
* ...
*
* // try to free the lock before exiting program
* try {
* &nbsp;&nbsp;&nbsp;&nbsp;unique.freeLock();
* } catch (InstanceException e) {
* &nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();
* }
* </pre>
*
* @author Pratanu Mandal
* @since 1.3
*
*/
public abstract class InstanceList extends Instance { public abstract class InstanceList extends Instance {
/** public InstanceList(String applicationId) {
* Parameterized constructor.<br> super(applicationId);
* This constructor configures to automatically exit the application for subsequent instances.<br><br>
*
* The APP_ID must be as unique as possible.
* Avoid generic names like "my_app_id" or "hello_world".<br>
* 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, boolean autoExit) {
* Parameterized constructor.<br> super(applicationId, autoExit);
* This constructor allows to explicitly specify the exit strategy for subsequent instances.<br><br>
*
* The APP_ID must be as unique as possible.
* Avoid generic names like "my_app_id" or "hello_world".<br>
* 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);
} }
/** /**
@ -101,16 +27,15 @@ public abstract class InstanceList extends Instance {
*/ */
@Override @Override
protected final void receiveMessage(String message) { protected final void receiveMessage(String message) {
if (message == null) { if(message == null) {
receiveMessageList(null); receiveMessageList(null);
} } else {
else {
// parse the JSON array string into an array of string arguments // parse the JSON array string into an array of string arguments
JsonArray jsonArgs = JsonParser.parseString(message).getAsJsonArray(); JsonArray jsonArgs = JsonParser.parseString(message).getAsJsonArray();
List<String> stringArgs = new ArrayList<String>(jsonArgs.size()); List<String> stringArgs = new ArrayList<String>(jsonArgs.size());
for (int i = 0; i < jsonArgs.size(); i++) { for(int i = 0; i < jsonArgs.size(); i++) {
JsonElement element = jsonArgs.get(i); JsonElement element = jsonArgs.get(i);
stringArgs.add(element.getAsString()); stringArgs.add(element.getAsString());
} }
@ -137,10 +62,11 @@ public abstract class InstanceList extends Instance {
JsonArray jsonArgs = new JsonArray(); JsonArray jsonArgs = new JsonArray();
List<String> stringArgs = sendMessageList(); List<String> stringArgs = sendMessageList();
if(stringArgs == null) {
return null;
}
if (stringArgs == null) return null; for(String arg : stringArgs) {
for (String arg : stringArgs) {
jsonArgs.add(arg); jsonArgs.add(arg);
} }
@ -168,5 +94,4 @@ public abstract class InstanceList extends Instance {
* @return list of messages sent from subsequent instances * @return list of messages sent from subsequent instances
*/ */
protected abstract List<String> sendMessageList(); protected abstract List<String> sendMessageList();
} }

View file

@ -535,7 +535,7 @@ public class Storage {
return certsDir; return certsDir;
} }
static File getSparrowDir() { public static File getSparrowDir() {
File sparrowDir; File sparrowDir;
if(Network.get() != Network.MAINNET) { if(Network.get() != Network.MAINNET) {
sparrowDir = new File(getSparrowHome(), Network.get().getName()); sparrowDir = new File(getSparrowHome(), Network.get().getName());

View file

@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory;
import java.io.*; import java.io.*;
import java.net.Socket; import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -28,11 +29,14 @@ public class TorUtils {
} else { } else {
HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1); HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1);
try(Socket socket = new Socket(control.getHost(), control.getPort())) { try(Socket socket = new Socket(control.getHost(), control.getPort())) {
socket.setSoTimeout(1500);
if(authenticate(socket)) { if(authenticate(socket)) {
writeNewNym(socket); writeNewNym(socket);
} }
} catch(TorAuthenticationException e) { } catch(TorAuthenticationException e) {
log.warn("Error authenticating to Tor at " + control + ", server returned " + e.getMessage()); 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) { } catch(Exception e) {
log.warn("Error connecting to " + control + ", no Tor ControlPort configured?"); log.warn("Error connecting to " + control + ", no Tor ControlPort configured?");
} }