Compare commits

...

8 Commits

Author SHA1 Message Date
GustavoHMDS 81c37225fa atualizando o arquivo documentation.md 2026-08-11 14:35:51 -03:00
GustavoHMDS 5c69189293 atualizando documentacao depois do merge 2026-08-11 14:30:17 -03:00
GustavoHMDS 543073e84e resolvendo conflitos 2026-08-11 10:12:04 -03:00
tonyhcj 7f21e89a0a fix: salvando nome e porta 2026-07-29 15:00:44 -03:00
tonyhcj 239a10e79f fix: Corrige bug que exibia frame0 como nome do usuário 2026-07-28 22:11:37 -03:00
tonyhcj a7ba307daf fix: usar instanceId único em vez de userName para identificar heartbeats 2026-07-24 19:31:39 -03:00
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
12 changed files with 224 additions and 67 deletions

7
.gitignore vendored Normal file
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/

View File

@ -4,29 +4,7 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="62118a1b-608f-4144-b29c-cf46419ca676" name="Changes" comment=""> <list default="true" id="62118a1b-608f-4144-b29c-cf46419ca676" name="Changes" comment="" />
<change afterPath="$PROJECT_DIR$/pitiupi/Documentation.md" afterDir="false" />
<change afterPath="$PROJECT_DIR$/pitiupi/PluginTutorial.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi.chat/build/built-jar.properties" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi.chat/build/built-jar.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi.chat/dist/pitiupi.chat.jar" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi.chat/dist/pitiupi.chat.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi.screenCast/build/built-jar.properties" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi.screenCast/build/built-jar.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi.screenCast/dist/pitiupi.screenCast.jar" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi.screenCast/dist/pitiupi.screenCast.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/.netbeans_automatic_build" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/.netbeans_update_resources" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/GUI/Login.class" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/GUI/Login.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/GUI/MainWindow.class" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/GUI/MainWindow.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/GUI/MenuBar.class" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/GUI/MenuBar.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/Main$1.class" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/Main$1.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/Main.class" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/Main.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/control/PluginLoader.class" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/control/PluginLoader.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/net/Socket.class" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/net/Socket.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/plugin/Plugin.class" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/build/classes/pitiupi/plugin/Plugin.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/dist/README.TXT" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/dist/pitiupi.jar" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/dist/pitiupi.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/plugins/pitiupi.chat.jar" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/plugins/pitiupi.chat.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/plugins/pitiupi.screenCast.jar" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/plugins/pitiupi.screenCast.jar" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pitiupi/src/pitiupi/GUI/MenuBar.java" beforeDir="false" afterPath="$PROJECT_DIR$/pitiupi/src/pitiupi/GUI/MenuBar.java" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@ -43,20 +21,20 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"Application.Main.executor": "Run", &quot;Application.Main.executor&quot;: &quot;Run&quot;,
"ModuleVcsDetector.initialDetectionPerformed": "true", &quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true", &quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
"RunOnceActivity.git.unshallow": "true", &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
"git-widget-placeholder": "feature/help-and-documentation", &quot;git-widget-placeholder&quot;: &quot;feature/help-and-documentation&quot;,
"ignore.virus.scanning.warn.message": "true", &quot;ignore.virus.scanning.warn.message&quot;: &quot;true&quot;,
"kotlin-language-version-configured": "true", &quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
"last_opened_file_path": "C:/Users/extre/OneDrive/Documentos/Faculdade/2026_02/Monitoria/Pitiupi", &quot;last_opened_file_path&quot;: &quot;C:/Users/extre/OneDrive/Documentos/Faculdade/2026_02/Monitoria/Pitiupi&quot;,
"settings.editor.selected.configurable": "preferences.keymap" &quot;settings.editor.selected.configurable&quot;: &quot;preferences.keymap&quot;
} }
}]]></component> }</component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\extre\OneDrive\Documentos\Faculdade\2026_02\Monitoria\Pitiupi\pitiupi\plugins" /> <recent name="C:\Users\extre\OneDrive\Documentos\Faculdade\2026_02\Monitoria\Pitiupi\pitiupi\plugins" />

View File

