From 6f0de554ae0f023f0e26fafe572e642f71bb514c Mon Sep 17 00:00:00 2001 From: Steven Tracey Date: Tue, 16 Jan 2024 10:28:00 -0600 Subject: [PATCH] Overhaul Protocol --- build.gradle | 2 +- .../java/tech/nevets/dlite/ChatWindow.java | 49 +++++-- .../java/tech/nevets/dlite/ChatWindow.jfd | 18 ++- .../java/tech/nevets/dlite/Connection.java | 123 ++++++++++++++++-- src/main/java/tech/nevets/dlite/Main.java | 4 +- src/main/java/tech/nevets/dlite/Notifier.java | 4 + 6 files changed, 170 insertions(+), 30 deletions(-) diff --git a/build.gradle b/build.gradle index 23a4b86..7f39a19 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group = 'tech.nevets' -version = '1.1.0' +version = '1.2.0' repositories { mavenCentral() diff --git a/src/main/java/tech/nevets/dlite/ChatWindow.java b/src/main/java/tech/nevets/dlite/ChatWindow.java index c7815af..5393dc6 100644 --- a/src/main/java/tech/nevets/dlite/ChatWindow.java +++ b/src/main/java/tech/nevets/dlite/ChatWindow.java @@ -30,9 +30,9 @@ public class ChatWindow extends JPanel { return instance; } - public void resetConnection() { + public void resetConnection(boolean error) { connected = false; - connStatus.setText("Disconnected: Socket Reset"); + connStatus.setText(error ? "Disconnected: Socket Reset" : "Disconnected"); connectBtn.setText("Connect"); } @@ -53,7 +53,7 @@ public class ChatWindow extends JPanel { return; } chat.setText(""); - conn.setComponents(chat); + conn.setComponents(chat, onlineUsers); connStatus.setText("Connected"); connectBtn.setText("Disconnect"); connected = true; @@ -115,6 +115,12 @@ public class ChatWindow extends JPanel { } } + private void usernameKeyTyped(KeyEvent e) { + if (username.getText().length() > 15) { + e.consume(); + } + } + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off connStatus = new JLabel(); @@ -122,9 +128,11 @@ public class ChatWindow extends JPanel { username = new JTextField(); connAddr = new JTextField(); connectBtn = new JButton(); - scrollPane = new JScrollPane(); + chatScrollPane = new JScrollPane(); chat = new JTextArea(); - new SmartScroller(scrollPane, SmartScroller.VERTICAL, SmartScroller.END); + new SmartScroller(chatScrollPane, SmartScroller.VERTICAL, SmartScroller.END); + onlineScrollPane = new JScrollPane(); + onlineUsers = new JTextArea(); messageBox = new JTextField(); sendBtn = new JButton(); @@ -168,6 +176,12 @@ public class ChatWindow extends JPanel { usernameFocusLost(e); } }); + username.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + usernameKeyTyped(e); + } + }); add(username, "cell 0 1,growy"); //---- connAddr ---- @@ -201,18 +215,29 @@ public class ChatWindow extends JPanel { }); add(connectBtn, "cell 2 1,grow"); - //======== scrollPane ======== + //======== chatScrollPane ======== { - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - scrollPane.setAutoscrolls(true); + chatScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + chatScrollPane.setAutoscrolls(true); //---- chat ---- chat.setEditable(false); chat.setFont(new Font("Tahoma", Font.PLAIN, 11)); chat.setLineWrap(true); - scrollPane.setViewportView(chat); + chatScrollPane.setViewportView(chat); } - add(scrollPane, "cell 0 2 3 1,grow"); + add(chatScrollPane, "cell 0 2 2 1,grow"); + + //======== onlineScrollPane ======== + { + onlineScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + onlineScrollPane.setAutoscrolls(true); + + //---- onlineUsers ---- + onlineUsers.setEditable(false); + onlineScrollPane.setViewportView(onlineUsers); + } + add(onlineScrollPane, "cell 2 2,grow"); //---- messageBox ---- messageBox.addKeyListener(new KeyAdapter() { @@ -242,8 +267,10 @@ public class ChatWindow extends JPanel { private JTextField username; private JTextField connAddr; private JButton connectBtn; - private JScrollPane scrollPane; + private JScrollPane chatScrollPane; private JTextArea chat; + private JScrollPane onlineScrollPane; + private JTextArea onlineUsers; private JTextField messageBox; private JButton sendBtn; // JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on diff --git a/src/main/java/tech/nevets/dlite/ChatWindow.jfd b/src/main/java/tech/nevets/dlite/ChatWindow.jfd index 44d834f..11ee694 100644 --- a/src/main/java/tech/nevets/dlite/ChatWindow.jfd +++ b/src/main/java/tech/nevets/dlite/ChatWindow.jfd @@ -28,6 +28,7 @@ new FormModel { "text": "Username" addEvent( new FormEvent( "java.awt.event.FocusListener", "focusGained", "usernameFocusGained", true ) ) addEvent( new FormEvent( "java.awt.event.FocusListener", "focusLost", "usernameFocusLost", true ) ) + addEvent( new FormEvent( "java.awt.event.KeyListener", "keyTyped", "usernameKeyTyped", true ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 1,growy" } ) @@ -49,7 +50,7 @@ new FormModel { "value": "cell 2 1,grow" } ) add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { - name: "scrollPane" + name: "chatScrollPane" "horizontalScrollBarPolicy": 31 "autoscrolls": true add( new FormComponent( "javax.swing.JTextArea" ) { @@ -58,11 +59,22 @@ new FormModel { "font": new java.awt.Font( "Tahoma", 0, 11 ) "lineWrap": true auxiliary() { - "JavaCodeGenerator.postCreateCode": "new SmartScroller(scrollPane, SmartScroller.VERTICAL, SmartScroller.END);" + "JavaCodeGenerator.postCreateCode": "new SmartScroller(chatScrollPane, SmartScroller.VERTICAL, SmartScroller.END);" } } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 2 3 1,grow" + "value": "cell 0 2 2 1,grow" + } ) + add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) { + name: "onlineScrollPane" + "horizontalScrollBarPolicy": 31 + "autoscrolls": true + add( new FormComponent( "javax.swing.JTextArea" ) { + name: "onlineUsers" + "editable": false + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 2,grow" } ) add( new FormComponent( "javax.swing.JTextField" ) { name: "messageBox" diff --git a/src/main/java/tech/nevets/dlite/Connection.java b/src/main/java/tech/nevets/dlite/Connection.java index c51baa2..862759c 100644 --- a/src/main/java/tech/nevets/dlite/Connection.java +++ b/src/main/java/tech/nevets/dlite/Connection.java @@ -9,14 +9,32 @@ import java.net.MalformedURLException; import java.net.Socket; import java.security.KeyStore; import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/* +Commands: + - 0x1: User connect + - 0x2: User disconnect + - 0x3: Message + - 0x4: Online List + + - 0xf: End of PDU + + Usable: 0-f + */ public class Connection { private Socket s; private InputStream is; + private OutputStream os; private PrintStream out; private String username; private JTextArea chat; + private List onlineList = new ArrayList<>(); + public Connection(String username, String address) throws Exception { String[] addrSplit = address.split(":"); @@ -38,33 +56,104 @@ public class Connection { s = factory.createSocket(addrSplit[0], Integer.parseInt(addrSplit[1])); is = s.getInputStream(); - OutputStream os = s.getOutputStream(); + os = s.getOutputStream(); out = new PrintStream(os); this.username = username; + + os.write(0x1); out.println(username); + os.write(0xf); } public void sendMessage(String message) { - out.println(message); + try { + os.write(0x3); + out.println(message); + os.write(0xf); + os.flush(); + } catch (IOException e) { + Notifier.errorPopup("Error Sending Message", e.getMessage()); + } } - public void setComponents(JTextArea chat) { + public void setComponents(JTextArea chat, JTextArea online) { this.chat = chat; new Thread(() -> { + byte[] buf = new byte[1024]; + ChatWriter chatWriter = new ChatWriter(); + try { - is.transferTo(new ChatWriter()); - } catch (IOException ignored) {} - close(); + while (!s.isClosed()) { + byte cmd; + try { + cmd = (byte) is.read(); + } catch (IOException e) { + ChatWindow.getInstance().resetConnection(false); + break; + } + int i = 0; + for (byte b = (byte) is.read(); b != 0xf && i < 1023; b = (byte) is.read()) { + buf[i] = b; + i++; + } + + switch (cmd) { + case 0x1 -> { // User Connect + addOnlineUser(new String(Arrays.copyOfRange(buf, 0, i ))); + online.setText(getFormattedOnline()); + } + case 0x2 -> { // User Disconnect + removeOnlineUser(new String(Arrays.copyOfRange(buf, 0, i))); + online.setText(getFormattedOnline()); + } + case 0x3 -> { + byte[] msg = Arrays.copyOfRange(buf, 0, i); + chatWriter.write(msg); // Send Message + } + case 0x4 -> { + onlineList.clear(); + onlineList.add(new String(Arrays.copyOfRange(buf, 0, i + 1))); + online.setText(getFormattedOnline()); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + Notifier.errorPopup("Error While Closing Connection Thread", e.getMessage()); + } }).start(); } - public void close() { - try { - s.close(); - } catch (IOException e) { - e.printStackTrace(); + public void addOnlineUser(String username) { + onlineList.add(username); + } + + public void removeOnlineUser(String username) { + onlineList.remove(username); + } + + public String getFormattedOnline() { + StringBuilder sb = new StringBuilder(); + for (String s : onlineList) { + sb.append(s.trim().replace("\r", "").replace("\n", "")); + sb.append("\n"); } - ChatWindow.getInstance().resetConnection(); + return sb.substring(0, sb.length()); + } + + public void close() { + if (!s.isClosed()) { + try { + os.write(0x2); + out.println(username); + os.write(0xf); + s.close(); + } catch (IOException e) { + Notifier.errorPopup("Connection Reset By Server", e.getMessage()); + ChatWindow.getInstance().resetConnection(true); + } + } + ChatWindow.getInstance().resetConnection(false); } private class ChatWriter extends OutputStream { @@ -73,11 +162,19 @@ public class Connection { @Override public void write(int b) { buf += (char) b; - if (((char) b) == '\n') this.flush(); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + for (int i = 0 ; i < len ; i++) { + write(b[off + i]); + } + flush(); } @Override public void flush() { + System.out.println(buf); chat.append(buf); Notifier.messageReceived(Main.getFrame()); //Notifier.sendNotification(buf.substring(0, buf.indexOf(">") - 1), buf.substring(buf.indexOf(">"))); diff --git a/src/main/java/tech/nevets/dlite/Main.java b/src/main/java/tech/nevets/dlite/Main.java index 53cae62..e59d710 100644 --- a/src/main/java/tech/nevets/dlite/Main.java +++ b/src/main/java/tech/nevets/dlite/Main.java @@ -9,7 +9,7 @@ import java.awt.*; public class Main { private static final FlatDarkLaf DARK_LAF = new FlatDarkLaf(); private static final FlatLightLaf LIGHT_LAF = new FlatLightLaf(); - private static TrayIcon trayIcon; + //private static TrayIcon trayIcon; private static JFrame frame; public static void main(String[] args) { @@ -44,7 +44,7 @@ public class Main { } SwingUtilities.updateComponentTreeUI(frame); } catch (UnsupportedLookAndFeelException e) { - e.printStackTrace(); + Notifier.errorPopup("Error Setting Look and Feel", e.getMessage()); } } diff --git a/src/main/java/tech/nevets/dlite/Notifier.java b/src/main/java/tech/nevets/dlite/Notifier.java index 87fbb9a..baab726 100644 --- a/src/main/java/tech/nevets/dlite/Notifier.java +++ b/src/main/java/tech/nevets/dlite/Notifier.java @@ -10,4 +10,8 @@ public class Notifier { Taskbar.getTaskbar().requestWindowUserAttention(frame); } } + + public static void errorPopup(String title, String message) { + JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE); + } }