ChatClient.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. import javax.swing.JFrame;
  2. import javax.swing.JTabbedPane;
  3. import javax.swing.JPanel;
  4. import javax.swing.JScrollPane;
  5. import javax.swing.JTextArea;
  6. import javax.swing.JTextField;
  7. import javax.swing.JLabel;
  8. import javax.swing.JButton;
  9. import javax.swing.JOptionPane;
  10. import javax.swing.JFileChooser;
  11. import javax.swing.filechooser.FileNameExtensionFilter;
  12. import java.awt.BorderLayout;
  13. import java.awt.event.ActionEvent;
  14. import java.awt.event.ActionListener;
  15. import java.util.ArrayList;
  16. import java.util.Iterator;
  17. import java.util.concurrent.Executors;
  18. import java.util.concurrent.ExecutorService;
  19. import java.io.File;
  20. import java.io.FileOutputStream;
  21. import java.io.FileNotFoundException;
  22. import java.io.BufferedReader;
  23. import java.io.BufferedWriter;
  24. import java.io.IOException;
  25. import java.io.InputStreamReader;
  26. import java.io.InterruptedIOException;
  27. import java.io.OutputStream;
  28. import java.io.OutputStreamWriter;
  29. import java.net.ConnectException;
  30. import java.net.NoRouteToHostException;
  31. import net.i2p.I2PException;
  32. import net.i2p.client.streaming.I2PSocket;
  33. import net.i2p.client.streaming.I2PServerSocket;
  34. import net.i2p.client.streaming.I2PSocketManager;
  35. import net.i2p.client.streaming.I2PSocketManagerFactory;
  36. import net.i2p.data.DataFormatException;
  37. import net.i2p.data.Destination;
  38. import java.net.SocketTimeoutException;
  39. import net.i2p.util.I2PThread;
  40. import net.i2p.client.I2PSession;
  41. public class ChatClient extends JFrame
  42. {
  43. private I2PSocket socket;
  44. private BufferedWriter bw;
  45. private BufferedReader br;
  46. private ChatServer server;
  47. private static final String name = "Simple I2P Chat";
  48. private String nick;
  49. private boolean isConnected, isServer;
  50. private Thread listenThread;
  51. private JTabbedPane tabs;
  52. private JPanel cPanel, sPanel, sbPanel, bPanel, tPanel;
  53. private JLabel nickLabel;
  54. private JScrollPane sp, stsp;
  55. private JTextArea ta, st;
  56. private JTextField tf;
  57. private JButton connectButton, sendButton, serverButton, saveLogButton;
  58. public static void main(String[] args)
  59. { new ChatClient(name); }
  60. public ChatClient(String title)
  61. {
  62. super(title);
  63. isConnected = false;
  64. isServer = false;
  65. tabs = new JTabbedPane();
  66. cPanel = new JPanel();
  67. sPanel = new JPanel();
  68. sbPanel = new JPanel();
  69. bPanel = new JPanel();
  70. tPanel = new JPanel();
  71. nickLabel = new JLabel();
  72. st = new JTextArea(); /* server log area */
  73. st.setEditable(false);
  74. stsp = new JScrollPane(st);
  75. ta = new JTextArea(); /* text area with all messages */
  76. ta.setEditable(false);
  77. sp = new JScrollPane(ta);
  78. tf = new JTextField(); /* message input field */
  79. connectButton = new JButton("Connect!");
  80. sendButton = new JButton("Send");
  81. serverButton = new JButton("Start Server!");
  82. saveLogButton = new JButton("Save Server Log");
  83. tf.addActionListener( new ActionListener()
  84. {
  85. public void actionPerformed(ActionEvent e)
  86. { writeLine(); }
  87. } );
  88. connectButton.addActionListener( new ActionListener()
  89. {
  90. public void actionPerformed(ActionEvent e)
  91. {
  92. if(!isConnected)
  93. connect();
  94. }
  95. } );
  96. sendButton.addActionListener( new ActionListener()
  97. {
  98. public void actionPerformed(ActionEvent e)
  99. { writeLine(); }
  100. } );
  101. serverButton.addActionListener( new ServerButtonListener() );
  102. saveLogButton.addActionListener( new ActionListener()
  103. {
  104. public void actionPerformed(ActionEvent e)
  105. {
  106. String text = st.getText();
  107. if(!text.equals(""))
  108. saveLog(text);
  109. }
  110. } );
  111. bPanel.add(sendButton);
  112. bPanel.add(connectButton);
  113. tPanel.setLayout(new BorderLayout());
  114. tPanel.add(nickLabel, BorderLayout.NORTH);
  115. tPanel.add(sp, BorderLayout.CENTER);
  116. tPanel.add(tf, BorderLayout.SOUTH);
  117. cPanel.setLayout(new BorderLayout());
  118. cPanel.add(bPanel, BorderLayout.SOUTH);
  119. cPanel.add(tPanel, BorderLayout.CENTER);
  120. sbPanel.add(serverButton);
  121. sbPanel.add(saveLogButton);
  122. sPanel.setLayout(new BorderLayout());
  123. sPanel.add(sbPanel, BorderLayout.NORTH);
  124. sPanel.add(stsp, BorderLayout.CENTER);
  125. tabs.addTab("Client", null, cPanel, "Client Tab");
  126. tabs.addTab("Server", null, sPanel, "Server Tab");
  127. add(tabs);
  128. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  129. setSize(420,420);
  130. setVisible(true);
  131. }
  132. private void connect()
  133. {
  134. I2PSocketManager manager = I2PSocketManagerFactory.createManager();
  135. String destStr = input("Please Enter a Destination:");
  136. if(destStr == null || destStr.trim().equals(""))
  137. return;
  138. Destination dest = null;
  139. try
  140. { dest = new Destination(destStr); }
  141. catch (DataFormatException ex)
  142. {
  143. err("Destination string incorrectly formatted.");
  144. return;
  145. }
  146. socket = null;
  147. try
  148. { socket = manager.connect(dest); }
  149. catch (I2PException ex)
  150. { err("General I2P exception occurred!"); }
  151. catch (ConnectException ex)
  152. { err("Failed to connect!"); }
  153. catch (NoRouteToHostException ex)
  154. { err("Couldn't find host!"); }
  155. catch (InterruptedIOException ex)
  156. { err("Sending/receiving was interrupted!"); }
  157. try
  158. {
  159. //Read from server
  160. br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  161. //Write to server
  162. bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
  163. }
  164. catch (IOException ex)
  165. { err("Error occurred while getting streams!"); }
  166. isConnected = true;
  167. message("Connected!");
  168. while( !goodNick() )
  169. nick = input("Please enter your nickname:");
  170. nick = nick.trim();
  171. nickLabel.setText( String.format("Nick: %s", nick) );
  172. listenThread = new Thread( new Runnable()
  173. {
  174. public void run()
  175. {
  176. while(isConnected)
  177. readLine();
  178. }
  179. } );
  180. listenThread.start();
  181. }
  182. private void closeConn()
  183. {
  184. try
  185. { socket.close(); }
  186. catch (IOException ex)
  187. { err("Error occurred while closing I2PSocket!"); }
  188. nick = null;
  189. nickLabel.setText("");
  190. isConnected = false;
  191. }
  192. private void writeLine()
  193. {
  194. String line = tf.getText();
  195. tf.setText("");
  196. if(line == null || line.trim().equals("") || !isConnected) return;
  197. line = String.format("%s: %s%n", nick, line);
  198. try
  199. {
  200. bw.write(line);
  201. bw.flush();
  202. }
  203. catch(IOException ex)
  204. { err( String.format("Error occurred during send.%nClosing connection.") );
  205. closeConn(); }
  206. }
  207. private void readLine()
  208. {
  209. try
  210. {
  211. String s = null;
  212. while ((s = br.readLine()) != null)
  213. {
  214. ta.append( String.format("%s%n",s) );
  215. ta.setCaretPosition(ta.getDocument().getLength());
  216. }
  217. }
  218. catch(IOException ex)
  219. { err( String.format("Error occurred during read.%nClosing connection.") );
  220. closeConn(); }
  221. }
  222. private boolean goodNick()
  223. {
  224. if(nick == null || nick.trim().equals(""))
  225. return false;
  226. return nick.matches("\\w+");
  227. }
  228. private void saveLog(String log)
  229. {
  230. String fn, ext="";
  231. JFileChooser jfc = new JFileChooser();
  232. jfc.addChoosableFileFilter( new FileNameExtensionFilter("Text files only.","txt") );
  233. int result = jfc.showSaveDialog(this);
  234. if(result == JFileChooser.CANCEL_OPTION)
  235. return;
  236. File logFile = jfc.getSelectedFile();
  237. fn = logFile.getName();
  238. int i = fn.lastIndexOf(".");
  239. if(i>=0)
  240. ext = fn.substring(i);
  241. if(!ext.equals(".txt"))
  242. fn += ".txt";
  243. logFile = new File(logFile.getParent(),fn);
  244. try
  245. {
  246. BufferedWriter out = new BufferedWriter(
  247. new OutputStreamWriter(
  248. new FileOutputStream( logFile )));
  249. out.write(log);
  250. out.close();
  251. }
  252. catch(FileNotFoundException fnfe)
  253. { err("Cannot write to disk!"); }
  254. catch(IOException ioe)
  255. { err("Error occured while saving!"); }
  256. }
  257. private void message(Object obj)
  258. { JOptionPane.showMessageDialog(this,obj,name,1); }
  259. private String input(String str)
  260. { return JOptionPane.showInputDialog(this,str,name,3); }
  261. private void err(String str)
  262. { JOptionPane.showMessageDialog(this,str,name,0); }
  263. class ServerButtonListener implements ActionListener
  264. {
  265. public void actionPerformed(ActionEvent e)
  266. {
  267. if(isServer) return;
  268. try
  269. { server = new ChatServer(st); }
  270. catch(NullPointerException ex)
  271. {
  272. err("I2P Router may not be running.");
  273. return;
  274. }
  275. isServer = true;
  276. serverButton.setText("Server Running.");
  277. JTextArea ta = new JTextArea(1, 23);
  278. ta.setText( server.getDest() );
  279. JScrollPane sp = new JScrollPane(ta);
  280. message(sp); // Print the base64 string
  281. }
  282. }
  283. }
  284. class ChatServer
  285. {
  286. private ArrayList<I2PSocket> clients;
  287. private I2PSocket currSock;
  288. private I2PSocketManager manager;
  289. private I2PServerSocket serverSocket;
  290. private I2PSession session;
  291. private String dest;
  292. private JTextArea log;
  293. private ExecutorService threadExecutor;
  294. public ChatServer(JTextArea log)
  295. {
  296. threadExecutor = Executors.newCachedThreadPool();
  297. clients = new ArrayList<I2PSocket>();
  298. manager = I2PSocketManagerFactory.createManager();
  299. serverSocket = manager.getServerSocket();
  300. session = manager.getSession();
  301. dest = session.getMyDestination().toBase64();
  302. this.log = log;
  303. log("Server: Starting up.");
  304. I2PThread t = new I2PThread(new ClientHandler());
  305. t.setName("clienthandler1");
  306. t.setDaemon(false);
  307. t.start();
  308. }
  309. private void post(String str)
  310. {
  311. Iterator<I2PSocket> it = clients.iterator();
  312. while(it.hasNext())
  313. {
  314. I2PSocket sock = (I2PSocket)it.next();
  315. if(sock == null)
  316. continue;
  317. try
  318. {
  319. BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream())); //Send to clients
  320. bw.write( String.format("%s%n",str) );
  321. bw.flush(); // Flush to make sure everything got sent
  322. }
  323. catch(IOException ex)
  324. { err("Error occurred while posting!"); }
  325. }
  326. }
  327. private void closeConns()
  328. {
  329. Iterator<I2PSocket> it = clients.iterator();
  330. while(it.hasNext())
  331. {
  332. I2PSocket sock = (I2PSocket) it.next();
  333. if(sock == null)
  334. continue;
  335. try{ sock.close(); }
  336. catch(IOException ex)
  337. { err("Error occurred while closing connections!"); }
  338. }
  339. }
  340. private void err(String str)
  341. {
  342. log(str);
  343. log("Server disconnecting from clients...");
  344. closeConns();
  345. }
  346. public String getDest()
  347. { return dest; }
  348. private void log(String str)
  349. { log.append( String.format("%s%n",str) ); }
  350. class ClientHandler implements Runnable
  351. {
  352. public void run()
  353. {
  354. while(true)
  355. {
  356. try
  357. {
  358. currSock = serverSocket.accept();
  359. if(currSock != null)
  360. {
  361. log("Server: Successfully accepted connection.");
  362. clients.add(currSock);
  363. startListenThread(currSock);
  364. }
  365. }
  366. catch (I2PException ex)
  367. { log("General I2P exception!"); }
  368. catch (ConnectException ex)
  369. { log("Error connecting!"); }
  370. catch (SocketTimeoutException ex)
  371. { log("Timeout!"); }
  372. }
  373. }
  374. private void startListenThread(I2PSocket sock)
  375. { threadExecutor.execute( new ListenThread(sock) ); }
  376. class ListenThread implements Runnable
  377. {
  378. I2PSocket sock;
  379. public ListenThread(I2PSocket sock)
  380. { this.sock = sock; }
  381. public void run()
  382. {
  383. try
  384. {
  385. BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream())); // Receive from clients
  386. String line = null;
  387. while( true )
  388. {
  389. if( (line = br.readLine()) == null ) continue;
  390. log("Recieved from client "+line);
  391. post(line);
  392. }
  393. }
  394. catch (IOException ex)
  395. { err("General read/write-exception!"); }
  396. }
  397. } // end inner class ListenThread
  398. } // end inner class ClientHandler
  399. } // end ChatServer