@ -94,7 +94,7 @@ public class ChatWindow extends javax.swing.JDialog {
if (jTextArea1.getText().trim().length() == 0) { if (jTextArea1.getText().trim().length() == 0) {
return; return;
} }
ChatMessage message = new ChatMessage(mainWindow.getName(), jTextArea1.getText()); ChatMessage message = new ChatMessage(mainWindow.getUserName(), jTextArea1.getText());
this.sendChatMessage(message); this.sendChatMessage(message);
jTextArea1.setText(""); jTextArea1.setText("");
} }

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="083e72ce-fa9b-4ddf-a0ef-24105aa3eabb" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/META-INF/services/pitiupi.plugin.Plugin" afterDir="false" />
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/GUI/QuizWindow.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/control/Pergunta.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/control/Quiz.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/net/QuizMessage.java" afterDir="false" />
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/plugin/Quiz.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pitiupi/screenCast/net/ScreenCastMessage.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/pitiupi/screenCast/net/ScreenCastMessage.java" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 7
}]]></component>
<component name="ProjectId" id="3Gs0wFaRD4HyhP9VXLUWBeq0qTL" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "QuizPlugin",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "C:/Users/extre/OneDrive/Documentos/Faculdade/2026_02/Monitoria/Pitiupi/pitiupi.screenCast",
"settings.editor.selected.configurable": "preferences.keymap"
}
}]]></component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.54" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="083e72ce-fa9b-4ddf-a0ef-24105aa3eabb" name="Changes" comment="" />
<created>1784745206049</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1784745206049</updated>
</task>
<servers />
</component>
</project>

View File

@ -60,7 +60,7 @@ O método `send()` recebe uma mensagem já serializada em bytes e cria um
De forma geral, o socket atua apenas como mecanismo de transporte das mensagens. De forma geral, o socket atua apenas como mecanismo de transporte das mensagens.
A única exceção é o tratamento de HeartbeatMessage, utilizado pela infraestrutura A única exceção é o tratamento de HeartbeatMessage, utilizado pela infraestrutura
da aplicação para manter a lista de usuários ativos. da aplicação para manter a lista de peers ativos.
#### Recepção de mensagens #### Recepção de mensagens
@ -98,9 +98,15 @@ comportamentos necessários para cada funcionalidade.
O mecanismo de heartbeat é utilizado para identificar quais usuários estão O mecanismo de heartbeat é utilizado para identificar quais usuários estão
ativos na rede. ativos na rede.
A aplicação envia periodicamente uma `HeartbeatMessage` contendo informações A aplicação envia periodicamente uma `HeartbeatMessage` contendo
do usuário e um timestamp. Quando outra instância recebe essa mensagem, o informações do usuário, um identificador único da instância da aplicação
`HeartbeatManager` atualiza o registro do usuário correspondente. e um timestamp. Quando outra instância recebe essa mensagem, o `HeartbeatManager`
registra ou atualiza o peer correspondente, utilizando o identificador
da instância para diferenciá-lo das demais.
Um peer é considerado inativo quando permanece mais de 15 segundos
sem receber um novo heartbeat. O `HeartbeatManager` então remove o peer
da lista e notifica os listeners sobre a alteração.
O fluxo de heartbeat é independente das mensagens dos plugins: O fluxo de heartbeat é independente das mensagens dos plugins:
@ -109,7 +115,7 @@ O fluxo de heartbeat é independente das mensagens dos plugins:
3. O `Socket` transmite a mensagem pela rede multicast. 3. O `Socket` transmite a mensagem pela rede multicast.
4. O `Socket` identifica mensagens de heartbeat recebidas e encaminha ao 4. O `Socket` identifica mensagens de heartbeat recebidas e encaminha ao
`HeartbeatManager`. `HeartbeatManager`.
5. O `HeartbeatManager` atualiza a lista de usuários ativos e notifica os 5. O `HeartbeatManager` atualiza a lista de peers ativos e notifica os
listeners. listeners.
## Sistema de plugins ## Sistema de plugins

