Files
pitiupi/docs/nucleo.md
T
Gabriel Felipe Souza SantosandCursor 8338936251 docs: reescreve o manual e registra o JAR instalado no catálogo
O texto explica o núcleo, a rede e o tutorial do ping como o código está hoje. Instalar ou remover um JAR atualiza plugins.registry.json para o próximo run não perder o plugin.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-04 14:14:18 -03:00

74 lines
5.4 KiB
Markdown

# 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).