Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c9e61ce0f | ||
|
|
6538dac8af | ||
|
|
67813e4881 | ||
|
|
4cc07a3754 | ||
|
|
fa696194aa | ||
|
|
9fb4952f32 | ||
|
|
96b8faca5b | ||
|
|
cac549f1ee | ||
|
|
a86f4dbf82 | ||
|
|
23559dcc06 | ||
|
|
fa4ee046b1 | ||
|
|
0c71a85cba | ||
|
|
e7c8983775 | ||
|
|
0ec3129a22 | ||
|
|
ba2f41daf7 | ||
|
|
57059c3ef0 | ||
|
|
7168431bcd | ||
|
|
0b7c67fef6 | ||
|
|
15ea8ac207 | ||
|
|
b5bbd1df2d | ||
|
|
93ac292f72 | ||
|
|
b610ec20e1 | ||
|
|
4461772ebf | ||
|
|
0d5a4dbe75 | ||
|
|
bbb1ceea23 | ||
|
|
7d9ca4ca71 | ||
|
|
1fcc03d9be | ||
|
|
3efc59444f | ||
|
|
947aaf00b7 | ||
|
|
e9ce17abb3 | ||
|
|
45e3bf6876 | ||
|
|
81c37225fa | ||
|
|
5c69189293 | ||
|
|
543073e84e | ||
|
|
7f21e89a0a | ||
|
|
239a10e79f | ||
|
|
a7ba307daf | ||
|
|
668b2d417b | ||
|
|
5e835f4d75 | ||
|
|
cf43f62793 | ||
|
|
5d650df8eb | ||
|
|
3beadca0c1 | ||
|
|
d2c155a2fa | ||
|
|
eff5fd2f12 | ||
|
|
076b4fcb43 | ||
|
|
d22b95e74f | ||
|
|
c89061db2e |
No files matched your search
@@ -5,3 +5,5 @@
|
|||||||
/pitiupi.chat/nbproject/private/
|
/pitiupi.chat/nbproject/private/
|
||||||
/pitiupi.screenCast/build/
|
/pitiupi.screenCast/build/
|
||||||
/pitiupi.screenCast/dist/
|
/pitiupi.screenCast/dist/
|
||||||
|
|
||||||
|
.idea/
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="pitiupi.chat">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/dist/pitiupi.chat.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/pitiupi.chat.iml" filepath="$PROJECT_DIR$/pitiupi.chat.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#Fri, 17 Jul 2026 17:31:55 -0300
|
#Fri, 02 Oct 2026 20:11:21 -0300
|
||||||
|
|
||||||
|
|
||||||
/home/perolanegra/facul/pitiupi/pitiupi.chat=
|
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.chat=
|
||||||
|
|
||||||
/home/perolanegra/facul/pitiupi/pitiupi=
|
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi=
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -47,8 +47,8 @@ javac.modulepath=
|
|||||||
javac.processormodulepath=
|
javac.processormodulepath=
|
||||||
javac.processorpath=\
|
javac.processorpath=\
|
||||||
${javac.classpath}
|
${javac.classpath}
|
||||||
javac.source=21
|
javac.source=25
|
||||||
javac.target=21
|
javac.target=25
|
||||||
javac.test.classpath=\
|
javac.test.classpath=\
|
||||||
${javac.classpath}:\
|
${javac.classpath}:\
|
||||||
${build.classes.dir}
|
${build.classes.dir}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../pitiupi/dist/pitiupi.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
File renamed without changes.
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../../pitiupi/dist/pitiupi.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -4,28 +4,29 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.chat.GUI;
|
package pitiupi.chat.GUI;
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
import pitiupi.models.Menus.MulticastMenus.MulticastMenu;
|
||||||
import javax.swing.JMenu;
|
import pitiupi.models.Menus.MulticastMenus.MulticastSubMenu;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ChatMenu extends JMenu {
|
public class ChatMenu extends MulticastSubMenu {
|
||||||
|
ChatMulticastMenu chatMulticastMenu;
|
||||||
private final ChatWindow chatWindow;
|
|
||||||
private javax.swing.JMenuItem menuItem;
|
|
||||||
|
|
||||||
public ChatMenu(ChatWindow chatWindow) {
|
public ChatMenu(ChatWindow chatWindow) {
|
||||||
this.chatWindow = chatWindow;
|
this.chatMulticastMenu = new ChatMulticastMenu(chatWindow);
|
||||||
this.setText("Chat");
|
|
||||||
menuItem = new javax.swing.JMenuItem();
|
|
||||||
menuItem.setText("Show Chat Window");
|
|
||||||
menuItem.addActionListener(this::chatMenuActionPerformed);
|
|
||||||
this.add(menuItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void chatMenuActionPerformed(ActionEvent ae) {
|
@Override
|
||||||
this.chatWindow.setVisible(true);
|
public String getText() {
|
||||||
|
return "Chat";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<MulticastMenu> getMenus() {
|
||||||
|
return List.of(chatMulticastMenu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package pitiupi.chat.GUI;
|
||||||
|
|
||||||
|
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;;
|
||||||
|
|
||||||
|
public class ChatMulticastMenu extends MulticastActionMenu {
|
||||||
|
ChatWindow chatWindow;
|
||||||
|
|
||||||
|
public ChatMulticastMenu(ChatWindow chatWindow) {
|
||||||
|
this.chatWindow = chatWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return "Show Chat Window";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
chatWindow.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,29 +8,30 @@ package pitiupi.chat.GUI;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import pitiupi.chat.net.ChatMessage;
|
import pitiupi.chat.net.ChatMessage;
|
||||||
import pitiupi.plugin.PitiupiFacade;
|
import pitiupi.control.ConnectionManager;
|
||||||
import pitiupi.net.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
* @author tony
|
|
||||||
*/
|
*/
|
||||||
public class ChatWindow extends javax.swing.JDialog {
|
public class ChatWindow extends JDialog {
|
||||||
|
|
||||||
private javax.swing.JButton jButton1;
|
private JButton jButton1;
|
||||||
private javax.swing.JEditorPane jEditorPane1;
|
private JEditorPane jEditorPane1;
|
||||||
private javax.swing.JPanel jPanel1;
|
private JPanel jPanel1;
|
||||||
private javax.swing.JPanel jPanel2;
|
private JPanel jPanel2;
|
||||||
private javax.swing.JScrollPane jScrollPane1;
|
private JScrollPane jScrollPane1;
|
||||||
private javax.swing.JScrollPane jScrollPane2;
|
private JScrollPane jScrollPane2;
|
||||||
private javax.swing.JSplitPane jSplitPane1;
|
private JSplitPane jSplitPane1;
|
||||||
private javax.swing.JTextArea jTextArea1;
|
private JTextArea jTextArea1;
|
||||||
|
|
||||||
private final PitiupiFacade pitiupi;
|
private final ConnectionManager connectionManager;
|
||||||
|
|
||||||
public ChatWindow(PitiupiFacade pitiupi) {
|
public ChatWindow(ConnectionManager connectionManager) {
|
||||||
this.pitiupi = pitiupi;
|
this.connectionManager = connectionManager;
|
||||||
initComponents();
|
initComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,23 +42,23 @@ public class ChatWindow extends javax.swing.JDialog {
|
|||||||
*/
|
*/
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
|
|
||||||
this.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
|
this.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
|
||||||
this.setTitle("Chat Window");
|
this.setTitle("Chat Window");
|
||||||
this.setBounds(new java.awt.Rectangle(0, 0, 800, 600));
|
this.setBounds(new java.awt.Rectangle(0, 0, 800, 600));
|
||||||
this.getContentPane().setLayout(new java.awt.BorderLayout(5, 5));
|
this.getContentPane().setLayout(new java.awt.BorderLayout(5, 5));
|
||||||
this.setPreferredSize(new java.awt.Dimension(800, 600));
|
this.setPreferredSize(new java.awt.Dimension(800, 600));
|
||||||
|
|
||||||
jSplitPane1 = new javax.swing.JSplitPane();
|
jSplitPane1 = new JSplitPane();
|
||||||
jScrollPane1 = new javax.swing.JScrollPane();
|
jScrollPane1 = new JScrollPane();
|
||||||
jEditorPane1 = new javax.swing.JEditorPane();
|
jEditorPane1 = new JEditorPane();
|
||||||
jPanel1 = new javax.swing.JPanel();
|
jPanel1 = new JPanel();
|
||||||
jScrollPane2 = new javax.swing.JScrollPane();
|
jScrollPane2 = new JScrollPane();
|
||||||
jTextArea1 = new javax.swing.JTextArea();
|
jTextArea1 = new JTextArea();
|
||||||
jPanel2 = new javax.swing.JPanel();
|
jPanel2 = new JPanel();
|
||||||
jButton1 = new javax.swing.JButton();
|
jButton1 = new JButton();
|
||||||
|
|
||||||
jSplitPane1.setDividerLocation(500);
|
jSplitPane1.setDividerLocation(500);
|
||||||
jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
|
jSplitPane1.setOrientation(JSplitPane.VERTICAL_SPLIT);
|
||||||
jEditorPane1.setContentType("text/html");
|
jEditorPane1.setContentType("text/html");
|
||||||
jPanel1.setLayout(new java.awt.BorderLayout());
|
jPanel1.setLayout(new java.awt.BorderLayout());
|
||||||
jTextArea1.setColumns(20);
|
jTextArea1.setColumns(20);
|
||||||
@@ -89,16 +90,16 @@ public class ChatWindow extends javax.swing.JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage() {
|
private void sendMessage() {
|
||||||
if (jTextArea1.getText().trim().length() == 0) {
|
if (jTextArea1.getText().trim().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ChatMessage message = new ChatMessage(pitiupi.getUserName(), jTextArea1.getText());
|
ChatMessage message = new ChatMessage(connectionManager.getUserName(), jTextArea1.getText());
|
||||||
this.sendChatMessage(message);
|
this.sendChatMessage(message);
|
||||||
jTextArea1.setText("");
|
jTextArea1.setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendChatMessage(Message message) {
|
public void sendChatMessage(Message message) {
|
||||||
pitiupi.send(message);
|
connectionManager.sendUdpMulticast(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveChatMessage(ChatMessage message) {
|
public void receiveChatMessage(ChatMessage message) {
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.chat.net;
|
package pitiupi.chat.net;
|
||||||
|
|
||||||
import pitiupi.net.Message;
|
|
||||||
import pitiupi.net.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ChatMessage extends Message{
|
public class ChatMessage extends Message {
|
||||||
|
|
||||||
private String text;
|
private String text;
|
||||||
private String name;
|
private String name;
|
||||||
|
|||||||
@@ -4,46 +4,43 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.chat.plugin;
|
package pitiupi.chat.plugin;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.util.ArrayList;
|
||||||
import java.io.IOException;
|
import java.util.Collection;
|
||||||
import java.io.ObjectInputStream;
|
import java.util.List;
|
||||||
import pitiupi.plugin.PitiupiFacade;
|
|
||||||
import pitiupi.chat.GUI.ChatMenu;
|
import pitiupi.chat.GUI.ChatMenu;
|
||||||
import pitiupi.chat.GUI.ChatWindow;
|
import pitiupi.chat.GUI.ChatWindow;
|
||||||
import pitiupi.chat.net.ChatMessage;
|
import pitiupi.chat.net.ChatMessage;
|
||||||
import pitiupi.net.Message;
|
import pitiupi.control.ConnectionManager;
|
||||||
import pitiupi.plugin.Plugin;
|
import pitiupi.control.GuiManager;
|
||||||
|
import pitiupi.models.Menus.MenuCategory;
|
||||||
|
import pitiupi.models.Messages.Message;
|
||||||
|
import pitiupi.models.Plugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
* @author tony
|
|
||||||
*/
|
*/
|
||||||
public class ChatPlugin implements Plugin {
|
public class ChatPlugin extends Plugin {
|
||||||
|
|
||||||
private ChatWindow chatWindow;
|
private ChatWindow chatWindow;
|
||||||
private PitiupiFacade pitiupi;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createPlugin(PitiupiFacade pitiupi) {
|
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||||
this.pitiupi = pitiupi;
|
this.chatWindow = new ChatWindow(connectionManager);
|
||||||
this.chatWindow = new ChatWindow(pitiupi);
|
guiManager.createMenu(MenuCategory.Social, new ChatMenu(chatWindow));
|
||||||
this.pitiupi.addMenu(new ChatMenu(chatWindow));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receiveMessage(byte[] message) {
|
public List<Class<? extends Message>> getMessages() {
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(message);
|
return List.of(ChatMessage.class);
|
||||||
ObjectInputStream ois;
|
}
|
||||||
try {
|
|
||||||
ois = new ObjectInputStream(bis);
|
|
||||||
if (ois.readObject() instanceof ChatMessage msg) {
|
@Override
|
||||||
this.chatWindow.receiveChatMessage(msg);
|
public void receiveMessage(Message message) {
|
||||||
}
|
if (message instanceof ChatMessage msg) {
|
||||||
} catch (IOException ex) {
|
this.chatWindow.receiveChatMessage(msg);
|
||||||
System.out.println(ex.toString());
|
|
||||||
} catch (ClassNotFoundException ex) {
|
|
||||||
System.out.println(ex.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +60,9 @@ public class ChatPlugin implements Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message getMessage() {
|
public String getIdentifier() {
|
||||||
return new ChatMessage();
|
return "Pitiupi.Chat.Example";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
JAR = ../pitiupi/dist/pitiupi.jar
|
||||||
|
OUT_DIR = out
|
||||||
|
META-INF_DIR = src/META-INF
|
||||||
|
DIST = dist/mpChat.jar
|
||||||
|
|
||||||
|
SOURCES = $(shell find -name "*.java")
|
||||||
|
|
||||||
|
all:
|
||||||
|
mkdir -p "$(OUT_DIR)"
|
||||||
|
mkdir -p "$(dir $(DIST))"
|
||||||
|
javac -cp "$(JAR)" -d "$(OUT_DIR)" $(SOURCES)
|
||||||
|
cp -r "$(META-INF_DIR)" "$(OUT_DIR)"
|
||||||
|
jar cf "$(DIST)" -C "$(OUT_DIR)" .
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf "$(OUT_DIR)"/*
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
plugin.MultiProtocolChat
|
||||||
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.
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../pitiupi/dist/pitiupi.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import pitiupi.models.Menus.MenuCategory;
|
||||||
|
import pitiupi.models.Menus.MulticastMenus.MulticastSubMenu;
|
||||||
|
import plugin.MultiProtocolChat;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class ChatMenu extends MulticastSubMenu {
|
||||||
|
private final MultiProtocolChat multiProtocolChat;
|
||||||
|
|
||||||
|
public ChatMenu(MultiProtocolChat multiProtocolChat) {
|
||||||
|
this.multiProtocolChat = multiProtocolChat;
|
||||||
|
this.setText("MP-Chat");
|
||||||
|
|
||||||
|
JMenuItem startPrivateChat = new JMenuItem("start Private Chat");
|
||||||
|
startPrivateChat.addActionListener(e -> showPrivateChatDialog());
|
||||||
|
this.add(startPrivateChat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void newMenuItem(JMenuItem newMenuItem, ChatWindow chatWindow) {
|
||||||
|
newMenuItem.addActionListener(e -> chatWindow.setVisible(true));
|
||||||
|
|
||||||
|
this.add(newMenuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeMenuItem(JMenuItem removeMenuItem) {
|
||||||
|
this.remove(removeMenuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPrivateChatDialog() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return "MP-Chat";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import net.MpChatMessage;
|
||||||
|
import pitiupi.control.ConnectionManager;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
public abstract class ChatWindow extends JFrame {
|
||||||
|
|
||||||
|
protected final ConnectionManager connectionManager;
|
||||||
|
|
||||||
|
private JTextArea receivedMessagesArea;
|
||||||
|
protected JTextField messageField;
|
||||||
|
|
||||||
|
public ChatWindow(ConnectionManager connectionManager) {
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
initComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
setTitle("Socket Test");
|
||||||
|
setSize(700, 500);
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
||||||
|
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
|
||||||
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
initReceivedMessagesPanel(mainPanel);
|
||||||
|
initBottomPanel(mainPanel);
|
||||||
|
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initReceivedMessagesPanel(JPanel mainPanel) {
|
||||||
|
receivedMessagesArea = new JTextArea();
|
||||||
|
receivedMessagesArea.setEditable(false);
|
||||||
|
receivedMessagesArea.setLineWrap(true);
|
||||||
|
receivedMessagesArea.setWrapStyleWord(true);
|
||||||
|
|
||||||
|
JScrollPane messagesScrollPane = new JScrollPane(receivedMessagesArea);
|
||||||
|
|
||||||
|
messagesScrollPane.setBorder(BorderFactory.createTitledBorder("Mensagens recebidas"));
|
||||||
|
|
||||||
|
mainPanel.add(messagesScrollPane, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initBottomPanel(JPanel mainPanel) {
|
||||||
|
messageField = new JTextField();
|
||||||
|
|
||||||
|
JPanel bottomPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
|
||||||
|
bottomPanel.add(messageField, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
initButtons(bottomPanel);
|
||||||
|
|
||||||
|
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void initButtons(JPanel bottomPanel);
|
||||||
|
|
||||||
|
protected MpChatMessage createMessage(String tipoMensagem) {
|
||||||
|
try {
|
||||||
|
String nome = connectionManager.getUserName();
|
||||||
|
String ip = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
String message = messageField.getText();
|
||||||
|
|
||||||
|
return new MpChatMessage(nome, ip, tipoMensagem, message);
|
||||||
|
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receiveMessage(MpChatMessage msg) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
String mensagem = String.format(
|
||||||
|
"[%s] %s (%s): %s%n",
|
||||||
|
msg.getTipoMensagem(),
|
||||||
|
msg.getNome(),
|
||||||
|
msg.getIp(),
|
||||||
|
msg.getMessage()
|
||||||
|
);
|
||||||
|
|
||||||
|
receivedMessagesArea.append(mensagem);
|
||||||
|
|
||||||
|
receivedMessagesArea.setCaretPosition(receivedMessagesArea.getDocument().getLength());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import control.ChatController;
|
||||||
|
import pitiupi.models.Menus.UnicastMenus.UnicastActionMenu;
|
||||||
|
import pitiupi.models.User;
|
||||||
|
import plugin.MultiProtocolChat;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
public class PrivateChatMenu extends UnicastActionMenu {
|
||||||
|
PrivateChatWindow privateChatWindow;
|
||||||
|
private final MultiProtocolChat multiProtocolChat;
|
||||||
|
private final ChatController chatController;
|
||||||
|
|
||||||
|
public PrivateChatMenu(PrivateChatWindow PrivateChatWindow, MultiProtocolChat multiProtocolChat, ChatController chatController) {
|
||||||
|
this.privateChatWindow = PrivateChatWindow;
|
||||||
|
this.multiProtocolChat = multiProtocolChat;
|
||||||
|
this.chatController = chatController;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(User user) {
|
||||||
|
if (multiProtocolChat.openConnection(user.getAddress())
|
||||||
|
else errorLabel.setVisible(true);
|
||||||
|
|
||||||
|
dialog.setContentPane(contentPanel);
|
||||||
|
dialog.pack();
|
||||||
|
dialog.setLocationRelativeTo(SwingUtilities.getWindowAncestor(privateChatWindow));
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return "start Private Chat";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import net.MpChatMessage;
|
||||||
|
import pitiupi.control.ConnectionManager;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class PrivateChatWindow extends ChatWindow {
|
||||||
|
|
||||||
|
private final TcpConnection connection;
|
||||||
|
|
||||||
|
public PrivateChatWindow(ConnectionManager ConnectionManager, TcpConnection connection) {
|
||||||
|
super(ConnectionManager);
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initButtons(JPanel bottomPanel) {
|
||||||
|
JButton tcpButton = new JButton("TCP");
|
||||||
|
|
||||||
|
tcpButton.addActionListener(e -> sendTCP());
|
||||||
|
|
||||||
|
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
||||||
|
|
||||||
|
buttonsPanel.add(tcpButton);
|
||||||
|
|
||||||
|
bottomPanel.add(buttonsPanel, BorderLayout.EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendTCP() {
|
||||||
|
try {
|
||||||
|
MpChatMessage msg = createMessage("TCP Private");
|
||||||
|
connection.send(msg.toByteArray());
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.getLogger(PrivateChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
|
||||||
|
|
||||||
|
public class PublicChatMenu extends MulticastActionMenu {
|
||||||
|
PublicChatWindow publicChatWindow;
|
||||||
|
|
||||||
|
PublicChatMenu(PublicChatWindow publicChatWindow) {
|
||||||
|
this.publicChatWindow = publicChatWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
publicChatWindow.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package GUI;
|
||||||
|
|
||||||
|
import net.MpChatMessage;
|
||||||
|
import pitiupi.control.ConnectionManager;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class PublicChatWindow extends ChatWindow {
|
||||||
|
|
||||||
|
private JTextField ipField;
|
||||||
|
|
||||||
|
public PublicChatWindow(ConnectionManager connectionManager) {
|
||||||
|
super(connectionManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initButtons(JPanel bottomPanel) {
|
||||||
|
initIpField(bottomPanel);
|
||||||
|
initCommunicationButtons(bottomPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initIpField(JPanel bottomPanel) {
|
||||||
|
ipField = new JTextField();
|
||||||
|
ipField.setPreferredSize(new Dimension(130, 30));
|
||||||
|
|
||||||
|
JPanel ipPanel = new JPanel(new BorderLayout(5, 5));
|
||||||
|
|
||||||
|
ipPanel.add(new JLabel("IP destino:"), BorderLayout.WEST);
|
||||||
|
|
||||||
|
ipPanel.add(ipField, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
bottomPanel.add(ipPanel, BorderLayout.WEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCommunicationButtons(JPanel bottomPanel) {
|
||||||
|
JButton tcpButton = new JButton("TCP");
|
||||||
|
JButton udpButton = new JButton("UDP");
|
||||||
|
JButton multicastButton = new JButton("Multicast");
|
||||||
|
|
||||||
|
tcpButton.addActionListener(e -> sendTCP());
|
||||||
|
udpButton.addActionListener(e -> sendUDP());
|
||||||
|
multicastButton.addActionListener(e -> sendMulticast());
|
||||||
|
|
||||||
|
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
||||||
|
|
||||||
|
buttonsPanel.add(tcpButton);
|
||||||
|
buttonsPanel.add(udpButton);
|
||||||
|
buttonsPanel.add(multicastButton);
|
||||||
|
|
||||||
|
bottomPanel.add(buttonsPanel, BorderLayout.EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendTCP() {
|
||||||
|
try {
|
||||||
|
MpChatMessage msg = createMessage("TCP Unicast");
|
||||||
|
|
||||||
|
InetAddress ip = InetAddress.getByName(ipField.getText());
|
||||||
|
|
||||||
|
connectionManager.sendTcp(msg, ip);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendUDP() {
|
||||||
|
try {
|
||||||
|
MpChatMessage msg = createMessage("UDP Unicast");
|
||||||
|
|
||||||
|
InetAddress ip = InetAddress.getByName(ipField.getText());
|
||||||
|
|
||||||
|
connectionManager.sendUdpUnicast(msg, ip);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMulticast() {
|
||||||
|
try {
|
||||||
|
MpChatMessage msg = createMessage("UDP Multicast");
|
||||||
|
|
||||||
|
connectionManager.sendUdpMulticast(msg);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
plugin.MultiProtocolChat
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package control;
|
||||||
|
|
||||||
|
import GUI.ChatMenu;
|
||||||
|
import GUI.ChatWindow;
|
||||||
|
import GUI.PrivateChatWindow;
|
||||||
|
import GUI.PublicChatWindow;
|
||||||
|
import net.MpChatMessage;
|
||||||
|
import pitiupi.control.ConnectionManager;
|
||||||
|
import pitiupi.control.PeerListener;
|
||||||
|
import pitiupi.models.User;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChatController implements PeerListener {
|
||||||
|
ConnectionManager connectionManager;
|
||||||
|
ChatMenu chatMenu;
|
||||||
|
|
||||||
|
PublicChatWindow publicChatWindow;
|
||||||
|
|
||||||
|
private final HashMap<TcpConnection, ChatWindow> chatWindows;
|
||||||
|
|
||||||
|
public ChatController(ConnectionManager connectionManager, ChatMenu chatMenu) {
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
publicChatWindow = new PublicChatWindow(connectionManager);
|
||||||
|
|
||||||
|
chatMenus = new HashMap<>();
|
||||||
|
chatWindows = new HashMap<>();
|
||||||
|
|
||||||
|
this.chatMenu = chatMenu;
|
||||||
|
|
||||||
|
this.chatMenu.newMenuItem(new JMenuItem("Show global chat"), publicChatWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChatWindow(TcpConnection tcpConnection) {
|
||||||
|
if (chatWindows.containsKey(tcpConnection)) return;
|
||||||
|
|
||||||
|
PrivateChatWindow newChat = new PrivateChatWindow(connectionManager, tcpConnection);
|
||||||
|
chatWindows.put(tcpConnection, newChat);
|
||||||
|
JMenuItem newMenuItem = new JMenuItem();
|
||||||
|
chatMenus.put(tcpConnection, newMenuItem);
|
||||||
|
newMenuItem.setText(tcpConnection.getAddress().toString());
|
||||||
|
chatMenu.newMenuItem(newMenuItem, newChat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeChatWindow(TcpConnection tcpConnection) {
|
||||||
|
ChatWindow removedWindow = chatWindows.remove(tcpConnection);
|
||||||
|
JMenuItem removedMenu = chatMenus.remove(tcpConnection);
|
||||||
|
|
||||||
|
chatMenu.removeMenuItem(removedMenu);
|
||||||
|
removedWindow.setVisible(false);
|
||||||
|
removedWindow.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receivePublic(MpChatMessage message) {
|
||||||
|
publicChatWindow.receiveMessage(message);
|
||||||
|
}
|
||||||
|
public void receivePrivate(TcpConnection tcpConnection, MpChatMessage message) {
|
||||||
|
chatWindows.get(tcpConnection).receiveMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openChatWindow(InetAddress address) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPeersChanged(List<User> list) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package net;
|
||||||
|
|
||||||
|
import pitiupi.models.Messages.Message;
|
||||||
|
|
||||||
|
public class MpChatMessage extends Message {
|
||||||
|
private String nome;
|
||||||
|
private String ip;
|
||||||
|
private String tipoMensagem;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public MpChatMessage(String nome, String ip, String tipoMensagem, String message) {
|
||||||
|
this.nome = nome;
|
||||||
|
this.ip = ip;
|
||||||
|
this.tipoMensagem = tipoMensagem;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MpChatMessage(){}
|
||||||
|
|
||||||
|
public String getNome() {
|
||||||
|
return nome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTipoMensagem() {
|
||||||
|
return tipoMensagem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package plugin;
|
||||||
|
|
||||||
|
import GUI.ChatMenu;
|
||||||
|
import control.ChatController;
|
||||||
|
import net.MpChatMessage;
|
||||||
|
import pitiupi.control.ConnectionManager;
|
||||||
|
import pitiupi.control.GuiManager;
|
||||||
|
import pitiupi.models.Messages.Message;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
import pitiupi.models.Plugin;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
public class MultiProtocolChat extends Plugin {
|
||||||
|
ChatController chatController;
|
||||||
|
ConnectionManager connectionManager;
|
||||||
|
GuiManager guiManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "MultiProtocol chat";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthor() {
|
||||||
|
return "Gustavo Henrique";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVersion() {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentifier() {
|
||||||
|
return "Multi.Protocol.Chat.Pitiupi";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
this.guiManager = guiManager;
|
||||||
|
|
||||||
|
ChatMenu chatMenu = new ChatMenu(this);
|
||||||
|
chatController = new ChatController(connectionManager, chatMenu);
|
||||||
|
guiManager.addMulticastMenu(chatMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message getMessage() {
|
||||||
|
return new MpChatMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receiveMessage(byte[] message) {
|
||||||
|
MpChatMessage mpChatMessage = parseMessage(message);
|
||||||
|
chatController.receivePublic(mpChatMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean receivePrivateConnection(TcpConnection connection) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
chatController.addChatWindow(connection);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean receiveBytes(TcpConnection connection, byte[] bytes) {
|
||||||
|
MpChatMessage mpChatMessage = parseMessage(bytes);
|
||||||
|
chatController.receivePrivate(connection, mpChatMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSocketClosed(TcpConnection connection) {
|
||||||
|
chatController.removeChatWindow(connection);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean openConnection(String ip) {
|
||||||
|
try {
|
||||||
|
connectionManager.connectPluginTcp(InetAddress.getByName(ip), getIdentifier(), getIdentifier());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
System.out.println("Error opening connection: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MpChatMessage parseMessage(byte[] bytes) {
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||||
|
ObjectInputStream ois;
|
||||||
|
try {
|
||||||
|
ois = new ObjectInputStream(bis);
|
||||||
|
if (ois.readObject() instanceof MpChatMessage msg) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AntConfiguration">
|
||||||
|
<buildFile url="file://$PROJECT_DIR$/build.xml" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+3
@@ -0,0 +1,3 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+115
@@ -0,0 +1,115 @@
|
|||||||
|
<?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.testSocket/Makefile" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/.idea/misc.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.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">{
|
||||||
|
"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.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
|
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||||
|
"git-widget-placeholder": "feature/separacao-gerenciadores",
|
||||||
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
|
"kotlin-language-version-configured": "true",
|
||||||
|
"last_opened_file_path": "C:/Users/Gustavo/OneDrive/Documentos/Faculdade/2026_2/Monitoria/pitiupi/pitiupi.screenCast",
|
||||||
|
"project.structure.last.edited": "Modules",
|
||||||
|
"project.structure.proportion": "0.15",
|
||||||
|
"project.structure.side.proportion": "0.2",
|
||||||
|
"settings.editor.selected.configurable": "preferences.keymap"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
|
||||||
|
<module name="pitiupi.screenCast" />
|
||||||
|
<option name="ENV_FILES" value="" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<option name="SCRIPT_NAME" value="" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="Tox" factoryName="Tox">
|
||||||
|
<module name="pitiupi.screenCast" />
|
||||||
|
<option name="ENV_FILES" value="" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="JetRunConfigurationType">
|
||||||
|
<module name="pitiupi.screenCast" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
|
||||||
|
<module name="pitiupi.screenCast" />
|
||||||
|
<option name="filePath" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-jdk-30f59d01ecdd-37e91769500f-intellij.indexing.shared.core-IU-261.26222.65" />
|
||||||
|
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-IU-261.26222.65" />
|
||||||
|
</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>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#Thu, 16 Jul 2026 17:43:45 -0300
|
#Tue, 29 Sep 2026 11:04:58 -0300
|
||||||
|
|
||||||
|
|
||||||
/home/perolanegra/facul/2.26/fls/pitiupi/pitiupi.screenCast=
|
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\pitiupi\\pitiupi.screenCast=
|
||||||
|
|
||||||
/home/perolanegra/facul/2.26/fls/pitiupi/pitiupi=
|
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\pitiupi\\pitiupi=
|
||||||
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.
@@ -11,7 +11,7 @@ JAR files manifest file (MANIFEST.MF).
|
|||||||
To run the project from the command line, go to the dist folder and
|
To run the project from the command line, go to the dist folder and
|
||||||
type the following:
|
type the following:
|
||||||
|
|
||||||
java -jar "pitiupi.jar"
|
java -jar "pitiupi.screenCast.jar"
|
||||||
|
|
||||||
To distribute this project, zip up the dist folder (including the lib folder)
|
To distribute this project, zip up the dist folder (including the lib folder)
|
||||||
and distribute the ZIP file.
|
and distribute the ZIP file.
|
||||||
BIN
Binary file not shown.
@@ -45,8 +45,8 @@ javac.modulepath=
|
|||||||
javac.processormodulepath=
|
javac.processormodulepath=
|
||||||
javac.processorpath=\
|
javac.processorpath=\
|
||||||
${javac.classpath}
|
${javac.classpath}
|
||||||
javac.source=21
|
javac.source=25
|
||||||
javac.target=21
|
javac.target=25
|
||||||
javac.test.classpath=\
|
javac.test.classpath=\
|
||||||
${javac.classpath}:\
|
${javac.classpath}:\
|
||||||
${build.classes.dir}
|
${build.classes.dir}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MODULE_DIR$/../../pitiupi/dist/pitiupi.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.screenCast.GUI;
|
package pitiupi.screenCast.GUI;
|
||||||
|
|
||||||
|
import pitiupi.control.ConnectionManager;
|
||||||
|
import pitiupi.control.GuiManager;
|
||||||
import pitiupi.screenCast.control.AlarmClock;
|
import pitiupi.screenCast.control.AlarmClock;
|
||||||
import pitiupi.screenCast.control.ImageTransformation;
|
import pitiupi.screenCast.control.ImageTransformation;
|
||||||
import pitiupi.screenCast.control.ScreenShot;
|
import pitiupi.screenCast.control.ScreenShot;
|
||||||
@@ -38,10 +40,13 @@ public class ScreenCastWindow extends javax.swing.JFrame {
|
|||||||
public final static int TIME = 1000;
|
public final static int TIME = 1000;
|
||||||
|
|
||||||
private float compressRatio;
|
private float compressRatio;
|
||||||
private final MainWindow mainWindow;
|
private final ConnectionManager connectionManager;
|
||||||
|
private final GuiManager guiManager;
|
||||||
|
|
||||||
|
public ScreenCastWindow(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
this.guiManager = guiManager;
|
||||||
|
|
||||||
public ScreenCastWindow(MainWindow mainWindow) {
|
|
||||||
this.mainWindow = mainWindow;
|
|
||||||
this.compressRatio = 0.5f;
|
this.compressRatio = 0.5f;
|
||||||
this.initComponents();
|
this.initComponents();
|
||||||
}
|
}
|
||||||
@@ -91,19 +96,12 @@ public class ScreenCastWindow extends javax.swing.JFrame {
|
|||||||
BufferedImage image = ScreenShot.takeAShot();
|
BufferedImage image = ScreenShot.takeAShot();
|
||||||
BufferedImage rescaled = ImageTransformation.resize(image, 1024, 768);
|
BufferedImage rescaled = ImageTransformation.resize(image, 1024, 768);
|
||||||
byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio);
|
byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio);
|
||||||
ScreenCastMessage message = new ScreenCastMessage(this.mainWindow.getUserName(), byteImage);
|
ScreenCastMessage message = new ScreenCastMessage(this.connectionManager.getUserName(), byteImage);
|
||||||
msg = message.toByteArray();
|
|
||||||
|
if (!connectionManager.sendUdpMulticast(message)) this.compressRatio -= 0.1f;
|
||||||
} catch (AWTException | IOException ex) {
|
} catch (AWTException | IOException ex) {
|
||||||
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
if (msg != null) {
|
|
||||||
try {
|
|
||||||
this.mainWindow.getSocket().send(msg);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
System.out.println(ex.toString());
|
|
||||||
this.compressRatio -= 0.1f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveScreenCastMessage(ScreenCastMessage message) {
|
public void receiveScreenCastMessage(ScreenCastMessage message) {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import pitiupi.GUI.MainWindow;
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.control.ConnectionManager;
|
||||||
|
import pitiupi.control.GuiManager;
|
||||||
import pitiupi.net.Message;
|
import pitiupi.net.Message;
|
||||||
import pitiupi.plugin.Plugin;
|
import pitiupi.plugin.Plugin;
|
||||||
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
||||||
@@ -21,7 +23,6 @@ import pitiupi.screenCast.net.ScreenCastMessage;
|
|||||||
public class ScreenCastPlugin implements Plugin {
|
public class ScreenCastPlugin implements Plugin {
|
||||||
|
|
||||||
private ScreenCastWindow screenCastWindow;
|
private ScreenCastWindow screenCastWindow;
|
||||||
private MainWindow mainWindow;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -39,10 +40,14 @@ public class ScreenCastPlugin implements Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createPlugin(MainWindow mainWindow) {
|
public String getIdentifier() {
|
||||||
this.mainWindow = mainWindow;
|
return "Pitiupi.ScreenCast.Example";
|
||||||
this.screenCastWindow = new ScreenCastWindow(mainWindow);
|
}
|
||||||
this.mainWindow.addMenu(new ScreenCastMenu(screenCastWindow));
|
|
||||||
|
@Override
|
||||||
|
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||||
|
this.screenCastWindow = new ScreenCastWindow(connectionManager, guiManager);
|
||||||
|
guiManager.addMenu(new ScreenCastMenu(screenCastWindow));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,9 +59,7 @@ public class ScreenCastPlugin implements Plugin {
|
|||||||
if (ois.readObject() instanceof ScreenCastMessage msg) {
|
if (ois.readObject() instanceof ScreenCastMessage msg) {
|
||||||
this.screenCastWindow.receiveScreenCastMessage(msg);
|
this.screenCastWindow.receiveScreenCastMessage(msg);
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
System.out.println(ex.toString());
|
|
||||||
} catch (ClassNotFoundException ex) {
|
|
||||||
System.out.println(ex.toString());
|
System.out.println(ex.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# NetBeans
|
||||||
|
/build/
|
||||||
|
/dist/
|
||||||
|
/nbproject/private/
|
||||||
|
|
||||||
|
# Java compilado
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# JARs gerados
|
||||||
|
*.jar
|
||||||
|
|
||||||
|
.idea/
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
Generated
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="pitiupi">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/dist/pitiupi.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="plugins">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/plugins/pitiupi.chat.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/plugins/pitiupi.screenCast.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_26_PREVIEW" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/pitiupi.iml" filepath="$PROJECT_DIR$/pitiupi.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
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.
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,547 @@
|
|||||||
|
# Visão geral
|
||||||
|
|
||||||
|
O `Pitiupi` é uma plataforma P2P baseada em uma arquitetura de plugins,
|
||||||
|
desenvolvida para permitir a criação de aplicações distribuídas sobre uma
|
||||||
|
infraestrutura comum de comunicação em rede.
|
||||||
|
|
||||||
|
A aplicação fornece mecanismos de descoberta de usuários, comunicação entre
|
||||||
|
peers, troca de mensagens serializadas e integração de extensões por meio de
|
||||||
|
plugins. A comunicação de rede utiliza dois protocolos de transporte: UDP,
|
||||||
|
empregado principalmente na descoberta e comunicação multicast entre as
|
||||||
|
instâncias, e TCP, utilizado para comunicação direta e persistente entre peers.
|
||||||
|
|
||||||
|
Dessa forma, novos comportamentos podem ser adicionados por meio de plugins
|
||||||
|
sem modificar o núcleo da aplicação.
|
||||||
|
|
||||||
|
O projeto foi desenvolvido como suporte às práticas da disciplina de Redes de
|
||||||
|
Computadores, servindo também como base para experimentação de aplicações
|
||||||
|
distribuídas e desenvolvimento de projetos acadêmicos.
|
||||||
|
|
||||||
|
# Arquitetura geral
|
||||||
|
|
||||||
|
A aplicação é dividida em três grupos principais:
|
||||||
|
|
||||||
|
- **Núcleo da aplicação**: A `MainWindow` atua como ponto central da aplicação,
|
||||||
|
sendo responsável por inicializar e integrar esses componentes durante a
|
||||||
|
execução.
|
||||||
|
- **Sistema de plugins**: responsável por adicionar funcionalidades sem
|
||||||
|
alterar o núcleo da aplicação.
|
||||||
|
- **Comunicação de rede**: responsável pela troca de mensagens entre
|
||||||
|
instâncias da aplicação.
|
||||||
|
|
||||||
|
## Inicialização da aplicação
|
||||||
|
|
||||||
|
1. A MainWindow é criada.
|
||||||
|
2. O SocketUDP é inicializado, associa-se à porta configurada e ingressa no
|
||||||
|
grupo multicast.
|
||||||
|
3. O SocketTCP é inicializado e cria um ServerSocket na porta da aplicação
|
||||||
|
para aceitar conexões TCP.
|
||||||
|
4. Os mecanismos de comunicação iniciam seus respectivos processos de recepção.
|
||||||
|
5. O HeartbeatManager é criado e inicia o envio periódico de heartbeats.
|
||||||
|
6. O painel de usuários online é registrado como listener do HeartbeatManager.
|
||||||
|
7. O PluginLoader procura e carrega os plugins disponíveis.
|
||||||
|
|
||||||
|
# Sistema de comunicação
|
||||||
|
|
||||||
|
A comunicação entre as instâncias da aplicação é realizada por meio de dois
|
||||||
|
mecanismos complementares:
|
||||||
|
|
||||||
|
- **UDP multicast/unicast**, implementado por `SocketUDP`
|
||||||
|
- **TCP**, implementado por `SocketTCP` e `TcpConnection`
|
||||||
|
|
||||||
|
Ambos os mecanismos utilizam mensagens serializadas em `byte[]`, permitindo que
|
||||||
|
os plugins permaneçam independentes dos detalhes do protocolo de transporte.
|
||||||
|
|
||||||
|
## Comunicação UDP
|
||||||
|
|
||||||
|
A classe `SocketUDP` é responsável pela comunicação UDP da aplicação.
|
||||||
|
|
||||||
|
Ela utiliza um `MulticastSocket` associado à porta configurada pela aplicação e
|
||||||
|
ingressa no grupo multicast `224.0.0.3`. Por ser baseada em UDP, a comunicação não
|
||||||
|
estabelece uma conexão permanente entre os peers.
|
||||||
|
|
||||||
|
A classe estende `Thread`, mantendo um processo contínuo de recepção de datagramas
|
||||||
|
enquanto a aplicação está em execução.
|
||||||
|
|
||||||
|
O SocketUDP suporta dois modos de envio:
|
||||||
|
|
||||||
|
- **multicast**, destinado ao grupo de peers
|
||||||
|
- **unicast**, destinado diretamente ao endereço IP de um peer específico
|
||||||
|
|
||||||
|
### Inicialização
|
||||||
|
|
||||||
|
Durante sua inicialização, o `SocketUDP`:
|
||||||
|
|
||||||
|
1. obtém o endereço do grupo multicast;
|
||||||
|
2. cria um `MulticastSocket` utilizando a porta configurada na `MainWindow`;
|
||||||
|
3. configura os buffers de envio e recepção;
|
||||||
|
4. habilita o reuso do endereço;
|
||||||
|
5. ingressa no grupo multicast.
|
||||||
|
|
||||||
|
O tamanho dos buffers de comunicação é configurado para `256000` bytes.
|
||||||
|
|
||||||
|
### Envio de mensagens UDP
|
||||||
|
|
||||||
|
O `SocketUDP` suporta dois modos de envio de mensagens: multicast e unicast, ambos
|
||||||
|
baseados no mesmo mecanismo interno de criação e envio de DatagramPacket.
|
||||||
|
|
||||||
|
O método `sendMulticast()` envia uma mensagem para o grupo multicast da aplicação.
|
||||||
|
Nesse caso, o `DatagramPacket` é direcionado ao endereço multicast configurado,
|
||||||
|
permitindo que todas as instâncias participantes do grupo recebam a mensagem
|
||||||
|
simultaneamente.
|
||||||
|
|
||||||
|
Já o método `sendUnicast()` permite enviar uma mensagem diretamente para um peer
|
||||||
|
específico. Nesse caso, o `DatagramPacket` é direcionado ao endereço IP informado pelo
|
||||||
|
chamador, mantendo a mesma porta utilizada pela aplicação.
|
||||||
|
|
||||||
|
Ambos os métodos são, na prática, abstrações de um único mecanismo interno de envio,
|
||||||
|
que utiliza o mesmo processo de criação e envio de `DatagramPacket` por baixo dos
|
||||||
|
panos. A diferença entre eles está apenas no endereço de destino: enquanto o multicast
|
||||||
|
utiliza o grupo compartilhado, o unicast direciona o pacote a um IP específico.
|
||||||
|
|
||||||
|
### Recepção de mensagens
|
||||||
|
|
||||||
|
O método `receive()` mantém um loop aguardando novos datagramas.
|
||||||
|
|
||||||
|
Ao receber uma mensagem, o `SocketUDP` inicialmente tenta identificar se os dados
|
||||||
|
correspondem a uma `HeartbeatMessage`.
|
||||||
|
|
||||||
|
O processamento segue o seguinte fluxo:
|
||||||
|
|
||||||
|
1. O datagrama é recebido pelo `MulticastSocket`.
|
||||||
|
2. Os dados recebidos são analisados pelo método `tryParseHeartbeat()`.
|
||||||
|
3. Caso seja identificada uma `HeartbeatMessage`, ela é encaminhada ao
|
||||||
|
`HeartbeatManager`, juntamente com o endereço IP de origem.
|
||||||
|
4. Caso não seja um heartbeat, a mensagem é encaminhada para todos os plugins
|
||||||
|
carregados.
|
||||||
|
5. Cada plugin decide se a mensagem recebida pertence à sua funcionalidade.
|
||||||
|
|
||||||
|
Essa separação mantém as mensagens de infraestrutura, como heartbeat, sob
|
||||||
|
responsabilidade do núcleo da aplicação, enquanto as mensagens específicas das
|
||||||
|
funcionalidades são processadas pelos plugins.
|
||||||
|
|
||||||
|
### Identificação de mensagens de heartbeat
|
||||||
|
|
||||||
|
O método `tryParseHeartbeat()` tenta desserializar os dados recebidos e verificar se o
|
||||||
|
objeto resultante é uma instância de `HeartbeatMessage`.
|
||||||
|
|
||||||
|
Caso a mensagem não possa ser desserializada ou não corresponda ao tipo esperado, o
|
||||||
|
método retorna `null`. Nesse caso, a mensagem é tratada como uma mensagem destinada aos
|
||||||
|
plugins.
|
||||||
|
|
||||||
|
### Encerramento
|
||||||
|
|
||||||
|
O método `close()` interrompe a execução do processo de recepção, remove a aplicação
|
||||||
|
do grupo multicast e fecha o `MulticastSocket`.
|
||||||
|
|
||||||
|
A variável `running` é utilizada para sinalizar que o processo de recepção deve ser
|
||||||
|
encerrado.
|
||||||
|
|
||||||
|
## Comunicação TCP
|
||||||
|
|
||||||
|
A comunicação TCP é implementada pela classe `SocketTCP`.
|
||||||
|
|
||||||
|
Diferentemente do UDP, o TCP estabelece conexões entre dois peers. Essas conexões
|
||||||
|
são mantidas enquanto forem necessárias e podem ser reutilizadas para o envio de
|
||||||
|
múltiplas mensagens.
|
||||||
|
|
||||||
|
A arquitetura utiliza uma conexão TCP persistente por endereço de peer.
|
||||||
|
|
||||||
|
O SocketTCP possui um mapa de conexões:
|
||||||
|
|
||||||
|
`InetAddress` -> `TcpConnection`
|
||||||
|
|
||||||
|
Esse mapa permite localizar rapidamente uma conexão existente para determinado peer e
|
||||||
|
reutilizá-la durante novos envios.
|
||||||
|
|
||||||
|
### SocketTCP
|
||||||
|
|
||||||
|
A classe `SocketTCP` estende `Thread` e implementa `PeerListener`.
|
||||||
|
|
||||||
|
Ao ser criada, ela inicializa um `ServerSocket` utilizando a porta configurada na
|
||||||
|
aplicação.
|
||||||
|
|
||||||
|
O servidor TCP permanece aguardando novas conexões enquanto a aplicação estiver
|
||||||
|
em execução.
|
||||||
|
|
||||||
|
### Envio de mensagens TCP
|
||||||
|
|
||||||
|
O método `send()` recebe uma mensagem serializada e o endereço IP do peer destinatário.
|
||||||
|
|
||||||
|
Antes de enviar a mensagem, o `SocketTCP` verifica se já existe uma conexão válida com
|
||||||
|
o peer.
|
||||||
|
|
||||||
|
O comportamento é:
|
||||||
|
|
||||||
|
1. Procurar uma conexão existente no mapa de conexões.
|
||||||
|
2. Verificar se a conexão está fechada.
|
||||||
|
3. Caso não exista uma conexão válida, criar um novo Socket TCP para o endereço do peer.
|
||||||
|
4. Encapsular o socket em uma `TcpConnection`.
|
||||||
|
5. Registrar a conexão no mapa.
|
||||||
|
6. Iniciar uma tarefa de recepção para essa conexão.
|
||||||
|
7. Enviar a mensagem pela conexão.
|
||||||
|
|
||||||
|
Assim, uma conexão TCP existente pode ser reutilizada para várias mensagens, evitando
|
||||||
|
a criação de um novo socket para cada envio.
|
||||||
|
|
||||||
|
### Recepção de conexões
|
||||||
|
|
||||||
|
O método `receive()` permanece aguardando novas conexões através de
|
||||||
|
`ServerSocket.accept()`.
|
||||||
|
|
||||||
|
Quando uma conexão é aceita:
|
||||||
|
|
||||||
|
O endereço IP do peer é obtido.
|
||||||
|
Um objeto `TcpConnection` é criado para encapsular o socket.
|
||||||
|
A conexão é registrada no mapa de conexões.
|
||||||
|
Caso já exista uma conexão associada ao mesmo endereço, a conexão anterior é encerrada.
|
||||||
|
Uma tarefa independente de recepção é criada para a nova conexão.
|
||||||
|
|
||||||
|
Esse modelo permite que o servidor aceite múltiplas conexões sem bloquear o
|
||||||
|
processamento das conexões já existentes.
|
||||||
|
|
||||||
|
### Recepção concorrente
|
||||||
|
|
||||||
|
A recepção das mensagens TCP é realizada de maneira concorrente por meio de um
|
||||||
|
`ExecutorService`.
|
||||||
|
|
||||||
|
Cada conexão possui uma tarefa própria responsável por receber continuamente as
|
||||||
|
mensagens.
|
||||||
|
|
||||||
|
O fluxo de recepção é:
|
||||||
|
|
||||||
|
```
|
||||||
|
SocketTCP
|
||||||
|
|
|
||||||
|
+-- Peer A → TcpConnection → tarefa de recepção
|
||||||
|
|
|
||||||
|
+-- Peer B → TcpConnection → tarefa de recepção
|
||||||
|
|
|
||||||
|
+-- Peer C → TcpConnection → tarefa de recepção
|
||||||
|
```
|
||||||
|
|
||||||
|
Dessa forma, uma conexão bloqueada ou aguardando dados não impede que outras conexões
|
||||||
|
continuem sendo processadas.
|
||||||
|
|
||||||
|
Quando uma mensagem é recebida por uma `TcpConnection`, ela é encaminhada para todos os
|
||||||
|
plugins registrados na `MainWindow`.
|
||||||
|
|
||||||
|
O plugin responsável pela funcionalidade deve identificar o tipo da mensagem e
|
||||||
|
realizar o processamento correspondente.
|
||||||
|
|
||||||
|
### TcpConnection
|
||||||
|
|
||||||
|
A classe TcpConnection encapsula uma conexão TCP individual entre dois peers.
|
||||||
|
|
||||||
|
Ela mantém:
|
||||||
|
|
||||||
|
o Socket utilizado na conexão;
|
||||||
|
um DataInputStream para recepção;
|
||||||
|
um DataOutputStream para envio.
|
||||||
|
|
||||||
|
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
|
||||||
|
|
||||||
|
### Framing das mensagens TCP
|
||||||
|
|
||||||
|
A classe `TcpConnection` encapsula uma conexão TCP individual entre dois peers.
|
||||||
|
|
||||||
|
Ela mantém:
|
||||||
|
|
||||||
|
1. o `Socket` utilizado na conexão;
|
||||||
|
2. um `DataInputStream` para recepção;
|
||||||
|
3. um `DataOutputStream` para envio.
|
||||||
|
|
||||||
|
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
|
||||||
|
|
||||||
|
Framing das mensagens TCP
|
||||||
|
|
||||||
|
O protocolo TCP fornece um fluxo contínuo de bytes e não preserva os limites das
|
||||||
|
mensagens enviadas pela aplicação.
|
||||||
|
|
||||||
|
Por esse motivo, é adicionado o tamanho da mensagem antes do conteúdo.
|
||||||
|
|
||||||
|
O formato utilizado é:
|
||||||
|
|
||||||
|
```
|
||||||
|
+----------------------+----------------------+
|
||||||
|
| tamanho da mensagem | dados da mensagem |
|
||||||
|
+----------------------+----------------------+
|
||||||
|
4 bytes N bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
O tamanho é enviado como um `int`, seguido pelos bytes da mensagem.
|
||||||
|
|
||||||
|
No envio, o método `TcpConnection.send()`:
|
||||||
|
|
||||||
|
1. obtém o tamanho do vetor de bytes;
|
||||||
|
2. escreve o tamanho no `DataOutputStream`;
|
||||||
|
3. escreve os dados da mensagem;
|
||||||
|
4. realiza `flush()` no stream.
|
||||||
|
|
||||||
|
O acesso ao stream de saída é sincronizado para evitar que envios concorrentes
|
||||||
|
misturem seus dados.
|
||||||
|
|
||||||
|
Na recepção, o método `receive()`:
|
||||||
|
|
||||||
|
1. lê o tamanho da mensagem;
|
||||||
|
2. verifica se o tamanho é válido;
|
||||||
|
3. lê a quantidade correspondente de bytes;
|
||||||
|
4. retorna o vetor de bytes completo.
|
||||||
|
|
||||||
|
Esse mecanismo permite que várias mensagens sejam transmitidas pela mesma conexão
|
||||||
|
TCP sem que o receptor perca a delimitação entre elas.
|
||||||
|
|
||||||
|
### Integração entre TCP e gerenciamento de peers
|
||||||
|
|
||||||
|
O `SocketTCP` implementa a interface PeerListener para acompanhar as alterações na
|
||||||
|
lista de peers ativos.
|
||||||
|
|
||||||
|
O `HeartbeatManager` mantém a lista de peers atualmente detectados na rede. Quando
|
||||||
|
essa lista é alterada, o SocketTCP recebe a notificação por meio do método
|
||||||
|
`onPeersChanged()`.
|
||||||
|
|
||||||
|
O método obtém os endereços IP dos peers atualmente ativos e compara esses endereços
|
||||||
|
com as conexões TCP existentes.
|
||||||
|
|
||||||
|
Quando uma conexão pertence a um peer que não está mais ativo:
|
||||||
|
|
||||||
|
a conexão é removida do mapa;
|
||||||
|
a conexão TCP é encerrada;
|
||||||
|
os recursos associados são liberados.
|
||||||
|
|
||||||
|
Esse mecanismo impede que conexões TCP permaneçam abertas indefinidamente para peers
|
||||||
|
que já deixaram de participar da rede.
|
||||||
|
|
||||||
|
O fluxo de integração pode ser representado da seguinte forma:
|
||||||
|
```
|
||||||
|
HeartbeatManager
|
||||||
|
|
|
||||||
|
| peers ativos alterados
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SocketTCP.onPeersChanged()
|
||||||
|
|
|
||||||
|
| compara peers ativos
|
||||||
|
|
|
||||||
|
v
|
||||||
|
+---- peer ativo ------> mantém conexão
|
||||||
|
|
|
||||||
|
+---- peer inativo ----> fecha conexão
|
||||||
|
```
|
||||||
|
|
||||||
|
### Message
|
||||||
|
|
||||||
|
A classe `Message` representa a estrutura base das mensagens trocadas pela aplicação.
|
||||||
|
|
||||||
|
Todas as mensagens utilizadas pelo sistema devem herdar dessa classe. Ela implementa
|
||||||
|
`Serializable`, permitindo que objetos de mensagem sejam convertidos em vetores de
|
||||||
|
bytes para transmissão pela rede.
|
||||||
|
|
||||||
|
A serialização é realizada pelo método `toByteArray()`, que transforma uma instância
|
||||||
|
da mensagem em uma representação binária enviada pelos mecanismos de comunicação.
|
||||||
|
|
||||||
|
As mensagens específicas devem estender essa classe, adicionando os atributos e
|
||||||
|
comportamentos necessários para cada funcionalidade.
|
||||||
|
|
||||||
|
A camada de transporte não depende do conteúdo específico das mensagens. Tanto o
|
||||||
|
UDP quanto o TCP recebem mensagens serializadas como `byte[]`.
|
||||||
|
|
||||||
|
### Heartbeat
|
||||||
|
|
||||||
|
O mecanismo de heartbeat é utilizado para identificar quais usuários estão ativos
|
||||||
|
na rede.
|
||||||
|
|
||||||
|
A aplicação envia periodicamente uma `HeartbeatMessage` contendo informações do usuário,
|
||||||
|
um identificador único da instância da aplicação e um timestamp.
|
||||||
|
|
||||||
|
Essas mensagens são transmitidas utilizando a comunicação UDP multicast.
|
||||||
|
|
||||||
|
Quando outra instância recebe uma mensagem de heartbeat, o `SocketUDP` identifica
|
||||||
|
o tipo da mensagem e encaminha o objeto ao `HeartbeatManager`, juntamente com o endereço
|
||||||
|
IP de origem.
|
||||||
|
|
||||||
|
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 todos os
|
||||||
|
componentes registrados como listeners de mudanças de peers.
|
||||||
|
|
||||||
|
Esses listeners podem incluir diferentes partes da aplicação, como componentes
|
||||||
|
da interface gráfica, mecanismos de comunicação (como o `SocketTCP`) e também plugins
|
||||||
|
que desejem reagir à presença ou ausência de usuários na rede.
|
||||||
|
|
||||||
|
Dessa forma, o sistema permite que tanto o núcleo da aplicação quanto extensões
|
||||||
|
externas sejam notificados sobre alterações no estado da rede, mantendo a arquitetura
|
||||||
|
flexível e extensível.
|
||||||
|
|
||||||
|
O fluxo completo é:
|
||||||
|
|
||||||
|
```
|
||||||
|
HeartbeatManager
|
||||||
|
|
|
||||||
|
| cria HeartbeatMessage
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Message.toByteArray()
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SocketUDP.sendMulticast()
|
||||||
|
|
|
||||||
|
v
|
||||||
|
UDP Multicast
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SocketUDP.receive()
|
||||||
|
|
|
||||||
|
v
|
||||||
|
HeartbeatManager.receiveHeartbeat()
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Lista de peers ativos
|
||||||
|
|
|
||||||
|
+----> Interface de usuários online
|
||||||
|
|
|
||||||
|
+----> SocketTCP (PeerListener)
|
||||||
|
|
|
||||||
|
+----> Plugins (PeerListener)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sistema de plugins
|
||||||
|
|
||||||
|
O sistema de plugins é responsável por permitir a extensão das funcionalidades da
|
||||||
|
aplicação de forma modular, sem a necessidade de alterações no núcleo do sistema.
|
||||||
|
Ele define um contrato comum que deve ser seguido por todas as extensões, garantindo
|
||||||
|
que possam ser carregadas dinamicamente e integradas à comunicação e à interface da
|
||||||
|
aplicação.
|
||||||
|
|
||||||
|
### Interface Plugin
|
||||||
|
|
||||||
|
A interface `Plugin` define o contrato que deve ser implementado por qualquer
|
||||||
|
extensão da aplicação.
|
||||||
|
|
||||||
|
Um plugin deve fornecer informações básicas de identificação (`name`,
|
||||||
|
`author` e `version`), inicializar seu estado através de `createPlugin()` e
|
||||||
|
participar do sistema de comunicação utilizando mensagens derivadas de
|
||||||
|
`Message`.
|
||||||
|
|
||||||
|
Os principais métodos são:
|
||||||
|
|
||||||
|
#### Identificação
|
||||||
|
|
||||||
|
Os métodos:
|
||||||
|
- `getName()`
|
||||||
|
- `getAuthor()`
|
||||||
|
- `getVersion()`
|
||||||
|
|
||||||
|
fornecem informações utilizadas pela aplicação para identificar o plugin.
|
||||||
|
|
||||||
|
#### Inicialização
|
||||||
|
|
||||||
|
`createPlugin(MainWindow window)`
|
||||||
|
|
||||||
|
É chamado após o carregamento do plugin e permite que ele registre seus
|
||||||
|
componentes na aplicação, como menus ou elementos de interface.
|
||||||
|
|
||||||
|
#### Comunicação
|
||||||
|
|
||||||
|
`getMessage()`
|
||||||
|
|
||||||
|
Deve retornar uma mensagem utilizada pelo plugin para comunicação com outras
|
||||||
|
instâncias da aplicação.
|
||||||
|
|
||||||
|
`receiveMessage(byte[] message)`
|
||||||
|
|
||||||
|
É chamado pela aplicação sempre que uma mensagem é recebida. Cada plugin deve
|
||||||
|
verificar se a mensagem pertence ao seu tipo e realizar o processamento
|
||||||
|
correspondente.
|
||||||
|
|
||||||
|
### Descoberta de plugins
|
||||||
|
|
||||||
|
Para serem reconhecidos, os plugins devem possuir os arquivos de configuração
|
||||||
|
do Java Service Provider Interface (SPI):
|
||||||
|
|
||||||
|
```
|
||||||
|
MeusPlugins
|
||||||
|
|
|
||||||
|
+---META-INF
|
||||||
|
|
|
||||||
|
+---services
|
||||||
|
|
|
||||||
|
+---pitiupi.models.Plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
O arquivo `pitiupi.models.Plugin` deve conter o nome completo das classes que
|
||||||
|
implementam a interface Plugin:
|
||||||
|
|
||||||
|
```
|
||||||
|
MeusPlugins.plugins.NomeDaClasse
|
||||||
|
```
|
||||||
|
|
||||||
|
Para serem carregados, os plugins devem estar empacotados como arquivos JAR
|
||||||
|
e colocados no diretório plugins da aplicação.
|
||||||
|
|
||||||
|
### Carregamento de plugins
|
||||||
|
|
||||||
|
Ao iniciar a aplicação, o `PluginLoader` executa os seguintes passos:
|
||||||
|
|
||||||
|
1. Procura arquivos `.jar` dentro do diretório `plugins`.
|
||||||
|
2. Cria um `URLClassLoader` contendo os JARs encontrados.
|
||||||
|
3. Utiliza `ServiceLoader` para localizar classes que implementam
|
||||||
|
`Plugin`.
|
||||||
|
4. Instancia cada plugin encontrado.
|
||||||
|
5. Chama o método `createPlugin(MainWindow)`.
|
||||||
|
6. Registra o plugin na lista de plugins ativos da aplicação.
|
||||||
|
|
||||||
|
## Interface gráfica
|
||||||
|
|
||||||
|
A interface gráfica da aplicação é baseada em Swing e é centralizada na
|
||||||
|
classe `MainWindow`.
|
||||||
|
|
||||||
|
Durante a inicialização, cada plugin recebe uma referência para a
|
||||||
|
`MainWindow` através do método `createPlugin(MainWindow)`.
|
||||||
|
|
||||||
|
Essa referência permite integrar componentes gráficos à aplicação, sendo a
|
||||||
|
principal forma de extensão a adição de menus à barra de menus:
|
||||||
|
|
||||||
|
```
|
||||||
|
JMenu menu = new JMenu("Meu Plugin");
|
||||||
|
window.addMenu(menu);
|
||||||
|
```
|
||||||
|
|
||||||
|
Visão geral da comunicação
|
||||||
|
|
||||||
|
A arquitetura de comunicação pode ser resumida da seguinte maneira:
|
||||||
|
```
|
||||||
|
+------------------+
|
||||||
|
| MainWindow |
|
||||||
|
+--------+---------+
|
||||||
|
|
|
||||||
|
+--------------------+--------------------+
|
||||||
|
| | |
|
||||||
|
v v v
|
||||||
|
HeartbeatManager SocketUDP SocketTCP
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
v v v
|
||||||
|
Heartbeat UDP Multicast TCP Connections
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+---------+----------+
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Plugins
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Message
|
||||||
|
```
|
||||||
|
|
||||||
|
O UDP multicast fornece a infraestrutura de descoberta e manutenção da presença dos
|
||||||
|
peers, além de permitir comunicação multicast e unicast.
|
||||||
|
|
||||||
|
O TCP fornece canais diretos e persistentes entre peers, com uma conexão reutilizável
|
||||||
|
por endereço.
|
||||||
|
|
||||||
|
Os dois mecanismos encaminham as mensagens destinadas às funcionalidades da aplicação
|
||||||
|
para os plugins, mantendo separadas as responsabilidades de transporte, infraestrutura
|
||||||
|
e lógica específica de cada extensão.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Como criar plugins para o Share my Sheet
|
||||||
|
|
||||||
@@ -5,4 +5,4 @@ do.jlink=false
|
|||||||
javac.debug=true
|
javac.debug=true
|
||||||
javadoc.preview=true
|
javadoc.preview=true
|
||||||
jlink.strip=false
|
jlink.strip=false
|
||||||
user.properties.file=/home/perolanegra/snap/netbeans/145/build.properties
|
user.properties.file=/home/flavio/snap/netbeans/145/build.properties
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
|
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
|
||||||
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/>
|
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/>
|
||||||
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2">
|
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2">
|
||||||
<group/>
|
<group>
|
||||||
|
<file>file:/home/flavio/NetBeansProjects/pitiupi/src/pitiupi/control/PluginManager.java</file>
|
||||||
|
<file>file:/home/flavio/NetBeansProjects/pitiupi/src/pitiupi/control/ImageTransformation.java</file>
|
||||||
|
</group>
|
||||||
</open-files>
|
</open-files>
|
||||||
</project-private>
|
</project-private>
|
||||||
@@ -46,8 +46,8 @@ javac.modulepath=
|
|||||||
javac.processormodulepath=
|
javac.processormodulepath=
|
||||||
javac.processorpath=\
|
javac.processorpath=\
|
||||||
${javac.classpath}
|
${javac.classpath}
|
||||||
javac.source=21
|
javac.source=17
|
||||||
javac.target=21
|
javac.target=17
|
||||||
javac.test.classpath=\
|
javac.test.classpath=\
|
||||||
${javac.classpath}:\
|
${javac.classpath}:\
|
||||||
${build.classes.dir}
|
${build.classes.dir}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../pitiupi.chat/dist/pitiupi.chat.jar
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Janela que exibe informações sobre a aplicação, incluindo versão,
|
||||||
|
* descrição e autores.
|
||||||
|
*
|
||||||
|
* @author gustavo
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class About extends javax.swing.JFrame {
|
||||||
|
public About () {
|
||||||
|
this.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
|
||||||
|
initComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
this.setTitle("About");
|
||||||
|
this.setBounds(new java.awt.Rectangle(0, 0, 800, 600));
|
||||||
|
this.getContentPane().setLayout(new java.awt.BorderLayout(5, 5));
|
||||||
|
this.setPreferredSize(new java.awt.Dimension(800, 600));
|
||||||
|
|
||||||
|
JScrollPane scroll = getJScrollPane();
|
||||||
|
|
||||||
|
this.add(scroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JScrollPane getJScrollPane() {
|
||||||
|
JEditorPane texto = new JEditorPane();
|
||||||
|
texto.setContentType("text/html");
|
||||||
|
texto.setEditable(false);
|
||||||
|
|
||||||
|
texto.setText("""
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Share My Sheet</h1>
|
||||||
|
\s
|
||||||
|
<p>
|
||||||
|
<b>Versão:</b> 1.0
|
||||||
|
</p>
|
||||||
|
\s
|
||||||
|
<p>
|
||||||
|
Plataforma P2P baseada em plugins para o desenvolvimento de aplicações
|
||||||
|
distribuídas. Criada para apoiar as práticas da disciplina de Redes de
|
||||||
|
Computadores e servir como base para projetos acadêmicos e TCCs.
|
||||||
|
\s
|
||||||
|
</p>
|
||||||
|
\s
|
||||||
|
<p>
|
||||||
|
<b>Autores:</b><br>
|
||||||
|
Flávio<br>
|
||||||
|
Tony<br>
|
||||||
|
Gustavo Henrique
|
||||||
|
</p>
|
||||||
|
\s
|
||||||
|
<p>
|
||||||
|
Universidade Federal de São João del-Rei (UFSJ)
|
||||||
|
</p>
|
||||||
|
\s
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
\s""");
|
||||||
|
|
||||||
|
return new JScrollPane(texto);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -20,11 +16,20 @@ import javax.swing.JSpinner;
|
|||||||
import javax.swing.JTextField;
|
import javax.swing.JTextField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Janela de preferencias da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Permite ao usuário informar o nome utilizado na rede e a porta
|
||||||
|
* 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: ");
|
||||||
|
|
||||||
@@ -43,8 +48,11 @@ 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));
|
String nomeSalvo = prefs.get(PREF_NOME, System.getProperty("user.name"));
|
||||||
nameField.setText(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));
|
||||||
@@ -83,6 +91,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,79 +5,93 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.GUI;
|
package pitiupi.GUI;
|
||||||
|
|
||||||
import pitiupi.net.Socket;
|
import pitiupi.control.ConnectionManager;
|
||||||
import java.util.ArrayList;
|
import pitiupi.control.GuiManager;
|
||||||
import java.util.List;
|
import pitiupi.control.plugins.PluginManager;
|
||||||
import javax.swing.JMenu;
|
|
||||||
import pitiupi.control.PluginLoader;
|
import javax.swing.*;
|
||||||
import pitiupi.plugin.Plugin;
|
|
||||||
import pitiupi.control.HeartbeatManager;
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Janela principal da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Responsável por inicializar a interface gráfica e coordenar os
|
||||||
|
* principais componentes da aplicação, incluindo comunicação de rede,
|
||||||
|
* gerenciamento de heartbeat e carregamento de plugins.</p>
|
||||||
|
*
|
||||||
|
* <p>A instância desta classe é compartilhada com os plugins para permitir
|
||||||
|
* integração com a aplicação principal.</p>
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
|
* @author tony
|
||||||
|
* @author Gustavo
|
||||||
*/
|
*/
|
||||||
public class MainWindow extends javax.swing.JFrame {
|
public class MainWindow extends JFrame {
|
||||||
|
|
||||||
private final MenuBar menuBar;
|
private MenuBar menuBar;
|
||||||
private javax.swing.JScrollPane jScrollPane1;
|
private JLabel status;
|
||||||
private javax.swing.JLabel status;
|
OnlineUsersPanel onlineUsersPanel;
|
||||||
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 PluginLoader pluginManager;
|
|
||||||
private final List<Plugin> plugins;
|
|
||||||
|
|
||||||
|
private final ConnectionManager connectionManager;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
private final GuiManager guiManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria a janela principal da aplicação.
|
||||||
|
*
|
||||||
|
* @param userName nome utilizado para identificação na rede.
|
||||||
|
* @param port porta utilizada na comunicação multicast.
|
||||||
|
*/
|
||||||
public MainWindow(String userName, int port) {
|
public MainWindow(String userName, int port) {
|
||||||
this.userName = userName;
|
initMenu();
|
||||||
this.port = port;
|
|
||||||
this.menuBar = new MenuBar(this);
|
|
||||||
this.plugins = new ArrayList<>();
|
|
||||||
this.initComponents();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initComponents() {
|
pluginManager = new PluginManager();
|
||||||
this.setJMenuBar(menuBar);
|
connectionManager = new ConnectionManager(this, userName, pluginManager, port);
|
||||||
this.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
|
|
||||||
this.setTitle("Share My Sheet");
|
|
||||||
|
|
||||||
jScrollPane1 = new javax.swing.JScrollPane();
|
initUserPanel();
|
||||||
tabbedPane = new javax.swing.JTabbedPane();
|
initStatusPanel();
|
||||||
statusPanel = new javax.swing.JPanel();
|
|
||||||
status = new javax.swing.JLabel();
|
|
||||||
|
|
||||||
jScrollPane1.setViewportView(tabbedPane);
|
guiManager = new GuiManager(this, menuBar, onlineUsersPanel);
|
||||||
statusPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT));
|
|
||||||
status.setText("Connected on port " + this.port + " name " + this.userName);
|
|
||||||
statusPanel.add(status);
|
|
||||||
|
|
||||||
this.getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);
|
loadPlugins();
|
||||||
this.getContentPane().add(statusPanel, java.awt.BorderLayout.PAGE_END);
|
|
||||||
|
|
||||||
this.pack();
|
this.pack();
|
||||||
|
|
||||||
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.setSize(new java.awt.Dimension(1024, 768));
|
||||||
this.pluginManager = new PluginLoader(this);
|
|
||||||
this.pluginManager.loadPlugins();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initMenu() {
|
||||||
|
menuBar = new MenuBar(this);
|
||||||
|
|
||||||
|
this.setJMenuBar(menuBar);
|
||||||
|
this.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
|
||||||
|
this.setTitle("Pitiupi");
|
||||||
|
|
||||||
|
javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane();
|
||||||
|
javax.swing.JTabbedPane tabbedPane = new javax.swing.JTabbedPane();
|
||||||
|
|
||||||
|
jScrollPane1.setViewportView(tabbedPane);
|
||||||
|
|
||||||
|
this.getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initUserPanel() {
|
||||||
|
onlineUsersPanel = new OnlineUsersPanel();
|
||||||
|
this.connectionManager.addPeerListener(onlineUsersPanel);
|
||||||
|
this.getContentPane().add(onlineUsersPanel, java.awt.BorderLayout.LINE_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initStatusPanel() {
|
||||||
|
JPanel statusPanel = new JPanel();
|
||||||
|
status = new JLabel();
|
||||||
|
|
||||||
|
statusPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
|
||||||
|
updateStatus();
|
||||||
|
statusPanel.add(status);
|
||||||
|
|
||||||
|
this.getContentPane().add(statusPanel, BorderLayout.PAGE_END);
|
||||||
|
}
|
||||||
|
|
||||||
public void exit() {
|
public void exit() {
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
@@ -85,27 +99,21 @@ public class MainWindow extends javax.swing.JFrame {
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HeartbeatManager getHeartbeatManager() {
|
public ConnectionManager getConnectionManager() {
|
||||||
return heartbeatManager;
|
return connectionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserName(){
|
public void loadPlugins() {
|
||||||
return this.userName;
|
pluginManager.loadPlugins(connectionManager, guiManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMenu(JMenu menu) {
|
public void setUserName(String userName) {
|
||||||
this.menuBar.addPlugin(menu);
|
connectionManager.setUserName(userName);
|
||||||
|
updateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPort() {
|
private void updateStatus() {
|
||||||
return this.port;
|
this.status.setText("Connected on port " + connectionManager.getPort() + " name " + connectionManager.getUserName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Socket getSocket() {
|
|
||||||
return this.socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Plugin> getPlugins() {
|
|
||||||
return plugins;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,64 +5,113 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.GUI;
|
package pitiupi.GUI;
|
||||||
|
|
||||||
import javax.swing.JMenu;
|
import pitiupi.models.Menus.MenuCategory;
|
||||||
import javax.swing.JMenuBar;
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Barra de menus principal da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Gerencia os menus padrões da interface e disponibiliza um espaço
|
||||||
|
* para que plugins adicionem seus próprios menus.</p>
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
|
* @author Gustavo
|
||||||
|
* @author tony
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public class MenuBar extends JMenuBar {
|
public class MenuBar extends JMenuBar {
|
||||||
|
|
||||||
private javax.swing.JMenuItem setupMenu;
|
private JMenuItem preferencesMenu;
|
||||||
private javax.swing.JMenu fileMenu;
|
private JMenuItem aboutMenu;
|
||||||
private javax.swing.JMenu helpMenu;
|
private JMenu fileMenu;
|
||||||
private javax.swing.JMenu pluginMenu;
|
private JMenu helpMenu;
|
||||||
private javax.swing.JMenuItem exitMenu;
|
private JMenu pluginMenu;
|
||||||
|
private Map<MenuCategory, JMenu> categories;
|
||||||
|
|
||||||
|
private 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
fileMenu = new javax.swing.JMenu();
|
initFileMenu();
|
||||||
exitMenu = new javax.swing.JMenuItem();
|
initPluginMenu();
|
||||||
helpMenu = new javax.swing.JMenu();
|
initHelpMenu();
|
||||||
pluginMenu = new javax.swing.JMenu();
|
}
|
||||||
setupMenu = new javax.swing.JMenuItem();
|
|
||||||
|
|
||||||
fileMenu.setText("File");
|
private void initFileMenu() {
|
||||||
exitMenu.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F4, java.awt.event.InputEvent.ALT_MASK));
|
fileMenu = new JMenu("File");
|
||||||
exitMenu.setText("Exit");
|
exitMenu = new JMenuItem("Exit");
|
||||||
|
|
||||||
|
exitMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK));
|
||||||
exitMenu.addActionListener(this::exitMenuActionPerformed);
|
exitMenu.addActionListener(this::exitMenuActionPerformed);
|
||||||
fileMenu.add(exitMenu);
|
fileMenu.add(exitMenu);
|
||||||
this.add(fileMenu);
|
this.add(fileMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPluginMenu() {
|
||||||
|
pluginMenu = new JMenu("Plugin");
|
||||||
|
categories = new HashMap<>();
|
||||||
|
|
||||||
|
for (MenuCategory category : MenuCategory.values()) {
|
||||||
|
JMenu newCategoryMenu = new JMenu(category.toString());
|
||||||
|
|
||||||
|
pluginMenu.add(newCategoryMenu);
|
||||||
|
categories.put(category, newCategoryMenu);
|
||||||
|
}
|
||||||
|
|
||||||
pluginMenu.setText("Plugin");
|
|
||||||
this.add(pluginMenu);
|
this.add(pluginMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initHelpMenu() {
|
||||||
|
helpMenu = new JMenu("Help");
|
||||||
|
preferencesMenu = new JMenuItem("Preferences");
|
||||||
|
aboutMenu = new JMenuItem("About");
|
||||||
|
|
||||||
|
preferencesMenu.addActionListener(this::setupMenuActionPerformed);
|
||||||
|
helpMenu.add(preferencesMenu);
|
||||||
|
|
||||||
|
aboutMenu.addActionListener(this::aboutMenuActionPerformed);
|
||||||
|
helpMenu.add(aboutMenu);
|
||||||
|
|
||||||
helpMenu.setText("Help");
|
|
||||||
setupMenu.setText("Preferences");
|
|
||||||
setupMenu.addActionListener(this::setupMenuActionPerformed);
|
|
||||||
helpMenu.add(setupMenu);
|
|
||||||
this.add(helpMenu);
|
this.add(helpMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void exitMenuActionPerformed(ActionEvent evt) {
|
||||||
|
|
||||||
private void exitMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
|
||||||
mainWindow.exit();
|
mainWindow.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
private void setupMenuActionPerformed(ActionEvent evt) {
|
||||||
Login login = new Login();
|
Login login = new Login();
|
||||||
login.setVisible(true);
|
login.setVisible(true);
|
||||||
|
|
||||||
|
mainWindow.setUserName(login.getName());
|
||||||
|
mainWindow.getConnectionManager().setPort(login.getPort());
|
||||||
|
|
||||||
login.dispose();
|
login.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPlugin(JMenu menu){
|
private void aboutMenuActionPerformed(ActionEvent evt) {
|
||||||
this.pluginMenu.add(menu);
|
About about = new About();
|
||||||
|
about.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPluginMenu(MenuCategory category, JMenuItem menu){
|
||||||
|
System.out.println("addPluginMenu");
|
||||||
|
categories.get(category).add(menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,22 +2,36 @@ package pitiupi.GUI;
|
|||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.swing.BorderFactory;
|
import java.util.Map;
|
||||||
import javax.swing.DefaultListModel;
|
import java.util.function.Consumer;
|
||||||
import javax.swing.JList;
|
import javax.swing.*;
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
import pitiupi.models.Menus.MenuCategory;
|
||||||
import javax.swing.SwingUtilities;
|
import pitiupi.models.User;
|
||||||
import pitiupi.control.PeerInfo;
|
|
||||||
import pitiupi.control.PeerListener;
|
import pitiupi.control.PeerListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Painel da interface gráfica responsável por exibir os usuários ativos
|
||||||
|
* na rede.
|
||||||
|
*
|
||||||
|
* @author tony
|
||||||
|
*/
|
||||||
public class OnlineUsersPanel extends JPanel implements PeerListener {
|
public class OnlineUsersPanel extends JPanel implements PeerListener {
|
||||||
|
|
||||||
private final DefaultListModel<String> listModel;
|
private final DefaultListModel<User> listModel;
|
||||||
private final JList<String> userList;
|
private final JList<User> userList;
|
||||||
|
JPopupMenu menu;
|
||||||
|
private Map<MenuCategory, JMenu> categories;
|
||||||
|
|
||||||
public OnlineUsersPanel() {
|
public OnlineUsersPanel() {
|
||||||
|
menu = new JPopupMenu();
|
||||||
|
categories = new HashMap<>();
|
||||||
|
initMenu();
|
||||||
|
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
setBorder(BorderFactory.createTitledBorder("Online"));
|
setBorder(BorderFactory.createTitledBorder("Online"));
|
||||||
setPreferredSize(new Dimension(160, 0));
|
setPreferredSize(new Dimension(160, 0));
|
||||||
@@ -25,14 +39,61 @@ public class OnlineUsersPanel extends JPanel implements PeerListener {
|
|||||||
listModel = new DefaultListModel<>();
|
listModel = new DefaultListModel<>();
|
||||||
userList = new JList<>(listModel);
|
userList = new JList<>(listModel);
|
||||||
add(new JScrollPane(userList), BorderLayout.CENTER);
|
add(new JScrollPane(userList), BorderLayout.CENTER);
|
||||||
|
|
||||||
|
setupUserListActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initMenu() {
|
||||||
|
for (MenuCategory category : MenuCategory.values()) {
|
||||||
|
JMenu newCategory = new JMenu(category.toString());
|
||||||
|
|
||||||
|
menu.add(newCategory);
|
||||||
|
categories.put(category, newCategory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupUserListActions() {
|
||||||
|
userList.addMouseListener(new MouseAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
int index = userList.locationToIndex(e.getPoint());
|
||||||
|
|
||||||
|
if (index == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
userList.setSelectedIndex(index);
|
||||||
|
menu.show(userList, e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initUnicastMenu(JMenuItem menuItem, Consumer<User> action) {
|
||||||
|
menuItem.addActionListener(e -> {
|
||||||
|
User user = userList.getSelectedValue();
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
action.accept(user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addUnicastMenu(MenuCategory category, JMenuItem menuItem) {
|
||||||
|
categories.get(category).add(menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atualiza a lista de usuários exibida no painel.
|
||||||
|
*
|
||||||
|
* @param peers lista atualizada de usuários ativos na rede.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onPeersChanged(List<PeerInfo> peers) {
|
public void onPeersChanged(List<User> peers) {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
listModel.clear();
|
listModel.clear();
|
||||||
for (PeerInfo peer : peers) {
|
for (User peer : peers) {
|
||||||
listModel.addElement(peer.toString());
|
listModel.addElement(peer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import java.util.logging.Logger;
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
|
* @author tony
|
||||||
*/
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param args the command line arguments
|
* @param args the command line arguments
|
||||||
*/
|
*/
|
||||||
@@ -40,14 +40,15 @@ 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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package pitiupi.control;
|
||||||
|
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.control.plugins.PluginManager;
|
||||||
|
import pitiupi.models.Messages.HeartbeatMessage;
|
||||||
|
import pitiupi.models.Messages.Message;
|
||||||
|
import pitiupi.net.tcp.SocketTCP;
|
||||||
|
import pitiupi.net.tcp.TcpConnection;
|
||||||
|
import pitiupi.net.udp.SocketUDP;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
public class ConnectionManager {
|
||||||
|
private final String pitiupiIdentifier;
|
||||||
|
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
|
private final HeartbeatManager heartbeatManager;
|
||||||
|
|
||||||
|
private SocketTCP socketTCP;
|
||||||
|
private SocketUDP socketUDP;
|
||||||
|
private String username;
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
public ConnectionManager(MainWindow main, String username, PluginManager pluginManager, int port) {
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
|
||||||
|
this.username = username;
|
||||||
|
this.port = port;
|
||||||
|
|
||||||
|
pitiupiIdentifier = "Pitiupi.Alice.2026";
|
||||||
|
|
||||||
|
reconnect();
|
||||||
|
|
||||||
|
heartbeatManager = new HeartbeatManager(this);
|
||||||
|
heartbeatManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TcpConnection connectPluginTcp(InetAddress destinationAdress, String senderId, String receiverId) {
|
||||||
|
return socketTCP.createPrivateConnection(destinationAdress, senderId, receiverId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sendTcp(Message message, InetAddress destinationAddress) {
|
||||||
|
return socketTCP.send(message, destinationAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sendUdpUnicast(Message message, InetAddress destinationAddress) {
|
||||||
|
try {
|
||||||
|
socketUDP.sendUnicast(message, destinationAddress);
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean sendUdpMulticast(Message message) {
|
||||||
|
try {
|
||||||
|
socketUDP.sendMulticast(message);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Error sending UDP Multicast");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcastLocal(byte[] message) {
|
||||||
|
pluginManager.handleMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPeerListener(PeerListener peerListener) {
|
||||||
|
heartbeatManager.addPeerListener(peerListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receiveHeartbeat(HeartbeatMessage message, InetAddress destinationAddress) {
|
||||||
|
heartbeatManager.receiveHeartbeat(message, destinationAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdentifier(){
|
||||||
|
return this.pitiupiIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reconnect() {
|
||||||
|
if (this.socketUDP != null) {
|
||||||
|
this.socketUDP.close();
|
||||||
|
}
|
||||||
|
if (this.socketTCP != null) {
|
||||||
|
this.socketTCP.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socketUDP = new SocketUDP(this, pluginManager);
|
||||||
|
this.socketTCP = new SocketTCP(this, pluginManager);
|
||||||
|
|
||||||
|
this.socketUDP.start();
|
||||||
|
this.socketTCP.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserName() {
|
||||||
|
return this.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserName(String userName) {
|
||||||
|
this.username = userName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package pitiupi.control;
|
||||||
|
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.GUI.MenuBar;
|
||||||
|
import pitiupi.GUI.OnlineUsersPanel;
|
||||||
|
import pitiupi.models.Menus.*;
|
||||||
|
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
|
||||||
|
import pitiupi.models.Menus.MulticastMenus.MulticastMenu;
|
||||||
|
import pitiupi.models.Menus.MulticastMenus.MulticastSubMenu;
|
||||||
|
import pitiupi.models.Menus.UnicastMenus.UnicastActionMenu;
|
||||||
|
import pitiupi.models.Menus.UnicastMenus.UnicastMenu;
|
||||||
|
import pitiupi.models.Menus.UnicastMenus.UnicastSubMenu;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class GuiManager {
|
||||||
|
private MainWindow mainWindow;
|
||||||
|
private MenuBar menuBar;
|
||||||
|
private OnlineUsersPanel onlineUsersPanel;
|
||||||
|
|
||||||
|
private Logger logger;
|
||||||
|
|
||||||
|
public GuiManager(MainWindow mainWindow, MenuBar menuBar, OnlineUsersPanel onlineUsersPanel) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
this.menuBar = menuBar;
|
||||||
|
this.onlineUsersPanel = onlineUsersPanel;
|
||||||
|
this.logger = Logger.getLogger(GuiManager.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createMenu(MenuCategory category, Menu menu) {
|
||||||
|
System.out.println(
|
||||||
|
menu.getClass().getSuperclass().getSuperclass()
|
||||||
|
);
|
||||||
|
|
||||||
|
System.out.println(
|
||||||
|
MulticastMenu.class
|
||||||
|
);
|
||||||
|
|
||||||
|
System.out.println(
|
||||||
|
menu.getClass().getSuperclass().getSuperclass()
|
||||||
|
== MulticastMenu.class
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
JMenuItem newMenu = buildMenu(menu);
|
||||||
|
if (menu instanceof MulticastMenu) {
|
||||||
|
menuBar.addPluginMenu(category, newMenu);
|
||||||
|
}
|
||||||
|
if (menu instanceof UnicastMenu) {
|
||||||
|
onlineUsersPanel.addUnicastMenu(category, newMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
logger.log(Level.WARNING, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JMenuItem buildMenu(Menu menu) {
|
||||||
|
if (menu instanceof MulticastSubMenu multicastSubMenu) {
|
||||||
|
JMenu newMenu = new JMenu(menu.getText());
|
||||||
|
|
||||||
|
for (Menu child : multicastSubMenu.getMenus()) {
|
||||||
|
newMenu.add(buildMenu(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menu instanceof UnicastSubMenu unicastSubMenu) {
|
||||||
|
JMenu newMenu = new JMenu(menu.getText());
|
||||||
|
|
||||||
|
for (Menu child : unicastSubMenu.getMenus()) {
|
||||||
|
newMenu.add(buildMenu(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menu instanceof MulticastActionMenu multicastActionMenu) {
|
||||||
|
JMenuItem newMenu = new JMenuItem(menu.getText());
|
||||||
|
newMenu.addActionListener(e -> multicastActionMenu.onClick());
|
||||||
|
return newMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menu instanceof UnicastActionMenu unicastActionMenu) {
|
||||||
|
JMenuItem newMenu = new JMenuItem(menu.getText());
|
||||||
|
|
||||||
|
onlineUsersPanel.initUnicastMenu(newMenu, unicastActionMenu::onClick);
|
||||||
|
return newMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Invalid menu type");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package pitiupi.control;
|
package pitiupi.control;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -10,31 +9,49 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import pitiupi.GUI.MainWindow;
|
|
||||||
import pitiupi.net.HeartbeatMessage;
|
|
||||||
|
|
||||||
|
import pitiupi.models.User;
|
||||||
|
import pitiupi.models.Messages.HeartbeatMessage;
|
||||||
|
import java.util.UUID;
|
||||||
|
/**
|
||||||
|
* Gerencia o mecanismo de heartbeat da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Responsável por anunciar a presença da aplicação na rede, receber
|
||||||
|
* heartbeats de outros usuários, controlar o tempo desde a última atividade
|
||||||
|
* dos peers e notificar listeners quando a lista de usuários ativos muda.</p>
|
||||||
|
*
|
||||||
|
* @author tony
|
||||||
|
*/
|
||||||
public class HeartbeatManager {
|
public class HeartbeatManager {
|
||||||
|
|
||||||
private static final long HEARTBEAT_INTERVAL_MS = 5000;
|
private static final long HEARTBEAT_INTERVAL_MS = 5000;
|
||||||
private static final long PEER_TIMEOUT_MS = 15000; // ~3 batimentos perdidos
|
private static final long PEER_TIMEOUT_MS = 15000; // ~3 batimentos perdidos
|
||||||
|
private final ConnectionManager connectionManager;
|
||||||
private final MainWindow mainWindow;
|
private final Map<String, User> 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();
|
||||||
|
|
||||||
public HeartbeatManager(MainWindow mainWindow) {
|
/**
|
||||||
this.mainWindow = mainWindow;
|
* Cria um gerenciador de heartbeat associado à janela principal da aplicação.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public HeartbeatManager(ConnectionManager connectionManager) {
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
@@ -42,25 +59,33 @@ public class HeartbeatManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void announce() {
|
private void announce() {
|
||||||
try {
|
HeartbeatMessage msg = new HeartbeatMessage(connectionManager.getUserName(), instanceId);
|
||||||
HeartbeatMessage msg = new HeartbeatMessage(mainWindow.getUserName());
|
connectionManager.sendUdpMulticast(msg);
|
||||||
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
|
// Chamado pelo Socket quando um pacote é identificado como heartbeat
|
||||||
|
/**
|
||||||
|
* Processa uma mensagem de heartbeat recebida de outro usuário.
|
||||||
|
*
|
||||||
|
* <p>Atualiza o momento da última atividade do peer correspondente e notifica os listeners
|
||||||
|
* caso outra instância da aplicação seja identificada.</p>
|
||||||
|
*
|
||||||
|
* <p>Mensagens de heartbeat enviadas pela própria aplicação são ignoradas.</p>
|
||||||
|
*
|
||||||
|
* @param msg mensagem de heartbeat recebida.
|
||||||
|
* @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 (multicast normalmente ecoa pro remetente)
|
// 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()));
|
User peer = peers.computeIfAbsent(key, k -> new User(msg.getUserName(), from, msg.getTimestamp()));
|
||||||
peer.touch(System.currentTimeMillis());
|
peer.touch(System.currentTimeMillis());
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
@@ -77,22 +102,40 @@ public class HeartbeatManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void notifyListeners() {
|
private void notifyListeners() {
|
||||||
List<PeerInfo> snapshot = new ArrayList<>(peers.values());
|
List<User> snapshot = new ArrayList<>(peers.values());
|
||||||
for (PeerListener l : listeners) {
|
for (PeerListener l : listeners) {
|
||||||
l.onPeersChanged(snapshot);
|
l.onPeersChanged(snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adiciona um listener para receber atualizações sobre os peers conhecidos.
|
||||||
|
*
|
||||||
|
* <p>O listener recebe imediatamente o estado atual dos peers ao ser
|
||||||
|
* registrado.</p>
|
||||||
|
*
|
||||||
|
* @param l listener que será notificado sobre alterações.
|
||||||
|
*/
|
||||||
public void addPeerListener(PeerListener l) {
|
public void addPeerListener(PeerListener l) {
|
||||||
listeners.add(l);
|
listeners.add(l);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PeerInfo> getPeers() {
|
/**
|
||||||
|
* Retorna uma cópia da lista atual de peers conhecidos.
|
||||||
|
*
|
||||||
|
* @return lista contendo os peers atualmente conhecidos.
|
||||||
|
*/
|
||||||
|
public List<User> getPeers() {
|
||||||
return new ArrayList<>(peers.values());
|
return new ArrayList<>(peers.values());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loaded 100 of 130 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user