/**
* Xtreme Media Player a cross-platform media player.
* Copyright (C) 2005-2011 Besmir Beqiri
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package xtrememp;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import javax.swing.SwingUtilities;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xtrememp.playlist.PlaylistIO;
import xtrememp.playlist.PlaylistItem;
import xtrememp.util.file.AudioFileFilter;
import xtrememp.util.file.PlaylistFileFilter;
/**
*
* @author Besmir Beqiri
*/
public class MultipleInstancesHandler {
private final Logger logger = LoggerFactory.getLogger(MultipleInstancesHandler.class);
/** The instance. */
private static final MultipleInstancesHandler instance = new MultipleInstancesHandler();
private final AudioFileFilter audioFileFilter = AudioFileFilter.INSTANCE;
private final PlaylistFileFilter playlistFileFilter = PlaylistFileFilter.INSTANCE;
/** Port number for multiple instances socket communication. */
public static final int MULTIPLE_INSTANCES_SOCKET = 9999;
private ServerSocket serverSocket;
private static boolean closing = false;
/**
* Instantiates a new multiple instances handler.
*/
private MultipleInstancesHandler() {
}
/**
* Gets the single instance of MultipleInstancesHandler.
*
* @return single instance of MultipleInstancesHandler
*/
public static MultipleInstancesHandler getInstance() {
return instance;
}
/**
* This class is responsible of listening to server socket and accept
* connections from "slave" instances.
*/
private class SocketListener extends Thread {
private ServerSocket serverSocket;
private PlaylistItemQueue queue;
/**
* Instantiates a new socket listener.
*
* @param serverSocket the server socket
* @param queue the queue
*/
SocketListener(ServerSocket serverSocket, PlaylistItemQueue queue) {
super();
this.serverSocket = serverSocket;
this.queue = queue;
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
@Override
public void run() {
Socket s = null;
BufferedReader br = null;
BufferedOutputStream bos = null;
try {
while (true) {
s = serverSocket.accept();
// Once a connection arrives, read args
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
File file = new File(line);
logger.info("Received connection with content: {}", line);
if (audioFileFilter.accept(file)) {
String fileName = file.getName().substring(0, file.getName().lastIndexOf(".")).trim();
PlaylistItem newPli = new PlaylistItem(fileName, file.getAbsolutePath(), -1, true);
queue.addItem(newPli);
} else if (playlistFileFilter.accept(file)) {
List<PlaylistItem> pliList = PlaylistIO.load(file.getAbsolutePath());
for (PlaylistItem pli : pliList) {
queue.addItem(pli);
}
} else {
// process line
}
}
IOUtils.closeQuietly(br);
IOUtils.closeQuietly(s);
logger.info("Connection finished");
}
} catch (Exception ex) {
if (!closing) {
logger.error(ex.getMessage(), ex);
}
} finally {
IOUtils.closeQuietly(bos);
IOUtils.closeQuietly(br);
IOUtils.closeQuietly(s);
}
}
}
/**
* This class is responsible of create a queue of songs to be added. When
* opening multiple files, OS launch a "slave" instance for every file, so
* this queue adds songs in the order connections are made, and when no more
* connections are received, then add to playlist.
*/
private class PlaylistItemQueue extends Thread {
private List<PlaylistItem> queue;
private volatile long lastItemAdded = 0;
/**
* Instantiates a new songs queue.
*/
PlaylistItemQueue() {
queue = new ArrayList<PlaylistItem>();
}
/**
* Adds the item.
*
* @param item the item
*/
public void addItem(PlaylistItem item) {
queue.add(item);
lastItemAdded = System.currentTimeMillis();
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try {
while (true) {
if (!queue.isEmpty() && lastItemAdded < System.currentTimeMillis() - 1000) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// Get an auxiliar list with songs
ArrayList<PlaylistItem> auxList = new ArrayList<PlaylistItem>(queue);
// Clear songs queue
queue.clear();
// Add songs
XtremeMP.getInstance().addToPlaylistAndPlay(auxList);
}
});
}
// Wait always, even if songsQueue was not empty, to avoid entering again until queue is cleared
Thread.sleep(1000);
}
} catch (InterruptedException ex) {
logger.error(ex.getMessage(), ex);
}
}
}
/**
* Called when application exits.
*/
public void dispose() {
if (serverSocket != null) {
closing = true;
try {
serverSocket.close();
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
}
}
/**
* Tries to open a server socket to listen to other instances.
*
* @return true if server socket could be opened
*/
public boolean isFirstInstance() {
try {
// Open server socket
serverSocket = new ServerSocket(MULTIPLE_INSTANCES_SOCKET);
logger.info("Listening on port {} for other instances", MULTIPLE_INSTANCES_SOCKET);
// Initialize songs queue
PlaylistItemQueue queue = new PlaylistItemQueue();
// Initialize socket listener
SocketListener listener = new SocketListener(serverSocket, queue);
// Start threads
queue.start();
listener.start();
// Server socket could be opened, so this instance is a "master"
return true;
} catch (Exception ex) {
// Server socket could not be opened, so this instance is a "slave"
logger.info("Another instance is running");
return false;
}
}
/**
* Opens a client socket and sends arguments to "master".
*
* @param args the args
*/
public void sendArgumentsToFirstInstance(String... args) {
Socket clientSocket = null;
PrintWriter output = null;
try {
// Open client socket to communicate with "master"
clientSocket = new Socket("localhost", MULTIPLE_INSTANCES_SOCKET);
output = new PrintWriter(clientSocket.getOutputStream(), true);
for (String arg : args) {
File file = new File(arg);
if (audioFileFilter.accept(file) || playlistFileFilter.accept(file)) {
// Send args: audio files or play lists
logger.info("Sending arg {}", arg);
output.write(arg);
} else {
// It's a command
logger.info("Sending command {}", arg);
output.write(arg);
}
}
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
} finally {
IOUtils.closeQuietly(output);
IOUtils.closeQuietly(clientSocket);
}
}
}