diff --git a/pitiupi/.gitignore b/pitiupi/.gitignore new file mode 100644 index 0000000..7530920 --- /dev/null +++ b/pitiupi/.gitignore @@ -0,0 +1,10 @@ +# NetBeans +/build/ +/dist/ +/nbproject/private/ + +# Java compilado +*.class + +# JARs gerados +*.jar diff --git a/pitiupi/build/built-jar.properties b/pitiupi/build/built-jar.properties deleted file mode 100644 index 7a88ba5..0000000 --- a/pitiupi/build/built-jar.properties +++ /dev/null @@ -1,4 +0,0 @@ -#Mon, 20 Jul 2026 12:23:12 -0300 - - -C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi= diff --git a/pitiupi/build/classes/pitiupi/GUI/About.class b/pitiupi/build/classes/pitiupi/GUI/About.class deleted file mode 100644 index e44d7f5..0000000 Binary files a/pitiupi/build/classes/pitiupi/GUI/About.class and /dev/null differ diff --git a/pitiupi/build/classes/pitiupi/GUI/Login.class b/pitiupi/build/classes/pitiupi/GUI/Login.class deleted file mode 100644 index 818ffd4..0000000 Binary files a/pitiupi/build/classes/pitiupi/GUI/Login.class and /dev/null differ diff --git a/pitiupi/build/classes/pitiupi/GUI/MainWindow.class b/pitiupi/build/classes/pitiupi/GUI/MainWindow.class deleted file mode 100644 index 69cf91f..0000000 Binary files a/pitiupi/build/classes/pitiupi/GUI/MainWindow.class and /dev/null differ diff --git a/pitiupi/build/classes/pitiupi/GUI/MenuBar.class b/pitiupi/build/classes/pitiupi/GUI/MenuBar.class deleted file mode 100644 index 9ee083e..0000000 Binary files a/pitiupi/build/classes/pitiupi/GUI/MenuBar.class and /dev/null differ diff --git a/pitiupi/build/classes/pitiupi/Main$1.class b/pitiupi/build/classes/pitiupi/Main$1.class deleted file mode 100644 index 567427f..0000000 Binary files a/pitiupi/build/classes/pitiupi/Main$1.class and /dev/null differ diff --git a/pitiupi/build/classes/pitiupi/Main.class b/pitiupi/build/classes/pitiupi/Main.class deleted file mode 100644 index 05cf605..0000000 Binary files a/pitiupi/build/classes/pitiupi/Main.class and /dev/null differ diff --git a/pitiupi/build/classes/pitiupi/control/PluginLoader.class b/pitiupi/build/classes/pitiupi/control/PluginLoader.class deleted file mode 100644 index f8c179c..0000000 Binary files a/pitiupi/build/classes/pitiupi/control/PluginLoader.class and /dev/null differ diff --git a/pitiupi/build/classes/pitiupi/net/Message.class b/pitiupi/build/classes/pitiupi/net/Message.class deleted file mode 100644 index 4033d96..0000000 Binary files a/pitiupi/build/classes/pitiupi/net/Message.class and /dev/null differ diff --git a/pitiupi/build/classes/pitiupi/net/Socket.class b/pitiupi/build/classes/pitiupi/net/Socket.class deleted file mode 100644 index a9c3a74..0000000 Binary files a/pitiupi/build/classes/pitiupi/net/Socket.class and /dev/null differ diff --git a/pitiupi/build/classes/pitiupi/plugin/Plugin.class b/pitiupi/build/classes/pitiupi/plugin/Plugin.class deleted file mode 100644 index 5b0bf10..0000000 Binary files a/pitiupi/build/classes/pitiupi/plugin/Plugin.class and /dev/null differ diff --git a/pitiupi/dist/pitiupi.jar b/pitiupi/dist/pitiupi.jar deleted file mode 100644 index 4103d01..0000000 Binary files a/pitiupi/dist/pitiupi.jar and /dev/null differ diff --git a/pitiupi/src/pitiupi/GUI/MainWindow.java b/pitiupi/src/pitiupi/GUI/MainWindow.java index bcd03fe..009f66d 100644 --- a/pitiupi/src/pitiupi/GUI/MainWindow.java +++ b/pitiupi/src/pitiupi/GUI/MainWindow.java @@ -11,10 +11,12 @@ import java.util.List; import javax.swing.JMenu; import pitiupi.control.PluginLoader; import pitiupi.plugin.Plugin; +import pitiupi.control.HeartbeatManager; /** * * @author flavio + * @author tony */ public class MainWindow extends javax.swing.JFrame { @@ -24,6 +26,10 @@ public class MainWindow extends javax.swing.JFrame { private javax.swing.JPanel statusPanel; private javax.swing.JTabbedPane tabbedPane; + private HeartbeatManager heartbeatManager; + private OnlineUsersPanel onlineUsersPanel; + + private Socket socket; private final String userName; @@ -64,6 +70,12 @@ public class MainWindow extends javax.swing.JFrame { this.socket = new Socket(this); this.socket.start(); + this.heartbeatManager = new HeartbeatManager(this); + this.onlineUsersPanel = new OnlineUsersPanel(); + this.heartbeatManager.addPeerListener(this.onlineUsersPanel); + this.getContentPane().add(this.onlineUsersPanel, java.awt.BorderLayout.LINE_END); + this.heartbeatManager.start(); + this.setSize(new java.awt.Dimension(1024, 768)); this.pluginManager = new PluginLoader(this); this.pluginManager.loadPlugins(); @@ -76,6 +88,10 @@ public class MainWindow extends javax.swing.JFrame { System.exit(0); } + public HeartbeatManager getHeartbeatManager() { + return heartbeatManager; + } + public String getUserName(){ return this.userName; } diff --git a/pitiupi/src/pitiupi/GUI/OnlineUsersPanel.java b/pitiupi/src/pitiupi/GUI/OnlineUsersPanel.java new file mode 100644 index 0000000..5821bbd --- /dev/null +++ b/pitiupi/src/pitiupi/GUI/OnlineUsersPanel.java @@ -0,0 +1,42 @@ +package pitiupi.GUI; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.util.List; +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import pitiupi.control.PeerInfo; +import pitiupi.control.PeerListener; +/** + * + * @author tony + */ +public class OnlineUsersPanel extends JPanel implements PeerListener { + + private final DefaultListModel listModel; + private final JList userList; + + public OnlineUsersPanel() { + setLayout(new BorderLayout()); + setBorder(BorderFactory.createTitledBorder("Online")); + setPreferredSize(new Dimension(160, 0)); + + listModel = new DefaultListModel<>(); + userList = new JList<>(listModel); + add(new JScrollPane(userList), BorderLayout.CENTER); + } + + @Override + public void onPeersChanged(List peers) { + SwingUtilities.invokeLater(() -> { + listModel.clear(); + for (PeerInfo peer : peers) { + listModel.addElement(peer.toString()); + } + }); + } +} \ No newline at end of file diff --git a/pitiupi/src/pitiupi/control/HeartbeatManager.java b/pitiupi/src/pitiupi/control/HeartbeatManager.java new file mode 100644 index 0000000..b3a83a9 --- /dev/null +++ b/pitiupi/src/pitiupi/control/HeartbeatManager.java @@ -0,0 +1,101 @@ +package pitiupi.control; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import pitiupi.GUI.MainWindow; +import pitiupi.net.HeartbeatMessage; +/** + * + * @author tony + */ +public class HeartbeatManager { + + private static final long HEARTBEAT_INTERVAL_MS = 5000; + private static final long PEER_TIMEOUT_MS = 15000; // ~3 batimentos perdidos + + private final MainWindow mainWindow; + private final Map peers = new ConcurrentHashMap<>(); + private final List listeners = new CopyOnWriteArrayList<>(); + private ScheduledExecutorService scheduler; + + public HeartbeatManager(MainWindow mainWindow) { + this.mainWindow = mainWindow; + } + + public void start() { + scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(this::announce, 0, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate(this::checkTimeouts, HEARTBEAT_INTERVAL_MS, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS); + } + + public void stop() { + if (scheduler != null) { + scheduler.shutdownNow(); + } + } + + private void announce() { + try { + HeartbeatMessage msg = new HeartbeatMessage(mainWindow.getUserName()); + mainWindow.getSocket().send(msg.toByteArray()); + } catch (IOException ex) { + Logger.getLogger(HeartbeatManager.class.getName()).log(Level.WARNING, "Falha ao enviar heartbeat", ex); + } + } + + // Chamado pelo Socket quando um pacote é identificado como heartbeat + public void receiveHeartbeat(HeartbeatMessage msg, InetAddress from) { + // ignora o próprio heartbeat + if (msg.getUserName().equals(mainWindow.getUserName())) { + return; + } + + String key = from.getHostAddress() + "#" + msg.getUserName(); + boolean isNew = !peers.containsKey(key); + + PeerInfo peer = peers.computeIfAbsent(key, k -> new PeerInfo(msg.getUserName(), from, msg.getTimestamp())); + peer.touch(System.currentTimeMillis()); + + if (isNew) { + notifyListeners(); + } + } + + private void checkTimeouts() { + long now = System.currentTimeMillis(); + boolean changed = peers.values().removeIf(p -> now - p.getLastSeen() > PEER_TIMEOUT_MS); + if (changed) { + notifyListeners(); + } + } + + private void notifyListeners() { + List snapshot = new ArrayList<>(peers.values()); + for (PeerListener l : listeners) { + l.onPeersChanged(snapshot); + } + } + + public void addPeerListener(PeerListener l) { + listeners.add(l); + l.onPeersChanged(new ArrayList<>(peers.values())); + } + + public void removePeerListener(PeerListener l) { + listeners.remove(l); + } + + public List getPeers() { + return new ArrayList<>(peers.values()); + } +} diff --git a/pitiupi/src/pitiupi/control/PeerInfo.java b/pitiupi/src/pitiupi/control/PeerInfo.java new file mode 100644 index 0000000..04e6bdd --- /dev/null +++ b/pitiupi/src/pitiupi/control/PeerInfo.java @@ -0,0 +1,40 @@ +package pitiupi.control; + +import java.net.InetAddress; +/** + * + * @author tony + */ +public class PeerInfo { + + private final String userName; + private final InetAddress address; + private volatile long lastSeen; + + public PeerInfo(String userName, InetAddress address, long lastSeen) { + this.userName = userName; + this.address = address; + this.lastSeen = lastSeen; + } + + public String getUserName() { + return userName; + } + + public InetAddress getAddress() { + return address; + } + + public long getLastSeen() { + return lastSeen; + } + + public void touch(long timestamp) { + this.lastSeen = timestamp; + } + + @Override + public String toString() { + return userName + " (" + address.getHostAddress() + ")"; + } +} diff --git a/pitiupi/src/pitiupi/control/PeerListener.java b/pitiupi/src/pitiupi/control/PeerListener.java new file mode 100644 index 0000000..1b97f8b --- /dev/null +++ b/pitiupi/src/pitiupi/control/PeerListener.java @@ -0,0 +1,10 @@ +package pitiupi.control; + +import java.util.List; +/** + * + * @author tony + */ +public interface PeerListener { + void onPeersChanged(List peers); +} diff --git a/pitiupi/src/pitiupi/net/HeartbeatMessage.java b/pitiupi/src/pitiupi/net/HeartbeatMessage.java new file mode 100644 index 0000000..f8cf549 --- /dev/null +++ b/pitiupi/src/pitiupi/net/HeartbeatMessage.java @@ -0,0 +1,23 @@ +package pitiupi.net; +/** + * + * @author tony + */ +public class HeartbeatMessage extends Message { + + private final String userName; + private final long timestamp; + + public HeartbeatMessage(String userName) { + this.userName = userName; + this.timestamp = System.currentTimeMillis(); + } + + public String getUserName() { + return userName; + } + + public long getTimestamp() { + return timestamp; + } +} diff --git a/pitiupi/src/pitiupi/net/Socket.java b/pitiupi/src/pitiupi/net/Socket.java index 8710697..0a99fde 100644 --- a/pitiupi/src/pitiupi/net/Socket.java +++ b/pitiupi/src/pitiupi/net/Socket.java @@ -17,10 +17,12 @@ import java.net.UnknownHostException; import java.util.logging.Level; import java.util.logging.Logger; import pitiupi.plugin.Plugin; +import pitiupi.net.HeartbeatMessage; /** * * @author flavio + * @author tony */ public class Socket extends Thread { @@ -53,19 +55,36 @@ public class Socket extends Thread { msgPacket = new DatagramPacket(msg, msg.length, this.address, this.main.getPort()); multicastSocket.send(msgPacket); } - + public void receive() throws UnknownHostException, IOException, ClassNotFoundException { byte[] buf = new byte[256000]; while (true) { - // Receive the information and print it. DatagramPacket msgPacket = new DatagramPacket(buf, buf.length); multicastSocket.receive(msgPacket); + + HeartbeatMessage heartbeat = tryParseHeartbeat(msgPacket.getData()); + if (heartbeat != null) { + main.getHeartbeatManager().receiveHeartbeat(heartbeat, msgPacket.getAddress()); + continue; + } + for (Plugin plugin : main.getPlugins()) { System.out.println(msgPacket.getAddress().toString()); plugin.receiveMessage(msgPacket.getData()); } } } + + private HeartbeatMessage tryParseHeartbeat(byte[] data) { + try { + ByteArrayInputStream bais = new ByteArrayInputStream(data); + ObjectInput in = new ObjectInputStream(bais); + Object obj = in.readObject(); + return (obj instanceof HeartbeatMessage) ? (HeartbeatMessage) obj : null; + } catch (IOException | ClassNotFoundException | ClassCastException ex) { + return null; + } + } @Override public void run() {