docs: reescreve o manual e registra o JAR instalado no catálogo
O texto explica o núcleo, a rede e o tutorial do ping como o código está hoje. Instalar ou remover um JAR atualiza plugins.registry.json para o próximo run não perder o plugin. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
61753c421a
commit
8338936251
47 files changed
+1423
-1712
No files matched your search
@@ -2,7 +2,7 @@
|
||||
|
||||
Ferramenta **P2P** para práticas de Redes de Computadores: núcleo em Java com arquitetura de **plugins** (conversa na rede e compartilhamento de tela).
|
||||
|
||||
**Documentação técnica:** [docs/README.md](docs/README.md) · **Quickstart:** [docs/documentation/00_quickstart.md](docs/documentation/00_quickstart.md) · **Convenções:** [docs/documentation/00_convencoes.md](docs/documentation/00_convencoes.md)
|
||||
**Documentação:** [docs/README.md](docs/README.md) · **Quickstart:** [docs/comecar.md](docs/comecar.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -80,7 +80,7 @@ Novos módulos entram no repositório adicionando uma entrada no JSON (não é p
|
||||
| --- | --- |
|
||||
| `id` | Usado em `--plugins chat,meuJogo` |
|
||||
| `dir` | Pasta do módulo (ex.: `plugins/xadrez`) |
|
||||
| `build` | `ant` ou `make` |
|
||||
| `build` | `ant`, `make` ou `jar` (arquivo já pronto, sem compilar) |
|
||||
| `jar` | Caminho do JAR relativo a `dir` |
|
||||
| `menuCategory` | `Social`, `Jogos` ou `Streaming` — filtro CLI e documentação |
|
||||
|
||||
@@ -104,9 +104,9 @@ Na interface, o texto das abas vem de `getDisplayName()` em cada plugin. O campo
|
||||
|
||||
## Novo plugin (resumo)
|
||||
|
||||
1. Copiar estrutura de `plugins/chat` → `plugins/meuPlugin` ([tutorial](docs/documentation/10_tutorial_plugin.md)).
|
||||
2. Em `createPlugin`, registrar painel com `PluginWorkspace` / `bindShellWorkspaceContent` e, se fizer sentido, **peer actions** na lista lateral.
|
||||
3. Adicionar entrada em [`plugins.registry.json`](plugins.registry.json).
|
||||
1. Copiar a estrutura de `plugins/chat` para `plugins/meuPlugin` ([plugins](docs/plugins.md), [funções do núcleo](docs/nucleo-para-plugins.md)).
|
||||
2. Em `createPlugin`, registrar o painel com `bindShellWorkspaceContent` e, se fizer sentido, peer actions na lista lateral.
|
||||
3. Adicionar entrada em [`plugins.registry.json`](plugins.registry.json), ou instalar o JAR pela aba **Configurações** (a instalação grava o catálogo).
|
||||
4. `./scripts/pitiupi.sh link --all` e reiniciar o app.
|
||||
|
||||
O núcleo carrega **todos os `*.jar`** em `PITIUPI_HOME/plugins`. Detalhes: [pitiupi/plugins/README.md](pitiupi/plugins/README.md).
|
||||
@@ -144,7 +144,7 @@ Opções: `--all` (padrão), `--plugins ids`, `--category Social|Jogos|Streaming
|
||||
- **Mesmo PC:** duas instâncias, portas distintas (ex. 5000 e 5001).
|
||||
- **Dois PCs na mesma Wi‑Fi:** mesma porta (ex. 5000), nomes diferentes. Cada um segue o caso da própria máquina em [Como rodar](#como-rodar). Linux abre o Java do Linux; Windows e WSL abrem o Java do Windows.
|
||||
|
||||
Ver [docs/documentation/15_testes_e_gate.md](docs/documentation/15_testes_e_gate.md), [appendix_portas_e_multicast.md](docs/documentation/appendix_portas_e_multicast.md) e [17_teste_com_colega.md](docs/documentation/17_teste_com_colega.md).
|
||||
Ver [docs/usar.md](docs/usar.md) e [docs/rede.md](docs/rede.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+10
-25
@@ -1,30 +1,15 @@
|
||||
# Documentação — Pitiupi
|
||||
|
||||
Entrada no repositório: [README.md](../README.md) (quickstart e comandos).
|
||||
O Pitiupi é uma aplicação P2P em Java: o núcleo descobre colegas na rede local, troca mensagens e carrega plugins em JAR. Esta pasta descreve o programa como ele está no repositório.
|
||||
|
||||
Convenções editoriais: [documentation/00_convencoes.md](documentation/00_convencoes.md).
|
||||
|
||||
## Pilares
|
||||
|
||||
| Pasta / arquivo | Papel |
|
||||
| Leitor | Comece por |
|
||||
| --- | --- |
|
||||
| **[documentation/](documentation/README.md)** | Código, rede, plugins, operação e tutoriais |
|
||||
| **[references/](references/README.md)** | Bibliografia e notas (UDP, TCP, SPI, multicast) |
|
||||
| **[decision-register.md](decision-register.md)** | Decisões de desenho da plataforma |
|
||||
| Primeira execução | [comecar.md](comecar.md) |
|
||||
| Usar a janela e testar com um colega | [usar.md](usar.md) |
|
||||
| Entender UDP, TCP e presença | [rede.md](rede.md) |
|
||||
| Escrever um plugin | [nucleo-para-plugins.md](nucleo-para-plugins.md) e [plugins.md](plugins.md) |
|
||||
| Ler ou alterar o núcleo | [nucleo.md](nucleo.md) |
|
||||
| Decisões estáveis do desenho | [decision-register.md](decision-register.md) |
|
||||
| Livros e RFCs | [referencias.md](referencias.md) |
|
||||
|
||||
## Jornadas
|
||||
|
||||
| Leitor | Trilha |
|
||||
| --- | --- |
|
||||
| **Primeiro uso** | [00_quickstart](documentation/00_quickstart.md) → [09_sistema_de_plugins](documentation/09_sistema_de_plugins.md) → [10_tutorial_plugin](documentation/10_tutorial_plugin.md) → [11_plugins_referencia](documentation/11_plugins_referencia.md) |
|
||||
| **Redes (conceito + código)** | [16_fundamentacao_redes](documentation/16_fundamentacao_redes.md) → [05](documentation/05_udp_multicast.md)–[08](documentation/08_heartbeat_e_presenca.md) → [appendix_portas_e_multicast](documentation/appendix_portas_e_multicast.md) → [17_teste_com_colega](documentation/17_teste_com_colega.md) |
|
||||
| **Desenvolvedor núcleo** | [13_guia_desenvolvimento](documentation/13_guia_desenvolvimento.md) → [14_arquitetura_e_manutencao](documentation/14_arquitetura_e_manutencao.md) → [04_inicializacao_e_gui](documentation/04_inicializacao_e_gui.md) |
|
||||
|
||||
## Manutenção rápida
|
||||
|
||||
| Mudança | Onde documentar |
|
||||
| --- | --- |
|
||||
| UDP/TCP, multicast, portas | 05, 06, appendix, 17 |
|
||||
| SPI / plugins | 09, 10 |
|
||||
| Decisão de arquitetura | decision-register |
|
||||
| Débito técnico | engineering_backlog |
|
||||
O código do núcleo está em `pitiupi/`. Os plugins de referência estão em `plugins/chat`, `plugins/screencast` e `plugins/ping`.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Começar
|
||||
|
||||
O Pitiupi abre uma janela Java. Nela você informa um nome e uma porta, vê quem está na mesma sala e abre plugins (chat, compartilhamento de tela, ping).
|
||||
|
||||
Plugins, cache e logs ficam em **`.pitiupi/`** dentro da pasta do projeto. Essa pasta não entra no Git. O núcleo lê os JARs de `.pitiupi/plugins/`.
|
||||
|
||||
Você precisa de JDK 17 ou superior (`java` e `javac`) e do Apache Ant. No Linux o script também usa `python3`.
|
||||
|
||||
## Linux (Ubuntu instalado na máquina)
|
||||
|
||||
Abra o terminal, entre na pasta do projeto e rode:
|
||||
|
||||
```bash
|
||||
sudo apt install openjdk-21-jdk ant python3
|
||||
chmod +x scripts/pitiupi.sh
|
||||
./scripts/pitiupi.sh run --all
|
||||
```
|
||||
|
||||
A janela é a do Java do Linux. Cabo e Wi-Fi são as placas deste computador.
|
||||
|
||||
## Windows
|
||||
|
||||
Abra o Prompt de Comando, entre na pasta do projeto e rode:
|
||||
|
||||
```bat
|
||||
scripts\pitiupi.cmd run --all
|
||||
```
|
||||
|
||||
`java`, `javac` e `ant` precisam estar no PATH. O JDK pode ser instalado com `winget install Microsoft.OpenJDK.17`. O Apache Ant é o zip de <https://ant.apache.org/bindownload.cgi>: extraia e acrescente a pasta `bin` ao PATH. Feche o cmd e abra de novo.
|
||||
|
||||
A janela é a do Java do Windows. Cabo e Wi-Fi são as placas deste computador.
|
||||
|
||||
## WSL
|
||||
|
||||
Abra o Ubuntu do WSL, entre na pasta do projeto e use o mesmo comando do Linux:
|
||||
|
||||
```bash
|
||||
sudo apt install openjdk-21-jdk ant python3
|
||||
chmod +x scripts/pitiupi.sh
|
||||
./scripts/pitiupi.sh run --all
|
||||
```
|
||||
|
||||
A compilação usa o `javac` e o `ant` do Ubuntu. A janela abre no Java do Windows, com as placas reais, então o JDK 17 do Windows também precisa estar instalado. Abrir o `java` de dentro do WSL, sem esse script, não enxerga o Wi-Fi do Windows. O motivo está em [rede.md](rede.md).
|
||||
|
||||
## Depois que a janela abrir
|
||||
|
||||
1. No login, escolha um **nome** e uma **porta**. Na sala, os dois colegas usam a mesma porta, em geral `5000`.
|
||||
2. No mesmo computador, a segunda instância precisa de outra porta (por exemplo `5001`) e de outro nome. A mesma porta no mesmo PC não consegue abrir o socket duas vezes.
|
||||
3. Abra um plugin pela aba **Plugins**, ou clique em uma pessoa na lista lateral e escolha uma ação.
|
||||
|
||||
Como usar a lista, as configurações e o que fazer se ninguém aparecer: [usar.md](usar.md).
|
||||
|
||||
## O que o comando faz
|
||||
|
||||
`./scripts/pitiupi.sh run --all` (ou `scripts\pitiupi.cmd run --all`):
|
||||
|
||||
1. Compila o núcleo com Ant (`pitiupi/dist/pitiupi.jar`).
|
||||
2. Compila cada módulo listado em [`plugins.registry.json`](../plugins.registry.json) que tenha `build.xml` ou `Makefile` nesta máquina.
|
||||
3. Coloca um atalho de cada JAR em `.pitiupi/plugins/`.
|
||||
4. Abre a janela.
|
||||
|
||||
Outros comandos do script:
|
||||
|
||||
| Comando | O que faz |
|
||||
| --- | --- |
|
||||
| `build --all` | Compila e atualiza os atalhos, sem abrir a janela |
|
||||
| `link --all` | Só recria os atalhos, se os JARs já existem |
|
||||
| `list` | Mostra o id, a categoria e se o JAR foi gerado |
|
||||
| `check` | Confere `java`, `javac`, `ant`, `python3` e o JSON |
|
||||
|
||||
O campo `menuCategory` do JSON (`Social`, `Jogos`, `Streaming`) só filtra o script (`--category Social`). Ele não cria menu na janela.
|
||||
|
||||
Para compilar à mão, na pasta do projeto:
|
||||
|
||||
```bash
|
||||
cd pitiupi && ant jar
|
||||
cd plugins/chat && ant jar
|
||||
cd plugins/screencast && ant jar
|
||||
cd plugins/ping && ant jar
|
||||
```
|
||||
|
||||
Testes do núcleo: `cd pitiupi && ant test`.
|
||||
|
||||
## Pastas
|
||||
|
||||
```
|
||||
pitiupi/ clone
|
||||
├── plugins.registry.json catálogo que o script compila e vincula
|
||||
├── scripts/pitiupi.sh Linux e WSL
|
||||
├── scripts/pitiupi.cmd Windows
|
||||
├── pitiupi/ núcleo (Main, rede, janela, contrato de plugin)
|
||||
├── plugins/chat
|
||||
├── plugins/screencast
|
||||
├── plugins/ping
|
||||
└── .pitiupi/plugins JARs que a janela carrega (gerado, não versionado)
|
||||
```
|
||||
|
||||
O ponto de entrada é `pitiupi.Main`: prepara `.pitiupi/`, pede nome e porta e abre `MainShellFrame`.
|
||||
+29
-29
@@ -1,32 +1,32 @@
|
||||
# Registro de decisões — Pitiupi
|
||||
|
||||
| ID | Decisão | Motivação | Doc |
|
||||
| --- | --- | --- | --- |
|
||||
| D1 | Grupo multicast `224.0.0.3` | Endereço fixo para descoberta e broadcast na LAN da sala | [appendix_portas_e_multicast.md](documentation/appendix_portas_e_multicast.md) |
|
||||
| D2 | Mesma porta UDP e TCP por instância | Configurada no login; simplifica firewall em laboratório | [00_quickstart.md](documentation/00_quickstart.md) |
|
||||
| D3 | Heartbeat via UDP multicast | Presença sem conexão TCP permanente com todos os peers | [08_heartbeat_e_presenca.md](documentation/08_heartbeat_e_presenca.md) |
|
||||
| D4 | Timeout de peer ~15 s sem heartbeat | Remover usuários inativos da lista | [08_heartbeat_e_presenca.md](documentation/08_heartbeat_e_presenca.md) |
|
||||
| D5 | Framing TCP: `int` tamanho + bytes | Delimitar mensagens em stream orientado a bytes | [06_tcp_conexoes.md](documentation/06_tcp_conexoes.md) |
|
||||
| D6 | Mensagens `Serializable` em `byte[]` | Independência do transporte; plugins definem tipos | [07_mensagens_e_serializacao.md](documentation/07_mensagens_e_serializacao.md) |
|
||||
| D7 | Plugins via JAR + Java SPI | Carregamento dinâmico sem recompilar o núcleo | [09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md) |
|
||||
| D8 | Classloader por JAR de plugin | Isolar dependências; desserialização com `PluginObjectInputStream` | [09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md) |
|
||||
| D9 | Plugins em `PITIUPI_HOME/plugins` | Dados de execução em `.pitiupi/` na raiz do clone (não versionado). `PITIUPI_HOME` ainda pode apontar outro diretório | [00_quickstart.md](documentation/00_quickstart.md) |
|
||||
| D10 | Erros com `ErrorCode` | Mensagens PT na UI; `PitiupiException` no domínio | [13_guia_desenvolvimento.md](documentation/13_guia_desenvolvimento.md) |
|
||||
| D11 | Reinício após instalar/remover JAR | Mutar `plugins/` exige reiniciar o app para recarregar SPI | [04_inicializacao_e_gui.md](documentation/04_inicializacao_e_gui.md) |
|
||||
| D16 | UI em abas; sem `JFrame` de plugin | Conteúdo na `MainShellFrame`; galeria e peer actions abrem abas | [04_inicializacao_e_gui.md](documentation/04_inicializacao_e_gui.md) |
|
||||
| D17 | Goodbye + timeout de presença | `GoodbyeMessage` em multicast ao fechar; timeout ~15 s mantido | [08_heartbeat_e_presenca.md](documentation/08_heartbeat_e_presenca.md) |
|
||||
| D18 | Peer actions + convites | `PeerActionBroker`, `SessionInviteMessage`, badge (UI completa na shell) | [09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md) |
|
||||
| D19 | Chat unificado | Um JAR `plugins/chat` com sala geral, peer/IP e TCP privado; mpChat removido do catálogo | [11_plugins_referencia.md](documentation/11_plugins_referencia.md) |
|
||||
| D20 | Peer actions por plugin no popup | Lista lateral agrupa ações de todos os plugins (submenus), não só a aba ativa | [09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md) |
|
||||
| D21 | `onShellTabClosed` | Fechar aba do plugin na shell notifica o plugin (ex.: parar screencast) | [09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md) |
|
||||
| D22 | `PeerIpv4` para IP de peer na UI | No Windows, `InetAddress.getHostAddress()` pode devolver IPv6-mapeado e sufixo de zona (`%n`); chat e roteamento usam IPv4 pontilhado | [appendix_portas_e_multicast.md](documentation/appendix_portas_e_multicast.md) |
|
||||
| D23 | Foco de atividade e badges | **Adiado na v1** — núcleo (`PeerActivityFocus` / `PeerActivityInbox`) mantido; UI sem badges; recebimento passivo em sub-abas (`addTabIfAbsent`) | [09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md) |
|
||||
| D24 | Avisos de sessão (`SessionNoticeMessage`) | Início/fim de atividades (ex. `screen_cast` END ao parar share); extensível por plugin/canal | [09_sistema_de_plugins.md](documentation/09_sistema_de_plugins.md) |
|
||||
| D25 | Nome novo no heartbeat já existente | A lista do colega atualiza o `User` quando o `userName` muda | [08_heartbeat_e_presenca.md](documentation/08_heartbeat_e_presenca.md) |
|
||||
| D26 | Troca de porta sai da sala | Goodbye na porta antiga, lista local zerada, anúncio na porta nova | [appendix_portas_e_multicast.md](documentation/appendix_portas_e_multicast.md) |
|
||||
| D27 | TTL padrão 1, ajustável de 1 a 32 | 1 permanece na sub-rede local; valor maior é opção de configuração | [appendix_portas_e_multicast.md](documentation/appendix_portas_e_multicast.md) |
|
||||
| D28 | Saída Automático, Cabo ou Wi-Fi | Multicast, IP exibido e TCP de saída usam a interface escolhida | [appendix_portas_e_multicast.md](documentation/appendix_portas_e_multicast.md) |
|
||||
| D29 | Connect TCP com prazo de 5 s fora da EDT | Aviso só enquanto não há socket aberto para aquele IP | [06_tcp_conexoes.md](documentation/06_tcp_conexoes.md) |
|
||||
| D30 | `eth*` continua cabo, inclusive no WSL | A placa Wi-Fi do Windows não existe no Linux do WSL; reclassificar `eth` quebraria o Ubuntu nativo. Na sala o processo é o Java do Windows ou o Ubuntu instalado na máquina | [appendix_portas_e_multicast.md](documentation/appendix_portas_e_multicast.md) |
|
||||
Decisões que o código atual segue. O detalhe está nos capítulos ligados.
|
||||
|
||||
Novas decisões: adicionar linha nesta tabela e detalhar no arquivo técnico correspondente.
|
||||
| ID | Decisão | Motivação | Onde ler |
|
||||
| --- | --- | --- | --- |
|
||||
| D1 | Grupo multicast `224.0.0.3` | Endereço fixo para descoberta e broadcast na LAN da sala | [rede.md](rede.md) |
|
||||
| D2 | Mesma porta UDP e TCP por instância | Configurada no login; simplifica o firewall em laboratório | [comecar.md](comecar.md) |
|
||||
| D3 | Heartbeat via UDP multicast | Presença sem conexão TCP permanente com todos os peers | [rede.md](rede.md) |
|
||||
| D4 | Timeout de peer cerca de 15 s sem heartbeat | Remover usuários inativos da lista | [rede.md](rede.md) |
|
||||
| D5 | Framing TCP: `int` tamanho + bytes | Delimitar mensagens num fluxo de bytes | [rede.md](rede.md) |
|
||||
| D6 | Mensagens `Serializable` em `byte[]` | O transporte não depende dos campos; o plugin define o tipo | [rede.md](rede.md) |
|
||||
| D7 | Plugins via JAR + SPI | Carregar uma classe nova sem recompilar o núcleo | [plugins.md](plugins.md) |
|
||||
| D8 | Classloader por JAR | Isolar classes e desserializar com `PluginObjectInputStream` | [plugins.md](plugins.md) |
|
||||
| D9 | Plugins em `PITIUPI_HOME/plugins` | Dados de execução em `.pitiupi/` na raiz do clone. `PITIUPI_HOME` pode apontar outro diretório | [comecar.md](comecar.md) |
|
||||
| D10 | Erros com `ErrorCode` | Mensagens em português na janela; `PitiupiException` no domínio | [nucleo.md](nucleo.md) |
|
||||
| D11 | Reinício após instalar ou remover JAR | A lista de plugins é lida na subida | [usar.md](usar.md) |
|
||||
| D16 | UI em abas, sem `JFrame` de plugin | O painel fica na `MainShellFrame`; galeria e peer actions abrem a aba | [nucleo-para-plugins.md](nucleo-para-plugins.md) |
|
||||
| D17 | Goodbye e timeout de presença | `GoodbyeMessage` em multicast ao fechar; timeout de cerca de 15 s | [rede.md](rede.md) |
|
||||
| D18 | Peer actions e convites | `PeerActionBroker` e `SessionInviteMessage` na shell | [plugins.md](plugins.md) |
|
||||
| D19 | Chat num único módulo | `plugins/chat` cobre sala, um IP e TCP privado | [plugins.md](plugins.md) |
|
||||
| D20 | Peer actions de todos os plugins no popup | A lista agrupa ações em submenu por plugin, não só da aba aberta | [nucleo-para-plugins.md](nucleo-para-plugins.md) |
|
||||
| D21 | `onShellTabClosed` | Fechar a aba avisa o plugin para parar timer ou captura | [nucleo-para-plugins.md](nucleo-para-plugins.md) |
|
||||
| D22 | `PeerIpv4` na interface | No Windows o endereço pode vir mapeado em IPv6 ou com sufixo de zona | [rede.md](rede.md) |
|
||||
| D23 | Sem selo numérico na lista | O recebimento aparece na aba do plugin. O núcleo guarda foco de atividade, a lista não desenha badge | [plugins.md](plugins.md) |
|
||||
| D24 | `SessionNoticeMessage` | Início e fim de uma atividade (por exemplo o fim do compartilhamento de tela) | [plugins.md](plugins.md) |
|
||||
| D25 | Nome novo no heartbeat já existente | A lista do colega atualiza o `User` quando o `userName` muda | [rede.md](rede.md) |
|
||||
| D26 | Troca de porta sai da sala | Goodbye na porta antiga, lista local zerada, anúncio na porta nova | [rede.md](rede.md) |
|
||||
| D27 | TTL padrão 1, de 1 a 32 | 1 permanece na sub-rede local | [rede.md](rede.md) |
|
||||
| D28 | Saída Automático, Cabo ou Wi-Fi | Multicast, IP exibido e TCP de saída usam a interface escolhida | [rede.md](rede.md) |
|
||||
| D29 | TCP com prazo de 5 s fora da thread da janela | O aviso aparece só enquanto não há socket aberto para aquele IP | [rede.md](rede.md) |
|
||||
| D30 | `eth*` continua cabo, inclusive no WSL | A placa Wi-Fi do Windows não existe no Linux do WSL. Tratar todo `eth` como Wi-Fi quebraria o Ubuntu nativo | [rede.md](rede.md) |
|
||||
@@ -1,32 +0,0 @@
|
||||
# Convenções da documentação
|
||||
|
||||
Índice geral: [docs/README.md](../README.md).
|
||||
|
||||
## Idioma e tom
|
||||
|
||||
- Português brasileiro, termos de Redes e Java como no código (`ConnectionManager`, `MulticastSocket`).
|
||||
- Parágrafos completos; evitar listas longas quando um texto curto explica melhor.
|
||||
|
||||
## Blocos teoria × implementação
|
||||
|
||||
Em capítulos de rede e arquitetura, quando um conceito for central, use mentalmente três camadas:
|
||||
|
||||
1. **Teoria** — como o protocolo ou padrão funciona em geral (com link para [references/README.md](../references/README.md) se precisar de fonte).
|
||||
2. **No Pitiupi** — classes e fluxo reais.
|
||||
3. **Limitação** — o que o projeto **não** faz (ex.: sem criptografia, sem ICMP nativo no plugin Ping).
|
||||
|
||||
Não descreva funcionalidade futura como se já existisse.
|
||||
|
||||
## Código nos exemplos
|
||||
|
||||
- Trechos de tutorial são **ilustrativos** quando indicado; complete o plugin conforme [10_tutorial_plugin.md](10_tutorial_plugin.md).
|
||||
- APIs citadas devem existir no núcleo atual (`PluginWorkspace`, `createPlugin(ConnectionManager, PluginWorkspace)`).
|
||||
|
||||
## Diagramas
|
||||
|
||||
- Preferir [Mermaid](https://github.blog/2014-02-14-render-diagrams-markdown/) em arquivos `.md` (GitHub).
|
||||
- Diagramas pequenos, um fluxo por figura.
|
||||
|
||||
## Manutenção
|
||||
|
||||
Alterou protocolo, SPI ou boot? Atualize o capítulo numerado correspondente e, se for decisão estável, [decision-register.md](../decision-register.md). Ver [13_guia_desenvolvimento.md](13_guia_desenvolvimento.md).
|
||||
@@ -1,79 +0,0 @@
|
||||
# Quickstart
|
||||
|
||||
O `run --all` compila o núcleo, os plugins do catálogo e abre a janela. Plugins, cache e logs ficam em **`.pitiupi/`** na pasta do projeto. JDK **17 ou superior** (`java` e `javac`) e Apache Ant. O script do Linux também usa `python3`.
|
||||
|
||||
## Linux (Ubuntu instalado na máquina)
|
||||
|
||||
Abra o terminal, entre na pasta do projeto e rode:
|
||||
|
||||
```bash
|
||||
sudo apt install openjdk-21-jdk ant python3
|
||||
chmod +x scripts/pitiupi.sh
|
||||
./scripts/pitiupi.sh run --all
|
||||
```
|
||||
|
||||
A janela é a do Java do Linux. Cabo e Wi-Fi são as placas deste computador.
|
||||
|
||||
## Windows
|
||||
|
||||
Abra o **Prompt de Comando**: tecla Windows, digite `cmd` e pressione Enter. Entre na pasta em que o projeto foi baixado e rode:
|
||||
|
||||
```bat
|
||||
cd %USERPROFILE%\Downloads\pitiupi
|
||||
scripts\pitiupi.cmd run --all
|
||||
```
|
||||
|
||||
Se a pasta for outra, troque o `cd`. No PowerShell o comando é o mesmo: `scripts\pitiupi.cmd run --all`.
|
||||
|
||||
`java`, `javac` e `ant` precisam estar no PATH. No PowerShell, o JDK:
|
||||
|
||||
```powershell
|
||||
winget install Microsoft.OpenJDK.17
|
||||
```
|
||||
|
||||
O Apache Ant é o zip de <https://ant.apache.org/bindownload.cgi>: extraia e acrescente a pasta `bin` ao PATH. Feche o cmd e abra de novo.
|
||||
|
||||
A janela é a do Java do Windows. Cabo e Wi-Fi são as placas deste computador.
|
||||
|
||||
## WSL
|
||||
|
||||
Abra o **Ubuntu** do WSL, não o cmd. Entre na pasta do projeto e use o comando do Linux:
|
||||
|
||||
```bash
|
||||
sudo apt install openjdk-21-jdk ant python3
|
||||
chmod +x scripts/pitiupi.sh
|
||||
./scripts/pitiupi.sh run --all
|
||||
```
|
||||
|
||||
Se o projeto foi baixado no Windows, por exemplo em Downloads:
|
||||
|
||||
```bash
|
||||
cd /mnt/c/Users/SEU_USUARIO/Downloads/pitiupi
|
||||
chmod +x scripts/pitiupi.sh
|
||||
./scripts/pitiupi.sh run --all
|
||||
```
|
||||
|
||||
A compilação usa o `javac` e o `ant` do Ubuntu. A janela abre no Windows, com as placas reais, então o JDK 17 do Windows também precisa estar instalado (`winget install Microsoft.OpenJDK.17` no PowerShell). Abrir o `java` de dentro do WSL, sem esse comando, não mostra o Wi-Fi do Windows. O motivo está em [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md).
|
||||
|
||||
## Depois que a janela abrir
|
||||
|
||||
Login com nome e porta (na sala, em geral `5000` nos dois). Abra plugins pela aba **Plugins**. Clique em **Todos** ou em um peer na lista lateral para ver as ações de cada plugin.
|
||||
|
||||
Validação rápida do núcleo: `cd pitiupi && ant test`.
|
||||
|
||||
## Build sem abrir a janela
|
||||
|
||||
No Linux ou no WSL, na pasta do projeto:
|
||||
|
||||
```bash
|
||||
./scripts/pitiupi.sh build --all
|
||||
./scripts/pitiupi.sh link --all
|
||||
```
|
||||
|
||||
Ou manualmente:
|
||||
|
||||
```bash
|
||||
cd pitiupi && ant jar
|
||||
cd plugins/chat && ant jar
|
||||
cd plugins/screencast && ant jar
|
||||
```
|
||||
@@ -1,27 +0,0 @@
|
||||
# Módulos do repositório
|
||||
|
||||
```
|
||||
pitiupi/ # clone (este repositório)
|
||||
├── plugins.registry.json
|
||||
├── scripts/pitiupi.sh
|
||||
├── pitiupi/ # núcleo P2P (executável)
|
||||
├── plugins/
|
||||
│ ├── chat/ # chat unificado (UDP/multicast/TCP + TCP privado)
|
||||
│ └── screencast/ # compartilhamento de tela
|
||||
└── docs/
|
||||
```
|
||||
|
||||
## Núcleo (`pitiupi/`)
|
||||
|
||||
- **Build:** Apache Ant.
|
||||
- **Entrada:** `pitiupi.Main` → login → `MainShellFrame`.
|
||||
- **Messaging:** `pitiupi.application.messaging.MessageDispatcher`.
|
||||
|
||||
## Plugins de referência
|
||||
|
||||
| Pasta | Build | JAR típico | SPI |
|
||||
| --- | --- | --- | --- |
|
||||
| `plugins/chat` | `ant jar` | `dist/pitiupi.chat.jar` | `pitiupi.chat.plugin.ChatPlugin` |
|
||||
| `plugins/screencast` | `ant jar` | `dist/pitiupi.screenCast.jar` | `pitiupi.screenCast.plugin.ScreenCastPlugin` |
|
||||
|
||||
Novo módulo: [10_tutorial_plugin.md](10_tutorial_plugin.md).
|
||||
@@ -1,17 +0,0 @@
|
||||
# Build — Ant e NetBeans
|
||||
|
||||
| Módulo | Build | Java |
|
||||
| --- | --- | --- |
|
||||
| `pitiupi` | Ant | 17 |
|
||||
| `plugins/chat` | Ant | 17 |
|
||||
| `plugins/screencast` | Ant | 17 |
|
||||
|
||||
```bash
|
||||
cd pitiupi && ant jar
|
||||
cd plugins/chat && ant jar
|
||||
cd plugins/screencast && ant jar
|
||||
```
|
||||
|
||||
Classpath dos plugins: `../../pitiupi/dist/pitiupi.jar`.
|
||||
|
||||
Catálogo: [`plugins.registry.json`](../../plugins.registry.json).
|
||||
@@ -1,151 +0,0 @@
|
||||
# Visão geral
|
||||
|
||||
O `Pitiupi` é uma plataforma P2P baseada em uma arquitetura de plugins,
|
||||
desenvolvida para permitir a criação de aplicações distribuídas sobre uma
|
||||
infraestrutura comum de comunicação em rede.
|
||||
|
||||
A aplicação fornece mecanismos de descoberta de usuários, comunicação entre
|
||||
peers, troca de mensagens serializadas e integração de extensões por meio de
|
||||
plugins. A comunicação de rede utiliza dois protocolos de transporte: UDP,
|
||||
empregado principalmente na descoberta e comunicação multicast entre as
|
||||
instâncias, e TCP, utilizado para comunicação direta e persistente entre peers.
|
||||
|
||||
O projeto serve como base para experimentação de aplicações distribuídas e
|
||||
desenvolvimento de extensões via plugins.
|
||||
|
||||
Índice: [README.md](README.md).
|
||||
|
||||
# Arquitetura geral
|
||||
|
||||
A aplicação é dividida em três grupos principais:
|
||||
|
||||
- **Núcleo da aplicação**: a `MainShellFrame` coordena GUI, rede e plugins.
|
||||
- **Sistema de plugins**: extensões em JAR carregadas de `PITIUPI_HOME/plugins` (dev: `./scripts/pitiupi.sh link --all`).
|
||||
- **Comunicação de rede**: UDP (`SocketUDP`) e TCP (`SocketTCP`, `TcpConnection`).
|
||||
|
||||
## Inicialização da aplicação
|
||||
|
||||
1. `Main` garante o layout em `PITIUPI_HOME` e abre o `LoginDialog` (nome e porta, PT-BR, ~30% da área útil do monitor, centralizado).
|
||||
2. Após confirmar o login, a `MainShellFrame` é exibida (1024×768, centralizada).
|
||||
3. O `SocketUDP` associa-se à porta configurada e ingressa no grupo multicast.
|
||||
4. O `SocketTCP` cria um `ServerSocket` na mesma porta para conexões TCP.
|
||||
5. Os mecanismos de comunicação iniciam threads de recepção.
|
||||
6. O `HeartbeatManager` envia heartbeats periodicamente; o `NetworkUsersPanel` lista peers via `PresenceService`.
|
||||
7. O `PluginManager` carrega os JARs em `plugins/` via SPI, com `ShellPluginWorkspace` ligado às abas centrais.
|
||||
|
||||
Ver também: [05_udp_multicast.md](05_udp_multicast.md), [09_sistema_de_plugins.md](09_sistema_de_plugins.md).
|
||||
|
||||
### Teste manual T-LOGIN-01 (login centralizado)
|
||||
|
||||
1. Compilar e executar: `./scripts/pitiupi.sh run --all` na raiz do repositório.
|
||||
2. O `LoginDialog` deve aparecer **no centro** do monitor principal (WSL/KDE), não no canto superior esquerdo.
|
||||
3. Após entrar, a `MainShellFrame` (1024×768) também deve abrir centralizada.
|
||||
|
||||
A posição exata depende do gerenciador de janelas; o núcleo recentra após exibir a janela.
|
||||
|
||||
### Atualizar plugin Chat (Onda E)
|
||||
|
||||
O JAR do módulo é `plugins/chat/dist/pitiupi.chat.jar` (catálogo `plugins.registry.json`). Não use `dist/chat.jar`.
|
||||
|
||||
```bash
|
||||
cd plugins/chat && ant clean jar
|
||||
./scripts/pitiupi.sh link --all
|
||||
```
|
||||
|
||||
Isso recria o symlink `.pitiupi/plugins/chat.jar` → o JAR buildado, na raiz do clone. O comando `link` lista o destino real de cada `.jar`.
|
||||
|
||||
# Interface gráfica
|
||||
|
||||
A interface gráfica é Swing, organizada em shell (`pitiupi.presentation.shell`):
|
||||
|
||||
| Região | Componente |
|
||||
|--------|------------|
|
||||
| Centro | `TabWorkspacePanel` — aba **Plugins** (galeria; abre plugin sob demanda), abas de plugin **fecháveis** (×), aba **Configurações** |
|
||||
| Leste | `NetworkUsersPanel` — “Usuários nesta rede”, peer actions (badges na lista: **D23**, adiados — ver [decision-register.md](../decision-register.md)) |
|
||||
| Sul | Barra de status (porta e nome local) |
|
||||
| Menu | `ShellMenuBar` — apenas **Ajuda → Sobre** (sair pelo X da janela) |
|
||||
|
||||
Plugins expõem `getName()` (técnico), `getIdentifier()` (SPI) e `getDisplayName()` (texto na galeria e nas abas).
|
||||
|
||||
A aba **Configurações** mostra em cima **Sua conexão**: nome e porta editáveis, saída (Automático, Cabo ou Wi-Fi), TTL, IP local, interface em uso, ID da instância e contagem de peers. **Aplicar** grava a preferência e, se porta, saída ou TTL mudaram, reabre a rede na hora. IP e ID continuam somente leitura. Embaixo fica a tabela de JARs instalados.
|
||||
|
||||
Durante a inicialização, cada plugin recebe `ConnectionManager` e `PluginWorkspace`
|
||||
no método `createPlugin()`. Menus multicast na barra superior não são mais usados;
|
||||
ações para todos os peers migram para **peer actions** (contexto na lista lateral).
|
||||
Ao iniciar, só as abas **Plugins** e **Configurações** ficam visíveis. Cada plugin registra um painel com `bindShellWorkspaceContent`; a aba abre ao clicar na galeria ou via peer action. Referência: **Conversa na rede** (`ChatPanel`) e **Compartilhar tela** (`ScreenCastPanel`).
|
||||
|
||||
O **chat** usa campo de mensagem compacto (~2 linhas, quebra de linha; barra de rolagem vertical só se o texto não couber). O histórico ocupa a maior parte da altura da aba (divisor proporcional ao abrir a janela). Cada pessoa tem uma sub-aba; o × fecha essa sub-aba mesmo com outras ao lado. Se a pessoa muda de cabo para Wi-Fi, a sub-aba já aberta passa a usar o IP novo em vez de aparecer uma segunda vez com o mesmo nome.
|
||||
|
||||
Canal TCP privado do chat fica no mesmo `ChatPanel` (histórico separado por conversa: sala geral, peer por IP, privado com toggle). O núcleo mantém `PeerActivityInbox` para atividade por peer; indicadores visuais completos na lista lateral ainda não fazem parte da v1 (**D23**).
|
||||
|
||||
Ao fechar a aba superior de **Compartilhar tela** (×), o núcleo chama `Plugin.onShellTabClosed()` — o plugin interrompe captura e envio (toggle desligado). Reabrir pela galeria deve mostrar compartilhamento inativo.
|
||||
|
||||
**TCP (transporte)** envia uma mensagem serializada pelo núcleo, na porta desta instância. A primeira conexão com um IP que ainda não tem socket aberto corre fora da thread da janela: o chat mostra “Conectando em …”, espera no máximo cerca de 5 s e, se falhar, avisa uma vez. Mensagens seguintes no mesmo socket não repetem esse aviso. **TCP privado** (toggle) abre conexão dedicada ao plugin entre dois peers, com o mesmo aviso só na abertura. Na UI só se informa o **IP** do peer: UDP/TCP unicast do Pitiupi usa sempre a **mesma porta** desta instância (convenção de sala: todos com a mesma porta, ex. 5000 e 5001 em testes no mesmo PC).
|
||||
|
||||
Textos da UI (destino, combo de forma de envio e linha **“Como esta mensagem vai”**) usam linguagem simples e o termo técnico entre parênteses (multicast UDP, UDP unicast, TCP). Na sala, o transporte não é só multicast: o combo pode escolher cópias unicast ou TCP para cada pessoa, conforme o plugin.
|
||||
|
||||
Ver [12_menus_unicast_multicast.md](12_menus_unicast_multicast.md).
|
||||
|
||||
## Boot (sequência)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Main
|
||||
participant Home as PitiupiHome
|
||||
participant Login as LoginDialog
|
||||
participant Shell as MainShellFrame
|
||||
participant CM as ConnectionManager
|
||||
participant PM as PluginManager
|
||||
|
||||
Main->>Home: ensureLayout()
|
||||
Main->>Login: showLogin()
|
||||
Login-->>Main: nome + porta
|
||||
Main->>Shell: new MainShellFrame(nome, porta)
|
||||
Shell->>CM: iniciar UDP/TCP/heartbeat
|
||||
Shell->>PM: carregar JARs SPI
|
||||
PM-->>Shell: createPlugin(cm, workspace)
|
||||
```
|
||||
|
||||
## Camadas de comunicação
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph ui [presentation.shell]
|
||||
Shell[MainShellFrame]
|
||||
Users[NetworkUsersPanel]
|
||||
end
|
||||
subgraph app [application + control]
|
||||
CM[ConnectionManager]
|
||||
HB[HeartbeatManager]
|
||||
Disp[MessageDispatcher]
|
||||
end
|
||||
subgraph net [net]
|
||||
UDP[SocketUDP]
|
||||
TCP[SocketTCP]
|
||||
end
|
||||
subgraph ext [plugins]
|
||||
PL[Plugin JARs]
|
||||
end
|
||||
Shell --> CM
|
||||
Users --> CM
|
||||
CM --> UDP
|
||||
CM --> TCP
|
||||
CM --> HB
|
||||
Disp --> CM
|
||||
PL --> Disp
|
||||
UDP --> PL
|
||||
TCP --> PL
|
||||
```
|
||||
|
||||
O UDP multicast sustenta presença e mensagens broadcast/unicast; o TCP sustenta
|
||||
canais diretos com framing de mensagens. Detalhes em [05](05_udp_multicast.md)
|
||||
e [06](06_tcp_conexoes.md).
|
||||
|
||||
### Exemplo: um envio UDP até outro peer
|
||||
|
||||
1. Plugin chama `MessageDispatcher` (ou `ConnectionManager`) com `TransportMode` e destino.
|
||||
2. Mensagem serializada (`Message.toByteArray()`).
|
||||
3. `SocketUDP` monta `DatagramPacket` (multicast `224.0.0.3` ou unicast ao IP do peer).
|
||||
4. Peer receptor: thread de `SocketUDP.receive()` desserializa e `PluginManager` entrega ao plugin que declarou o tipo em `getMessages()`.
|
||||
|
||||
Ver [07_mensagens_e_serializacao.md](07_mensagens_e_serializacao.md).
|
||||
@@ -1,114 +0,0 @@
|
||||
# Comunicação UDP
|
||||
|
||||
**Teoria:** UDP envia datagramas sem conexão; multicast replica um pacote na LAN para todos os membros do grupo. **No Pitiupi:** presença e muitas mensagens de plugins usam o mesmo `MulticastSocket` na porta do login. **Limitação:** sem garantia de entrega nem ordem — plugins com imagens grandes devem considerar perda e MTU.
|
||||
|
||||
A classe `SocketUDP` é responsável pela comunicação UDP da aplicação.
|
||||
|
||||
Ela utiliza um `MulticastSocket` associado à porta configurada pela aplicação e
|
||||
ingressa no grupo multicast `224.0.0.3`. Por ser baseada em UDP, a comunicação não
|
||||
estabelece uma conexão permanente entre os peers.
|
||||
|
||||
A classe estende `Thread`, mantendo um processo contínuo de recepção de datagramas
|
||||
enquanto a aplicação está em execução.
|
||||
|
||||
O SocketUDP suporta dois modos de envio:
|
||||
|
||||
- **multicast**, destinado ao grupo de peers
|
||||
- **unicast**, destinado diretamente ao endereço IP de um peer específico
|
||||
|
||||
### Inicialização
|
||||
|
||||
Durante sua inicialização, o `SocketUDP`:
|
||||
|
||||
1. obtém o endereço do grupo multicast;
|
||||
2. cria um `MulticastSocket` na **porta escolhida no login** (mesma porta TCP da sala);
|
||||
3. configura os buffers de envio e recepção;
|
||||
4. habilita o reuso do endereço;
|
||||
5. define o TTL (padrão **1**, alterável na aba Configurações) e a interface de saída;
|
||||
6. 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.
|
||||
|
||||
### Interface de rede
|
||||
|
||||
`LocalNetworkInfo.preferredNetworkInterface()` escolhe a interface para o multicast: **Ethernet antes de Wi‑Fi**; em cabo APIPA (`169.254.*`) prioriza esse endereço. Em sala **só Wi‑Fi**, a interface wireless é usada normalmente. Requisito: peers na **mesma sub-rede** e multicast permitido — ver [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md).
|
||||
|
||||
### Sequência didática (plugin → peer B)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UI as Plugin_UI
|
||||
participant Disp as MessageDispatcher
|
||||
participant CM as ConnectionManager
|
||||
participant UDP as SocketUDP
|
||||
participant LAN as LAN_UDP
|
||||
participant UDPb as SocketUDP_peerB
|
||||
participant PM as PluginManager_peerB
|
||||
|
||||
UI->>Disp: enviar Message + modo + destino
|
||||
Disp->>CM: send UDP
|
||||
CM->>UDP: sendUnicast ou sendMulticast
|
||||
UDP->>LAN: DatagramPacket
|
||||
LAN->>UDPb: receive
|
||||
UDPb->>PM: byte[] desserializado
|
||||
PM->>UI: receiveMessage no plugin
|
||||
```
|
||||
|
||||
**Falhas comuns:** firewall bloqueando UDP na porta do login; portas diferentes entre peers; isolamento de clientes no Wi‑Fi; um processo dentro do WSL e o outro no Java do Windows. Onde abrir a janela: [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md).
|
||||
@@ -1,207 +0,0 @@
|
||||
# Comunicação TCP
|
||||
|
||||
**Teoria:** TCP oferece fluxo confiável e ordenado entre dois sockets; a aplicação ainda precisa definir **limites de mensagem**. **No Pitiupi:** framing `int` tamanho + bytes serializados. **Limitação:** uma conexão por endereço IP de peer no mapa gerenciado por `TcpConnectionManager`.
|
||||
|
||||
A comunicação TCP é implementada principalmente por `SocketTCP`, com o mapa de conexões em `TcpConnectionManager`.
|
||||
|
||||
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.
|
||||
|
||||
**Teoria:** `connect` sem prazo pode ficar parado até o sistema operacional desistir. **No Pitiupi:** `ConnectionFactory` usa `Socket.connect` com prazo de **5 s** (`CONNECT_TIMEOUT_MS`). O chat dispara essa abertura numa thread do núcleo (`sendTcpAsync` / `connectPluginTcpAsync`) e só mostra “Conectando em …” quando ainda não existe socket aberto para aquele IP. **Limitação:** o prazo não é configurável na tela; o timeout de leitura depois do handshake continua longo (minutos).
|
||||
|
||||
Ao mudar porta, saída ou TTL, o `ConnectionManager` envia goodbye, fecha as TCP locais e reabre o `ServerSocket`. Se a porta nova não abre, a sala anterior é restaurada.
|
||||
|
||||
O `TcpConnectionManager` mantém um mapa `InetAddress` → `TcpConnection`, permitindo reutilizar a mesma conexão para vários envios ao mesmo peer.
|
||||
|
||||
### 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 ao `PluginManager`, que despacha para os plugins cujo `getMessages()` inclui o tipo.
|
||||
|
||||
O plugin identifica o tipo em `receiveMessage()` e processa na thread adequada (UI via `SwingUtilities.invokeLater` quando necessário).
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Sender as TcpConnection_envio
|
||||
participant Stream as DataOutputStream
|
||||
participant Net as TCP
|
||||
participant Recv as TcpConnection_recepção
|
||||
|
||||
Sender->>Stream: writeInt(tamanho)
|
||||
Sender->>Stream: write(bytes)
|
||||
Stream->>Net: fluxo de bytes
|
||||
Net->>Recv: readInt()
|
||||
Recv->>Recv: readFully(N bytes)
|
||||
Recv->>Recv: PluginManager.dispatch
|
||||
```
|
||||
|
||||
Quando usar TCP no Pitiupi: chat e screencast oferecem TCP na UI; presença continua em UDP. Compare com [05_udp_multicast.md](05_udp_multicast.md) e [16_fundamentacao_redes.md](16_fundamentacao_redes.md).
|
||||
@@ -1,33 +0,0 @@
|
||||
# Mensagens
|
||||
|
||||
### Message
|
||||
|
||||
A classe `Message` representa a estrutura base das mensagens trocadas pela aplicação.
|
||||
|
||||
Todas as mensagens utilizadas pelo sistema devem herdar dessa classe. Ela implementa
|
||||
`Serializable`, permitindo que objetos de mensagem sejam convertidos em vetores de
|
||||
bytes para transmissão pela rede.
|
||||
|
||||
A serialização é realizada pelo método `toByteArray()`, que transforma uma instância
|
||||
da mensagem em uma representação binária enviada pelos mecanismos de comunicação.
|
||||
|
||||
As mensagens específicas devem estender essa classe, adicionando os atributos e
|
||||
comportamentos necessários para cada funcionalidade.
|
||||
|
||||
A camada de transporte não depende do conteúdo específico das mensagens. Tanto o
|
||||
UDP quanto o TCP recebem mensagens serializadas como `byte[]`.
|
||||
|
||||
## Despacho aos plugins
|
||||
|
||||
1. **Registro:** cada plugin declara `List<Class<? extends Message>> getMessages()`.
|
||||
2. **Recepção UDP:** `SocketUDP` entrega bytes ao `PluginManager`, que usa `PluginObjectInputStream` com o classloader do JAR do plugin.
|
||||
3. **Recepção TCP:** `TcpConnection` lê um frame completo e o `PluginManager` tenta o mesmo despacho.
|
||||
4. **Tipos desconhecidos:** se nenhum plugin reconhece, a mensagem é ignorada (sem crash do núcleo).
|
||||
|
||||
Mensagens de infraestrutura (`HeartbeatMessage`, `GoodbyeMessage`, etc.) são tratadas no núcleo antes ou em paralelo ao despacho de plugins — ver [08_heartbeat_e_presenca.md](08_heartbeat_e_presenca.md).
|
||||
|
||||
## Riscos da desserialização Java
|
||||
|
||||
Peers devem usar **mesma versão** das classes de mensagem. JARs de plugins devem vir de fonte confiável (instalação local pelo usuário). O núcleo **não** implementa sandbox de desserialização — ver [09_sistema_de_plugins.md](09_sistema_de_plugins.md).
|
||||
|
||||
Notas: [references/notas/java_serializacao.md](../references/notas/java_serializacao.md).
|
||||
@@ -1,91 +0,0 @@
|
||||
# Heartbeat e presença
|
||||
|
||||
**Presença ≠ conexão TCP:** ver um peer na lista lateral significa que heartbeats UDP recentes foram recebidos; ainda assim o primeiro envio TCP pode abrir um novo socket.
|
||||
|
||||
### Heartbeat
|
||||
|
||||
O mecanismo de heartbeat é utilizado para identificar quais usuários estão ativos
|
||||
na rede.
|
||||
|
||||
A aplicação envia periodicamente uma `HeartbeatMessage` contendo informações do usuário,
|
||||
um identificador único da instância da aplicação e um timestamp.
|
||||
|
||||
Essas mensagens são transmitidas utilizando a comunicação UDP multicast.
|
||||
|
||||
Quando outra instância recebe uma mensagem de heartbeat, o `SocketUDP` identifica
|
||||
o tipo da mensagem e encaminha o objeto ao `HeartbeatManager`, juntamente com o endereço
|
||||
IP de origem.
|
||||
|
||||
O `HeartbeatManager` registra ou atualiza o peer correspondente, utilizando o
|
||||
identificador da instância para diferenciá-lo das demais.
|
||||
|
||||
Um peer é considerado inativo quando permanece mais de 15 segundos sem receber um novo
|
||||
heartbeat. O `HeartbeatManager` então remove o peer da lista e notifica todos os
|
||||
componentes registrados como listeners de mudanças de peers.
|
||||
|
||||
Esses listeners podem incluir diferentes partes da aplicação, como componentes
|
||||
da interface gráfica, mecanismos de comunicação (como o `SocketTCP`) e também plugins
|
||||
que desejem reagir à presença ou ausência de usuários na rede.
|
||||
|
||||
Dessa forma, o sistema permite que tanto o núcleo da aplicação quanto extensões
|
||||
externas sejam notificados sobre alterações no estado da rede, mantendo a arquitetura
|
||||
flexível e extensível.
|
||||
|
||||
O fluxo completo é:
|
||||
|
||||
```
|
||||
HeartbeatManager
|
||||
|
|
||||
| cria HeartbeatMessage
|
||||
|
|
||||
v
|
||||
Message.toByteArray()
|
||||
|
|
||||
v
|
||||
SocketUDP.sendMulticast()
|
||||
|
|
||||
v
|
||||
UDP Multicast
|
||||
|
|
||||
v
|
||||
SocketUDP.receive()
|
||||
|
|
||||
v
|
||||
HeartbeatManager.receiveHeartbeat()
|
||||
|
|
||||
v
|
||||
Lista de peers ativos
|
||||
|
|
||||
+----> Interface de usuários online
|
||||
|
|
||||
+----> SocketTCP (PeerListener)
|
||||
|
|
||||
+----> Plugins (PeerListener)
|
||||
```
|
||||
|
||||
### Parâmetros práticos
|
||||
|
||||
| Item | Valor no núcleo | Efeito na sala |
|
||||
| --- | --- | --- |
|
||||
| Grupo multicast | `224.0.0.3` | Todos na mesma porta participam |
|
||||
| Intervalo de envio | ~5 s | Lista atualiza em alguns segundos |
|
||||
| Timeout de ausência | ~15 s | Peer some após queda ou fechamento sem goodbye |
|
||||
| Goodbye | multicast ao sair | Remove peer mais rápido que só timeout |
|
||||
|
||||
Em **Wi‑Fi de laboratório**, heartbeat usa o mesmo multicast que o chat na sala geral — mesma porta, mesma sub-rede, sem isolamento AP→AP. Ver [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md) e [17_teste_com_colega.md](17_teste_com_colega.md).
|
||||
|
||||
### Nome e troca de sala
|
||||
|
||||
O nome vai dentro do `HeartbeatMessage` que já existe. Se o nome mudar, o receptor atualiza o peer na lista e avisa a interface; não há outra mensagem só para isso.
|
||||
|
||||
Trocar a porta (ou a saída, ou o TTL) envia `GoodbyeMessage` na sala atual, esvazia a lista local e anuncia de imediato depois do novo socket. Quem ficou na porta antiga deixa de ver esta instância. Quem já está na porta nova aparece no anúncio seguinte. Se o goodbye se perder, o timeout de cerca de 15 s ainda remove o peer.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
HM[HeartbeatManager] -->|HeartbeatMessage| UDP[SocketUDP multicast]
|
||||
UDP --> LAN[LAN]
|
||||
LAN --> UDP2[SocketUDP outro peer]
|
||||
UDP2 --> HM2[HeartbeatManager]
|
||||
HM2 --> PS[PresenceService]
|
||||
PS --> UI[NetworkUsersPanel]
|
||||
```
|
||||
@@ -1,93 +0,0 @@
|
||||
# Sistema de plugins
|
||||
|
||||
O sistema de plugins permite extensão modular sem alterar o núcleo. O contrato está em `pitiupi.models.Plugin` (classe abstrata).
|
||||
|
||||
**Breaking change (pós-v1):** plugins devem implementar apenas `createPlugin(ConnectionManager, PluginWorkspace)`. A API antiga com `GuiManager` e menus por categoria foi removida.
|
||||
|
||||
## Interface Plugin
|
||||
|
||||
Métodos obrigatórios:
|
||||
|
||||
| Método | Função |
|
||||
| --- | --- |
|
||||
| `getName()`, `getAuthor()`, `getVersion()`, `getIdentifier()` | Identificação (o `identifier` distingue plugins em conexões TCP dedicadas) |
|
||||
| `getDisplayName()` | Texto na galeria e título de aba (padrão: `getName()`) |
|
||||
| `createPlugin(ConnectionManager, PluginWorkspace)` | Inicialização: `bindShellWorkspaceContent`, `installPeerActions`, rede |
|
||||
| `getMessages()` | Lista de classes `Message` que o plugin desserializa |
|
||||
| `receiveMessage(Message)` | Processamento após o `PluginManager` identificar o tipo |
|
||||
|
||||
Métodos opcionais (ciclo de vida e TCP avançado):
|
||||
|
||||
| Método | Função |
|
||||
| --- | --- |
|
||||
| `onShellTabClosed()` | Usuário fechou a aba do plugin no topo; liberar timers, captura, envio contínuo |
|
||||
| `receiveAllTypes()` | Receber todas as mensagens públicas |
|
||||
| `receivePrivateConnection(TcpConnection)` | Aceitar conexão TCP direcionada ao plugin |
|
||||
| `receiveBytes(TcpConnection, byte[])` | Ler payload bruto na conexão privada |
|
||||
| `onSocketClosed(TcpConnection)` | Limpar estado ao fechar socket |
|
||||
|
||||
### Descoberta de plugins
|
||||
|
||||
Para serem reconhecidos, os plugins devem possuir os arquivos de configuração
|
||||
do Java Service Provider Interface (SPI):
|
||||
|
||||
```
|
||||
MeusPlugins
|
||||
|
|
||||
+---META-INF
|
||||
|
|
||||
+---services
|
||||
|
|
||||
+---pitiupi.models.Plugin
|
||||
```
|
||||
|
||||
O arquivo `pitiupi.models.Plugin` deve conter o nome completo das classes que
|
||||
implementam a interface Plugin:
|
||||
|
||||
```
|
||||
MeusPlugins.plugins.NomeDaClasse
|
||||
```
|
||||
|
||||
Para serem carregados, os plugins devem estar empacotados como arquivos JAR
|
||||
e colocados no diretório plugins da aplicação.
|
||||
|
||||
O catálogo de build (`plugins.registry.json`) cobre **chat** e **screencast** no gate de CI. Outros módulos podem ser buildados localmente e instalados via JAR em `PITIUPI_HOME/plugins` sem entrar no catálogo.
|
||||
|
||||
### Carregamento de plugins
|
||||
|
||||
O `PluginManager.loadPlugins()` (chamado pela `MainShellFrame`):
|
||||
|
||||
1. Lista arquivos `.jar` em `PITIUPI_HOME/plugins` (ver [00_quickstart.md](00_quickstart.md); symlinks de dev via `./scripts/pitiupi.sh link`).
|
||||
2. Para cada JAR, cria um `URLClassLoader` e usa `ServiceLoader.load(Plugin.class, classLoader)`.
|
||||
3. Chama `createPlugin(connectionManager, pluginWorkspace)` em cada instância (`ShellPluginWorkspace`).
|
||||
4. Na recepção UDP/TCP, tenta desserializar com `PluginObjectInputStream` e despacha para plugins que declararam o tipo em `getMessages()`.
|
||||
|
||||
Ao fechar uma aba dinâmica de plugin, `TabWorkspacePanel` notifica `PluginManager.notifyShellTabClosed(pluginId)` **antes** de remover o conteúdo, para o plugin encerrar trabalho em segundo plano.
|
||||
|
||||
Implementação: `pitiupi/src/pitiupi/control/plugins/PluginManager.java`.
|
||||
|
||||
## Shell e abas (PluginWorkspace)
|
||||
|
||||
Na `MainShellFrame`, `createPlugin(ConnectionManager, PluginWorkspace)` registra o painel com `bindShellWorkspaceContent(JPanel)` **sem** abrir aba no boot. A galeria **Plugins** e as peer actions abrem a aba sob demanda.
|
||||
|
||||
## Peer actions na lista lateral
|
||||
|
||||
`PeerActionBroker.getAllActionsFor(target)` agrupa ações de **todos** os plugins (submenu por `getDisplayName()`). Destinos: `SINGLE_PEER` (um usuário) ou `ALL_PEERS` (linha **Todos**).
|
||||
|
||||
## Envio didático (núcleo)
|
||||
|
||||
`PluginRoutingPanel` (núcleo) padroniza destino, forma de envio e dica embaixo para chat, screencast e futuros plugins.
|
||||
|
||||
`MessageDispatcher` envia com `TransportMode` e `SendTarget`. `PluginTransportCatalog` define quais transportes cada plugin mostra na UI (sala geral vs um IP). Os combos usam textos didáticos (`describeForUi`) com o termo técnico entre parênteses (multicast UDP, UDP unicast, TCP). Na sala geral o campo IP fica desativado; um resumo **“Como esta mensagem vai”** (chat) deixa explícito o transporte escolhido — na sala pode ser multicast ou cópias unicast/TCP para cada pessoa, conforme o plugin. Para um peer use o IP manualmente ou a peer action na lista lateral.
|
||||
|
||||
Em abas de **recebimento** (ex. screencast `view:`), `PluginRoutingPanel.bindReceiveSession` deixa IP e forma de envio somente leitura, refletindo o que o remetente usou.
|
||||
|
||||
## Segurança e plugins de terceiros
|
||||
|
||||
- Instale apenas JARs de fontes confiáveis (`PITIUPI_HOME/plugins` ou aba Configurações).
|
||||
- O núcleo desserializa mensagens de rede com classloader por plugin; mensagens malformadas devem ser ignoradas, mas **não** há sandbox — um JAR malicioso tem o mesmo poder de um programa local.
|
||||
- Não há atualização automática de plugins pelo núcleo na v1.
|
||||
|
||||
### Avisos de sessão (D24)
|
||||
|
||||
`SessionNoticeMessage` (núcleo) sinaliza fases `START` / `END` por `pluginId` e canal (`SessionNoticeChannel`, ex. `screen_cast`). Plugins registram o tipo em `getMessages()` e reagem na UI (ex. fechar aba de recebimento ao `END` de compartilhamento). Campos incluem remetente, escopo (broadcast/direto) e `TransportMode` observado.
|
||||
@@ -1,111 +0,0 @@
|
||||
# Tutorial — criar um plugin
|
||||
|
||||
Complementa [09_sistema_de_plugins.md](09_sistema_de_plugins.md). Use `plugins/chat` como **modelo de estrutura** (Ant, SPI, painel).
|
||||
|
||||
Os exemplos abaixo são **parciais** — complete métodos, UI e testes conforme o seu módulo.
|
||||
|
||||
## 1. Novo projeto
|
||||
|
||||
- Copie `plugins/chat` para `plugins/meuPlugin`.
|
||||
- Ajuste `project.properties` / `build.xml` com o nome do JAR (`dist/pitiupi.meuplugin.jar`).
|
||||
- `javac.classpath` → `../../pitiupi/dist/pitiupi.jar` (ou caminho equivalente).
|
||||
|
||||
## 2. Classe principal (trecho)
|
||||
|
||||
```java
|
||||
public class MeuPlugin extends Plugin {
|
||||
private PluginWorkspace workspace;
|
||||
|
||||
@Override
|
||||
public void createPlugin(ConnectionManager cm, PluginWorkspace workspace) {
|
||||
this.workspace = workspace;
|
||||
MeuPanel panel = new MeuPanel(cm);
|
||||
bindShellWorkspaceContent(panel);
|
||||
installPeerActions(workspace.peerActions());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installPeerActions(PeerActionsRegistrar registrar) {
|
||||
registrar.register(PeerAction.of(
|
||||
"meuplugin.abrir",
|
||||
"Abrir",
|
||||
PeerActionTarget.ALL_PEERS,
|
||||
target -> workspace.openOrFocusTab(getIdentifier(), getDisplayName())
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Message>> getMessages() {
|
||||
return List.of(MinhaMessage.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveMessage(Message message) {
|
||||
if (message instanceof MinhaMessage m) {
|
||||
// atualizar UI com SwingUtilities.invokeLater quando necessário
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Implemente `getName()`, `getAuthor()`, `getVersion()`, `getIdentifier()` (identificador único na sala).
|
||||
|
||||
## 3. Mensagem e envio
|
||||
|
||||
```java
|
||||
public class MinhaMessage extends Message implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String texto;
|
||||
// construtor, getters
|
||||
}
|
||||
```
|
||||
|
||||
Envio típico via `MessageDispatcher` (modo `TransportMode` + destino). Exemplo conceitual:
|
||||
|
||||
```java
|
||||
// dentro do painel ou controller, com ConnectionManager injetado:
|
||||
dispatcher.send(minhaMsg, TransportMode.UNICAST_UDP, SendTarget.forPeer(ip));
|
||||
```
|
||||
|
||||
Detalhes: [07_mensagens_e_serializacao.md](07_mensagens_e_serializacao.md), [12_menus_unicast_multicast.md](12_menus_unicast_multicast.md).
|
||||
|
||||
## 4. SPI
|
||||
|
||||
`src/META-INF/services/pitiupi.models.Plugin`:
|
||||
|
||||
```
|
||||
pacote.MeuPlugin
|
||||
```
|
||||
|
||||
Confirme com `jar tf dist/seu.jar | grep META-INF/services`.
|
||||
|
||||
## 5. Catálogo (opcional)
|
||||
|
||||
Para build com `./scripts/pitiupi.sh`, adicione entrada em [`plugins.registry.json`](../../plugins.registry.json). Módulos só locais podem instalar o JAR pela aba **Configurações** sem entrar no catálogo.
|
||||
|
||||
## 6. Build e instalação
|
||||
|
||||
```bash
|
||||
cd plugins/meuPlugin && ant jar
|
||||
# copiar ou Instalar JAR… na UI; reiniciar o app
|
||||
```
|
||||
|
||||
## 7. Dois cenários de teste
|
||||
|
||||
**A — Mesmo computador:** duas instâncias, portas `5000` e `5001`, nomes distintos, mesma versão do JAR.
|
||||
|
||||
**B — Dois PCs:** mesma porta (ex. `5000`), mesma sub-rede Wi‑Fi ou cabo, firewall UDP/TCP liberado, mesmo plugin instalado. Ver [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md).
|
||||
|
||||
## Problemas comuns
|
||||
|
||||
| Sintoma | Causa provável | O que fazer |
|
||||
| --- | --- | --- |
|
||||
| Plugin não aparece | SPI ausente ou sem reinício | Verificar JAR, reiniciar |
|
||||
| `ClassNotFoundException` na rede | Classe de mensagem não em `getMessages()` ou JAR diferente no peer | Alinhar tipos e reinstalar nos dois |
|
||||
| Peers não se veem | Porta/sub-rede/multicast | Appendix Wi‑Fi |
|
||||
| UI não atualiza | Rede na EDT | `SwingUtilities.invokeLater` |
|
||||
| TCP falha | Peer offline ou IP errado | Conferir lista lateral e `PeerIpv4` |
|
||||
|
||||
## Próximos passos
|
||||
|
||||
- [11_plugins_referencia.md](11_plugins_referencia.md) — chat e screencast
|
||||
@@ -1,58 +0,0 @@
|
||||
# Plugins de referência
|
||||
|
||||
Dois módulos buildáveis no catálogo; o chat unifica o antigo mpChat (multicast, unicast, TCP e canal TCP privado).
|
||||
|
||||
## Comparativo
|
||||
|
||||
| | plugins/chat | plugins/screencast |
|
||||
| --- | --- | --- |
|
||||
| Build | Ant | Ant |
|
||||
| id | `chat` | `screencast` |
|
||||
| Classe | `ChatPlugin` | `ScreenCastPlugin` |
|
||||
| Mensagem | `ChatMessage`, `LegacyMpChatMessage` | `ScreenCastMessage` |
|
||||
| UI | `ChatPanel` (destino + transporte) | `ScreenCastPanel` (destino + transporte) |
|
||||
| TCP privado | Toggle no `ChatPanel` + hooks `receiveBytes` (histórico `private:<IP>`) | — |
|
||||
|
||||
## plugins/chat
|
||||
|
||||
Fluxo resumido:
|
||||
|
||||
1. `ChatPlugin.createPlugin` registra `ChatPanel` e peer actions (sala, peer, TCP privado).
|
||||
2. Envio: usuário escolhe destino e `TransportMode` → `MessageDispatcher` → UDP ou TCP do núcleo.
|
||||
3. Recepção: `ChatMessage` em `receiveMessage` → histórico na aba correta (sala, IP ou canal privado).
|
||||
4. TCP privado: toggle desliga o combo de transporte e usa bytes dedicados ao plugin.
|
||||
|
||||
Código de entrada: `plugins/chat/src/pitiupi/chat/plugin/ChatPlugin.java`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
UI[ChatPanel] --> Disp[MessageDispatcher]
|
||||
Disp --> CM[ConnectionManager]
|
||||
CM --> Net[UDP ou TCP]
|
||||
Net --> CM2[ConnectionManager peer]
|
||||
CM2 --> CP[ChatPlugin.receiveMessage]
|
||||
CP --> UI2[ChatPanel peer]
|
||||
```
|
||||
|
||||
## plugins/screencast
|
||||
|
||||
Fluxo resumido:
|
||||
|
||||
1. Captura de tela em loop (com throttling) → `ScreenCastMessage` com imagem serializada.
|
||||
2. Destino geral: multicast; um peer: unicast/TCP conforme painel.
|
||||
3. `SessionNoticeMessage` (núcleo) marca início/fim de sessão para fechar abas de recepção.
|
||||
|
||||
Código de entrada: `plugins/screencast/src/pitiupi/screenCast/plugin/ScreenCastPlugin.java`.
|
||||
|
||||
**Limitação:** largura de banda e MTU — frames grandes podem sofrer perda em UDP; adequado para demonstração, não para produção.
|
||||
|
||||
## Limitações (não são bugs)
|
||||
|
||||
Os plugins de referência são **exemplos de demonstração**, não produtos completos:
|
||||
|
||||
| Plugin | Inclui | Não inclui (de propósito) |
|
||||
| --- | --- | --- |
|
||||
| **chat** | texto, sala geral, unicast/multicast/TCP, TCP privado básico | imagens, moderação, histórico persistente, criptografia |
|
||||
| **screencast** | stream read-only, avisos de sessão | controle remoto do mouse/teclado, áudio |
|
||||
|
||||
Para evoluir: copie o padrão (`Plugin` + mensagens + painel na aba) e acrescente features no seu JAR — sem inflar o núcleo. Ver [10_tutorial_plugin.md](10_tutorial_plugin.md).
|
||||
@@ -1,15 +0,0 @@
|
||||
# Transporte na UI e peer actions
|
||||
|
||||
Na v1 atual, plugins **não** registram menus na barra superior nem submenus legados por categoria.
|
||||
|
||||
## Como expor ações hoje
|
||||
|
||||
1. **Galeria Plugins** — abre a aba central do plugin.
|
||||
2. **Lista lateral** — clique em um peer ou em **Todos** → menu com **peer actions** registradas por cada plugin (`PeerActionBroker`).
|
||||
3. **`PluginWorkspace.openOrFocusTab`** — dentro do handler da peer action ou após carregar o painel.
|
||||
|
||||
## Catálogo `menuCategory`
|
||||
|
||||
O campo `menuCategory` em [`plugins.registry.json`](../../plugins.registry.json) (`Social`, `Jogos`, `Streaming`) serve apenas ao script `./scripts/pitiupi.sh` para filtrar build/run — não cria menus Swing.
|
||||
|
||||
Referência de plugins: [11_plugins_referencia.md](11_plugins_referencia.md).
|
||||
@@ -1,59 +0,0 @@
|
||||
# Guia para desenvolvimento
|
||||
|
||||
Esta documentação descreve a ferramenta Pitiupi neste repositório: build, rede, plugins e convenções de código.
|
||||
|
||||
## Leitura mínima antes de mudar código
|
||||
|
||||
1. [00_quickstart.md](00_quickstart.md) — build e execução
|
||||
2. [04_inicializacao_e_gui.md](04_inicializacao_e_gui.md) — boot e diagrama
|
||||
3. [09_sistema_de_plugins.md](09_sistema_de_plugins.md) — contrato de extensão
|
||||
4. [14_arquitetura_e_manutencao.md](14_arquitetura_e_manutencao.md) — mapa de pacotes
|
||||
|
||||
## Por tipo de tarefa
|
||||
|
||||
| Tarefa | Ler primeiro |
|
||||
| --- | --- |
|
||||
| Novo plugin | [10_tutorial_plugin.md](10_tutorial_plugin.md), [11_plugins_referencia.md](11_plugins_referencia.md) |
|
||||
| Bug UDP / multicast | [05_udp_multicast.md](05_udp_multicast.md), [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md) |
|
||||
| Bug TCP / conexão | [06_tcp_conexoes.md](06_tcp_conexoes.md) |
|
||||
| Presença / lista de usuários | [08_heartbeat_e_presenca.md](08_heartbeat_e_presenca.md) |
|
||||
| Desserialização / mensagens | [07_mensagens_e_serializacao.md](07_mensagens_e_serializacao.md) |
|
||||
|
||||
## Gate de build
|
||||
|
||||
```bash
|
||||
cd pitiupi && ant jar && ant test
|
||||
cd plugins/chat && ant jar
|
||||
cd plugins/screencast && ant jar
|
||||
```
|
||||
|
||||
Ou na raiz: `./scripts/pitiupi.sh build --all`.
|
||||
|
||||
Testes manuais em rede: [15_testes_e_gate.md](15_testes_e_gate.md).
|
||||
|
||||
## Documentação viva
|
||||
|
||||
Ao alterar comportamento visível ou protocolo:
|
||||
|
||||
| Mudou | Atualizar |
|
||||
| --- | --- |
|
||||
| Boot, shell, abas | `04`, `14` |
|
||||
| UDP/multicast/portas | `05`, `appendix` |
|
||||
| TCP/framing | `06` |
|
||||
| Mensagens/despacho | `07` |
|
||||
| Heartbeat/presença | `08`, `decision-register` se constante |
|
||||
| SPI / API plugin | `09`, `10` |
|
||||
| Plugin de referência | `11` |
|
||||
|
||||
Convenções de escrita: [00_convencoes.md](00_convencoes.md).
|
||||
|
||||
## Convenções
|
||||
|
||||
- Código e identificadores em inglês (pacotes `pitiupi.*`).
|
||||
- Documentação em português (BR) em `docs/`.
|
||||
- Evitar alterar o núcleo para um único plugin — preferir extensão via `Plugin` e `PluginWorkspace`.
|
||||
|
||||
## Decisões e débitos
|
||||
|
||||
- [decision-register.md](../decision-register.md)
|
||||
- [engineering_backlog.md](engineering_backlog.md)
|
||||
@@ -1,73 +0,0 @@
|
||||
# Arquitetura e manutenção
|
||||
|
||||
## Visão em camadas
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph presentation [presentation.shell]
|
||||
Login[LoginDialog]
|
||||
Shell[MainShellFrame]
|
||||
end
|
||||
subgraph control [control]
|
||||
CM[ConnectionManager]
|
||||
HB[HeartbeatManager]
|
||||
PM[PluginManager]
|
||||
end
|
||||
subgraph application [application]
|
||||
Disp[MessageDispatcher]
|
||||
Presence[PresenceService]
|
||||
end
|
||||
subgraph netlayer [net]
|
||||
UDP[SocketUDP]
|
||||
TCP[SocketTCP]
|
||||
end
|
||||
Login --> Shell
|
||||
Shell --> CM
|
||||
CM --> UDP
|
||||
CM --> TCP
|
||||
CM --> HB
|
||||
CM --> PM
|
||||
PM --> Disp
|
||||
HB --> Presence
|
||||
```
|
||||
|
||||
Boot detalhado: [04_inicializacao_e_gui.md](04_inicializacao_e_gui.md).
|
||||
|
||||
## Onde editar
|
||||
|
||||
| Comportamento | Pacote / classe |
|
||||
| --- | --- |
|
||||
| Login, porta, nome | `pitiupi.presentation.shell.LoginDialog` |
|
||||
| Shell principal (abas, galeria) | `pitiupi.presentation.shell.MainShellFrame`, `TabWorkspacePanel` |
|
||||
| Menu Ajuda | `pitiupi.presentation.shell.ShellMenuBar` |
|
||||
| Usuários online, peer actions | `pitiupi.presentation.shell.NetworkUsersPanel`, `PeerActionBroker` |
|
||||
| Facade rede para plugins | `pitiupi.control.ConnectionManager` |
|
||||
| Workspace na aba (SPI) | `pitiupi.api.plugin.PluginWorkspace`, `ShellPluginWorkspace` |
|
||||
| UDP | `pitiupi.net.udp.SocketUDP` |
|
||||
| TCP servidor e mapa de conexões | `pitiupi.net.tcp.SocketTCP`, `TcpConnectionManager` |
|
||||
| Conexão TCP por peer | `pitiupi.net.tcp.TcpConnection` |
|
||||
| Handshake / fábrica TCP | `pitiupi.net.tcp.ConnectionFactory`, `HandshakeMessage` |
|
||||
| Heartbeat | `pitiupi.control.HeartbeatManager`, `HeartbeatMessage` |
|
||||
| Carregar JARs, despacho | `pitiupi.control.plugins.PluginManager` |
|
||||
| Contrato plugin | `pitiupi.models.Plugin`, `pitiupi.models.Messages.Message` |
|
||||
## Dependências entre módulos
|
||||
|
||||
```
|
||||
pitiupi (núcleo)
|
||||
↑ classpath
|
||||
plugins/chat | plugins/screencast
|
||||
```
|
||||
|
||||
Plugins **não** devem ser dependência do núcleo.
|
||||
|
||||
## Alterações arriscadas
|
||||
|
||||
- Mudar grupo multicast ou formato de heartbeat → quebra interoperabilidade na sala.
|
||||
- Mudar framing TCP → todos os plugins que usam TCP precisam acompanhar.
|
||||
- Alterar nome do arquivo SPI (`pitiupi.models.Plugin`) → todos os JARs de plugin.
|
||||
|
||||
Registrar decisão em [decision-register.md](../decision-register.md).
|
||||
|
||||
## Documentação viva
|
||||
|
||||
Ao mudar protocolo ou API pública do núcleo, atualizar o número correspondente em [documentation/README.md](README.md) e, se aplicável, [11_plugins_referencia.md](11_plugins_referencia.md).
|
||||
@@ -1,56 +0,0 @@
|
||||
# Testes e gate
|
||||
|
||||
## Gate automatizado (núcleo)
|
||||
|
||||
O módulo `pitiupi/` inclui testes JUnit:
|
||||
|
||||
```bash
|
||||
cd pitiupi && ant test
|
||||
```
|
||||
|
||||
Falha se `java`, `javac` ou Ant estiverem ausentes.
|
||||
|
||||
Pacotes com testes automatizados (JUnit 5): `platform` (`PitiupiHome`, `ConfigLoader`), `plugins.store`, `application.presence`, `application.peeractions`, `application.invites`, `application.messaging`, `application.activity`, `presentation.shell`, `control` (goodbye), `net.tcp` (fechamento de conexões), `models`.
|
||||
|
||||
## Gate de build (produto)
|
||||
|
||||
Na raiz do clone:
|
||||
|
||||
```bash
|
||||
./scripts/pitiupi.sh build --all
|
||||
```
|
||||
|
||||
Equivalente manual:
|
||||
|
||||
```bash
|
||||
cd pitiupi && ant jar
|
||||
cd plugins/chat && ant jar
|
||||
cd plugins/screencast && ant jar
|
||||
```
|
||||
|
||||
Depois, para runtime local:
|
||||
|
||||
```bash
|
||||
./scripts/pitiupi.sh link --all
|
||||
```
|
||||
|
||||
## Testes manuais — uma máquina (5000 / 5001)
|
||||
|
||||
- [ ] Duas instâncias com portas distintas; nomes diferentes
|
||||
- [ ] Peers visíveis na lista em até ~15 s
|
||||
- [ ] Chat: sala geral e conversa por IP; tags de transporte coloridas no histórico
|
||||
- [ ] ScreenCast: abrir aba, destino sala ou peer; fechar aba com × interrompe compartilhamento
|
||||
- [ ] Compose do chat sem barra de rolagem vazio em janela ~1024×768
|
||||
- [ ] Encerrar uma instância remove o peer após timeout de heartbeat
|
||||
|
||||
## Testes manuais — dois PCs (mesma Wi‑Fi)
|
||||
|
||||
Mesma **porta** em ambos (ex. 5000), **mesmos JARs** em `.pitiupi/plugins` na raiz do clone de cada máquina:
|
||||
|
||||
- [ ] Presença e linha **Todos**
|
||||
- [ ] Troca de mensagens no chat (multicast e, se possível, unicast/TCP)
|
||||
- [ ] Compartilhamento de tela entre os dois (ambiente com captura de tela funcional — no WSL a captura nativa pode falhar)
|
||||
|
||||
## Melhorias futuras
|
||||
|
||||
Ver [engineering_backlog.md](engineering_backlog.md) (CI contínuo, mais testes de rede sem sockets reais).
|
||||
@@ -1,64 +0,0 @@
|
||||
# Fundamentação — redes e código
|
||||
|
||||
Mapa entre conceitos de **Redes de Computadores** e o Pitiupi. Aprofundamento: [references/README.md](../references/README.md).
|
||||
|
||||
## Camada de aplicação P2P
|
||||
|
||||
| Conceito | No Pitiupi |
|
||||
| --- | --- |
|
||||
| Aplicação distribuída sem servidor dedicado | Cada peer executa o mesmo núcleo + plugins |
|
||||
| Identidade do participante | Nome no login + `HeartbeatMessage` (instância) |
|
||||
| Broadcast lógico | UDP multicast no grupo `224.0.0.3` |
|
||||
| Comunicação 1:1 | UDP unicast ou TCP com conexão persistente |
|
||||
|
||||
## UDP
|
||||
|
||||
- Sem conexão; mensagens delimitadas por datagrama.
|
||||
- Multicast economiza envios para todos os peers do grupo.
|
||||
- Limitações: perda, ordem, MTU — relevante para plugins com imagens (screenCast).
|
||||
|
||||
Doc: [05_udp_multicast.md](05_udp_multicast.md).
|
||||
|
||||
## TCP
|
||||
|
||||
- Conexão orientada a fluxo; o núcleo adiciona **framing** (tamanho + payload).
|
||||
- Uma conexão por endereço de peer reutilizada para várias mensagens.
|
||||
- Fechamento quando peer some do heartbeat.
|
||||
|
||||
Doc: [06_tcp_conexoes.md](06_tcp_conexoes.md).
|
||||
|
||||
## Presença
|
||||
|
||||
Heartbeats periódicos em UDP; timeout ~15 s. Analogia com “keepalive” de presença, não com camada de transporte confiável.
|
||||
|
||||
Doc: [08_heartbeat_e_presenca.md](08_heartbeat_e_presenca.md).
|
||||
|
||||
## Serialização
|
||||
|
||||
Objetos Java `Serializable` em `byte[]` — acoplamento de versão de classes entre peers (mesmo plugin instalado).
|
||||
|
||||
Doc: [07_mensagens_e_serializacao.md](07_mensagens_e_serializacao.md).
|
||||
|
||||
## Extensibilidade
|
||||
|
||||
SPI e classloader por JAR — padrão de plug-in em aplicações Java.
|
||||
|
||||
Doc: [09_sistema_de_plugins.md](09_sistema_de_plugins.md).
|
||||
|
||||
## Quando o Pitiupi usa cada transporte
|
||||
|
||||
| Situação | Transporte típico | Motivo |
|
||||
| --- | --- | --- |
|
||||
| Lista de usuários / heartbeat | UDP multicast | Todos os peers sem conexão prévia |
|
||||
| Sala geral (chat, broadcast) | UDP multicast ou cópias unicast/TCP conforme combo | Escolha explícita na UI do plugin |
|
||||
| Mensagem para um IP | UDP unicast ou TCP | Destino explícito |
|
||||
| Fluxo contínuo (tela, TCP privado chat) | TCP | Fluxo e framing no núcleo |
|
||||
|
||||
UDP não é “sempre mais rápido” nem TCP “resolve tudo”: perda e ordem ainda importam na aplicação; TCP adiciona handshake e estado por peer.
|
||||
|
||||
## Leitura complementar
|
||||
|
||||
- [references/notas/udp_vs_tcp.md](../references/notas/udp_vs_tcp.md)
|
||||
- [references/README.md](../references/README.md) (bibliografia)
|
||||
- [references/notas/multicast_lan.md](../references/notas/multicast_lan.md)
|
||||
- [references/notas/java_spi_plugins.md](../references/notas/java_spi_plugins.md)
|
||||
@@ -1,52 +0,0 @@
|
||||
# Teste com um colega
|
||||
|
||||
**Teoria:** dois processos só se descobrem se compartilham a mesma sub-rede, a mesma porta de aplicação e um caminho em que o multicast da LAN não seja filtrado. **No Pitiupi:** a lista **Usuários nesta rede** enche com heartbeats UDP. **Limitação:** o Pitiupi não executa `ping` nem `traceroute` do sistema; esses comandos servem para separar problema de rede de problema do aplicativo.
|
||||
|
||||
Os dois Pitiupi precisam usar a **mesma porta** (na sala, em geral **5000**). Portas diferentes são salas diferentes: quem está em 5001 não aparece para quem ficou em 5000.
|
||||
|
||||
## O que conferir na aplicação
|
||||
|
||||
Abra a aba **Configurações**, seção **Sua conexão**:
|
||||
|
||||
- **IP local** e o nome da **interface** (por exemplo `eth0 · 192.168.0.15`).
|
||||
- **Porta** em que esta instância escuta UDP e TCP.
|
||||
- **Saída:** Automático, Cabo ou Wi-Fi. Os dois colegas precisam estar na mesma rede dessa interface.
|
||||
- **TTL:** o padrão é **1**. O número de saltos do `tracert` (ou `traceroute`) até o IP do colega é o mínimo a colocar aqui. Se a linha 1 já é o IP dele, use **1**. Cada roteador no meio soma 1, até o máximo **32**. O teste na mesma sub-rede usa 1.
|
||||
|
||||
O nome na rede também se altera aí. O colega passa a ver o nome novo no próximo anúncio (até cerca de 5 s), sem você sair e voltar da lista.
|
||||
|
||||
## O que conferir no sistema
|
||||
|
||||
Substitua `5000` e o IP do colega pelos valores da aba Configurações.
|
||||
|
||||
### Windows
|
||||
|
||||
- IP: `ipconfig` e o IPv4 do adaptador Ethernet ou Wi-Fi.
|
||||
- Porta em escuta: `netstat -ano | findstr :5000`
|
||||
- Alcance: `ping` no IP do colega.
|
||||
- Caminho: `tracert` no IP do colega. Conte as linhas numeradas até o IP dele: esse número é o TTL mínimo na aba Configurações.
|
||||
- Porta TCP do aplicativo: no PowerShell, `Test-NetConnection -ComputerName IP -Port 5000`
|
||||
|
||||
### Linux
|
||||
|
||||
- IP: `ip -4 addr`
|
||||
- Porta em escuta: `ss -lntu | grep 5000`
|
||||
- Alcance: `ping -c 3 IP`
|
||||
- Caminho: `traceroute IP` ou `tracepath IP`. Conte os saltos até o IP do colega: esse número é o TTL mínimo.
|
||||
- Porta TCP do aplicativo: `nc -vz -w 3 IP 5000` (ou `ncat`)
|
||||
|
||||
## WSL na sala
|
||||
|
||||
Cada um abre o programa pelo caso da própria máquina, descrito no [quickstart](00_quickstart.md). No Linux, o terminal e `./scripts/pitiupi.sh run --all` (Java do Linux). No Windows, o cmd e `scripts\pitiupi.cmd run --all` (Java do Windows). No WSL, o terminal do Ubuntu e `./scripts/pitiupi.sh run --all` (a janela abre no Java do Windows). Os dois lados se veem. Abrir o `java` de dentro do WSL, sem esse comando, mostra só `eth0` ou `eth1`. Detalhe: [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md).
|
||||
|
||||
## Como ler o resultado
|
||||
|
||||
- **O ping falha.** O problema está na rede entre os PCs: sub-rede diferente, cabo, Wi-Fi ou isolamento de clientes no roteador. O Pitiupi não contorna isso.
|
||||
- **O ping funciona e a porta TCP não abre.** Firewall, porta errada ou o outro Pitiupi não está escutando. Confira a porta na aba Configurações e libere UDP e TCP nessa porta.
|
||||
- **O ping com TTL 1 responde e a lista só mostra Todos.** A descoberta é multicast, e um TTL baixo não é a causa quando o `tracert` tem um salto só. Confira porta igual, a mesma saída (os dois no cabo ou os dois no Wi-Fi; `169.254` e `192.168` ao mesmo tempo não se veem), firewall de UDP e isolamento de clientes. Um processo aberto dentro do WSL e o outro no Java do Windows não compartilham a mesma placa: os dois precisam estar no Java do Windows ou os dois no Ubuntu instalado na máquina. Ver [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md).
|
||||
- **O `tracert` tem mais de um salto e a lista fica vazia.** Suba o TTL em Configurações até o número de saltos (máximo 32) e aplique nos dois. Um roteador no meio só entrega o multicast se estiver disposto a encaminhar o grupo.
|
||||
- **O colega está na lista e o TCP avisa que não conectou.** A tentativa de abrir o socket espera no máximo cerca de **5 s** e a janela continua utilizável. Veja firewall de TCP ou se a porta mudou no meio do caminho.
|
||||
|
||||
A lista espera cerca de **5 s** entre anúncios e remove quem fica cerca de **15 s** sem heartbeat. Ao trocar de porta, esta instância envia goodbye na sala antiga e passa a anunciar na porta nova.
|
||||
|
||||
Detalhe de portas, interface e TTL: [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md).
|
||||
@@ -1,27 +0,0 @@
|
||||
# Documentação técnica — Pitiupi
|
||||
|
||||
Índice geral: [docs/README.md](../README.md) · Convenções: [00_convencoes.md](00_convencoes.md).
|
||||
|
||||
## Por área
|
||||
|
||||
| Área | Arquivos |
|
||||
| --- | --- |
|
||||
| **Início** | [00_quickstart](00_quickstart.md), [02_modulos](02_modulos_do_repositorio.md), [03_build](03_build_ant_netbeans.md) |
|
||||
| **Arquitetura e GUI** | [04_inicializacao_e_gui](04_inicializacao_e_gui.md), [13_guia_desenvolvimento](13_guia_desenvolvimento.md), [14_arquitetura_e_manutencao](14_arquitetura_e_manutencao.md) |
|
||||
| **Rede** | [05_udp_multicast](05_udp_multicast.md), [06_tcp_conexoes](06_tcp_conexoes.md), [07_mensagens](07_mensagens_e_serializacao.md), [08_heartbeat](08_heartbeat_e_presenca.md), [16_fundamentacao_redes](16_fundamentacao_redes.md), [17_teste_com_colega](17_teste_com_colega.md), [appendix_portas_e_multicast](appendix_portas_e_multicast.md) |
|
||||
| **Plugins** | [09_sistema_de_plugins](09_sistema_de_plugins.md), [10_tutorial_plugin](10_tutorial_plugin.md), [11_plugins_referencia](11_plugins_referencia.md), [12_transporte_e_peer_actions](12_menus_unicast_multicast.md) |
|
||||
| **Qualidade** | [15_testes_e_gate](15_testes_e_gate.md), [engineering_backlog](engineering_backlog.md) |
|
||||
|
||||
## Ordem linear (referência)
|
||||
|
||||
| # | Arquivo | Conteúdo |
|
||||
| --- | --- | --- |
|
||||
| 0 | [00_quickstart.md](00_quickstart.md) | Build, plugins, duas instâncias |
|
||||
| 2–3 | 02–03 | Módulos, Ant |
|
||||
| 4 | [04_inicializacao_e_gui.md](04_inicializacao_e_gui.md) | Boot, `MainShellFrame`, shell Swing |
|
||||
| 5–8 | 05–08 | UDP, TCP, mensagens, presença |
|
||||
| 9–12 | 09–12 | Plugins, tutorial genérico, transporte na UI |
|
||||
| 13–15 | 13–15 | Dev, manutenção, testes |
|
||||
| 16 | [16_fundamentacao_redes.md](16_fundamentacao_redes.md) | Fundamentação redes ↔ código |
|
||||
| 17 | [17_teste_com_colega.md](17_teste_com_colega.md) | IP, porta, ping e lista entre dois PCs |
|
||||
| — | [appendix_portas_e_multicast.md](appendix_portas_e_multicast.md) | Portas, Wi‑Fi, WSL |
|
||||
@@ -1,78 +0,0 @@
|
||||
# Apêndice — portas e multicast
|
||||
|
||||
## Configuração
|
||||
|
||||
- **Nome** e **porta** começam no diálogo de login e podem ser alterados na aba **Configurações** sem reiniciar. A alteração vale na hora.
|
||||
- A mesma porta é usada para UDP (multicast socket) e TCP (`ServerSocket`). Trocar a porta sai da sala antiga (goodbye) e passa a procurar colegas na porta nova.
|
||||
- **Saída:** Automático (cabo antes de Wi-Fi), só Cabo ou só Wi-Fi. O multicast e o IP exibido seguem essa escolha. O TCP de saída também sai pelo IPv4 dessa interface.
|
||||
- **TTL** do multicast: padrão **1** (só a rede local). Na Configurações, de 1 a 32.
|
||||
- Preferências persistidas localmente (`java.util.prefs`): nome, porta, TTL e saída. `config.properties` não guarda esses campos.
|
||||
|
||||
## Multicast
|
||||
|
||||
- Grupo: **`224.0.0.3`** (fixo no código do núcleo).
|
||||
- Buffer UDP: **256000** bytes (configuração em `SocketUDP`).
|
||||
|
||||
Todos os peers na mesma LAN devem usar a **mesma porta** para participar do mesmo grupo lógico de peers (portas diferentes = grupos isolados).
|
||||
|
||||
## Mesmo computador
|
||||
|
||||
Execute duas instâncias com **portas diferentes** (ex.: 5000 e 5001). Mesmo nome de usuário em duas instâncias pode confundir a lista — use nomes distintos.
|
||||
|
||||
## Firewall e Wi‑Fi
|
||||
|
||||
- Bloqueio de multicast em redes de visitantes ou isolamento AP→AP impede descoberta.
|
||||
- Em laboratório, preferir cabo ou Wi‑Fi com multicast habilitado.
|
||||
|
||||
## Onde o processo Java roda
|
||||
|
||||
A aba **Configurações** só lista interfaces que **este** processo enxerga. O nome decide a família: `eth`, `eno`, `enp`, `ens` ou “Ethernet” é **Cabo**; `wlan`, `wlp` ou “Wi-Fi” é **Wi-Fi**. O clone Git pode ficar no disco do WSL ou numa pasta do Windows. O que muda a lista é qual `java` abre o JAR.
|
||||
|
||||
| Processo | O que a saída mostra |
|
||||
| --- | --- |
|
||||
| `java.exe` do Windows | Placas reais. Sem cabo com IPv4, **Cabo** não é oferecido. **Wi-Fi** aparece quando a placa sem fio está ligada. |
|
||||
| Ubuntu instalado no PC (não é WSL) | `wlan*` e `eth*`/`enp*` reais. O mesmo `./scripts/pitiupi.sh run --all` abre o Java do Linux. |
|
||||
| Java de dentro do WSL | Em geral uma interface virtual `eth0` ou `eth1`. O programa classifica isso como **Cabo**, mesmo com o cabo físico desconectado e o Windows no Wi-Fi. Não existe `wlan` nesse Linux, então a saída **Wi-Fi** não aparece. |
|
||||
|
||||
Dois arranjos de WSL, e nenhum dos dois substitui o Java do Windows na sala:
|
||||
|
||||
- **NAT** (IP `172.x` no `ip -4 addr`): o Linux não está na rede dos colegas. Quem está em `192.168.x` ou `169.254.x` não recebe o multicast. Aumentar o TTL não coloca esse processo na LAN.
|
||||
- **Rede espelhada** (o `eth*` mostra o mesmo IPv4 do Wi-Fi do Windows, por exemplo `192.168.3.x`): o rótulo **Cabo** segue o nome Linux, não o cabo da tomada. O socket ainda é o da interface virtual. A descoberta não é a mesma de um colega que abriu o Java no Windows.
|
||||
|
||||
Não dá para o código “pegar a placa Wi-Fi do Windows” a partir do WSL: essa placa não está no espaço de rede do Linux. Tratar todo `eth*` como Wi-Fi quebraria o Ubuntu nativo, em que `eth` é cabo de verdade. Por isso a sala usa o mesmo tipo de processo nos dois lados: os dois no Java do Windows, ou os dois no Ubuntu instalado na máquina. Não misture um Pitiupi aberto no WSL com outro aberto no Windows.
|
||||
|
||||
O passo a passo de cada máquina está no [quickstart](00_quickstart.md).
|
||||
|
||||
| Onde a pessoa está | O que abrir | Comando, na pasta do projeto |
|
||||
| --- | --- | --- |
|
||||
| Linux (Ubuntu na máquina) | Terminal | `./scripts/pitiupi.sh run --all` — Java do Linux |
|
||||
| Windows | Prompt de Comando (`cmd`) | `scripts\pitiupi.cmd run --all` — Java do Windows |
|
||||
| WSL | Terminal do Ubuntu | `./scripts/pitiupi.sh run --all` — a janela abre no Java do Windows |
|
||||
|
||||
No PowerShell o comando é o mesmo do cmd. Plugins, cache e logs ficam em `.pitiupi/` na pasta do projeto. JDK 17 do Windows, se faltar: `winget install Microsoft.OpenJDK.17`. No Windows o Ant também precisa estar no PATH.
|
||||
|
||||
## Escolha de interface (multicast)
|
||||
|
||||
No modo **Automático**, o núcleo associa o socket multicast à interface preferida: **cabo (Ethernet) antes de Wi‑Fi**; em cabo com link-local APIPA, prioriza IPv4 **`169.254.*`**. **Cabo** ou **Wi-Fi** na aba Configurações restringe a essa família (nome da interface ou rótulo `Ethernet` / `Wi-Fi`). Isso não substitui a exigência de **mesma sub-rede** entre os dois PCs — `169.254.x` e `192.168.x` ao mesmo tempo em hosts diferentes não compartilham heartbeat.
|
||||
|
||||
Roteiro para achar o próprio IP, a porta e o motivo de a lista ficar vazia: [17_teste_com_colega.md](17_teste_com_colega.md).
|
||||
|
||||
## IP do peer na UI (Windows)
|
||||
|
||||
Ao abrir conversa pela lista de presença, o campo de destino usa **`PeerIpv4`**: IPv4 pontilhado legível, sem forma IPv6-mapeada (`::ffff:…`) nem sufixo de zona (`%7`). Ver [decision-register.md](../decision-register.md) (D22).
|
||||
|
||||
## Dois PCs na mesma Wi‑Fi
|
||||
|
||||
1. **Mesma porta no login** (ex.: `5000` nos dois). Portas diferentes isolam a sala.
|
||||
2. **Configurações → Sua conexão:** IPs na mesma sub-rede (ex. ambos `192.168.0.x` ou `192.168.1.x`). Se um for `172.x` (WSL/Docker) e outro `192.168.x`, o multicast entre esses hosts não cruza.
|
||||
3. **Firewall:** liberar **UDP entrada e saída** na porta do login (Windows Defender / `ufw`).
|
||||
4. **Roteador:** desativar *isolamento de clientes* / *AP isolation* / Wi‑Fi de visitantes.
|
||||
5. Aguardar até **~15 s** após abrir os dois apps (heartbeat a cada 5 s).
|
||||
|
||||
## Sintomas
|
||||
|
||||
| Sintoma | Verificar |
|
||||
| --- | --- |
|
||||
| Lista só com **Todos**, sem peers | Porta igual? Mesma sub-rede? Firewall UDP? AP isolation? WSL num dos PCs? |
|
||||
| Plugin não recebe | Desserialização / SPI / JAR em `PITIUPI_HOME/plugins` |
|
||||
| TCP falha | Peer visível na lista? IP correto? A tentativa avisa em cerca de 5 s e não trava a janela |
|
||||
@@ -1,12 +0,0 @@
|
||||
# Engineering backlog
|
||||
|
||||
| Item | Prioridade | Notas |
|
||||
| --- | --- | --- |
|
||||
| Testes JUnit framing TCP com socket mock | Média | Rede real coberta manualmente na LAN |
|
||||
| CI (GitHub Actions) com `ant jar` + `ant test` | Baixa | Requer imagem com Ant/JDK |
|
||||
| REQ-11 badges na lista lateral | Baixa | D23 — ver `decision-register.md` |
|
||||
| Automação GUI 2 instâncias / 2 PCs | Baixa | Limitação Swing; aceite manual |
|
||||
|
||||
**Concluído (refinamento v1):** shell abas + peer actions; SPI `PluginWorkspace`; suíte JUnit no núcleo; tutorial Ping; deploy LAN documentado no repositório de desenvolvimento do mantenedor (fora deste clone).
|
||||
|
||||
Ao fechar um item, atualizar esta tabela e o doc técnico relacionado.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Funções do núcleo para um plugin
|
||||
|
||||
Um plugin é uma classe que estende `pitiupi.models.Plugin`. O núcleo cria a instância, chama `createPlugin` e, quando chega um datagrama ou um frame TCP do tipo que você declarou, chama `receiveMessage`. Você não abre `MulticastSocket` nem `ServerSocket`.
|
||||
|
||||
A classe base está em `pitiupi/src/pitiupi/models/Plugin.java`.
|
||||
|
||||
## O que implementar
|
||||
|
||||
| Método | Para que serve |
|
||||
| --- | --- |
|
||||
| `getName()`, `getAuthor()`, `getVersion()` | Texto de identificação do módulo |
|
||||
| `getIdentifier()` | Id estável, único na sala. O núcleo usa esse texto para achar o plugin numa conexão TCP dirigida a ele. Exemplo do ping: `pitiupi.ping` |
|
||||
| `getDisplayName()` | Nome na galeria e na aba. Se você não sobrescrever, vale `getName()` |
|
||||
| `createPlugin(ConnectionManager, PluginWorkspace)` | Único lugar de arranque. Aqui você cria o painel, chama `bindShellWorkspaceContent` e registra as ações da lista |
|
||||
| `getMessages()` | Lista das classes `Message` deste plugin. Sem a classe aqui, o núcleo não entrega o objeto |
|
||||
| `receiveMessage(Message)` | Chegou uma mensagem cuja classe está na lista. Trate na thread do Swing com `SwingUtilities.invokeLater` se for mexer em botão ou texto |
|
||||
|
||||
`createPlugin` **não** abre a aba. A aba abre quando a pessoa clica na galeria ou numa peer action.
|
||||
|
||||
## O que é opcional
|
||||
|
||||
| Método | Quando usar |
|
||||
| --- | --- |
|
||||
| `onShellTabClosed()` | A pessoa fechou a aba do plugin no topo. Pare timer, captura de tela ou envio contínuo |
|
||||
| `receiveAllTypes()` | Receber toda mensagem pública, não só as suas. O padrão é `false` |
|
||||
| `receivePrivateConnection(TcpConnection)` | Aceitar um TCP aberto para o seu `getIdentifier()` |
|
||||
| `receiveBytes(TcpConnection, byte[])` | Ler bytes crus nessa conexão, em vez de uma `Message` |
|
||||
| `onSocketClosed(TcpConnection)` | Limpar estado quando o socket fecha |
|
||||
| `onInviteAccepted(InviteDetails)` | Reagir a um convite aceito |
|
||||
|
||||
`bindShellWorkspaceContent(JComponent)` é `protected` na classe `Plugin`. Guarda o painel que a shell mostra. Não use `JFrame`: a janela já é a `MainShellFrame`.
|
||||
|
||||
## Enviar
|
||||
|
||||
`MessageDispatcher.send` devolve quantos envios o núcleo tentou.
|
||||
|
||||
```java
|
||||
MessageDispatcher.send(
|
||||
connectionManager,
|
||||
mensagem,
|
||||
SendTarget.singlePeer(endereco),
|
||||
TransportMode.UNICAST_UDP
|
||||
);
|
||||
```
|
||||
|
||||
`SendTarget` é o destino:
|
||||
|
||||
- `SendTarget.broadcastAll()` — a sala (a linha **Todos**).
|
||||
- `SendTarget.singlePeer(InetAddress)` — um IPv4.
|
||||
|
||||
`TransportMode` é o meio:
|
||||
|
||||
| Valor | O que o núcleo faz |
|
||||
| --- | --- |
|
||||
| `MULTICAST_UDP` | Um datagrama para `224.0.0.3` |
|
||||
| `UNICAST_UDP` | Um datagrama para o IP. Se o destino for a sala inteira, o núcleo manda uma cópia para cada pessoa online |
|
||||
| `TCP` | Frame tamanho + bytes. Na sala inteira, uma conexão por pessoa |
|
||||
|
||||
`describeForUi(boolean)` é o texto do combo (“Direto para o IP da pessoa (UDP unicast)”). O plugin de referência usa `PluginRoutingPanel` do núcleo para esse combo, em vez de desenhar o seletor do zero.
|
||||
|
||||
`ConnectionManager` que chega em `createPlugin` também expõe `getUserName()`, `sendUdpUnicast`, `sendUdpMulticast` e `sendTcp`. Para o caso comum, `MessageDispatcher` já escolhe o método certo.
|
||||
|
||||
IP legível na interface: `PeerIpv4.normalize(...)` e `LocalNetworkInfo.bestLocalIpv4()`. No Windows, `InetAddress.getHostAddress()` pode vir com `::ffff:` ou sufixo `%n`.
|
||||
|
||||
## Aba e lista de pessoas
|
||||
|
||||
`PluginWorkspace` (`pitiupi.api.plugin.PluginWorkspace`):
|
||||
|
||||
- `connectionManager()` — o mesmo gestor de rede.
|
||||
- `peerActions()` — onde você registra ações.
|
||||
- `openOrFocusTab(pluginId, titulo)` — abre ou foca a aba central. As peer actions chamam isso; `createPlugin` não.
|
||||
- `setStatusMessage(String)` — texto de status da shell.
|
||||
|
||||
Uma peer action é um `PeerAction.of(id, rótulo, alvo, o que fazer)`:
|
||||
|
||||
- `PeerActionTarget.SINGLE_PEER` — aparece ao clicar numa pessoa. O alvo chega como `PeerTarget.Single` e `single.peer()` é a ficha dessa pessoa (nome e IP).
|
||||
- `PeerActionTarget.ALL_PEERS` — aparece na linha **Todos**.
|
||||
|
||||
```java
|
||||
registrar.register(PeerAction.of(
|
||||
"ping.peer",
|
||||
"Enviar ping para esta pessoa",
|
||||
PeerActionTarget.SINGLE_PEER,
|
||||
target -> {
|
||||
workspace.openOrFocusTab(getIdentifier(), getDisplayName());
|
||||
if (target instanceof PeerTarget.Single single) {
|
||||
panel.focusPeerAddress(single.peer());
|
||||
}
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
A lista agrupa as ações de todos os plugins, num submenu por `getDisplayName()`. A ação do ping de referência só abre a aba e o IP. O envio fica no botão do painel.
|
||||
|
||||
## Mensagem sua
|
||||
|
||||
Estenda `pitiupi.models.Messages.Message`. Construtor sem argumentos é necessário para a desserialização Java. Campos precisam ser serializáveis (`String`, `long`, enum, etc.).
|
||||
|
||||
```java
|
||||
public class PingMessage extends Message {
|
||||
private String pingId;
|
||||
private long sentAtMillis;
|
||||
|
||||
public PingMessage() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Registre a classe:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public List<Class<? extends Message>> getMessages() {
|
||||
return List.of(PingMessage.class, PongMessage.class);
|
||||
}
|
||||
```
|
||||
|
||||
O núcleo só entrega essas classes para este plugin. Outro JAR não recebe o objeto por engano, e um tipo que você esqueceu de listar é descartado.
|
||||
|
||||
Em `receiveMessage`, separe com `instanceof` e passe o trabalho para uma classe sem Swing (no ping, `PingController`). A atualização da tela volta pelo `invokeLater`: o núcleo pode chamar `receiveMessage` fora da thread do Swing, e botão ou texto só mudam nessa thread.
|
||||
|
||||
`Message` tem `isPublic()`. O padrão do construtor é mensagem pública (a sala e o despacho normal). Uma conexão TCP privada não passa por essa lista: ela usa os métodos da seção seguinte.
|
||||
|
||||
## TCP dirigido a um plugin
|
||||
|
||||
Serve quando o fluxo não é uma `Message` da sala, e sim bytes numa conexão só sua. O chat faz isso para o texto privado.
|
||||
|
||||
1. Quem inicia chama `connectionManager.connectPluginTcpAsync(ip, getIdentifier(), getIdentifier(), listener)`. Os dois ids são o mesmo `getIdentifier()`: o destinatário no handshake é este plugin, nos dois lados. O `TcpAttemptListener` recebe “conectando”, sucesso ou falha. O prazo de 5 segundos e o aviso na janela estão em [rede.md](rede.md).
|
||||
2. No outro lado, `receivePrivateConnection` devolve `true` para aceitar. `false` faz o núcleo responder que o plugin não está lá.
|
||||
3. Cada frame seguinte chega em `receiveBytes(TcpConnection, byte[])`. O plugin interpreta os bytes. O chat desserializa a mensagem antiga do texto privado.
|
||||
4. `onSocketClosed` limpa o estado quando o socket fecha.
|
||||
|
||||
Não use esse caminho para um ping ou um texto de sala. `MessageDispatcher.send(..., TransportMode.TCP)` já manda uma `Message` na conexão pública.
|
||||
|
||||
## Aviso de sessão
|
||||
|
||||
`SessionNoticeMessage` avisa que uma atividade começou ou terminou, sem ser o payload em si. Campos: id do plugin, fase `START` ou `END`, canal, nome e IP de quem enviou, escopo `BROADCAST` ou `DIRECT`, e o `TransportMode` usado.
|
||||
|
||||
Inclua `SessionNoticeMessage.class` em `getMessages()` se quiser reagir. O screencast envia fase `END` e canal `SCREEN_CAST` quando a pessoa para o compartilhamento. Quem está na aba de recebimento fecha essa aba. O canal hoje só tem `SCREEN_CAST`; outro canal pede um valor novo no enum do núcleo.
|
||||
|
||||
## Abas de sessão
|
||||
|
||||
O núcleo oferece as chaves em `PluginSessionKeys`:
|
||||
|
||||
| Chave | Significado |
|
||||
| --- | --- |
|
||||
| `ROOM` (`"room"`) | A sala, a linha **Todos** |
|
||||
| `peer(ip)` (`"peer:"` + IP) | Aba de envio para aquele IP |
|
||||
| `PEER_DRAFT` | Rascunho do **+ Nova**, antes do IP ser confirmado |
|
||||
|
||||
A chave `view:` + IP não é do núcleo. É convenção dos plugins de referência para a aba que nasceu porque chegou tráfego, com envio desligado (`PluginRoutingPanel.bindReceiveSession`).
|
||||
|
||||
`routingPanel.snapshot()` lê destino e transporte no instante do clique. O controller guarda esse retrato e não consulta o combo depois. Assim a regra de envio não depende do componente Swing.
|
||||
|
||||
## Convite e selo
|
||||
|
||||
`onInviteAccepted(InviteDetails)` existe. O núcleo entrega o convite se alguém enviar `SessionInviteMessage`. Chat, screencast e ping não enviam. A janela não mostra um diálogo de convite.
|
||||
|
||||
`PeerActivityInbox` conta atividade por pessoa. A lista lateral não desenha um número. O que a pessoa vê é a aba do plugin.
|
||||
|
||||
## O que o plugin não faz
|
||||
|
||||
- Não escolhe a porta nem o grupo multicast. Isso é login e Configurações.
|
||||
- Não mantém a lista de presença. Leia as pessoas por `connectionManager.getPresenceService().listPeerViews()` se precisar; a shell já mostra a lista.
|
||||
- Não recompila o núcleo. O classpath de compilação do plugin aponta para `pitiupi/dist/pitiupi.jar`.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Mapa do núcleo
|
||||
|
||||
O código do programa está em `pitiupi/src/pitiupi`. Um plugin não precisa abrir essas pastas para funcionar: o contrato de chamada está em [nucleo-para-plugins.md](nucleo-para-plugins.md). Este capítulo é para quem vai ler o código ou alterar o núcleo.
|
||||
|
||||
## Pastas
|
||||
|
||||
| Pacote | O que faz |
|
||||
| --- | --- |
|
||||
| `Main` | Garante `.pitiupi/`, pede nome e porta no `LoginDialog` e abre `MainShellFrame` |
|
||||
| `control.ConnectionManager` | Fachada da rede: porta, nome, envio UDP e TCP, presença, lista de ações |
|
||||
| `control.HeartbeatManager` | Anúncio periódico e timeout de quem sumiu |
|
||||
| `control.plugins.PluginManager` | Lê os JARs, acha as classes pelo SPI e entrega `receiveMessage` |
|
||||
| `net.udp.SocketUDP` | O `MulticastSocket` do grupo `224.0.0.3` |
|
||||
| `net.tcp` | `ServerSocket`, uma `TcpConnection` por IP, framing de 4 bytes + N bytes |
|
||||
| `application.messaging` | `MessageDispatcher`, `SendTarget`, `TransportMode`, combo de transporte |
|
||||
| `application.presence` | A lista **Usuários nesta rede** (`PresenceService`, `UserPeerView`) |
|
||||
| `application.peeractions` | O menu que aparece ao clicar numa pessoa |
|
||||
| `application.session` | Aviso de início ou fim de uma atividade (`SessionNoticeMessage`) |
|
||||
| `application.invites` | Convite de sessão. O núcleo recebe e pode chamar `onInviteAccepted`. Nenhum plugin de referência envia convite, e a janela não mostra uma caixa de convite |
|
||||
| `application.activity` | Contagem de atividade por pessoa. A lista lateral não desenha selo |
|
||||
| `presentation.shell` | Janela, abas, galeria, configurações, painéis que o plugin reutiliza |
|
||||
| `models` | `Plugin`, `Message` e as mensagens do próprio núcleo |
|
||||
| `plugins.store` | Copia o JAR para `.pitiupi/plugins` e, no clone, grava `plugins.registry.json` |
|
||||
| `platform` | Onde fica `.pitiupi` (`PitiupiHome`) e `config.properties` de layout |
|
||||
| `errors.ErrorCode` | Quatro textos em português: falha ao instalar, ao remover, ao carregar, caminho inválido |
|
||||
|
||||
Nome, porta, TTL e saída de rede ficam nas preferências do Java (`NetworkPreferences`), não nesse `config.properties`.
|
||||
|
||||
## Caminho de um datagrama UDP
|
||||
|
||||
`SocketUDP.receive` lê o pacote e tenta desserializar com o classloader do núcleo.
|
||||
|
||||
1. `HeartbeatMessage` vai para `ConnectionManager.receiveHeartbeat`. Não chega no plugin.
|
||||
2. `GoodbyeMessage` vai para `receiveGoodbye`. Não chega no plugin.
|
||||
3. `SessionInviteMessage` vai para `receiveSessionInvite`. Não chega em `receiveMessage`.
|
||||
4. Qualquer outro byte vai para `PluginManager.handleMessage`. Cada JAR tenta desserializar com o próprio classloader. Se a classe está em `getMessages()` (ou `receiveAllTypes()` é verdadeiro e a mensagem é pública), o núcleo chama `receiveMessage`.
|
||||
|
||||
Por isso heartbeat e goodbye não entram na lista do plugin. Se entrarem, o classloader do JAR não é o dono dessas classes.
|
||||
|
||||
## Caminho de um frame TCP
|
||||
|
||||
`TcpConnection` lê um `int` e depois exatamente essa quantidade de bytes. Na abertura, o núcleo troca um `HandshakeMessage` (classe interna de `net.tcp`, invisível ao plugin) com o remetente, o destinatário e a marca de conexão privada.
|
||||
|
||||
- Conexão pública: o frame, se for uma `Message`, segue o mesmo `PluginManager.handleMessage` do UDP.
|
||||
- Conexão privada cujo destinatário é um `getIdentifier()` de plugin: `PluginManager.handleConnection` chama `receivePrivateConnection`. Se o plugin devolve `true`, os frames seguintes vão para `receiveBytes`, e o fechamento chama `onSocketClosed`.
|
||||
- Destinatário que nenhum plugin aceita: o núcleo manda um `InfoMessage` de receptor não encontrado e não entrega os bytes.
|
||||
|
||||
O prazo de 5 segundos para abrir o socket está em [rede.md](rede.md).
|
||||
|
||||
## Mensagens que o núcleo define
|
||||
|
||||
| Classe | Quem cria | Quem consome |
|
||||
| --- | --- | --- |
|
||||
| `HeartbeatMessage` | `HeartbeatManager`, em multicast | `SocketUDP` → presença. Fora do plugin |
|
||||
| `GoodbyeMessage` | Fechamento da janela ou troca de porta | `SocketUDP` → tira a pessoa da lista. Fora do plugin |
|
||||
| `InfoMessage` | Núcleo, em conexões TCP de controle | O próprio TCP (erro, fechamento, plugin ausente). Não é texto de chat |
|
||||
| `HandshakeMessage` | Abertura TCP, pacote `net.tcp` | Só o núcleo. Não declare no plugin |
|
||||
| `SessionNoticeMessage` | Um plugin, em geral o screencast ao parar | Outro plugin que listar a classe em `getMessages()` |
|
||||
| `SessionInviteMessage` | Quem chamar o envio | `InviteInboxService`. A interface atual não mostra o convite |
|
||||
|
||||
`SessionNoticeMessage` carrega o id do plugin, a fase (`START` ou `END`), o canal (`SCREEN_CAST` hoje), o nome e o IP de quem enviou, o escopo (`BROADCAST` ou `DIRECT`) e o `TransportMode`. O screencast manda `END` no canal `SCREEN_CAST` quando o compartilhamento para. Quem recebe e registrou o tipo fecha a aba de recebimento.
|
||||
|
||||
## Onde alterar o núcleo
|
||||
|
||||
| Mudança | Onde |
|
||||
| --- | --- |
|
||||
| Novo tipo só do seu plugin | Classe no JAR, `extends Message`, entrada em `getMessages()`. O núcleo não muda |
|
||||
| Novo transporte no combo da interface | Valor em `PluginTransportCatalog.PluginKind`. Hoje existem `CHAT`, `SCREEN_CAST` e `PING` |
|
||||
| Mensagem de infraestrutura (todo mundo precisa entender sem ter o JAR) | `models.Messages` e o filtro em `SocketUDP.tryParseCoreUdpMessage` |
|
||||
| Nova ação na lista de pessoas | Não é mudança de núcleo: `PeerAction` no plugin |
|
||||
| Texto de erro de instalação | `errors.ErrorCode` |
|
||||
|
||||
O grupo multicast, a porta única e o framing TCP estão decididos em [decision-register.md](decision-register.md). O comportamento na rede está em [rede.md](rede.md).
|
||||
@@ -0,0 +1,92 @@
|
||||
# Plugins
|
||||
|
||||
O núcleo não conhece chat, tela ou ping pelo nome. Na subida, `PluginManager` lista os `.jar` de `.pitiupi/plugins` (ou de `PITIUPI_HOME/plugins`, se você definir essa variável). Para cada arquivo cria um classloader só daquele JAR e pede ao Java as implementações de `pitiupi.models.Plugin`.
|
||||
|
||||
## Por que existe o arquivo SPI
|
||||
|
||||
SPI (Service Provider Interface) é o mecanismo padrão do Java para achar implementações sem o núcleo importar a sua classe. A classe que faz isso é `java.util.ServiceLoader`.
|
||||
|
||||
O `ServiceLoader` abre, dentro do JAR, um arquivo de texto com este caminho exato:
|
||||
|
||||
```
|
||||
META-INF/services/pitiupi.models.Plugin
|
||||
```
|
||||
|
||||
O **nome do arquivo** tem de ser o nome completo da classe que o núcleo procura (`pitiupi.models.Plugin`). O **conteúdo** é uma linha: o nome completo da sua subclasse, por exemplo:
|
||||
|
||||
```
|
||||
pitiupi.ping.plugin.PingPlugin
|
||||
```
|
||||
|
||||
Se o arquivo tiver outro nome, o Java não associa a lista à classe `Plugin` e o JAR sobe vazio. Se a linha não for o pacote mais o nome da classe, o carregador não instancia nada. Uma linha, sem espaço no fim, sem `.java`.
|
||||
|
||||
No código-fonte do módulo o arquivo fica em:
|
||||
|
||||
```
|
||||
plugins/ping/src/META-INF/services/pitiupi.models.Plugin
|
||||
```
|
||||
|
||||
O Ant copia `src/META-INF` para dentro do JAR (`meta.inf.dir` no `project.properties`). Depois de `ant jar`, confira:
|
||||
|
||||
```bash
|
||||
jar tf plugins/ping/dist/pitiupi.ping.jar | grep META-INF/services
|
||||
```
|
||||
|
||||
A saída precisa mostrar `META-INF/services/pitiupi.models.Plugin`.
|
||||
|
||||
## O que acontece ao abrir o programa
|
||||
|
||||
1. `Main` garante a pasta `.pitiupi/` e o login (nome e porta).
|
||||
2. `MainShellFrame` chama `PluginManager.loadPlugins()`.
|
||||
3. Cada classe listada no arquivo SPI é instanciada.
|
||||
4. O núcleo chama `createPlugin(connectionManager, workspace)`.
|
||||
5. O painel registrado com `bindShellWorkspaceContent` fica guardado. A aba só abre na galeria ou numa peer action.
|
||||
6. Quando um UDP não é heartbeat, ou quando um frame TCP é uma `Message`, o núcleo desserializa com o classloader do JAR e chama `receiveMessage` se o tipo estiver em `getMessages()`.
|
||||
|
||||
Fechar a aba do plugin no topo chama `onShellTabClosed` antes de tirar o painel. Instalar ou remover um JAR não recarrega a lista em memória: a aba **Configurações** pede para reiniciar.
|
||||
|
||||
Cada JAR tem o próprio classloader. A desserialização usa `PluginObjectInputStream` com esse classloader, para achar `PingMessage` dentro do plugin e não dentro do núcleo. Instale só JARs em que você confia: não há sandbox. Um JAR carregado roda com o mesmo poder do programa.
|
||||
|
||||
## Catálogo e a aba Configurações
|
||||
|
||||
São duas listas diferentes.
|
||||
|
||||
**`plugins.registry.json`**, na raiz do clone, diz ao `./scripts/pitiupi.sh` o que compilar e o que colocar em `.pitiupi/plugins` no próximo `run`, `build` ou `link`. Cada entrada tem:
|
||||
|
||||
| Campo | Significado |
|
||||
| --- | --- |
|
||||
| `id` | Nome do atalho (`chat` vira `.pitiupi/plugins/chat.jar`) e o filtro `--plugins` |
|
||||
| `dir` | Pasta do módulo, relativa à raiz |
|
||||
| `build` | `ant`, `make` ou `jar` |
|
||||
| `jar` | Caminho do JAR relativo a `dir` |
|
||||
| `menuCategory` | `Social`, `Jogos` ou `Streaming` — só filtro do script |
|
||||
|
||||
`build: ant` ou `make` compila a pasta se existir `build.xml` ou `Makefile`. `build: jar` não compila: o script só vincula o arquivo que já está em `dir`. A aba **Configurações → Instalar JAR…** grava uma cópia em `.pitiupi/jar-catalog/` e acrescenta uma entrada `build: jar`. **Remover selecionado** apaga o JAR e tira essa entrada do JSON. O próximo `run` recria `.pitiupi/plugins` só com o que ainda está no catálogo: um JAR instalado e depois apagado do JSON não volta.
|
||||
|
||||
Se o programa rodar fora de um clone (sem `plugins.registry.json` ao lado), instalar e remover ainda copiam ou apagam o arquivo em `.pitiupi/plugins`, e o catálogo simplesmente não é alterado.
|
||||
|
||||
## O que cada módulo de referência faz
|
||||
|
||||
Os três usam a mesma casca de tela quando a atividade fica na aba central: `PluginSessionWorkspace` no corpo, `PluginRoutingPanel` no topo (destino e transporte) e `SessionTabBar` nas abas. A sala usa a chave `room`. A aba que você abriu para um IP começa com `peer:`. A aba que nasceu porque chegou tráfego, nos plugins de referência, começa com `view:` e deixa o envio somente leitura (`bindReceiveSession`).
|
||||
|
||||
### Chat — `plugins/chat`
|
||||
|
||||
`ChatPlugin` declara `ChatMessage` e `LegacyMpChatMessage`. `ChatMessage` leva o texto, o nome, o rótulo do transporte e o IP de quem enviou. Sala e um IP saem por `MessageDispatcher`: multicast, unicast ou TCP público, conforme o combo.
|
||||
|
||||
O texto privado é outro caminho. `connectPluginTcpAsync` abre TCP cujo destinatário é o `getIdentifier()` do chat. `receivePrivateConnection` aceita, `receiveBytes` lê o frame e `PrivateChatController` guarda a conversa. Essa conversa abre `PrivateChatWindow`, um `JFrame` separado. O painel da aba central continua dentro da shell. Um plugin novo não precisa dessa janela extra: o privado do chat é o que o código faz hoje, não o modelo da aba.
|
||||
|
||||
`LegacyMpChatMessage` existe para ler um formato antigo de mensagem. Não é o tipo que você cria num plugin novo.
|
||||
|
||||
### Screencast — `plugins/screencast`
|
||||
|
||||
`ScreenCastMessage` leva o nome e o `byte[]` da imagem. O combo da sala oferece UDP multicast e UDP unicast. Para um IP, oferece unicast e TCP. A captura fica em `control` (`ScreenShot`, temporizador). O painel é `ScreenCastPanel`.
|
||||
|
||||
Ao parar, o painel envia `SessionNoticeMessage` com fase `END` e canal `SCREEN_CAST`. O `ScreenCastPlugin` também lista essa classe em `getMessages()` e, ao receber `END`, fecha a aba de quem estava assistindo.
|
||||
|
||||
### Ping — `plugins/ping`
|
||||
|
||||
Mede RTT com `PingMessage`, `PongMessage` e `PingStopMessage`, só em UDP. A montagem arquivo por arquivo está no material de aula. Aqui basta saber que o envio passa por `MessageDispatcher` e que a aba não abre sozinha no login.
|
||||
|
||||
A lista lateral não desenha um selo numérico de mensagens não lidas. O recebimento aparece na aba.
|
||||
|
||||
Como chamar cada método: [nucleo-para-plugins.md](nucleo-para-plugins.md). Onde cada pacote mora: [nucleo.md](nucleo.md). Como a rede trata o byte: [rede.md](rede.md).
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
# Rede no Pitiupi
|
||||
|
||||
Dois processos só se veem se estiverem na mesma sub-rede, na mesma porta e se o multicast da rede local não for filtrado. O Pitiupi não usa ICMP: o “ping” da disciplina é um plugin de mensagens Java, descrito à parte. Também não há criptografia.
|
||||
|
||||
A mesma porta do login serve para UDP e para TCP. Trocar a porta na aba **Configurações** envia um goodbye na sala antiga, fecha as conexões TCP locais e passa a anunciar na porta nova. Se a porta nova não abre, a sala anterior volta.
|
||||
|
||||
## UDP
|
||||
|
||||
UDP manda um datagrama e não abre conexão. Não há garantia de entrega nem de ordem. Multicast entrega uma cópia do pacote a quem entrou no mesmo grupo.
|
||||
|
||||
No Pitiupi, `SocketUDP` cria um `MulticastSocket` na porta do login, entra no grupo **`224.0.0.3`** e fica numa thread recebendo datagramas. Os buffers têm 256000 bytes. O TTL padrão é **1** (só a rede local). Na aba Configurações ele vai de 1 a 32.
|
||||
|
||||
Há dois destinos, no mesmo socket:
|
||||
|
||||
- **Multicast** (`sendMulticast`): o pacote vai para `224.0.0.3`. Quem está no grupo e na mesma porta recebe.
|
||||
- **Unicast** (`sendUnicast`): o pacote vai para o IPv4 de uma pessoa, na mesma porta.
|
||||
|
||||
Um plugin não abre esse socket. Ele pede o envio a `MessageDispatcher`, que chama `ConnectionManager.sendUdpMulticast` ou `sendUdpUnicast`. O desenho está em [nucleo-para-plugins.md](nucleo-para-plugins.md).
|
||||
|
||||
Na chegada, `SocketUDP` tenta ler o datagrama como `HeartbeatMessage`. Se for heartbeat, entrega ao `HeartbeatManager`. Se não for, os bytes vão ao `PluginManager`, que tenta desserializar com as classes que cada plugin declarou.
|
||||
|
||||
## Presença
|
||||
|
||||
Ver alguém na lista **Usuários nesta rede** significa que um heartbeat UDP recente chegou. Isso não abre TCP.
|
||||
|
||||
A cada cerca de 5 segundos o núcleo manda, em multicast, uma `HeartbeatMessage` com o nome, um id da instância e o instante. Quem recebe atualiza o peer pelo id da instância. Se o nome mudar, a lista do colega mostra o nome novo no anúncio seguinte.
|
||||
|
||||
Quem fica cerca de **15 segundos** sem heartbeat sai da lista. Ao fechar a janela, o núcleo manda `GoodbyeMessage` em multicast. `SocketTCP` escuta essa lista e fecha a conexão TCP de quem saiu.
|
||||
|
||||
Heartbeat e goodbye são do núcleo. O plugin não declara esses tipos em `getMessages()`.
|
||||
|
||||
## TCP
|
||||
|
||||
TCP é um fluxo de bytes entre dois sockets, com entrega ordenada. O fluxo não marca onde uma mensagem acaba e a outra começa. Por isso cada envio no Pitiupi é:
|
||||
|
||||
```
|
||||
4 bytes (int, tamanho N) + N bytes da mensagem serializada
|
||||
```
|
||||
|
||||
`TcpConnection.send` escreve o tamanho e os bytes e dá `flush`. `receive` lê o inteiro e depois exatamente N bytes. O acesso à saída é sincronizado para dois envios não se misturarem.
|
||||
|
||||
`SocketTCP` abre um `ServerSocket` na mesma porta do login. Existe uma conexão pública reaproveitada por endereço IP: se já houver socket aberto para aquele IP, o próximo envio usa o mesmo. Ao aceitar uma conexão nova do mesmo IP, a anterior é fechada.
|
||||
|
||||
Abrir TCP pode demorar. `ConnectionFactory` usa `Socket.connect` com prazo de **5 segundos**. O chat dispara isso fora da thread da janela e só mostra “Conectando em …” enquanto ainda não há socket para aquele IP. O prazo não aparece na tela. O tempo de leitura depois do handshake continua longo.
|
||||
|
||||
Há também conexão TCP dirigida a um plugin (`receivePrivateConnection`, `receiveBytes`, `onSocketClosed`). O chat usa isso para o texto privado. O identificador do plugin (`getIdentifier()`) diz a quem a conexão pertence.
|
||||
|
||||
## Mensagens
|
||||
|
||||
`pitiupi.models.Messages.Message` implementa `Serializable`. `toByteArray()` vira o `byte[]` que UDP e TCP carregam. O transporte não olha os campos: quem entende o conteúdo é o plugin que declarou a classe.
|
||||
|
||||
Na recepção, `PluginManager` percorre os JARs. Para cada um, `PluginObjectInputStream` desserializa com o classloader daquele JAR. Se a classe estiver em `getMessages()` (ou se `receiveAllTypes()` for verdadeiro e a mensagem for pública), o núcleo chama `receiveMessage`. Tipo desconhecido é ignorado.
|
||||
|
||||
Os dois lados precisam da mesma versão das classes. JARs diferentes geram falha de desserialização, não uma mensagem “quase certa”.
|
||||
|
||||
## Qual transporte usar
|
||||
|
||||
| Necessidade | Transporte no Pitiupi |
|
||||
| --- | --- |
|
||||
| Avisar a sala inteira de uma vez | UDP multicast |
|
||||
| Falar com um IP sem conexão | UDP unicast |
|
||||
| Fluxo confiável e ordenado com um IP | TCP |
|
||||
| Saber quem está online | Heartbeat UDP (núcleo) |
|
||||
|
||||
Imagem grande em UDP pode passar do tamanho do datagrama e se perder. O screencast oferece TCP por isso. O ping de referência mede RTT com UDP unicast.
|
||||
|
||||
## Interface, TTL e WSL
|
||||
|
||||
A aba **Configurações** lista só as interfaces que **este** processo enxerga. Nome `eth`, `eno`, `enp`, `ens` ou “Ethernet” é cabo. Nome `wlan`, `wlp` ou “Wi-Fi” é Wi-Fi.
|
||||
|
||||
A saída pode ser Automático (cabo antes de Wi-Fi), só Cabo ou só Wi-Fi. Multicast, o IP exibido e o TCP de saída usam essa interface. No Automático, cabo com endereço `169.254.*` tem prioridade. `169.254.x` e `192.168.x` em máquinas diferentes não compartilham heartbeat.
|
||||
|
||||
| Processo | O que aparece |
|
||||
| --- | --- |
|
||||
| `java.exe` do Windows | Placas reais |
|
||||
| Ubuntu instalado no PC | `wlan*` e `eth*` / `enp*` reais |
|
||||
| `java` de dentro do WSL | Em geral `eth0` ou `eth1`, classificado como cabo. Não há `wlan` |
|
||||
|
||||
Dois arranjos de WSL, e nenhum substitui o Java do Windows na sala:
|
||||
|
||||
- **NAT** (IP `172.x`): o Linux não está na rede dos colegas. Aumentar o TTL não coloca esse processo na LAN.
|
||||
- **Rede espelhada** (o `eth*` mostra o mesmo IPv4 do Windows): o rótulo continua cabo por causa do nome Linux. O socket ainda é o da interface virtual.
|
||||
|
||||
Na sala, os dois lados usam o mesmo tipo de processo: os dois no Java do Windows, ou os dois no Ubuntu instalado na máquina. O script do WSL (`./scripts/pitiupi.sh run --all`) abre a janela no Java do Windows de propósito. O passo a passo de cada máquina está em [comecar.md](comecar.md).
|
||||
|
||||
No Windows, o campo de IP do peer usa `PeerIpv4`: IPv4 pontilhado, sem forma `::ffff:` e sem sufixo de zona (`%7`).
|
||||
|
||||
## Dois computadores
|
||||
|
||||
1. A mesma porta no login (por exemplo `5000`).
|
||||
2. IPs na mesma sub-rede (`192.168.0.x` com `192.168.0.y`, ou os dois em `169.254.x`).
|
||||
3. Firewall liberando UDP e TCP nessa porta.
|
||||
4. No roteador, sem isolamento de clientes, sem Wi-Fi de visitantes que bloqueie multicast.
|
||||
5. Esperar até cerca de 15 segundos (o anúncio sai a cada 5).
|
||||
|
||||
Se o `tracert` até o colega tiver um salto só, TTL 1 basta. Se houver mais roteadores no meio, suba o TTL até o número de saltos, no máximo 32, nos dois lados. Um roteador só encaminha o grupo multicast se estiver configurado para isso.
|
||||
|
||||
Como ler ping do sistema, `netstat` e lista vazia: [usar.md](usar.md).
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Plugin as Plugin
|
||||
participant Disp as MessageDispatcher
|
||||
participant CM as ConnectionManager
|
||||
participant UDP as SocketUDP
|
||||
participant Peer as SocketUDPDoColega
|
||||
participant PM as PluginManager
|
||||
|
||||
Plugin->>Disp: Message, destino, transporte
|
||||
Disp->>CM: sendUdpUnicast ou sendUdpMulticast
|
||||
CM->>UDP: DatagramPacket
|
||||
UDP->>Peer: rede local
|
||||
Peer->>PM: bytes
|
||||
PM->>Plugin: receiveMessage se o tipo foi declarado
|
||||
```
|
||||
@@ -1,22 +1,8 @@
|
||||
# Referências — Pitiupi
|
||||
# Referências bibliográficas
|
||||
|
||||
Bibliografia e notas curtas para apoiar o estudo de Redes de Computadores. **Fonte única** para citações longas — capítulos em `documentation/` linkam aqui em vez de repetir bibliografia.
|
||||
O texto de estudo está em [referencias.md](../referencias.md). Este diretório guarda o BibTeX e PDFs locais.
|
||||
|
||||
**Sugestões de livro-texto:** Kurose & Ross — *Redes de Computadores e a Internet*; Tanenbaum — *Redes de Computadores*; Peterson & Davie — *Redes de Computadores*.
|
||||
|
||||
**RFCs úteis:** UDP (768), TCP (793), IP multicast (1112, 3376). Documentação Java: [Serialization](https://docs.oracle.com/en/java/javase/17/docs/specs/serialization/index.html), [SPI](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html), sockets e Swing EDT.
|
||||
|
||||
| Recurso | Descrição |
|
||||
| Arquivo | Conteúdo |
|
||||
| --- | --- |
|
||||
| [bibliografia.bib](bibliografia.bib) | Entradas BibTeX (livro-texto, RFCs, documentação Java) |
|
||||
| [notas/](notas/) | Resumos em PT-BR ligados ao código |
|
||||
| [pdfs/](pdfs/) | PDFs locais (não versionados se grandes; ver `.gitkeep`) |
|
||||
|
||||
## Notas disponíveis
|
||||
|
||||
- [udp_vs_tcp.md](notas/udp_vs_tcp.md)
|
||||
- [multicast_lan.md](notas/multicast_lan.md)
|
||||
- [java_serializacao.md](notas/java_serializacao.md)
|
||||
- [java_spi_plugins.md](notas/java_spi_plugins.md)
|
||||
|
||||
Voltar ao índice: [docs/README.md](../README.md).
|
||||
| [bibliografia.bib](bibliografia.bib) | Livros, RFCs e documentação Java |
|
||||
| [pdfs/](pdfs/) | PDFs locais, quando houver |
|
||||
@@ -1,11 +0,0 @@
|
||||
# Serialização Java (nota)
|
||||
|
||||
O Pitiupi serializa objetos `Message` (Java `Serializable`) para `byte[]` antes do envio.
|
||||
|
||||
Implicações:
|
||||
|
||||
- Mesma versão de classe nos peers (mesmo JAR de plugin).
|
||||
- `serialVersionUID` explícito evita incompatibilidade silenciosa.
|
||||
- Desserialização no núcleo usa `PluginObjectInputStream` com classloader do plugin.
|
||||
|
||||
Doc: [07_mensagens_e_serializacao.md](../../documentation/07_mensagens_e_serializacao.md).
|
||||
@@ -1,9 +0,0 @@
|
||||
# Java SPI — plugins (nota)
|
||||
|
||||
O núcleo usa `ServiceLoader` para localizar implementações de `pitiupi.models.Plugin` declaradas em:
|
||||
|
||||
`META-INF/services/pitiupi.models.Plugin`
|
||||
|
||||
Uma linha por classe concreta (FQCN). O JAR deve estar em `pitiupi/plugins/`.
|
||||
|
||||
Doc: [09_sistema_de_plugins.md](../../documentation/09_sistema_de_plugins.md).
|
||||
@@ -1,12 +0,0 @@
|
||||
# Multicast em LAN (nota)
|
||||
|
||||
IP multicast envia um datagrama que roteadores/switches encaminham aos assinantes do grupo (ex.: `224.0.0.3`).
|
||||
|
||||
Na sala de aula:
|
||||
|
||||
- Todos os peers devem estar na mesma sub-rede e permitir IGMP/multicast.
|
||||
- Wi‑Fi com “client isolation” impede descoberta.
|
||||
|
||||
Operação no código: [appendix_portas_e_multicast.md](../../documentation/appendix_portas_e_multicast.md).
|
||||
|
||||
RFC introdutória: ver entrada `rfc1112` em [bibliografia.bib](../bibliografia.bib).
|
||||
@@ -1,10 +0,0 @@
|
||||
# UDP vs TCP (nota)
|
||||
|
||||
| | UDP | TCP |
|
||||
| --- | --- | --- |
|
||||
| Conexão | Não | Sim |
|
||||
| Confiabilidade | Melhor esforço | Entrega ordenada na conexão |
|
||||
| Delimitação de mensagem | Por datagrama | Fluxo de bytes — Pitiupi usa framing 4+N |
|
||||
| Uso no Pitiupi | Heartbeat, multicast, plugins | Canais diretos peer-a-peer |
|
||||
|
||||
Ver [05_udp_multicast.md](../../documentation/05_udp_multicast.md) e [06_tcp_conexoes.md](../../documentation/06_tcp_conexoes.md).
|
||||
@@ -0,0 +1,12 @@
|
||||
# Referências
|
||||
|
||||
Os capítulos [rede.md](rede.md) e [plugins.md](plugins.md) já descrevem o que o Pitiupi faz. Os livros e as RFCs abaixo são o pano de fundo, não um segundo manual do código.
|
||||
|
||||
- Kurose e Ross — *Redes de Computadores e a Internet*.
|
||||
- Tanenbaum — *Redes de Computadores*.
|
||||
- Peterson e Davie — *Redes de Computadores*.
|
||||
- RFC 768 (UDP), RFC 793 (TCP), RFC 1112 e RFC 3376 (multicast).
|
||||
|
||||
Entradas BibTeX: [references/bibliografia.bib](references/bibliografia.bib).
|
||||
|
||||
Documentação Java usada pelo núcleo: [serialização](https://docs.oracle.com/en/java/javase/17/docs/specs/serialization/index.html), [ServiceLoader](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html), `MulticastSocket` e Swing.
|
||||
@@ -1,29 +1,17 @@
|
||||
# Visão da disciplina
|
||||
|
||||
## Papel do Pitiupi
|
||||
O Pitiupi é uma plataforma P2P com plugins para praticar, na mesma aplicação:
|
||||
|
||||
O Pitiupi é uma **plataforma P2P com plugins** usada em Redes de Computadores para:
|
||||
- UDP multicast e unicast, e TCP com mensagem delimitada por tamanho.
|
||||
- Presença por heartbeat, sem servidor central.
|
||||
- Uma extensão em JAR que usa o núcleo em vez de abrir o próprio socket.
|
||||
|
||||
- Experimentar UDP (multicast/unicast) e TCP na mesma aplicação.
|
||||
- Observar **presença** de peers (heartbeat) sem servidor central de aplicação.
|
||||
- Relacionar conceitos de redes com código Java executável na LAN.
|
||||
O código do programa está descrito em [comecar.md](../comecar.md), [rede.md](../rede.md) e [plugins.md](../plugins.md). As funções que um plugin chama estão em [nucleo-para-plugins.md](../nucleo-para-plugins.md).
|
||||
|
||||
O núcleo concentra rede, presença e interface; extensões entram como plugins em JAR.
|
||||
|
||||
## Plugins de referência no repositório
|
||||
|
||||
| Módulo | Ideia |
|
||||
| Módulo no repositório | O que exercita |
|
||||
| --- | --- |
|
||||
| `plugins/chat` | Mensagens e vários transportes (multicast, unicast, TCP) |
|
||||
| `plugins/screencast` | Payload maior / streaming de imagem |
|
||||
| `plugins/chat` | Sala, um IP, UDP e TCP |
|
||||
| `plugins/screencast` | Payload maior, multicast, unicast ou TCP |
|
||||
| `plugins/ping` | RTT com ping, pong e parada em UDP |
|
||||
|
||||
Detalhes técnicos: [11_plugins_referencia.md](../documentation/11_plugins_referencia.md).
|
||||
|
||||
## Exercício em sala
|
||||
|
||||
O passo a passo do **Ping** (mensagens na rede, RTT, UI mínima) está em [02_tutorial_ping_aula.md](02_tutorial_ping_aula.md).
|
||||
|
||||
## Aprofundamento técnico
|
||||
|
||||
- Primeiro uso: [00_quickstart.md](../documentation/00_quickstart.md)
|
||||
- Rede e protocolos no código: [16_fundamentacao_redes.md](../documentation/16_fundamentacao_redes.md) e capítulos 05–08 em [documentation/](../documentation/README.md)
|
||||
O passo a passo para montar o ping — qual arquivo, qual linha e qual método do núcleo — está em [02_tutorial_ping_aula.md](02_tutorial_ping_aula.md). A pasta `plugins/ping` já contém essa implementação.
|
||||
@@ -1,143 +1,461 @@
|
||||
# Tutorial de aula — plugin Ping
|
||||
# Tutorial — construir o plugin Ping
|
||||
|
||||
Exercício para montar o plugin **mais simples** do Pitiupi: medir **RTT** (tempo de ida e volta) com mensagens `Ping` / `Pong` na rede, igual a um `ping` de laboratório — mas usando o **mesmo mecanismo de plugins** do chat e do screencast.
|
||||
Este texto monta o plugin que mede o tempo de ida e volta (RTT) com mensagens Java na rede local. Não é o `ping` do sistema operacional: são objetos `PingMessage` e `PongMessage` serializados, no mesmo mecanismo do chat.
|
||||
|
||||
A solução completa **não** entra no repositório público: o exercício é montado em `plugins/ping/` na máquina local (fora do gate CI). Este tutorial descreve escopo, trechos e passos — o restante é implementado em sala.
|
||||
O resultado pronto deste repositório está em `plugins/ping`. Os passos dizem qual arquivo criar, o que escrever e qual função do núcleo entra em cada momento. Se `plugins/ping` já existe, não copie outra pasta por cima: abra o arquivo citado e confira o trecho. Para refazer do zero numa pasta vazia, comece pelo passo do projeto Ant.
|
||||
|
||||
Índice TP: [README.md](README.md) · Plugins de referência: [11_plugins_referencia.md](../documentation/11_plugins_referencia.md).
|
||||
Leia antes, em uma sentada curta:
|
||||
|
||||
## O que você vai aprender
|
||||
- [comecar.md](../comecar.md) — o programa abre e o núcleo já está compilado (`pitiupi/dist/pitiupi.jar`).
|
||||
- [plugins.md](../plugins.md) — por que existe o arquivo de texto do SPI.
|
||||
- [nucleo-para-plugins.md](../nucleo-para-plugins.md) — `Plugin`, `MessageDispatcher`, peer actions.
|
||||
|
||||
| Componente do núcleo | Para que serve no Ping |
|
||||
## O que vai existir no fim
|
||||
|
||||
| Arquivo | Papel |
|
||||
| --- | --- |
|
||||
| `Plugin` + SPI | O Pitiupi descobre seu JAR e chama `createPlugin` |
|
||||
| `Message` serializável | `PingMessage`, `PongMessage`, `PingStopMessage` |
|
||||
| `getMessages()` / `receiveMessage` | O núcleo entrega só os tipos que você registrou |
|
||||
| `MessageDispatcher` | Envia UDP unicast/multicast sem montar socket manual |
|
||||
| `PluginWorkspace` | Galeria e peer actions abrem a aba central |
|
||||
| `PeerAction` | Menu ao clicar em um usuário na lista lateral |
|
||||
| `SessionTabBar` | Abas por peer (passivas ao receber, como no chat) |
|
||||
| `src/META-INF/services/pitiupi.models.Plugin` | Diz ao Java o nome da classe do plugin |
|
||||
| `src/pitiupi/ping/net/PingMode.java` | `ONCE` ou `CONTINUOUS_TICK` |
|
||||
| `src/pitiupi/ping/net/PingMessage.java` | Ida: id, instante, nome, IP, modo, sessão |
|
||||
| `src/pitiupi/ping/net/PongMessage.java` | Volta: o mesmo id e o instante original |
|
||||
| `src/pitiupi/ping/net/PingStopMessage.java` | Avisa que o ping contínuo parou |
|
||||
| `src/pitiupi/ping/control/PingLogSink.java` | A tela recebe linhas de log |
|
||||
| `src/pitiupi/ping/control/PingController.java` | Envia, responde e calcula o RTT, sem desenhar botão |
|
||||
| `src/pitiupi/ping/GUI/PingPanel.java` | Abas, botões e a área de texto |
|
||||
| `src/pitiupi/ping/plugin/PingPlugin.java` | Liga tudo ao núcleo |
|
||||
|
||||
## Limitações didáticas (de propósito)
|
||||
|
||||
- Não é ICMP do sistema operacional — são mensagens Java na LAN.
|
||||
- Sem gráficos nem histórico em arquivo.
|
||||
- Transporte padrão: **UDP unicast** para um peer; multicast só no “ping para todos (uma vez)”.
|
||||
- Não há moderação, login extra nem criptografia.
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
1. Pitiupi abrindo com login (nome + porta).
|
||||
2. [`09_sistema_de_plugins.md`](../documentation/09_sistema_de_plugins.md).
|
||||
3. JDK 17 + Ant.
|
||||
4. Núcleo compilado: `pitiupi/dist/pitiupi.jar`.
|
||||
Limites de propósito: sem gráfico, sem arquivo de histórico, sem TCP. Um peer usa UDP unicast. A sala (**Todos**) pode usar multicast ou uma cópia unicast por pessoa. O ping contínuo (um por segundo) só existe na aba de uma pessoa.
|
||||
|
||||
## Passo 1 — Projeto Ant
|
||||
|
||||
1. Copie `plugins/chat` para `plugins/ping` (somente na sua máquina).
|
||||
2. Renomeie o projeto para `pitiupi.ping` e o JAR de saída para `dist/pitiupi.ping.jar`.
|
||||
3. Classpath de compilação: `../../pitiupi/dist/pitiupi.jar`.
|
||||
O chat já é um projeto NetBeans/Ant com classpath no núcleo. Copiar essa casca evita escrever `build.xml` à mão.
|
||||
|
||||
## Passo 2 — SPI
|
||||
Numa pasta `plugins/ping` ainda vazia:
|
||||
|
||||
Arquivo `src/META-INF/services/pitiupi.models.Plugin`:
|
||||
```bash
|
||||
cp -a plugins/chat plugins/ping
|
||||
rm -rf plugins/ping/src/pitiupi plugins/ping/build plugins/ping/dist
|
||||
```
|
||||
|
||||
O `rm` tira as classes do chat. O `build.xml`, o `nbproject/` e a pasta `src/` ficam.
|
||||
|
||||
Troque o nome do projeto em três arquivos. Em cada um, `chat` vira `ping`.
|
||||
|
||||
`plugins/ping/nbproject/project.xml`, a tag do nome (por volta da linha 6):
|
||||
|
||||
```xml
|
||||
<name>pitiupi.ping</name>
|
||||
```
|
||||
|
||||
`plugins/ping/build.xml`, a tag `project` da linha 10 e a descrição da linha 11:
|
||||
|
||||
```xml
|
||||
<project name="pitiupi.ping" default="default" basedir=".">
|
||||
<description>Builds, tests, and runs the project pitiupi.ping.</description>
|
||||
```
|
||||
|
||||
`plugins/ping/nbproject/project.properties`:
|
||||
|
||||
| Propriedade | Valor |
|
||||
| --- | --- |
|
||||
| `application.title` | `pitiupi.ping` |
|
||||
| `dist.jar` | `${dist.dir}/pitiupi.ping.jar` |
|
||||
| `dist.jlink.output` | `${dist.jlink.dir}/pitiupi.ping` |
|
||||
| `jlink.launcher.name` | `pitiupi.ping` |
|
||||
|
||||
Não mexa nestas duas, no final do mesmo `project.properties`. Elas dizem ao Ant onde está o JAR do núcleo:
|
||||
|
||||
```
|
||||
project.pitiupi=../../pitiupi
|
||||
reference.pitiupi.jar=${project.pitiupi}/dist/pitiupi.jar
|
||||
```
|
||||
|
||||
E o classpath de compilação continua:
|
||||
|
||||
```
|
||||
javac.classpath=\
|
||||
${reference.pitiupi.jar}
|
||||
```
|
||||
|
||||
Sem esse JAR o compilador não acha `Plugin` nem `Message`. Gere-o antes, na raiz do clone: `cd pitiupi && ant jar`.
|
||||
|
||||
Apague também o SPI do chat, se a cópia ainda tiver `src/META-INF/services/pitiupi.models.Plugin` com a linha `pitiupi.chat.plugin.ChatPlugin`. O passo 2 cria o arquivo certo.
|
||||
|
||||
## Passo 2 — Arquivo SPI
|
||||
|
||||
O núcleo não dá `new PingPlugin()`. Na subida, `PluginManager` chama `ServiceLoader.load(Plugin.class, classLoader)` para cada JAR. O `ServiceLoader` procura um arquivo de texto cujo **nome** é a classe que ele deve implementar:
|
||||
|
||||
```
|
||||
src/META-INF/services/pitiupi.models.Plugin
|
||||
```
|
||||
|
||||
Crie as pastas `src/META-INF/services/` e grave o arquivo **sem extensão extra**, com uma linha só:
|
||||
|
||||
```
|
||||
pitiupi.ping.plugin.PingPlugin
|
||||
```
|
||||
|
||||
## Passo 3 — Mensagens
|
||||
O nome do arquivo tem de ser `pitiupi.models.Plugin` porque é essa a classe abstrata do núcleo (`pitiupi.models.Plugin`). A linha tem de ser o pacote mais o nome da classe que você vai escrever no passo 6. Se o arquivo se chamar `Plugin` ou a linha for `PingPlugin` sem pacote, o JAR compila e o programa não mostra o plugin.
|
||||
|
||||
Três classes em `pitiupi.ping.net`, todas estendendo `pitiupi.models.Messages.Message`:
|
||||
O Ant inclui `src/META-INF` no JAR por causa de `meta.inf.dir=${src.dir}/META-INF` no `project.properties`. Depois do `ant jar`:
|
||||
|
||||
1. **`PingMessage`** — campos: `pingId` (UUID), `sentAtMillis`, `senderName`, `senderIp`, `mode` (`ONCE` ou `CONTINUOUS_TICK`), `sessionId` (para série contínua).
|
||||
2. **`PongMessage`** — `pingId`, `echoSentAtMillis` (copiado do ping), `responderName`, `responderIp`.
|
||||
3. **`PingStopMessage`** — `sessionId`, `senderName`, `senderIp` (avisa que o contínuo parou).
|
||||
|
||||
Registre em `PingPlugin.getMessages()`:
|
||||
|
||||
```java
|
||||
return List.of(PingMessage.class, PongMessage.class, PingStopMessage.class);
|
||||
```bash
|
||||
jar tf dist/pitiupi.ping.jar | grep META-INF/services
|
||||
```
|
||||
|
||||
**Por quê:** o núcleo só desserializa tipos declarados; evita que outro plugin receba seus bytes por engano.
|
||||
A saída esperada é `META-INF/services/pitiupi.models.Plugin`.
|
||||
|
||||
## Passo 4 — `PingController` (lógica)
|
||||
## Passo 3 — Mensagens
|
||||
|
||||
Classe em `pitiupi.ping.control` **sem Swing**:
|
||||
Todas estendem `pitiupi.models.Messages.Message`, que já é `Serializable`. O construtor vazio existe porque a desserialização Java precisa criar o objeto e depois preencher os campos. Sem ele, o outro lado não reconstrói a mensagem.
|
||||
|
||||
- Ao **enviar** ping: grave `pendingByPingId.put(pingId, sentAtMillis)` e use `MessageDispatcher.send(..., UNICAST_UDP)`.
|
||||
- Ao **receber** `PingMessage`: responda com `PongMessage` (eco do `sentAtMillis`).
|
||||
- Ao **receber** `PongMessage`: `rtt = now - pending.remove(pingId)` e notifique a UI.
|
||||
- **Contínuo:** `javax.swing.Timer` a cada 1 s envia `CONTINUOUS_TICK` com o mesmo `sessionId`.
|
||||
- **Parar:** cancele o timer local e envie `PingStopMessage` ao peer.
|
||||
`PingMode` não é mensagem. É o enum que diz se aquele ping é único ou um tique da série contínua. Arquivo `src/pitiupi/ping/net/PingMode.java`:
|
||||
|
||||
Use `PeerIpv4` e `LocalNetworkInfo.bestLocalIpv4()` para IP legível no Windows.
|
||||
```java
|
||||
package pitiupi.ping.net;
|
||||
|
||||
## Passo 5 — `PingPanel` (UI)
|
||||
public enum PingMode {
|
||||
ONCE,
|
||||
CONTINUOUS_TICK
|
||||
}
|
||||
```
|
||||
|
||||
Siga o mesmo chrome do screencast: `PluginSessionWorkspace` + `PluginRoutingPanel` (`PluginKind.PING`, sem TCP privado).
|
||||
`src/pitiupi/ping/net/PingMessage.java` — a ida:
|
||||
|
||||
- **Destino e transporte** no topo (IP, forma de envio, banner da sessão).
|
||||
- Toolbar: **Enviar ping** (uma vez) e **Ping contínuo** (toggle só em abas de peer **↑**; na sala **Todos** só o envio único).
|
||||
- `SessionTabBar`: sala `Todos ↑`; peers que você abre `Nome ↑`; recebimento passivo `Nome ↓` (`view:<ip>`).
|
||||
- `+ Nova` / `PeerDraftPanel` para IP manual.
|
||||
- `JTextArea` monoespaçada por aba; linhas como `[HH:mm:ss] pong ← Alice · RTT 42 ms`.
|
||||
- Abas **↓**: `bindReceiveSession` — controles de envio desabilitados.
|
||||
|
||||
**Não** use `JFrame`. No `PingPlugin`: `bindShellWorkspaceContent(panel)`.
|
||||
|
||||
A aba da shell só abre quando o usuário clicar na galeria **Plugins** ou numa peer action (sem enviar ping automaticamente).
|
||||
|
||||
## Passo 6 — `PingPlugin`
|
||||
|
||||
| Método | Valor |
|
||||
| Campo | Por que existe |
|
||||
| --- | --- |
|
||||
| `getIdentifier()` | `pitiupi.ping` (único na sala) |
|
||||
| `pingId` | Casa o pong com o ping que você enviou |
|
||||
| `sentAtMillis` | Instante local de envio; o pong devolve este número |
|
||||
| `senderName`, `senderIp` | A aba de quem recebe mostra quem mandou |
|
||||
| `mode` | `ONCE` ou `CONTINUOUS_TICK` |
|
||||
| `sessionId` | Igual em todos os tiques de um ping contínuo; `null` no envio único |
|
||||
|
||||
```java
|
||||
package pitiupi.ping.net;
|
||||
|
||||
import pitiupi.models.Messages.Message;
|
||||
|
||||
public class PingMessage extends Message {
|
||||
|
||||
private String pingId;
|
||||
private long sentAtMillis;
|
||||
private String senderName;
|
||||
private String senderIp;
|
||||
private PingMode mode;
|
||||
private String sessionId;
|
||||
|
||||
public PingMessage() {
|
||||
}
|
||||
|
||||
public PingMessage(
|
||||
String pingId,
|
||||
long sentAtMillis,
|
||||
String senderName,
|
||||
String senderIp,
|
||||
PingMode mode,
|
||||
String sessionId
|
||||
) {
|
||||
this.pingId = pingId;
|
||||
this.sentAtMillis = sentAtMillis;
|
||||
this.senderName = senderName;
|
||||
this.senderIp = senderIp;
|
||||
this.mode = mode;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getPingId() { return pingId; }
|
||||
public long getSentAtMillis() { return sentAtMillis; }
|
||||
public String getSenderName() { return senderName; }
|
||||
public String getSenderIp() { return senderIp; }
|
||||
public PingMode getMode() { return mode; }
|
||||
public String getSessionId() { return sessionId; }
|
||||
}
|
||||
```
|
||||
|
||||
Inclua os setters se for seguir o arquivo do repositório; a desserialização padrão preenche os campos privados direto e o setter não é obrigatório para a rede.
|
||||
|
||||
`src/pitiupi/ping/net/PongMessage.java` — a volta. `echoSentAtMillis` é cópia de `sentAtMillis` do ping. Quem enviou calcula `agora - sentAt` guardado localmente; o eco existe para conferir que o pong responde àquele envio.
|
||||
|
||||
| Campo | Por que existe |
|
||||
| --- | --- |
|
||||
| `pingId` | O mesmo id do ping |
|
||||
| `echoSentAtMillis` | Cópia do instante original |
|
||||
| `responderName`, `responderIp` | Quem respondeu |
|
||||
|
||||
Construtor vazio mais o construtor `(pingId, echoSentAtMillis, responderName, responderIp)` e os getters. A classe também estende `Message`.
|
||||
|
||||
`src/pitiupi/ping/net/PingStopMessage.java` — só quando o contínuo para:
|
||||
|
||||
| Campo | Por que existe |
|
||||
| --- | --- |
|
||||
| `sessionId` | Qual série parou |
|
||||
| `senderName`, `senderIp` | Quem parou, para a aba de recebimento |
|
||||
|
||||
Essas três classes entram em `PingPlugin.getMessages()` no passo 6. Se faltar uma, o núcleo descarta o byte: o outro lado “não recebe”.
|
||||
|
||||
## Passo 4 — Controller (enviar e receber)
|
||||
|
||||
`PingController` não cria janela. Ele usa o `ConnectionManager` que o núcleo entregou e o `MessageDispatcher` para não abrir socket.
|
||||
|
||||
Crie `src/pitiupi/ping/control/PingLogSink.java` para a tela plugar o log:
|
||||
|
||||
```java
|
||||
package pitiupi.ping.control;
|
||||
|
||||
public interface PingLogSink {
|
||||
void appendLine(String tabKey, String line);
|
||||
void ensureInboundTab(String peerIp, String displayName);
|
||||
}
|
||||
```
|
||||
|
||||
`src/pitiupi/ping/control/PingController.java` guarda:
|
||||
|
||||
- `pendingByPingId` — `pingId` → `sentAtMillis`, para achar o RTT quando o pong chegar.
|
||||
- `logTabByPingId` — em qual aba escrever esse RTT.
|
||||
- `continuousTimers` — um `javax.swing.Timer` de 1000 ms por IP.
|
||||
- `continuousSessionByPeer` — o `sessionId` da série daquele IP.
|
||||
|
||||
Constante: `CONTINUOUS_INTERVAL_MS = 1000`. Prefixo da aba que só recebe: `INBOUND_VIEW_PREFIX = "view:"`.
|
||||
|
||||
### Enviar um ping para um IP
|
||||
|
||||
`sendPing` faz, nesta ordem:
|
||||
|
||||
1. `buildPing` cria `PingMessage` com `UUID.randomUUID()`, `System.currentTimeMillis()`, `connectionManager.getUserName()`, `LocalNetworkInfo.bestLocalIpv4()`, o `PingMode` e o `sessionId`.
|
||||
2. Guarda o instante em `pendingByPingId` e a aba em `logTabByPingId`.
|
||||
3. Envia **sempre** em UDP unicast para aquele IP:
|
||||
|
||||
```java
|
||||
MessageDispatcher.send(
|
||||
connectionManager,
|
||||
message,
|
||||
SendTarget.singlePeer(addr),
|
||||
TransportMode.UNICAST_UDP
|
||||
);
|
||||
```
|
||||
|
||||
`SendTarget.singlePeer` é um endereço. `TransportMode.UNICAST_UDP` pede ao núcleo `sendUdpUnicast`. Não use TCP aqui: o exercício é o datagrama.
|
||||
|
||||
`PeerIpv4.normalize` deixa o IP em IPv4 pontilhado antes de `InetAddress.getByName`.
|
||||
|
||||
### Enviar um ping para a sala
|
||||
|
||||
`pingOnceRoom` usa `SendTarget.broadcastAll()` e o transporte que o painel escolheu (`MULTICAST_UDP` ou `UNICAST_UDP`). O núcleo, no unicast para a sala, manda uma cópia para cada pessoa online. O log vai na aba `PluginSessionKeys.ROOM`.
|
||||
|
||||
`pingOnce(PluginRoutingPanel.RoutingSnapshot)` olha `routing.everyoneInRoom()`. Se for a sala, chama `pingOnceRoom`. Se for um IP, chama `sendPing` com `PingMode.ONCE` e `sessionId` nulo. A aba de log desse IP é `PluginSessionKeys.peer(ip)` (a chave começa com `peer:`).
|
||||
|
||||
### Ping contínuo
|
||||
|
||||
`startContinuousForPeerIp`:
|
||||
|
||||
1. Para um timer anterior desse IP, se houver.
|
||||
2. Gera um `sessionId`. O mesmo id vai em todos os tiques, para o outro lado saber que a série é uma só.
|
||||
3. Cria `new Timer(1000, e -> sendPing(..., PingMode.CONTINUOUS_TICK, sessionId, aba))` e dá `timer.start()`.
|
||||
|
||||
O `javax.swing.Timer` fica no controller, não no painel. O intervalo de 1 segundo é regra do ping, e a tela só liga ou desliga. Esse timer dispara na thread da interface, então a linha de log pode ir direto ao `PingLogSink` sem outro `invokeLater`.
|
||||
|
||||
`stopContinuousForPeerIp` para o timer e, se havia `sessionId`, manda `PingStopMessage` com o mesmo `MessageDispatcher.send(..., UNICAST_UDP)`. Sem essa mensagem, o outro lado continua mostrando a aba `↓` como se a série seguisse.
|
||||
|
||||
`stopAllContinuous` percorre os IPs. O plugin chama isso quando a aba central fecha.
|
||||
|
||||
### Receber
|
||||
|
||||
`onPingReceived`:
|
||||
|
||||
1. `logSink.ensureInboundTab(ipDeQuemEnviou, nome)` abre a aba `view:<ip>` se ela não existir.
|
||||
2. Escreve uma linha nessa aba.
|
||||
3. Monta `PongMessage` com o `pingId`, o `sentAtMillis` **do ping recebido** (não o relógio de agora), o seu nome e `LocalNetworkInfo.bestLocalIpv4()`.
|
||||
4. Devolve em unicast para o IP de quem enviou.
|
||||
|
||||
O RTT não usa o relógio de quem responde. Os dois computadores não estão sincronizados. Quem enviou guardou o próprio `System.currentTimeMillis()` em `pendingByPingId` e, quando o pong volta com o mesmo `pingId`, subtrai esse valor do relógio local. O `echoSentAtMillis` só confirma que o pong responde àquele envio.
|
||||
|
||||
`onPongReceived`:
|
||||
|
||||
1. `pendingByPingId.remove(pingId)`. Se não havia entrada, ignore (pong atrasado ou de outro envio).
|
||||
2. `rtt = agora - sentAt` guardado, nunca negativo.
|
||||
3. Linha na aba que enviou: `pong ← Nome · RTT 42 ms`.
|
||||
|
||||
`onStopReceived` abre a aba `view:<ip>` e escreve que a pessoa parou o contínuo.
|
||||
|
||||
O controller não chama `receiveMessage`. Quem recebe do núcleo é o `PingPlugin`, no passo 6, e ele repassa para estes três métodos.
|
||||
|
||||
## Passo 5 — Painel
|
||||
|
||||
`PingPanel` estende `JPanel` e implementa `PingLogSink`. A janela do programa já é a `MainShellFrame`. `bindShellWorkspaceContent` coloca este painel dentro da aba. Um `JFrame` abriria outra janela por cima da shell, com login e lista duplicados. Não chame `setVisible` numa janela nova.
|
||||
|
||||
O núcleo já desenha destino, transporte e abas. Reutilizar isso evita um combo que manda multicast para um IP só, ou unicast para a sala inteira sem passar pelo `MessageDispatcher`.
|
||||
|
||||
- `PluginRoutingPanel` com `PluginTransportCatalog.PluginKind.PING`. Esse valor, no núcleo, lista multicast e unicast na sala, e só UDP unicast para um IP. O ping não tem TCP: o enum esconde essa opção. O terceiro argumento `false` esconde também a conexão privada (o chat usa `true` no texto privado).
|
||||
- `PluginSessionWorkspace` recebe o painel de roteamento e a barra de botões. Ele empilha o combo em cima e as abas no meio.
|
||||
- `SessionTabBar` é `workspace.getSessionTabBar()`.
|
||||
|
||||
Na construção:
|
||||
|
||||
```java
|
||||
this.routingPanel = new PluginRoutingPanel(
|
||||
PluginTransportCatalog.PluginKind.PING,
|
||||
"<html><i>Abas <b>↑</b> enviam; <b>↓</b> só recebem. Use + Nova ou a lista lateral para abrir um peer.</i></html>",
|
||||
false
|
||||
);
|
||||
controller.setLogSink(this);
|
||||
this.workspace = new PluginSessionWorkspace(routingPanel, buildPingToolbar());
|
||||
```
|
||||
|
||||
`setLogSink` liga o controller à tela. Sem isso, o RTT é calculado e não aparece.
|
||||
|
||||
Barra (`buildPingToolbar`):
|
||||
|
||||
- `JButton` **Enviar ping** chama `controller.pingOnce(routingPanel.snapshot())`. `snapshot()` copia destino e transporte **neste clique**. Se o controller lesse o combo depois, um segundo clique ou uma troca de aba mudaria o envio que já estava decidido.
|
||||
- `JToggleButton` **Ping contínuo**. Só age se a aba selecionada for `peer:<ip>` (envio para uma pessoa). Ligado chama `startContinuousForPeerIp`. Desligado chama `stopContinuousForPeerIp`. Na sala e nas abas `view:` o botão fica desabilitado (`applyToolbarForTab`). A aba `view:` é de quem recebeu: um ping contínuo dali responderia para dentro da própria recepção e misturaria as duas direções.
|
||||
|
||||
Abas. A chave é uma `String` que o `SessionTabBar` usa para não abrir a mesma conversa duas vezes.
|
||||
|
||||
| Chave | Título | Por que essa chave |
|
||||
| --- | --- | --- |
|
||||
| `PluginSessionKeys.ROOM` | `Todos ↑` (`SessionTabDirectionLabels.outboundTitle`) | A sala já tem nome no núcleo (`"room"`). Não fecha: é o fallback quando as outras fecham |
|
||||
| `peer:<ip>` | `Nome ↑` | Você escolheu o destino. Nasce na lista lateral, no **+ Nova** ou no rascunho de IP |
|
||||
| `view:<ip>` | `Nome ↓` | Convenção do plugin, não do núcleo. Nasce em `ensureInboundTab` quando chega um ping. `bindReceiveSession` deixa IP e transporte somente leitura |
|
||||
|
||||
`focusMulticastSession()` seleciona a aba da sala. `focusPeerAddress(UserPeerView)` abre `peer:<ip>` com `PeerIpv4.fromInetAddress`. As peer actions do passo 6 chamam esses dois métodos e não disparam ping: a ação da lista só escolhe com quem falar. O datagrama continua no botão, senão um clique no nome já mediria RTT sem a pessoa confirmar.
|
||||
|
||||
`appendLine` escreve `[HH:mm:ss] ` mais o texto num `JTextArea` monoespaçado daquela chave. Uma área por aba, dentro de um `CardLayout`: o card mostra só a aba selecionada e as outras permanecem na memória. Sem o card, trocar de aba apagaria o histórico ou empilharia todos os textos ao mesmo tempo.
|
||||
|
||||
`PeerDraftPanel` no **+ Nova** pede um IP e vira uma aba `peer:`. É o mesmo componente do núcleo que o chat usa para IP que ainda não está na lista.
|
||||
|
||||
O contrato com o controller é `PingLogSink` mais `pingOnce`, `startContinuousForPeerIp` e `stopContinuousForPeerIp`. O arquivo `plugins/ping/src/pitiupi/ping/GUI/PingPanel.java` é essa montagem pronta. Os métodos acima são os que mudam o comportamento; o resto é borda, fonte e rótulo.
|
||||
|
||||
## Passo 6 — PingPlugin
|
||||
|
||||
Arquivo `src/pitiupi/ping/plugin/PingPlugin.java`. A classe estende `pitiupi.models.Plugin`. O nome na linha do SPI tem de ser exatamente `pitiupi.ping.plugin.PingPlugin`.
|
||||
|
||||
Identidade:
|
||||
|
||||
| Método | Retorno |
|
||||
| --- | --- |
|
||||
| `getName()` | `Ping` |
|
||||
| `getDisplayName()` | `Ping na rede` |
|
||||
| `getAuthor()` | o seu nome ou `Aula Pitiupi` |
|
||||
| `getVersion()` | `1.0.0` |
|
||||
| `getIdentifier()` | `pitiupi.ping` |
|
||||
|
||||
`createPlugin(ConnectionManager, PluginWorkspace)`:
|
||||
`getIdentifier()` é o id que o núcleo usa se um dia houver TCP dirigido a este plugin. Tem de ser único na sala.
|
||||
|
||||
1. Crie `PingController` e `PingPanel`.
|
||||
2. `bindShellWorkspaceContent(panel)`.
|
||||
3. `installPeerActions(workspace.peerActions())`.
|
||||
`createPlugin` guarda o `PluginWorkspace`, cria o controller com o `ConnectionManager`, cria o painel, registra o painel e as ações. Não chame `openOrFocusTab` aqui: a aba não abre sozinha no login.
|
||||
|
||||
**Peer actions sugeridas:**
|
||||
```java
|
||||
@Override
|
||||
public void createPlugin(ConnectionManager connectionManager, PluginWorkspace workspace) {
|
||||
this.workspace = workspace;
|
||||
this.controller = new PingController(connectionManager);
|
||||
this.panel = new PingPanel(connectionManager, controller);
|
||||
bindShellWorkspaceContent(panel);
|
||||
installPeerActions(workspace.peerActions());
|
||||
}
|
||||
```
|
||||
|
||||
| id | Label | Alvo | Comportamento |
|
||||
`bindShellWorkspaceContent` é método da classe `Plugin`. Sem ele a galeria abre uma aba vazia.
|
||||
|
||||
`getMessages` declara os três tipos. É esta lista que autoriza a desserialização:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public List<Class<? extends Message>> getMessages() {
|
||||
return List.of(PingMessage.class, PongMessage.class, PingStopMessage.class);
|
||||
}
|
||||
```
|
||||
|
||||
`receiveMessage` não mexe na tela direto. O núcleo pode chamar esse método na thread que leu o socket. `JButton` e `JTextArea` só aceitam mudança na thread do Swing; fora dela a interface trava ou desenha pela metade. `SwingUtilities.invokeLater` joga o `instanceof` e a chamada ao controller para essa thread:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void receiveMessage(Message message) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (message instanceof PingMessage ping) {
|
||||
controller.onPingReceived(ping);
|
||||
} else if (message instanceof PongMessage pong) {
|
||||
controller.onPongReceived(pong);
|
||||
} else if (message instanceof PingStopMessage stop) {
|
||||
controller.onStopReceived(stop);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
`onShellTabClosed` chama `controller.stopAllContinuous()` para o timer não continuar com a aba fechada.
|
||||
|
||||
### Peer actions
|
||||
|
||||
Registre em `installPeerActions`. As duas só abrem a interface. O datagrama sai do botão.
|
||||
|
||||
| id | Rótulo | Alvo | O que faz |
|
||||
| --- | --- | --- | --- |
|
||||
| `ping.peer` | Enviar ping para esta pessoa | um peer | Abre/foca o plugin e a aba `↑` com o IP — **sem** disparar ping |
|
||||
| `ping.room` | Abrir ping na sala (Todos) | Todos | Abre a aba sala — **sem** disparar ping |
|
||||
| `ping.room` | Abrir ping na sala (Todos) | `PeerActionTarget.ALL_PEERS` | `openOrFocusTab` e `panel.focusMulticastSession()` |
|
||||
| `ping.peer` | Enviar ping para esta pessoa | `PeerActionTarget.SINGLE_PEER` | `openOrFocusTab` e, se o alvo for `PeerTarget.Single`, `panel.focusPeerAddress(single.peer())` |
|
||||
|
||||
O envio fica nos botões do painel.
|
||||
```java
|
||||
registrar.register(PeerAction.of(
|
||||
"ping.peer",
|
||||
"Enviar ping para esta pessoa",
|
||||
PeerActionTarget.SINGLE_PEER,
|
||||
target -> {
|
||||
workspace.openOrFocusTab(getIdentifier(), getDisplayName());
|
||||
if (target instanceof PeerTarget.Single single) {
|
||||
panel.focusPeerAddress(single.peer());
|
||||
}
|
||||
}
|
||||
));
|
||||
```
|
||||
|
||||
`receiveMessage`: despache para o controller com `SwingUtilities.invokeLater`.
|
||||
A ação de sala é a mesma forma, com `PeerActionTarget.ALL_PEERS` e `focusMulticastSession()`. O id (`ping.peer`, `ping.room`) só precisa ser único dentro do plugin.
|
||||
|
||||
`onShellTabClosed`: pare todos os timers contínuos.
|
||||
## Passo 7 — Catálogo, compilar, instalar
|
||||
|
||||
## Passo 7 — Instalar e testar
|
||||
Na raiz do clone, `plugins.registry.json` precisa de uma entrada para o script compilar e vincular o módulo:
|
||||
|
||||
1. `ant jar` → `dist/pitiupi.ping.jar`.
|
||||
2. Aba **Configurações** → **Instalar JAR…** (ou copie para `.pitiupi/plugins/` na raiz do clone).
|
||||
3. **Reinicie** o Pitiupi (obrigatório após mudar plugins).
|
||||
4. Dois processos no mesmo PC: portas **5000** e **5001**, nomes diferentes.
|
||||
5. Lista lateral → **Enviar ping para esta pessoa** → aba `↑` com IP; clique **Enviar ping** → RTT no log.
|
||||
6. Toggle **Ping contínuo** na aba `↑` → linhas a cada ~1 s; desligar o toggle → o outro vê “parou o ping contínuo” na aba `↓`.
|
||||
7. Com colega na LAN: mesma porta, JAR instalado nos dois.
|
||||
```json
|
||||
{
|
||||
"id": "ping",
|
||||
"dir": "plugins/ping",
|
||||
"build": "ant",
|
||||
"jar": "dist/pitiupi.ping.jar",
|
||||
"menuCategory": "Social"
|
||||
}
|
||||
```
|
||||
|
||||
Instalação usa o mesmo `PluginStore` da aba **Configurações** (`installFromFile` + reinício). O núcleo expõe `pitiupi.tools.PluginStoreCli` para testes sem interface gráfica.
|
||||
`id` vira o arquivo `.pitiupi/plugins/ping.jar`. `dir` + `jar` acham `plugins/ping/dist/pitiupi.ping.jar`. `build: ant` manda rodar `ant jar` nessa pasta. `menuCategory` só filtra `./scripts/pitiupi.sh run --category Social`. Não cria menu na janela.
|
||||
|
||||
## Passo 8 — Comparar com o chat
|
||||
Compile, na pasta `plugins/ping`:
|
||||
|
||||
O chat usa a mesma ideia de abas de sessão, mas adiciona roteamento TCP privado e HTML. No Ping mantenha **só** UDP e texto em `JTextArea` — ideal para entender o fluxo antes de copiar padrões para um plugin novo.
|
||||
```bash
|
||||
ant jar
|
||||
jar tf dist/pitiupi.ping.jar | grep META-INF/services
|
||||
```
|
||||
|
||||
## Problemas comuns (Ping)
|
||||
Depois, na raiz:
|
||||
|
||||
| Sintoma | Diagnóstico |
|
||||
```bash
|
||||
./scripts/pitiupi.sh link --all
|
||||
```
|
||||
|
||||
Ou instale `dist/pitiupi.ping.jar` em **Configurações → Instalar JAR…**. Isso copia o arquivo e grava uma entrada `build: jar` no catálogo. Reinicie o Pitiupi nos dois casos. O processo aberto não relê a pasta de plugins.
|
||||
|
||||
## Passo 8 — Ver o RTT
|
||||
|
||||
1. Duas janelas no mesmo PC: portas **5000** e **5001**, nomes diferentes. Elas não ficam na mesma sala (a sala é a porta). Servem para ver se o JAR carregou: a galeria mostra **Ping na rede**.
|
||||
2. Dois computadores, mesma porta (5000), mesmos JARs: a lista mostra a outra pessoa. Clique nela → **Enviar ping para esta pessoa**. A aba `↑` abre com o IP. Clique **Enviar ping**. A linha do RTT aparece na aba de quem enviou; a outra ganha uma aba `↓`.
|
||||
3. Na aba `↑`, ligue **Ping contínuo**. Sai um ping por segundo, com o mesmo `sessionId`. Desligue: o outro vê “parou o ping contínuo” na aba `↓`.
|
||||
4. **Abrir ping na sala (Todos)** foca a aba da sala e não envia. O envio é o botão, com multicast ou unicast no combo.
|
||||
|
||||
Se a lista só mostra **Todos**, o problema é rede (porta, sub-rede, firewall, WSL), não o painel. Veja [usar.md](../usar.md).
|
||||
|
||||
## Quando algo não acontece
|
||||
|
||||
| O que você vê | O que conferir |
|
||||
| --- | --- |
|
||||
| RTT não aparece | `Pong` sem `pingId` correspondente em `pendingByPingId` |
|
||||
| Peer não recebe | Tipo não listado em `getMessages()` ou UDP bloqueado |
|
||||
| `ClassCastException` | Versões diferentes do JAR entre PCs |
|
||||
| Aba não abre | Esqueceu `bindShellWorkspaceContent` ou peer action só abre UI (envio é no painel) |
|
||||
|
||||
Mais geral: [10_tutorial_plugin.md](../documentation/10_tutorial_plugin.md#problemas-comuns).
|
||||
| O plugin não aparece na galeria | A linha do SPI, o `jar tf`, e se o JAR está em `.pitiupi/plugins` depois do reinício |
|
||||
| O peer não recebe | A classe está em `getMessages()`? Os dois usam o mesmo JAR? UDP na porta do login está liberado? |
|
||||
| O RTT não aparece | O `pingId` do pong não está em `pendingByPingId` (id diferente, ou o pong chegou antes de você guardar o mapa) |
|
||||
| A aba abre vazia | Faltou `bindShellWorkspaceContent(panel)` |
|
||||
| A ação da lista manda ping sozinha | A peer action deve só focar a aba; o `MessageDispatcher.send` fica no botão |
|
||||
| `ClassCastException` ou a mensagem some | Versões diferentes da classe nos dois PCs |
|
||||
| O contínuo continua com a aba fechada | `onShellTabClosed` não chamou `stopAllContinuous` |
|
||||
+4
-6
@@ -1,12 +1,10 @@
|
||||
# Material da disciplina — Redes de Computadores
|
||||
|
||||
Contexto do uso do Pitiupi na disciplina e o **tutorial do plugin Ping** para construir em sala.
|
||||
Uso do Pitiupi na disciplina e o tutorial para construir o plugin Ping.
|
||||
|
||||
Para protocolos, núcleo e plugins em geral: [documentation/](../documentation/README.md).
|
||||
A descrição do programa, da rede e do contrato de plugin está em [docs/README.md](../README.md). Esta pasta não é necessária para compilar o núcleo.
|
||||
|
||||
| Documento | Conteúdo |
|
||||
| --- | --- |
|
||||
| [01_visao_disciplina.md](01_visao_disciplina.md) | Papel do Pitiupi na disciplina |
|
||||
| [02_tutorial_ping_aula.md](02_tutorial_ping_aula.md) | Exercício Ping em sala (o que é e como montar) |
|
||||
|
||||
Esta pasta pode ser removida localmente depois; `docs/documentation/` descreve o projeto de forma independente.
|
||||
| [01_visao_disciplina.md](01_visao_disciplina.md) | O que a ferramenta cobre na disciplina |
|
||||
| [02_tutorial_ping_aula.md](02_tutorial_ping_aula.md) | Construção do Ping, arquivo por arquivo |
|
||||
@@ -0,0 +1,60 @@
|
||||
# Usar a plataforma
|
||||
|
||||
A janela principal é a `MainShellFrame`. No centro ficam as abas dos plugins. Do lado, a lista **Usuários nesta rede**. Em **Plugins** está a galeria. Em **Configurações** estão nome, porta, saída, TTL e os JARs instalados.
|
||||
|
||||
## Login e sala
|
||||
|
||||
Nome e porta começam no diálogo de login e podem mudar em **Configurações** sem reiniciar o programa. A alteração vale na hora. Nome, porta, TTL e saída ficam gravados nas preferências locais do Java, não num `config.properties` do projeto.
|
||||
|
||||
A lista mostra **Todos** e cada pessoa de quem chegou heartbeat. Clicar numa linha abre as ações dos plugins (um submenu por plugin). **Todos** mostra as ações de sala. Uma pessoa mostra as ações daquele IP.
|
||||
|
||||
Abrir a ação não envia a mensagem sozinha: no ping, a ação só foca a aba; o botão **Enviar ping** é que manda o datagrama. No chat, a ação abre a conversa.
|
||||
|
||||
## Plugins na janela
|
||||
|
||||
A galeria lista `getDisplayName()` de cada JAR carregado. Clicar abre a aba central.
|
||||
|
||||
Depois de **Instalar JAR…** ou **Remover selecionado**, reinicie. O processo que já está aberto não relê a pasta de plugins. Instalar também registra o arquivo no `plugins.registry.json` do clone, para o próximo `./scripts/pitiupi.sh run` não apagar o JAR. Remover tira o registro. Detalhe em [plugins.md](plugins.md).
|
||||
|
||||
## Dois no mesmo computador
|
||||
|
||||
Duas janelas, dois nomes, duas portas (5000 e 5001). Elas não entram na mesma sala multicast, porque a sala é a porta. Servem para ver se o plugin abre, se o JAR carregou e se a aba responde. Para ver uma pessoa na lista da outra, use a mesma porta em dois computadores, ou aceite que no mesmo PC a segunda instância precisa de outra porta e portanto de outra sala.
|
||||
|
||||
## Dois computadores
|
||||
|
||||
Os dois no Java do Windows, ou os dois no Ubuntu instalado na máquina. Mesma porta, nomes diferentes, mesma sub-rede. O caminho de cada sistema está em [comecar.md](comecar.md). Porta, grupo multicast e WSL estão em [rede.md](rede.md).
|
||||
|
||||
Na aba **Configurações**, seção **Sua conexão**:
|
||||
|
||||
- **IP local** e o nome da interface (por exemplo `eth0 · 192.168.0.15`).
|
||||
- **Porta** em que esta instância escuta UDP e TCP.
|
||||
- **Saída:** Automático, Cabo ou Wi-Fi. Os dois precisam usar a rede dessa interface.
|
||||
- **TTL:** padrão 1. Se o caminho até o colega passar por mais de um roteador, use o número de saltos, até 32.
|
||||
|
||||
O colega vê um nome novo no próximo anúncio (até cerca de 5 segundos).
|
||||
|
||||
### Conferir no sistema
|
||||
|
||||
Troque `5000` e o IP pelos valores da aba.
|
||||
|
||||
No Windows: `ipconfig`; `netstat -ano | findstr :5000`; `ping` no IP do colega; `tracert` no IP do colega. No PowerShell, `Test-NetConnection -ComputerName IP -Port 5000`.
|
||||
|
||||
No Linux: `ip -4 addr`; `ss -lntu | grep 5000`; `ping -c 3 IP`; `traceroute IP` ou `tracepath IP`; `nc -vz -w 3 IP 5000`.
|
||||
|
||||
### Como ler o resultado
|
||||
|
||||
- **O ping do sistema falha.** A rede entre os PCs não entrega pacote: sub-rede, cabo, Wi-Fi ou isolamento de clientes. O Pitiupi não contorna isso.
|
||||
- **O ping funciona e a porta TCP não abre.** Firewall, porta errada ou o outro Pitiupi não está escutando. Libere UDP e TCP nessa porta.
|
||||
- **O ping responde num salto e a lista só mostra Todos.** A descoberta é multicast. Confira porta igual, a mesma família de interface (os dois no cabo ou os dois no Wi-Fi; `169.254` e `192.168` não se veem), firewall de UDP e isolamento de clientes. Um processo no WSL e outro no Java do Windows não compartilham a placa.
|
||||
- **O traceroute tem vários saltos e a lista fica vazia.** Suba o TTL até o número de saltos, nos dois.
|
||||
- **A pessoa está na lista e o TCP avisa que não conectou.** A tentativa espera no máximo cerca de 5 segundos e a janela continua usável. Veja firewall de TCP.
|
||||
|
||||
A lista remove quem fica cerca de 15 segundos sem heartbeat.
|
||||
|
||||
## Limites visíveis
|
||||
|
||||
- Sem criptografia e sem conta de usuário além do nome local.
|
||||
- Sem atualização automática de plugin: você instala o JAR.
|
||||
- A lista não mostra um contador de mensagens não lidas.
|
||||
- UDP pode perder datagrama. TCP é o transporte confiável entre dois IPs.
|
||||
- O programa não executa o `ping` nem o `traceroute` do sistema; esses comandos só ajudam a separar falha de rede de falha do aplicativo.
|
||||
@@ -21,4 +21,4 @@ ln -sf /caminho/ao/clone/plugins/chat/dist/pitiupi.chat.jar /caminho/ao/clone/.p
|
||||
|
||||
Não commite `.jar` binários no Git — apenas este README.
|
||||
|
||||
Novo módulo: registrar no catálogo e ver [docs/documentation/10_tutorial_plugin.md](../../docs/documentation/10_tutorial_plugin.md).
|
||||
Novo módulo: [docs/plugins.md](../../docs/plugins.md). A instalação pela janela também grava [`plugins.registry.json`](../../plugins.registry.json).
|
||||
@@ -0,0 +1,159 @@
|
||||
package pitiupi.plugins.store;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Lê e grava {@code plugins.registry.json} na raiz do clone.
|
||||
* Entradas {@code build: jar} apontam para uma cópia estável em {@code .pitiupi/jar-catalog}.
|
||||
*/
|
||||
public final class PluginCatalog {
|
||||
|
||||
public static final String BUILD_JAR = "jar";
|
||||
public static final String CATALOG_DIR = ".pitiupi/jar-catalog";
|
||||
public static final String DEFAULT_CATEGORY = "Social";
|
||||
|
||||
private static final Pattern OBJECT = Pattern.compile("\\{[^{}]*}");
|
||||
private static final Pattern FIELD = Pattern.compile("\"([^\"]+)\"\\s*:\\s*\"((?:\\\\.|[^\"\\\\])*)\"");
|
||||
|
||||
private PluginCatalog() {
|
||||
}
|
||||
|
||||
public record Entry(String id, String dir, String build, String jar, String menuCategory) {
|
||||
}
|
||||
|
||||
/**
|
||||
* O catálogo fica ao lado de {@code .pitiupi}, pai da raiz de dados.
|
||||
*/
|
||||
public static Optional<Path> locateFromHome(Path homeRoot) {
|
||||
if (homeRoot == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Path parent = homeRoot.toAbsolutePath().normalize().getParent();
|
||||
if (parent == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Path registry = parent.resolve("plugins.registry.json");
|
||||
if (Files.isRegularFile(registry)) {
|
||||
return Optional.of(registry);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static List<Entry> read(Path registry) throws IOException {
|
||||
String text = Files.readString(registry);
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
Matcher objects = OBJECT.matcher(text);
|
||||
while (objects.find()) {
|
||||
String body = objects.group();
|
||||
String id = field(body, "id");
|
||||
String dir = field(body, "dir");
|
||||
String build = field(body, "build");
|
||||
String jar = field(body, "jar");
|
||||
String category = field(body, "menuCategory");
|
||||
if (id == null || dir == null || build == null || jar == null || category == null) {
|
||||
continue;
|
||||
}
|
||||
entries.add(new Entry(id, dir, build, jar, category));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
public static void write(Path registry, List<Entry> entries) throws IOException {
|
||||
StringBuilder out = new StringBuilder();
|
||||
out.append("{\n \"plugins\": [\n");
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
Entry entry = entries.get(i);
|
||||
out.append(" {\n");
|
||||
out.append(" \"id\": \"").append(escape(entry.id())).append("\",\n");
|
||||
out.append(" \"dir\": \"").append(escape(entry.dir())).append("\",\n");
|
||||
out.append(" \"build\": \"").append(escape(entry.build())).append("\",\n");
|
||||
out.append(" \"jar\": \"").append(escape(entry.jar())).append("\",\n");
|
||||
out.append(" \"menuCategory\": \"").append(escape(entry.menuCategory())).append("\"\n");
|
||||
out.append(" }");
|
||||
if (i + 1 < entries.size()) {
|
||||
out.append(',');
|
||||
}
|
||||
out.append('\n');
|
||||
}
|
||||
out.append(" ]\n}\n");
|
||||
Files.writeString(registry, out.toString());
|
||||
}
|
||||
|
||||
public static void upsert(Path registry, Entry entry) throws IOException {
|
||||
List<Entry> entries = read(registry);
|
||||
boolean replaced = false;
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
if (entries.get(i).id().equals(entry.id())) {
|
||||
entries.set(i, entry);
|
||||
replaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!replaced) {
|
||||
entries.add(entry);
|
||||
}
|
||||
write(registry, entries);
|
||||
}
|
||||
|
||||
public static void removeMatchingJar(Path registry, String fileName) throws IOException {
|
||||
if (registry == null || fileName == null || !Files.isRegularFile(registry)) {
|
||||
return;
|
||||
}
|
||||
String stem = stemOf(fileName);
|
||||
List<Entry> kept = new ArrayList<>();
|
||||
for (Entry entry : read(registry)) {
|
||||
if (!matches(entry, fileName, stem)) {
|
||||
kept.add(entry);
|
||||
}
|
||||
}
|
||||
write(registry, kept);
|
||||
}
|
||||
|
||||
public static boolean matches(Entry entry, String fileName, String stem) {
|
||||
if (entry.id().equals(stem)) {
|
||||
return true;
|
||||
}
|
||||
if (BUILD_JAR.equals(entry.build())) {
|
||||
String jarName = Path.of(entry.jar()).getFileName().toString();
|
||||
return jarName.equals(fileName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String stemOf(String fileName) {
|
||||
String name = fileName;
|
||||
if (name.endsWith(".jar")) {
|
||||
name = name.substring(0, name.length() - 4);
|
||||
}
|
||||
String cleaned = name.replaceAll("[^A-Za-z0-9._-]", "");
|
||||
if (cleaned.isBlank()) {
|
||||
return "plugin";
|
||||
}
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
public static Path catalogDirectory(Path registry) {
|
||||
return registry.getParent().resolve(CATALOG_DIR);
|
||||
}
|
||||
|
||||
private static String field(String objectBody, String name) {
|
||||
Matcher matcher = FIELD.matcher(objectBody);
|
||||
while (matcher.find()) {
|
||||
if (name.equals(matcher.group(1))) {
|
||||
return matcher.group(2).replace("\\\"", "\"").replace("\\\\", "\\");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String escape(String value) {
|
||||
return value.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
}
|
||||
@@ -23,16 +23,23 @@ public final class PluginStore {
|
||||
|
||||
private final Path pluginsDir;
|
||||
private final Path cacheDir;
|
||||
private final Path registryFile;
|
||||
|
||||
public PluginStore() {
|
||||
PitiupiHome home = PitiupiHome.get();
|
||||
this.pluginsDir = home.pluginsDirectory();
|
||||
this.cacheDir = home.cacheDirectory();
|
||||
this.registryFile = PluginCatalog.locateFromHome(home.rootDirectory()).orElse(null);
|
||||
}
|
||||
|
||||
public PluginStore(Path pluginsDir, Path cacheDir) {
|
||||
this(pluginsDir, cacheDir, null);
|
||||
}
|
||||
|
||||
public PluginStore(Path pluginsDir, Path cacheDir, Path registryFile) {
|
||||
this.pluginsDir = pluginsDir.normalize().toAbsolutePath();
|
||||
this.cacheDir = cacheDir.normalize().toAbsolutePath();
|
||||
this.registryFile = registryFile == null ? null : registryFile.normalize().toAbsolutePath();
|
||||
}
|
||||
|
||||
public List<InstalledPluginRef> listInstalled() throws IOException {
|
||||
@@ -68,6 +75,7 @@ public final class PluginStore {
|
||||
try {
|
||||
Files.copy(sourceJar, tmp, StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.move(tmp, dest, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
rememberInCatalog(sourceJar, fileName);
|
||||
return dest;
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
@@ -88,6 +96,7 @@ public final class PluginStore {
|
||||
try {
|
||||
Files.deleteIfExists(normalized);
|
||||
Files.deleteIfExists(PluginMetadataWriter.metaPathFor(normalized));
|
||||
forgetFromCatalog(normalized.getFileName().toString());
|
||||
} catch (IOException e) {
|
||||
throw new PitiupiException(ErrorCode.PLUGIN_REMOVE_FAILED, e);
|
||||
}
|
||||
@@ -144,4 +153,28 @@ public final class PluginStore {
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
private void rememberInCatalog(Path sourceJar, String fileName) throws IOException {
|
||||
if (registryFile == null || !Files.isRegularFile(registryFile)) {
|
||||
return;
|
||||
}
|
||||
Path catalogDir = PluginCatalog.catalogDirectory(registryFile);
|
||||
Files.createDirectories(catalogDir);
|
||||
Files.copy(sourceJar, catalogDir.resolve(fileName), StandardCopyOption.REPLACE_EXISTING);
|
||||
PluginCatalog.upsert(registryFile, new PluginCatalog.Entry(
|
||||
PluginCatalog.stemOf(fileName),
|
||||
PluginCatalog.CATALOG_DIR,
|
||||
PluginCatalog.BUILD_JAR,
|
||||
fileName,
|
||||
PluginCatalog.DEFAULT_CATEGORY
|
||||
));
|
||||
}
|
||||
|
||||
private void forgetFromCatalog(String fileName) throws IOException {
|
||||
if (registryFile == null || !Files.isRegularFile(registryFile)) {
|
||||
return;
|
||||
}
|
||||
Files.deleteIfExists(PluginCatalog.catalogDirectory(registryFile).resolve(fileName));
|
||||
PluginCatalog.removeMatchingJar(registryFile, fileName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package pitiupi.plugins.store;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PluginCatalogTest {
|
||||
|
||||
@TempDir
|
||||
Path temp;
|
||||
|
||||
@Test
|
||||
void upsertKeepsOtherEntriesAndRemoveDropsOnlyTheMatch() throws Exception {
|
||||
Path registry = temp.resolve("plugins.registry.json");
|
||||
Files.writeString(registry, """
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"id": "chat",
|
||||
"dir": "plugins/chat",
|
||||
"build": "ant",
|
||||
"jar": "dist/pitiupi.chat.jar",
|
||||
"menuCategory": "Social"
|
||||
},
|
||||
{
|
||||
"id": "screencast",
|
||||
"dir": "plugins/screencast",
|
||||
"build": "ant",
|
||||
"jar": "dist/pitiupi.screenCast.jar",
|
||||
"menuCategory": "Streaming"
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
PluginCatalog.upsert(registry, new PluginCatalog.Entry(
|
||||
"extra",
|
||||
PluginCatalog.CATALOG_DIR,
|
||||
PluginCatalog.BUILD_JAR,
|
||||
"extra.jar",
|
||||
PluginCatalog.DEFAULT_CATEGORY
|
||||
));
|
||||
|
||||
List<PluginCatalog.Entry> afterAdd = PluginCatalog.read(registry);
|
||||
assertEquals(3, afterAdd.size());
|
||||
assertEquals("chat", afterAdd.get(0).id());
|
||||
assertEquals("ant", afterAdd.get(0).build());
|
||||
assertEquals("extra", afterAdd.get(2).id());
|
||||
assertEquals(PluginCatalog.BUILD_JAR, afterAdd.get(2).build());
|
||||
|
||||
PluginCatalog.removeMatchingJar(registry, "extra.jar");
|
||||
List<PluginCatalog.Entry> afterRemove = PluginCatalog.read(registry);
|
||||
assertEquals(2, afterRemove.size());
|
||||
assertEquals("chat", afterRemove.get(0).id());
|
||||
assertEquals("screencast", afterRemove.get(1).id());
|
||||
|
||||
PluginCatalog.removeMatchingJar(registry, "chat.jar");
|
||||
List<PluginCatalog.Entry> afterChat = PluginCatalog.read(registry);
|
||||
assertEquals(1, afterChat.size());
|
||||
assertEquals("screencast", afterChat.get(0).id());
|
||||
}
|
||||
|
||||
@Test
|
||||
void locateFromHomeFindsRegistryBesideDataDir() throws Exception {
|
||||
Path registry = temp.resolve("plugins.registry.json");
|
||||
Files.writeString(registry, "{ \"plugins\": [] }\n");
|
||||
Path home = temp.resolve(".pitiupi");
|
||||
Files.createDirectories(home);
|
||||
assertTrue(PluginCatalog.locateFromHome(home).isPresent());
|
||||
assertEquals(registry, PluginCatalog.locateFromHome(home).orElseThrow());
|
||||
Path unrelated = temp.resolve("sem-catalogo").resolve("dados");
|
||||
Files.createDirectories(unrelated);
|
||||
assertFalse(PluginCatalog.locateFromHome(unrelated).isPresent());
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -64,4 +65,43 @@ class PluginStoreTest {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void installAndRemoveUpdateRegistryWithoutDroppingOtherPlugins() throws Exception {
|
||||
Path registry = temp.resolve("plugins.registry.json");
|
||||
Files.writeString(registry, """
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"id": "chat",
|
||||
"dir": "plugins/chat",
|
||||
"build": "ant",
|
||||
"jar": "dist/pitiupi.chat.jar",
|
||||
"menuCategory": "Social"
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
PluginStore withCatalog = new PluginStore(pluginsDir, cacheDir, registry);
|
||||
Path source = temp.resolve("extra.jar");
|
||||
Files.writeString(source, "jar-bytes");
|
||||
|
||||
withCatalog.installFromFile(source);
|
||||
|
||||
assertTrue(Files.isRegularFile(PluginCatalog.catalogDirectory(registry).resolve("extra.jar")));
|
||||
List<PluginCatalog.Entry> added = PluginCatalog.read(registry);
|
||||
assertEquals(2, added.size());
|
||||
assertEquals("chat", added.get(0).id());
|
||||
assertEquals("ant", added.get(0).build());
|
||||
assertEquals("extra", added.get(1).id());
|
||||
assertEquals(PluginCatalog.BUILD_JAR, added.get(1).build());
|
||||
|
||||
withCatalog.remove(pluginsDir.resolve("extra.jar"));
|
||||
|
||||
assertFalse(Files.exists(PluginCatalog.catalogDirectory(registry).resolve("extra.jar")));
|
||||
List<PluginCatalog.Entry> left = PluginCatalog.read(registry);
|
||||
assertEquals(1, left.size());
|
||||
assertEquals("chat", left.get(0).id());
|
||||
assertEquals("plugins/chat", left.get(0).dir());
|
||||
}
|
||||
}
|
||||
+3
-15
@@ -1,17 +1,5 @@
|
||||
# Plugin Ping (exercício local)
|
||||
# Plugin Ping
|
||||
|
||||
Pasta opcional para desenvolver um módulo de Ping/Pong com medição de RTT. O código de solução **não** faz parte do gate CI do repositório.
|
||||
Módulo de referência que mede RTT com mensagens `Ping` / `Pong` na rede local. O código desta pasta é a implementação pronta. O passo a passo de como ela foi montada está em [docs/tp/02_tutorial_ping_aula.md](../../docs/tp/02_tutorial_ping_aula.md).
|
||||
|
||||
Tutorial passo a passo: [docs/tp/02_tutorial_ping_aula.md](../../docs/tp/02_tutorial_ping_aula.md).
|
||||
|
||||
## Fluxo de trabalho
|
||||
|
||||
1. Copiar a estrutura de [`plugins/chat`](../chat) para `plugins/ping` (local).
|
||||
2. Implementar `PingPlugin`, mensagens e painel (`bindShellWorkspaceContent`).
|
||||
3. `ant jar` → instalar JAR em `PITIUPI_HOME/plugins/` e reiniciar o Pitiupi.
|
||||
4. Abrir o plugin pela galeria **Plugins**.
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
- Núcleo: `../../pitiupi/dist/pitiupi.jar`
|
||||
- SPI e mensagens: [09_sistema_de_plugins.md](../../docs/documentation/09_sistema_de_plugins.md)
|
||||
Contrato do núcleo: [docs/nucleo-para-plugins.md](../../docs/nucleo-para-plugins.md). Descoberta do JAR: [docs/plugins.md](../../docs/plugins.md).
|
||||
+1
-1
@@ -25,7 +25,7 @@ del /q "%CD%\.pitiupi\plugins\*.jar" 2>nul
|
||||
|
||||
set "PITIUPI_REPO=%CD%"
|
||||
set "PITIUPI_PLUGINS=%CD%\.pitiupi\plugins"
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$repo = $env:PITIUPI_REPO; $dest = $env:PITIUPI_PLUGINS; $reg = Get-Content -Raw (Join-Path $repo 'plugins.registry.json') | ConvertFrom-Json; foreach ($p in @($reg.plugins)) { $dir = Join-Path $repo ($p.dir -replace '/','\'); $marker = Join-Path $dir 'build.xml'; if ($p.build -eq 'make') { $marker = Join-Path $dir 'Makefile' }; if (-not (Test-Path -LiteralPath $marker)) { Write-Host ('Plugin ' + $p.id + ' ausente nesta maquina; nao sera carregado.'); continue }; if ($p.build -ne 'ant') { Write-Error ('build nao suportado neste prompt: ' + $p.build); exit 1 }; & ant -f (Join-Path $dir 'build.xml') jar; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; $src = Join-Path $dir ($p.jar -replace '/','\'); if (-not (Test-Path -LiteralPath $src)) { Write-Error ('JAR ausente: ' + $src); exit 1 }; Copy-Item -LiteralPath $src -Destination (Join-Path $dest ($p.id + '.jar')) -Force }"
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$repo = $env:PITIUPI_REPO; $dest = $env:PITIUPI_PLUGINS; $reg = Get-Content -Raw (Join-Path $repo 'plugins.registry.json') | ConvertFrom-Json; foreach ($p in @($reg.plugins)) { $dir = Join-Path $repo ($p.dir -replace '/','\'); $src = Join-Path $dir ($p.jar -replace '/','\'); if ($p.build -eq 'jar') { if (-not (Test-Path -LiteralPath $src)) { Write-Error ('JAR ausente: ' + $src); exit 1 }; Copy-Item -LiteralPath $src -Destination (Join-Path $dest ($p.id + '.jar')) -Force; continue }; $marker = Join-Path $dir 'build.xml'; if ($p.build -eq 'make') { $marker = Join-Path $dir 'Makefile' }; if (-not (Test-Path -LiteralPath $marker)) { Write-Host ('Plugin ' + $p.id + ' ausente nesta maquina; nao sera carregado.'); continue }; if ($p.build -ne 'ant') { Write-Error ('build nao suportado neste prompt: ' + $p.build); exit 1 }; & ant -f (Join-Path $dir 'build.xml') jar; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; if (-not (Test-Path -LiteralPath $src)) { Write-Error ('JAR ausente: ' + $src); exit 1 }; Copy-Item -LiteralPath $src -Destination (Join-Path $dest ($p.id + '.jar')) -Force }"
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0lib\launch-windows.ps1" -PitiupiHome "%CD%\.pitiupi" -Jar "%CD%\pitiupi\dist\pitiupi.jar"
|
||||
|
||||
@@ -70,6 +70,9 @@ if mode == "validate":
|
||||
if key not in p:
|
||||
print(f"ERRO: campo ausente {key} em {p}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if p["build"] not in ("ant", "make", "jar"):
|
||||
print(f"ERRO: build desconhecido {p['build']} em {p['id']}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
|
||||
if mode == "list":
|
||||
@@ -183,6 +186,9 @@ build_one_plugin() {
|
||||
local dir build_cmd marker
|
||||
dir="$(registry_python field "$id" dir)"
|
||||
build_cmd="$(registry_python field "$id" build)"
|
||||
if [[ "$build_cmd" == "jar" ]]; then
|
||||
return 0
|
||||
fi
|
||||
case "$build_cmd" in
|
||||
ant) marker="${REPO_ROOT}/${dir}/build.xml" ;;
|
||||
make) marker="${REPO_ROOT}/${dir}/Makefile" ;;
|
||||
@@ -244,6 +250,10 @@ link_plugins_from_selection() {
|
||||
local marker=""
|
||||
local build_cmd
|
||||
build_cmd="$(registry_python field "$id" build)"
|
||||
if [[ "$build_cmd" == "jar" ]]; then
|
||||
printf 'ERRO: JAR ausente: %s\n' "${src_abs}" >&2
|
||||
exit 1
|
||||
fi
|
||||
case "$build_cmd" in
|
||||
ant) marker="${REPO_ROOT}/${dir}/build.xml" ;;
|
||||
make) marker="${REPO_ROOT}/${dir}/Makefile" ;;
|
||||
|
||||
Reference in new issue
Block a user