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>
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
Maingarante a pasta.pitiupi/e o login (nome e porta).MainShellFramechamaPluginManager.loadPlugins().- Cada classe listada no arquivo SPI é instanciada.
- O núcleo chama
createPlugin(connectionManager, workspace). - O painel registrado com
bindShellWorkspaceContentfica guardado. A aba só abre na galeria ou numa peer action. - Quando um UDP não é heartbeat, ou quando um frame TCP é uma
Message, o núcleo desserializa com o classloader do JAR e chamareceiveMessagese o tipo estiver emgetMessages().
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.