Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb3731232b | ||
|
|
fa85b6ee0c | ||
|
|
74153bcdf3 | ||
|
|
70db48e645 |
No files matched your search
@@ -5,5 +5,3 @@
|
|||||||
/pitiupi.chat/nbproject/private/
|
/pitiupi.chat/nbproject/private/
|
||||||
/pitiupi.screenCast/build/
|
/pitiupi.screenCast/build/
|
||||||
/pitiupi.screenCast/dist/
|
/pitiupi.screenCast/dist/
|
||||||
|
|
||||||
.idea/
|
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+83
@@ -0,0 +1,83 @@
|
|||||||
|
<?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="62118a1b-608f-4144-b29c-cf46419ca676" name="Changes" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 0
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="3GbOdQKFlKqBjAjqjAv5TRPxr6Z" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">{
|
||||||
|
"keyToString": {
|
||||||
|
"Application.Main.executor": "Run",
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"git-widget-placeholder": "feature/help-and-documentation",
|
||||||
|
"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",
|
||||||
|
"settings.editor.selected.configurable": "preferences.keymap"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\Users\extre\OneDrive\Documentos\Faculdade\2026_02\Monitoria\Pitiupi\pitiupi\plugins" />
|
||||||
|
</key>
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\Users\extre\OneDrive\Documentos\Faculdade\2026_02\Monitoria\Pitiupi\pitiupi\plugins" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="Main" type="Application" factoryName="Application" temporary="true" nameIsGenerated="true">
|
||||||
|
<option name="MAIN_CLASS_NAME" value="pitiupi.Main" />
|
||||||
|
<module name="pitiupi" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="pitiupi.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
<recent_temporary>
|
||||||
|
<list>
|
||||||
|
<item itemvalue="Application.Main" />
|
||||||
|
</list>
|
||||||
|
</recent_temporary>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-jdk-9823dce3aa75-bf35d07a577b-intellij.indexing.shared.core-IU-252.28539.54" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="62118a1b-608f-4144-b29c-cf46419ca676" name="Changes" comment="" />
|
||||||
|
<created>1784236888334</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1784236888334</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
+258
@@ -0,0 +1,258 @@
|
|||||||
|
# Como criar um plugin para o pitiupi ?
|
||||||
|
|
||||||
|
O objetivo aqui, além de criar plugins para esse sistema, é entender **por que cada parte existe**. Isso é importante porque a ideia de **carregar código externo em tempo de execução** aparece em várias ferramentas nas áreas da computação.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Visão rápida
|
||||||
|
|
||||||
|
O repositório contém projetos Java **independentes**:
|
||||||
|
|
||||||
|
| Projeto | O que é |
|
||||||
|
|---|---|
|
||||||
|
| `pitiupi` | A ferramenta principal : P2P, janela, rede, e o "motor" que carrega plugins |
|
||||||
|
| `pitiupi.chat` | Um plugin que adiciona um chat |
|
||||||
|
| `pitiupi.screenCast` | Outro plugin que adiciona compartilhamento de tela |
|
||||||
|
|
||||||
|
Cada plugin é compilado separadamente com `ant` e o `.jar` gerado é colocado (ou linkado) dentro da pasta `pitiupi/plugins`.
|
||||||
|
|
||||||
|
Quando o pitiupi principal inicia, ele varre essa pasta e **carrega dinamicamente** cada plugin encontrado, sem que o código principal precise conhecer o plugin em tempo de compilação.
|
||||||
|
|
||||||
|
|
||||||
|
Isso é possível graças a três partes que trabalham juntas:
|
||||||
|
|
||||||
|
1. A **interface `Plugin`** : o contrato que todo plugin deve cumprir.
|
||||||
|
2. O **`PluginLoader`** : a classe do pitiupi que varre a pasta `plugins/` e carrega os `.jar`.
|
||||||
|
3. O **arquivo de registro do `ServiceLoader`** : um arquivo de texto dentro do `.jar` que diz "esta classe aqui implementa `Plugin`".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. O contrato: a interface `Plugin`
|
||||||
|
|
||||||
|
Todo plugin **precisa** implementar esta interface (`pitiupi/src/pitiupi/plugin/Plugin.java`):
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface Plugin {
|
||||||
|
public String getName();
|
||||||
|
public String getAuthor();
|
||||||
|
public String getVersion();
|
||||||
|
public void createPlugin(MainWindow window);
|
||||||
|
public Message getMessage();
|
||||||
|
public void receiveMessage(byte[] message);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
O que cada método faz:
|
||||||
|
|
||||||
|
- **`getName()` / `getAuthor()` / `getVersion()`** : indentificação de dados básicos.
|
||||||
|
- **`createPlugin(MainWindow window)`** : É aqui que o plugin cria sua interface gráfica, adiciona itens de menu na janela principal, etc.
|
||||||
|
- **`getMessage()`** : Serve como modelo para a camada de rede saber que tipo de mensagem esse plugin envia.
|
||||||
|
- **`receiveMessage(byte[] message)`** : recebe as mensagens que chegam pela rede e permite que o plugin interprete esses dados.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Como o pitiupi encontra e carrega os plugins
|
||||||
|
|
||||||
|
A classe `PluginLoader` (`pitiupi/src/pitiupi/control/PluginLoader.java`) faz o seguinte:
|
||||||
|
|
||||||
|
1. Olha dentro da pasta `plugins/` e lista todos os arquivos `.jar`.
|
||||||
|
2. Cria um `URLClassLoader` : um "carregador de classes" isolado, que sabe ler código de dentro desses `.jar`.
|
||||||
|
3. Usa `ServiceLoader.load(Plugin.class, pluginClassLoader)` para perguntar: *"dentre essas classes, quais implementam `Plugin`?"*
|
||||||
|
4. Para cada plugin encontrado, chama `createPlugin(mainWindow)` e guarda o plugin numa lista.
|
||||||
|
```java
|
||||||
|
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, pluginClassLoader);
|
||||||
|
|
||||||
|
for (Plugin plugin : serviceLoader) {
|
||||||
|
plugin.createPlugin(this.mainWindow);
|
||||||
|
this.mainWindow.getPlugins().add(plugin);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Obs:** o `ServiceLoader` do Java **não escaneia o bytecode procurando quem implementa a interface**. Ele lê um arquivo de texto simples dentro do `.jar`. Se esse arquivo não existir ou estiver errado, o plugin é ignorado : mesmo que o código esteja perfeito.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4.`META-INF/services`
|
||||||
|
|
||||||
|
Dentro do projeto `pitiupi.chat` por exemplo, existe este arquivo:
|
||||||
|
|
||||||
|
```
|
||||||
|
pitiupi.chat/src/META-INF/services/pitiupi.plugin.Plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
Repare em duas coisas:
|
||||||
|
|
||||||
|
- A **pasta** se chama `META-INF/services`.
|
||||||
|
- O **nome do arquivo** é o nome completo da interface: `pitiupi.plugin.Plugin`.
|
||||||
|
- O **conteúdo** do arquivo é uma linha com o nome completo da classe que implementa essa interface:
|
||||||
|
```
|
||||||
|
pitiupi.chat.plugin.ChatPlugin
|
||||||
|
```
|
||||||
|
|
||||||
|
- O`Plugin` diz **"eu sei fazer o trabalho"**.
|
||||||
|
- O arquivo em `META-INF/services` diz **"aqui está quem sabe fazer o trabalho"**.
|
||||||
|
- Sem o segundo, o pitiupi nunca fica sabendo que a classe existe.
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Exemplo real: o `ChatPlugin`
|
||||||
|
|
||||||
|
Veja como o plugin de chat implementa o contrato : use isso como referência ao escrever o seu:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class ChatPlugin implements Plugin {
|
||||||
|
|
||||||
|
private ChatWindow chatWindow;
|
||||||
|
private MainWindow mainWindow;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createPlugin(MainWindow mainWindow) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
this.chatWindow = new ChatWindow(mainWindow);
|
||||||
|
this.mainWindow.addMenu(new ChatMenu(chatWindow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receiveMessage(byte[] message) {
|
||||||
|
// desserializa os bytes recebidos de volta em um objeto ChatMessage
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(message);
|
||||||
|
try {
|
||||||
|
ObjectInputStream ois = new ObjectInputStream(bis);
|
||||||
|
if (ois.readObject() instanceof ChatMessage msg) {
|
||||||
|
this.chatWindow.receiveChatMessage(msg);
|
||||||
|
}
|
||||||
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "Chat"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthor() { return "Flávio, Tony e Gustavo "; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVersion() { return "0"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message getMessage() { return new ChatMessage(); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Em `createPlugin` é onde o bum de "aparecer na tela" acontece: ele cria a janela do plugin e adiciona um menu na janela principal do pitiupi.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Passo a passo: criando um plugin novo do zero
|
||||||
|
|
||||||
|
Vamos chamar o plugin de exemplo de `SeuPlugin`.
|
||||||
|
|
||||||
|
### Passo 1 : Criar a estrutura do projeto
|
||||||
|
|
||||||
|
Se você quiser um jeito mais facil sem se aprofundar, você pode criar o projeto de duas formas:
|
||||||
|
|
||||||
|
- **Mais simples:** copie um projeto Ant existente, como `pitiupi.chat` por exemplo, e remova o código específico do Chat.
|
||||||
|
- **Outra opção:** crie um novo projeto no NetBeans usando **"Java Class Library"**, mantendo a mesma estrutura de pastas.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
pitiupi.seuplugin/
|
||||||
|
├── build.xml
|
||||||
|
├── nbproject/
|
||||||
|
└── src/
|
||||||
|
├── META-INF/services/
|
||||||
|
└── pitiupi/seuplugin/plugin/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Passo 2 : Adicionar o `pitiupi.jar` como dependência
|
||||||
|
|
||||||
|
O plugin precisa do arquivo **`pitiupi/dist/pitiupi.jar`** para acessar `Plugin` e `MainWindow`.
|
||||||
|
|
||||||
|
- **Se criou do zero:** configure o `pitiupi.jar` como dependência.
|
||||||
|
- **Se copiou um projeto existente:** essa configuração já está pronta.
|
||||||
|
- **Se criou pelo NetBeans:** **Properties → Libraries → Add JAR/Folder** → selecione `pitiupi/dist/pitiupi.jar`.
|
||||||
|
|
||||||
|
|
||||||
|
### Passo 3 : Implementar a interface `Plugin`
|
||||||
|
|
||||||
|
Crie `src/pitiupi/seuplugin/plugin/SeuPlugin.java`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
package pitiupi.seuplugin.plugin;
|
||||||
|
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.net.Message;
|
||||||
|
import pitiupi.plugin.Plugin;
|
||||||
|
|
||||||
|
public class SeuPlugin implements Plugin {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createPlugin(MainWindow window) {
|
||||||
|
System.out.println("SeuPlugin foi carregado!");
|
||||||
|
// aqui você criaria sua janela/menu, como o ChatPlugin faz
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receiveMessage(byte[] message) {
|
||||||
|
// trate as mensagens recebidas da rede aqui
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() { return "MeuPlugin"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthor() { return "Seu Nome"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVersion() { return "0.1"; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message getMessage() { return null; } // troque por uma Message real, se tiver
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Passo 4 : Registrar o plugin no `ServiceLoader`
|
||||||
|
|
||||||
|
Crie o arquivo (sem extensão):
|
||||||
|
|
||||||
|
```
|
||||||
|
src/META-INF/services/pitiupi.plugin.Plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
Com **uma única linha** dentro, o nome completo da sua classe:
|
||||||
|
|
||||||
|
```
|
||||||
|
pitiupi.seuplugin.plugin.MeuPlugin
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Passo 5 : Compilar com Ant
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd pitiupi.seuplugin
|
||||||
|
ant jar
|
||||||
|
```
|
||||||
|
|
||||||
|
Isso gera o `.jar` em `pitiupi.seuplugin/dist/`.
|
||||||
|
|
||||||
|
### Passo 6 : Linkar o `.jar` na pasta de plugins
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd pitiupi/plugins
|
||||||
|
ln -s ../../pitiupi.meuplugin/dist/*.jar .
|
||||||
|
```
|
||||||
|
|
||||||
|
Usar link simbólico (`ln -s`) é melhor durante o desenvolvimento: toda vez que você recompilar o plugin, não precisa copiar de novo.
|
||||||
|
|
||||||
|
|
||||||
|
## 7. Geral
|
||||||
|
|
||||||
|
```
|
||||||
|
Plugin.java → "o que todo plugin precisa saber fazer" (o contrato)
|
||||||
|
MeuPlugin.java → "como EU faço esse trabalho" (a implementação)
|
||||||
|
META-INF/services/pitiupi.plugin.Plugin → "aqui está quem faz o trabalho" (o registro)
|
||||||
|
PluginLoader.java → "eu procuro registros e chamo quem se registrou" (o carregador, já pronto no pitiupi)
|
||||||
|
```
|
||||||
|
|
||||||
|
Entendendo essas quatro peças, criar qualquer plugin novo : chat, screencast, ou algo totalmente diferente (arquivos, jogos, sensores) : é sempre o mesmo padrão: implementar a interface, registrar no `META-INF/services`, compilar e linkar.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#Fri, 02 Oct 2026 20:11:21 -0300
|
#Sat, 18 Jul 2026 10:32:46 -0300
|
||||||
|
|
||||||
|
|
||||||
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.chat=
|
C\:\\Users\\extre\\OneDrive\\Documentos\\Faculdade\\2026_02\\Monitoria\\Pitiupi\\pitiupi.chat=
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -7,14 +7,5 @@
|
|||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="module-library">
|
|
||||||
<library>
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MODULE_DIR$/../pitiupi/dist/pitiupi.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</orderEntry>
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
File renamed without changes.
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="JAVA_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="module-library">
|
|
||||||
<library>
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MODULE_DIR$/../../pitiupi/dist/pitiupi.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</orderEntry>
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -4,29 +4,28 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.chat.GUI;
|
package pitiupi.chat.GUI;
|
||||||
|
|
||||||
import pitiupi.models.Menus.MulticastMenus.MulticastMenu;
|
import java.awt.event.ActionEvent;
|
||||||
import pitiupi.models.Menus.MulticastMenus.MulticastSubMenu;
|
import javax.swing.JMenu;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ChatMenu extends MulticastSubMenu {
|
public class ChatMenu extends JMenu {
|
||||||
ChatMulticastMenu chatMulticastMenu;
|
|
||||||
|
private final ChatWindow chatWindow;
|
||||||
|
private javax.swing.JMenuItem menuItem;
|
||||||
|
|
||||||
public ChatMenu(ChatWindow chatWindow) {
|
public ChatMenu(ChatWindow chatWindow) {
|
||||||
this.chatMulticastMenu = new ChatMulticastMenu(chatWindow);
|
this.chatWindow = chatWindow;
|
||||||
|
this.setText("Chat");
|
||||||
|
menuItem = new javax.swing.JMenuItem();
|
||||||
|
menuItem.setText("Show Chat Window");
|
||||||
|
menuItem.addActionListener(this::chatMenuActionPerformed);
|
||||||
|
this.add(menuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void chatMenuActionPerformed(ActionEvent ae) {
|
||||||
public String getText() {
|
this.chatWindow.setVisible(true);
|
||||||
return "Chat";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<MulticastMenu> getMenus() {
|
|
||||||
return List.of(chatMulticastMenu);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package pitiupi.chat.GUI;
|
|
||||||
|
|
||||||
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;;
|
|
||||||
|
|
||||||
public class ChatMulticastMenu extends MulticastActionMenu {
|
|
||||||
ChatWindow chatWindow;
|
|
||||||
|
|
||||||
public ChatMulticastMenu(ChatWindow chatWindow) {
|
|
||||||
this.chatWindow = chatWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getText() {
|
|
||||||
return "Show Chat Window";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
chatWindow.setVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,33 +5,34 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.chat.GUI;
|
package pitiupi.chat.GUI;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import pitiupi.chat.net.ChatMessage;
|
import pitiupi.chat.net.ChatMessage;
|
||||||
import pitiupi.control.ConnectionManager;
|
import pitiupi.GUI.MainWindow;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.net.Message;
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ChatWindow extends JDialog {
|
public class ChatWindow extends javax.swing.JDialog {
|
||||||
|
|
||||||
private JButton jButton1;
|
private javax.swing.JButton jButton1;
|
||||||
private JEditorPane jEditorPane1;
|
private javax.swing.JEditorPane jEditorPane1;
|
||||||
private JPanel jPanel1;
|
private javax.swing.JPanel jPanel1;
|
||||||
private JPanel jPanel2;
|
private javax.swing.JPanel jPanel2;
|
||||||
private JScrollPane jScrollPane1;
|
private javax.swing.JScrollPane jScrollPane1;
|
||||||
private JScrollPane jScrollPane2;
|
private javax.swing.JScrollPane jScrollPane2;
|
||||||
private JSplitPane jSplitPane1;
|
private javax.swing.JSplitPane jSplitPane1;
|
||||||
private JTextArea jTextArea1;
|
private javax.swing.JTextArea jTextArea1;
|
||||||
|
|
||||||
private final ConnectionManager connectionManager;
|
private final MainWindow mainWindow;
|
||||||
|
|
||||||
public ChatWindow(ConnectionManager connectionManager) {
|
public ChatWindow(MainWindow mainWindow) {
|
||||||
this.connectionManager = connectionManager;
|
this.mainWindow = mainWindow;
|
||||||
initComponents();
|
initComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,23 +43,23 @@ public class ChatWindow extends JDialog {
|
|||||||
*/
|
*/
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
|
|
||||||
this.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
|
this.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
|
||||||
this.setTitle("Chat Window");
|
this.setTitle("Chat Window");
|
||||||
this.setBounds(new java.awt.Rectangle(0, 0, 800, 600));
|
this.setBounds(new java.awt.Rectangle(0, 0, 800, 600));
|
||||||
this.getContentPane().setLayout(new java.awt.BorderLayout(5, 5));
|
this.getContentPane().setLayout(new java.awt.BorderLayout(5, 5));
|
||||||
this.setPreferredSize(new java.awt.Dimension(800, 600));
|
this.setPreferredSize(new java.awt.Dimension(800, 600));
|
||||||
|
|
||||||
jSplitPane1 = new JSplitPane();
|
jSplitPane1 = new javax.swing.JSplitPane();
|
||||||
jScrollPane1 = new JScrollPane();
|
jScrollPane1 = new javax.swing.JScrollPane();
|
||||||
jEditorPane1 = new JEditorPane();
|
jEditorPane1 = new javax.swing.JEditorPane();
|
||||||
jPanel1 = new JPanel();
|
jPanel1 = new javax.swing.JPanel();
|
||||||
jScrollPane2 = new JScrollPane();
|
jScrollPane2 = new javax.swing.JScrollPane();
|
||||||
jTextArea1 = new JTextArea();
|
jTextArea1 = new javax.swing.JTextArea();
|
||||||
jPanel2 = new JPanel();
|
jPanel2 = new javax.swing.JPanel();
|
||||||
jButton1 = new JButton();
|
jButton1 = new javax.swing.JButton();
|
||||||
|
|
||||||
jSplitPane1.setDividerLocation(500);
|
jSplitPane1.setDividerLocation(500);
|
||||||
jSplitPane1.setOrientation(JSplitPane.VERTICAL_SPLIT);
|
jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
|
||||||
jEditorPane1.setContentType("text/html");
|
jEditorPane1.setContentType("text/html");
|
||||||
jPanel1.setLayout(new java.awt.BorderLayout());
|
jPanel1.setLayout(new java.awt.BorderLayout());
|
||||||
jTextArea1.setColumns(20);
|
jTextArea1.setColumns(20);
|
||||||
@@ -90,16 +91,20 @@ public class ChatWindow extends JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage() {
|
private void sendMessage() {
|
||||||
if (jTextArea1.getText().trim().isEmpty()) {
|
if (jTextArea1.getText().trim().length() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ChatMessage message = new ChatMessage(connectionManager.getUserName(), jTextArea1.getText());
|
ChatMessage message = new ChatMessage(mainWindow.getUserName(), jTextArea1.getText());
|
||||||
this.sendChatMessage(message);
|
this.sendChatMessage(message);
|
||||||
jTextArea1.setText("");
|
jTextArea1.setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendChatMessage(Message message) {
|
public void sendChatMessage(Message message) {
|
||||||
connectionManager.sendUdpMulticast(message);
|
try {
|
||||||
|
mainWindow.getSocket().send(message.toByteArray());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.getLogger(MainWindow.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveChatMessage(ChatMessage message) {
|
public void receiveChatMessage(ChatMessage message) {
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.chat.net;
|
package pitiupi.chat.net;
|
||||||
|
|
||||||
|
import pitiupi.net.Message;
|
||||||
import pitiupi.models.Messages.Message;
|
import pitiupi.net.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ChatMessage extends Message {
|
public class ChatMessage extends Message{
|
||||||
|
|
||||||
private String text;
|
private String text;
|
||||||
private String name;
|
private String name;
|
||||||
|
|||||||
@@ -4,43 +4,48 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.chat.plugin;
|
package pitiupi.chat.plugin;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.util.Collection;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.io.ObjectInputStream;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
import pitiupi.chat.GUI.ChatMenu;
|
import pitiupi.chat.GUI.ChatMenu;
|
||||||
import pitiupi.chat.GUI.ChatWindow;
|
import pitiupi.chat.GUI.ChatWindow;
|
||||||
import pitiupi.chat.net.ChatMessage;
|
import pitiupi.chat.net.ChatMessage;
|
||||||
import pitiupi.control.ConnectionManager;
|
import pitiupi.net.Message;
|
||||||
import pitiupi.control.GuiManager;
|
import pitiupi.plugin.Plugin;
|
||||||
import pitiupi.models.Menus.MenuCategory;
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
import pitiupi.models.Plugin;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public class ChatPlugin extends Plugin {
|
public class ChatPlugin implements Plugin {
|
||||||
|
|
||||||
private ChatWindow chatWindow;
|
private ChatWindow chatWindow;
|
||||||
|
private MainWindow mainWindow;
|
||||||
|
|
||||||
@Override
|
public ChatPlugin() {
|
||||||
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
|
||||||
this.chatWindow = new ChatWindow(connectionManager);
|
|
||||||
guiManager.createMenu(MenuCategory.Social, new ChatMenu(chatWindow));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Class<? extends Message>> getMessages() {
|
public void createPlugin(MainWindow mainWindow) {
|
||||||
return List.of(ChatMessage.class);
|
this.mainWindow = mainWindow;
|
||||||
|
this.chatWindow = new ChatWindow(mainWindow);
|
||||||
|
this.mainWindow.addMenu(new ChatMenu(chatWindow));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receiveMessage(Message message) {
|
public void receiveMessage(byte[] message) {
|
||||||
if (message instanceof ChatMessage msg) {
|
ByteArrayInputStream bis = new ByteArrayInputStream(message);
|
||||||
this.chatWindow.receiveChatMessage(msg);
|
ObjectInputStream ois;
|
||||||
|
try {
|
||||||
|
ois = new ObjectInputStream(bis);
|
||||||
|
if (ois.readObject() instanceof ChatMessage msg) {
|
||||||
|
this.chatWindow.receiveChatMessage(msg);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
} catch (ClassNotFoundException ex) {
|
||||||
|
System.out.println(ex.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,9 +65,7 @@ public class ChatPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIdentifier() {
|
public Message getMessage() {
|
||||||
return "Pitiupi.Chat.Example";
|
return new ChatMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
JAR = ../pitiupi/dist/pitiupi.jar
|
|
||||||
OUT_DIR = out
|
|
||||||
META-INF_DIR = src/META-INF
|
|
||||||
DIST = dist/mpChat.jar
|
|
||||||
|
|
||||||
SOURCES = $(shell find -name "*.java")
|
|
||||||
|
|
||||||
all:
|
|
||||||
mkdir -p "$(OUT_DIR)"
|
|
||||||
mkdir -p "$(dir $(DIST))"
|
|
||||||
javac -cp "$(JAR)" -d "$(OUT_DIR)" $(SOURCES)
|
|
||||||
cp -r "$(META-INF_DIR)" "$(OUT_DIR)"
|
|
||||||
jar cf "$(DIST)" -C "$(OUT_DIR)" .
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf "$(OUT_DIR)"/*
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
plugin.MultiProtocolChat
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="JAVA_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="module-library">
|
|
||||||
<library>
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MODULE_DIR$/../pitiupi/dist/pitiupi.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</orderEntry>
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package GUI;
|
|
||||||
|
|
||||||
import pitiupi.models.Menus.MenuCategory;
|
|
||||||
import pitiupi.models.Menus.MulticastMenus.MulticastSubMenu;
|
|
||||||
import plugin.MultiProtocolChat;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
public class ChatMenu extends MulticastSubMenu {
|
|
||||||
private final MultiProtocolChat multiProtocolChat;
|
|
||||||
|
|
||||||
public ChatMenu(MultiProtocolChat multiProtocolChat) {
|
|
||||||
this.multiProtocolChat = multiProtocolChat;
|
|
||||||
this.setText("MP-Chat");
|
|
||||||
|
|
||||||
JMenuItem startPrivateChat = new JMenuItem("start Private Chat");
|
|
||||||
startPrivateChat.addActionListener(e -> showPrivateChatDialog());
|
|
||||||
this.add(startPrivateChat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void newMenuItem(JMenuItem newMenuItem, ChatWindow chatWindow) {
|
|
||||||
newMenuItem.addActionListener(e -> chatWindow.setVisible(true));
|
|
||||||
|
|
||||||
this.add(newMenuItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeMenuItem(JMenuItem removeMenuItem) {
|
|
||||||
this.remove(removeMenuItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showPrivateChatDialog() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getText() {
|
|
||||||
return "MP-Chat";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package GUI;
|
|
||||||
|
|
||||||
import net.MpChatMessage;
|
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
|
|
||||||
public abstract class ChatWindow extends JFrame {
|
|
||||||
|
|
||||||
protected final ConnectionManager connectionManager;
|
|
||||||
|
|
||||||
private JTextArea receivedMessagesArea;
|
|
||||||
protected JTextField messageField;
|
|
||||||
|
|
||||||
public ChatWindow(ConnectionManager connectionManager) {
|
|
||||||
this.connectionManager = connectionManager;
|
|
||||||
initComponents();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initComponents() {
|
|
||||||
setTitle("Socket Test");
|
|
||||||
setSize(700, 500);
|
|
||||||
setLocationRelativeTo(null);
|
|
||||||
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
|
||||||
|
|
||||||
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
|
||||||
|
|
||||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
|
||||||
|
|
||||||
initReceivedMessagesPanel(mainPanel);
|
|
||||||
initBottomPanel(mainPanel);
|
|
||||||
|
|
||||||
setContentPane(mainPanel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initReceivedMessagesPanel(JPanel mainPanel) {
|
|
||||||
receivedMessagesArea = new JTextArea();
|
|
||||||
receivedMessagesArea.setEditable(false);
|
|
||||||
receivedMessagesArea.setLineWrap(true);
|
|
||||||
receivedMessagesArea.setWrapStyleWord(true);
|
|
||||||
|
|
||||||
JScrollPane messagesScrollPane = new JScrollPane(receivedMessagesArea);
|
|
||||||
|
|
||||||
messagesScrollPane.setBorder(BorderFactory.createTitledBorder("Mensagens recebidas"));
|
|
||||||
|
|
||||||
mainPanel.add(messagesScrollPane, BorderLayout.CENTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initBottomPanel(JPanel mainPanel) {
|
|
||||||
messageField = new JTextField();
|
|
||||||
|
|
||||||
JPanel bottomPanel = new JPanel(new BorderLayout(10, 10));
|
|
||||||
|
|
||||||
bottomPanel.add(messageField, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
initButtons(bottomPanel);
|
|
||||||
|
|
||||||
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void initButtons(JPanel bottomPanel);
|
|
||||||
|
|
||||||
protected MpChatMessage createMessage(String tipoMensagem) {
|
|
||||||
try {
|
|
||||||
String nome = connectionManager.getUserName();
|
|
||||||
String ip = InetAddress.getLocalHost().getHostAddress();
|
|
||||||
String message = messageField.getText();
|
|
||||||
|
|
||||||
return new MpChatMessage(nome, ip, tipoMensagem, message);
|
|
||||||
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void receiveMessage(MpChatMessage msg) {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
String mensagem = String.format(
|
|
||||||
"[%s] %s (%s): %s%n",
|
|
||||||
msg.getTipoMensagem(),
|
|
||||||
msg.getNome(),
|
|
||||||
msg.getIp(),
|
|
||||||
msg.getMessage()
|
|
||||||
);
|
|
||||||
|
|
||||||
receivedMessagesArea.append(mensagem);
|
|
||||||
|
|
||||||
receivedMessagesArea.setCaretPosition(receivedMessagesArea.getDocument().getLength());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package GUI;
|
|
||||||
|
|
||||||
import control.ChatController;
|
|
||||||
import pitiupi.models.Menus.UnicastMenus.UnicastActionMenu;
|
|
||||||
import pitiupi.models.User;
|
|
||||||
import plugin.MultiProtocolChat;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
|
|
||||||
public class PrivateChatMenu extends UnicastActionMenu {
|
|
||||||
PrivateChatWindow privateChatWindow;
|
|
||||||
private final MultiProtocolChat multiProtocolChat;
|
|
||||||
private final ChatController chatController;
|
|
||||||
|
|
||||||
public PrivateChatMenu(PrivateChatWindow PrivateChatWindow, MultiProtocolChat multiProtocolChat, ChatController chatController) {
|
|
||||||
this.privateChatWindow = PrivateChatWindow;
|
|
||||||
this.multiProtocolChat = multiProtocolChat;
|
|
||||||
this.chatController = chatController;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(User user) {
|
|
||||||
if (multiProtocolChat.openConnection(user.getAddress())
|
|
||||||
else errorLabel.setVisible(true);
|
|
||||||
|
|
||||||
dialog.setContentPane(contentPanel);
|
|
||||||
dialog.pack();
|
|
||||||
dialog.setLocationRelativeTo(SwingUtilities.getWindowAncestor(privateChatWindow));
|
|
||||||
dialog.setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getText() {
|
|
||||||
return "start Private Chat";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package GUI;
|
|
||||||
|
|
||||||
import net.MpChatMessage;
|
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class PrivateChatWindow extends ChatWindow {
|
|
||||||
|
|
||||||
private final TcpConnection connection;
|
|
||||||
|
|
||||||
public PrivateChatWindow(ConnectionManager ConnectionManager, TcpConnection connection) {
|
|
||||||
super(ConnectionManager);
|
|
||||||
this.connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initButtons(JPanel bottomPanel) {
|
|
||||||
JButton tcpButton = new JButton("TCP");
|
|
||||||
|
|
||||||
tcpButton.addActionListener(e -> sendTCP());
|
|
||||||
|
|
||||||
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
|
||||||
|
|
||||||
buttonsPanel.add(tcpButton);
|
|
||||||
|
|
||||||
bottomPanel.add(buttonsPanel, BorderLayout.EAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendTCP() {
|
|
||||||
try {
|
|
||||||
MpChatMessage msg = createMessage("TCP Private");
|
|
||||||
connection.send(msg.toByteArray());
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.getLogger(PrivateChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package GUI;
|
|
||||||
|
|
||||||
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
|
|
||||||
|
|
||||||
public class PublicChatMenu extends MulticastActionMenu {
|
|
||||||
PublicChatWindow publicChatWindow;
|
|
||||||
|
|
||||||
PublicChatMenu(PublicChatWindow publicChatWindow) {
|
|
||||||
this.publicChatWindow = publicChatWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
publicChatWindow.setVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getText() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package GUI;
|
|
||||||
|
|
||||||
import net.MpChatMessage;
|
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class PublicChatWindow extends ChatWindow {
|
|
||||||
|
|
||||||
private JTextField ipField;
|
|
||||||
|
|
||||||
public PublicChatWindow(ConnectionManager connectionManager) {
|
|
||||||
super(connectionManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initButtons(JPanel bottomPanel) {
|
|
||||||
initIpField(bottomPanel);
|
|
||||||
initCommunicationButtons(bottomPanel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initIpField(JPanel bottomPanel) {
|
|
||||||
ipField = new JTextField();
|
|
||||||
ipField.setPreferredSize(new Dimension(130, 30));
|
|
||||||
|
|
||||||
JPanel ipPanel = new JPanel(new BorderLayout(5, 5));
|
|
||||||
|
|
||||||
ipPanel.add(new JLabel("IP destino:"), BorderLayout.WEST);
|
|
||||||
|
|
||||||
ipPanel.add(ipField, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
bottomPanel.add(ipPanel, BorderLayout.WEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initCommunicationButtons(JPanel bottomPanel) {
|
|
||||||
JButton tcpButton = new JButton("TCP");
|
|
||||||
JButton udpButton = new JButton("UDP");
|
|
||||||
JButton multicastButton = new JButton("Multicast");
|
|
||||||
|
|
||||||
tcpButton.addActionListener(e -> sendTCP());
|
|
||||||
udpButton.addActionListener(e -> sendUDP());
|
|
||||||
multicastButton.addActionListener(e -> sendMulticast());
|
|
||||||
|
|
||||||
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
|
||||||
|
|
||||||
buttonsPanel.add(tcpButton);
|
|
||||||
buttonsPanel.add(udpButton);
|
|
||||||
buttonsPanel.add(multicastButton);
|
|
||||||
|
|
||||||
bottomPanel.add(buttonsPanel, BorderLayout.EAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendTCP() {
|
|
||||||
try {
|
|
||||||
MpChatMessage msg = createMessage("TCP Unicast");
|
|
||||||
|
|
||||||
InetAddress ip = InetAddress.getByName(ipField.getText());
|
|
||||||
|
|
||||||
connectionManager.sendTcp(msg, ip);
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendUDP() {
|
|
||||||
try {
|
|
||||||
MpChatMessage msg = createMessage("UDP Unicast");
|
|
||||||
|
|
||||||
InetAddress ip = InetAddress.getByName(ipField.getText());
|
|
||||||
|
|
||||||
connectionManager.sendUdpUnicast(msg, ip);
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMulticast() {
|
|
||||||
try {
|
|
||||||
MpChatMessage msg = createMessage("UDP Multicast");
|
|
||||||
|
|
||||||
connectionManager.sendUdpMulticast(msg);
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.getLogger(PublicChatWindow.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
plugin.MultiProtocolChat
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package control;
|
|
||||||
|
|
||||||
import GUI.ChatMenu;
|
|
||||||
import GUI.ChatWindow;
|
|
||||||
import GUI.PrivateChatWindow;
|
|
||||||
import GUI.PublicChatWindow;
|
|
||||||
import net.MpChatMessage;
|
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
import pitiupi.control.PeerListener;
|
|
||||||
import pitiupi.models.User;
|
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ChatController implements PeerListener {
|
|
||||||
ConnectionManager connectionManager;
|
|
||||||
ChatMenu chatMenu;
|
|
||||||
|
|
||||||
PublicChatWindow publicChatWindow;
|
|
||||||
|
|
||||||
private final HashMap<TcpConnection, ChatWindow> chatWindows;
|
|
||||||
|
|
||||||
public ChatController(ConnectionManager connectionManager, ChatMenu chatMenu) {
|
|
||||||
this.connectionManager = connectionManager;
|
|
||||||
publicChatWindow = new PublicChatWindow(connectionManager);
|
|
||||||
|
|
||||||
chatMenus = new HashMap<>();
|
|
||||||
chatWindows = new HashMap<>();
|
|
||||||
|
|
||||||
this.chatMenu = chatMenu;
|
|
||||||
|
|
||||||
this.chatMenu.newMenuItem(new JMenuItem("Show global chat"), publicChatWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addChatWindow(TcpConnection tcpConnection) {
|
|
||||||
if (chatWindows.containsKey(tcpConnection)) return;
|
|
||||||
|
|
||||||
PrivateChatWindow newChat = new PrivateChatWindow(connectionManager, tcpConnection);
|
|
||||||
chatWindows.put(tcpConnection, newChat);
|
|
||||||
JMenuItem newMenuItem = new JMenuItem();
|
|
||||||
chatMenus.put(tcpConnection, newMenuItem);
|
|
||||||
newMenuItem.setText(tcpConnection.getAddress().toString());
|
|
||||||
chatMenu.newMenuItem(newMenuItem, newChat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeChatWindow(TcpConnection tcpConnection) {
|
|
||||||
ChatWindow removedWindow = chatWindows.remove(tcpConnection);
|
|
||||||
JMenuItem removedMenu = chatMenus.remove(tcpConnection);
|
|
||||||
|
|
||||||
chatMenu.removeMenuItem(removedMenu);
|
|
||||||
removedWindow.setVisible(false);
|
|
||||||
removedWindow.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void receivePublic(MpChatMessage message) {
|
|
||||||
publicChatWindow.receiveMessage(message);
|
|
||||||
}
|
|
||||||
public void receivePrivate(TcpConnection tcpConnection, MpChatMessage message) {
|
|
||||||
chatWindows.get(tcpConnection).receiveMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void openChatWindow(InetAddress address) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPeersChanged(List<User> list) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package net;
|
|
||||||
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
|
|
||||||
public class MpChatMessage extends Message {
|
|
||||||
private String nome;
|
|
||||||
private String ip;
|
|
||||||
private String tipoMensagem;
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
public MpChatMessage(String nome, String ip, String tipoMensagem, String message) {
|
|
||||||
this.nome = nome;
|
|
||||||
this.ip = ip;
|
|
||||||
this.tipoMensagem = tipoMensagem;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MpChatMessage(){}
|
|
||||||
|
|
||||||
public String getNome() {
|
|
||||||
return nome;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIp() {
|
|
||||||
return ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTipoMensagem() {
|
|
||||||
return tipoMensagem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package plugin;
|
|
||||||
|
|
||||||
import GUI.ChatMenu;
|
|
||||||
import control.ChatController;
|
|
||||||
import net.MpChatMessage;
|
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
import pitiupi.control.GuiManager;
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
|
||||||
import pitiupi.models.Plugin;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
|
|
||||||
public class MultiProtocolChat extends Plugin {
|
|
||||||
ChatController chatController;
|
|
||||||
ConnectionManager connectionManager;
|
|
||||||
GuiManager guiManager;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "MultiProtocol chat";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthor() {
|
|
||||||
return "Gustavo Henrique";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getVersion() {
|
|
||||||
return "0";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getIdentifier() {
|
|
||||||
return "Multi.Protocol.Chat.Pitiupi";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
|
||||||
this.connectionManager = connectionManager;
|
|
||||||
this.guiManager = guiManager;
|
|
||||||
|
|
||||||
ChatMenu chatMenu = new ChatMenu(this);
|
|
||||||
chatController = new ChatController(connectionManager, chatMenu);
|
|
||||||
guiManager.addMulticastMenu(chatMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message getMessage() {
|
|
||||||
return new MpChatMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receiveMessage(byte[] message) {
|
|
||||||
MpChatMessage mpChatMessage = parseMessage(message);
|
|
||||||
chatController.receivePublic(mpChatMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean receivePrivateConnection(TcpConnection connection) {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
chatController.addChatWindow(connection);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean receiveBytes(TcpConnection connection, byte[] bytes) {
|
|
||||||
MpChatMessage mpChatMessage = parseMessage(bytes);
|
|
||||||
chatController.receivePrivate(connection, mpChatMessage);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSocketClosed(TcpConnection connection) {
|
|
||||||
chatController.removeChatWindow(connection);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean openConnection(String ip) {
|
|
||||||
try {
|
|
||||||
connectionManager.connectPluginTcp(InetAddress.getByName(ip), getIdentifier(), getIdentifier());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
System.out.println("Error opening connection: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MpChatMessage parseMessage(byte[] bytes) {
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
|
||||||
ObjectInputStream ois;
|
|
||||||
try {
|
|
||||||
ois = new ObjectInputStream(bis);
|
|
||||||
if (ois.readObject() instanceof MpChatMessage msg) {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
} catch (IOException | ClassNotFoundException ex) {
|
|
||||||
System.out.println(ex.toString());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="AntConfiguration">
|
|
||||||
<buildFile url="file://$PROJECT_DIR$/build.xml" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-3
@@ -1,3 +0,0 @@
|
|||||||
<project version="4">
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK" />
|
|
||||||
</project>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
+14
-68
@@ -5,10 +5,14 @@
|
|||||||
</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 afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/META-INF/services/pitiupi.plugin.Plugin" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/GUI/QuizWindow.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/.idea/misc.xml" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/control/Pergunta.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java" beforeDir="false" afterPath="$PROJECT_DIR$/../pitiupi/src/pitiupi/net/tcp/ConnectionFactory.java" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/control/Quiz.java" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/net/QuizMessage.java" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/../pitiupi.quiz/src/pitiupi/plugin/Quiz.java" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pitiupi/screenCast/net/ScreenCastMessage.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/pitiupi/screenCast/net/ScreenCastMessage.java" afterDir="false" />
|
||||||
</list>
|
</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" />
|
||||||
@@ -18,9 +22,9 @@
|
|||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectColorInfo">{
|
<component name="ProjectColorInfo"><![CDATA[{
|
||||||
"associatedIndex": 7
|
"associatedIndex": 7
|
||||||
}</component>
|
}]]></component>
|
||||||
<component name="ProjectId" id="3Gs0wFaRD4HyhP9VXLUWBeq0qTL" />
|
<component name="ProjectId" id="3Gs0wFaRD4HyhP9VXLUWBeq0qTL" />
|
||||||
<component name="ProjectViewState">
|
<component name="ProjectViewState">
|
||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
@@ -30,72 +34,17 @@
|
|||||||
"keyToString": {
|
"keyToString": {
|
||||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"git-widget-placeholder": "QuizPlugin",
|
||||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
|
||||||
"git-widget-placeholder": "feature/separacao-gerenciadores",
|
|
||||||
"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/extre/OneDrive/Documentos/Faculdade/2026_02/Monitoria/Pitiupi/pitiupi.screenCast",
|
||||||
"project.structure.last.edited": "Modules",
|
|
||||||
"project.structure.proportion": "0.15",
|
|
||||||
"project.structure.side.proportion": "0.2",
|
|
||||||
"settings.editor.selected.configurable": "preferences.keymap"
|
"settings.editor.selected.configurable": "preferences.keymap"
|
||||||
}
|
}
|
||||||
}]]></component>
|
}]]></component>
|
||||||
<component name="RunManager">
|
|
||||||
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
|
|
||||||
<module name="pitiupi.screenCast" />
|
|
||||||
<option name="ENV_FILES" value="" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<envs>
|
|
||||||
<env name="PYTHONUNBUFFERED" value="1" />
|
|
||||||
</envs>
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
|
||||||
<option name="IS_MODULE_SDK" value="false" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<option name="SCRIPT_NAME" value="" />
|
|
||||||
<option name="PARAMETERS" value="" />
|
|
||||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
|
||||||
<option name="EMULATE_TERMINAL" value="false" />
|
|
||||||
<option name="MODULE_MODE" value="false" />
|
|
||||||
<option name="REDIRECT_INPUT" value="false" />
|
|
||||||
<option name="INPUT_FILE" value="" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration default="true" type="Tox" factoryName="Tox">
|
|
||||||
<module name="pitiupi.screenCast" />
|
|
||||||
<option name="ENV_FILES" value="" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
|
||||||
<option name="IS_MODULE_SDK" value="false" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration default="true" type="JetRunConfigurationType">
|
|
||||||
<module name="pitiupi.screenCast" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="Make" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
|
|
||||||
<module name="pitiupi.screenCast" />
|
|
||||||
<option name="filePath" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
<component name="SharedIndexes">
|
<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.54" />
|
||||||
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-IU-261.26222.65" />
|
|
||||||
</set>
|
</set>
|
||||||
</attachedChunks>
|
</attachedChunks>
|
||||||
</component>
|
</component>
|
||||||
@@ -109,7 +58,4 @@
|
|||||||
</task>
|
</task>
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
|
||||||
<option name="version" value="3" />
|
|
||||||
</component>
|
|
||||||
</project>
|
</project>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#Tue, 29 Sep 2026 11:04:58 -0300
|
#Sat, 18 Jul 2026 10:33:07 -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.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="JAVA_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="module-library">
|
|
||||||
<library>
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MODULE_DIR$/../../pitiupi/dist/pitiupi.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</orderEntry>
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -5,8 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.screenCast.GUI;
|
package pitiupi.screenCast.GUI;
|
||||||
|
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
import pitiupi.control.GuiManager;
|
|
||||||
import pitiupi.screenCast.control.AlarmClock;
|
import pitiupi.screenCast.control.AlarmClock;
|
||||||
import pitiupi.screenCast.control.ImageTransformation;
|
import pitiupi.screenCast.control.ImageTransformation;
|
||||||
import pitiupi.screenCast.control.ScreenShot;
|
import pitiupi.screenCast.control.ScreenShot;
|
||||||
@@ -40,13 +38,10 @@ 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 MainWindow mainWindow;
|
||||||
private final GuiManager guiManager;
|
|
||||||
|
|
||||||
public ScreenCastWindow(ConnectionManager connectionManager, GuiManager guiManager) {
|
|
||||||
this.connectionManager = connectionManager;
|
|
||||||
this.guiManager = guiManager;
|
|
||||||
|
|
||||||
|
public ScreenCastWindow(MainWindow mainWindow) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
this.compressRatio = 0.5f;
|
this.compressRatio = 0.5f;
|
||||||
this.initComponents();
|
this.initComponents();
|
||||||
}
|
}
|
||||||
@@ -96,12 +91,19 @@ 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(this.mainWindow.getUserName(), byteImage);
|
||||||
|
msg = message.toByteArray();
|
||||||
if (!connectionManager.sendUdpMulticast(message)) this.compressRatio -= 0.1f;
|
|
||||||
} catch (AWTException | IOException ex) {
|
} catch (AWTException | IOException ex) {
|
||||||
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(AlarmClock.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
|
if (msg != null) {
|
||||||
|
try {
|
||||||
|
this.mainWindow.getSocket().send(msg);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
this.compressRatio -= 0.1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receiveScreenCastMessage(ScreenCastMessage message) {
|
public void receiveScreenCastMessage(ScreenCastMessage message) {
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import pitiupi.GUI.MainWindow;
|
import pitiupi.GUI.MainWindow;
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
import pitiupi.control.GuiManager;
|
|
||||||
import pitiupi.net.Message;
|
import pitiupi.net.Message;
|
||||||
import pitiupi.plugin.Plugin;
|
import pitiupi.plugin.Plugin;
|
||||||
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
import pitiupi.screenCast.GUI.ScreenCastMenu;
|
||||||
@@ -23,6 +21,7 @@ import pitiupi.screenCast.net.ScreenCastMessage;
|
|||||||
public class ScreenCastPlugin implements Plugin {
|
public class ScreenCastPlugin implements Plugin {
|
||||||
|
|
||||||
private ScreenCastWindow screenCastWindow;
|
private ScreenCastWindow screenCastWindow;
|
||||||
|
private MainWindow mainWindow;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -40,14 +39,10 @@ public class ScreenCastPlugin implements Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIdentifier() {
|
public void createPlugin(MainWindow mainWindow) {
|
||||||
return "Pitiupi.ScreenCast.Example";
|
this.mainWindow = mainWindow;
|
||||||
}
|
this.screenCastWindow = new ScreenCastWindow(mainWindow);
|
||||||
|
this.mainWindow.addMenu(new ScreenCastMenu(screenCastWindow));
|
||||||
@Override
|
|
||||||
public void createPlugin(ConnectionManager connectionManager, GuiManager guiManager) {
|
|
||||||
this.screenCastWindow = new ScreenCastWindow(connectionManager, guiManager);
|
|
||||||
guiManager.addMenu(new ScreenCastMenu(screenCastWindow));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -59,7 +54,9 @@ public class ScreenCastPlugin implements Plugin {
|
|||||||
if (ois.readObject() instanceof ScreenCastMessage msg) {
|
if (ois.readObject() instanceof ScreenCastMessage msg) {
|
||||||
this.screenCastWindow.receiveScreenCastMessage(msg);
|
this.screenCastWindow.receiveScreenCastMessage(msg);
|
||||||
}
|
}
|
||||||
} catch (IOException | ClassNotFoundException ex) {
|
} catch (IOException ex) {
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
} catch (ClassNotFoundException ex) {
|
||||||
System.out.println(ex.toString());
|
System.out.println(ex.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,3 @@
|
|||||||
|
|
||||||
# JARs gerados
|
# JARs gerados
|
||||||
*.jar
|
*.jar
|
||||||
|
|
||||||
.idea/
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_26_PREVIEW" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK">
|
<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" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
+79
-412
@@ -1,23 +1,21 @@
|
|||||||
# Visão geral
|
# Arquitetura da aplicação
|
||||||
|
|
||||||
O `Pitiupi` é uma plataforma P2P baseada em uma arquitetura de plugins,
|
## Visão geral
|
||||||
desenvolvida para permitir a criação de aplicações distribuídas sobre uma
|
|
||||||
|
O Share My Sheet é uma plataforma P2P baseada em uma arquitetura de plugins,
|
||||||
|
desenvolvida para permitir a criação de aplicações distribuídas sobre uma
|
||||||
infraestrutura comum de comunicação em rede.
|
infraestrutura comum de comunicação em rede.
|
||||||
|
|
||||||
A aplicação fornece mecanismos de descoberta de usuários, comunicação entre
|
A aplicação fornece mecanismos de descoberta de usuários, comunicação
|
||||||
peers, troca de mensagens serializadas e integração de extensões por meio de
|
multicast, troca de mensagens serializadas e integração de extensões por meio
|
||||||
plugins. A comunicação de rede utiliza dois protocolos de transporte: UDP,
|
de plugins. Dessa forma, novos comportamentos podem ser adicionados sem
|
||||||
empregado principalmente na descoberta e comunicação multicast entre as
|
modificar o núcleo da aplicação.
|
||||||
instâncias, e TCP, utilizado para comunicação direta e persistente entre peers.
|
|
||||||
|
|
||||||
Dessa forma, novos comportamentos podem ser adicionados por meio de plugins
|
O projeto foi desenvolvido como suporte às práticas da disciplina de Redes de
|
||||||
sem modificar o núcleo da aplicação.
|
Computadores, servindo também como base para experimentação de aplicações
|
||||||
|
|
||||||
O projeto foi desenvolvido como suporte às práticas da disciplina de Redes de
|
|
||||||
Computadores, servindo também como base para experimentação de aplicações
|
|
||||||
distribuídas e desenvolvimento de projetos acadêmicos.
|
distribuídas e desenvolvimento de projetos acadêmicos.
|
||||||
|
|
||||||
# Arquitetura geral
|
## Arquitetura geral
|
||||||
|
|
||||||
A aplicação é dividida em três grupos principais:
|
A aplicação é dividida em três grupos principais:
|
||||||
|
|
||||||
@@ -25,396 +23,103 @@ A aplicação é dividida em três grupos principais:
|
|||||||
sendo responsável por inicializar e integrar esses componentes durante a
|
sendo responsável por inicializar e integrar esses componentes durante a
|
||||||
execução.
|
execução.
|
||||||
- **Sistema de plugins**: responsável por adicionar funcionalidades sem
|
- **Sistema de plugins**: responsável por adicionar funcionalidades sem
|
||||||
alterar o núcleo da aplicação.
|
alterar o núcleo.
|
||||||
- **Comunicação de rede**: responsável pela troca de mensagens entre
|
- **Comunicação de rede**: responsável pela troca de mensagens entre
|
||||||
instâncias da aplicação.
|
instâncias da aplicação.
|
||||||
|
|
||||||
## Inicialização da aplicação
|
## Inicialização da aplicação
|
||||||
|
|
||||||
1. A MainWindow é criada.
|
1. A MainWindow é criada.
|
||||||
2. O SocketUDP é inicializado, associa-se à porta configurada e ingressa no
|
2. O Socket é inicializado e inicia a comunicação multicast.
|
||||||
grupo multicast.
|
3. O HeartbeatManager é criado e inicia o envio periódico de heartbeats.
|
||||||
3. O SocketTCP é inicializado e cria um ServerSocket na porta da aplicação
|
4. O painel de usuários online é registrado como listener do HeartbeatManager.
|
||||||
para aceitar conexões TCP.
|
5. O PluginLoader procura e carrega os plugins disponíveis.
|
||||||
4. Os mecanismos de comunicação iniciam seus respectivos processos de recepção.
|
|
||||||
5. O HeartbeatManager é criado e inicia o envio periódico de heartbeats.
|
|
||||||
6. O painel de usuários online é registrado como listener do HeartbeatManager.
|
|
||||||
7. O PluginLoader procura e carrega os plugins disponíveis.
|
|
||||||
|
|
||||||
# Sistema de comunicação
|
## Sistema de comunicação
|
||||||
|
|
||||||
A comunicação entre as instâncias da aplicação é realizada por meio de dois
|
### Socket
|
||||||
mecanismos complementares:
|
|
||||||
|
|
||||||
- **UDP multicast/unicast**, implementado por `SocketUDP`
|
A classe `Socket` é responsável pela comunicação de rede da aplicação.
|
||||||
- **TCP**, implementado por `SocketTCP` e `TcpConnection`
|
Ela utiliza comunicação multicast UDP para permitir que diferentes
|
||||||
|
instâncias da aplicação troquem mensagens sem a necessidade de um servidor
|
||||||
|
central.
|
||||||
|
|
||||||
Ambos os mecanismos utilizam mensagens serializadas em `byte[]`, permitindo que
|
A classe estende `Thread`, pois mantém um processo contínuo de recepção de
|
||||||
os plugins permaneçam independentes dos detalhes do protocolo de transporte.
|
mensagens enquanto a aplicação continua executando suas demais atividades.
|
||||||
|
|
||||||
## Comunicação UDP
|
Durante sua inicialização, o socket:
|
||||||
|
- obtém o endereço do grupo multicast;
|
||||||
|
- cria um `MulticastSocket` utilizando a porta configurada;
|
||||||
|
- configura os buffers de comunicação;
|
||||||
|
- entra no grupo multicast.
|
||||||
|
|
||||||
A classe `SocketUDP` é responsável pela comunicação UDP da aplicação.
|
#### Envio de mensagens
|
||||||
|
|
||||||
Ela utiliza um `MulticastSocket` associado à porta configurada pela aplicação e
|
O método `send()` recebe uma mensagem já serializada em bytes e cria um
|
||||||
ingressa no grupo multicast `224.0.0.3`. Por ser baseada em UDP, a comunicação não
|
`DatagramPacket`, enviando-o para o grupo multicast.
|
||||||
estabelece uma conexão permanente entre os peers.
|
|
||||||
|
|
||||||
A classe estende `Thread`, mantendo um processo contínuo de recepção de datagramas
|
De forma geral, o socket atua apenas como mecanismo de transporte das mensagens.
|
||||||
enquanto a aplicação está em execução.
|
A única exceção é o tratamento de HeartbeatMessage, utilizado pela infraestrutura
|
||||||
|
da aplicação para manter a lista de peers ativos.
|
||||||
|
|
||||||
O SocketUDP suporta dois modos de envio:
|
#### Recepção de mensagens
|
||||||
|
|
||||||
- **multicast**, destinado ao grupo de peers
|
O método `receive()` mantém um loop aguardando novas mensagens multicast.
|
||||||
- **unicast**, destinado diretamente ao endereço IP de um peer específico
|
|
||||||
|
|
||||||
### Inicialização
|
Ao receber uma mensagem:
|
||||||
|
1. Os dados são analisados para verificar se correspondem a um
|
||||||
|
`HeartbeatMessage`.
|
||||||
|
2. Caso seja um heartbeat, a mensagem é encaminhada ao `HeartbeatManager`
|
||||||
|
para atualização dos usuários ativos.
|
||||||
|
3. Caso contrário, a mensagem é enviada para todos os plugins carregados
|
||||||
|
através do método `receiveMessage()`.
|
||||||
|
|
||||||
Durante sua inicialização, o `SocketUDP`:
|
Esse comportamento permite que a aplicação principal trate mensagens de
|
||||||
|
infraestrutura (como heartbeat), enquanto mensagens específicas de plugins
|
||||||
1. obtém o endereço do grupo multicast;
|
são processadas pelas extensões correspondentes.
|
||||||
2. cria um `MulticastSocket` utilizando a porta configurada na `MainWindow`;
|
|
||||||
3. configura os buffers de envio e recepção;
|
|
||||||
4. habilita o reuso do endereço;
|
|
||||||
5. ingressa no grupo multicast.
|
|
||||||
|
|
||||||
O tamanho dos buffers de comunicação é configurado para `256000` bytes.
|
|
||||||
|
|
||||||
### Envio de mensagens UDP
|
|
||||||
|
|
||||||
O `SocketUDP` suporta dois modos de envio de mensagens: multicast e unicast, ambos
|
|
||||||
baseados no mesmo mecanismo interno de criação e envio de DatagramPacket.
|
|
||||||
|
|
||||||
O método `sendMulticast()` envia uma mensagem para o grupo multicast da aplicação.
|
|
||||||
Nesse caso, o `DatagramPacket` é direcionado ao endereço multicast configurado,
|
|
||||||
permitindo que todas as instâncias participantes do grupo recebam a mensagem
|
|
||||||
simultaneamente.
|
|
||||||
|
|
||||||
Já o método `sendUnicast()` permite enviar uma mensagem diretamente para um peer
|
|
||||||
específico. Nesse caso, o `DatagramPacket` é direcionado ao endereço IP informado pelo
|
|
||||||
chamador, mantendo a mesma porta utilizada pela aplicação.
|
|
||||||
|
|
||||||
Ambos os métodos são, na prática, abstrações de um único mecanismo interno de envio,
|
|
||||||
que utiliza o mesmo processo de criação e envio de `DatagramPacket` por baixo dos
|
|
||||||
panos. A diferença entre eles está apenas no endereço de destino: enquanto o multicast
|
|
||||||
utiliza o grupo compartilhado, o unicast direciona o pacote a um IP específico.
|
|
||||||
|
|
||||||
### Recepção de mensagens
|
|
||||||
|
|
||||||
O método `receive()` mantém um loop aguardando novos datagramas.
|
|
||||||
|
|
||||||
Ao receber uma mensagem, o `SocketUDP` inicialmente tenta identificar se os dados
|
|
||||||
correspondem a uma `HeartbeatMessage`.
|
|
||||||
|
|
||||||
O processamento segue o seguinte fluxo:
|
|
||||||
|
|
||||||
1. O datagrama é recebido pelo `MulticastSocket`.
|
|
||||||
2. Os dados recebidos são analisados pelo método `tryParseHeartbeat()`.
|
|
||||||
3. Caso seja identificada uma `HeartbeatMessage`, ela é encaminhada ao
|
|
||||||
`HeartbeatManager`, juntamente com o endereço IP de origem.
|
|
||||||
4. Caso não seja um heartbeat, a mensagem é encaminhada para todos os plugins
|
|
||||||
carregados.
|
|
||||||
5. Cada plugin decide se a mensagem recebida pertence à sua funcionalidade.
|
|
||||||
|
|
||||||
Essa separação mantém as mensagens de infraestrutura, como heartbeat, sob
|
|
||||||
responsabilidade do núcleo da aplicação, enquanto as mensagens específicas das
|
|
||||||
funcionalidades são processadas pelos plugins.
|
|
||||||
|
|
||||||
### Identificação de mensagens de heartbeat
|
|
||||||
|
|
||||||
O método `tryParseHeartbeat()` tenta desserializar os dados recebidos e verificar se o
|
|
||||||
objeto resultante é uma instância de `HeartbeatMessage`.
|
|
||||||
|
|
||||||
Caso a mensagem não possa ser desserializada ou não corresponda ao tipo esperado, o
|
|
||||||
método retorna `null`. Nesse caso, a mensagem é tratada como uma mensagem destinada aos
|
|
||||||
plugins.
|
|
||||||
|
|
||||||
### Encerramento
|
|
||||||
|
|
||||||
O método `close()` interrompe a execução do processo de recepção, remove a aplicação
|
|
||||||
do grupo multicast e fecha o `MulticastSocket`.
|
|
||||||
|
|
||||||
A variável `running` é utilizada para sinalizar que o processo de recepção deve ser
|
|
||||||
encerrado.
|
|
||||||
|
|
||||||
## Comunicação TCP
|
|
||||||
|
|
||||||
A comunicação TCP é implementada pela classe `SocketTCP`.
|
|
||||||
|
|
||||||
Diferentemente do UDP, o TCP estabelece conexões entre dois peers. Essas conexões
|
|
||||||
são mantidas enquanto forem necessárias e podem ser reutilizadas para o envio de
|
|
||||||
múltiplas mensagens.
|
|
||||||
|
|
||||||
A arquitetura utiliza uma conexão TCP persistente por endereço de peer.
|
|
||||||
|
|
||||||
O SocketTCP possui um mapa de conexões:
|
|
||||||
|
|
||||||
`InetAddress` -> `TcpConnection`
|
|
||||||
|
|
||||||
Esse mapa permite localizar rapidamente uma conexão existente para determinado peer e
|
|
||||||
reutilizá-la durante novos envios.
|
|
||||||
|
|
||||||
### SocketTCP
|
|
||||||
|
|
||||||
A classe `SocketTCP` estende `Thread` e implementa `PeerListener`.
|
|
||||||
|
|
||||||
Ao ser criada, ela inicializa um `ServerSocket` utilizando a porta configurada na
|
|
||||||
aplicação.
|
|
||||||
|
|
||||||
O servidor TCP permanece aguardando novas conexões enquanto a aplicação estiver
|
|
||||||
em execução.
|
|
||||||
|
|
||||||
### Envio de mensagens TCP
|
|
||||||
|
|
||||||
O método `send()` recebe uma mensagem serializada e o endereço IP do peer destinatário.
|
|
||||||
|
|
||||||
Antes de enviar a mensagem, o `SocketTCP` verifica se já existe uma conexão válida com
|
|
||||||
o peer.
|
|
||||||
|
|
||||||
O comportamento é:
|
|
||||||
|
|
||||||
1. Procurar uma conexão existente no mapa de conexões.
|
|
||||||
2. Verificar se a conexão está fechada.
|
|
||||||
3. Caso não exista uma conexão válida, criar um novo Socket TCP para o endereço do peer.
|
|
||||||
4. Encapsular o socket em uma `TcpConnection`.
|
|
||||||
5. Registrar a conexão no mapa.
|
|
||||||
6. Iniciar uma tarefa de recepção para essa conexão.
|
|
||||||
7. Enviar a mensagem pela conexão.
|
|
||||||
|
|
||||||
Assim, uma conexão TCP existente pode ser reutilizada para várias mensagens, evitando
|
|
||||||
a criação de um novo socket para cada envio.
|
|
||||||
|
|
||||||
### Recepção de conexões
|
|
||||||
|
|
||||||
O método `receive()` permanece aguardando novas conexões através de
|
|
||||||
`ServerSocket.accept()`.
|
|
||||||
|
|
||||||
Quando uma conexão é aceita:
|
|
||||||
|
|
||||||
O endereço IP do peer é obtido.
|
|
||||||
Um objeto `TcpConnection` é criado para encapsular o socket.
|
|
||||||
A conexão é registrada no mapa de conexões.
|
|
||||||
Caso já exista uma conexão associada ao mesmo endereço, a conexão anterior é encerrada.
|
|
||||||
Uma tarefa independente de recepção é criada para a nova conexão.
|
|
||||||
|
|
||||||
Esse modelo permite que o servidor aceite múltiplas conexões sem bloquear o
|
|
||||||
processamento das conexões já existentes.
|
|
||||||
|
|
||||||
### Recepção concorrente
|
|
||||||
|
|
||||||
A recepção das mensagens TCP é realizada de maneira concorrente por meio de um
|
|
||||||
`ExecutorService`.
|
|
||||||
|
|
||||||
Cada conexão possui uma tarefa própria responsável por receber continuamente as
|
|
||||||
mensagens.
|
|
||||||
|
|
||||||
O fluxo de recepção é:
|
|
||||||
|
|
||||||
```
|
|
||||||
SocketTCP
|
|
||||||
|
|
|
||||||
+-- Peer A → TcpConnection → tarefa de recepção
|
|
||||||
|
|
|
||||||
+-- Peer B → TcpConnection → tarefa de recepção
|
|
||||||
|
|
|
||||||
+-- Peer C → TcpConnection → tarefa de recepção
|
|
||||||
```
|
|
||||||
|
|
||||||
Dessa forma, uma conexão bloqueada ou aguardando dados não impede que outras conexões
|
|
||||||
continuem sendo processadas.
|
|
||||||
|
|
||||||
Quando uma mensagem é recebida por uma `TcpConnection`, ela é encaminhada para todos os
|
|
||||||
plugins registrados na `MainWindow`.
|
|
||||||
|
|
||||||
O plugin responsável pela funcionalidade deve identificar o tipo da mensagem e
|
|
||||||
realizar o processamento correspondente.
|
|
||||||
|
|
||||||
### TcpConnection
|
|
||||||
|
|
||||||
A classe TcpConnection encapsula uma conexão TCP individual entre dois peers.
|
|
||||||
|
|
||||||
Ela mantém:
|
|
||||||
|
|
||||||
o Socket utilizado na conexão;
|
|
||||||
um DataInputStream para recepção;
|
|
||||||
um DataOutputStream para envio.
|
|
||||||
|
|
||||||
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
|
|
||||||
|
|
||||||
### Framing das mensagens TCP
|
|
||||||
|
|
||||||
A classe `TcpConnection` encapsula uma conexão TCP individual entre dois peers.
|
|
||||||
|
|
||||||
Ela mantém:
|
|
||||||
|
|
||||||
1. o `Socket` utilizado na conexão;
|
|
||||||
2. um `DataInputStream` para recepção;
|
|
||||||
3. um `DataOutputStream` para envio.
|
|
||||||
|
|
||||||
Além de encapsular esses recursos, a classe é responsável pelo framing das mensagens.
|
|
||||||
|
|
||||||
Framing das mensagens TCP
|
|
||||||
|
|
||||||
O protocolo TCP fornece um fluxo contínuo de bytes e não preserva os limites das
|
|
||||||
mensagens enviadas pela aplicação.
|
|
||||||
|
|
||||||
Por esse motivo, é adicionado o tamanho da mensagem antes do conteúdo.
|
|
||||||
|
|
||||||
O formato utilizado é:
|
|
||||||
|
|
||||||
```
|
|
||||||
+----------------------+----------------------+
|
|
||||||
| tamanho da mensagem | dados da mensagem |
|
|
||||||
+----------------------+----------------------+
|
|
||||||
4 bytes N bytes
|
|
||||||
```
|
|
||||||
|
|
||||||
O tamanho é enviado como um `int`, seguido pelos bytes da mensagem.
|
|
||||||
|
|
||||||
No envio, o método `TcpConnection.send()`:
|
|
||||||
|
|
||||||
1. obtém o tamanho do vetor de bytes;
|
|
||||||
2. escreve o tamanho no `DataOutputStream`;
|
|
||||||
3. escreve os dados da mensagem;
|
|
||||||
4. realiza `flush()` no stream.
|
|
||||||
|
|
||||||
O acesso ao stream de saída é sincronizado para evitar que envios concorrentes
|
|
||||||
misturem seus dados.
|
|
||||||
|
|
||||||
Na recepção, o método `receive()`:
|
|
||||||
|
|
||||||
1. lê o tamanho da mensagem;
|
|
||||||
2. verifica se o tamanho é válido;
|
|
||||||
3. lê a quantidade correspondente de bytes;
|
|
||||||
4. retorna o vetor de bytes completo.
|
|
||||||
|
|
||||||
Esse mecanismo permite que várias mensagens sejam transmitidas pela mesma conexão
|
|
||||||
TCP sem que o receptor perca a delimitação entre elas.
|
|
||||||
|
|
||||||
### Integração entre TCP e gerenciamento de peers
|
|
||||||
|
|
||||||
O `SocketTCP` implementa a interface PeerListener para acompanhar as alterações na
|
|
||||||
lista de peers ativos.
|
|
||||||
|
|
||||||
O `HeartbeatManager` mantém a lista de peers atualmente detectados na rede. Quando
|
|
||||||
essa lista é alterada, o SocketTCP recebe a notificação por meio do método
|
|
||||||
`onPeersChanged()`.
|
|
||||||
|
|
||||||
O método obtém os endereços IP dos peers atualmente ativos e compara esses endereços
|
|
||||||
com as conexões TCP existentes.
|
|
||||||
|
|
||||||
Quando uma conexão pertence a um peer que não está mais ativo:
|
|
||||||
|
|
||||||
a conexão é removida do mapa;
|
|
||||||
a conexão TCP é encerrada;
|
|
||||||
os recursos associados são liberados.
|
|
||||||
|
|
||||||
Esse mecanismo impede que conexões TCP permaneçam abertas indefinidamente para peers
|
|
||||||
que já deixaram de participar da rede.
|
|
||||||
|
|
||||||
O fluxo de integração pode ser representado da seguinte forma:
|
|
||||||
```
|
|
||||||
HeartbeatManager
|
|
||||||
|
|
|
||||||
| peers ativos alterados
|
|
||||||
|
|
|
||||||
v
|
|
||||||
SocketTCP.onPeersChanged()
|
|
||||||
|
|
|
||||||
| compara peers ativos
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+---- peer ativo ------> mantém conexão
|
|
||||||
|
|
|
||||||
+---- peer inativo ----> fecha conexão
|
|
||||||
```
|
|
||||||
|
|
||||||
### Message
|
### Message
|
||||||
|
|
||||||
A classe `Message` representa a estrutura base das mensagens trocadas pela aplicação.
|
A classe `Message` representa a estrutura base das mensagens trocadas pela
|
||||||
|
aplicação.
|
||||||
|
|
||||||
Todas as mensagens utilizadas pelo sistema devem herdar dessa classe. Ela implementa
|
Todas as mensagens utilizadas pelo sistema devem herdar dessa classe. Ela
|
||||||
`Serializable`, permitindo que objetos de mensagem sejam convertidos em vetores de
|
implementa `Serializable`, permitindo que objetos de mensagem sejam
|
||||||
bytes para transmissão pela rede.
|
convertidos em vetores de bytes para transmissão pela rede.
|
||||||
|
|
||||||
A serialização é realizada pelo método `toByteArray()`, que transforma uma instância
|
A serialização é realizada pelo método `toByteArray()`, que transforma uma
|
||||||
da mensagem em uma representação binária enviada pelos mecanismos de comunicação.
|
instância da mensagem em uma representação binária enviada pelo `Socket`.
|
||||||
|
|
||||||
As mensagens específicas devem estender essa classe, adicionando os atributos e
|
Mensagens específicas devem estender essa classe adicionando os atributos e
|
||||||
comportamentos necessários para cada funcionalidade.
|
comportamentos necessários para cada funcionalidade.
|
||||||
|
|
||||||
A camada de transporte não depende do conteúdo específico das mensagens. Tanto o
|
|
||||||
UDP quanto o TCP recebem mensagens serializadas como `byte[]`.
|
|
||||||
|
|
||||||
### Heartbeat
|
### Heartbeat
|
||||||
|
|
||||||
O mecanismo de heartbeat é utilizado para identificar quais usuários estão ativos
|
O mecanismo de heartbeat é utilizado para identificar quais usuários estão
|
||||||
na rede.
|
ativos na rede.
|
||||||
|
|
||||||
A aplicação envia periodicamente uma `HeartbeatMessage` contendo informações do usuário,
|
A aplicação envia periodicamente uma `HeartbeatMessage` contendo
|
||||||
um identificador único da instância da aplicação e um timestamp.
|
informações do usuário, um identificador único da instância da aplicação
|
||||||
|
e um timestamp. Quando outra instância recebe essa mensagem, o `HeartbeatManager`
|
||||||
|
registra ou atualiza o peer correspondente, utilizando o identificador
|
||||||
|
da instância para diferenciá-lo das demais.
|
||||||
|
|
||||||
Essas mensagens são transmitidas utilizando a comunicação UDP multicast.
|
Um peer é considerado inativo quando permanece mais de 15 segundos
|
||||||
|
sem receber um novo heartbeat. O `HeartbeatManager` então remove o peer
|
||||||
|
da lista e notifica os listeners sobre a alteração.
|
||||||
|
|
||||||
Quando outra instância recebe uma mensagem de heartbeat, o `SocketUDP` identifica
|
O fluxo de heartbeat é independente das mensagens dos plugins:
|
||||||
o tipo da mensagem e encaminha o objeto ao `HeartbeatManager`, juntamente com o endereço
|
|
||||||
IP de origem.
|
|
||||||
|
|
||||||
O `HeartbeatManager` registra ou atualiza o peer correspondente, utilizando o
|
1. `HeartbeatManager` cria uma `HeartbeatMessage`.
|
||||||
identificador da instância para diferenciá-lo das demais.
|
2. A mensagem é serializada utilizando `Message.toByteArray()`.
|
||||||
|
3. O `Socket` transmite a mensagem pela rede multicast.
|
||||||
Um peer é considerado inativo quando permanece mais de 15 segundos sem receber um novo
|
4. O `Socket` identifica mensagens de heartbeat recebidas e encaminha ao
|
||||||
heartbeat. O `HeartbeatManager` então remove o peer da lista e notifica todos os
|
`HeartbeatManager`.
|
||||||
componentes registrados como listeners de mudanças de peers.
|
5. O `HeartbeatManager` atualiza a lista de peers ativos e notifica os
|
||||||
|
listeners.
|
||||||
Esses listeners podem incluir diferentes partes da aplicação, como componentes
|
|
||||||
da interface gráfica, mecanismos de comunicação (como o `SocketTCP`) e também plugins
|
|
||||||
que desejem reagir à presença ou ausência de usuários na rede.
|
|
||||||
|
|
||||||
Dessa forma, o sistema permite que tanto o núcleo da aplicação quanto extensões
|
|
||||||
externas sejam notificados sobre alterações no estado da rede, mantendo a arquitetura
|
|
||||||
flexível e extensível.
|
|
||||||
|
|
||||||
O fluxo completo é:
|
|
||||||
|
|
||||||
```
|
|
||||||
HeartbeatManager
|
|
||||||
|
|
|
||||||
| cria HeartbeatMessage
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Message.toByteArray()
|
|
||||||
|
|
|
||||||
v
|
|
||||||
SocketUDP.sendMulticast()
|
|
||||||
|
|
|
||||||
v
|
|
||||||
UDP Multicast
|
|
||||||
|
|
|
||||||
v
|
|
||||||
SocketUDP.receive()
|
|
||||||
|
|
|
||||||
v
|
|
||||||
HeartbeatManager.receiveHeartbeat()
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Lista de peers ativos
|
|
||||||
|
|
|
||||||
+----> Interface de usuários online
|
|
||||||
|
|
|
||||||
+----> SocketTCP (PeerListener)
|
|
||||||
|
|
|
||||||
+----> Plugins (PeerListener)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Sistema de plugins
|
## Sistema de plugins
|
||||||
|
|
||||||
O sistema de plugins é responsável por permitir a extensão das funcionalidades da
|
|
||||||
aplicação de forma modular, sem a necessidade de alterações no núcleo do sistema.
|
|
||||||
Ele define um contrato comum que deve ser seguido por todas as extensões, garantindo
|
|
||||||
que possam ser carregadas dinamicamente e integradas à comunicação e à interface da
|
|
||||||
aplicação.
|
|
||||||
|
|
||||||
### Interface Plugin
|
### Interface Plugin
|
||||||
|
|
||||||
A interface `Plugin` define o contrato que deve ser implementado por qualquer
|
A interface `Plugin` define o contrato que deve ser implementado por qualquer
|
||||||
@@ -468,10 +173,10 @@ MeusPlugins
|
|||||||
|
|
|
|
||||||
+---services
|
+---services
|
||||||
|
|
|
|
||||||
+---pitiupi.models.Plugin
|
+---pitiupi.plugin.Plugin
|
||||||
```
|
```
|
||||||
|
|
||||||
O arquivo `pitiupi.models.Plugin` deve conter o nome completo das classes que
|
O arquivo `pitiupi.plugin.Plugin` deve conter o nome completo das classes que
|
||||||
implementam a interface Plugin:
|
implementam a interface Plugin:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -481,7 +186,7 @@ MeusPlugins.plugins.NomeDaClasse
|
|||||||
Para serem carregados, os plugins devem estar empacotados como arquivos JAR
|
Para serem carregados, os plugins devem estar empacotados como arquivos JAR
|
||||||
e colocados no diretório plugins da aplicação.
|
e colocados no diretório plugins da aplicação.
|
||||||
|
|
||||||
### Carregamento de plugins
|
### Carregamento
|
||||||
|
|
||||||
Ao iniciar a aplicação, o `PluginLoader` executa os seguintes passos:
|
Ao iniciar a aplicação, o `PluginLoader` executa os seguintes passos:
|
||||||
|
|
||||||
@@ -504,44 +209,6 @@ Durante a inicialização, cada plugin recebe uma referência para a
|
|||||||
Essa referência permite integrar componentes gráficos à aplicação, sendo a
|
Essa referência permite integrar componentes gráficos à aplicação, sendo a
|
||||||
principal forma de extensão a adição de menus à barra de menus:
|
principal forma de extensão a adição de menus à barra de menus:
|
||||||
|
|
||||||
```
|
```java
|
||||||
JMenu menu = new JMenu("Meu Plugin");
|
JMenu menu = new JMenu("Meu Plugin");
|
||||||
window.addMenu(menu);
|
window.addMenu(menu);
|
||||||
```
|
|
||||||
|
|
||||||
Visão geral da comunicação
|
|
||||||
|
|
||||||
A arquitetura de comunicação pode ser resumida da seguinte maneira:
|
|
||||||
```
|
|
||||||
+------------------+
|
|
||||||
| MainWindow |
|
|
||||||
+--------+---------+
|
|
||||||
|
|
|
||||||
+--------------------+--------------------+
|
|
||||||
| | |
|
|
||||||
v v v
|
|
||||||
HeartbeatManager SocketUDP SocketTCP
|
|
||||||
| | |
|
|
||||||
| | |
|
|
||||||
v v v
|
|
||||||
Heartbeat UDP Multicast TCP Connections
|
|
||||||
| |
|
|
||||||
| |
|
|
||||||
+---------+----------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Plugins
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Message
|
|
||||||
```
|
|
||||||
|
|
||||||
O UDP multicast fornece a infraestrutura de descoberta e manutenção da presença dos
|
|
||||||
peers, além de permitir comunicação multicast e unicast.
|
|
||||||
|
|
||||||
O TCP fornece canais diretos e persistentes entre peers, com uma conexão reutilizável
|
|
||||||
por endereço.
|
|
||||||
|
|
||||||
Os dois mecanismos encaminham as mensagens destinadas às funcionalidades da aplicação
|
|
||||||
para os plugins, mantendo separadas as responsabilidades de transporte, infraestrutura
|
|
||||||
e lógica específica de cada extensão.
|
|
||||||
@@ -46,8 +46,8 @@ javac.modulepath=
|
|||||||
javac.processormodulepath=
|
javac.processormodulepath=
|
||||||
javac.processorpath=\
|
javac.processorpath=\
|
||||||
${javac.classpath}
|
${javac.classpath}
|
||||||
javac.source=17
|
javac.source=25
|
||||||
javac.target=17
|
javac.target=25
|
||||||
javac.test.classpath=\
|
javac.test.classpath=\
|
||||||
${javac.classpath}:\
|
${javac.classpath}:\
|
||||||
${build.classes.dir}
|
${build.classes.dir}
|
||||||
|
|||||||
Symlink
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -48,6 +48,7 @@ public final class Login extends JDialog {
|
|||||||
public void initComponents() {
|
public void initComponents() {
|
||||||
this.setTitle("Login");
|
this.setTitle("Login");
|
||||||
|
|
||||||
|
|
||||||
String nomeSalvo = prefs.get(PREF_NOME, System.getProperty("user.name"));
|
String nomeSalvo = prefs.get(PREF_NOME, System.getProperty("user.name"));
|
||||||
int portaSalva = prefs.getInt(PREF_PORTA, 8000);
|
int portaSalva = prefs.getInt(PREF_PORTA, 8000);
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,13 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.GUI;
|
package pitiupi.GUI;
|
||||||
|
|
||||||
import pitiupi.control.ConnectionManager;
|
import pitiupi.net.Socket;
|
||||||
import pitiupi.control.GuiManager;
|
import java.util.ArrayList;
|
||||||
import pitiupi.control.plugins.PluginManager;
|
import java.util.List;
|
||||||
|
import javax.swing.JMenu;
|
||||||
import javax.swing.*;
|
import pitiupi.control.PluginLoader;
|
||||||
|
import pitiupi.plugin.Plugin;
|
||||||
import java.awt.*;
|
import pitiupi.control.HeartbeatManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Janela principal da aplicação.
|
* Janela principal da aplicação.
|
||||||
@@ -27,15 +27,25 @@ import java.awt.*;
|
|||||||
* @author tony
|
* @author tony
|
||||||
* @author Gustavo
|
* @author Gustavo
|
||||||
*/
|
*/
|
||||||
public class MainWindow extends JFrame {
|
public class MainWindow extends javax.swing.JFrame {
|
||||||
|
|
||||||
private MenuBar menuBar;
|
private final MenuBar menuBar;
|
||||||
private JLabel status;
|
private javax.swing.JScrollPane jScrollPane1;
|
||||||
OnlineUsersPanel onlineUsersPanel;
|
private javax.swing.JLabel status;
|
||||||
|
private javax.swing.JPanel statusPanel;
|
||||||
|
private javax.swing.JTabbedPane tabbedPane;
|
||||||
|
|
||||||
private final ConnectionManager connectionManager;
|
private HeartbeatManager heartbeatManager;
|
||||||
private final PluginManager pluginManager;
|
private OnlineUsersPanel onlineUsersPanel;
|
||||||
private final GuiManager guiManager;
|
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
|
||||||
|
private String userName;
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
private PluginLoader pluginManager;
|
||||||
|
private final List<Plugin> plugins;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cria a janela principal da aplicação.
|
* Cria a janela principal da aplicação.
|
||||||
@@ -44,53 +54,58 @@ 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();
|
this.userName = userName;
|
||||||
|
this.port = port;
|
||||||
pluginManager = new PluginManager();
|
this.menuBar = new MenuBar(this);
|
||||||
connectionManager = new ConnectionManager(this, userName, pluginManager, port);
|
this.plugins = new ArrayList<>();
|
||||||
|
this.initComponents();
|
||||||
initUserPanel();
|
}
|
||||||
initStatusPanel();
|
|
||||||
|
|
||||||
guiManager = new GuiManager(this, menuBar, onlineUsersPanel);
|
|
||||||
|
|
||||||
loadPlugins();
|
|
||||||
|
|
||||||
this.pack();
|
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
initGUI();
|
||||||
|
initSocket();
|
||||||
|
initHeartbeat();
|
||||||
|
initPlugins();
|
||||||
this.setSize(new java.awt.Dimension(1024, 768));
|
this.setSize(new java.awt.Dimension(1024, 768));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMenu() {
|
private void initGUI() {
|
||||||
menuBar = new MenuBar(this);
|
|
||||||
|
|
||||||
this.setJMenuBar(menuBar);
|
this.setJMenuBar(menuBar);
|
||||||
this.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
|
this.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
|
||||||
this.setTitle("Pitiupi");
|
this.setTitle("Share My Sheet");
|
||||||
|
|
||||||
javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane();
|
jScrollPane1 = new javax.swing.JScrollPane();
|
||||||
javax.swing.JTabbedPane tabbedPane = new javax.swing.JTabbedPane();
|
tabbedPane = new javax.swing.JTabbedPane();
|
||||||
|
statusPanel = new javax.swing.JPanel();
|
||||||
|
status = new javax.swing.JLabel();
|
||||||
|
|
||||||
jScrollPane1.setViewportView(tabbedPane);
|
jScrollPane1.setViewportView(tabbedPane);
|
||||||
|
statusPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT));
|
||||||
this.getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);
|
status.setText("Connected on port " + this.port + " name " + this.userName);
|
||||||
}
|
|
||||||
|
|
||||||
private void initUserPanel() {
|
|
||||||
onlineUsersPanel = new OnlineUsersPanel();
|
|
||||||
this.connectionManager.addPeerListener(onlineUsersPanel);
|
|
||||||
this.getContentPane().add(onlineUsersPanel, java.awt.BorderLayout.LINE_END);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initStatusPanel() {
|
|
||||||
JPanel statusPanel = new JPanel();
|
|
||||||
status = new JLabel();
|
|
||||||
|
|
||||||
statusPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
|
|
||||||
updateStatus();
|
|
||||||
statusPanel.add(status);
|
statusPanel.add(status);
|
||||||
|
|
||||||
this.getContentPane().add(statusPanel, BorderLayout.PAGE_END);
|
this.getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);
|
||||||
|
this.getContentPane().add(statusPanel, java.awt.BorderLayout.PAGE_END);
|
||||||
|
|
||||||
|
this.pack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initSocket() {
|
||||||
|
this.socket = new Socket(this);
|
||||||
|
this.socket.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initHeartbeat() {
|
||||||
|
this.heartbeatManager = new HeartbeatManager(this);
|
||||||
|
this.onlineUsersPanel = new OnlineUsersPanel();
|
||||||
|
this.heartbeatManager.addPeerListener(this.onlineUsersPanel);
|
||||||
|
this.getContentPane().add(this.onlineUsersPanel, java.awt.BorderLayout.LINE_END);
|
||||||
|
this.heartbeatManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPlugins() {
|
||||||
|
this.pluginManager = new PluginLoader(this);
|
||||||
|
this.pluginManager.loadPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void exit() {
|
public void exit() {
|
||||||
@@ -99,21 +114,51 @@ public class MainWindow extends JFrame {
|
|||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConnectionManager getConnectionManager() {
|
public HeartbeatManager getHeartbeatManager() {
|
||||||
return connectionManager;
|
return heartbeatManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadPlugins() {
|
public String getUserName(){
|
||||||
pluginManager.loadPlugins(connectionManager, guiManager);
|
return this.userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMenu(JMenu menu) {
|
||||||
|
this.menuBar.addPlugin(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return this.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket getSocket() {
|
||||||
|
return this.socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Plugin> getPlugins() {
|
||||||
|
return plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUserName(String userName) {
|
public void setUserName(String userName) {
|
||||||
connectionManager.setUserName(userName);
|
this.userName = userName;
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPort(int port) {
|
||||||
|
this.port = port;
|
||||||
|
reconnect();
|
||||||
updateStatus();
|
updateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStatus() {
|
private void updateStatus() {
|
||||||
this.status.setText("Connected on port " + connectionManager.getPort() + " name " + connectionManager.getUserName());
|
this.status.setText("Connected on port " + this.port + " name " + this.userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reconnect() {
|
||||||
|
if (this.socket != null) {
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
this.socket = new Socket(this);
|
||||||
|
this.socket.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -5,14 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
package pitiupi.GUI;
|
package pitiupi.GUI;
|
||||||
|
|
||||||
import pitiupi.models.Menus.MenuCategory;
|
import javax.swing.JMenu;
|
||||||
|
import javax.swing.JMenuBar;
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.InputEvent;
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Barra de menus principal da aplicação.
|
* Barra de menus principal da aplicação.
|
||||||
@@ -27,14 +21,12 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class MenuBar extends JMenuBar {
|
public class MenuBar extends JMenuBar {
|
||||||
|
|
||||||
private JMenuItem preferencesMenu;
|
private javax.swing.JMenuItem setupMenu;
|
||||||
private JMenuItem aboutMenu;
|
private javax.swing.JMenuItem aboutMenu;
|
||||||
private JMenu fileMenu;
|
private javax.swing.JMenu fileMenu;
|
||||||
private JMenu helpMenu;
|
private javax.swing.JMenu helpMenu;
|
||||||
private JMenu pluginMenu;
|
private javax.swing.JMenu pluginMenu;
|
||||||
private Map<MenuCategory, JMenu> categories;
|
private javax.swing.JMenuItem exitMenu;
|
||||||
|
|
||||||
private JMenuItem exitMenu;
|
|
||||||
private final MainWindow mainWindow;
|
private final MainWindow mainWindow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,70 +40,63 @@ public class MenuBar extends JMenuBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
initFileMenu();
|
fileMenu = new javax.swing.JMenu();
|
||||||
initPluginMenu();
|
exitMenu = new javax.swing.JMenuItem();
|
||||||
initHelpMenu();
|
helpMenu = new javax.swing.JMenu();
|
||||||
}
|
pluginMenu = new javax.swing.JMenu();
|
||||||
|
setupMenu = new javax.swing.JMenuItem();
|
||||||
|
aboutMenu = new javax.swing.JMenuItem();
|
||||||
|
|
||||||
private void initFileMenu() {
|
fileMenu.setText("File");
|
||||||
fileMenu = new JMenu("File");
|
exitMenu.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F4, java.awt.event.InputEvent.ALT_MASK));
|
||||||
exitMenu = new JMenuItem("Exit");
|
exitMenu.setText("Exit");
|
||||||
|
|
||||||
exitMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK));
|
|
||||||
exitMenu.addActionListener(this::exitMenuActionPerformed);
|
exitMenu.addActionListener(this::exitMenuActionPerformed);
|
||||||
fileMenu.add(exitMenu);
|
fileMenu.add(exitMenu);
|
||||||
this.add(fileMenu);
|
this.add(fileMenu);
|
||||||
}
|
|
||||||
|
|
||||||
private void initPluginMenu() {
|
|
||||||
pluginMenu = new JMenu("Plugin");
|
|
||||||
categories = new HashMap<>();
|
|
||||||
|
|
||||||
for (MenuCategory category : MenuCategory.values()) {
|
|
||||||
JMenu newCategoryMenu = new JMenu(category.toString());
|
|
||||||
|
|
||||||
pluginMenu.add(newCategoryMenu);
|
|
||||||
categories.put(category, newCategoryMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
pluginMenu.setText("Plugin");
|
||||||
this.add(pluginMenu);
|
this.add(pluginMenu);
|
||||||
}
|
|
||||||
|
|
||||||
private void initHelpMenu() {
|
helpMenu.setText("Help");
|
||||||
helpMenu = new JMenu("Help");
|
|
||||||
preferencesMenu = new JMenuItem("Preferences");
|
|
||||||
aboutMenu = new JMenuItem("About");
|
|
||||||
|
|
||||||
preferencesMenu.addActionListener(this::setupMenuActionPerformed);
|
setupMenu.setText("Preferences");
|
||||||
helpMenu.add(preferencesMenu);
|
setupMenu.addActionListener(this::setupMenuActionPerformed);
|
||||||
|
helpMenu.add(setupMenu);
|
||||||
|
|
||||||
|
aboutMenu.setText("About");
|
||||||
aboutMenu.addActionListener(this::aboutMenuActionPerformed);
|
aboutMenu.addActionListener(this::aboutMenuActionPerformed);
|
||||||
helpMenu.add(aboutMenu);
|
helpMenu.add(aboutMenu);
|
||||||
|
|
||||||
this.add(helpMenu);
|
this.add(helpMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exitMenuActionPerformed(ActionEvent evt) {
|
private void exitMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
mainWindow.exit();
|
mainWindow.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupMenuActionPerformed(ActionEvent evt) {
|
|
||||||
|
private void setupMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
Login login = new Login();
|
Login login = new Login();
|
||||||
login.setVisible(true);
|
login.setVisible(true);
|
||||||
|
|
||||||
mainWindow.setUserName(login.getName());
|
mainWindow.setUserName(login.getName());
|
||||||
mainWindow.getConnectionManager().setPort(login.getPort());
|
mainWindow.setPort(login.getPort());
|
||||||
|
|
||||||
login.dispose();
|
login.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void aboutMenuActionPerformed(ActionEvent evt) {
|
private void aboutMenuActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
About about = new About();
|
About about = new About();
|
||||||
about.setVisible(true);
|
about.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPluginMenu(MenuCategory category, JMenuItem menu){
|
/**
|
||||||
System.out.println("addPluginMenu");
|
* Adiciona um menu fornecido por um plugin à seção de plugins da barra
|
||||||
categories.get(category).add(menu);
|
* de menus.
|
||||||
|
*
|
||||||
|
* @param menu menu criado pelo plugin.
|
||||||
|
*/
|
||||||
|
public void addPlugin(JMenu menu){
|
||||||
|
this.pluginMenu.add(menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,16 +2,14 @@ package pitiupi.GUI;
|
|||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import javax.swing.BorderFactory;
|
||||||
import java.util.function.Consumer;
|
import javax.swing.DefaultListModel;
|
||||||
import javax.swing.*;
|
import javax.swing.JList;
|
||||||
|
import javax.swing.JPanel;
|
||||||
import pitiupi.models.Menus.MenuCategory;
|
import javax.swing.JScrollPane;
|
||||||
import pitiupi.models.User;
|
import javax.swing.SwingUtilities;
|
||||||
|
import pitiupi.control.PeerInfo;
|
||||||
import pitiupi.control.PeerListener;
|
import pitiupi.control.PeerListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,16 +20,10 @@ import pitiupi.control.PeerListener;
|
|||||||
*/
|
*/
|
||||||
public class OnlineUsersPanel extends JPanel implements PeerListener {
|
public class OnlineUsersPanel extends JPanel implements PeerListener {
|
||||||
|
|
||||||
private final DefaultListModel<User> listModel;
|
private final DefaultListModel<String> listModel;
|
||||||
private final JList<User> userList;
|
private final JList<String> userList;
|
||||||
JPopupMenu menu;
|
|
||||||
private Map<MenuCategory, JMenu> categories;
|
|
||||||
|
|
||||||
public OnlineUsersPanel() {
|
public OnlineUsersPanel() {
|
||||||
menu = new JPopupMenu();
|
|
||||||
categories = new HashMap<>();
|
|
||||||
initMenu();
|
|
||||||
|
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
setBorder(BorderFactory.createTitledBorder("Online"));
|
setBorder(BorderFactory.createTitledBorder("Online"));
|
||||||
setPreferredSize(new Dimension(160, 0));
|
setPreferredSize(new Dimension(160, 0));
|
||||||
@@ -39,48 +31,6 @@ public class OnlineUsersPanel extends JPanel implements PeerListener {
|
|||||||
listModel = new DefaultListModel<>();
|
listModel = new DefaultListModel<>();
|
||||||
userList = new JList<>(listModel);
|
userList = new JList<>(listModel);
|
||||||
add(new JScrollPane(userList), BorderLayout.CENTER);
|
add(new JScrollPane(userList), BorderLayout.CENTER);
|
||||||
|
|
||||||
setupUserListActions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initMenu() {
|
|
||||||
for (MenuCategory category : MenuCategory.values()) {
|
|
||||||
JMenu newCategory = new JMenu(category.toString());
|
|
||||||
|
|
||||||
menu.add(newCategory);
|
|
||||||
categories.put(category, newCategory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupUserListActions() {
|
|
||||||
userList.addMouseListener(new MouseAdapter() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked(MouseEvent e) {
|
|
||||||
int index = userList.locationToIndex(e.getPoint());
|
|
||||||
|
|
||||||
if (index == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
userList.setSelectedIndex(index);
|
|
||||||
menu.show(userList, e.getX(), e.getY());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initUnicastMenu(JMenuItem menuItem, Consumer<User> action) {
|
|
||||||
menuItem.addActionListener(e -> {
|
|
||||||
User user = userList.getSelectedValue();
|
|
||||||
|
|
||||||
if (user != null) {
|
|
||||||
action.accept(user);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addUnicastMenu(MenuCategory category, JMenuItem menuItem) {
|
|
||||||
categories.get(category).add(menuItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,11 +39,11 @@ public class OnlineUsersPanel extends JPanel implements PeerListener {
|
|||||||
* @param peers lista atualizada de usuários ativos na rede.
|
* @param peers lista atualizada de usuários ativos na rede.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onPeersChanged(List<User> peers) {
|
public void onPeersChanged(List<PeerInfo> peers) {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
listModel.clear();
|
listModel.clear();
|
||||||
for (User peer : peers) {
|
for (PeerInfo peer : peers) {
|
||||||
listModel.addElement(peer);
|
listModel.addElement(peer.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
package pitiupi.control;
|
|
||||||
|
|
||||||
import pitiupi.GUI.MainWindow;
|
|
||||||
import pitiupi.control.plugins.PluginManager;
|
|
||||||
import pitiupi.models.Messages.HeartbeatMessage;
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
import pitiupi.net.tcp.SocketTCP;
|
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
|
||||||
import pitiupi.net.udp.SocketUDP;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
|
|
||||||
public class ConnectionManager {
|
|
||||||
private final String pitiupiIdentifier;
|
|
||||||
|
|
||||||
private final PluginManager pluginManager;
|
|
||||||
|
|
||||||
private final HeartbeatManager heartbeatManager;
|
|
||||||
|
|
||||||
private SocketTCP socketTCP;
|
|
||||||
private SocketUDP socketUDP;
|
|
||||||
private String username;
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
public ConnectionManager(MainWindow main, String username, PluginManager pluginManager, int port) {
|
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
|
|
||||||
this.username = username;
|
|
||||||
this.port = port;
|
|
||||||
|
|
||||||
pitiupiIdentifier = "Pitiupi.Alice.2026";
|
|
||||||
|
|
||||||
reconnect();
|
|
||||||
|
|
||||||
heartbeatManager = new HeartbeatManager(this);
|
|
||||||
heartbeatManager.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TcpConnection connectPluginTcp(InetAddress destinationAdress, String senderId, String receiverId) {
|
|
||||||
return socketTCP.createPrivateConnection(destinationAdress, senderId, receiverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean sendTcp(Message message, InetAddress destinationAddress) {
|
|
||||||
return socketTCP.send(message, destinationAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean sendUdpUnicast(Message message, InetAddress destinationAddress) {
|
|
||||||
try {
|
|
||||||
socketUDP.sendUnicast(message, destinationAddress);
|
|
||||||
return true;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean sendUdpMulticast(Message message) {
|
|
||||||
try {
|
|
||||||
socketUDP.sendMulticast(message);
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("Error sending UDP Multicast");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void broadcastLocal(byte[] message) {
|
|
||||||
pluginManager.handleMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addPeerListener(PeerListener peerListener) {
|
|
||||||
heartbeatManager.addPeerListener(peerListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void receiveHeartbeat(HeartbeatMessage message, InetAddress destinationAddress) {
|
|
||||||
heartbeatManager.receiveHeartbeat(message, destinationAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPort(int port) {
|
|
||||||
this.port = port;
|
|
||||||
reconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIdentifier(){
|
|
||||||
return this.pitiupiIdentifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reconnect() {
|
|
||||||
if (this.socketUDP != null) {
|
|
||||||
this.socketUDP.close();
|
|
||||||
}
|
|
||||||
if (this.socketTCP != null) {
|
|
||||||
this.socketTCP.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.socketUDP = new SocketUDP(this, pluginManager);
|
|
||||||
this.socketTCP = new SocketTCP(this, pluginManager);
|
|
||||||
|
|
||||||
this.socketUDP.start();
|
|
||||||
this.socketTCP.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserName() {
|
|
||||||
return this.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserName(String userName) {
|
|
||||||
this.username = userName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package pitiupi.control;
|
|
||||||
|
|
||||||
import pitiupi.GUI.MainWindow;
|
|
||||||
import pitiupi.GUI.MenuBar;
|
|
||||||
import pitiupi.GUI.OnlineUsersPanel;
|
|
||||||
import pitiupi.models.Menus.*;
|
|
||||||
import pitiupi.models.Menus.MulticastMenus.MulticastActionMenu;
|
|
||||||
import pitiupi.models.Menus.MulticastMenus.MulticastMenu;
|
|
||||||
import pitiupi.models.Menus.MulticastMenus.MulticastSubMenu;
|
|
||||||
import pitiupi.models.Menus.UnicastMenus.UnicastActionMenu;
|
|
||||||
import pitiupi.models.Menus.UnicastMenus.UnicastMenu;
|
|
||||||
import pitiupi.models.Menus.UnicastMenus.UnicastSubMenu;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class GuiManager {
|
|
||||||
private MainWindow mainWindow;
|
|
||||||
private MenuBar menuBar;
|
|
||||||
private OnlineUsersPanel onlineUsersPanel;
|
|
||||||
|
|
||||||
private Logger logger;
|
|
||||||
|
|
||||||
public GuiManager(MainWindow mainWindow, MenuBar menuBar, OnlineUsersPanel onlineUsersPanel) {
|
|
||||||
this.mainWindow = mainWindow;
|
|
||||||
this.menuBar = menuBar;
|
|
||||||
this.onlineUsersPanel = onlineUsersPanel;
|
|
||||||
this.logger = Logger.getLogger(GuiManager.class.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createMenu(MenuCategory category, Menu menu) {
|
|
||||||
System.out.println(
|
|
||||||
menu.getClass().getSuperclass().getSuperclass()
|
|
||||||
);
|
|
||||||
|
|
||||||
System.out.println(
|
|
||||||
MulticastMenu.class
|
|
||||||
);
|
|
||||||
|
|
||||||
System.out.println(
|
|
||||||
menu.getClass().getSuperclass().getSuperclass()
|
|
||||||
== MulticastMenu.class
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
JMenuItem newMenu = buildMenu(menu);
|
|
||||||
if (menu instanceof MulticastMenu) {
|
|
||||||
menuBar.addPluginMenu(category, newMenu);
|
|
||||||
}
|
|
||||||
if (menu instanceof UnicastMenu) {
|
|
||||||
onlineUsersPanel.addUnicastMenu(category, newMenu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException e) {
|
|
||||||
logger.log(Level.WARNING, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private JMenuItem buildMenu(Menu menu) {
|
|
||||||
if (menu instanceof MulticastSubMenu multicastSubMenu) {
|
|
||||||
JMenu newMenu = new JMenu(menu.getText());
|
|
||||||
|
|
||||||
for (Menu child : multicastSubMenu.getMenus()) {
|
|
||||||
newMenu.add(buildMenu(child));
|
|
||||||
}
|
|
||||||
|
|
||||||
return newMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menu instanceof UnicastSubMenu unicastSubMenu) {
|
|
||||||
JMenu newMenu = new JMenu(menu.getText());
|
|
||||||
|
|
||||||
for (Menu child : unicastSubMenu.getMenus()) {
|
|
||||||
newMenu.add(buildMenu(child));
|
|
||||||
}
|
|
||||||
|
|
||||||
return newMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menu instanceof MulticastActionMenu multicastActionMenu) {
|
|
||||||
JMenuItem newMenu = new JMenuItem(menu.getText());
|
|
||||||
newMenu.addActionListener(e -> multicastActionMenu.onClick());
|
|
||||||
return newMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menu instanceof UnicastActionMenu unicastActionMenu) {
|
|
||||||
JMenuItem newMenu = new JMenuItem(menu.getText());
|
|
||||||
|
|
||||||
onlineUsersPanel.initUnicastMenu(newMenu, unicastActionMenu::onClick);
|
|
||||||
return newMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Invalid menu type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package pitiupi.control;
|
package pitiupi.control;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -9,9 +10,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
import pitiupi.models.User;
|
import java.util.logging.Logger;
|
||||||
import pitiupi.models.Messages.HeartbeatMessage;
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.net.HeartbeatMessage;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
/**
|
/**
|
||||||
* Gerencia o mecanismo de heartbeat da aplicação.
|
* Gerencia o mecanismo de heartbeat da aplicação.
|
||||||
@@ -26,8 +28,9 @@ public class HeartbeatManager {
|
|||||||
|
|
||||||
private static final long HEARTBEAT_INTERVAL_MS = 5000;
|
private static final long HEARTBEAT_INTERVAL_MS = 5000;
|
||||||
private static final long PEER_TIMEOUT_MS = 15000; // ~3 batimentos perdidos
|
private static final long PEER_TIMEOUT_MS = 15000; // ~3 batimentos perdidos
|
||||||
private final ConnectionManager connectionManager;
|
|
||||||
private final Map<String, User> peers = new ConcurrentHashMap<>();
|
private final MainWindow mainWindow;
|
||||||
|
private final Map<String, PeerInfo> peers = new ConcurrentHashMap<>();
|
||||||
private final List<PeerListener> listeners = new CopyOnWriteArrayList<>();
|
private final List<PeerListener> listeners = new CopyOnWriteArrayList<>();
|
||||||
private ScheduledExecutorService scheduler;
|
private ScheduledExecutorService scheduler;
|
||||||
private final String instanceId = UUID.randomUUID().toString();
|
private final String instanceId = UUID.randomUUID().toString();
|
||||||
@@ -35,9 +38,11 @@ public class HeartbeatManager {
|
|||||||
/**
|
/**
|
||||||
* Cria um gerenciador de heartbeat associado à janela principal da aplicação.
|
* Cria um gerenciador de heartbeat associado à janela principal da aplicação.
|
||||||
*
|
*
|
||||||
|
* @param mainWindow janela principal utilizada para obter o nome do usuário
|
||||||
|
* e o socket de comunicação.
|
||||||
*/
|
*/
|
||||||
public HeartbeatManager(ConnectionManager connectionManager) {
|
public HeartbeatManager(MainWindow mainWindow) {
|
||||||
this.connectionManager = connectionManager;
|
this.mainWindow = mainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,8 +64,12 @@ public class HeartbeatManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void announce() {
|
private void announce() {
|
||||||
HeartbeatMessage msg = new HeartbeatMessage(connectionManager.getUserName(), instanceId);
|
try {
|
||||||
connectionManager.sendUdpMulticast(msg);
|
HeartbeatMessage msg = new HeartbeatMessage(mainWindow.getUserName(), instanceId);
|
||||||
|
mainWindow.getSocket().send(msg.toByteArray());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.getLogger(HeartbeatManager.class.getName()).log(Level.WARNING, "Falha ao enviar heartbeat", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chamado pelo Socket quando um pacote é identificado como heartbeat
|
// Chamado pelo Socket quando um pacote é identificado como heartbeat
|
||||||
@@ -85,7 +94,7 @@ public class HeartbeatManager {
|
|||||||
String key = msg.getInstanceId();
|
String key = msg.getInstanceId();
|
||||||
boolean isNew = !peers.containsKey(key);
|
boolean isNew = !peers.containsKey(key);
|
||||||
|
|
||||||
User peer = peers.computeIfAbsent(key, k -> new User(msg.getUserName(), from, msg.getTimestamp()));
|
PeerInfo peer = peers.computeIfAbsent(key, k -> new PeerInfo(msg.getUserName(), from, msg.getTimestamp()));
|
||||||
peer.touch(System.currentTimeMillis());
|
peer.touch(System.currentTimeMillis());
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
@@ -102,7 +111,7 @@ public class HeartbeatManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void notifyListeners() {
|
private void notifyListeners() {
|
||||||
List<User> snapshot = new ArrayList<>(peers.values());
|
List<PeerInfo> snapshot = new ArrayList<>(peers.values());
|
||||||
for (PeerListener l : listeners) {
|
for (PeerListener l : listeners) {
|
||||||
l.onPeersChanged(snapshot);
|
l.onPeersChanged(snapshot);
|
||||||
}
|
}
|
||||||
@@ -135,7 +144,7 @@ public class HeartbeatManager {
|
|||||||
*
|
*
|
||||||
* @return lista contendo os peers atualmente conhecidos.
|
* @return lista contendo os peers atualmente conhecidos.
|
||||||
*/
|
*/
|
||||||
public List<User> getPeers() {
|
public List<PeerInfo> getPeers() {
|
||||||
return new ArrayList<>(peers.values());
|
return new ArrayList<>(peers.values());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package pitiupi.models;
|
package pitiupi.control;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
/**
|
/**
|
||||||
@@ -10,13 +10,13 @@ import java.net.InetAddress;
|
|||||||
*
|
*
|
||||||
* @author tony
|
* @author tony
|
||||||
*/
|
*/
|
||||||
public class User {
|
public class PeerInfo {
|
||||||
|
|
||||||
private final String userName;
|
private final String userName;
|
||||||
private final InetAddress address;
|
private final InetAddress address;
|
||||||
private volatile long lastSeen;
|
private volatile long lastSeen;
|
||||||
|
|
||||||
public User(String userName, InetAddress address, long lastSeen) {
|
public PeerInfo(String userName, InetAddress address, long lastSeen) {
|
||||||
this.userName = userName;
|
this.userName = userName;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.lastSeen = lastSeen;
|
this.lastSeen = lastSeen;
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package pitiupi.control;
|
package pitiupi.control;
|
||||||
|
|
||||||
import pitiupi.models.User;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,5 +17,5 @@ public interface PeerListener {
|
|||||||
*
|
*
|
||||||
* @param peers lista atualizada de usuários ativos.
|
* @param peers lista atualizada de usuários ativos.
|
||||||
*/
|
*/
|
||||||
void onPeersChanged(List<User> peers);
|
void onPeersChanged(List<PeerInfo> peers);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
||||||
|
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
|
||||||
|
*/
|
||||||
|
package pitiupi.control;
|
||||||
|
|
||||||
|
import java.awt.List;
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.Vector;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.Main;
|
||||||
|
import pitiupi.plugin.Plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsável por localizar, carregar e registrar os plugins
|
||||||
|
* presentes no diretório {@code plugins}.
|
||||||
|
*
|
||||||
|
* <p>Os plugins são descobertos utilizando {@link java.util.ServiceLoader}
|
||||||
|
* e devem estar empacotados como arquivos JAR contendo a configuração
|
||||||
|
* {@code META-INF/services} correspondente.</p>
|
||||||
|
* @author flavio
|
||||||
|
*/
|
||||||
|
public class PluginLoader {
|
||||||
|
|
||||||
|
private final pitiupi.GUI.MainWindow mainWindow;
|
||||||
|
|
||||||
|
public PluginLoader(MainWindow main) {
|
||||||
|
this.mainWindow = main;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadPlugins() {
|
||||||
|
File pluginDir = new File("plugins");
|
||||||
|
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
|
||||||
|
|
||||||
|
if (jarFiles == null || jarFiles.length == 0) {
|
||||||
|
System.out.println("No plugins found in the " + pluginDir.getAbsolutePath() + " directory.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2. Convert jar file paths into URLs
|
||||||
|
ArrayList urls = new ArrayList();
|
||||||
|
for (File file : jarFiles) {
|
||||||
|
urls.add(file.toURI().toURL());
|
||||||
|
System.out.println("Discovered plugin package: " + file.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Create a class loader isolated from the application's default loader
|
||||||
|
URLClassLoader pluginClassLoader = new URLClassLoader(
|
||||||
|
(URL[])urls.toArray(new URL[0]),
|
||||||
|
Main.class.getClassLoader()
|
||||||
|
);
|
||||||
|
System.out.println(pluginClassLoader);
|
||||||
|
|
||||||
|
// 4. Locate and instantiate all classes implementing the Plugin interface
|
||||||
|
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, pluginClassLoader);
|
||||||
|
|
||||||
|
System.out.println("\n--- Executing Discovered Plugins ---" + serviceLoader.findFirst());
|
||||||
|
for (Plugin plugin : serviceLoader) {
|
||||||
|
System.out.println("Loading: " + plugin.getName());
|
||||||
|
plugin.createPlugin(this.mainWindow);
|
||||||
|
this.mainWindow.getPlugins().add(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
System.out.println(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package pitiupi.control.plugins;
|
|
||||||
|
|
||||||
import pitiupi.models.Plugin;
|
|
||||||
|
|
||||||
class LoadedPlugin {
|
|
||||||
|
|
||||||
private final Plugin plugin;
|
|
||||||
private final ClassLoader classLoader;
|
|
||||||
|
|
||||||
public LoadedPlugin(Plugin plugin, ClassLoader classLoader) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.classLoader = classLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Plugin getPlugin() {
|
|
||||||
return plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClassLoader getClassLoader() {
|
|
||||||
return classLoader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
package pitiupi.control.plugins;
|
|
||||||
|
|
||||||
import pitiupi.Main;
|
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
import pitiupi.control.GuiManager;
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
|
||||||
import pitiupi.models.Plugin;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLClassLoader;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ServiceLoader;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class PluginManager {
|
|
||||||
private final List<LoadedPlugin> loadedPlugins;
|
|
||||||
private final HashSet<String> loadedJars;
|
|
||||||
|
|
||||||
private final Logger logger;
|
|
||||||
|
|
||||||
public PluginManager() {
|
|
||||||
this.loadedPlugins = new ArrayList<>();
|
|
||||||
this.loadedJars = new HashSet<>();
|
|
||||||
|
|
||||||
logger = Logger.getLogger(getClass().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleMessage(byte[] msg) {
|
|
||||||
Message message = null;
|
|
||||||
|
|
||||||
for (LoadedPlugin loadedPlugin : loadedPlugins) {
|
|
||||||
message = deserialize(msg, loadedPlugin.getClassLoader());
|
|
||||||
if (message != null) break;
|
|
||||||
}
|
|
||||||
if (message == null) return;
|
|
||||||
|
|
||||||
for (LoadedPlugin loadedPlugin : loadedPlugins) {
|
|
||||||
Plugin plugin = loadedPlugin.getPlugin();
|
|
||||||
|
|
||||||
if (plugin.receiveAllTypes() && message.isPublic()) {
|
|
||||||
plugin.receiveMessage(message);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Class<? extends Message> messageType : plugin.getMessages()) {
|
|
||||||
if (messageType.isInstance(message)) {
|
|
||||||
plugin.receiveMessage(message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Plugin getPlugin(String pluginId) {
|
|
||||||
for (LoadedPlugin loadedPlugin : loadedPlugins) {
|
|
||||||
if (loadedPlugin.getPlugin().getIdentifier().equals(pluginId))
|
|
||||||
return loadedPlugin.getPlugin();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleMessageFromConnection(TcpConnection connection, byte[] msg) {
|
|
||||||
Plugin plugin = getPlugin(connection.getReceiver());
|
|
||||||
if (plugin == null) return;
|
|
||||||
|
|
||||||
plugin.receiveBytes(connection, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean handleConnection(TcpConnection connection, String receiverId) {
|
|
||||||
Plugin plugin = getPlugin(receiverId);
|
|
||||||
if (plugin == null) return false;
|
|
||||||
|
|
||||||
plugin.receivePrivateConnection(connection);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleDisconnection(TcpConnection connection, String receiverId) {
|
|
||||||
Plugin plugin = getPlugin(receiverId);
|
|
||||||
if (plugin == null) return;
|
|
||||||
|
|
||||||
plugin.onSocketClosed(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) {
|
|
||||||
File pluginDir = new File("plugins");
|
|
||||||
File[] jarFiles = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
|
|
||||||
|
|
||||||
if (jarFiles == null || jarFiles.length == 0) {
|
|
||||||
System.out.println("No plugins found in the " + pluginDir.getAbsolutePath() + " directory.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (File file : jarFiles) {
|
|
||||||
|
|
||||||
if (loadedJars.contains(file.getName())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("Discovered plugin package: " + file.getName());
|
|
||||||
|
|
||||||
try {
|
|
||||||
URLClassLoader classLoader = new URLClassLoader(
|
|
||||||
new URL[]{file.toURI().toURL()},
|
|
||||||
Main.class.getClassLoader()
|
|
||||||
);
|
|
||||||
|
|
||||||
ServiceLoader<Plugin> serviceLoader = ServiceLoader.load(Plugin.class, classLoader);
|
|
||||||
|
|
||||||
for (Plugin plugin : serviceLoader) {
|
|
||||||
System.out.println("Loading: " + plugin.getName());
|
|
||||||
LoadedPlugin loadedPlugin = new LoadedPlugin(plugin, classLoader);
|
|
||||||
|
|
||||||
plugin.createPlugin(connectionManager, guiManager);
|
|
||||||
loadedPlugins.add(loadedPlugin);
|
|
||||||
loadedJars.add(file.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.WARNING, "Failed to load plugin " + file.getName(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message deserialize(byte[] data, ClassLoader classLoader) {
|
|
||||||
try {
|
|
||||||
ByteArrayInputStream input = new ByteArrayInputStream(data);
|
|
||||||
|
|
||||||
PluginObjectInputStream objectInput = new PluginObjectInputStream(input, classLoader);
|
|
||||||
|
|
||||||
Object object = objectInput.readObject();
|
|
||||||
|
|
||||||
if (!(object instanceof Message)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (Message) object;
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
logger.log(Level.SEVERE, "Failed to load plugin package", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package pitiupi.control.plugins;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectStreamClass;
|
|
||||||
|
|
||||||
public class PluginObjectInputStream extends ObjectInputStream {
|
|
||||||
private final ClassLoader classLoader;
|
|
||||||
|
|
||||||
public PluginObjectInputStream(InputStream input, ClassLoader classLoader) throws IOException {
|
|
||||||
super(input);
|
|
||||||
this.classLoader = classLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Class<?> resolveClass(ObjectStreamClass description) throws ClassNotFoundException {
|
|
||||||
return Class.forName(description.getName(), false, classLoader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package pitiupi.models.Menus;
|
|
||||||
|
|
||||||
public abstract class Menu {
|
|
||||||
public abstract String getText();
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package pitiupi.models.Menus;
|
|
||||||
|
|
||||||
public enum MenuCategory {
|
|
||||||
Jogos,
|
|
||||||
Social,
|
|
||||||
Streaming
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package pitiupi.models.Menus.MulticastMenus;
|
|
||||||
|
|
||||||
public abstract class MulticastActionMenu extends MulticastMenu{
|
|
||||||
public abstract void onClick();
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package pitiupi.models.Menus.MulticastMenus;
|
|
||||||
|
|
||||||
import pitiupi.models.Menus.Menu;
|
|
||||||
|
|
||||||
public abstract class MulticastMenu extends Menu {
|
|
||||||
MulticastMenu() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package pitiupi.models.Menus.MulticastMenus;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class MulticastSubMenu extends MulticastMenu{
|
|
||||||
public abstract List<MulticastMenu> getMenus();
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package pitiupi.models.Menus.UnicastMenus;
|
|
||||||
|
|
||||||
import pitiupi.models.User;
|
|
||||||
|
|
||||||
public abstract class UnicastActionMenu extends UnicastMenu{
|
|
||||||
public abstract void onClick(User user);
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package pitiupi.models.Menus.UnicastMenus;
|
|
||||||
|
|
||||||
import pitiupi.models.Menus.Menu;
|
|
||||||
import pitiupi.models.User;
|
|
||||||
|
|
||||||
public abstract class UnicastMenu extends Menu {
|
|
||||||
UnicastMenu() {}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package pitiupi.models.Menus.UnicastMenus;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class UnicastSubMenu extends UnicastMenu{
|
|
||||||
public abstract List<UnicastMenu> getMenus();
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
|
||||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Interface.java to edit this template
|
|
||||||
*/
|
|
||||||
package pitiupi.models;
|
|
||||||
|
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
import pitiupi.control.GuiManager;
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
import pitiupi.net.tcp.TcpConnection;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface base para plugins da aplicação.
|
|
||||||
*
|
|
||||||
* <p>Cada plugin deve implementar esta interface para ser carregado
|
|
||||||
* pela aplicação e participar do sistema de comunicação por mensagens.</p>
|
|
||||||
*
|
|
||||||
* @author flavio
|
|
||||||
*/
|
|
||||||
public abstract class Plugin {
|
|
||||||
public abstract String getName();
|
|
||||||
public abstract String getAuthor();
|
|
||||||
public abstract String getVersion();
|
|
||||||
public abstract String getIdentifier();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inicializa o plugin.
|
|
||||||
*
|
|
||||||
* <p>Este método é chamado pela aplicação após o carregamento do plugin.</p>
|
|
||||||
*
|
|
||||||
* @param connectionManager interface com o sistema de conexões da aplicação.
|
|
||||||
* @param guiManager interface com a janela principal da aplicação
|
|
||||||
*/
|
|
||||||
public abstract void createPlugin(ConnectionManager connectionManager, GuiManager guiManager);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retorna uma mensagem utilizada pelo plugin.
|
|
||||||
*
|
|
||||||
* @return mensagem criada pelo plugin.
|
|
||||||
*/
|
|
||||||
public abstract List<Class<? extends Message>> getMessages();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recebe uma mensagem enviada pela aplicação.
|
|
||||||
*
|
|
||||||
* <p>A mensagem é recebida em formato serializado. O plugin deve verificar
|
|
||||||
* se a mensagem pertence ao seu tipo e realizar o processamento necessário.</p>
|
|
||||||
*
|
|
||||||
* @param message mensagem serializada recebida.
|
|
||||||
*/
|
|
||||||
public abstract void receiveMessage(Message message);
|
|
||||||
|
|
||||||
public boolean receiveAllTypes() {return false;}
|
|
||||||
public boolean receivePrivateConnection(TcpConnection connection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
public boolean receiveBytes(TcpConnection connection, byte[] bytes) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
public boolean onSocketClosed(TcpConnection connection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
package pitiupi.models.Messages;
|
package pitiupi.net;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mensagem utilizada para sinalizar a presença de um usuário na rede.
|
* Mensagem utilizada para sinalizar a presença de um usuário na rede.
|
||||||
*
|
*
|
||||||
+1
-14
@@ -3,7 +3,7 @@
|
|||||||
* To change this template file, choose Tools | Templates
|
* To change this template file, choose Tools | Templates
|
||||||
* and open the template in the editor.
|
* and open the template in the editor.
|
||||||
*/
|
*/
|
||||||
package pitiupi.models.Messages;
|
package pitiupi.net;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -24,19 +24,6 @@ import java.io.Serializable;
|
|||||||
* @author flavio
|
* @author flavio
|
||||||
*/
|
*/
|
||||||
public abstract class Message implements Serializable{
|
public abstract class Message implements Serializable{
|
||||||
private boolean isPublic;
|
|
||||||
|
|
||||||
protected Message(){
|
|
||||||
isPublic = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Message(boolean isPublic){
|
|
||||||
this.isPublic = isPublic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPublic(){
|
|
||||||
return isPublic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializa a mensagem para um vetor de bytes.
|
* Serializa a mensagem para um vetor de bytes.
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package pitiupi.net;
|
||||||
|
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInput;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.MulticastSocket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import pitiupi.plugin.Plugin;
|
||||||
|
import pitiupi.net.HeartbeatMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Responsável pela comunicação multicast da aplicação e pelo encaminhamento de mensagens aos plugins."
|
||||||
|
*
|
||||||
|
* <p>Esta classe gerencia o envio e o recebimento de mensagens, além de
|
||||||
|
* manter a conexão com o grupo multicast utilizado pela aplicação.</p>
|
||||||
|
*
|
||||||
|
* @author flavio
|
||||||
|
* @author tony
|
||||||
|
*/
|
||||||
|
public class Socket extends Thread {
|
||||||
|
|
||||||
|
private MulticastSocket multicastSocket;
|
||||||
|
private InetAddress address;
|
||||||
|
private MainWindow main;
|
||||||
|
private volatile boolean running = true;
|
||||||
|
public final static String INET_ADDR = "224.0.0.3";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria e inicializa a conexão multicast utilizada pela aplicação.
|
||||||
|
*
|
||||||
|
* @param main janela principal da aplicação.
|
||||||
|
*/
|
||||||
|
public Socket(MainWindow main) {
|
||||||
|
this.main = main;
|
||||||
|
try {
|
||||||
|
this.address = InetAddress.getByName(Socket.INET_ADDR);
|
||||||
|
} catch (UnknownHostException ex) {
|
||||||
|
System.getLogger(Socket.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
multicastSocket = new MulticastSocket(this.main.getPort());
|
||||||
|
multicastSocket.setSendBufferSize(256000);
|
||||||
|
multicastSocket.setReceiveBufferSize(256000);
|
||||||
|
multicastSocket.setReuseAddress(true);
|
||||||
|
multicastSocket.joinGroup(address);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.out.println("There is no socket connection. Sorry.");
|
||||||
|
System.out.println(ex.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envia uma mensagem para o grupo multicast da aplicação.
|
||||||
|
*
|
||||||
|
* @param msg mensagem serializada a ser enviada.
|
||||||
|
* @throws IOException caso ocorra um erro durante o envio da mensagem.
|
||||||
|
*/
|
||||||
|
public void send(byte[] msg) throws IOException {
|
||||||
|
DatagramPacket msgPacket;
|
||||||
|
msgPacket = new DatagramPacket(msg, msg.length, this.address, this.main.getPort());
|
||||||
|
multicastSocket.send(msgPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encerra a comunicação multicast e interrompe a recepção de mensagens.
|
||||||
|
*/
|
||||||
|
public void close() {
|
||||||
|
running = false;
|
||||||
|
if (multicastSocket != null) {
|
||||||
|
try {
|
||||||
|
multicastSocket.leaveGroup(address);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
|
||||||
|
}
|
||||||
|
multicastSocket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicia o loop de recepção de mensagens da aplicação.
|
||||||
|
*
|
||||||
|
* <p>As mensagens recebidas são verificadas inicialmente para identificar
|
||||||
|
* mensagens de heartbeat. Caso não sejam heartbeats, seu conteúdo é
|
||||||
|
* encaminhado para todos os plugins carregados, que decidem se devem ou
|
||||||
|
* não processá-lo.</p>
|
||||||
|
*
|
||||||
|
* @throws IOException caso ocorra um erro durante a recepção da mensagem.
|
||||||
|
* @throws ClassNotFoundException caso a desserialização de uma mensagem falhe.
|
||||||
|
*/
|
||||||
|
public void receive() throws UnknownHostException, IOException, ClassNotFoundException {
|
||||||
|
byte[] buf = new byte[256000];
|
||||||
|
while (running) {
|
||||||
|
DatagramPacket msgPacket = new DatagramPacket(buf, buf.length);
|
||||||
|
try {
|
||||||
|
multicastSocket.receive(msgPacket);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
if (!running) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
HeartbeatMessage heartbeat = tryParseHeartbeat(msgPacket.getData());
|
||||||
|
if (heartbeat != null) {
|
||||||
|
main.getHeartbeatManager().receiveHeartbeat(heartbeat, msgPacket.getAddress());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (Plugin plugin : main.getPlugins()) {
|
||||||
|
System.out.println(msgPacket.getAddress().toString());
|
||||||
|
plugin.receiveMessage(msgPacket.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tenta desserializar os dados recebidos como uma {@code HeartbeatMessage}.
|
||||||
|
*
|
||||||
|
* @param data dados serializados da mensagem.
|
||||||
|
* @return a mensagem desserializada caso os dados representem uma
|
||||||
|
* {@code HeartbeatMessage}; caso contrário, {@code null}.
|
||||||
|
*/
|
||||||
|
private HeartbeatMessage tryParseHeartbeat(byte[] data) {
|
||||||
|
try {
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||||
|
ObjectInput in = new ObjectInputStream(bais);
|
||||||
|
Object obj = in.readObject();
|
||||||
|
return (obj instanceof HeartbeatMessage) ? (HeartbeatMessage) obj : null;
|
||||||
|
} catch (IOException | ClassNotFoundException | ClassCastException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
this.receive();
|
||||||
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
|
Logger.getLogger(Socket.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
/*
|
|
||||||
* To change this license header, choose License Headers in Project Properties.
|
|
||||||
* To change this template file, choose Tools | Templates
|
|
||||||
* and open the template in the editor.
|
|
||||||
*/
|
|
||||||
package pitiupi.net.udp;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInput;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.net.*;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import pitiupi.control.ConnectionManager;
|
|
||||||
import pitiupi.control.plugins.PluginManager;
|
|
||||||
import pitiupi.models.Messages.HeartbeatMessage;
|
|
||||||
import pitiupi.models.Messages.Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gerencia a comunicação UDP multicast e unicast da aplicação.
|
|
||||||
*
|
|
||||||
* <p>Mantém a conexão com o grupo multicast da aplicação e permite o envio
|
|
||||||
* de mensagens tanto para o grupo quanto diretamente para um peer.</p>
|
|
||||||
*
|
|
||||||
* <p>As mensagens recebidas são verificadas para identificar mensagens de
|
|
||||||
* heartbeat. As demais mensagens são encaminhadas aos plugins, que são
|
|
||||||
* responsáveis por interpretá-las.</p>
|
|
||||||
*
|
|
||||||
* @author flavio
|
|
||||||
* @author tony
|
|
||||||
* @author Gustavo
|
|
||||||
*/
|
|
||||||
public class SocketUDP extends Thread {
|
|
||||||
|
|
||||||
private MulticastSocket multicastSocket;
|
|
||||||
|
|
||||||
private InetAddress address;
|
|
||||||
|
|
||||||
private final ConnectionManager connectionManager;
|
|
||||||
private final PluginManager pluginManager;
|
|
||||||
|
|
||||||
private volatile boolean running = true;
|
|
||||||
public final static String INET_ADDR = "224.0.0.3";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cria e inicializa o socket UDP utilizado pela aplicação.
|
|
||||||
*
|
|
||||||
* <p>O socket é associado à porta da aplicação e ingressa no grupo
|
|
||||||
* multicast configurado.</p>
|
|
||||||
*
|
|
||||||
* @param connectionManager interface que gerencia toda conexão da aplicação.
|
|
||||||
* @param pluginManager interface que gerencia os plugins da conexão.
|
|
||||||
*/
|
|
||||||
public SocketUDP(ConnectionManager connectionManager, PluginManager pluginManager) {
|
|
||||||
this.connectionManager = connectionManager;
|
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.address = InetAddress.getByName(SocketUDP.INET_ADDR);
|
|
||||||
|
|
||||||
multicastSocket = new MulticastSocket(connectionManager.getPort());
|
|
||||||
multicastSocket.setSendBufferSize(256000);
|
|
||||||
multicastSocket.setReceiveBufferSize(256000);
|
|
||||||
multicastSocket.setReuseAddress(true);
|
|
||||||
multicastSocket.joinGroup(address);
|
|
||||||
}
|
|
||||||
catch (UnknownHostException ex) {
|
|
||||||
System.getLogger(SocketUDP.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
System.out.println("There is no socket connection. Sorry.");
|
|
||||||
System.out.println(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Envia uma mensagem para o grupo multicast da aplicação.
|
|
||||||
*
|
|
||||||
* @param msg mensagem a ser enviada.
|
|
||||||
* @throws IOException caso ocorra um erro durante o envio.
|
|
||||||
*/
|
|
||||||
public void sendMulticast(Message msg) throws IOException {
|
|
||||||
send(msg.toByteArray(), this.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Envia uma mensagem diretamente para um peer utilizando UDP.
|
|
||||||
*
|
|
||||||
* @param msg mensagem a ser enviada.
|
|
||||||
* @param destinationAddress endereço IP do peer destinatário.
|
|
||||||
* @throws IOException caso ocorra um erro durante o envio.
|
|
||||||
*/
|
|
||||||
public void sendUnicast(Message msg, InetAddress destinationAddress) throws IOException {
|
|
||||||
send(msg.toByteArray(), destinationAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Envia uma mensagem UDP para o endereço especificado.
|
|
||||||
*
|
|
||||||
* @param msg mensagem serializada a ser enviada.
|
|
||||||
* @param destinationAddress endereço de destino.
|
|
||||||
* @throws IOException caso ocorra um erro durante o envio.
|
|
||||||
*/
|
|
||||||
private void send(byte[] msg, InetAddress destinationAddress) throws IOException {
|
|
||||||
DatagramPacket msgPacket;
|
|
||||||
msgPacket = new DatagramPacket(msg, msg.length, destinationAddress, connectionManager.getPort());
|
|
||||||
multicastSocket.send(msgPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encerra a comunicação multicast e interrompe a recepção de mensagens.
|
|
||||||
*/
|
|
||||||
public void close() {
|
|
||||||
running = false;
|
|
||||||
if (multicastSocket != null) {
|
|
||||||
try {
|
|
||||||
multicastSocket.leaveGroup(address);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
System.out.println(ex);
|
|
||||||
}
|
|
||||||
multicastSocket.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recebe e processa mensagens UDP.
|
|
||||||
*
|
|
||||||
* <p>Mensagens de heartbeat são encaminhadas ao gerenciador de heartbeat.
|
|
||||||
* As demais mensagens são encaminhadas aos plugins carregados pela
|
|
||||||
* aplicação.</p>
|
|
||||||
*
|
|
||||||
* @throws UnknownHostException caso não seja possível resolver o endereço
|
|
||||||
* utilizado pela comunicação.
|
|
||||||
* @throws IOException caso ocorra um erro durante a recepção.
|
|
||||||
* @throws ClassNotFoundException caso ocorra um erro ao verificar uma
|
|
||||||
* mensagem de heartbeat.
|
|
||||||
*/
|
|
||||||
public void receive() throws UnknownHostException, IOException, ClassNotFoundException {
|
|
||||||
byte[] buf = new byte[256000];
|
|
||||||
while (running) {
|
|
||||||
DatagramPacket msgPacket = new DatagramPacket(buf, buf.length);
|
|
||||||
try {
|
|
||||||
multicastSocket.receive(msgPacket);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
if (!running) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
HeartbeatMessage heartbeat = tryParseHeartbeat(msgPacket.getData());
|
|
||||||
if (heartbeat != null) {
|
|
||||||
connectionManager.receiveHeartbeat(heartbeat, msgPacket.getAddress());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginManager.handleMessage(msgPacket.getData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tenta identificar os dados recebidos como uma mensagem de heartbeat.
|
|
||||||
*
|
|
||||||
* @param data dados serializados recebidos.
|
|
||||||
* @return a mensagem de heartbeat caso os dados correspondam a uma;
|
|
||||||
* {@code null} caso contrário.
|
|
||||||
*/
|
|
||||||
private HeartbeatMessage tryParseHeartbeat(byte[] data) {
|
|
||||||
try {
|
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
|
||||||
ObjectInput in = new ObjectInputStream(bais);
|
|
||||||
Object obj = in.readObject();
|
|
||||||
return (obj instanceof HeartbeatMessage) ? (HeartbeatMessage) obj : null;
|
|
||||||
} catch (IOException | ClassNotFoundException | ClassCastException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inicia a recepção de mensagens UDP.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
this.receive();
|
|
||||||
} catch (IOException | ClassNotFoundException ex) {
|
|
||||||
Logger.getLogger(SocketUDP.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
||||||
|
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Interface.java to edit this template
|
||||||
|
*/
|
||||||
|
package pitiupi.plugin;
|
||||||
|
|
||||||
|
import javax.swing.JMenu;
|
||||||
|
import pitiupi.GUI.MainWindow;
|
||||||
|
import pitiupi.net.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface base para plugins da aplicação.
|
||||||
|
*
|
||||||
|
* <p>Cada plugin deve implementar esta interface para ser carregado
|
||||||
|
* pela aplicação e participar do sistema de comunicação por mensagens.</p>
|
||||||
|
*
|
||||||
|
* @author flavio
|
||||||
|
*/
|
||||||
|
public interface Plugin {
|
||||||
|
|
||||||
|
public String getName();
|
||||||
|
public String getAuthor();
|
||||||
|
public String getVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicializa o plugin.
|
||||||
|
*
|
||||||
|
* <p>Este método é chamado pela aplicação após o carregamento do plugin.</p>
|
||||||
|
*
|
||||||
|
* @param window janela principal da aplicação.
|
||||||
|
*/
|
||||||
|
public void createPlugin(MainWindow window);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna uma mensagem utilizada pelo plugin.
|
||||||
|
*
|
||||||
|
* @return mensagem criada pelo plugin.
|
||||||
|
*/
|
||||||
|
public Message getMessage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recebe uma mensagem enviada pela aplicação.
|
||||||
|
*
|
||||||
|
* <p>A mensagem é recebida em formato serializado. O plugin deve verificar
|
||||||
|
* se a mensagem pertence ao seu tipo e realizar o processamento necessário.</p>
|
||||||
|
*
|
||||||
|
* @param message mensagem serializada recebida.
|
||||||
|
*/
|
||||||
|
public void receiveMessage(byte[] message);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user