Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab3840e4a4 | ||
|
|
e25d3a425d | ||
|
|
a628490fcb | ||
|
|
395363e4bd | ||
|
|
12497594e4 | ||
|
|
6a597092fe | ||
|
|
ffd474e7ff | ||
|
|
4d39648ee7 | ||
|
|
095af14463 | ||
|
|
d0083e8069 | ||
|
|
dda324228f | ||
|
|
840183ee9f | ||
|
|
eb3ce132cd | ||
|
|
d1dc427185 |
No files matched your search
+3
-9
@@ -1,9 +1,3 @@
|
||||
/pitiupi/build/
|
||||
/pitiupi/dist/
|
||||
/pitiupi.chat/build/
|
||||
/pitiupi.chat/dist/
|
||||
/pitiupi.chat/nbproject/private/
|
||||
/pitiupi.screenCast/build/
|
||||
/pitiupi.screenCast/dist/
|
||||
|
||||
.idea/
|
||||
build/
|
||||
dist/
|
||||
*.class
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+9
@@ -0,0 +1,9 @@
|
||||
<component name="libraryTable">
|
||||
<library name="pitiupi">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/pitiupi.screenCast/dist/lib/pitiupi.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+9
@@ -0,0 +1,9 @@
|
||||
<component name="libraryTable">
|
||||
<library name="pitiupi.chat">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/pitiupi.chat/dist/pitiupi.chat.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+9
@@ -0,0 +1,9 @@
|
||||
<component name="libraryTable">
|
||||
<library name="pitiupi.screenCast">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/pitiupi.screenCast/dist/pitiupi.screenCast.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="plugins">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/pitiupi/plugins/pitiupi.screenCast.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/pitiupi/plugins/pitiupi.chat.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+3
@@ -0,0 +1,3 @@
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="openjdk-27" project-jdk-type="JavaSDK" />
|
||||
</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$/.idea/pitiupi.iml" filepath="$PROJECT_DIR$/.idea/pitiupi.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+9
@@ -0,0 +1,9 @@
|
||||
<?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$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/udpTest" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,10 +1,13 @@
|
||||
# pitiupi
|
||||
|
||||
Ferramenta p2p para práticas em rede
|
||||
Este repositório tem 4 projetos Java que são independentes:
|
||||
|
||||
Este repositório tem 3 projetos Java que são independentes:
|
||||
### Sistema:
|
||||
- A ferramenta pitiupi (P2P)
|
||||
|
||||
### Plugins:
|
||||
- Um plugin de Chat
|
||||
- Um plugin de Chat multiprotocolo (TCP/UDP)
|
||||
- Um plugin de ScreenCast
|
||||
|
||||
Cada um destes projetos deve ser compilado individualmente. Para isso, use a ferramenta ant no diretório do projeto.
|
||||
@@ -21,9 +24,26 @@ Para criar links simbólicos no Linux, use o comando:
|
||||
```
|
||||
cd pitiupi/plugins
|
||||
ln -s ../../pitiupi.chat/dist/*.jar .
|
||||
ln -s ../../pitiupi.mpChat/dist/*.jar .
|
||||
ln -s ../../pitiupi.screenCast/dist/*.jar .
|
||||
```
|
||||
Isso deve resolver!
|
||||
|
||||
Para criar um novo plugin:
|
||||
## Como um plugin conversa com o pitiupi
|
||||
|
||||
Um plugin nunca recebe acesso direto às classes internas do núcleo (ConnectionManager, GuiManager). Em vez disso, recebe uma facade, a classe Pitiupi, que expõe só o que um plugin precisa: enviar mensagem (multicast, unicast ou tcp), abrir conexão tcp privada, ler nome de usuário e porta, criar menu.
|
||||
|
||||
```java
|
||||
public abstract void createPlugin(Pitiupi pitiupi);
|
||||
```
|
||||
|
||||
## Para criar um novo plugin
|
||||
|
||||
1. Implemente `pitiupi.models.Plugin`, incluindo `createPlugin(Pitiupi pitiupi)`
|
||||
2. Registre a classe em `src/META-INF/services/pitiupi.models.Plugin`, com o nome completo da classe numa linha
|
||||
3. Compile com `ant jar`
|
||||
4. Linke o `.jar` em `pitiupi/plugins`
|
||||
|
||||
Use `pitiupi.chat` como referência — é o exemplo mais simples e completo.
|
||||
|
||||
Plugins que ainda não foram atualizados para a Pitiupi (como uma versão mais antiga do screenCast) não compilam contra o núcleo atual e precisam de migração antes de serem usados.
|
||||
@@ -1,6 +0,0 @@
|
||||
#Fri, 02 Oct 2026 20:11:21 -0300
|
||||
|
||||
|
||||
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.chat=
|
||||
|
||||
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi=
|
||||
@@ -1 +0,0 @@
|
||||
pitiupi.chat.plugin.ChatPlugin
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -47,8 +47,8 @@ javac.modulepath=
|
||||
javac.processormodulepath=
|
||||
javac.processorpath=\
|
||||
${javac.classpath}
|
||||
javac.source=25
|
||||
javac.target=25
|
||||
javac.source=17
|
||||
javac.target=17
|
||||
javac.test.classpath=\
|
||||
${javac.classpath}:\
|
||||
${build.classes.dir}
|
||||
|
||||
@@ -8,13 +8,13 @@ package pitiupi.chat.GUI;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import pitiupi.chat.net.ChatMessage;
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.plugin.Pitiupi;
|
||||
import pitiupi.models.Messages.Message;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tony
|
||||
* @author flavio
|
||||
*/
|
||||
public class ChatWindow extends JDialog {
|
||||
@@ -28,13 +28,12 @@ public class ChatWindow extends JDialog {
|
||||
private JSplitPane jSplitPane1;
|
||||
private JTextArea jTextArea1;
|
||||
|
||||
private final ConnectionManager connectionManager;
|
||||
|
||||
public ChatWindow(ConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
initComponents();
|
||||
}
|
||||
private final Pitiupi pitiupi;
|
||||
|
||||
public ChatWindow(Pitiupi pitiupi) {
|
||||
this.pitiupi = pitiupi;
|
||||
initComponents();
|
||||
}
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
@@ -93,13 +92,13 @@ public class ChatWindow extends JDialog {
|
||||
if (jTextArea1.getText().trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ChatMessage message = new ChatMessage(connectionManager.getUserName(), jTextArea1.getText());
|
||||
ChatMessage message = new ChatMessage(pitiupi.getUserName(), jTextArea1.getText());
|
||||
this.sendChatMessage(message);
|
||||
jTextArea1.setText("");
|
||||
}
|
||||
|
||||
public void sendChatMessage(Message message) {
|
||||
connectionManager.sendUdpMulticast(message);
|
||||
pitiupi.sendUdpMulticast(message);
|
||||
}
|
||||
|
||||
public void receiveChatMessage(ChatMessage message) {
|
||||
|
||||
@@ -8,29 +8,29 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import pitiupi.plugin.Pitiupi;
|
||||
import pitiupi.chat.GUI.ChatMenu;
|
||||
import pitiupi.chat.GUI.ChatWindow;
|
||||
import pitiupi.chat.net.ChatMessage;
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.control.GuiManager;
|
||||
import pitiupi.models.Menus.MenuCategory;
|
||||
import pitiupi.models.Messages.Message;
|
||||
import pitiupi.models.Plugin;
|
||||
|
||||
/**
|
||||
*
|
||||
*@author tony
|
||||
* @author flavio
|
||||
*/
|
||||
public class ChatPlugin extends Plugin {
|
||||
|
||||
private ChatWindow chatWindow;
|
||||
|
||||
@Override
|
||||
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||
this.chatWindow = new ChatWindow(connectionManager);
|
||||
guiManager.createMenu(MenuCategory.Social, new ChatMenu(chatWindow));
|
||||
@Override
|
||||
public void createPlugin(Pitiupi pitiupi) {
|
||||
this.chatWindow = new ChatWindow(pitiupi);
|
||||
pitiupi.createMenu(MenuCategory.Social, new ChatMenu(chatWindow));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Message>> getMessages() {
|
||||
return List.of(ChatMessage.class);
|
||||
|
||||
Generated
+85
@@ -0,0 +1,85 @@
|
||||
<?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="e2cdacc6-69cd-4190-bef1-27719e32140a" name="Changes" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/../.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/misc.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/vcs.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/GUI/PrivateChatWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/GUI/PrivateChatWindow.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/GUI/PublicChatWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/GUI/PublicChatWindow.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/plugin/MultiProtocolChat.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/plugin/MultiProtocolChat.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/misc.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/built-jar.properties" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/built-jar.properties" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastMenu.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastMenu.class" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastWindow.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/GUI/ScreenCastWindow.class" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/net/ScreenCastMessage.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/net/ScreenCastMessage.class" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/plugin/ScreenCastPlugin.class" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/build/classes/pitiupi/screenCast/plugin/ScreenCastPlugin.class" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi.screenCast.iml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi.screenCast.iml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastMenu.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastMenu.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/GUI/ScreenCastWindow.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/net/ScreenCastMessage.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/net/ScreenCastMessage.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/plugin/ScreenCastPlugin.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi.screenCast/src/pitiupi/screenCast/plugin/ScreenCastPlugin.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/control/plugins/PluginManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/control/plugins/PluginManager.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/SocketTCP.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/SocketTCP.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/TcpConnection.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/TcpConnection.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="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Class" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 6
|
||||
}</component>
|
||||
<component name="ProjectId" id="3K7MYfvmfmI7sWODbOakShpPyuR" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"git-widget-placeholder": "update/mpChat",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "C:/Users/extre/OneDrive/Documentos/Faculdade/2026_02/Monitoria/Pitiupi/pitiupi.mpChat",
|
||||
"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="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.97" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="e2cdacc6-69cd-4190-bef1-27719e32140a" name="Changes" comment="" />
|
||||
<created>1790904201925</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1790904201925</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,73 +0,0 @@
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
||||
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$/.idea/pitiupi.screenCast.iml" filepath="$PROJECT_DIR$/.idea/pitiupi.screenCast.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+16
-18
@@ -5,10 +5,9 @@
|
||||
</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" />
|
||||
<change beforePath="$PROJECT_DIR$/../.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/misc.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/../.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/vcs.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -34,7 +33,7 @@
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||
"git-widget-placeholder": "feature/separacao-gerenciadores",
|
||||
"git-widget-placeholder": "update/screenCast",
|
||||
"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",
|
||||
@@ -45,6 +44,17 @@
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RunManager">
|
||||
<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>
|
||||
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
|
||||
<module name="pitiupi.screenCast" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
@@ -79,23 +89,11 @@
|
||||
<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" />
|
||||
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.97" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Tue, 29 Sep 2026 11:04:58 -0300
|
||||
#Wed, 07 Oct 2026 15:36:37 -0300
|
||||
|
||||
|
||||
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\pitiupi\\pitiupi.screenCast=
|
||||
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.screenCast=
|
||||
|
||||
C\:\\Users\\Gustavo\\OneDrive\\Documentos\\Faculdade\\2026_2\\Monitoria\\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.
Generated
+5
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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.screenCast.iml" filepath="$PROJECT_DIR$/pitiupi.screenCast.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>
|
||||
@@ -6,6 +6,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
@@ -15,6 +16,5 @@
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -4,28 +4,29 @@
|
||||
*/
|
||||
package pitiupi.screenCast.GUI;
|
||||
|
||||
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.JMenu;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author flavio
|
||||
*/
|
||||
public class ScreenCastMenu extends JMenu {
|
||||
public class ScreenCastMenu extends MulticastActionMenu {
|
||||
|
||||
private final ScreenCastWindow screenCastWindow;
|
||||
private final javax.swing.JMenuItem menuItem;
|
||||
|
||||
public ScreenCastMenu(ScreenCastWindow screenCastWindow) {
|
||||
this.screenCastWindow = screenCastWindow;
|
||||
this.setText("Screen Cast");
|
||||
menuItem = new javax.swing.JMenuItem();
|
||||
menuItem.setText("ScreenCast Window");
|
||||
menuItem.addActionListener(this::chatMenuActionPerformed);
|
||||
this.add(menuItem);
|
||||
}
|
||||
|
||||
private void chatMenuActionPerformed(ActionEvent ae) {
|
||||
@Override
|
||||
public void onClick() {
|
||||
this.screenCastWindow.setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return "Screen Cast";
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ package pitiupi.screenCast.GUI;
|
||||
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.control.GuiManager;
|
||||
import pitiupi.plugin.Pitiupi;
|
||||
import pitiupi.screenCast.control.AlarmClock;
|
||||
import pitiupi.screenCast.control.ImageTransformation;
|
||||
import pitiupi.screenCast.control.ScreenShot;
|
||||
@@ -22,8 +23,6 @@ import java.util.logging.Logger;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import pitiupi.GUI.MainWindow;
|
||||
import pitiupi.net.Message;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -40,13 +39,11 @@ public class ScreenCastWindow extends javax.swing.JFrame {
|
||||
public final static int TIME = 1000;
|
||||
|
||||
private float compressRatio;
|
||||
private final ConnectionManager connectionManager;
|
||||
private final GuiManager guiManager;
|
||||
|
||||
public ScreenCastWindow(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.guiManager = guiManager;
|
||||
private final Pitiupi pitiupi;
|
||||
|
||||
public ScreenCastWindow(Pitiupi pitiupi) {
|
||||
this.pitiupi = pitiupi;
|
||||
this.compressRatio = 0.5f;
|
||||
this.initComponents();
|
||||
}
|
||||
@@ -96,9 +93,9 @@ public class ScreenCastWindow extends javax.swing.JFrame {
|
||||
BufferedImage image = ScreenShot.takeAShot();
|
||||
BufferedImage rescaled = ImageTransformation.resize(image, 1024, 768);
|
||||
byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio);
|
||||
ScreenCastMessage message = new ScreenCastMessage(this.connectionManager.getUserName(), byteImage);
|
||||
ScreenCastMessage message = new ScreenCastMessage(pitiupi.getUserName(), byteImage);
|
||||
|
||||
if (!connectionManager.sendUdpMulticast(message)) this.compressRatio -= 0.1f;
|
||||
if (!pitiupi.sendUdpMulticast(message)) this.compressRatio -= 0.1f;
|
||||
} catch (AWTException | IOException ex) {
|
||||
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
*/
|
||||
package pitiupi.screenCast.net;
|
||||
|
||||
import pitiupi.net.Message;
|
||||
|
||||
import pitiupi.models.Messages.Message;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
*/
|
||||
package pitiupi.screenCast.plugin;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import pitiupi.GUI.MainWindow;
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.control.GuiManager;
|
||||
import pitiupi.net.Message;
|
||||
import pitiupi.plugin.Plugin;
|
||||
import java.util.List;
|
||||
|
||||
import pitiupi.models.Menus.MenuCategory;
|
||||
import pitiupi.models.Messages.Message;
|
||||
import pitiupi.models.Plugin;
|
||||
import pitiupi.plugin.Pitiupi;
|
||||
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
||||
import pitiupi.screenCast.GUI.ScreenCastWindow;
|
||||
import pitiupi.screenCast.net.ScreenCastMessage;
|
||||
@@ -20,7 +18,7 @@ import pitiupi.screenCast.net.ScreenCastMessage;
|
||||
*
|
||||
* @author flavio
|
||||
*/
|
||||
public class ScreenCastPlugin implements Plugin {
|
||||
public class ScreenCastPlugin extends Plugin {
|
||||
|
||||
private ScreenCastWindow screenCastWindow;
|
||||
|
||||
@@ -45,28 +43,20 @@ public class ScreenCastPlugin implements Plugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||
this.screenCastWindow = new ScreenCastWindow(connectionManager, guiManager);
|
||||
guiManager.addMenu(new ScreenCastMenu(screenCastWindow));
|
||||
public void createPlugin(Pitiupi pitiupi) {
|
||||
this.screenCastWindow = new ScreenCastWindow(pitiupi);
|
||||
pitiupi.createMenu(MenuCategory.Streaming, new ScreenCastMenu(screenCastWindow));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveMessage(byte[] message) {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(message);
|
||||
ObjectInputStream ois;
|
||||
try {
|
||||
ois = new ObjectInputStream(bis);
|
||||
if (ois.readObject() instanceof ScreenCastMessage msg) {
|
||||
this.screenCastWindow.receiveScreenCastMessage(msg);
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException ex) {
|
||||
System.out.println(ex.toString());
|
||||
public List<Class<? extends Message>> getMessages() {
|
||||
return List.of(ScreenCastMessage.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveMessage(Message message) {
|
||||
if (message instanceof ScreenCastMessage msg) {
|
||||
this.screenCastWindow.receiveScreenCastMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getMessage() {
|
||||
return new ScreenCastMessage();
|
||||
}
|
||||
|
||||
}
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" 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.testSocket.iml" filepath="$PROJECT_DIR$/pitiupi.testSocket.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,2 +1,512 @@
|
||||
# Como criar plugins para o Share my Sheet
|
||||
# Como criar plugins para o Pitiupi
|
||||
|
||||
Aqui serão discutidos os métodos e classes que um desenvolvedor pode ou deve usar para criar um plugin para a plataforma Pitiupi.
|
||||
|
||||
Alguns deles estarão marcados com um <span style="color: red;">[REF]</span>, isso indica que eles estão sendo alterados para a versão 3.0 e deixarão de existir ou vão mudar consideravelmente.
|
||||
|
||||
## GUI
|
||||
|
||||
O Pitiupi é feito usando a biblioteca Swing do Java, por isso é altamente recomendado que os plugins também a utilizem na hora de criar sua interface gráfica, porém não é obrigatório.
|
||||
|
||||
## Como tornar o seu plugin visível para o Pitiupi
|
||||
|
||||
A busca por plugins é realizada em três etapas pelo pitiupi:
|
||||
|
||||
1. Encontra todos os jars em `plugins`.
|
||||
2. Verifica o `META-INF` dos jars para saber onde estão as implementações da classe `Plugin`.
|
||||
3. Carrega o plugin no sistema.
|
||||
|
||||
Em relação ao `META-INF`, ele deve conter um diretório `resources` e dentro dele um arquivo contendo o nome completamente qualificado da classe `Plugin` do pitiupi: `pitiupi.models.Plugin`, por fim dentro desse arquivo você deve colocar o nome completamente qualificado de todas as classes que implementam `Plugin` do seu projeto, um em cada linha.
|
||||
|
||||
```
|
||||
build/
|
||||
|---->META-INF/resources/pitiupi.models.Plugin
|
||||
|---->MeusPlugins/...
|
||||
|---->MinhasMensagens/...
|
||||
|---->Restante dos .class
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
src/MeusPlugins/MeuPrimeiroPlugin
|
||||
src/MeusPlugins/MeuSegundoPlugin
|
||||
...
|
||||
```
|
||||
|
||||
### Dicas
|
||||
|
||||
- Faça um makefile que automaticamente mova o META-INF para o build/ quando você compilar uma nova versão caso ele esteja sendo alterado com frequência.
|
||||
Ex:
|
||||
```
|
||||
JAR = ../pitiupi/dist/pitiupi.jar
|
||||
BUILD_DIR = build
|
||||
META-INF_DIR = src/META-INF
|
||||
DIST = dist/mpChat.jar
|
||||
|
||||
SOURCES = $(shell find -name "*.java")
|
||||
|
||||
all:
|
||||
mkdir -p "$(BUILD_DIR)"
|
||||
mkdir -p "$(dir $(DIST))"
|
||||
javac -cp "$(JAR)" -d "$(BUILD_DIR)" $(SOURCES)
|
||||
cp -r "$(META-INF_DIR)" "$(BUILD_DIR)"
|
||||
jar cf "$(DIST)" -C "$(BUILD_DIR)" .
|
||||
|
||||
clean:
|
||||
rm -rf "$(BUILD_DIR)"/*
|
||||
```
|
||||
|
||||
- Para a versão 3.0 será necessário fazer o mesmo processo para as implementações da classe `Message`, então se quiser deixar preparado para a nova versão adicione `pitiupi.models.Messages.Message` em `resources` e coloque nele todas suas implementações de `Message`, assim como você fez para os de `Plugin`.
|
||||
|
||||
## Pitiupi
|
||||
|
||||
A classe `Pitiupi` é a interface que o seu plugin vai receber quando for criado, ela fornece os métodos necessários para interagir com a interface gráfica e comunicação para outros peers.
|
||||
|
||||
### boolean sendUdpMulticast(Message message)
|
||||
|
||||
Envia uma mensagem multicast, retorna `boolean` indicando se teve sucesso.
|
||||
|
||||
### boolean sendUdpUnicast(Message message, InetAddress receiverAddress)
|
||||
|
||||
Envia uma mensagem unicast via udp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
|
||||
|
||||
### boolean sendTcp(Message message, InetAddress receiverAddress)
|
||||
|
||||
Envia uma mensagem unicast via tcp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
|
||||
|
||||
### <span style="color: red;">[REF]</span> TcpConnection connectTcp(InetAddress receiverAddress, String senderId, String receiverId)
|
||||
|
||||
Pede uma conexão tcp privada para o seu plugin com outro usuário, para utilizar essa funcionalidade é necessário implementar alguns métodos opcionais em Plugin. Maisinformações serão discutidas na seção TcpConnection.
|
||||
|
||||
### String getUserName()
|
||||
|
||||
Retorna o nome do usuário conectado
|
||||
|
||||
### int getPort()
|
||||
|
||||
Retorna a porta que o usuário está usando o Pitiupi
|
||||
|
||||
### void createMenu(MenuCategory category, Menu menu)
|
||||
|
||||
Adiciona um menu ao pitiupi, category define em qual das categoria de Menus ele será adicionado: Jogos, Social, Streaming.
|
||||
|
||||
Os `Menus` serão discutidos em uma seção posterior.
|
||||
|
||||
## Classe Plugin
|
||||
|
||||
Essa é a classe que o pitiupi vai procurar ao carregar o seu plugin, ela define um contrato de métodos que todo plugin deve possuir.
|
||||
|
||||
### getName()
|
||||
|
||||
Indica o nome público do plugin.
|
||||
|
||||
### getAuthor()
|
||||
|
||||
Indica o nome dos desenvolvedores do plugin.
|
||||
|
||||
### getVersion()
|
||||
|
||||
Indica a versão atual do plugin.
|
||||
|
||||
### getIdentifier()
|
||||
|
||||
Indica um id que o pitiupi vai utilizar para enontrar seu plugin, por isso é importante que ele seja bem especifico para evitar que dois plugins sejam confundidos durante a comunicação. Procure usar algo como: MeuPlugin.Autor.Números
|
||||
|
||||
### createPlugin(Pitiupi pitiupi)
|
||||
|
||||
Após o pitiupi identificar e carregar o seu plugin, ele vai chamar esse método para inicializá-lo, por isso pense nele como o construtor do seu plugin, é aqui que você inicializa atributos, adiciona os menus inciais, etc.
|
||||
|
||||
### getMessages()
|
||||
|
||||
Esse método indica ao pitiupi quais tipos de mensagem seu plugin quer receber, normalmente as que ele mesmo implementar.
|
||||
Nesse caso, é normal que ele faça:
|
||||
|
||||
```
|
||||
return List.of(
|
||||
SuaMensagem1.class,
|
||||
SuaMensagem2.class,
|
||||
...
|
||||
);
|
||||
```
|
||||
|
||||
Caso você queira que seu plugin receba todo tipo de mensagem, existe um método `receiveAllTypes()` que ele pode sobreescrever para receber todas mensagens
|
||||
que não sejam consideradas privadas.
|
||||
|
||||
### receiveMessage(Message message)
|
||||
|
||||
Esse é o método que o pitiupi vai chamar quando quiser te entregar uma mensagem pelo canal publico de mensagens. Note que ele entrega uma `Message`, então é necessário que
|
||||
seu plugin confira o tipo real ele mesmo. Como em:
|
||||
|
||||
```
|
||||
if (message instanceOf MinhaMensagem minhaMensagem) {...}
|
||||
```
|
||||
|
||||
O pitiupi garante que é uma mensagem de um tipo que o seu plugin diz aceitar.
|
||||
|
||||
### <span style="color: red;">[REF]</span> métodos opcionais
|
||||
|
||||
Os métodos a seguir possuem uma implementação padrão da própria classe que retornam false. Os três ultimos são necessários caso seu plugin deseje criar e receber conexões privadas.
|
||||
|
||||
### <span style="color: red;">[REF]</span> receiveAllTypes()
|
||||
|
||||
Indica ao Pitiupi que seu plugin deseja receber todas as mensagens publicas da plataforma, independente do tipo.
|
||||
|
||||
### <span style="color: red;">[REF]</span> receivePrivateConnection(TcpConnection connection)
|
||||
|
||||
Esse método é chamado quando o Pitiupi precisa entregar uma `TcpConnection` ao seu plugin, seja um que você pediu ou recebeu de um peer.
|
||||
|
||||
### <span style="color: red;">[REF]</span> receiveBytes(TcpConnection connection, byte[] bytes)
|
||||
|
||||
Esse método é chamado para entregar as mensagens recebidas por um `TcpConnection`, o tcpPitiupi no parametro indica qual a fonte da mensagem.
|
||||
|
||||
### <span style="color: red;">[REF]</span> onSocketClosed(TcpConnection connection)
|
||||
|
||||
Esse é método é chamado quando o socket de um `TcpConnection` pertencente ao plugin é fechado.
|
||||
|
||||
## Message
|
||||
|
||||
Essa é a classe abstrata que o pitiupi usa para comunicação nos canais publicos.
|
||||
|
||||
<span style="color: red;">[REF]</span> Ela possui um atributo isPublic que por padrão é true, mas é possível defini-lo no construtor como false caso a mensagem deva ser privada.
|
||||
|
||||
Essa classe implementa `Serializable` e possui um método `toByteArray` que transforma a mensagem em `byte[]`. Porém é importante notar que caso ela possua um objeto de outra classe como atributo aquela classe também deve implementar `Serializable`.
|
||||
|
||||
## <span style="color: red;">[REF]</span> TcpConnection
|
||||
|
||||
Representa uma conexão Tcp ativa para algum outro peer, mensagens enviadas por essa conexão não chegam ao canal publico e em vez disso vão direto para destinatário.
|
||||
|
||||
Caso fique inativa (Sem enviar ou receber mensagens) por 5 minutos a conexão é fechada automaticamente e o dono é notificado.
|
||||
|
||||
### <span style="color: red;">[REF]</span> void send(byte[] message)
|
||||
|
||||
Envia um conjunto de bytes[] para o destinatario.
|
||||
|
||||
Caso queira enviar uma `Message` chame o método toByteArray para transforma-la em bytes primeiro.
|
||||
|
||||
### <span style="color: red;">[REF]</span> boolean isClosed()
|
||||
|
||||
Indica se a conexão esta fechada
|
||||
|
||||
### <span style="color: red;">[REF]</span> InetAddress getAddress()
|
||||
|
||||
Retorna o endereço do peer que ela esta conectada.
|
||||
|
||||
### <span style="color: red;">[REF]</span> String getReceiver()
|
||||
|
||||
Retorna o identificador do plugin que ela esta conectada.
|
||||
|
||||
### <span style="color: red;">[REF]</span> getReceiver()
|
||||
|
||||
Retorna o identifador do plugin que pediu pela conexão.
|
||||
|
||||
## Menu
|
||||
|
||||
Essa é a classe que indica ao pitiupi que um menu deve ser adicionado a janela principal da aplicação.
|
||||
|
||||
Menus podem ser de dois tipos `UnicastMenu` e `MulticastMenu` com a principal diferença entre eles sendo onde eles são adicionados na janela principal. Outras mudanças serão discutidas quando for relevante nas classes a seguir.
|
||||
|
||||
Atualmente Menus não podem ser alterados uma vez que são adicionados, então é importante que eles sejam feitos com isso em mente.
|
||||
|
||||
Tem um método `String getText()` que retorna o que deve ser escrito no Menu`.
|
||||
|
||||
### SubMenu
|
||||
|
||||
Permite que o `Menu` expanda em uma nova seção de menus.
|
||||
|
||||
`UnicastSubMenu` o método `getMenu()` retorna `List<UnicastMenu>`
|
||||
|
||||
`MulticastSubMenu` o método `getMenu()` retorna `List<MulticastMenu>`
|
||||
|
||||
Exemplo:
|
||||
|
||||
```
|
||||
|
||||
ChatCasualMulticastMenu chatCasualMulticastMenu;
|
||||
ChatEstudoMutlticastMenu chatEstudoMutlticastMenu;
|
||||
|
||||
...
|
||||
|
||||
@Override
|
||||
public List<MulticastMenu> getMenus() {
|
||||
return List.of(chatCasualMulticastMenu, chatEstudoMutlticastMenu);
|
||||
}
|
||||
```
|
||||
|
||||
### ActionMenu
|
||||
|
||||
É um `Menu` que que realiza alguma ação quando selecionado pelo usuário.
|
||||
|
||||
`UnicastActionMenu` o método `onClick(User user)` é chamado quando selecionado pelo usuário, recebendo como parametro o peer escolhido pelo usuário.
|
||||
|
||||
`MulticastActionMenu` o método `onClick()` é chamado quando selecionado pelo usuário.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```
|
||||
|
||||
@Override
|
||||
public void onClick(User user) {
|
||||
callManager.openCallWindow(user);
|
||||
}
|
||||
```
|
||||
|
||||
## Exemplo de plugin no pitiupi:
|
||||
|
||||
### Plugin
|
||||
|
||||
Implementa o contrato de plugin.
|
||||
|
||||
Inicializa a janela principal e cria os menus.
|
||||
|
||||
Também define quais tipos de mensagem ele quer receber.
|
||||
|
||||
```
|
||||
public class ChatPlugin extends Plugin {
|
||||
|
||||
private ChatWindow chatWindow;
|
||||
|
||||
@Override
|
||||
public void createPlugin(Pitiupi pitiupi) {
|
||||
this.chatWindow = new ChatWindow(pitiupi);
|
||||
pitiupi.createMenu(MenuCategory.Social, new ChatMenu(chatWindow));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Message>> getMessages() {
|
||||
return List.of(ChatMessage.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void receiveMessage(Message message) {
|
||||
if (message instanceof ChatMessage msg) {
|
||||
this.chatWindow.receiveChatMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Chat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor() {
|
||||
return "Flávio Schiavoni";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "Pitiupi.Chat.Example.123456";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Message
|
||||
|
||||
Implementa uma mensagem customizada que o plugin vai utilizar para comunicação.
|
||||
|
||||
```
|
||||
public class ChatMessage extends Message {
|
||||
|
||||
private String text;
|
||||
private String name;
|
||||
|
||||
|
||||
public ChatMessage(){
|
||||
|
||||
}
|
||||
|
||||
public ChatMessage(String name, String text){
|
||||
this.name = name;
|
||||
this.text = text;
|
||||
}
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the text
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param text the text to set
|
||||
*/
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Menus
|
||||
|
||||
Implementa os dois menus de Multicast utilizados pela aplicação.
|
||||
|
||||
#### ActionMenu
|
||||
|
||||
Esse menu apenas abre a janela principal do plugin.
|
||||
|
||||
```
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Esse menu abre a seção de menus do plugin Chat.
|
||||
|
||||
#### SubMenu
|
||||
|
||||
```
|
||||
public class ChatMenu extends MulticastSubMenu {
|
||||
ChatMulticastMenu chatMulticastMenu;
|
||||
|
||||
public ChatMenu(ChatWindow chatWindow) {
|
||||
this.chatMulticastMenu = new ChatMulticastMenu(chatWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return "Chat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MulticastMenu> getMenus() {
|
||||
return List.of(chatMulticastMenu);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GUI
|
||||
|
||||
Essa é a janela principal do plugin de chat.
|
||||
|
||||
```
|
||||
public class ChatWindow extends JDialog {
|
||||
|
||||
private JButton jButton1;
|
||||
private JEditorPane jEditorPane1;
|
||||
private JPanel jPanel1;
|
||||
private JPanel jPanel2;
|
||||
private JScrollPane jScrollPane1;
|
||||
private JScrollPane jScrollPane2;
|
||||
private JSplitPane jSplitPane1;
|
||||
private JTextArea jTextArea1;
|
||||
|
||||
private final Pitiupi pitiupi;
|
||||
|
||||
public ChatWindow(Pitiupi pitiupi) {
|
||||
this.pitiupi = pitiupi;
|
||||
initComponents();
|
||||
}
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
private void initComponents() {
|
||||
|
||||
this.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
|
||||
this.setTitle("Chat Window");
|
||||
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));
|
||||
|
||||
jSplitPane1 = new JSplitPane();
|
||||
jScrollPane1 = new JScrollPane();
|
||||
jEditorPane1 = new JEditorPane();
|
||||
jPanel1 = new JPanel();
|
||||
jScrollPane2 = new JScrollPane();
|
||||
jTextArea1 = new JTextArea();
|
||||
jPanel2 = new JPanel();
|
||||
jButton1 = new JButton();
|
||||
|
||||
jSplitPane1.setDividerLocation(500);
|
||||
jSplitPane1.setOrientation(JSplitPane.VERTICAL_SPLIT);
|
||||
jEditorPane1.setContentType("text/html");
|
||||
jPanel1.setLayout(new java.awt.BorderLayout());
|
||||
jTextArea1.setColumns(20);
|
||||
jTextArea1.setRows(5);
|
||||
jTextArea1.addKeyListener(new java.awt.event.KeyAdapter() {
|
||||
@Override
|
||||
public void keyTyped(java.awt.event.KeyEvent evt) {
|
||||
if (evt.getKeyChar() == '\n') {
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
});
|
||||
jButton1.setText("Send");
|
||||
|
||||
jScrollPane1.setViewportView(jEditorPane1);
|
||||
jScrollPane2.setViewportView(jTextArea1);
|
||||
jSplitPane1.setTopComponent(jScrollPane1);
|
||||
|
||||
jPanel1.add(jScrollPane2, java.awt.BorderLayout.CENTER);
|
||||
jPanel2.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT));
|
||||
jButton1.addActionListener((java.awt.event.ActionEvent evt) -> {
|
||||
sendMessage();
|
||||
});
|
||||
jPanel2.add(jButton1);
|
||||
jPanel1.add(jPanel2, java.awt.BorderLayout.PAGE_END);
|
||||
jSplitPane1.setRightComponent(jPanel1);
|
||||
getContentPane().add(jSplitPane1, java.awt.BorderLayout.CENTER);
|
||||
pack();
|
||||
}
|
||||
|
||||
private void sendMessage() {
|
||||
if (jTextArea1.getText().trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ChatMessage message = new ChatMessage(pitiupi.getUserName(), jTextArea1.getText());
|
||||
this.sendChatMessage(message);
|
||||
jTextArea1.setText("");
|
||||
}
|
||||
|
||||
public void sendChatMessage(Message message) {
|
||||
pitiupi.sendUdpMulticast(message);
|
||||
}
|
||||
|
||||
public void receiveChatMessage(ChatMessage message) {
|
||||
SimpleDateFormat dt = new SimpleDateFormat("dd/MM/yyyyy hh:mm:ss");
|
||||
Date date = new Date();
|
||||
this.receiveChatMessage(dt.format(date) + " "
|
||||
+ "<b>" + message.getName() + "</b>: "
|
||||
+ message.getText() + "<br>");
|
||||
}
|
||||
|
||||
public void receiveChatMessage(String message) {
|
||||
jEditorPane1.setText(message + jEditorPane1.getText());
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -700,7 +700,7 @@ is divided into following sections:
|
||||
<sequential>
|
||||
<j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
|
||||
<customize>
|
||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/>
|
||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
|
||||
<customizeDebuggee/>
|
||||
</customize>
|
||||
</j2seproject3:junit>
|
||||
@@ -843,7 +843,7 @@ is divided into following sections:
|
||||
<attribute default="${debug.classpath}" name="classpath"/>
|
||||
<attribute default="" name="stopclassname"/>
|
||||
<sequential>
|
||||
<nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
|
||||
<nbjpdastart addressproperty="jpda.receiverAddress" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
|
||||
<modulepath>
|
||||
<path path="@{modulepath}"/>
|
||||
</modulepath>
|
||||
@@ -882,7 +882,7 @@ is divided into following sections:
|
||||
<sequential>
|
||||
<j2seproject1:java classname="@{classname}" classpath="@{classpath}" modulename="@{modulename}" modulepath="@{modulepath}">
|
||||
<customize>
|
||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/>
|
||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
|
||||
<customizeDebuggee/>
|
||||
</customize>
|
||||
</j2seproject1:java>
|
||||
@@ -1434,7 +1434,7 @@ is divided into following sections:
|
||||
</nbprofiledirect>
|
||||
<j2seproject3:junit excludes="${excludes}" includes="${includes}" testincludes="${profile.class}" testmethods="">
|
||||
<customize>
|
||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/>
|
||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
|
||||
<env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
|
||||
<jvmarg value="${profiler.info.jvmargs.agent}"/>
|
||||
<jvmarg line="${profiler.info.jvmargs}"/>
|
||||
|
||||
@@ -12,6 +12,7 @@ import pitiupi.control.plugins.PluginManager;
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* Janela principal da aplicação.
|
||||
@@ -33,9 +34,9 @@ public class MainWindow extends JFrame {
|
||||
private JLabel status;
|
||||
OnlineUsersPanel onlineUsersPanel;
|
||||
|
||||
private final ConnectionManager connectionManager;
|
||||
private final PluginManager pluginManager;
|
||||
private final GuiManager guiManager;
|
||||
private ConnectionManager connectionManager;
|
||||
private PluginManager pluginManager;
|
||||
private GuiManager guiManager;
|
||||
|
||||
/**
|
||||
* Cria a janela principal da aplicação.
|
||||
@@ -44,21 +45,34 @@ public class MainWindow extends JFrame {
|
||||
* @param port porta utilizada na comunicação multicast.
|
||||
*/
|
||||
public MainWindow(String userName, int port) {
|
||||
initMenu();
|
||||
try {
|
||||
initMenu();
|
||||
|
||||
pluginManager = new PluginManager();
|
||||
connectionManager = new ConnectionManager(this, userName, pluginManager, port);
|
||||
pluginManager = new PluginManager();
|
||||
connectionManager = new ConnectionManager(userName, pluginManager, port);
|
||||
|
||||
initUserPanel();
|
||||
initStatusPanel();
|
||||
initUserPanel();
|
||||
initStatusPanel();
|
||||
|
||||
guiManager = new GuiManager(this, menuBar, onlineUsersPanel);
|
||||
guiManager = new GuiManager(this, menuBar, onlineUsersPanel);
|
||||
|
||||
loadPlugins();
|
||||
loadPlugins();
|
||||
|
||||
this.pack();
|
||||
this.pack();
|
||||
|
||||
this.setSize(new java.awt.Dimension(1024, 768));
|
||||
this.setSize(new java.awt.Dimension(1024, 768));
|
||||
}
|
||||
catch (UnknownHostException e) {
|
||||
JOptionPane.showMessageDialog(
|
||||
null,
|
||||
"Ocorreu um erro abrindo a conexão TCP, verifique sua rede. \n" +
|
||||
"Se o problema persistir entre em contato com a equipe do Pitiupi.",
|
||||
"Erro na conexão TCP",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void initMenu() {
|
||||
|
||||
@@ -2,46 +2,58 @@ package pitiupi.control;
|
||||
|
||||
import pitiupi.GUI.MainWindow;
|
||||
import pitiupi.control.plugins.PluginManager;
|
||||
import pitiupi.models.ConnectionData;
|
||||
import pitiupi.models.Messages.HeartbeatMessage;
|
||||
import pitiupi.models.Messages.Message;
|
||||
import pitiupi.net.tcp.SocketTCP;
|
||||
import pitiupi.net.tcp.TcpConnection;
|
||||
import pitiupi.net.tcp.TcpClient;
|
||||
import pitiupi.net.tcp.TcpManager;
|
||||
import pitiupi.net.tcp.TcpServer;
|
||||
import pitiupi.net.udp.SocketUDP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ConnectionManager {
|
||||
private final String pitiupiIdentifier;
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
private final HeartbeatManager heartbeatManager;
|
||||
|
||||
private SocketTCP socketTCP;
|
||||
private TcpManager tcpManager;
|
||||
private SocketUDP socketUDP;
|
||||
private String username;
|
||||
private final InetAddress address;
|
||||
private int port;
|
||||
|
||||
public ConnectionManager(MainWindow main, String username, PluginManager pluginManager, int port) {
|
||||
public ConnectionManager(String username, PluginManager pluginManager, int port) throws UnknownHostException {
|
||||
this.pluginManager = pluginManager;
|
||||
|
||||
this.username = username;
|
||||
this.address = InetAddress.getLocalHost();
|
||||
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 TcpClient connectToTcpServer(ConnectionData serverData, String pluginId) {
|
||||
return tcpManager.getClient(serverData, pluginId, username);
|
||||
}
|
||||
|
||||
public TcpServer getTcpServer(String serverName, String pluginId) {
|
||||
return tcpManager.getServer(serverName, pluginId, username, address, port);
|
||||
}
|
||||
|
||||
public void closeTcpServer(TcpServer tcpServer) {
|
||||
tcpManager.closeServer(tcpServer);
|
||||
}
|
||||
|
||||
public boolean sendTcp(Message message, InetAddress destinationAddress) {
|
||||
return socketTCP.send(message, destinationAddress);
|
||||
return tcpManager.send(message, destinationAddress, port);
|
||||
}
|
||||
|
||||
public boolean sendUdpUnicast(Message message, InetAddress destinationAddress) {
|
||||
@@ -63,7 +75,7 @@ public class ConnectionManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void broadcastLocal(byte[] message) {
|
||||
public void receiveMessage(byte[] message) {
|
||||
pluginManager.handleMessage(message);
|
||||
}
|
||||
|
||||
@@ -84,29 +96,34 @@ public class ConnectionManager {
|
||||
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();
|
||||
}
|
||||
try {
|
||||
if (this.socketUDP != null) {
|
||||
this.socketUDP.close();
|
||||
}
|
||||
if (this.tcpManager != null) {
|
||||
this.tcpManager.closeManager();
|
||||
}
|
||||
|
||||
this.socketUDP = new SocketUDP(this, pluginManager);
|
||||
this.socketTCP = new SocketTCP(this, pluginManager);
|
||||
this.socketUDP = new SocketUDP(this, pluginManager);
|
||||
this.tcpManager = new TcpManager(port, this::receiveMessage);
|
||||
|
||||
this.socketUDP.start();
|
||||
this.socketTCP.start();
|
||||
this.socketUDP.start();
|
||||
this.tcpManager.start();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.getLogger(ConnectionManager.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public InetAddress getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.username = userName;
|
||||
}
|
||||
|
||||
@@ -30,18 +30,6 @@ public class GuiManager {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -55,7 +43,6 @@ public class GuiManager {
|
||||
logger.log(Level.WARNING, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private JMenuItem buildMenu(Menu menu) {
|
||||
if (menu instanceof MulticastSubMenu multicastSubMenu) {
|
||||
JMenu newMenu = new JMenu(menu.getText());
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package pitiupi.control.plugins;
|
||||
|
||||
import pitiupi.plugin.Pitiupi;
|
||||
import pitiupi.Main;
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.control.GuiManager;
|
||||
import pitiupi.models.Messages.Message;
|
||||
import pitiupi.net.tcp.TcpConnection;
|
||||
import pitiupi.models.Plugin;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -66,32 +66,10 @@ public class PluginManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleMessageFromConnection(TcpConnection connection, byte[] msg) {
|
||||
Plugin plugin = getPlugin(connection.getReceiver());
|
||||
if (plugin == null) return;
|
||||
|
||||
plugin.receiveBytes(connection, msg);
|
||||
}
|
||||
|
||||
public boolean handleConnection(TcpConnection connection, String receiverId) {
|
||||
Plugin plugin = getPlugin(receiverId);
|
||||
if (plugin == null) return false;
|
||||
|
||||
plugin.receivePrivateConnection(connection);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void handleDisconnection(TcpConnection connection, String receiverId) {
|
||||
Plugin plugin = getPlugin(receiverId);
|
||||
if (plugin == null) return;
|
||||
|
||||
plugin.onSocketClosed(connection);
|
||||
}
|
||||
|
||||
public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||
File pluginDir = new File("plugins");
|
||||
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
|
||||
Pitiupi pitiupi = new Pitiupi(connectionManager, guiManager);
|
||||
|
||||
if (jarFiles == null || jarFiles.length == 0) {
|
||||
System.out.println("No plugins found in the " + pluginDir.getAbsolutePath() + " directory.");
|
||||
@@ -118,7 +96,7 @@ public class PluginManager {
|
||||
System.out.println("Loading: " + plugin.getName());
|
||||
LoadedPlugin loadedPlugin = new LoadedPlugin(plugin, classLoader);
|
||||
|
||||
plugin.createPlugin(connectionManager, guiManager);
|
||||
plugin.createPlugin(pitiupi);
|
||||
loadedPlugins.add(loadedPlugin);
|
||||
loadedJars.add(file.getName());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package pitiupi.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ConnectionData implements Serializable {
|
||||
private final UUID uuid;
|
||||
private final int port;
|
||||
|
||||
private final String connectionName;
|
||||
|
||||
private final String ownerName;
|
||||
private final InetAddress ownerAddress;
|
||||
private final String pluginId;
|
||||
private final boolean isServer;
|
||||
|
||||
public ConnectionData(String connectionName, String ownerName, InetAddress ownerAddress, String pluginId, int port, boolean isServer) {
|
||||
uuid = UUID.randomUUID();
|
||||
|
||||
this.port = port;
|
||||
this.connectionName = connectionName;
|
||||
this.ownerName = ownerName;
|
||||
this.ownerAddress = ownerAddress;
|
||||
this.pluginId = pluginId;
|
||||
this.isServer = isServer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getConnectionName() {
|
||||
return connectionName;
|
||||
}
|
||||
|
||||
public String getOwnerName() {
|
||||
return ownerName;
|
||||
}
|
||||
public InetAddress getOwnerAddress() {
|
||||
return ownerAddress;
|
||||
}
|
||||
public String getPluginId() {
|
||||
return pluginId;
|
||||
}
|
||||
public boolean isServer() {
|
||||
return isServer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ConnectionData that = (ConnectionData) o;
|
||||
|
||||
if (port != that.port) return false;
|
||||
if (!uuid.equals(that.uuid)) return false;
|
||||
if (!connectionName.equals(that.connectionName)) return false;
|
||||
if (!Objects.equals(ownerName, that.ownerName)) return false;
|
||||
if (!Objects.equals(ownerAddress, that.ownerAddress)) return false;
|
||||
if (!Objects.equals(pluginId, that.pluginId)) return false;
|
||||
return isServer == that.isServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
uuid,
|
||||
port,
|
||||
connectionName,
|
||||
ownerName,
|
||||
ownerAddress,
|
||||
pluginId,
|
||||
isServer
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return connectionName + ": " + pluginId;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package pitiupi.models.Messages;
|
||||
|
||||
import pitiupi.net.tcp.TcpConnection;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class InfoMessage extends Message {
|
||||
private final InfoType type;
|
||||
private final String message;
|
||||
|
||||
public InfoMessage(String message, InfoType type) {
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public InfoType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public static void sendSystemInfo(TcpConnection connection, InfoType type, String description) {
|
||||
try {
|
||||
InfoMessage error = new InfoMessage(description, type);
|
||||
connection.send(error.toByteArray());
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(InfoMessage.class.getName()).log(Level.WARNING, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static InfoMessage parseSystemInfo(byte[] bytes) {
|
||||
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(bis)) {
|
||||
|
||||
Object object = ois.readObject();
|
||||
if (object instanceof InfoMessage) {
|
||||
return (InfoMessage) object;
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException ex) {
|
||||
Logger.getLogger(InfoMessage.class.getName()).log(Level.WARNING, null, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package pitiupi.models.Messages;
|
||||
|
||||
public enum InfoType {
|
||||
CONNECTION_REJECTED,
|
||||
RECEIVER_NOT_FOUND,
|
||||
MESSAGE_REJECTED,
|
||||
INTERNAL_ERROR,
|
||||
CONNECTION_CLOSED,
|
||||
}
|
||||
@@ -3,11 +3,8 @@
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Interface.java to edit this template
|
||||
*/
|
||||
package pitiupi.models;
|
||||
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.control.GuiManager;
|
||||
import pitiupi.plugin.Pitiupi;
|
||||
import pitiupi.models.Messages.Message;
|
||||
import pitiupi.net.tcp.TcpConnection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -30,10 +27,9 @@ public abstract class Plugin {
|
||||
*
|
||||
* <p>Este método é chamado pela aplicação após o carregamento do plugin.</p>
|
||||
*
|
||||
* @param connectionManager interface com o sistema de conexões da aplicação.
|
||||
* @param guiManager interface com a janela principal da aplicação
|
||||
* @param pitiupi interface com o sistema de conexões da aplicação.
|
||||
*/
|
||||
public abstract void createPlugin(ConnectionManager connectionManager, GuiManager guiManager);
|
||||
public abstract void createPlugin(Pitiupi pitiupi);
|
||||
|
||||
/**
|
||||
* Retorna uma mensagem utilizada pelo plugin.
|
||||
@@ -53,13 +49,5 @@ public abstract class Plugin {
|
||||
public abstract void receiveMessage(Message message);
|
||||
|
||||
public boolean receiveAllTypes() {return false;}
|
||||
public boolean receivePrivateConnection(TcpConnection connection) {
|
||||
return false;
|
||||
}
|
||||
public boolean receiveBytes(TcpConnection connection, byte[] bytes) {
|
||||
return false;
|
||||
}
|
||||
public boolean onSocketClosed(TcpConnection connection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.models.Messages.Message;
|
||||
import pitiupi.models.Messages.InfoMessage;
|
||||
import pitiupi.models.Messages.InfoType;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Fábrica responsável pela criação e configuração de conexões TCP.
|
||||
*
|
||||
* <p>Esta classe centraliza o estabelecimento de conexões de saída e a
|
||||
* configuração de conexões recebidas. Antes de uma conexão ser considerada
|
||||
* válida, é realizado um handshake entre os peers para identificar o remetente,
|
||||
* o destinatário e se a conexão é privada.</p>
|
||||
*
|
||||
* <p>Durante o handshake é utilizado um timeout menor, definido por
|
||||
* {@link #HANDSHAKE_TIMEOUT}. Após a validação bem-sucedida, a conexão passa
|
||||
* a utilizar o timeout padrão definido por {@link #STANDARD_TIMEOUT}.</p>
|
||||
*/
|
||||
public class ConnectionFactory {
|
||||
private static final int HANDSHAKE_TIMEOUT = 1000;
|
||||
private static final int STANDARD_TIMEOUT = 300_000;
|
||||
|
||||
/**
|
||||
* Cria uma conexão TCP de saída com o peer de destino e realiza o handshake
|
||||
* necessário para validá-la.
|
||||
*
|
||||
* <p>A conexão é configurada com os identificadores do remetente e do
|
||||
* destinatário e com o tipo indicado pelo parâmetro {@code isPrivate}.
|
||||
* Após o estabelecimento do socket, o método envia um
|
||||
* {@link HandshakeMessage} e aguarda uma resposta válida.</p>
|
||||
*
|
||||
* <p>Se o handshake for validado com sucesso, o timeout da conexão é
|
||||
* alterado de {@link #HANDSHAKE_TIMEOUT} para {@link #STANDARD_TIMEOUT}
|
||||
* e a conexão é retornada. Caso contrário, {@code null} é retornado.</p>
|
||||
*
|
||||
* @param address endereço IP do peer de destino.
|
||||
* @param port porta TCP na qual o peer de destino está aguardando conexões.
|
||||
* @param sender identificador do remetente da conexão.
|
||||
* @param receiver identificador do destinatário da conexão.
|
||||
* @param isPrivate {@code true} se a conexão deve ser privada;
|
||||
* {@code false} se deve ser pública.
|
||||
* @return a conexão TCP estabelecida e validada ou {@code null} caso
|
||||
* não seja possível estabelecer ou validar a conexão.
|
||||
*/
|
||||
public static TcpConnection createConnection(InetAddress address, int port, String sender, String receiver, boolean isPrivate) {
|
||||
try {
|
||||
TcpConnection connection;
|
||||
|
||||
Socket socket = new Socket(address, port);
|
||||
connection = new TcpConnection(socket);
|
||||
connection.setSender(sender);
|
||||
connection.setReceiver(receiver);
|
||||
connection.setPrivate(isPrivate);
|
||||
|
||||
HandshakeMessage handshake = new HandshakeMessage(isPrivate, sender, receiver);
|
||||
|
||||
connection.send(handshake.toByteArray());
|
||||
HandshakeMessage handshakeResponse = validateConnection(connection);
|
||||
|
||||
if (handshakeResponse == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
connection.setTimeout(STANDARD_TIMEOUT);
|
||||
|
||||
return connection;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura uma conexão TCP recebida e valida o handshake enviado pelo
|
||||
* peer remoto.
|
||||
*
|
||||
* <p>O handshake recebido é utilizado para obter e configurar os
|
||||
* identificadores do remetente e do destinatário, bem como o tipo da
|
||||
* conexão. Após a validação bem-sucedida, o timeout da conexão é alterado
|
||||
* para {@link #STANDARD_TIMEOUT}.</p>
|
||||
*
|
||||
* <p>O {@link Socket} recebido deve representar uma conexão TCP já aceita
|
||||
* pelo servidor.</p>
|
||||
*
|
||||
* @param socket socket TCP correspondente à conexão recebida.
|
||||
* @return a conexão TCP configurada e validada ou {@code null} caso
|
||||
* o handshake seja inválido ou não possa ser processado.
|
||||
*/
|
||||
public static TcpConnection receiveConnection(Socket socket) {
|
||||
try {
|
||||
TcpConnection connection;
|
||||
|
||||
connection = new TcpConnection(socket);
|
||||
|
||||
HandshakeMessage handshake = validateConnection(connection);
|
||||
|
||||
if (handshake == null) return null;
|
||||
|
||||
connection.send(handshake.createResponse().toByteArray());
|
||||
|
||||
connection.setSender(handshake.getSender());
|
||||
connection.setReceiver(handshake.getReceiver());
|
||||
connection.setPrivate(handshake.isPrivate());
|
||||
|
||||
connection.setTimeout(STANDARD_TIMEOUT);
|
||||
|
||||
return connection;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aguarda e valida a mensagem de handshake de uma conexão TCP.
|
||||
*
|
||||
* <p>Durante a validação, a conexão utiliza {@link #HANDSHAKE_TIMEOUT}
|
||||
* como limite para o recebimento do handshake. A mensagem recebida deve
|
||||
* ser uma instância de {@link Message} e, especificamente, de
|
||||
* {@link HandshakeMessage}. Mensagens de sistema ou mensagens de outros
|
||||
* tipos são rejeitadas.</p>
|
||||
*
|
||||
* <p>Quando o handshake é recebido, seus campos de remetente e destinatário
|
||||
* são validados por {@link #validateHandshake(HandshakeMessage, TcpConnection)}.
|
||||
* Caso a validação seja bem-sucedida, o próprio handshake é retornado.</p>
|
||||
*
|
||||
* <p>Em caso de timeout, mensagem inválida ou erro durante o processamento,
|
||||
* uma mensagem de sistema apropriada pode ser enviada ao peer e
|
||||
* {@code null} é retornado.</p>
|
||||
*
|
||||
* @param connection conexão TCP da qual o handshake será recebido.
|
||||
* @return o {@link HandshakeMessage} validado ou {@code null} caso o
|
||||
* handshake não seja recebido ou seja inválido.
|
||||
*/
|
||||
private static HandshakeMessage validateConnection(TcpConnection connection) {
|
||||
try {
|
||||
connection.setTimeout(HANDSHAKE_TIMEOUT);
|
||||
byte[] bytes = connection.receive();
|
||||
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(bis);
|
||||
|
||||
Object object = ois.readObject();
|
||||
|
||||
if (object instanceof Message message) {
|
||||
InfoMessage sysInfo = InfoMessage.parseSystemInfo(bytes);
|
||||
if (sysInfo != null) return null;
|
||||
|
||||
|
||||
if (message instanceof HandshakeMessage handshake) {
|
||||
return (validateHandshake(handshake, connection)) ? handshake : null;
|
||||
}
|
||||
}
|
||||
|
||||
InfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
InfoType.CONNECTION_REJECTED,
|
||||
"The message was not from an allowed type."
|
||||
);
|
||||
}
|
||||
catch (SocketTimeoutException ex) {
|
||||
InfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
InfoType.CONNECTION_REJECTED,
|
||||
"No handshake was received within the allowed time."
|
||||
);
|
||||
}
|
||||
catch (ClassNotFoundException | IOException ex) {
|
||||
Logger.getLogger(ConnectionFactory.class.getName()).log(Level.SEVERE, null, ex);
|
||||
|
||||
InfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
InfoType.INTERNAL_ERROR,
|
||||
"The server could not process the handshake."
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida os identificadores presentes em uma mensagem de handshake.
|
||||
*
|
||||
* <p>O remetente e o destinatário devem estar presentes e não podem ser
|
||||
* vazios ou conter apenas espaços em branco. Quando um dos identificadores
|
||||
* é inválido, uma mensagem de rejeição é enviada ao peer e a validação
|
||||
* falha.</p>
|
||||
*
|
||||
* @param handshake mensagem de handshake a ser validada.
|
||||
* @param connection conexão associada ao handshake, utilizada para enviar
|
||||
* uma mensagem de rejeição quando necessário.
|
||||
* @return {@code true} se os identificadores do remetente e do destinatário
|
||||
* forem válidos; {@code false} caso contrário.
|
||||
*/
|
||||
private static boolean validateHandshake(HandshakeMessage handshake, TcpConnection connection) {
|
||||
String sender = handshake.getSender();
|
||||
String receiver = handshake.getReceiver();
|
||||
|
||||
if (sender == null || sender.isBlank()) {
|
||||
InfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
InfoType.CONNECTION_REJECTED,
|
||||
"Invalid Sender: " + sender + "."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (receiver == null || receiver.isBlank()) {
|
||||
InfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
InfoType.CONNECTION_REJECTED,
|
||||
"Invalid Receiver: " + receiver + "."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.models.Messages.Message;
|
||||
|
||||
class HandshakeMessage extends Message {
|
||||
private final boolean isPrivate;
|
||||
private final String sender;
|
||||
private final String receiver;
|
||||
|
||||
|
||||
public HandshakeMessage(boolean isPrivate, String sender, String receiver) {
|
||||
this.isPrivate = isPrivate;
|
||||
this.sender = sender;
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
public boolean isPrivate() {
|
||||
return isPrivate;
|
||||
}
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
public String getReceiver() {
|
||||
return receiver;
|
||||
}
|
||||
|
||||
public HandshakeMessage createResponse() {
|
||||
return new HandshakeMessage(isPrivate, receiver, sender);
|
||||
}
|
||||
}
|
||||
@@ -1,456 +0,0 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.control.plugins.PluginManager;
|
||||
import pitiupi.models.Messages.Message;
|
||||
import pitiupi.models.Messages.InfoMessage;
|
||||
import pitiupi.models.Messages.InfoType;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Gerencia a comunicação TCP da aplicação.
|
||||
*
|
||||
* <p>Mantém conexões persistentes com os peers e reutiliza essas conexões
|
||||
* para o envio de mensagens. As mensagens recebidas são encaminhadas aos
|
||||
* plugins, que são responsáveis por interpretá-las.</p>
|
||||
*
|
||||
* <p>Novas conexões são aceitas pelo servidor e cada conexão possui uma
|
||||
* tarefa própria para receber mensagens, permitindo que múltiplas conexões
|
||||
* sejam mantidas simultaneamente.</p>
|
||||
*
|
||||
* <p>As conexões são associadas aos endereços dos peers e podem ser
|
||||
* encerradas quando um peer deixa de estar ativo.</p>
|
||||
*
|
||||
* @author Gustavo
|
||||
*/
|
||||
public class SocketTCP extends Thread {
|
||||
private final Logger logger;
|
||||
|
||||
private final ExecutorService connectionExecutor = Executors.newCachedThreadPool();
|
||||
|
||||
private final ConnectionManager connectionManager;
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
private final TcpConnectionManager tcpConnectionManager;
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
private InetAddress address;
|
||||
|
||||
private volatile boolean running = true;
|
||||
|
||||
/**
|
||||
* Cria o servidor TCP da aplicação.
|
||||
*
|
||||
* <p>O servidor é criado na porta definida pela aplicação. As conexões
|
||||
* com os peers são estabelecidas sob demanda durante o envio ou aceitas
|
||||
* quando iniciadas por outros peers.</p>
|
||||
*
|
||||
* @param connectionManager interface de conexão da aplicação, usada para obter a porta,
|
||||
* o identificador local e os plugins disponíveis.
|
||||
*/
|
||||
public SocketTCP(ConnectionManager connectionManager, PluginManager pluginManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.pluginManager = pluginManager;
|
||||
|
||||
logger = Logger.getLogger(getClass().getName());
|
||||
tcpConnectionManager = new TcpConnectionManager();
|
||||
|
||||
try {
|
||||
serverSocket = new ServerSocket(connectionManager.getPort());
|
||||
address = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress());
|
||||
} catch (IOException ex) {
|
||||
System.out.println("There is no socket connection. Sorry.");
|
||||
System.out.println(ex.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma mensagem por meio de uma conexão TCP pública com o peer de destino.
|
||||
*
|
||||
* <p>A conexão pública existente para o endereço informado é reutilizada.
|
||||
* Caso não exista uma conexão válida, uma nova conexão é estabelecida antes
|
||||
* do envio. Falhas durante o estabelecimento ou envio são registradas na
|
||||
* saída padrão e não são propagadas ao chamador.</p>
|
||||
*
|
||||
* @param msg mensagem que será enviada.
|
||||
* @param destinationAddress endereço IP do peer que receberá a mensagem.
|
||||
*/
|
||||
public boolean send(Message msg, InetAddress destinationAddress) {
|
||||
try {
|
||||
TcpConnection connection = getPublicConnection(
|
||||
destinationAddress,
|
||||
connectionManager.getIdentifier(),
|
||||
connectionManager.getIdentifier()
|
||||
);
|
||||
|
||||
if (connection == null) {
|
||||
System.out.println("Could not establish TCP connection.");
|
||||
return false;
|
||||
}
|
||||
connection.send(msg.toByteArray());
|
||||
} catch (Exception ex) {
|
||||
System.out.println("Could not send TCP message.");
|
||||
System.out.println(ex.toString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estabelece uma conexão TCP privada com um plugin específico do peer de destino.
|
||||
*
|
||||
* <p>A conexão é criada como privada e passa a ser gerenciada pelo
|
||||
* {@link TcpConnectionManager}. O método também inicia a tarefa responsável
|
||||
* por receber os dados dessa conexão.</p>
|
||||
*
|
||||
* @param destinationAddress endereço IP do peer de destino.
|
||||
* @param senderId identificador do remetente da conexão.
|
||||
* @param receiverId identificador do plugin que receberá os dados.
|
||||
* @return a conexão privada estabelecida ou {@code null} quando a criação
|
||||
* da conexão falhar.
|
||||
* @throws IllegalArgumentException se o endereço de destino for o endereço
|
||||
* local desta instância.
|
||||
*/
|
||||
public TcpConnection createPrivateConnection(InetAddress destinationAddress, String senderId, String receiverId) {
|
||||
return establishOutgoingConnection(destinationAddress, senderId, receiverId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtém uma conexão TCP pública existente com o peer de destino ou estabelece
|
||||
* uma nova caso não exista uma conexão válida.
|
||||
*
|
||||
* <p>As conexões públicas são gerenciadas pelo {@link TcpConnectionManager}.
|
||||
* O método primeiro verifica se já existe uma conexão pública registrada
|
||||
* para o endereço de destino e, caso ela exista e não esteja fechada,
|
||||
* reutiliza-a. Caso contrário, uma nova conexão pública é estabelecida,
|
||||
* registrada no {@link TcpConnectionManager} e disponibilizada para uso.</p>
|
||||
*
|
||||
* <p>O acesso ao método é sincronizado para evitar que múltiplas threads
|
||||
* estabeleçam simultaneamente conexões públicas para o mesmo peer.</p>
|
||||
*
|
||||
* @param destinationAddress endereço IP do peer de destino.
|
||||
* @param senderId identificador usado como remetente no estabelecimento da conexão.
|
||||
* @param receiverId identificador usado como destinatário no estabelecimento da conexão.
|
||||
* @return uma conexão pública válida ou {@code null} caso não seja possível
|
||||
* estabelecê-la.
|
||||
*/
|
||||
private synchronized TcpConnection getPublicConnection(InetAddress destinationAddress, String senderId, String receiverId) {
|
||||
TcpConnection connection;
|
||||
|
||||
connection = tcpConnectionManager.getPublicConnection(destinationAddress);
|
||||
if (connection != null && !connection.isClosed()) return connection;
|
||||
|
||||
connection = establishOutgoingConnection(destinationAddress, senderId, receiverId, false);
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aguarda e aceita novas conexões TCP.
|
||||
*
|
||||
* <p>Cada socket aceito é encaminhado para processamento assíncrono,
|
||||
* interpretado como uma {@link TcpConnection}, registrado pelo
|
||||
* {@link TcpConnectionManager} e associado ao receptor adequado.</p>
|
||||
*/
|
||||
private void receive() {
|
||||
while (running) {
|
||||
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
acceptIncomingConnection(socket);
|
||||
|
||||
} catch (IOException ex) {
|
||||
if (!running) {
|
||||
break;
|
||||
}
|
||||
|
||||
System.out.println("Error accepting TCP connection.");
|
||||
System.out.println(ex.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Estabelece uma conexão TCP de saída com outro peer.
|
||||
*
|
||||
* <p>O método impede conexões com o próprio endereço local, cria a conexão
|
||||
* por meio da {@link ConnectionFactory}, registra-a no gerenciador de
|
||||
* conexões e inicia uma tarefa para receber dados continuamente.</p>
|
||||
*
|
||||
* @param destinationAddress endereço IP do peer de destino.
|
||||
* @param sender identificador do remetente.
|
||||
* @param receiver identificador do destinatário.
|
||||
* @param isPrivate {@code true} para uma conexão destinada a um plugin
|
||||
* específico; {@code false} para uma conexão pública.
|
||||
* @return a conexão estabelecida ou {@code null} se a
|
||||
* {@link ConnectionFactory} não conseguir criá-la.
|
||||
* @throws IllegalArgumentException se o destino for o endereço desta máquina.
|
||||
*/
|
||||
private TcpConnection establishOutgoingConnection(InetAddress destinationAddress, String sender, String receiver, boolean isPrivate) {
|
||||
if (address.equals(destinationAddress)) {
|
||||
System.out.println("Failed to get socket.");
|
||||
throw new IllegalArgumentException("Connections to the local host are forbidden.");
|
||||
}
|
||||
|
||||
TcpConnection connection = ConnectionFactory.createConnection(destinationAddress, connectionManager.getPort(), sender, receiver, isPrivate);
|
||||
if (connection == null) return null;
|
||||
|
||||
saveConnection(connection);
|
||||
|
||||
connectionExecutor.submit(() -> {
|
||||
startReceiver(connection);
|
||||
});
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agenda o processamento de uma conexão TCP recebida.
|
||||
*
|
||||
* <p>A conexão é interpretada pela {@link ConnectionFactory}, registrada
|
||||
* no gerenciador e associada a uma tarefa de recepção. O processamento é
|
||||
* executado de forma assíncrona pelo executor compartilhado.</p>
|
||||
*
|
||||
* @param socket socket aceito pelo {@link ServerSocket}.
|
||||
*/
|
||||
private void acceptIncomingConnection(Socket socket) {
|
||||
connectionExecutor.submit(() -> {
|
||||
TcpConnection connection = ConnectionFactory.receiveConnection(socket);
|
||||
if (connection == null) return;
|
||||
|
||||
saveConnection(connection);
|
||||
startReceiver(connection);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registra uma conexão no gerenciador de acordo com seu tipo.
|
||||
*
|
||||
* <p>Conexões privadas são armazenadas na coleção de conexões privadas;
|
||||
* as demais são armazenadas na coleção de conexões públicas.</p>
|
||||
*
|
||||
* @param connection conexão que será registrada.
|
||||
*/
|
||||
private void saveConnection(TcpConnection connection) {
|
||||
if (connection.isPrivate()) {
|
||||
tcpConnectionManager.addPrivateConnection(connection);
|
||||
}
|
||||
else {
|
||||
tcpConnectionManager.addPublicConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Seleciona e inicia o receptor adequado para uma conexão.
|
||||
*
|
||||
* <p>Conexões públicas são tratadas pelo receptor de mensagens públicas.
|
||||
* Conexões privadas destinadas ao próprio sistema são tratadas
|
||||
* separadamente; as demais são encaminhadas ao plugin identificado como
|
||||
* destinatário. Quando solicitado, o plugin também é notificado sobre o
|
||||
* estabelecimento da conexão.</p>
|
||||
*
|
||||
* @param connection conexão cujo fluxo de entrada será processado.
|
||||
*/
|
||||
private void startReceiver(TcpConnection connection) {
|
||||
|
||||
if (!connection.isPrivate()) {
|
||||
publicConnectionReceiver(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connection.getReceiver().equals(connectionManager.getIdentifier())) {
|
||||
systemConnectionReceiver(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pluginManager.handleConnection(connection, connection.getReceiver()))
|
||||
privateConnectionReceiver(connection);
|
||||
else {
|
||||
InfoMessage.sendSystemInfo(
|
||||
connection,
|
||||
InfoType.RECEIVER_NOT_FOUND,
|
||||
"This peer does not have the plugin: " + connection.getReceiver()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Processa continuamente as mensagens recebidas em uma conexão pública.
|
||||
*
|
||||
* <p>As mensagens são desserializadas e, quando são instâncias de
|
||||
* {@link Message}, encaminhadas a todos os plugins da aplicação. Mensagens
|
||||
* de controle do sistema podem encerrar a conexão ou registrar uma
|
||||
* condição de erro.</p>
|
||||
*
|
||||
* <p>O método também trata timeouts e erros de I/O, removendo a conexão
|
||||
* do {@link TcpConnectionManager} quando ela deixa de ser utilizável.</p>
|
||||
*
|
||||
* @param connection conexão pública da qual as mensagens serão recebidas.
|
||||
*/
|
||||
private void publicConnectionReceiver(TcpConnection connection) {
|
||||
while (running) {
|
||||
try {
|
||||
byte[] bytes = connection.receive();
|
||||
|
||||
InfoMessage sysMessage = InfoMessage.parseSystemInfo(bytes);
|
||||
|
||||
if (sysMessage == null) {
|
||||
pluginManager.handleMessage(bytes);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (sysMessage.getType()) {
|
||||
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
|
||||
tcpConnectionManager.closeConnection(connection);
|
||||
return;
|
||||
}
|
||||
case MESSAGE_REJECTED -> {
|
||||
logger.log(Level.SEVERE, "Somehow sent something that was not an Message");
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
catch (SocketTimeoutException ex) {
|
||||
if (connection.hasTimedOut()) {
|
||||
tcpConnectionManager.closeConnection(connection, "Connection timeout");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
tcpConnectionManager.closeConnection(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processa os dados recebidos em uma conexão privada e os encaminha
|
||||
* ao plugin destinatário.
|
||||
*
|
||||
* <p>Dados que não representam mensagens de controle são entregues
|
||||
* diretamente ao {@link PrivateConnectionPlugin}. Mensagens de controle
|
||||
* relacionadas ao encerramento ou a erros internos provocam a notificação
|
||||
* do plugin e o fechamento da conexão.</p>
|
||||
*
|
||||
* @param connection conexão privada da qual os dados serão recebidos.
|
||||
*/
|
||||
private void privateConnectionReceiver(TcpConnection connection) {
|
||||
while (running) {
|
||||
try {
|
||||
byte[] message = connection.receive();
|
||||
InfoMessage sysMessage = InfoMessage.parseSystemInfo(message);
|
||||
|
||||
if (sysMessage == null) {
|
||||
pluginManager.handleMessageFromConnection(connection, message);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (sysMessage.getType()) {
|
||||
case CONNECTION_CLOSED, INTERNAL_ERROR -> {
|
||||
pluginManager.handleDisconnection(connection, connection.getReceiver());
|
||||
tcpConnectionManager.closeConnection(connection);
|
||||
}
|
||||
default -> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SocketTimeoutException ex) {
|
||||
if (connection.hasTimedOut()) {
|
||||
tcpConnectionManager.closeConnection(connection, "Connection timeout");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
tcpConnectionManager.closeConnection(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processa mensagens recebidas em uma conexão destinada ao próprio sistema.
|
||||
*
|
||||
* <p>Atualmente, mensagens destinadas ao sistema são apenas verificadas
|
||||
* quanto ao seu formato de controle. Não há processamento adicional de
|
||||
* mensagens de sistema neste método.</p>
|
||||
*
|
||||
* @param connection conexão destinada ao identificador da própria aplicação.
|
||||
*/
|
||||
private void systemConnectionReceiver(TcpConnection connection) {
|
||||
while (running) {
|
||||
try {
|
||||
byte[] message = connection.receive();
|
||||
try (ByteArrayInputStream bis = new ByteArrayInputStream(message);
|
||||
ObjectInputStream ois = new ObjectInputStream(bis)) {
|
||||
|
||||
InfoMessage sysMessage = InfoMessage.parseSystemInfo(message);
|
||||
if (sysMessage != null) continue;
|
||||
|
||||
//TODO: Ainda não existem mensagens trocadas entre o sistema.
|
||||
}
|
||||
|
||||
}
|
||||
catch (SocketTimeoutException ex) {
|
||||
if (connection.hasTimedOut()) return;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
tcpConnectionManager.closeConnection(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encerra uma conexão TCP e registra o motivo informado.
|
||||
*
|
||||
* <p>Quando nenhuma descrição é fornecida, é utilizado um motivo padrão.
|
||||
* O fechamento efetivo da conexão é delegado ao {@link TcpConnectionManager}.</p>
|
||||
*
|
||||
* @param connection conexão que será encerrada.
|
||||
* @param description motivo do encerramento; pode ser {@code null}.
|
||||
*/
|
||||
public void disconnect(TcpConnection connection, String description) {
|
||||
tcpConnectionManager.closeConnection(
|
||||
connection,
|
||||
Objects.requireNonNullElse(description, "No reason provided")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
receive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encerra o servidor TCP e libera os recursos associados.
|
||||
*
|
||||
* <p>Interrompe o laço de aceitação de conexões, fecha o
|
||||
* {@link ServerSocket}, encerra todas as conexões gerenciadas e interrompe
|
||||
* as tarefas do executor.</p>
|
||||
*
|
||||
* <p>Após a chamada, esta instância não deve ser reutilizada para aceitar
|
||||
* novas conexões.</p>
|
||||
*/
|
||||
public void close() {
|
||||
running = false;
|
||||
|
||||
if (serverSocket != null) {
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
tcpConnectionManager.closeAllConnections("The peer must have closed the application or changed ports");
|
||||
|
||||
connectionExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.models.ConnectionData;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
public final class TcpClient extends TcpPitiupi {
|
||||
private final ConnectionData clientData;
|
||||
private final ConnectionData serverData;
|
||||
|
||||
TcpClient(
|
||||
ConnectionData clientData,
|
||||
ConnectionData serverData,
|
||||
Socket socket,
|
||||
DataInputStream input,
|
||||
DataOutputStream output
|
||||
) throws IllegalArgumentException {
|
||||
|
||||
super(socket, input, output);
|
||||
|
||||
if (clientData == null) throw new IllegalArgumentException("clientData is null");
|
||||
if (serverData == null) throw new IllegalArgumentException("ServerData is null");
|
||||
|
||||
this.clientData = clientData;
|
||||
this.serverData = serverData;
|
||||
}
|
||||
|
||||
public ConnectionData getServerData() {
|
||||
return serverData;
|
||||
}
|
||||
|
||||
public ConnectionData getClientData() {
|
||||
return clientData;
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
|
||||
/**
|
||||
* Representa uma conexão TCP ativa com um peer remoto.
|
||||
*
|
||||
* <p>A conexão permite o envio e recebimento de mensagens, além de manter
|
||||
* informações sobre o endereço remoto e metadados da conexão, como remetente,
|
||||
* destinatário e se a conexão é privada.</p>
|
||||
*
|
||||
* @author Gustavo
|
||||
*/
|
||||
public class TcpConnection {
|
||||
|
||||
private final Socket socket;
|
||||
private final DataInputStream input;
|
||||
private final DataOutputStream output;
|
||||
private final InetAddress address;
|
||||
private String sender;
|
||||
private String receiver;
|
||||
private boolean isPrivate;
|
||||
private volatile long lastActivity;
|
||||
private long timeout;
|
||||
|
||||
/**
|
||||
* Cria uma conexão TCP a partir de um socket existente.
|
||||
*
|
||||
* @param socket socket utilizado para a comunicação
|
||||
* @throws IOException se não for possível obter os fluxos de entrada ou saída
|
||||
* do socket
|
||||
*/
|
||||
TcpConnection(Socket socket) throws IOException {
|
||||
this.socket = socket;
|
||||
this.input = new DataInputStream(socket.getInputStream());
|
||||
this.output = new DataOutputStream(socket.getOutputStream());
|
||||
this.address = socket.getInetAddress();
|
||||
this.lastActivity = System.currentTimeMillis();
|
||||
this.timeout = 300_000; // 5 minutos
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma mensagem pela conexão.
|
||||
*
|
||||
* <p>A mensagem é enviada precedida pelo seu tamanho, permitindo que o
|
||||
* receptor determine quantos bytes devem ser lidos.</p>
|
||||
*
|
||||
* @param message mensagem a ser enviada
|
||||
* @throws IOException se ocorrer um erro durante o envio
|
||||
*/
|
||||
public synchronized void send(byte[] message) throws IOException {
|
||||
output.writeInt(message.length);
|
||||
output.write(message);
|
||||
output.flush();
|
||||
updateLastActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recebe uma mensagem da conexão.
|
||||
*
|
||||
* <p>Primeiro lê o tamanho da mensagem e, em seguida, lê aquantidade
|
||||
* correspondente de bytes.</p>
|
||||
*
|
||||
* @return mensagem recebida
|
||||
* @throws IOException se o tamanho da mensagem for inválido ou se a conexão
|
||||
* for encerrada antes que a mensagem seja completamente recebida
|
||||
*/
|
||||
byte[] receive() throws IOException {
|
||||
int length = input.readInt();
|
||||
|
||||
if (length < 0) throw new IOException("Invalid message length.");
|
||||
|
||||
byte[] message = input.readNBytes(length);
|
||||
|
||||
if (message.length != length) {
|
||||
throw new IOException("Connection closed before receiving the complete message.");
|
||||
}
|
||||
|
||||
updateLastActivity();
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se o socket da conexão está fechado.
|
||||
*
|
||||
* @return {@code true} se o socket estiver fechado;
|
||||
* {@code false} caso contrário
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return socket.isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fecha o socket da conexão.
|
||||
*
|
||||
* @throws IOException se ocorrer um erro ao fechar o socket
|
||||
*/
|
||||
void close() throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna o endereço do peer remoto conectado.
|
||||
*
|
||||
* @return endereço IP do peer remoto
|
||||
*/
|
||||
public InetAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Atualiza o instante da última atividade da conexão.
|
||||
*/
|
||||
private void updateLastActivity() {
|
||||
lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define o tempo limite de leitura do socket.
|
||||
*
|
||||
* @param timeout tempo limite, em milissegundos
|
||||
* @throws SocketException se ocorrer um erro ao definir o tempo limite
|
||||
*/
|
||||
void setTimeout(int timeout) throws SocketException {
|
||||
socket.setSoTimeout(timeout);
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se a conexão ficou inativa pelo tempo limite configurado.
|
||||
*
|
||||
* <p>A verificação é baseada no instante da última atividade registrada
|
||||
* durante o envio ou recebimento de uma mensagem.</p>
|
||||
*
|
||||
* @return {@code true} se a conexão estiver inativa há pelo menos 5 minutos;
|
||||
* {@code false} caso contrário
|
||||
*/
|
||||
boolean hasTimedOut() {
|
||||
return System.currentTimeMillis() - lastActivity >= timeout;
|
||||
}
|
||||
|
||||
public String getReceiver() {
|
||||
return receiver;
|
||||
}
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public boolean isPrivate() {
|
||||
return isPrivate;
|
||||
}
|
||||
|
||||
void setSender(String sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
void setReceiver(String receiver) {
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
void setPrivate(boolean aPrivate) {
|
||||
isPrivate = aPrivate;
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.models.Messages.InfoType;
|
||||
import pitiupi.models.User;
|
||||
import pitiupi.control.PeerListener;
|
||||
import pitiupi.models.Messages.InfoMessage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Gerencia as conexões TCP estabelecidas com os peers da aplicação.
|
||||
*
|
||||
* <p>As conexões públicas são armazenadas individualmente por endereço IP,
|
||||
* enquanto as conexões privadas são agrupadas por endereço, permitindo que
|
||||
* um mesmo peer mantenha múltiplas conexões privadas simultaneamente.</p>
|
||||
*
|
||||
* <p>Além de manter o registro das conexões ativas, esta classe é responsável
|
||||
* por encerrá-las quando necessário e por remover do gerenciamento as
|
||||
* conexões que forem fechadas. Também acompanha as alterações na lista de
|
||||
* peers ativos para encerrar conexões associadas a peers que deixaram de
|
||||
* estar disponíveis.</p>
|
||||
*/
|
||||
class TcpConnectionManager implements PeerListener {
|
||||
private final Logger logger;
|
||||
private final Map<InetAddress, TcpConnection> publicConnections;
|
||||
private final Map<InetAddress, List<TcpConnection>> privateConnections;
|
||||
|
||||
/**
|
||||
* Cria um gerenciador de conexões TCP vazio.
|
||||
*/
|
||||
public TcpConnectionManager() {
|
||||
publicConnections = new ConcurrentHashMap<>();
|
||||
privateConnections = new ConcurrentHashMap<>();
|
||||
|
||||
logger = Logger.getLogger(getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtém a conexão pública registrada para um determinado peer.
|
||||
*
|
||||
* @param address endereço IP do peer.
|
||||
* @return a conexão pública registrada para o endereço ou {@code null}
|
||||
* caso não exista uma conexão pública registrada.
|
||||
*/
|
||||
|
||||
public TcpConnection getPublicConnection(InetAddress address) {
|
||||
return publicConnections.get(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registra uma conexão privada associada a um peer.
|
||||
*
|
||||
* <p>Um mesmo peer pode possuir múltiplas conexões privadas. Por isso,
|
||||
* as conexões são armazenadas em uma lista associada ao endereço do
|
||||
* peer.</p>
|
||||
*
|
||||
* @param connection conexão privada a ser registrada.
|
||||
*/
|
||||
public void addPrivateConnection(TcpConnection connection) {
|
||||
List<TcpConnection> connections =
|
||||
privateConnections.computeIfAbsent(connection.getAddress(), e -> new ArrayList<>());
|
||||
|
||||
synchronized (connections) {
|
||||
connections.add(connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registra uma conexão pública associada a um peer.
|
||||
*
|
||||
* <p>Cada peer pode possuir apenas uma conexão pública registrada.
|
||||
* Quando já existe uma conexão pública para o endereço da nova conexão,
|
||||
* a conexão anterior é encerrada e substituída pela nova.</p>
|
||||
*
|
||||
* @param connection nova conexão pública a ser registrada.
|
||||
*/
|
||||
|
||||
public void addPublicConnection(TcpConnection connection) {
|
||||
TcpConnection oldConnection = publicConnections.put(connection.getAddress(), connection);
|
||||
if (oldConnection != null && oldConnection != connection) {
|
||||
closeConnection(oldConnection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma mensagem de encerramento ao peer e fecha a conexão.
|
||||
*
|
||||
* <p>A mensagem enviada informa o motivo do encerramento antes que a
|
||||
* conexão seja removida do gerenciamento e fechada.</p>
|
||||
*
|
||||
* @param connection conexão TCP a ser encerrada.
|
||||
* @param reason motivo do encerramento que será informado ao peer.
|
||||
*/
|
||||
public void closeConnection(TcpConnection connection, String reason) {
|
||||
InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason);
|
||||
closeConnection(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove uma conexão do gerenciamento e encerra seu socket.
|
||||
*
|
||||
* <p>Para conexões privadas, a conexão é removida da lista associada ao
|
||||
* endereço do peer. Caso essa seja a última conexão privada do endereço,
|
||||
* a lista também é removida do mapa.</p>
|
||||
*
|
||||
* <p>Para conexões públicas, a conexão é removida do mapa somente se
|
||||
* ainda for a conexão registrada para seu endereço.</p>
|
||||
*
|
||||
* <p>Se a conexão já estiver fechada ou for {@code null}, nenhuma ação
|
||||
* adicional de fechamento é realizada.</p>
|
||||
*
|
||||
* @param connection conexão TCP a ser removida e encerrada.
|
||||
*/
|
||||
|
||||
public void closeConnection(TcpConnection connection) {
|
||||
if (connection == null) return;
|
||||
if (connection.isPrivate()) {
|
||||
List<TcpConnection> connections = privateConnections.get(connection.getAddress());
|
||||
|
||||
if (connections != null) {
|
||||
synchronized (connections) {
|
||||
connections.remove(connection);
|
||||
|
||||
if (connections.isEmpty()) privateConnections.remove(connection.getAddress(), connections);
|
||||
}
|
||||
}
|
||||
}
|
||||
else publicConnections.remove(connection.getAddress(), connection);
|
||||
|
||||
try {
|
||||
if (!connection.isClosed()) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, "Error closing TCP connection", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma mensagem de encerramento para todas as conexões de um peer
|
||||
* e, em seguida, fecha essas conexões.
|
||||
*
|
||||
* @param address endereço IP do peer cujas conexões serão encerradas.
|
||||
* @param reason motivo do encerramento informado ao peer.
|
||||
*/
|
||||
public void closeByAdress(InetAddress address, String reason) {
|
||||
List<TcpConnection> connections = privateConnections.get(address);
|
||||
for (TcpConnection connection : connections) {
|
||||
InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason);
|
||||
}
|
||||
closeByAdress(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fecha e remove todas as conexões associadas a um determinado endereço.
|
||||
*
|
||||
* <p>Tanto a conexão pública quanto todas as conexões privadas associadas
|
||||
* ao endereço são encerradas e removidas do gerenciamento.</p>
|
||||
*
|
||||
* @param address endereço IP do peer cujas conexões serão encerradas.
|
||||
*/
|
||||
public void closeByAdress(InetAddress address) {
|
||||
TcpConnection publicConnection = publicConnections.remove(address);
|
||||
|
||||
if (publicConnection != null) {
|
||||
try {
|
||||
publicConnection.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, "Error closing TCP connection", ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
List<TcpConnection> connections = privateConnections.remove(address);
|
||||
|
||||
if (connections != null) {
|
||||
synchronized (connections) {
|
||||
for (TcpConnection connection : connections) {
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, "Error closing TCP connection", ex);
|
||||
}
|
||||
}
|
||||
connections.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma mensagem de encerramento para todas as conexões privadas
|
||||
* gerenciadas e, em seguida, fecha todas elas.
|
||||
*
|
||||
* @param reason motivo do encerramento informado aos peers.
|
||||
*/
|
||||
public void closeAllConnections(String reason) {
|
||||
for (List<TcpConnection> connections : privateConnections.values()) {
|
||||
for (TcpConnection connection : connections) {
|
||||
InfoMessage.sendSystemInfo(connection, InfoType.CONNECTION_CLOSED, reason);
|
||||
}
|
||||
}
|
||||
closeAllConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fecha e remove todas as conexões públicas e privadas gerenciadas.
|
||||
*
|
||||
* <p>Após a execução, não devem permanecer conexões registradas nos
|
||||
* mapas de conexões deste gerenciador.</p>
|
||||
*/
|
||||
|
||||
public void closeAllConnections() {
|
||||
for (TcpConnection connection : publicConnections.values()) {
|
||||
closeConnection(connection);
|
||||
}
|
||||
|
||||
synchronized (privateConnections) {
|
||||
for (List<TcpConnection> connectionsList : privateConnections.values()) {
|
||||
for (TcpConnection connection : connectionsList) {
|
||||
closeConnection(connection);
|
||||
}
|
||||
privateConnections.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atualiza as conexões de acordo com os peers atualmente ativos.
|
||||
*
|
||||
* <p>Os endereços presentes na lista de peers são utilizados como
|
||||
* referência para determinar quais conexões ainda devem permanecer
|
||||
* ativas. Conexões públicas e privadas associadas a endereços que não
|
||||
* estão mais presentes na lista de peers são encerradas e removidas.</p>
|
||||
*
|
||||
* @param peers lista atualizada de peers ativos.
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onPeersChanged(List<User> peers) {
|
||||
Set<InetAddress> activePeers = peers.stream().map(User::getAddress).collect(Collectors.toSet());
|
||||
|
||||
for (InetAddress address : publicConnections.keySet()) {
|
||||
if (!activePeers.contains(address)) {
|
||||
closeByAdress(address);
|
||||
}
|
||||
}
|
||||
for (InetAddress address : privateConnections.keySet()) {
|
||||
if (!activePeers.contains(address)) {
|
||||
closeByAdress(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.models.ConnectionData;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
final class TcpFactory {
|
||||
private static final int HANDSHAKE_TIMEOUT = 3000;
|
||||
private static final int DISABLE_TIMEOUT = 0;
|
||||
|
||||
private final TcpManager manager;
|
||||
|
||||
TcpFactory(TcpManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
TcpPitiupi createPitiupiConnection(InetAddress receiverAddress, int port) {
|
||||
Socket socket = new Socket();
|
||||
try {
|
||||
socket = new Socket(receiverAddress, port);
|
||||
socket.connect(new InetSocketAddress(receiverAddress, port), HANDSHAKE_TIMEOUT);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
|
||||
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
|
||||
|
||||
sendHandshake(dataOutputStream);
|
||||
socket.setSoTimeout(HANDSHAKE_TIMEOUT);
|
||||
TcpHandshakeResponse response = receiveHandshakeResponse(dataInputStream);
|
||||
socket.setSoTimeout(DISABLE_TIMEOUT);
|
||||
|
||||
if (response.getSucess()) return new TcpPitiupi(socket, dataInputStream, dataOutputStream);
|
||||
}
|
||||
catch (IOException | ClassNotFoundException e) {
|
||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
|
||||
tryClose(socket);
|
||||
return null;
|
||||
}
|
||||
|
||||
TcpClient createClient(ConnectionData serverData, String pluginIdentifier, String username, InetAddress receiverAddress, int port) {
|
||||
Socket socket = null;
|
||||
try {
|
||||
socket = new Socket(receiverAddress, port);
|
||||
socket.connect(new InetSocketAddress(receiverAddress, port), HANDSHAKE_TIMEOUT);
|
||||
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
|
||||
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
|
||||
|
||||
ConnectionData clientData = new ConnectionData(
|
||||
"Connection to " + serverData.getConnectionName() + " (" + serverData.getOwnerAddress() + ")",
|
||||
username,
|
||||
receiverAddress,
|
||||
pluginIdentifier,
|
||||
port,
|
||||
false);
|
||||
|
||||
sendHandshake(dataOutputStream, serverData, clientData);
|
||||
socket.setSoTimeout(HANDSHAKE_TIMEOUT);
|
||||
TcpHandshakeResponse response = receiveHandshakeResponse(dataInputStream);
|
||||
socket.setSoTimeout(DISABLE_TIMEOUT);
|
||||
|
||||
if (response.getSucess()) return new TcpClient(clientData, serverData, socket, dataInputStream, dataOutputStream);
|
||||
}
|
||||
catch (IOException | ClassNotFoundException e) {
|
||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
|
||||
if (socket != null) tryClose(socket);
|
||||
return null;
|
||||
}
|
||||
|
||||
TcpServer createServer(String serverName, String pluginIdentifier, String username, InetAddress receiverAddress, int port) {
|
||||
ConnectionData data = new ConnectionData(
|
||||
serverName,
|
||||
username,
|
||||
receiverAddress,
|
||||
pluginIdentifier,
|
||||
port,
|
||||
true);
|
||||
|
||||
return new TcpServer(data);
|
||||
}
|
||||
|
||||
TcpPitiupi createFromReceiver(Socket socket) throws IOException {
|
||||
DataOutputStream dataOutputStream = null;
|
||||
DataInputStream dataInputStream = null;
|
||||
|
||||
try {
|
||||
dataOutputStream = new DataOutputStream(socket.getOutputStream());
|
||||
dataInputStream = new DataInputStream(socket.getInputStream());
|
||||
|
||||
socket.setSoTimeout(HANDSHAKE_TIMEOUT);
|
||||
TcpHandshake tcpHandshake = receiveHandshake(dataInputStream);
|
||||
socket.setSoTimeout(DISABLE_TIMEOUT);
|
||||
|
||||
if (tcpHandshake.isPrivate()) {
|
||||
ConnectionData clientData = tcpHandshake.getClientData();
|
||||
ConnectionData serverData = tcpHandshake.getServerData();
|
||||
|
||||
if (manager.findServer(serverData) == null) {
|
||||
sendHandshakeResponse(false, dataOutputStream);
|
||||
tryClose(socket);
|
||||
return null;
|
||||
}
|
||||
|
||||
sendHandshakeResponse(true, dataOutputStream);
|
||||
return new TcpClient(clientData, serverData, socket, dataInputStream, dataOutputStream);
|
||||
}
|
||||
else {
|
||||
sendHandshakeResponse(true, dataOutputStream);
|
||||
return new TcpPitiupi(socket, dataInputStream, dataOutputStream);
|
||||
}
|
||||
}
|
||||
catch (IOException | ClassNotFoundException e) {
|
||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.SEVERE, null, e);
|
||||
try {
|
||||
sendHandshakeResponse(false, dataOutputStream);
|
||||
}
|
||||
catch (IOException e1) {
|
||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.INFO, null, e1);
|
||||
}
|
||||
tryClose(socket);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendHandshake(DataOutputStream output, ConnectionData serverData, ConnectionData clientData) throws IOException {
|
||||
TcpHandshake handshake = TcpHandshake.createPrivate(serverData, clientData);
|
||||
byte[] message = handshake.toByteArray();
|
||||
|
||||
sendMessage(message, output);
|
||||
}
|
||||
|
||||
private void sendHandshake(DataOutputStream output) throws IOException {
|
||||
TcpHandshake handshake = TcpHandshake.createPublic();
|
||||
byte[] message = handshake.toByteArray();
|
||||
|
||||
sendMessage(message, output);
|
||||
}
|
||||
|
||||
private void sendHandshakeResponse(boolean success, DataOutputStream output) throws IOException {
|
||||
TcpHandshakeResponse response = new TcpHandshakeResponse(success);
|
||||
|
||||
byte[] message = response.toByteArray();
|
||||
sendMessage(message, output);
|
||||
}
|
||||
|
||||
private void sendMessage(byte[] message, DataOutputStream output) throws IOException {
|
||||
output.writeInt(message.length);
|
||||
output.write(message);
|
||||
output.flush();
|
||||
}
|
||||
|
||||
private TcpHandshake receiveHandshake(DataInputStream input) throws IOException, ClassNotFoundException {
|
||||
byte[] msg = receiveMessage(input);
|
||||
|
||||
if (byteToObject(msg) instanceof TcpHandshake handshake) return handshake;
|
||||
else throw new IOException("Not a handshake");
|
||||
}
|
||||
|
||||
private TcpHandshakeResponse receiveHandshakeResponse(DataInputStream input) throws IOException, ClassNotFoundException {
|
||||
byte[] msg = receiveMessage(input);
|
||||
|
||||
if (byteToObject(msg) instanceof TcpHandshakeResponse response) return response;
|
||||
else throw new IOException("Not a handshake");
|
||||
}
|
||||
|
||||
private byte[] receiveMessage(DataInputStream input) throws IOException {
|
||||
int length = input.readInt();
|
||||
if (length < 0) throw new IOException("Invalid length");
|
||||
|
||||
byte[] msg = input.readNBytes(length);
|
||||
if (msg.length != length) throw new IOException("Socket closed while receiving data");
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
private Object byteToObject(byte[] bytes) throws IOException, ClassNotFoundException {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
ObjectInput in = new ObjectInputStream(bais);
|
||||
|
||||
return in.readObject();
|
||||
}
|
||||
|
||||
public void tryClose(Socket socket) {
|
||||
try {
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
Logger.getLogger(TcpFactory.class.getName()).log(Level.WARNING, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.models.ConnectionData;
|
||||
import pitiupi.models.Messages.Message;
|
||||
|
||||
final class TcpHandshake extends Message {
|
||||
private final boolean isPrivate;
|
||||
private final ConnectionData serverData;
|
||||
private final ConnectionData clientData;
|
||||
|
||||
private TcpHandshake(boolean isPrivate, ConnectionData serverData, ConnectionData clientData) {
|
||||
this.isPrivate = isPrivate;
|
||||
this.serverData = serverData;
|
||||
this.clientData = clientData;
|
||||
}
|
||||
|
||||
static TcpHandshake createPrivate(ConnectionData serverData, ConnectionData clientData) {
|
||||
return new TcpHandshake(true, serverData, clientData);
|
||||
}
|
||||
|
||||
static TcpHandshake createPublic() {
|
||||
return new TcpHandshake(false, null, null);
|
||||
}
|
||||
|
||||
public boolean isPrivate() {
|
||||
return isPrivate;
|
||||
}
|
||||
|
||||
public ConnectionData getServerData() {
|
||||
return serverData;
|
||||
}
|
||||
|
||||
public ConnectionData getClientData() {
|
||||
return clientData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.models.Messages.Message;
|
||||
|
||||
class TcpHandshakeResponse extends Message {
|
||||
private final boolean sucess;
|
||||
|
||||
TcpHandshakeResponse(boolean sucess) {
|
||||
this.sucess = sucess;
|
||||
}
|
||||
|
||||
public boolean getSucess() {
|
||||
return sucess;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.models.ConnectionData;
|
||||
import pitiupi.models.Messages.Message;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public final class TcpManager extends Thread{
|
||||
private final TcpFactory tcpFactory;
|
||||
|
||||
private final Map<ConnectionData, TcpServer> servers;
|
||||
|
||||
// TODO: Atualizar a identificação das conexões públicas quando
|
||||
// a identificação de usuário/processo for revisada.
|
||||
// Atualmente, o mapa permite apenas uma conexão pública por endereço IP.
|
||||
private final Map<InetAddress, TcpPitiupi> connections;
|
||||
private final List<TcpClient> clients;
|
||||
private final ExecutorService connectionExecutor;
|
||||
private final ServerSocket serverSocket;
|
||||
|
||||
private final Consumer<byte[]> handleMessage;
|
||||
|
||||
private volatile boolean isRunning;
|
||||
|
||||
public TcpManager(int port, Consumer<byte[]> handleMessage) throws IOException {
|
||||
servers = new ConcurrentHashMap<>();
|
||||
connections = new ConcurrentHashMap<>();
|
||||
clients = new ArrayList<>();
|
||||
|
||||
tcpFactory = new TcpFactory(this);
|
||||
|
||||
connectionExecutor = Executors.newCachedThreadPool();
|
||||
|
||||
serverSocket = new ServerSocket(port);
|
||||
this.handleMessage = handleMessage;
|
||||
}
|
||||
|
||||
public TcpServer getServer(String serverName, String pluginIdentifier, String username, InetAddress userAddress, int port) {
|
||||
TcpServer tcpServer = tcpFactory.createServer(serverName, pluginIdentifier, username, userAddress, port);
|
||||
servers.put(tcpServer.getConnectionData(), tcpServer);
|
||||
|
||||
return tcpServer;
|
||||
}
|
||||
|
||||
public TcpClient getClient(ConnectionData serverData, String pluginIdentifier, String username) {
|
||||
TcpClient tcpClient = tcpFactory.createClient(serverData, pluginIdentifier, username, serverData.getOwnerAddress(), serverData.getPort());
|
||||
|
||||
if (tcpClient == null) return null;
|
||||
|
||||
clients.add(tcpClient);
|
||||
return tcpClient;
|
||||
}
|
||||
|
||||
private TcpPitiupi createConnection(InetAddress receiverAddress, int port) {
|
||||
TcpPitiupi tcpPitiupi = tcpFactory.createPitiupiConnection(receiverAddress, port);
|
||||
|
||||
if (tcpPitiupi == null) return null;
|
||||
|
||||
manageConnectionList(tcpPitiupi, receiverAddress);
|
||||
startConnectionReceiver(tcpPitiupi);
|
||||
|
||||
return tcpPitiupi;
|
||||
}
|
||||
|
||||
public boolean send(Message message, InetAddress receiverAddress, int port) {
|
||||
synchronized (connections) {
|
||||
TcpPitiupi tcpPitiupi = findConnection(receiverAddress);
|
||||
|
||||
if (tcpPitiupi == null || tcpPitiupi.isClosed()) tcpPitiupi = createConnection(receiverAddress, port);
|
||||
if (tcpPitiupi == null) return false;
|
||||
|
||||
try {
|
||||
tcpPitiupi.send(message.toByteArray());
|
||||
return true;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void receive() {
|
||||
while (isRunning) {
|
||||
Socket socket = null;
|
||||
|
||||
try {
|
||||
socket = serverSocket.accept();
|
||||
processConnection(socket);
|
||||
}
|
||||
catch (SocketException e) {
|
||||
if (isRunning) {
|
||||
Logger.getLogger(TcpManager.class.getName()).log(Level.SEVERE, "Erro no servidor TCP.", e);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, "Erro ao aceitar conexão.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processConnection(Socket socket) {
|
||||
connectionExecutor.submit(() -> {
|
||||
try {
|
||||
TcpPitiupi tcpPitiupi = tcpFactory.createFromReceiver(socket);
|
||||
|
||||
if (tcpPitiupi == null) return;
|
||||
|
||||
if (tcpPitiupi instanceof TcpClient client) {
|
||||
ConnectionData serverData = client.getServerData();
|
||||
|
||||
TcpServer tcpServer;
|
||||
|
||||
tcpServer = servers.get(serverData);
|
||||
|
||||
clients.add(client);
|
||||
tcpServer.addClient(client);
|
||||
} else {
|
||||
manageConnectionList(tcpPitiupi, socket.getInetAddress());
|
||||
startConnectionReceiver(tcpPitiupi);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logger.getLogger(TcpClient.class.getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void closeManager() {
|
||||
try {
|
||||
isRunning = false;
|
||||
serverSocket.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, e);
|
||||
}
|
||||
|
||||
for (TcpClient tcpClient : clients) {
|
||||
try {
|
||||
tcpClient.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
for (TcpPitiupi tcpPitiupi : connections.values()) {
|
||||
try {
|
||||
tcpPitiupi.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Logger.getLogger(TcpManager.class.getName()).log(Level.WARNING, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
servers.clear();
|
||||
clients.clear();
|
||||
connections.clear();
|
||||
|
||||
connectionExecutor.shutdown();
|
||||
}
|
||||
|
||||
public void closeServer(TcpServer tcpServer) {
|
||||
ConnectionData serverData = tcpServer.getConnectionData();
|
||||
if (serverData.isServer()) servers.remove(serverData);
|
||||
|
||||
tcpServer.close();
|
||||
}
|
||||
|
||||
private TcpPitiupi findConnection(InetAddress receiverAddress) {
|
||||
return connections.get(receiverAddress);
|
||||
}
|
||||
|
||||
public TcpServer findServer(ConnectionData serverData) {
|
||||
return servers.get(serverData);
|
||||
}
|
||||
|
||||
private void manageConnectionList(TcpPitiupi tcpPitiupi, InetAddress receiverAddress) {
|
||||
synchronized (connections) {
|
||||
TcpPitiupi oldPitiupiConnection = connections.put(receiverAddress, tcpPitiupi);
|
||||
if (oldPitiupiConnection != null) {
|
||||
tcpFactory.tryClose(oldPitiupiConnection.getSocket());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startConnectionReceiver(TcpPitiupi tcpPitiupi) {
|
||||
connectionExecutor.submit(() -> {
|
||||
while (!tcpPitiupi.isClosed()) {
|
||||
try {
|
||||
handleMessage.accept(tcpPitiupi.receive());
|
||||
}
|
||||
catch (IOException e) {
|
||||
Logger.getLogger(TcpManager.class.getName()).log(Level.SEVERE, null, e);
|
||||
break;
|
||||
}
|
||||
finally {
|
||||
connections.remove(tcpPitiupi.getSocket().getInetAddress());
|
||||
|
||||
try {
|
||||
tcpPitiupi.close();
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(TcpManager.class.getName()).log(Level.INFO, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
isRunning = true;
|
||||
receive();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class TcpPitiupi {
|
||||
private static final int MAX_MESSAGE_SIZE = 1_048_576;
|
||||
|
||||
protected final Socket socket;
|
||||
|
||||
protected final DataInputStream input;
|
||||
protected final DataOutputStream output;
|
||||
|
||||
protected boolean isClosed;
|
||||
|
||||
public TcpPitiupi(Socket socket, DataInputStream input, DataOutputStream output) {
|
||||
this.socket = socket;
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
|
||||
this.isClosed = socket.isClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia uma mensagem pela conexão.
|
||||
*
|
||||
* <p>A mensagem é enviada precedida pelo seu tamanho, permitindo que o
|
||||
* receptor determine quantos bytes devem ser lidos.</p>
|
||||
*
|
||||
* @param message mensagem a ser enviada
|
||||
* @throws IOException se ocorrer um erro durante o envio
|
||||
*/
|
||||
public void send(byte[] message) throws IOException {
|
||||
if (message.length > MAX_MESSAGE_SIZE) throw new IOException("Oversized message");
|
||||
|
||||
try {
|
||||
output.writeInt(message.length);
|
||||
output.write(message);
|
||||
output.flush();
|
||||
} catch (EOFException e) {
|
||||
Logger.getLogger(TcpPitiupi.class.getName()).log(Level.INFO, null, e);
|
||||
isClosed = true;
|
||||
throw new IOException("Socket closed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recebe uma mensagem da conexão.
|
||||
*
|
||||
* <p>Primeiro lê o tamanho da mensagem e, em seguida, lê a quantidade
|
||||
* correspondente de bytes.</p>
|
||||
*
|
||||
* @return mensagem recebida
|
||||
* @throws IOException se o tamanho da mensagem for inválido ou se a conexão
|
||||
* for encerrada antes que a mensagem seja completamente recebida
|
||||
*/
|
||||
public byte[] receive() throws IOException {
|
||||
try {
|
||||
int length = input.readInt();
|
||||
|
||||
if (length < 0 || length > MAX_MESSAGE_SIZE) throw new IOException("Invalid message length.");
|
||||
|
||||
byte[] message = input.readNBytes(length);
|
||||
|
||||
if (message.length != length) {
|
||||
Logger.getLogger(TcpPitiupi.class.getName()).log(Level.INFO, null, "Socket closed");
|
||||
isClosed = true;
|
||||
throw new IOException("Socket closed");
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
catch (EOFException e) {
|
||||
Logger.getLogger(TcpPitiupi.class.getName()).log(Level.INFO, null, e);
|
||||
isClosed = true;
|
||||
throw new IOException("Socket closed");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
if (socket.isClosed()) isClosed = true;
|
||||
if (socket.isInputShutdown()) isClosed = true;
|
||||
if (socket.isOutputShutdown()) isClosed = true;
|
||||
|
||||
return isClosed;
|
||||
}
|
||||
|
||||
Socket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package pitiupi.net.tcp;
|
||||
|
||||
import pitiupi.models.ConnectionData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public final class TcpServer {
|
||||
private final ConnectionData connectionData;
|
||||
|
||||
private final BlockingQueue<TcpClient> buffer;
|
||||
private final Map<ConnectionData, TcpClient> clients;
|
||||
|
||||
TcpServer(ConnectionData connectionData) {
|
||||
this.connectionData = connectionData;
|
||||
|
||||
this.buffer = new LinkedBlockingQueue<>();
|
||||
this.clients = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
void addClient(TcpClient tcpClient) {
|
||||
buffer.add(tcpClient);
|
||||
clients.put(tcpClient.getClientData(), tcpClient);
|
||||
}
|
||||
|
||||
public TcpClient receive() throws IOException {
|
||||
try {
|
||||
return buffer.take();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Logger.getLogger(TcpServer.class.getName()).log(Level.SEVERE, null, e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ConnectionData> getClients() {
|
||||
return clients.keySet().stream().toList();
|
||||
}
|
||||
|
||||
public void removeClient(ConnectionData clientData) {
|
||||
TcpClient client = clients.remove(clientData);
|
||||
try {
|
||||
client.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Logger.getLogger(TcpServer.class.getName()).log(Level.INFO, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionData getConnectionData() {
|
||||
return connectionData;
|
||||
}
|
||||
|
||||
void close() {
|
||||
for (TcpClient tcpClient : clients.values()) {
|
||||
try {
|
||||
tcpClient.close();
|
||||
} catch (IOException e) {
|
||||
Logger.getLogger(TcpServer.class.getName()).log(Level.WARNING, null, e);
|
||||
}
|
||||
}
|
||||
buffer.clear();
|
||||
clients.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package pitiupi.plugin;
|
||||
|
||||
import pitiupi.control.ConnectionManager;
|
||||
import pitiupi.control.GuiManager;
|
||||
import pitiupi.models.ConnectionData;
|
||||
import pitiupi.models.Menus.Menu;
|
||||
import pitiupi.models.Menus.MenuCategory;
|
||||
import pitiupi.models.Messages.Message;
|
||||
import pitiupi.net.tcp.TcpClient;
|
||||
import pitiupi.net.tcp.TcpServer;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
public class Pitiupi {
|
||||
|
||||
private final ConnectionManager connectionManager;
|
||||
private final GuiManager guiManager;
|
||||
|
||||
public Pitiupi(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.guiManager = guiManager;
|
||||
}
|
||||
|
||||
public boolean sendUdpMulticast(Message message) {
|
||||
return connectionManager.sendUdpMulticast(message);
|
||||
}
|
||||
|
||||
public boolean sendUdpUnicast(Message message, InetAddress address) {
|
||||
return connectionManager.sendUdpUnicast(message, address);
|
||||
}
|
||||
|
||||
public boolean sendTcp(Message message, InetAddress address) {
|
||||
return connectionManager.sendTcp(message, address);
|
||||
}
|
||||
|
||||
public TcpServer openTcpServer(String serverName, String pluginId) {
|
||||
return connectionManager.getTcpServer(serverName, pluginId);
|
||||
}
|
||||
|
||||
public TcpClient connectToTcpServer(ConnectionData connectionData, String pluginId) {
|
||||
return connectionManager.connectToTcpServer(connectionData, pluginId);
|
||||
}
|
||||
|
||||
public void closeTcpServer(TcpServer tcpServer) {
|
||||
connectionManager.closeTcpServer(tcpServer);
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return connectionManager.getUserName();
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return connectionManager.getPort();
|
||||
}
|
||||
|
||||
public void createMenu(MenuCategory category, Menu menu) {
|
||||
guiManager.createMenu(category, menu);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user