Author SHA1 Message Date
tonyhcj 668b2d417b Corrige Preferences: name/port do Login agora atualizam MainWindow
- Login.commitEdit() no JSpinner para capturar porta digitada manualmente
- Main.java agora exibe o Login antes de criar o MainWindow
- MenuBar aplica os valores do Login no MainWindow via setUserName/setPort
- MainWindow: campos userName/port deixam de ser final; setPort() reconecta o socket
- Socket.java ganha close() para permitir reconexão limpa em nova porta
2026-07-22 18:53:11 -03:00
tonyhcj 5e835f4d75 Adiciona .gitignore 2026-07-22 15:58:13 -03:00
tonyhcj d2c155a2fa Adiciona painel de usuários online 2026-07-17 21:49:04 -03:00
tonyhcj eff5fd2f12 Integra recebimento de heartbeat no socket multicast 2026-07-17 21:48:48 -03:00
tonyhcj 076b4fcb43 Adiciona gerenciamento de heartbeat e peers 2026-07-17 21:48:26 -03:00
tonyhcj d22b95e74f Remove arquivos compilados do versionamento 2026-07-17 21:48:07 -03:00
tonyhcj c89061db2e Adiciona gitignore para arquivos gerados 2026-07-17 21:45:52 -03:00
25 changed files with 339 additions and 42 deletions

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
/pitiupi/build/
/pitiupi/dist/
/pitiupi.chat/build/
/pitiupi.chat/dist/
/pitiupi.chat/nbproject/private/
/pitiupi.screenCast/build/
/pitiupi.screenCast/dist/
+10
View File
@@ -0,0 +1,10 @@
# NetBeans
/build/
/dist/
/nbproject/private/
# Java compilado
*.class
# JARs gerados
*.jar
Whitespace-only changes.
Whitespace-only changes.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-32
View File
@@ -1,32 +0,0 @@
========================
BUILD OUTPUT DESCRIPTION
========================
When you build an Java application project that has a main class, the IDE
automatically copies all of the JAR
files on the projects classpath to your projects dist/lib folder. The IDE
also adds each of the JAR files to the Class-Path element in the application
JAR files manifest file (MANIFEST.MF).
To run the project from the command line, go to the dist folder and
type the following:
java -jar "pitiupi.jar"
To distribute this project, zip up the dist folder (including the lib folder)
and distribute the ZIP file.
Notes:
* If two JAR files on the project classpath have the same name, only the first
JAR file is copied to the lib folder.
* Only JAR files are copied to the lib folder.
If the classpath contains other types of files or folders, these files (folders)
are not copied.
* If a library on the projects classpath also has a Class-Path element
specified in the manifest,the content of the Class-Path element has to be on
the projects runtime path.
* To set a main class in a standard Java project, right-click the project node
in the Projects window and choose Properties. Then click Run and enter the
class name in the Main Class field. Alternatively, you can manually type the
class name in the manifest Main-Class element.
BIN
View File
Binary file not shown.
+8
View File
@@ -22,6 +22,7 @@ import javax.swing.JTextField;
/**
*
* @author flavio
* @author tony
*/
public final class Login extends JDialog {
@@ -82,10 +83,17 @@ public final class Login extends JDialog {
this.add(topPanel);
this.add(buttonPanel, BorderLayout.SOUTH);
okButton.addActionListener((ActionEvent e) -> {
try {
portField.commitEdit();
} catch (java.text.ParseException ex) {
return;
}
Login.this.setVisible(false);
});
cancelButton.addActionListener((ActionEvent e) -> {
System.exit(0);
});
+42 -2
View File
@@ -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,10 +26,14 @@ 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;
private final int port;
private String userName;
private int port;
private PluginLoader pluginManager;
private final List<Plugin> plugins;
@@ -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;
}
@@ -95,4 +111,28 @@ public class MainWindow extends javax.swing.JFrame {
public List<Plugin> getPlugins() {
return plugins;
}
public void setUserName(String userName) {
this.userName = userName;
updateStatus();
}
public void setPort(int port) {
this.port = port;
reconnect();
updateStatus();
}
private void updateStatus() {
this.status.setText("Connected on port " + this.port + " name " + this.userName);
}
private void reconnect() {
if (this.socket != null) {
this.socket.close();
}
this.socket = new Socket(this);
this.socket.start();
}
}
+8 -1
View File
@@ -11,6 +11,8 @@ import javax.swing.JMenuBar;
/**
*
* @author flavio
* @author tony
*
*/
public class MenuBar extends JMenuBar {
@@ -56,9 +58,14 @@ public class MenuBar extends JMenuBar {
mainWindow.exit();
}
private void setupMenuActionPerformed(java.awt.event.ActionEvent evt) {
private void setupMenuActionPerformed(java.awt.event.ActionEvent evt) {
Login login = new Login();
login.setVisible(true);
mainWindow.setUserName(login.getName());
mainWindow.setPort(login.getPort());
login.dispose();
}
@@ -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<String> listModel;
private final JList<String> 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<PeerInfo> peers) {
SwingUtilities.invokeLater(() -> {
listModel.clear();
for (PeerInfo peer : peers) {
listModel.addElement(peer.toString());
}
});
}
}
+6 -4
View File
@@ -12,6 +12,7 @@ import java.util.logging.Logger;
/**
*
* @author flavio
* @author tony
*/
public class Main {
@@ -40,16 +41,17 @@ public class Main {
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(pitiupi.GUI.MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
pitiupi.GUI.Login login = new pitiupi.GUI.Login();
login.setVisible(true);
pitiupi.GUI.MainWindow main;
main = new pitiupi.GUI.MainWindow(System.getProperty("user.name"), 8000);
main = new pitiupi.GUI.MainWindow(login.getName(), login.getPort());
main.setVisible(true);
}
}
});
}
@@ -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<String, PeerInfo> peers = new ConcurrentHashMap<>();
private final List<PeerListener> 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<PeerInfo> 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<PeerInfo> getPeers() {
return new ArrayList<>(peers.values());
}
}
+40
View File
@@ -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() + ")";
}
}
@@ -0,0 +1,10 @@
package pitiupi.control;
import java.util.List;
/**
*
* @author tony
*/
public interface PeerListener {
void onPeersChanged(List<PeerInfo> peers);
}
@@ -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;
}
}
+42 -3
View File
@@ -17,16 +17,19 @@ 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 {
private MulticastSocket multicastSocket;
private InetAddress address;
private MainWindow main;
private volatile boolean running = true;
public final static String INET_ADDR = "224.0.0.3";
public Socket(MainWindow main) {
@@ -53,13 +56,36 @@ public class Socket extends Thread {
msgPacket = new DatagramPacket(msg, msg.length, this.address, this.main.getPort());
multicastSocket.send(msgPacket);
}
public void close() {
running = false;
if (multicastSocket != null) {
try {
multicastSocket.leaveGroup(address);
} catch (IOException ex) {
}
multicastSocket.close();
}
}
public void receive() throws UnknownHostException, IOException, ClassNotFoundException {
byte[] buf = new byte[256000];
while (true) {
// Receive the information and print it.
while (running) {
DatagramPacket msgPacket = new DatagramPacket(buf, buf.length);
multicastSocket.receive(msgPacket);
try {
multicastSocket.receive(msgPacket);
} catch (IOException ex) {
if (!running) {
break;
}
throw ex;
}
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());
@@ -67,6 +93,19 @@ public class Socket extends Thread {
}
}
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() {
try {