package de.tum.in.www1.simplechat;
import de.tum.in.www1.jReto.Connection;
import de.tum.in.www1.jReto.RemotePeer;
import de.tum.in.www1.jReto.connectivity.InTransfer;
import de.tum.in.www1.jReto.connectivity.OutTransfer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/*
Each ChatRoom represents a chat conversation with the peer it represents.
For simplicity, when a chat room is created, a connection is created to the remote peer, which is used to send chat messages.
This means that both participating peers create a connection, i.e. there are two connections between the peers, even though one would be enough.
Files can be exchanged in using the ChatRoom class. To transmit a file, a new connection is established. First, the file name is transmitted, then the actual file.
After transmission, the connection is closed.
Therefore, the first incoming connection from a remote peer is used to receive chat messages; any further connections are used to receive files.
*/
public class ChatRoom {
private SimpleChatUI chatUI;
/** The display name of the local peer in the chat */
private String localDisplayName;
/** The display name of the remote peer in the chat */
private String remoteDisplayName;
/** The full text in the chat room; contains all messages. */
private String chatText = "";
/** The progress of a file if it one is being transmitted. */
private int fileProgress = 0;
/** The file name of the file that is currently being received, if any. */
private String fileName = null;
/** The remotePeer object representing the other peer in the chat room (besides the local peer) */
private RemotePeer remotePeer;
/** The Connection used to receive chat messages. */
private Connection incomingConnection;
/** The Connection used to send chat messages. */
private Connection outgoingConnection;
public ChatRoom(RemotePeer remotePeer, String localDisplayName, SimpleChatUI chatUI) {
this.localDisplayName = localDisplayName;
this.chatUI = chatUI;
this.remotePeer = remotePeer;
// When an incoming connection is available, call acceptConnection.
this.remotePeer.setIncomingConnectionHandler((peer, connection) -> this.acceptIncomingConnection(connection));
// Create a connection to the remote peer
this.outgoingConnection = remotePeer.connect();
// The first message sent through the outgoing connection contains the display name that should be used, so it is sent here.
this.outgoingConnection.send(new TextMessage(localDisplayName).serialize());
}
private void acceptIncomingConnection(Connection connection) {
if (this.incomingConnection == null) {
// If this is the first connection, we use it to receive message data. Therefore we call handleMessageData when data was received.
this.incomingConnection = connection;
this.incomingConnection.setOnData((t, data) -> handleMessageData(data));
} else {
// Any additional connections are used to receive a file transfer.
connection.setOnTransfer((c, t) -> this.receiveFileTransfer(c, t));
}
}
public void sendMessage(String message) {
this.outgoingConnection.send(new TextMessage(message).serialize());
appendChatMessage(this.localDisplayName, message);
}
private void handleMessageData(ByteBuffer data) {
String message = new TextMessage(data).text;
if (this.remoteDisplayName == null) {
this.setDisplayName(message);
} else {
appendChatMessage(this.remoteDisplayName, message);
}
}
private void appendChatMessage(String displayName, String message) {
this.chatText += displayName+": "+ message+"\n";
chatUI.updateChatData();
}
public void sendFile(FileChannel fileChannel, String fileName, int fileSize) throws IOException {
// Establish a new connection to transmit the file.
Connection connection = this.remotePeer.connect();
// Send the file name
connection.send(new TextMessage(fileName).serialize());
// Send the file itself. Data will be read as the file is being sent (the closure will be called multiple times).
OutTransfer transfer = connection.send(fileSize, (position, length) -> readData(fileChannel, position, length));
// When progress is made, we update the file progress property.
transfer.setOnProgress( t -> setFileProgress(t.getProgress(), t.getLength()));
// When the transfer is done, we reset the progress and close the file handle.
transfer.setOnEnd(t -> endTransfer(fileChannel));
}
private void receiveFileTransfer(Connection connection, InTransfer transfer) {
/*
* 2.4e Receive a file transfer.
*
* 1. As data is received, it should be written to the file channel (use writeData).
* 2. Update the progress bar using setFileProgress.
* 3. Call endTransfer and close the connection when the transfer completes or is cancelled.
* */
// receiveFile will be called twice; once for the transfer that contains the fileName only, and once for the actual file.
if (fileName == null) {
transfer.setOnCompleteData((t, data) -> { fileName = new TextMessage(data).text; });
} else {
// If we already have a filePath, we can receive the file.
FileChannel fileChannel = this.chatUI.getSaveFileChannel(fileName);
// Whenever data is received, we write data to the file channel.
transfer.setOnPartialData((t, data) -> writeData(fileChannel, data));
// When progress is made, update the progress property
transfer.setOnProgress( t -> setFileProgress(t.getProgress(), t.getLength()));
// When everything is received, close the connection, reset the filePath, close the file handle, and inform the delegate that a file was received.
transfer.setOnEnd(t -> { endTransfer(fileChannel); connection.close(); });
// Reset the file path.
this.fileName = null;
}
}
/** Reads data from a file. */
private ByteBuffer readData(FileChannel fileChannel, int position, int length) {
try {
ByteBuffer buffer = ByteBuffer.allocate(length);
fileChannel.read(buffer, position);
buffer.rewind();
return buffer;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/** Writes data to a file. */
private void writeData(FileChannel fileChannel, ByteBuffer data) {
try {
fileChannel.write(data);
} catch (Exception e) {
e.printStackTrace();
}
}
/** Closes the file channel and resets progress. */
private void endTransfer(FileChannel fileChannel) {
setFileProgress(0, 1);
try {
fileChannel.force(true);
fileChannel.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void setDisplayName(String displayName) {
this.remoteDisplayName = displayName;
chatUI.addChatPeer(this);
}
public String getDisplayName() {
return this.remoteDisplayName;
}
public String getChatText() {
return this.chatText;
}
public int getFileProgress() {
return this.fileProgress;
}
public void setFileProgress(long progress, long totalLength) {
this.fileProgress = (int)((progress*100) / totalLength);
this.chatUI.updateChatData();
}
}