package org.freeplane.main.application;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import org.apache.commons.lang.StringUtils;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.util.Compat;
import org.freeplane.core.util.LogUtils;
import org.freeplane.main.application.CommandLineParser.Options;
public class SingleInstanceManager {
private File lockFile = new File(Compat.getApplicationUserDirectory(), "single_instance.lock");
private boolean isSingleInstanceMode;
private boolean isSingleInstanceForceMode;
private Integer port;
private boolean isSlave;
private boolean isMasterPresent;
final private FreeplaneStarter starter;
public SingleInstanceManager(FreeplaneStarter starter) {
this.starter = starter;
final ResourceController resourceController = starter.getResourceController();
isSingleInstanceMode = resourceController.getBooleanProperty("single_instance");
isSingleInstanceForceMode = resourceController.getBooleanProperty("single_instance_force");
}
public void start(String[] args) {
final Options options = CommandLineParser.parse(args);
final String[] filesToLoad = options.getFilesToOpenAsArray();
if (isSingleInstanceMode && !options.hasMenuItemsToExecute()) {
initLockFile();
if (filesToLoad.length == 0 && !isSingleInstanceForceMode && checkIsMasterPresent()) {
isMasterPresent = true;
startStandAlone(filesToLoad);
}
else if (!startAsSlave(filesToLoad)) {
if (!startAsMaster(filesToLoad)) {
startStandAlone(filesToLoad);
}
}
}
else {
startStandAlone(filesToLoad);
}
}
private boolean checkIsMasterPresent() {
if (port == null)
return false;
try {
Socket clientSocket = new Socket(InetAddress.getLocalHost(), port);
clientSocket.close();
LogUtils.info("master is present.");
return true;
}
catch (Exception e) {
// this is only a check - we'll log later
return false;
}
}
public boolean isSlave() {
return isSlave;
}
public boolean isMasterPresent() {
return isSlave || isMasterPresent;
}
private boolean startAsSlave(String[] filesToLoad) {
if (port != null) {
isSlave = openFilesInMaster(filesToLoad);
return isSlave;
}
return false;
}
private boolean openFilesInMaster(String[] filesToLoad) {
if (port == null)
throw new IllegalArgumentException("port may not be null");
try {
Socket clientSocket = new Socket(InetAddress.getLocalHost(), port);
OutputStream out = clientSocket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(filesToLoad);
oos.close();
clientSocket.close();
LogUtils.info("Successfully notified first instance.");
return true;
}
catch (UnknownHostException e) {
LogUtils.severe(e.getMessage(), e);
return false;
}
catch (IOException e) {
LogUtils.warn("Error connecting to existing instance (stale lockfiles may cause this).", e);
return false;
}
}
private boolean startAsMaster(String[] filesToLoad) {
try {
// port number 0: use any free socket
final ServerSocket socket = new ServerSocket(0, 10, InetAddress.getLocalHost());
port = socket.getLocalPort();
LogUtils.info("Listening for application instances on socket " + port);
createLockFile();
Thread filesToOpenListenerThread = new Thread(new Runnable() {
public void run() {
boolean socketClosed = false;
while (!socketClosed) {
if (socket.isClosed()) {
socketClosed = true;
}
else {
try {
Socket client = socket.accept();
ObjectInputStream in = new ObjectInputStream(client.getInputStream());
String[] filesToLoadForClient = (String[]) in.readObject();
LogUtils.info("opening '" + StringUtils.join(filesToLoadForClient, "', '")
+ "' for client");
in.close();
client.close();
starter.loadMapsLater(filesToLoadForClient);
}
catch (SecurityException e) {
// this happens when the master is currently executing a script
LogUtils.warn("master is currently not accepting new files. Try again later", e);
}
catch (IOException e) {
socketClosed = true;
}
catch (ClassNotFoundException e) {
// this should never happen
throw new RuntimeException("implementation error", e);
}
}
}
}
});
filesToOpenListenerThread.start();
return true;
// listen
}
catch (UnknownHostException e) {
LogUtils.severe(e.getMessage(), e);
return false;
}
catch (IOException e) {
LogUtils.severe(e.getMessage(), e);
return false;
}
}
private void createLockFile() throws IOException {
final RandomAccessFile randomAccessLockFile = new RandomAccessFile(lockFile, "rwd");
randomAccessLockFile.writeBytes(port.toString());
randomAccessLockFile.close();
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
lockFile.delete();
}
catch (Exception e) {
error("Unable to remove lock file: " + lockFile, e);
}
}
});
}
private void startStandAlone(String[] filesToLoad) {
// do nothing - whatever is needed will happen later
}
/**
* opens the lock file and tries to get a lock for it.
* If it is locked already then try to read the port number from it.
*/
private boolean initLockFile() {
try {
if (lockFile.exists()) {
// slave: read the port from the file
final RandomAccessFile randomAccessLockFile = new RandomAccessFile(lockFile, "r");
String portAsString = randomAccessLockFile.readLine().trim();
randomAccessLockFile.close();
port = Integer.parseInt(portAsString);
}
}
catch (Exception e) {
error("Unable to create and/or lock file: " + lockFile, e);
}
return false;
}
private void error(String message, Exception e) {
LogUtils.severe(message, e);
}
}