View File

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package pitiupi.GUI; package pitiupi.GUI;
import java.awt.BorderLayout; import java.awt.BorderLayout;
@ -11,6 +6,7 @@ import java.awt.GridBagConstraints;
import java.awt.GridBagLayout; import java.awt.GridBagLayout;
import java.awt.Insets; import java.awt.Insets;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.util.prefs.Preferences;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JDialog; import javax.swing.JDialog;
@ -26,9 +22,14 @@ import javax.swing.JTextField;
* multicast que será utilizada pela comunicação.</p> * multicast que será utilizada pela comunicação.</p>
* *
* @author flavio * @author flavio
* @author tony
*/ */
public final class Login extends JDialog { public final class Login extends JDialog {
private static final Preferences prefs = Preferences.userNodeForPackage(Login.class);
private static final String PREF_NOME = "lastUserName";
private static final String PREF_PORTA = "lastPort";
JLabel nameLabel = new JLabel("Name: "); JLabel nameLabel = new JLabel("Name: ");
JLabel portLabel = new JLabel("Port: "); JLabel portLabel = new JLabel("Port: ");
@ -47,8 +48,12 @@ public final class Login extends JDialog {
public void initComponents() { public void initComponents() {
this.setTitle("Login"); this.setTitle("Login");
portField.setModel(new javax.swing.SpinnerNumberModel(8000, 8000, 9000, 1));
nameField.setText(System.getProperty("user.name")); String nomeSalvo = prefs.get(PREF_NOME, System.getProperty("user.name"));
int portaSalva = prefs.getInt(PREF_PORTA, 8000);
portField.setModel(new javax.swing.SpinnerNumberModel(portaSalva, 8000, 9000, 1));
nameField.setText(nomeSalvo);
JPanel topPanel = new JPanel(new GridBagLayout()); JPanel topPanel = new JPanel(new GridBagLayout());
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
@ -87,6 +92,15 @@ public final class Login extends JDialog {
this.add(buttonPanel, BorderLayout.SOUTH); this.add(buttonPanel, BorderLayout.SOUTH);
okButton.addActionListener((ActionEvent e) -> { okButton.addActionListener((ActionEvent e) -> {
try {
portField.commitEdit();
} catch (java.text.ParseException ex) {
return;
}
prefs.put(PREF_NOME, nameField.getText());
prefs.putInt(PREF_PORTA, (Integer) portField.getValue());
Login.this.setVisible(false); Login.this.setVisible(false);
}); });

View File

@ -41,8 +41,8 @@ public class MainWindow extends javax.swing.JFrame {
private Socket socket; private Socket socket;
private final String userName; private String userName;
private final int port; private int port;
private PluginLoader pluginManager; private PluginLoader pluginManager;
private final List<Plugin> plugins; private final List<Plugin> plugins;
@ -137,4 +137,28 @@ public class MainWindow extends javax.swing.JFrame {
public List<Plugin> getPlugins() { public List<Plugin> getPlugins() {
return plugins; 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();
}
} }

View File

@ -16,6 +16,8 @@ import javax.swing.JMenuBar;
* *
* @author flavio * @author flavio
* @author Gustavo * @author Gustavo
* @author tony
*
*/ */
public class MenuBar extends JMenuBar { public class MenuBar extends JMenuBar {
@ -27,6 +29,11 @@ public class MenuBar extends JMenuBar {
private javax.swing.JMenuItem exitMenu; private javax.swing.JMenuItem exitMenu;
private final MainWindow mainWindow; private final MainWindow mainWindow;
/**
* Cria a barra de menus principal da aplicação.
*
* @param mainWindow mainWindow janela principal da aplicação utilizada para executar
*/
public MenuBar(MainWindow mainWindow) { public MenuBar(MainWindow mainWindow) {
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
this.initComponents(); this.initComponents();
@ -67,9 +74,14 @@ public class MenuBar extends JMenuBar {
mainWindow.exit(); mainWindow.exit();
} }
private void setupMenuActionPerformed(java.awt.event.ActionEvent evt) {
private void setupMenuActionPerformed(java.awt.event.ActionEvent evt) {
Login login = new Login(); Login login = new Login();
login.setVisible(true); login.setVisible(true);
mainWindow.setUserName(login.getName());
mainWindow.setPort(login.getPort());
login.dispose(); login.dispose();
} }

View File

@ -12,6 +12,7 @@ import java.util.logging.Logger;
/** /**
* *
* @author flavio * @author flavio
* @author tony
*/ */
public class Main { public class Main {
/** /**
@ -39,16 +40,17 @@ public class Main {
} catch (javax.swing.UnsupportedLookAndFeelException ex) { } catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(pitiupi.GUI.MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, 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() { java.awt.EventQueue.invokeLater(new Runnable() {
@Override @Override
public void run() { public void run() {
pitiupi.GUI.Login login = new pitiupi.GUI.Login();
login.setVisible(true);
pitiupi.GUI.MainWindow main; 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); main.setVisible(true);
} }
}); });
} }

View File

@ -14,6 +14,7 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import pitiupi.GUI.MainWindow; import pitiupi.GUI.MainWindow;
import pitiupi.net.HeartbeatMessage; import pitiupi.net.HeartbeatMessage;
import java.util.UUID;
/** /**
* Gerencia o mecanismo de heartbeat da aplicação. * Gerencia o mecanismo de heartbeat da aplicação.
* *
@ -32,17 +33,30 @@ public class HeartbeatManager {
private final Map<String, PeerInfo> peers = new ConcurrentHashMap<>(); private final Map<String, PeerInfo> peers = new ConcurrentHashMap<>();
private final List<PeerListener> listeners = new CopyOnWriteArrayList<>(); private final List<PeerListener> listeners = new CopyOnWriteArrayList<>();
private ScheduledExecutorService scheduler; private ScheduledExecutorService scheduler;
private final String instanceId = UUID.randomUUID().toString();
/**
* Cria um gerenciador de heartbeat associado à janela principal da aplicação.
*
* @param mainWindow janela principal utilizada para obter o nome do usuário
* e o socket de comunicação.
*/
public HeartbeatManager(MainWindow mainWindow) { public HeartbeatManager(MainWindow mainWindow) {
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
} }
/**
* Inicia o mecanismo de heartbeat.
*/
public void start() { public void start() {
scheduler = Executors.newScheduledThreadPool(1); scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(this::announce, 0, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS); scheduler.scheduleAtFixedRate(this::announce, 0, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS);
scheduler.scheduleAtFixedRate(this::checkTimeouts, HEARTBEAT_INTERVAL_MS, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS); scheduler.scheduleAtFixedRate(this::checkTimeouts, HEARTBEAT_INTERVAL_MS, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS);
} }
/**
* Para o mecanismo de heartbeat e encerra as tarefas agendadas.
*/
public void stop() { public void stop() {
if (scheduler != null) { if (scheduler != null) {
scheduler.shutdownNow(); scheduler.shutdownNow();
@ -51,7 +65,7 @@ public class HeartbeatManager {
private void announce() { private void announce() {
try { try {
HeartbeatMessage msg = new HeartbeatMessage(mainWindow.getUserName()); HeartbeatMessage msg = new HeartbeatMessage(mainWindow.getUserName(), instanceId);
mainWindow.getSocket().send(msg.toByteArray()); mainWindow.getSocket().send(msg.toByteArray());
} catch (IOException ex) { } catch (IOException ex) {
Logger.getLogger(HeartbeatManager.class.getName()).log(Level.WARNING, "Falha ao enviar heartbeat", ex); Logger.getLogger(HeartbeatManager.class.getName()).log(Level.WARNING, "Falha ao enviar heartbeat", ex);
@ -62,21 +76,22 @@ public class HeartbeatManager {
/** /**
* Processa uma mensagem de heartbeat recebida de outro usuário. * Processa uma mensagem de heartbeat recebida de outro usuário.
* *
* <p>Atualiza a informação do peer correspondente e notifica os listeners * <p>Atualiza o momento da última atividade do peer correspondente e notifica os listeners
* caso um novo usuário seja identificado.</p> * caso outra instância da aplicação seja identificada.</p>
* *
* <p>Mensagens de heartbeat enviadas pela própria aplicação são ignoradas.</p> * <p>Mensagens de heartbeat enviadas pela própria aplicação são ignoradas.</p>
* *
* @param msg mensagem de heartbeat recebida. * @param msg mensagem de heartbeat recebida.
* @param from endereço de rede do usuário que enviou a mensagem. * @param from endereço de rede do usuário que enviou a mensagem.
*/ */
public void receiveHeartbeat(HeartbeatMessage msg, InetAddress from) { public void receiveHeartbeat(HeartbeatMessage msg, InetAddress from) {
// ignora o próprio heartbeat // ignora o próprio heartbeat
if (msg.getUserName().equals(mainWindow.getUserName())) { if (msg.getInstanceId().equals(instanceId) ) {
return; return;
} }
String key = from.getHostAddress() + "#" + msg.getUserName(); String key = msg.getInstanceId();
boolean isNew = !peers.containsKey(key); boolean isNew = !peers.containsKey(key);
PeerInfo peer = peers.computeIfAbsent(key, k -> new PeerInfo(msg.getUserName(), from, msg.getTimestamp())); PeerInfo peer = peers.computeIfAbsent(key, k -> new PeerInfo(msg.getUserName(), from, msg.getTimestamp()));
@ -115,10 +130,20 @@ public class HeartbeatManager {
l.onPeersChanged(new ArrayList<>(peers.values())); l.onPeersChanged(new ArrayList<>(peers.values()));
} }
/**
* Remove um listener previamente registrado.
*
* @param l listener que deixará de receber atualizações.
*/
public void removePeerListener(PeerListener l) { public void removePeerListener(PeerListener l) {
listeners.remove(l); listeners.remove(l);
} }
/**
* Retorna uma cópia da lista atual de peers conhecidos.
*
* @return lista contendo os peers atualmente conhecidos.
*/
public List<PeerInfo> getPeers() { public List<PeerInfo> getPeers() {
return new ArrayList<>(peers.values()); return new ArrayList<>(peers.values());
} }

View File

@ -11,9 +11,11 @@ public class HeartbeatMessage extends Message {
private final String userName; private final String userName;
private final long timestamp; private final long timestamp;
private final String instanceId;
public HeartbeatMessage(String userName) { public HeartbeatMessage(String userName, String instanceId) {
this.userName = userName; this.userName = userName;
this.instanceId = instanceId;
this.timestamp = System.currentTimeMillis(); this.timestamp = System.currentTimeMillis();
} }
@ -24,4 +26,8 @@ public class HeartbeatMessage extends Message {
public long getTimestamp() { public long getTimestamp() {
return timestamp; return timestamp;
} }
public String getInstanceId() {
return instanceId;
}
} }

View File

@ -20,7 +20,7 @@ import pitiupi.plugin.Plugin;
import pitiupi.net.HeartbeatMessage; import pitiupi.net.HeartbeatMessage;
/** /**
* Responsável pela comunicação multicast entre a aplicação e os plugins. * "Responsável pela comunicação multicast da aplicação e pelo encaminhamento de mensagens aos plugins."
* *
* <p>Esta classe gerencia o envio e o recebimento de mensagens, além de * <p>Esta classe gerencia o envio e o recebimento de mensagens, além de
* manter a conexão com o grupo multicast utilizado pela aplicação.</p> * manter a conexão com o grupo multicast utilizado pela aplicação.</p>
@ -33,6 +33,7 @@ public class Socket extends Thread {
private MulticastSocket multicastSocket; private MulticastSocket multicastSocket;
private InetAddress address; private InetAddress address;
private MainWindow main; private MainWindow main;
private volatile boolean running = true;
public final static String INET_ADDR = "224.0.0.3"; public final static String INET_ADDR = "224.0.0.3";
/** /**
@ -71,6 +72,22 @@ public class Socket extends Thread {
multicastSocket.send(msgPacket); multicastSocket.send(msgPacket);
} }
/**
* Encerra a comunicação multicast e interrompe a recepção de mensagens.
*/
public void close() {
running = false;
if (multicastSocket != null) {
try {
multicastSocket.leaveGroup(address);
} catch (IOException ex) {
}
multicastSocket.close();
}
}
/** /**
* Inicia o loop de recepção de mensagens da aplicação. * Inicia o loop de recepção de mensagens da aplicação.
* *
@ -84,16 +101,21 @@ public class Socket extends Thread {
*/ */
public void receive() throws UnknownHostException, IOException, ClassNotFoundException { public void receive() throws UnknownHostException, IOException, ClassNotFoundException {
byte[] buf = new byte[256000]; byte[] buf = new byte[256000];
while (true) { while (running) {
DatagramPacket msgPacket = new DatagramPacket(buf, buf.length); 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()); HeartbeatMessage heartbeat = tryParseHeartbeat(msgPacket.getData());
if (heartbeat != null) { if (heartbeat != null) {
main.getHeartbeatManager().receiveHeartbeat(heartbeat, msgPacket.getAddress()); main.getHeartbeatManager().receiveHeartbeat(heartbeat, msgPacket.getAddress());
continue; continue;
} }
for (Plugin plugin : main.getPlugins()) { for (Plugin plugin : main.getPlugins()) {
System.out.println(msgPacket.getAddress().toString()); System.out.println(msgPacket.getAddress().toString());
plugin.receiveMessage(msgPacket.getData()); plugin.receiveMessage(msgPacket.getData());