package tk.baumi.jaraco;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Arrays;
import tk.baumi.jaraco.JaracoPlayer.PlayingChangeNetworkListener;
import tk.baumi.jaraco.JaracoPlayer.PlaylistChange;
/**
* <p>
* The class NetworkController is responsible for all networking operations.
* It registers observers to the Player and sends these informations to the client
* </p>
* <ul>
* <li>Playlist changes (whole playlist on each change)</li>
* <li>The current song (Artist + Title)</li>
* <li>Listing the files in the given folder</li>
* </ul>
*
* @author Manuel Baumgartner
*
*/
public class NetworkController {
private ServerSocket server;
private boolean serverRunning = true;
private Thread thAcceptRun, thBroadcastRun;
private JaracoPlayer playerInstance;
private JaracoGUI guiInstance;
private PlaylistChange playlistChange;
private PlayingChangeNetworkListener playChangeNetwork;
private ArrayList<Socket> clients;
private CloseSocket socketClose;
public interface CloseSocket{
public void closeSocket();
}
/**
* A broadcast sends a byte-stream to all connected clients.<br/>
* This could be the playlist or the currently played song.
* @param message The message as byte-array (MTU 1024)
*/
public void sendBroadcast(byte[] message) {
for(int i = 0; i < clients.size(); i++) {
Socket client = clients.get(i);
if(client != null && client.isConnected()) {
try {
client.getOutputStream().write(message);
} catch (IOException e) {
try {
client.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
} else {
try {
client.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* The Broadcast-run is responsible for receiving broadcast messages
* from the network <br/>
* It shows the availability to the non-yet-connected users.
*/
Runnable broadcastRun = new Runnable() {
@Override
public void run() {
try {
DatagramSocket udp = new DatagramSocket(8802);
byte[] buf = new byte[8];
DatagramPacket udpreceive = new DatagramPacket(buf, 8);
DatagramPacket udpsend;
udp.setBroadcast(true);
udp.setSoTimeout(1000);
while(serverRunning) {
try{
udp.receive(udpreceive);
byte[] addr = InetAddress.getLocalHost().getHostName().getBytes();
SocketAddress socketSender = udpreceive.getSocketAddress();
udpsend = new DatagramPacket(addr, addr.length, socketSender);
udp.send(udpsend);
} catch (Exception ex) {
//An exception will be thrown thrown
//if there it no packet to receive.
}
}
udp.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
/**
* The acceptRun is the actual listener of the server.
* It provides client
*/
Runnable acceptRun = new Runnable() {
@Override
public void run() {
while(serverRunning) {
try {
Socket client = server.accept();
clients.add(client);
final String name = client.getInetAddress().getHostName();
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
guiInstance.addUser(name);
}
});
Thread thClient = new Thread(new DeserveClientThread(client, name));
thClient.start();
} catch (Exception e) {
}
}
}
};
/**
* <p>This Runnable deserves each client with waiting for a request.</p>
* <p>It provides functions for operations:
* <ul>
* <li>Play/Pause</li>
* <li>Stop</li>
* <li>Play the previous file</li>
* <li>Play the next file</li>
* <li>List a folder</li>
* <li>Add a song to the playlist</li>
* <li>Upload a file
* ` <ul>
* <li>Opens the upload-mode</li>
* <li>Closes the upload mode, when file-transmission is finished</li>
* </ul>
* </li>
* @author Manuel Baumgartner
*
*/
class DeserveClientThread implements Runnable{
private Socket client;
private String name;
//private byte[] key;
public DeserveClientThread(Socket client, String name) {
this.client = client;
this.name = name;
try {
client.setSoTimeout(1000);
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Lists all elements of a folder.
* Each element has it's own first character. <br/>
* If the first character is d --> folder<br/>
* If the first character is f --> file<br/>
* @param folder The path of the folder.
* @return The elements of the folder
*/
public String[] getFolder(String folder) {
folder = folder.replace('\\', File.separatorChar);
File f = null;
if(folder.contains(JaracoGUI.userMusic)) {
f = new File(folder);
} else {
f = new File(JaracoGUI.userMusic + File.separator + folder);
}
File[] files = f.listFiles();
Arrays.sort(files);
String[] sf = new String[files.length];
for(int i=0;i<files.length;i++) {
if(files[i].isDirectory()) {
sf[i] = "d" + files[i].getAbsolutePath();
} else {
sf[i] = "f" + files[i].getAbsolutePath();
}
}
return sf;
}
@Override
public void run() {
byte[] buf;
OutputStream osDl = null;
boolean operation = false;
//key = client.getInetAddress().getAddress();
//SEND playlist
File fDl = null;
/*try {
buf = out.getBytes("UTF-8");
buf[0] = Jaraco.TYPE_PLAYLIST;
client.getOutputStream().write(buf);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
try {
//client.setTcpNoDelay(true);
client.setSendBufferSize(Jaraco.MTU);
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sendPlaylist(playerInstance.playListToString(), client);
playChangeNetwork = new PlayingChangeNetworkListener() {
/**
* The changeplay here sends the information for the current played
* file to all users.
*/
@Override
public void changePlay(int currFile, String filename, boolean playing, int currPos, int max) {
try {
String[] idtags = Jaraco.getIDTags(filename);
byte[] str = (currFile + "\n" + idtags[0]+"\n" + idtags[2]+"\n"+"F\n"+"0\n0").getBytes("UTF-8");
byte[] buf = new byte[Jaraco.MTU];
buf[0] = Jaraco.TYPE_SONGNAME;
for(int i = 0; i < str.length && (i+1) < buf.length; i++) {
buf[i + 1] = str[i];
}
sendBroadcast(buf);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
try {
client.setSendBufferSize(1);
} catch (SocketException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
String[] playing = playerInstance.getCurrentFile();
if(playing != null) {
String[] idtags = Jaraco.getIDTags(playing[1]);
try {
byte[] str = (playing[0] + "\n" + idtags[0]+"\n" + idtags[2]+"\n"+"F\n"+"0\n0").getBytes("UTF-8");
buf = new byte[Jaraco.MTU];
buf[0] = Jaraco.TYPE_SONGNAME;
for(int i = 0; i < str.length && (i+1) < buf.length; i++) {
buf[i + 1] = str[i];
}
client.getOutputStream().write(buf);
client.getOutputStream().flush();
} catch (Exception e) {
e.printStackTrace();
}
}
try {
client.setSendBufferSize(Jaraco.MTU);
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
while(serverRunning && client != null && client.isConnected()
&& !client.isClosed()) {
try {
if(operation) {
buf = new byte[Jaraco.MTU];
} else {
buf = new byte[255];
}
int length = 0;
length = client.getInputStream().read(buf);
//buf = ipEn.decrypt(buf, key);
try {
if(length >= 0) {
if(!operation) {
if(buf[0] == 1) {
playerInstance.play();
} else if(buf[0] == 2) {
playerInstance.stop();
} else if(buf[0] == 3) {
playerInstance.playBefore();
} else if(buf[0] == 4) {
playerInstance.playNext();
} else if(buf[0] == Jaraco.TYPE_FOLDER) {
String folder = new String(buf, 1, length - 1, "UTF-8");
int index0 = folder.indexOf(0);
folder = folder.substring(0, index0);
System.out.println(folder);
String[] files = getFolder(folder);
byte[] b = new byte[Jaraco.MTU];
b[0] = Jaraco.TYPE_FOLDER;
client.setSendBufferSize(Jaraco.MTU);
int offset = 1;
for(int i = 0; i < files.length; i++) {
byte[] file = (files[i] + "\n").getBytes("UTF-8");
if(offset + file.length < Jaraco.MTU) {
for(int j = 0; j < file.length; j++) {
b[offset] = file[j];
offset++;
}
} else {
client.getOutputStream().write(b);
client.getOutputStream().flush();
b = new byte[Jaraco.MTU];
b[0] = Jaraco.TYPE_FOLDER_ADD;
offset = 1;
for(int j = 0; j < file.length; j++) {
b[offset] = file[j];
offset++;
}
}
}
client.getOutputStream().write(b);
client.getOutputStream().flush();
//Thread.sleep(10);
} else if(buf[0] == Jaraco.TYPE_ADDSONG) {
String folder = new String(buf, 1, length - 1, "UTF-8");
System.out.println(folder);
int index0 = folder.indexOf(0);
final String mfolder = folder.substring(0, index0);
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
playerInstance.addFile(mfolder);
}
});
} else if(buf[0] == Jaraco.TYPE_ULSONG) {
String folder = new String(buf, 1, length - 1, "UTF-8");
File fJaraco = new File(JaracoGUI.userMusic + File.separator + "jaraco");
if(!fJaraco.exists()) {
fJaraco.mkdir();
}
fDl = new File(fJaraco, folder.substring(folder.lastIndexOf("/") + 1));
fDl.createNewFile();
operation = true;
System.out.println("uploading " + folder);
osDl = new FileOutputStream(fDl);
} else if(buf[0] == Jaraco.TYPE_PHONE_STOP) {
if(guiInstance.getPhoneStop()) {
if(playerInstance.isPlaying()) {
playerInstance.play();
}
}
} else if(buf[0] == Jaraco.TYPE_VOTE) {
byte[] b = new byte[4];
b[0] = buf[1];
b[1] = buf[2];
b[2] = buf[3];
b[3] = buf[4];
int v = IPEncryption.byteArrayToInt(b);
System.out.println("Vote for " + v);
playerInstance.voteSong(v, client.getInetAddress());
} else {
System.err.println("Invalid operation\n:"+new String(buf, 0, length, "UTF-8"));
clients.remove(client);
client.close();
client = null;
}
} else {
if(osDl != null) {
String mEnd = new String(buf, "UTF-8");
//System.out.println(mEnd);
if(mEnd.startsWith("ENDOFMUSICFILE") || length <= 0) {
System.out.println("ENDING UPLOAD");
osDl.close();
operation = false;
osDl = null;
playerInstance.addFile(fDl.getAbsolutePath());
fDl = null;
} else if(mEnd.startsWith("ABORTTRANSMISS")) {
System.out.println("Aborted upload");
osDl.close();
fDl.delete();
operation = false;
osDl = null;
fDl = null;
} else {
osDl.write(buf, 0, length);
}
}
}
} else {
clients.remove(client);
client.close();
client = null;
}
} catch (Exception ex) {
ex.printStackTrace();
}
} catch (SocketTimeoutException ex) {
} catch (IOException ex) {
}
}
clients.remove(client);
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
guiInstance.removeUser(name);
}
});
}
}
/**
* Sends the whole given ArrayList<String> to all or the given socket.
* @param playlist The given playlist as ArrayList
* @param socket If null the playlist will be sent to all clients
* otherwise it will be sent to the given client.
*/
public void sendPlaylist(ArrayList<String> playlist, Socket socket) {
byte[] buf = new byte[Jaraco.MTU];
int offset = 1;
buf[0] = Jaraco.TYPE_PLAYLIST;
try {
for(int i = 0; i < playlist.size(); i++) {
byte[] elem = (playlist.get(i) + "\n").getBytes("UTF-8");
if(offset + elem.length >= buf.length) {
if(socket != null) {
socket.getOutputStream().write(buf);
socket.getOutputStream().flush();
} else {
sendBroadcast(buf);
}
buf = new byte[Jaraco.MTU];
buf[0] = Jaraco.TYPE_PLAYLIST_ADD;
offset = 1;
for(int j = 0; j < elem.length; j++) {
buf[offset + j] = elem[j];
}
offset += elem.length;
} else {
int j = 0;
for(; j < elem.length; j++) {
buf[offset + j] = elem[j];
}
offset += elem.length;
}
}
if(offset > 0) {
if(socket != null) {
socket.getOutputStream().write(buf);
socket.getOutputStream().flush();
} else {
sendBroadcast(buf);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Initializes a new Network-TCP-Socket with the given port
* For operations and listeners it needs the instance of the player and the GUI.
* @param port The specified port (8800 should be the default port)
* @param playerInstance The JaracoPlayer-object
* @param guiInstance The JaracoGUI-object
*/
public NetworkController(int port, JaracoPlayer playerInstance, JaracoGUI guiInstance) {
try {
serverRunning = true;
this.playerInstance = playerInstance;
this.guiInstance = guiInstance;
clients = new ArrayList<Socket>();
//ipEn = new IPEncryption();
server = new ServerSocket(port);
server.setSoTimeout(1000);
thAcceptRun = new Thread(acceptRun);
thAcceptRun.start();
thBroadcastRun = new Thread(broadcastRun);
thBroadcastRun.start();
playChangeNetwork = new PlayingChangeNetworkListener() {
@Override
public void changePlay(int currFile, String filename, boolean playing, int curr, int max) {
try {
String[] idtags = Jaraco.getIDTags(filename);
byte[] str = (currFile + "\n"
+ idtags[0]+"\n"
+ idtags[2]+"\n"
+ (playing ? "T" : "F") + "\n"
+ curr+"\n"
+ max).getBytes("UTF-8");
byte[] buf = new byte[Jaraco.MTU];
buf[0] = Jaraco.TYPE_SONGNAME;
for(int i = 0; i < str.length && (i+1) < buf.length; i++) {
buf[i + 1] = str[i];
}
sendBroadcast(buf);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
/**
* The playlist-change sends the current playlist without
* a specified client.
*/
playlistChange = new PlaylistChange() {
@Override
public void getPlaylist(ArrayList<String> playlist) {
sendPlaylist(playlist, null);
}
};
playerInstance.setPlaylistchange(playlistChange);
playerInstance.setPlayingChangeNetworkListener(playChangeNetwork);
socketClose = new CloseSocket() {
@Override
public void closeSocket() {
serverRunning = false;
thAcceptRun.interrupt();
thAcceptRun = null;
}
};
guiInstance.setCloseSocket(socketClose);
} catch (IOException e) {
e.printStackTrace();
}
}
}