package com.limegroup.gnutella.chat;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import com.limegroup.gnutella.ActivityCallback;
import com.limegroup.gnutella.Constants;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.util.CommonUtils;
import com.limegroup.gnutella.util.ManagedThread;
import com.limegroup.gnutella.util.ThreadFactory;
/**
* this class is a subclass of Chat, also implementing
* Chatter interface. it is a one-to-one instant message
* style chat implementation.
*
*@author rsoule
*/
public class InstantMessenger implements Chatter {
// Attributes
private Socket _socket;
private BufferedReader _reader;
private BufferedWriter _out;
private String _host;
private int _port;
private String _message = "";
private ActivityCallback _activityCallback;
private ChatManager _manager;
private boolean _outgoing = false;
/** constructor for an incoming chat request */
public InstantMessenger(Socket socket, ChatManager manager,
ActivityCallback callback) throws IOException {
_manager = manager;
_socket = socket;
_port = socket.getPort();
_host = _socket.getInetAddress().getHostAddress();
_activityCallback = callback;
OutputStream os = _socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
_out=new BufferedWriter(osw);
InputStream istream = _socket.getInputStream();
_reader = new BufferedReader(new InputStreamReader(istream, "UTF-8"));
readHeader();
}
/** constructor for an outgoing chat request */
public InstantMessenger(String host, int port, ChatManager manager,
ActivityCallback callback) throws IOException {
_host = host;
_port = port;
_manager = manager;
_activityCallback = callback;
_outgoing = true;
}
/** this is only called for outgoing connections, so that the
creation of the socket will be in the thread */
private void OutgoingInitializer() throws IOException {
_socket = new Socket(_host, _port);
_socket.setSoTimeout(Constants.TIMEOUT);
OutputStream os = _socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
_out=new BufferedWriter(osw);
// CHAT protocal :
// First we send the Chat connect string, followed by
// any number of '\r\n' terminated header strings,
// followed by a singe '\r\n'
_out.write("CHAT CONNECT/0.1\r\n");
_out.write("User-Agent: "+CommonUtils.getVendor()+"\r\n");
_out.write("\r\n");
_out.flush();
// next we expect to read 'CHAT/0.1 200 OK' followed
// by headers, and then a blank line.
// TODO: Add socket timeouts.
InputStream istream = _socket.getInputStream();
_reader = new BufferedReader(new InputStreamReader(istream, "UTF-8"));
// we are being lazy here: not actually checking for the
// header, and reading until a blank line
while (true) {
String str = _reader.readLine();
if (str == null)
return;
if (str.equals(""))
break;
}
// finally, we send
_out.write("CHAT/0.1 200 OK\r\n");
_out.write("\r\n");
_out.flush();
_socket.setSoTimeout(0);
_activityCallback.acceptChat(this);
}
/** starts the chatting */
public void start() {
MessageReader messageReader = new MessageReader(this);
ThreadFactory.startThread(messageReader, "MessageReader");
}
/** stop the chat, and close the connections
* this is always safe to call, but it is recommended
* that the gui try to encourage the user not to call
* this
*/
public void stop() {
_manager.removeChat(this);
try {
_out.close();
_socket.close();
} catch (IOException e) {
}
}
/**
* send a message accross the socket to the other host
* as with stop, this is alway safe to call, but it is
* recommended that the gui discourage the user from
* calling it when a connection is not yet established.
*/
public void send(String message) {
try {
_out.write(message+"\n");
_out.flush();
} catch (IOException e) {
// TODO: shouldn't we perform some cleanup here?? Shouldn't we
// remove this instant messenger from the current chat sessions??
}
}
/** returns the host name to which the
socket is connected */
public String getHost() {
return _host;
}
/** returns the port to which the socket is
connected */
public int getPort() {
return _port;
}
public synchronized String getMessage() {
String str = _message;
_message = "";
return str;
}
public void blockHost(String host) {
_manager.blockHost(host);
}
/** Reads the header information from the chat
request. At the moment, the header information
is pretty useless */
public void readHeader() throws IOException {
_socket.setSoTimeout(Constants.TIMEOUT);
// For the Server side of the chat protocal:
// We expect to be recieving 'CHAT CONNECT/0.1'
// but 'CHAT' has been consumed by acceptor.
// then, headers, followed by a blank line.
// we are going to be lazy, and just read until
// the blank line.
while (true) {
String str = _reader.readLine();
if (str == null)
return;
if (str.equals(""))
break;
}
// then we want to send 'CHAT/0.1 200 OK'
_out.write("CHAT/0.1 200 OK\r\n");
_out.write("\r\n");
_out.flush();
// Now we expect to read 'CHAT/0.1 200 OK'
// followed by headers, followed by a blank line.
// once again we will be lazy, and just read until
// a blank line.
// TODO: add socket timeouts.
while (true) {
String str = _reader.readLine();
if (str == null)
return;
if (str.equals(""))
break;
}
_socket.setSoTimeout(0);
}
/**
* a private class that handles the thread for reading
* off of the socket.
*
*@author rsoule
*/
private class MessageReader implements Runnable {
Chatter _chatter;
public MessageReader(Chatter chatter) {
_chatter = chatter;
}
public void run() {
try {
if(_outgoing) {
try {
OutgoingInitializer();
} catch (IOException e) {
_activityCallback.chatUnavailable(_chatter);
return;
}
}
while (true){
String str;
try {
// read into a buffer off of the socket
// until a "\r" or a "\n" has been
// reached. then alert the gui to
// write to the screen.
str = _reader.readLine();
synchronized(InstantMessenger.this) {
if( ( str == null ) || (str == "") )
throw new IOException();
_message += str;
_activityCallback.receiveMessage(_chatter);
}
} catch (IOException e) {
// if an exception was thrown, then
// the socket was closed, and the chat
// was terminated.
// return;
_activityCallback.chatUnavailable(_chatter);
break;
}
}
} catch(Throwable t) {
ErrorService.error(t);
}
}
}
}