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); } } } }