Files
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

6.0 KiB

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:

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. Onde cada pacote mora: nucleo.md. Como a rede trata o byte: rede.md.