mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
add unique4j classes to avoid module dependency issue
This commit is contained in:
parent
ed323a8388
commit
8f4cf9f2a0
6 changed files with 783 additions and 14 deletions
|
@ -71,9 +71,6 @@ 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'
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ import com.sparrowwallet.sparrow.net.PublicElectrumServer;
|
||||||
import com.sparrowwallet.sparrow.net.ServerType;
|
import com.sparrowwallet.sparrow.net.ServerType;
|
||||||
import com.sparrowwallet.sparrow.preferences.PreferenceGroup;
|
import com.sparrowwallet.sparrow.preferences.PreferenceGroup;
|
||||||
import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
|
import com.sparrowwallet.sparrow.preferences.PreferencesDialog;
|
||||||
|
import com.sparrowwallet.sparrow.instance.InstanceException;
|
||||||
|
import com.sparrowwallet.sparrow.instance.InstanceList;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
@ -23,8 +25,6 @@ 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.util.*;
|
import java.util.*;
|
||||||
|
@ -39,7 +39,7 @@ public class MainApp extends Application {
|
||||||
|
|
||||||
private Stage mainStage;
|
private Stage mainStage;
|
||||||
|
|
||||||
private static SparrowUnique sparrowUnique;
|
private static SparrowInstance sparrowInstance;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
|
@ -122,8 +122,8 @@ 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) {
|
if(sparrowInstance != null) {
|
||||||
sparrowUnique.freeLock();
|
sparrowInstance.freeLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,9 +175,9 @@ public class MainApp extends Application {
|
||||||
List<String> fileUriArguments = jCommander.getUnknownOptions();
|
List<String> fileUriArguments = jCommander.getUnknownOptions();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sparrowUnique = new SparrowUnique(fileUriArguments);
|
sparrowInstance = new SparrowInstance(fileUriArguments);
|
||||||
sparrowUnique.acquireLock(); //If fileUriArguments is not empty, will exit app after sending fileUriArguments if lock cannot be acquired
|
sparrowInstance.acquireLock(); //If fileUriArguments is not empty, will exit app after sending fileUriArguments if lock cannot be acquired
|
||||||
} catch(Unique4jException e) {
|
} catch(InstanceException e) {
|
||||||
getLogger().error("Could not access application lock", e);
|
getLogger().error("Could not access application lock", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,10 +194,10 @@ public class MainApp extends Application {
|
||||||
return LoggerFactory.getLogger(MainApp.class);
|
return LoggerFactory.getLogger(MainApp.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SparrowUnique extends Unique4jList {
|
private static class SparrowInstance extends InstanceList {
|
||||||
private final List<String> fileUriArguments;
|
private final List<String> fileUriArguments;
|
||||||
|
|
||||||
public SparrowUnique(List<String> fileUriArguments) {
|
public SparrowInstance(List<String> fileUriArguments) {
|
||||||
super(MainApp.APP_ID + "." + Network.get(), !fileUriArguments.isEmpty());
|
super(MainApp.APP_ID + "." + Network.get(), !fileUriArguments.isEmpty());
|
||||||
this.fileUriArguments = fileUriArguments;
|
this.fileUriArguments = fileUriArguments;
|
||||||
}
|
}
|
||||||
|
|
531
src/main/java/com/sparrowwallet/sparrow/instance/Instance.java
Normal file
531
src/main/java/com/sparrowwallet/sparrow/instance/Instance.java
Normal file
|
@ -0,0 +1,531 @@
|
||||||
|
/**
|
||||||
|
* 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 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
* @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();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author Pratanu Mandal
|
||||||
|
* @since 1.3
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class Instance {
|
||||||
|
|
||||||
|
// 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.<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.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameterized constructor.<br>
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @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 <code>acquireLock()</code> 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 <code>freeLock()</code> 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.<br><br>
|
||||||
|
*
|
||||||
|
* 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.<br><br>
|
||||||
|
*
|
||||||
|
* It is not recommended to perform blocking (long running) tasks here. Use <code>beforeExit()</code> method instead.<br>
|
||||||
|
* One exception to this rule is if you intend to perform some user interaction before sending the message.<br><br>
|
||||||
|
*
|
||||||
|
* 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.<br><br>
|
||||||
|
*
|
||||||
|
* By default prints stack trace of all exceptions. Override this method to handle exceptions explicitly.<br><br>
|
||||||
|
*
|
||||||
|
* This method is not synchronized.
|
||||||
|
*
|
||||||
|
* @param exception exception occurring while first instance is listening for subsequent instances
|
||||||
|
*/
|
||||||
|
protected void handleException(Exception exception) {
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called before exiting from subsequent instances.<br><br>
|
||||||
|
*
|
||||||
|
* Override this method to perform blocking tasks before exiting from subsequent instances.<br>
|
||||||
|
* This method is not invoked if auto exit is turned off.<br><br>
|
||||||
|
*
|
||||||
|
* This method is not synchronized.
|
||||||
|
*
|
||||||
|
* @since 1.2
|
||||||
|
*/
|
||||||
|
protected void beforeExit() {}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The <code>InstanceException</code> class is a wrapper for all exceptions thrown from Instance.
|
||||||
|
*
|
||||||
|
* @author Pratanu Mandal
|
||||||
|
* @since 1.1
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class InstanceException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 268060627071973613L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new exception with null as its detail message.
|
||||||
|
*/
|
||||||
|
public InstanceException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new exception with the specified detail message.<br>
|
||||||
|
* The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}.
|
||||||
|
*
|
||||||
|
* @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
|
||||||
|
*/
|
||||||
|
public InstanceException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new exception with the specified detail message and cause.<br><br>
|
||||||
|
* Note that the detail message associated with cause is not automatically incorporated in this exception's detail message.
|
||||||
|
*
|
||||||
|
* @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method).
|
||||||
|
* @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
|
||||||
|
*/
|
||||||
|
public InstanceException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new exception with the specified cause and a detail message of (cause==null ? null : cause.toString()) (which typically contains the class and detail message of cause).<br>
|
||||||
|
* This constructor is useful for exceptions that are little more than wrappers for other throwables (for example, {@link java.security.PrivilegedActionException}).
|
||||||
|
*
|
||||||
|
* @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
|
||||||
|
*/
|
||||||
|
public InstanceException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
package com.sparrowwallet.sparrow.instance;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
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) {
|
||||||
|
* @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();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author Pratanu Mandal
|
||||||
|
* @since 1.3
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class InstanceList extends Instance {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 InstanceList(String APP_ID) {
|
||||||
|
super(APP_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameterized constructor.<br>
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method used in first instance to receive and parse messages from subsequent instances.<br>
|
||||||
|
* The use of this method directly in <code>InstanceList</code> is discouraged. Use <code>receiveMessageList()</code> instead.<br><br>
|
||||||
|
*
|
||||||
|
* This method is not synchronized.
|
||||||
|
*
|
||||||
|
* @param message message received by first instance from subsequent instances
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected final void receiveMessage(String message) {
|
||||||
|
if (message == null) {
|
||||||
|
receiveMessageList(null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// parse the JSON array string into an array of string arguments
|
||||||
|
JsonArray jsonArgs = JsonParser.parseString(message).getAsJsonArray();
|
||||||
|
|
||||||
|
List<String> stringArgs = new ArrayList<String>(jsonArgs.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < jsonArgs.size(); i++) {
|
||||||
|
JsonElement element = jsonArgs.get(i);
|
||||||
|
stringArgs.add(element.getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the parsed string list
|
||||||
|
receiveMessageList(stringArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method used in subsequent instances to parse and send message to first instance.<br>
|
||||||
|
* The use of this method directly in <code>InstanceList</code> is discouraged. Use <code>sendMessageList()</code> instead.<br><br>
|
||||||
|
*
|
||||||
|
* It is not recommended to perform blocking (long running) tasks here. Use <code>beforeExit()</code> method instead.<br>
|
||||||
|
* One exception to this rule is if you intend to perform some user interaction before sending the message.<br><br>
|
||||||
|
*
|
||||||
|
* This method is not synchronized.
|
||||||
|
*
|
||||||
|
* @return message sent from subsequent instances
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected final String sendMessage() {
|
||||||
|
// convert arguments to JSON array string
|
||||||
|
JsonArray jsonArgs = new JsonArray();
|
||||||
|
|
||||||
|
List<String> stringArgs = sendMessageList();
|
||||||
|
|
||||||
|
if (stringArgs == null) return null;
|
||||||
|
|
||||||
|
for (String arg : stringArgs) {
|
||||||
|
jsonArgs.add(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the JSON array string
|
||||||
|
return jsonArgs.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used in first instance to receive list of messages from subsequent instances.<br><br>
|
||||||
|
*
|
||||||
|
* This method is not synchronized.
|
||||||
|
*
|
||||||
|
* @param messageList list of messages received by first instance from subsequent instances
|
||||||
|
*/
|
||||||
|
protected abstract void receiveMessageList(List<String> messageList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used in subsequent instances to send list of messages to first instance.<br><br>
|
||||||
|
*
|
||||||
|
* It is not recommended to perform blocking (long running) tasks here. Use <code>beforeExit()</code> method instead.<br>
|
||||||
|
* One exception to this rule is if you intend to perform some user interaction before sending the message.<br><br>
|
||||||
|
*
|
||||||
|
* This method is not synchronized.
|
||||||
|
*
|
||||||
|
* @return list of messages sent from subsequent instances
|
||||||
|
*/
|
||||||
|
protected abstract List<String> sendMessageList();
|
||||||
|
|
||||||
|
}
|
|
@ -27,7 +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;
|
||||||
|
requires com.google.gson;
|
||||||
}
|
}
|
Loading…
Reference in a new issue