Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab3840e4a4 | ||
|
|
e25d3a425d |
No files matched your search
Generated
-1
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="openjdk-27" project-jdk-type="JavaSDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="openjdk-27" project-jdk-type="JavaSDK" />
|
||||||
</project>
|
</project>
|
||||||
Generated
+1
@@ -2,5 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/udpTest" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
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>
|
||||||
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>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="083e72ce-fa9b-4ddf-a0ef-24105aa3eabb" name="Changes" comment="">
|
<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/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/misc.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/vcs.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$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -34,7 +33,7 @@
|
|||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||||
"git-widget-placeholder": "feature/separacao-gerenciadores",
|
"git-widget-placeholder": "update/screenCast",
|
||||||
"ignore.virus.scanning.warn.message": "true",
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
"kotlin-language-version-configured": "true",
|
"kotlin-language-version-configured": "true",
|
||||||
"last_opened_file_path": "C:/Users/Gustavo/OneDrive/Documentos/Faculdade/2026_2/Monitoria/pitiupi/pitiupi.screenCast",
|
"last_opened_file_path": "C:/Users/Gustavo/OneDrive/Documentos/Faculdade/2026_2/Monitoria/pitiupi/pitiupi.screenCast",
|
||||||
@@ -45,6 +44,17 @@
|
|||||||
}
|
}
|
||||||
}]]></component>
|
}]]></component>
|
||||||
<component name="RunManager">
|
<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">
|
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
|
||||||
<module name="pitiupi.screenCast" />
|
<module name="pitiupi.screenCast" />
|
||||||
<option name="ENV_FILES" value="" />
|
<option name="ENV_FILES" value="" />
|
||||||
@@ -79,23 +89,11 @@
|
|||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
</configuration>
|
</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>
|
||||||
<component name="SharedIndexes">
|
<component name="SharedIndexes">
|
||||||
<attachedChunks>
|
<attachedChunks>
|
||||||
<set>
|
<set>
|
||||||
<option value="bundled-jdk-30f59d01ecdd-37e91769500f-intellij.indexing.shared.core-IU-261.26222.65" />
|
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.97" />
|
||||||
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-IU-261.26222.65" />
|
|
||||||
</set>
|
</set>
|
||||||
</attachedChunks>
|
</attachedChunks>
|
||||||
</component>
|
</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" />
|
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="module-library">
|
<orderEntry type="module-library">
|
||||||
<library>
|
<library>
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
@@ -15,6 +16,5 @@
|
|||||||
<SOURCES />
|
<SOURCES />
|
||||||
</library>
|
</library>
|
||||||
</orderEntry>
|
</orderEntry>
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -4,28 +4,29 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.screenCast.GUI;
|
package pitiupi.screenCast.GUI;
|
||||||
|
|
||||||
|
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import javax.swing.JMenu;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ScreenCastMenu extends JMenu {
|
public class ScreenCastMenu extends MulticastActionMenu {
|
||||||
|
|
||||||
private final ScreenCastWindow screenCastWindow;
|
private final ScreenCastWindow screenCastWindow;
|
||||||
private final javax.swing.JMenuItem menuItem;
|
|
||||||
|
|
||||||
public ScreenCastMenu(ScreenCastWindow screenCastWindow) {
|
public ScreenCastMenu(ScreenCastWindow screenCastWindow) {
|
||||||
this.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);
|
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.ConnectionManager;
|
||||||
import pitiupi.control.GuiManager;
|
import pitiupi.control.GuiManager;
|
||||||
|
import pitiupi.plugin.Pitiupi;
|
||||||
import pitiupi.screenCast.control.AlarmClock;
|
import pitiupi.screenCast.control.AlarmClock;
|
||||||
import pitiupi.screenCast.control.ImageTransformation;
|
import pitiupi.screenCast.control.ImageTransformation;
|
||||||
import pitiupi.screenCast.control.ScreenShot;
|
import pitiupi.screenCast.control.ScreenShot;
|
||||||
@@ -22,8 +23,6 @@ import java.util.logging.Logger;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JLabel;
|
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;
|
public final static int TIME = 1000;
|
||||||
|
|
||||||
private float compressRatio;
|
private float compressRatio;
|
||||||
private final ConnectionManager connectionManager;
|
|
||||||
private final GuiManager guiManager;
|
|
||||||
|
|
||||||
public ScreenCastWindow(ConnectionManager connectionManager, GuiManager guiManager) {
|
private final Pitiupi pitiupi;
|
||||||
this.connectionManager = connectionManager;
|
|
||||||
this.guiManager = guiManager;
|
|
||||||
|
|
||||||
|
public ScreenCastWindow(Pitiupi pitiupi) {
|
||||||
|
this.pitiupi = pitiupi;
|
||||||
this.compressRatio = 0.5f;
|
this.compressRatio = 0.5f;
|
||||||
this.initComponents();
|
this.initComponents();
|
||||||
}
|
}
|
||||||
@@ -96,9 +93,9 @@ public class ScreenCastWindow extends javax.swing.JFrame {
|
|||||||
BufferedImage image = ScreenShot.takeAShot();
|
BufferedImage image = ScreenShot.takeAShot();
|
||||||
BufferedImage rescaled = ImageTransformation.resize(image, 1024, 768);
|
BufferedImage rescaled = ImageTransformation.resize(image, 1024, 768);
|
||||||
byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio);
|
byte[] byteImage = ImageTransformation.compress(rescaled, compressRatio);
|
||||||
ScreenCastMessage message = new ScreenCastMessage(this.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) {
|
} catch (AWTException | IOException ex) {
|
||||||
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.screenCast.net;
|
package pitiupi.screenCast.net;
|
||||||
|
|
||||||
import pitiupi.net.Message;
|
|
||||||
|
import pitiupi.models.Messages.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,14 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.screenCast.plugin;
|
package pitiupi.screenCast.plugin;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.util.List;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
import pitiupi.models.Menus.MenuCategory;
|
||||||
import pitiupi.GUI.MainWindow;
|
import pitiupi.models.Messages.Message;
|
||||||
import pitiupi.control.ConnectionManager;
|
import pitiupi.models.Plugin;
|
||||||
import pitiupi.control.GuiManager;
|
import pitiupi.plugin.Pitiupi;
|
||||||
import pitiupi.net.Message;
|
|
||||||
import pitiupi.plugin.Plugin;
|
|
||||||
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
||||||
import pitiupi.screenCast.GUI.ScreenCastWindow;
|
import pitiupi.screenCast.GUI.ScreenCastWindow;
|
||||||
import pitiupi.screenCast.net.ScreenCastMessage;
|
import pitiupi.screenCast.net.ScreenCastMessage;
|
||||||
@@ -20,7 +18,7 @@ import pitiupi.screenCast.net.ScreenCastMessage;
|
|||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ScreenCastPlugin implements Plugin {
|
public class ScreenCastPlugin extends Plugin {
|
||||||
|
|
||||||
private ScreenCastWindow screenCastWindow;
|
private ScreenCastWindow screenCastWindow;
|
||||||
|
|
||||||
@@ -45,28 +43,20 @@ public class ScreenCastPlugin implements Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
public void createPlugin(Pitiupi pitiupi) {
|
||||||
this.screenCastWindow = new ScreenCastWindow(connectionManager, guiManager);
|
this.screenCastWindow = new ScreenCastWindow(pitiupi);
|
||||||
guiManager.addMenu(new ScreenCastMenu(screenCastWindow));
|
pitiupi.createMenu(MenuCategory.Streaming, new ScreenCastMenu(screenCastWindow));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receiveMessage(byte[] message) {
|
public List<Class<? extends Message>> getMessages() {
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(message);
|
return List.of(ScreenCastMessage.class);
|
||||||
ObjectInputStream ois;
|
}
|
||||||
try {
|
|
||||||
ois = new ObjectInputStream(bis);
|
@Override
|
||||||
if (ois.readObject() instanceof ScreenCastMessage msg) {
|
public void receiveMessage(Message message) {
|
||||||
this.screenCastWindow.receiveScreenCastMessage(msg);
|
if (message instanceof ScreenCastMessage msg) {
|
||||||
}
|
this.screenCastWindow.receiveScreenCastMessage(msg);
|
||||||
} catch (IOException | ClassNotFoundException ex) {
|
|
||||||
System.out.println(ex.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message getMessage() {
|
|
||||||
return new ScreenCastMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -66,15 +66,15 @@ A classe `Pitiupi` é a interface que o seu plugin vai receber quando for criado
|
|||||||
|
|
||||||
Envia uma mensagem multicast, retorna `boolean` indicando se teve sucesso.
|
Envia uma mensagem multicast, retorna `boolean` indicando se teve sucesso.
|
||||||
|
|
||||||
### boolean sendUdpUnicast(Message message, InetAddress address)
|
### boolean sendUdpUnicast(Message message, InetAddress receiverAddress)
|
||||||
|
|
||||||
Envia uma mensagem unicast via udp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
|
Envia uma mensagem unicast via udp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
|
||||||
|
|
||||||
### boolean sendTcp(Message message, InetAddress address)
|
### boolean sendTcp(Message message, InetAddress receiverAddress)
|
||||||
|
|
||||||
Envia uma mensagem unicast via tcp para o endereço fornecido, retorna `boolean` indicando se teve sucesso.
|
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 address, String senderId, String receiverId)
|
### <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.
|
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.
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ Esse método é chamado quando o Pitiupi precisa entregar uma `TcpConnection` ao
|
|||||||
|
|
||||||
### <span style="color: red;">[REF]</span> receiveBytes(TcpConnection connection, byte[] bytes)
|
### <span style="color: red;">[REF]</span> receiveBytes(TcpConnection connection, byte[] bytes)
|
||||||
|
|
||||||
Esse método é chamado para entregar as mensagens recebidas por um `TcpConnection`, o tcpConnection no parametro indica qual a fonte da mensagem.
|
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)
|
### <span style="color: red;">[REF]</span> onSocketClosed(TcpConnection connection)
|
||||||
|
|
||||||
|
|||||||
@@ -700,7 +700,7 @@ is divided into following sections:
|
|||||||
<sequential>
|
<sequential>
|
||||||
<j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
|
<j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
|
||||||
<customize>
|
<customize>
|
||||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/>
|
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
|
||||||
<customizeDebuggee/>
|
<customizeDebuggee/>
|
||||||
</customize>
|
</customize>
|
||||||
</j2seproject3:junit>
|
</j2seproject3:junit>
|
||||||
@@ -843,7 +843,7 @@ is divided into following sections:
|
|||||||
<attribute default="${debug.classpath}" name="classpath"/>
|
<attribute default="${debug.classpath}" name="classpath"/>
|
||||||
<attribute default="" name="stopclassname"/>
|
<attribute default="" name="stopclassname"/>
|
||||||
<sequential>
|
<sequential>
|
||||||
<nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
|
<nbjpdastart addressproperty="jpda.receiverAddress" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
|
||||||
<modulepath>
|
<modulepath>
|
||||||
<path path="@{modulepath}"/>
|
<path path="@{modulepath}"/>
|
||||||
</modulepath>
|
</modulepath>
|
||||||
@@ -882,7 +882,7 @@ is divided into following sections:
|
|||||||
<sequential>
|
<sequential>
|
||||||
<j2seproject1:java classname="@{classname}" classpath="@{classpath}" modulename="@{modulename}" modulepath="@{modulepath}">
|
<j2seproject1:java classname="@{classname}" classpath="@{classpath}" modulename="@{modulename}" modulepath="@{modulepath}">
|
||||||
<customize>
|
<customize>
|
||||||
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},address=${jpda.address}"/>
|
<jvmarg value="-agentlib:jdwp=transport=${debug-transport},receiverAddress=${jpda.receiverAddress}"/>
|
||||||
<customizeDebuggee/>
|
<customizeDebuggee/>
|
||||||
</customize>
|
</customize>
|
||||||
</j2seproject1:java>
|
</j2seproject1:java>
|
||||||
@@ -1434,7 +1434,7 @@ is divided into following sections:
|
|||||||
</nbprofiledirect>
|
</nbprofiledirect>
|
||||||
<j2seproject3:junit excludes="${excludes}" includes="${includes}" testincludes="${profile.class}" testmethods="">
|
<j2seproject3:junit excludes="${excludes}" includes="${includes}" testincludes="${profile.class}" testmethods="">
|
||||||
<customize>
|
<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}"/>
|
<env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
|
||||||
<jvmarg value="${profiler.info.jvmargs.agent}"/>
|
<jvmarg value="${profiler.info.jvmargs.agent}"/>
|
||||||
<jvmarg line="${profiler.info.jvmargs}"/>
|
<jvmarg line="${profiler.info.jvmargs}"/>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import pitiupi.control.plugins.PluginManager;
|
|||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Janela principal da aplicação.
|
* Janela principal da aplicação.
|
||||||
@@ -33,9 +34,9 @@ public class MainWindow extends JFrame {
|
|||||||
private JLabel status;
|
private JLabel status;
|
||||||
OnlineUsersPanel onlineUsersPanel;
|
OnlineUsersPanel onlineUsersPanel;
|
||||||
|
|
||||||
private final ConnectionManager connectionManager;
|
private ConnectionManager connectionManager;
|
||||||
private final PluginManager pluginManager;
|
private PluginManager pluginManager;
|
||||||
private final GuiManager guiManager;
|
private GuiManager guiManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cria a janela principal da aplicação.
|
* Cria a janela principal da aplicação.
|
||||||
@@ -44,21 +45,34 @@ public class MainWindow extends JFrame {
|
|||||||
* @param port porta utilizada na comunicação multicast.
|
* @param port porta utilizada na comunicação multicast.
|
||||||
*/
|
*/
|
||||||
public MainWindow(String userName, int port) {
|
public MainWindow(String userName, int port) {
|
||||||
initMenu();
|
try {
|
||||||
|
initMenu();
|
||||||
|
|
||||||
pluginManager = new PluginManager();
|
pluginManager = new PluginManager();
|
||||||
connectionManager = new ConnectionManager(this, userName, pluginManager, port);
|
connectionManager = new ConnectionManager(userName, pluginManager, port);
|
||||||
|
|
||||||
initUserPanel();
|
initUserPanel();
|
||||||
initStatusPanel();
|
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() {
|
private void initMenu() {
|
||||||
|
|||||||
@@ -2,46 +2,58 @@ package pitiupi.control;
|
|||||||
|
|
||||||
import pitiupi.GUI.MainWindow;
|
import pitiupi.GUI.MainWindow;
|
||||||
import pitiupi.control.plugins.PluginManager;
|
import pitiupi.control.plugins.PluginManager;
|
||||||
|
import pitiupi.models.ConnectionData;
|
||||||
import pitiupi.models.Messages.HeartbeatMessage;
|
import pitiupi.models.Messages.HeartbeatMessage;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
import pitiupi.net.tcp.SocketTCP;
|
import pitiupi.net.tcp.TcpClient;
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
import pitiupi.net.tcp.TcpManager;
|
||||||
|
import pitiupi.net.tcp.TcpServer;
|
||||||
import pitiupi.net.udp.SocketUDP;
|
import pitiupi.net.udp.SocketUDP;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ConnectionManager {
|
public class ConnectionManager {
|
||||||
private final String pitiupiIdentifier;
|
|
||||||
|
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
private final HeartbeatManager heartbeatManager;
|
private final HeartbeatManager heartbeatManager;
|
||||||
|
|
||||||
private SocketTCP socketTCP;
|
private TcpManager tcpManager;
|
||||||
private SocketUDP socketUDP;
|
private SocketUDP socketUDP;
|
||||||
private String username;
|
private String username;
|
||||||
|
private final InetAddress address;
|
||||||
private int port;
|
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.pluginManager = pluginManager;
|
||||||
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
this.address = InetAddress.getLocalHost();
|
||||||
this.port = port;
|
this.port = port;
|
||||||
|
|
||||||
pitiupiIdentifier = "Pitiupi.Alice.2026";
|
|
||||||
|
|
||||||
reconnect();
|
reconnect();
|
||||||
|
|
||||||
heartbeatManager = new HeartbeatManager(this);
|
heartbeatManager = new HeartbeatManager(this);
|
||||||
heartbeatManager.start();
|
heartbeatManager.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TcpConnection connectPluginTcp(InetAddress destinationAdress, String senderId, String receiverId) {
|
public TcpClient connectToTcpServer(ConnectionData serverData, String pluginId) {
|
||||||
return socketTCP.createPrivateConnection(destinationAdress, senderId, receiverId);
|
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) {
|
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) {
|
public boolean sendUdpUnicast(Message message, InetAddress destinationAddress) {
|
||||||
@@ -63,7 +75,7 @@ public class ConnectionManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void broadcastLocal(byte[] message) {
|
public void receiveMessage(byte[] message) {
|
||||||
pluginManager.handleMessage(message);
|
pluginManager.handleMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,30 +96,35 @@ public class ConnectionManager {
|
|||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIdentifier(){
|
|
||||||
return this.pitiupiIdentifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reconnect() {
|
private void reconnect() {
|
||||||
if (this.socketUDP != null) {
|
try {
|
||||||
this.socketUDP.close();
|
if (this.socketUDP != null) {
|
||||||
}
|
this.socketUDP.close();
|
||||||
if (this.socketTCP != null) {
|
}
|
||||||
this.socketTCP.close();
|
if (this.tcpManager != null) {
|
||||||
}
|
this.tcpManager.closeManager();
|
||||||
|
}
|
||||||
|
|
||||||
this.socketUDP = new SocketUDP(this, pluginManager);
|
this.socketUDP = new SocketUDP(this, pluginManager);
|
||||||
this.socketTCP = new SocketTCP(this, pluginManager);
|
this.tcpManager = new TcpManager(port, this::receiveMessage);
|
||||||
|
|
||||||
this.socketUDP.start();
|
this.socketUDP.start();
|
||||||
this.socketTCP.start();
|
this.tcpManager.start();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
Logger.getLogger(ConnectionManager.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserName() {
|
public String getUserName() {
|
||||||
return this.username;
|
return this.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InetAddress getAddress() {
|
||||||
|
return this.address;
|
||||||
|
}
|
||||||
|
|
||||||
public void setUserName(String userName) {
|
public void setUserName(String userName) {
|
||||||
this.username = userName;
|
this.username = userName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ import pitiupi.Main;
|
|||||||
import pitiupi.control.ConnectionManager;
|
import pitiupi.control.ConnectionManager;
|
||||||
import pitiupi.control.GuiManager;
|
import pitiupi.control.GuiManager;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
|
||||||
import pitiupi.models.Plugin;
|
import pitiupi.models.Plugin;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -67,29 +66,6 @@ public class PluginManager {
|
|||||||
return null;
|
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) {
|
public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) {
|
||||||
File pluginDir = new File("plugins");
|
File pluginDir = new File("plugins");
|
||||||
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
|
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
package pitiupi.models;
|
package pitiupi.models;
|
||||||
import pitiupi.plugin.Pitiupi;
|
import pitiupi.plugin.Pitiupi;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -50,13 +49,5 @@ public abstract class Plugin {
|
|||||||
public abstract void receiveMessage(Message message);
|
public abstract void receiveMessage(Message message);
|
||||||
|
|
||||||
public boolean receiveAllTypes() {return false;}
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@ package pitiupi.plugin;
|
|||||||
|
|
||||||
import pitiupi.control.ConnectionManager;
|
import pitiupi.control.ConnectionManager;
|
||||||
import pitiupi.control.GuiManager;
|
import pitiupi.control.GuiManager;
|
||||||
|
import pitiupi.models.ConnectionData;
|
||||||
import pitiupi.models.Menus.Menu;
|
import pitiupi.models.Menus.Menu;
|
||||||
import pitiupi.models.Menus.MenuCategory;
|
import pitiupi.models.Menus.MenuCategory;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.models.Messages.Message;
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
import pitiupi.net.tcp.TcpClient;
|
||||||
|
import pitiupi.net.tcp.TcpServer;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
|
||||||
@@ -31,8 +33,16 @@ public class Pitiupi {
|
|||||||
return connectionManager.sendTcp(message, address);
|
return connectionManager.sendTcp(message, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TcpConnection connectTcp(InetAddress address, String senderId, String receiverId) {
|
public TcpServer openTcpServer(String serverName, String pluginId) {
|
||||||
return connectionManager.connectPluginTcp(address, senderId, receiverId);
|
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() {
|
public String getUserName() {
|
||||||
|
|||||||
Reference in new issue
Block a user