From 8338936251dfb001025e90383dc70ff1bb9723fd Mon Sep 17 00:00:00 2001 From: Gabriel Felipe Souza Santos Date: Wed, 4 Nov 2026 14:14:18 -0300 Subject: [PATCH] =?UTF-8?q?docs:=20reescreve=20o=20manual=20e=20registra?= =?UTF-8?q?=20o=20JAR=20instalado=20no=20cat=C3=A1logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- README.md | 12 +- docs/README.md | 35 +- docs/comecar.md | 98 ++++ docs/decision-register.md | 58 +- docs/documentation/00_convencoes.md | 32 -- docs/documentation/00_quickstart.md | 79 --- .../02_modulos_do_repositorio.md | 27 - docs/documentation/03_build_ant_netbeans.md | 17 - docs/documentation/04_inicializacao_e_gui.md | 151 ------ docs/documentation/05_udp_multicast.md | 114 ---- docs/documentation/06_tcp_conexoes.md | 207 ------- .../07_mensagens_e_serializacao.md | 33 -- docs/documentation/08_heartbeat_e_presenca.md | 91 ---- docs/documentation/09_sistema_de_plugins.md | 93 ---- docs/documentation/10_tutorial_plugin.md | 111 ---- docs/documentation/11_plugins_referencia.md | 58 -- .../12_menus_unicast_multicast.md | 15 - docs/documentation/13_guia_desenvolvimento.md | 59 -- .../14_arquitetura_e_manutencao.md | 73 --- docs/documentation/15_testes_e_gate.md | 56 -- docs/documentation/16_fundamentacao_redes.md | 64 --- docs/documentation/17_teste_com_colega.md | 52 -- docs/documentation/README.md | 27 - .../appendix_portas_e_multicast.md | 78 --- docs/documentation/engineering_backlog.md | 12 - docs/nucleo-para-plugins.md | 165 ++++++ docs/nucleo.md | 73 +++ docs/plugins.md | 92 ++++ docs/rede.md | 115 ++++ docs/references/README.md | 24 +- docs/references/notas/java_serializacao.md | 11 - docs/references/notas/java_spi_plugins.md | 9 - docs/references/notas/multicast_lan.md | 12 - docs/references/notas/udp_vs_tcp.md | 10 - docs/referencias.md | 12 + docs/tp/01_visao_disciplina.md | 32 +- docs/tp/02_tutorial_ping_aula.md | 512 ++++++++++++++---- docs/tp/README.md | 10 +- docs/usar.md | 60 ++ pitiupi/plugins/README.md | 2 +- .../pitiupi/plugins/store/PluginCatalog.java | 159 ++++++ .../pitiupi/plugins/store/PluginStore.java | 33 ++ .../plugins/store/PluginCatalogTest.java | 82 +++ .../plugins/store/PluginStoreTest.java | 40 ++ plugins/ping/README.md | 18 +- scripts/pitiupi.cmd | 2 +- scripts/pitiupi.sh | 10 + 47 files changed, 1423 insertions(+), 1712 deletions(-) create mode 100644 docs/comecar.md delete mode 100644 docs/documentation/00_convencoes.md delete mode 100644 docs/documentation/00_quickstart.md delete mode 100644 docs/documentation/02_modulos_do_repositorio.md delete mode 100644 docs/documentation/03_build_ant_netbeans.md delete mode 100644 docs/documentation/04_inicializacao_e_gui.md delete mode 100644 docs/documentation/05_udp_multicast.md delete mode 100644 docs/documentation/06_tcp_conexoes.md delete mode 100644 docs/documentation/07_mensagens_e_serializacao.md delete mode 100644 docs/documentation/08_heartbeat_e_presenca.md delete mode 100644 docs/documentation/09_sistema_de_plugins.md delete mode 100644 docs/documentation/10_tutorial_plugin.md delete mode 100644 docs/documentation/11_plugins_referencia.md delete mode 100644 docs/documentation/12_menus_unicast_multicast.md delete mode 100644 docs/documentation/13_guia_desenvolvimento.md delete mode 100644 docs/documentation/14_arquitetura_e_manutencao.md delete mode 100644 docs/documentation/15_testes_e_gate.md delete mode 100644 docs/documentation/16_fundamentacao_redes.md delete mode 100644 docs/documentation/17_teste_com_colega.md delete mode 100644 docs/documentation/README.md delete mode 100644 docs/documentation/appendix_portas_e_multicast.md delete mode 100644 docs/documentation/engineering_backlog.md create mode 100644 docs/nucleo-para-plugins.md create mode 100644 docs/nucleo.md create mode 100644 docs/plugins.md create mode 100644 docs/rede.md delete mode 100644 docs/references/notas/java_serializacao.md delete mode 100644 docs/references/notas/java_spi_plugins.md delete mode 100644 docs/references/notas/multicast_lan.md delete mode 100644 docs/references/notas/udp_vs_tcp.md create mode 100644 docs/referencias.md create mode 100644 docs/usar.md create mode 100644 pitiupi/src/pitiupi/plugins/store/PluginCatalog.java create mode 100644 pitiupi/test/pitiupi/plugins/store/PluginCatalogTest.java diff --git a/README.md b/README.md index 514e9bb..406516b 100644 --- a/README.md +++ b/README.md @@ -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). --- diff --git a/docs/README.md b/docs/README.md index f262c51..e38e2c1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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`. diff --git a/docs/comecar.md b/docs/comecar.md new file mode 100644 index 0000000..9209918 --- /dev/null +++ b/docs/comecar.md @@ -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 : 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`. diff --git a/docs/decision-register.md b/docs/decision-register.md index 582afc3..1d73b3d 100644 --- a/docs/decision-register.md +++ b/docs/decision-register.md @@ -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) | diff --git a/docs/documentation/00_convencoes.md b/docs/documentation/00_convencoes.md deleted file mode 100644 index 5d530f1..0000000 --- a/docs/documentation/00_convencoes.md +++ /dev/null @@ -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). diff --git a/docs/documentation/00_quickstart.md b/docs/documentation/00_quickstart.md deleted file mode 100644 index 435b8dd..0000000 --- a/docs/documentation/00_quickstart.md +++ /dev/null @@ -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 : 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 -``` diff --git a/docs/documentation/02_modulos_do_repositorio.md b/docs/documentation/02_modulos_do_repositorio.md deleted file mode 100644 index 0abf6fc..0000000 --- a/docs/documentation/02_modulos_do_repositorio.md +++ /dev/null @@ -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). diff --git a/docs/documentation/03_build_ant_netbeans.md b/docs/documentation/03_build_ant_netbeans.md deleted file mode 100644 index 3a6e05e..0000000 --- a/docs/documentation/03_build_ant_netbeans.md +++ /dev/null @@ -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). diff --git a/docs/documentation/04_inicializacao_e_gui.md b/docs/documentation/04_inicializacao_e_gui.md deleted file mode 100644 index e6ce19a..0000000 --- a/docs/documentation/04_inicializacao_e_gui.md +++ /dev/null @@ -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). diff --git a/docs/documentation/05_udp_multicast.md b/docs/documentation/05_udp_multicast.md deleted file mode 100644 index 98d04a8..0000000 --- a/docs/documentation/05_udp_multicast.md +++ /dev/null @@ -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). diff --git a/docs/documentation/06_tcp_conexoes.md b/docs/documentation/06_tcp_conexoes.md deleted file mode 100644 index bdb4eed..0000000 --- a/docs/documentation/06_tcp_conexoes.md +++ /dev/null @@ -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). diff --git a/docs/documentation/07_mensagens_e_serializacao.md b/docs/documentation/07_mensagens_e_serializacao.md deleted file mode 100644 index 439e6e1..0000000 --- a/docs/documentation/07_mensagens_e_serializacao.md +++ /dev/null @@ -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> 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). diff --git a/docs/documentation/08_heartbeat_e_presenca.md b/docs/documentation/08_heartbeat_e_presenca.md deleted file mode 100644 index c26a47d..0000000 --- a/docs/documentation/08_heartbeat_e_presenca.md +++ /dev/null @@ -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] -``` diff --git a/docs/documentation/09_sistema_de_plugins.md b/docs/documentation/09_sistema_de_plugins.md deleted file mode 100644 index 8156dd4..0000000 --- a/docs/documentation/09_sistema_de_plugins.md +++ /dev/null @@ -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. diff --git a/docs/documentation/10_tutorial_plugin.md b/docs/documentation/10_tutorial_plugin.md deleted file mode 100644 index 5695e3c..0000000 --- a/docs/documentation/10_tutorial_plugin.md +++ /dev/null @@ -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> 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 diff --git a/docs/documentation/11_plugins_referencia.md b/docs/documentation/11_plugins_referencia.md deleted file mode 100644 index 3ae21e5..0000000 --- a/docs/documentation/11_plugins_referencia.md +++ /dev/null @@ -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:`) | — | - -## 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). diff --git a/docs/documentation/12_menus_unicast_multicast.md b/docs/documentation/12_menus_unicast_multicast.md deleted file mode 100644 index afcc229..0000000 --- a/docs/documentation/12_menus_unicast_multicast.md +++ /dev/null @@ -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). diff --git a/docs/documentation/13_guia_desenvolvimento.md b/docs/documentation/13_guia_desenvolvimento.md deleted file mode 100644 index 11ea95b..0000000 --- a/docs/documentation/13_guia_desenvolvimento.md +++ /dev/null @@ -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) diff --git a/docs/documentation/14_arquitetura_e_manutencao.md b/docs/documentation/14_arquitetura_e_manutencao.md deleted file mode 100644 index 2ebce60..0000000 --- a/docs/documentation/14_arquitetura_e_manutencao.md +++ /dev/null @@ -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). diff --git a/docs/documentation/15_testes_e_gate.md b/docs/documentation/15_testes_e_gate.md deleted file mode 100644 index d4ae9aa..0000000 --- a/docs/documentation/15_testes_e_gate.md +++ /dev/null @@ -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). diff --git a/docs/documentation/16_fundamentacao_redes.md b/docs/documentation/16_fundamentacao_redes.md deleted file mode 100644 index 7257f25..0000000 --- a/docs/documentation/16_fundamentacao_redes.md +++ /dev/null @@ -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) diff --git a/docs/documentation/17_teste_com_colega.md b/docs/documentation/17_teste_com_colega.md deleted file mode 100644 index 4155105..0000000 --- a/docs/documentation/17_teste_com_colega.md +++ /dev/null @@ -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). diff --git a/docs/documentation/README.md b/docs/documentation/README.md deleted file mode 100644 index e86ad61..0000000 --- a/docs/documentation/README.md +++ /dev/null @@ -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 | diff --git a/docs/documentation/appendix_portas_e_multicast.md b/docs/documentation/appendix_portas_e_multicast.md deleted file mode 100644 index 901b6b3..0000000 --- a/docs/documentation/appendix_portas_e_multicast.md +++ /dev/null @@ -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 | diff --git a/docs/documentation/engineering_backlog.md b/docs/documentation/engineering_backlog.md deleted file mode 100644 index e796b0f..0000000 --- a/docs/documentation/engineering_backlog.md +++ /dev/null @@ -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. diff --git a/docs/nucleo-para-plugins.md b/docs/nucleo-para-plugins.md new file mode 100644 index 0000000..4c297e7 --- /dev/null +++ b/docs/nucleo-para-plugins.md @@ -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> 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`. diff --git a/docs/nucleo.md b/docs/nucleo.md new file mode 100644 index 0000000..6423832 --- /dev/null +++ b/docs/nucleo.md @@ -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). diff --git a/docs/plugins.md b/docs/plugins.md new file mode 100644 index 0000000..dd78451 --- /dev/null +++ b/docs/plugins.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). diff --git a/docs/rede.md b/docs/rede.md new file mode 100644 index 0000000..fb68022 --- /dev/null +++ b/docs/rede.md @@ -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 +``` diff --git a/docs/references/README.md b/docs/references/README.md index bcb08e9..86898a8 100644 --- a/docs/references/README.md +++ b/docs/references/README.md @@ -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 | diff --git a/docs/references/notas/java_serializacao.md b/docs/references/notas/java_serializacao.md deleted file mode 100644 index d269334..0000000 --- a/docs/references/notas/java_serializacao.md +++ /dev/null @@ -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). diff --git a/docs/references/notas/java_spi_plugins.md b/docs/references/notas/java_spi_plugins.md deleted file mode 100644 index fa9198f..0000000 --- a/docs/references/notas/java_spi_plugins.md +++ /dev/null @@ -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). diff --git a/docs/references/notas/multicast_lan.md b/docs/references/notas/multicast_lan.md deleted file mode 100644 index 140d03c..0000000 --- a/docs/references/notas/multicast_lan.md +++ /dev/null @@ -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). diff --git a/docs/references/notas/udp_vs_tcp.md b/docs/references/notas/udp_vs_tcp.md deleted file mode 100644 index e47607e..0000000 --- a/docs/references/notas/udp_vs_tcp.md +++ /dev/null @@ -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). diff --git a/docs/referencias.md b/docs/referencias.md new file mode 100644 index 0000000..bfbbae5 --- /dev/null +++ b/docs/referencias.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. diff --git a/docs/tp/01_visao_disciplina.md b/docs/tp/01_visao_disciplina.md index b312a2a..90a976d 100644 --- a/docs/tp/01_visao_disciplina.md +++ b/docs/tp/01_visao_disciplina.md @@ -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. diff --git a/docs/tp/02_tutorial_ping_aula.md b/docs/tp/02_tutorial_ping_aula.md index a53b3ea..394ac03 100644 --- a/docs/tp/02_tutorial_ping_aula.md +++ b/docs/tp/02_tutorial_ping_aula.md @@ -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 +pitiupi.ping +``` + +`plugins/ping/build.xml`, a tag `project` da linha 10 e a descrição da linha 11: + +```xml + + Builds, tests, and runs the project pitiupi.ping. +``` + +`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:`). -- `+ 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:` 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:` 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, + "Abas ↑ enviam; ↓ só recebem. Use + Nova ou a lista lateral para abrir um peer.", + 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:` (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:` | `Nome ↑` | Você escolheu o destino. Nasce na lista lateral, no **+ Nova** ou no rascunho de IP | +| `view:` | `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:` 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> 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` | diff --git a/docs/tp/README.md b/docs/tp/README.md index 529d27c..dfd2f56 100644 --- a/docs/tp/README.md +++ b/docs/tp/README.md @@ -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 | diff --git a/docs/usar.md b/docs/usar.md new file mode 100644 index 0000000..3aa3d01 --- /dev/null +++ b/docs/usar.md @@ -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. diff --git a/pitiupi/plugins/README.md b/pitiupi/plugins/README.md index b075bb4..7567ec4 100644 --- a/pitiupi/plugins/README.md +++ b/pitiupi/plugins/README.md @@ -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). diff --git a/pitiupi/src/pitiupi/plugins/store/PluginCatalog.java b/pitiupi/src/pitiupi/plugins/store/PluginCatalog.java new file mode 100644 index 0000000..6ad6318 --- /dev/null +++ b/pitiupi/src/pitiupi/plugins/store/PluginCatalog.java @@ -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 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 read(Path registry) throws IOException { + String text = Files.readString(registry); + List 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 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 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 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("\"", "\\\""); + } +} diff --git a/pitiupi/src/pitiupi/plugins/store/PluginStore.java b/pitiupi/src/pitiupi/plugins/store/PluginStore.java index e22fa72..e3dc091 100644 --- a/pitiupi/src/pitiupi/plugins/store/PluginStore.java +++ b/pitiupi/src/pitiupi/plugins/store/PluginStore.java @@ -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 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); + } } diff --git a/pitiupi/test/pitiupi/plugins/store/PluginCatalogTest.java b/pitiupi/test/pitiupi/plugins/store/PluginCatalogTest.java new file mode 100644 index 0000000..57db54e --- /dev/null +++ b/pitiupi/test/pitiupi/plugins/store/PluginCatalogTest.java @@ -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 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 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 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()); + } +} diff --git a/pitiupi/test/pitiupi/plugins/store/PluginStoreTest.java b/pitiupi/test/pitiupi/plugins/store/PluginStoreTest.java index 882b861..2ab9a12 100644 --- a/pitiupi/test/pitiupi/plugins/store/PluginStoreTest.java +++ b/pitiupi/test/pitiupi/plugins/store/PluginStoreTest.java @@ -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 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 left = PluginCatalog.read(registry); + assertEquals(1, left.size()); + assertEquals("chat", left.get(0).id()); + assertEquals("plugins/chat", left.get(0).dir()); + } } diff --git a/plugins/ping/README.md b/plugins/ping/README.md index 5035491..c7c7607 100644 --- a/plugins/ping/README.md +++ b/plugins/ping/README.md @@ -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). diff --git a/scripts/pitiupi.cmd b/scripts/pitiupi.cmd index 86a1ae4..9ff4b4c 100644 --- a/scripts/pitiupi.cmd +++ b/scripts/pitiupi.cmd @@ -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" diff --git a/scripts/pitiupi.sh b/scripts/pitiupi.sh index 3828f31..1489a62 100755 --- a/scripts/pitiupi.sh +++ b/scripts/pitiupi.sh @@ -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" ;;