From fc9739a998cc01f78870392318a5ce4b45edb1af Mon Sep 17 00:00:00 2001 From: Gabriel Felipe Souza Santos Date: Tue, 27 Oct 2026 19:31:32 -0300 Subject: [PATCH] =?UTF-8?q?feat(v1):=20REQ-02=20CLI,=20tutorial=20Ping=20e?= =?UTF-8?q?=20remo=C3=A7=C3=A3o=20da=20GUI=20legada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adiciona PluginStoreCli para smoke de instalação de JAR; reescreve o tutorial de ping e limitações didáticas; remove MainWindow/LegacyPluginWorkspace e move Sobre para AboutDialog na shell. Co-authored-by: Cursor --- docs/documentation/09_sistema_de_plugins.md | 2 + docs/documentation/11_plugins_referencia.md | 14 +- docs/documentation/17_tutorial_ping_aula.md | 201 ++++++++++-------- pitiupi/src/pitiupi/GUI/About.java | 72 ------- pitiupi/src/pitiupi/GUI/Login.java | 119 ----------- pitiupi/src/pitiupi/GUI/MainWindow.java | 103 --------- pitiupi/src/pitiupi/GUI/MenuBar.java | 116 ---------- pitiupi/src/pitiupi/GUI/OnlineUsersPanel.java | 105 --------- pitiupi/src/pitiupi/Main.java | 8 +- .../api/plugin/LegacyPluginWorkspace.java | 78 ------- .../activity/PeerActivityInbox.java | 2 +- .../control/plugins/PluginManager.java | 14 -- .../presentation/shell/AboutDialog.java | 32 +++ .../presentation/shell/ShellMenuBar.java | 4 +- pitiupi/src/pitiupi/tools/PluginStoreCli.java | 78 +++++++ plugins/ping/.gitignore | 4 +- plugins/ping/README.md | 4 +- 17 files changed, 242 insertions(+), 714 deletions(-) delete mode 100644 pitiupi/src/pitiupi/GUI/About.java delete mode 100644 pitiupi/src/pitiupi/GUI/Login.java delete mode 100644 pitiupi/src/pitiupi/GUI/MainWindow.java delete mode 100644 pitiupi/src/pitiupi/GUI/MenuBar.java delete mode 100644 pitiupi/src/pitiupi/GUI/OnlineUsersPanel.java delete mode 100644 pitiupi/src/pitiupi/api/plugin/LegacyPluginWorkspace.java create mode 100644 pitiupi/src/pitiupi/presentation/shell/AboutDialog.java create mode 100644 pitiupi/src/pitiupi/tools/PluginStoreCli.java diff --git a/docs/documentation/09_sistema_de_plugins.md b/docs/documentation/09_sistema_de_plugins.md index 0b8cdb2..5f0ad7f 100644 --- a/docs/documentation/09_sistema_de_plugins.md +++ b/docs/documentation/09_sistema_de_plugins.md @@ -51,6 +51,8 @@ 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 só **chat** e **screencast** no CI. Exercícios (ex. [ping](17_tutorial_ping_aula.md)) ficam fora do repositório — o aluno instala o JAR localmente após `ant jar`. + ### Carregamento de plugins O `PluginManager.loadPlugins()` (chamado pela `MainShellFrame`): diff --git a/docs/documentation/11_plugins_referencia.md b/docs/documentation/11_plugins_referencia.md index 12b5661..e8e09ff 100644 --- a/docs/documentation/11_plugins_referencia.md +++ b/docs/documentation/11_plugins_referencia.md @@ -28,4 +28,16 @@ Dois módulos buildáveis no catálogo; o chat unifica o antigo mpChat (multicas ## Exercício Ping -Material em [17_tutorial_ping_aula.md](17_tutorial_ping_aula.md) e pasta `plugins/ping/` (sem JAR no repositório). +Material em [17_tutorial_ping_aula.md](17_tutorial_ping_aula.md) e pasta `plugins/ping/` (sem código/JAR no repositório). Não entra no `plugins.registry.json` nem no gate CI. + +## Limitações didáticas (não são bugs) + +Os plugins de referência são **exemplos de sala de aula**, 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 | +| **ping** (exercício do aluno) | RTT, ping uma vez/contínuo, parar e avisar o peer | ICMP do SO, gráficos, SLA | + +Para evoluir: copie o padrão (`Plugin` + mensagens + painel na aba) e acrescente features no **seu** JAR — sem inflar o núcleo. diff --git a/docs/documentation/17_tutorial_ping_aula.md b/docs/documentation/17_tutorial_ping_aula.md index 5be2728..b660415 100644 --- a/docs/documentation/17_tutorial_ping_aula.md +++ b/docs/documentation/17_tutorial_ping_aula.md @@ -1,134 +1,147 @@ # Tutorial de aula — plugin Ping -Material para o aluno implementar um plugin **Ping/Pong** com medição de RTT (round-trip time). A solução em Java **não** entra no repositório oficial; apenas este tutorial e a pasta [`plugins/ping/`](../../plugins/ping/) (README + `.gitignore`). +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. -Índice geral: [README.md](README.md). +A solução Java **não** entra no Git: só este tutorial e [`plugins/ping/README.md`](../../plugins/ping/README.md). O professor valida localmente; o aluno implementa no próprio `plugins/ping/` (gitignored). -## Objetivos +Índice: [README.md](README.md) · Plugins de referência: [11_plugins_referencia.md](11_plugins_referencia.md). -- Estender `Plugin` e registrar SPI. -- Mensagens `Serializable` que estendem `Message`. -- `ConnectionManager`: multicast UDP, unicast UDP e TCP. -- UI na aba central: `JPanel` + `bindShellWorkspaceContent` (sem `JFrame`); abrir pela galeria **Plugins** ou peer actions. -- Instalar JAR em `PITIUPI_HOME/plugins` e testar com um colega. +## O que você vai aprender + +| Componente do núcleo | Para que serve no Ping | +| --- | --- | +| `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) | + +## 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 -- Pitiupi com login (nome + porta). -- [09_sistema_de_plugins.md](09_sistema_de_plugins.md). -- JDK, Ant; núcleo em `pitiupi/dist/pitiupi.jar`. - -## Estrutura sugerida (projeto do aluno) - -``` -plugins/ping/ # ou projeto fora do repo oficial -├── build.xml -├── nbproject/ -├── src/ -│ ├── META-INF/services/pitiupi.models.Plugin -│ └── br/ufsj/redes/ping/ -│ ├── PingPlugin.java -│ ├── PingMessage.java -│ ├── PongMessage.java -│ └── PingPanel.java -└── dist/pitiupi.ping.jar -``` - -Modelo Ant: copiar [`plugins/chat`](../chat) e ajustar classpath para `../../pitiupi/dist/pitiupi.jar`. +1. Pitiupi abrindo com login (nome + porta). +2. [`09_sistema_de_plugins.md`](09_sistema_de_plugins.md). +3. JDK 17 + Ant. +4. Núcleo compilado: `pitiupi/dist/pitiupi.jar`. ## Passo 1 — Projeto Ant -1. Copiar `plugins/chat`. -2. JAR de saída: `dist/pitiupi.ping.jar`. -3. Classpath de compilação: JAR do núcleo. +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`. **Verificação:** `ant jar` sem erros. -## Passo 2 — `PingPlugin` +## Passo 2 — SPI -| Método | Exemplo | -| --- | --- | -| `getName()` | `"Ping"` | -| `getDisplayName()` | `"Ping na rede"` | -| `getIdentifier()` | `"pitiupi.ping"` (único na sala) | +Arquivo `src/META-INF/services/pitiupi.models.Plugin`: -Em `createPlugin(ConnectionManager cm, PluginWorkspace workspace)`: - -```java -pingPanel = new PingPanel(cm); -bindShellWorkspaceContent(pingPanel); -installPeerActions(workspace.peerActions()); ``` - -**Não** chame `openOrFocusTab` no boot — a aba abre quando o usuário clicar na galeria ou usar peer action. - -Peer actions sugeridas: “Enviar ping” (peer único), “Ping para todos”. - -**Verificação:** após instalar o JAR e reiniciar, só **Plugins** e **Configurações** no boot; botão na galeria abre a aba Ping. +pitiupi.ping.plugin.PingPlugin +``` ## Passo 3 — Mensagens -`PingMessage` e `PongMessage` com `pingId` (UUID) e timestamps para RTT. +Três classes em `pitiupi.ping.net`, todas estendendo `pitiupi.models.Messages.Message`: -Registrar em `getMessages()`: +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); +return List.of(PingMessage.class, PongMessage.class, PingStopMessage.class); ``` -Em `receiveMessage`: ao receber ping, responder pong; ao receber pong, calcular RTT e atualizar a UI com `SwingUtilities.invokeLater`. +**Por quê:** o núcleo só desserializa tipos declarados; evita que outro plugin receba seus bytes por engano. -## Passo 4 — `PingPanel` +## Passo 4 — `PingController` (lógica) -Botões para multicast, unicast UDP e TCP; área de log ou label com último RTT em ms. Para um peer, use campo IP como no `ChatPanel` ou peer action na lista lateral. +Classe em `pitiupi.ping.control` **sem Swing**: -## Passo 5 — Transportes +- 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. -Use `pitiupi.application.messaging.MessageDispatcher` no núcleo: +Use `PeerIpv4` e `LocalNetworkInfo.bestLocalIpv4()` para IP legível no Windows. + +## Passo 5 — `PingPanel` (UI) + +`JPanel` com: + +- `SessionTabBar` — aba fixa **Todos** + `addTabIfAbsent` quando chega tráfego de um peer (**sem** mudar a aba ativa da shell). +- `JTextArea` monoespaçada por aba; linhas como `[HH:mm:ss] pong ← Alice · RTT 42 ms`. +- Botão **Parar todos os pings contínuos** chamando o controller. + +**Não** use `JFrame`. No `PingPlugin`: ```java -MessageDispatcher.send(connectionManager, pingMessage, SendTarget.broadcastAll(), TransportMode.MULTICAST_UDP); -MessageDispatcher.send(connectionManager, pingMessage, SendTarget.singlePeer(addr), TransportMode.UNICAST_UDP); -MessageDispatcher.send(connectionManager, pingMessage, SendTarget.singlePeer(addr), TransportMode.TCP); +bindShellWorkspaceContent(panel); ``` -| Destino | Transporte didático | Observação | -| --- | --- | --- | -| Todos | Multicast UDP | Padrão em sala | -| Todos | Unicast UDP | Loop em cada peer online (demo) | -| Um peer | Unicast UDP / TCP | IP da lista ou manual | +A aba só abre quando o usuário clicar na galeria **Plugins** ou numa peer action. -Não alterar o endereço multicast do núcleo (`224.0.0.3`). +## Passo 6 — `PingPlugin` -## Passo 6 — SPI e instalação - -`META-INF/services/pitiupi.models.Plugin`: - -``` -br.ufsj.redes.ping.PingPlugin -``` - -Copiar JAR para `~/pitiupi/plugins/` (ou instalar pela aba Configurações) e **reiniciar**. - -## Critérios de sucesso - -| # | Critério | +| Método | Valor | | --- | --- | -| C1 | JAR carrega sem exceção | -| C2 | Pelo menos um transporte funciona entre duas instâncias | -| C3 | RTT em ms após o pong | -| C4 | README do aluno com build e transporte usado | +| `getIdentifier()` | `pitiupi.ping` (único na sala) | +| `getDisplayName()` | `Ping na rede` | -## Anti-padrões +`createPlugin(ConnectionManager, PluginWorkspace)`: -- Commitar solução no repo oficial. -- Busy-wait na EDT. -- Objetos não `Serializable`. -- Mesmo `getIdentifier()` que outro plugin. +1. Crie `PingController` e `PingPanel`. +2. `bindShellWorkspaceContent(panel)`. +3. `installPeerActions(workspace.peerActions())`. -## Referências +**Peer actions sugeridas:** -- [10_tutorial_plugin.md](10_tutorial_plugin.md) -- [05_udp_multicast.md](05_udp_multicast.md) -- [06_tcp_conexoes.md](06_tcp_conexoes.md) -- [04_inicializacao_e_gui.md](04_inicializacao_e_gui.md) +| id | Label | Alvo | +| --- | --- | --- | +| `ping.once` | Ping uma vez | um peer | +| `ping.continuous` | Ping contínuo | um peer | +| `ping.stop` | Parar ping | um peer | +| `ping.all.once` | Ping para todos (uma vez) | Todos → `MULTICAST_UDP` | + +Em cada ação de peer único: `workspace.openOrFocusTab(getIdentifier(), getDisplayName())` e chame o controller. + +`receiveMessage`: despache para o controller com `SwingUtilities.invokeLater`. + +`onShellTabClosed`: pare todos os timers contínuos. + +## Passo 7 — Instalar e testar + +1. `ant jar` → `dist/pitiupi.ping.jar`. +2. Aba **Configurações** → **Instalar JAR…** (ou copie para `~/pitiupi/plugins/`). +3. **Reinicie** o Pitiupi (obrigatório após mudar plugins). +4. Dois processos no mesmo PC: portas **5000** e **5001**, nomes diferentes. +5. Peer action **Ping uma vez** → veja RTT no log. +6. **Ping contínuo** → linhas a cada ~1 s; **Parar ping** → o outro vê “parou o ping contínuo”. +7. Com colega na LAN: mesma porta, JAR instalado nos dois. + +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. + +## Passo 8 — Comparar com o chat + +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. + +## Checklist do aluno + +- [ ] SPI e `getIdentifier` únicos +- [ ] Três mensagens registradas +- [ ] RTT calculado no `Pong` +- [ ] Aba passiva ao receber ping de peer novo +- [ ] Contínuo + parar com aviso ao peer +- [ ] Reinício após instalar JAR +- [ ] Teste com segundo usuário (5001 ou outro PC) diff --git a/pitiupi/src/pitiupi/GUI/About.java b/pitiupi/src/pitiupi/GUI/About.java deleted file mode 100644 index 8e92516..0000000 --- a/pitiupi/src/pitiupi/GUI/About.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package pitiupi.GUI; - -import javax.swing.*; - -/** - * Janela que exibe informações sobre a aplicação, incluindo versão, - * descrição e autores. - * - * @author gustavo - */ - -public class About extends javax.swing.JFrame { - public About () { - this.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE); - initComponents(); - } - - private void initComponents() { - this.setTitle("About"); - this.setBounds(new java.awt.Rectangle(0, 0, 800, 600)); - this.getContentPane().setLayout(new java.awt.BorderLayout(5, 5)); - this.setPreferredSize(new java.awt.Dimension(800, 600)); - - JScrollPane scroll = getJScrollPane(); - - this.add(scroll); - } - - private static JScrollPane getJScrollPane() { - JEditorPane texto = new JEditorPane(); - texto.setContentType("text/html"); - texto.setEditable(false); - - texto.setText(""" - - -

Share My Sheet

- \s -

- Versão: 1.0 -

- \s -

- Plataforma P2P baseada em plugins para o desenvolvimento de aplicações - distribuídas. Criada para apoiar as práticas da disciplina de Redes de - Computadores e servir como base para projetos acadêmicos e TCCs. - \s -

- \s -

- Autores:
- Flávio
- Tony
- Gustavo Henrique -

- \s -

- Universidade Federal de São João del-Rei (UFSJ) -

- \s - - - \s"""); - - return new JScrollPane(texto); - } -} \ No newline at end of file diff --git a/pitiupi/src/pitiupi/GUI/Login.java b/pitiupi/src/pitiupi/GUI/Login.java deleted file mode 100644 index 893161d..0000000 --- a/pitiupi/src/pitiupi/GUI/Login.java +++ /dev/null @@ -1,119 +0,0 @@ -package pitiupi.GUI; - -import java.awt.BorderLayout; -import java.awt.FlowLayout; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.util.prefs.Preferences; - -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.JTextField; - -/** - * Janela de preferencias da aplicação. - * - *

Permite ao usuário informar o nome utilizado na rede e a porta - * multicast que será utilizada pela comunicação.

- * - * @author flavio - * @author tony - */ -public final class Login extends JDialog { - - private static final Preferences prefs = Preferences.userNodeForPackage(Login.class); - private static final String PREF_NOME = "lastUserName"; - private static final String PREF_PORTA = "lastPort"; - - JLabel nameLabel = new JLabel("Name: "); - JLabel portLabel = new JLabel("Port: "); - - JTextField nameField = new JTextField(); - JSpinner portField = new JSpinner(); - - JButton okButton = new JButton("OK"); - JButton cancelButton = new JButton("Cancel"); - - public Login() { - initComponents(); - this.setSize(400, 150); - this.setModal(true); - } - - public void initComponents() { - this.setTitle("Login"); - - String nomeSalvo = prefs.get(PREF_NOME, System.getProperty("user.name")); - int portaSalva = prefs.getInt(PREF_PORTA, 8000); - - portField.setModel(new javax.swing.SpinnerNumberModel(portaSalva, 8000, 9000, 1)); - nameField.setText(nomeSalvo); - - JPanel topPanel = new JPanel(new GridBagLayout()); - JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); - - buttonPanel.add(okButton); - buttonPanel.add(cancelButton); - - GridBagConstraints gbc = new GridBagConstraints(); - - gbc.insets = new Insets(4, 4, 4, 4); - - gbc.gridx = 0; - gbc.gridy = 0; - gbc.weightx = 0; - topPanel.add(nameLabel, gbc); - - gbc.gridx = 1; - gbc.gridy = 0; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 1; - topPanel.add(nameField, gbc); - - gbc.gridx = 0; - gbc.gridy = 1; - gbc.fill = GridBagConstraints.NONE; - gbc.weightx = 0; - topPanel.add(portLabel, gbc); - - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.gridx = 1; - gbc.gridy = 1; - gbc.weightx = 1; - topPanel.add(portField, gbc); - - this.add(topPanel); - this.add(buttonPanel, BorderLayout.SOUTH); - - okButton.addActionListener((ActionEvent e) -> { - try { - portField.commitEdit(); - } catch (java.text.ParseException ex) { - return; - } - - prefs.put(PREF_NOME, nameField.getText()); - prefs.putInt(PREF_PORTA, (Integer) portField.getValue()); - - Login.this.setVisible(false); - }); - - cancelButton.addActionListener((ActionEvent e) -> { - System.exit(0); - }); - } - - @Override - public String getName() { - return nameField.getText(); - } - - public int getPort() { - return ((Integer) portField.getValue()); - } -} diff --git a/pitiupi/src/pitiupi/GUI/MainWindow.java b/pitiupi/src/pitiupi/GUI/MainWindow.java deleted file mode 100644 index ceddaa3..0000000 --- a/pitiupi/src/pitiupi/GUI/MainWindow.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package pitiupi.GUI; - -import pitiupi.control.ConnectionManager; -import pitiupi.control.GuiManager; -import pitiupi.control.plugins.PluginManager; -import pitiupi.presentation.shell.NetworkUsersPanel; -import pitiupi.presentation.shell.ShellHost; -import pitiupi.presentation.shell.ShellMenuBar; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -/** - * @deprecated Substituído por {@link pitiupi.presentation.shell.MainShellFrame}. - */ -@Deprecated -public class MainWindow extends JFrame implements ShellHost { - - private JLabel status; - private final NetworkUsersPanel networkUsersPanel; - - private final ConnectionManager connectionManager; - private final PluginManager pluginManager; - private final GuiManager guiManager; - - public MainWindow(String userName, int port) { - pluginManager = new PluginManager(); - connectionManager = new ConnectionManager(userName, pluginManager, port); - - ShellMenuBar menuBar = new ShellMenuBar(); - setJMenuBar(menuBar); - setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); - setTitle("Pitiupi"); - - networkUsersPanel = new NetworkUsersPanel(connectionManager); - getContentPane().add(new JScrollPane(new JTabbedPane()), BorderLayout.CENTER); - getContentPane().add(networkUsersPanel, BorderLayout.LINE_END); - - initStatusPanel(); - - guiManager = new GuiManager(this, menuBar, networkUsersPanel); - - loadPlugins(); - - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - exit(); - } - }); - - pack(); - setSize(new Dimension(1024, 768)); - } - - private void initStatusPanel() { - JPanel statusPanel = new JPanel(); - status = new JLabel(); - statusPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); - updateStatus(); - statusPanel.add(status); - getContentPane().add(statusPanel, BorderLayout.PAGE_END); - } - - @Override - public void exit() { - connectionManager.shutdownGracefully(); - setVisible(false); - dispose(); - System.exit(0); - } - - @Override - public ConnectionManager getConnectionManager() { - return connectionManager; - } - - public void loadPlugins() { - pluginManager.loadPlugins(connectionManager, guiManager); - } - - @Override - public void setUserName(String userName) { - connectionManager.setUserName(userName); - updateStatus(); - } - - @Override - public void setStatusMessage(String message) { - status.setText(message); - } - - private void updateStatus() { - status.setText("Connected on port " + connectionManager.getPort() + " name " + connectionManager.getUserName()); - } -} diff --git a/pitiupi/src/pitiupi/GUI/MenuBar.java b/pitiupi/src/pitiupi/GUI/MenuBar.java deleted file mode 100644 index 846dd10..0000000 --- a/pitiupi/src/pitiupi/GUI/MenuBar.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package pitiupi.GUI; - -import pitiupi.models.Menus.MenuCategory; - -import javax.swing.*; -import java.awt.event.ActionEvent; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.util.HashMap; -import java.util.Map; - -/** - * Barra de menus principal da aplicação. - * - *

Gerencia os menus padrões da interface e disponibiliza um espaço - * para que plugins adicionem seus próprios menus.

- * - * @author flavio - * @author Gustavo - * @author tony - * - */ -public class MenuBar extends JMenuBar { - - private JMenuItem preferencesMenu; - private JMenuItem aboutMenu; - private JMenu fileMenu; - private JMenu helpMenu; - private JMenu pluginMenu; - private Map categories; - - private JMenuItem exitMenu; - private final MainWindow mainWindow; - - /** - * Cria a barra de menus principal da aplicação. - * - * @param mainWindow mainWindow janela principal da aplicação utilizada para executar - */ - public MenuBar(MainWindow mainWindow) { - this.mainWindow = mainWindow; - this.initComponents(); - } - - private void initComponents() { - initFileMenu(); - initPluginMenu(); - initHelpMenu(); - } - - private void initFileMenu() { - fileMenu = new JMenu("File"); - exitMenu = new JMenuItem("Exit"); - - exitMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK)); - exitMenu.addActionListener(this::exitMenuActionPerformed); - fileMenu.add(exitMenu); - this.add(fileMenu); - } - - private void initPluginMenu() { - pluginMenu = new JMenu("Plugin"); - categories = new HashMap<>(); - - for (MenuCategory category : MenuCategory.values()) { - JMenu newCategoryMenu = new JMenu(category.toString()); - - pluginMenu.add(newCategoryMenu); - categories.put(category, newCategoryMenu); - } - - this.add(pluginMenu); - } - - private void initHelpMenu() { - helpMenu = new JMenu("Help"); - preferencesMenu = new JMenuItem("Preferences"); - aboutMenu = new JMenuItem("About"); - - preferencesMenu.addActionListener(this::setupMenuActionPerformed); - helpMenu.add(preferencesMenu); - - aboutMenu.addActionListener(this::aboutMenuActionPerformed); - helpMenu.add(aboutMenu); - - this.add(helpMenu); - } - - private void exitMenuActionPerformed(ActionEvent evt) { - mainWindow.exit(); - } - - private void setupMenuActionPerformed(ActionEvent evt) { - Login login = new Login(); - login.setVisible(true); - - mainWindow.setUserName(login.getName()); - mainWindow.getConnectionManager().setPort(login.getPort()); - - login.dispose(); - } - - private void aboutMenuActionPerformed(ActionEvent evt) { - About about = new About(); - about.setVisible(true); - } - - public void addPluginMenu(MenuCategory category, JMenuItem menu){ - categories.get(category).add(menu); - } -} diff --git a/pitiupi/src/pitiupi/GUI/OnlineUsersPanel.java b/pitiupi/src/pitiupi/GUI/OnlineUsersPanel.java deleted file mode 100644 index 88ce969..0000000 --- a/pitiupi/src/pitiupi/GUI/OnlineUsersPanel.java +++ /dev/null @@ -1,105 +0,0 @@ -package pitiupi.GUI; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import javax.swing.*; - -import pitiupi.models.Menus.MenuCategory; -import pitiupi.models.User; -import pitiupi.control.PeerListener; - -/** - * Painel da interface gráfica responsável por exibir os usuários ativos - * na rede. - * - * @author tony - */ -public class OnlineUsersPanel extends JPanel implements PeerListener { - - private final DefaultListModel listModel; - private final JList userList; - JPopupMenu menu; - private Map categories; - - public OnlineUsersPanel() { - menu = new JPopupMenu(); - categories = new HashMap<>(); - initMenu(); - - setLayout(new BorderLayout()); - setBorder(BorderFactory.createTitledBorder("Usuários nesta rede")); - setPreferredSize(new Dimension(160, 0)); - - listModel = new DefaultListModel<>(); - userList = new JList<>(listModel); - add(new JScrollPane(userList), BorderLayout.CENTER); - - setupUserListActions(); - } - - private void initMenu() { - for (MenuCategory category : MenuCategory.values()) { - JMenu newCategory = new JMenu(category.toString()); - - menu.add(newCategory); - categories.put(category, newCategory); - } - } - - private void setupUserListActions() { - userList.addMouseListener(new MouseAdapter() { - - @Override - public void mouseClicked(MouseEvent e) { - int index = userList.locationToIndex(e.getPoint()); - - if (index == -1) { - return; - } - - userList.setSelectedIndex(index); - User selected = listModel.getElementAt(index); - if (selected != null && selected.isBroadcastTarget()) { - return; - } - menu.show(userList, e.getX(), e.getY()); - } - }); - } - - public void initUnicastMenu(JMenuItem menuItem, Consumer action) { - menuItem.addActionListener(e -> { - User user = userList.getSelectedValue(); - - if (user != null && !user.isBroadcastTarget() && user.getAddress() != null) { - action.accept(user); - } - }); - } - - public void addUnicastMenu(MenuCategory category, JMenuItem menuItem) { - categories.get(category).add(menuItem); - } - - /** - * Atualiza a lista de usuários exibida no painel. - * - * @param peers lista atualizada de usuários ativos na rede. - */ - @Override - public void onPeersChanged(List peers) { - SwingUtilities.invokeLater(() -> { - listModel.clear(); - listModel.addElement(User.broadcastAll()); - for (User peer : peers) { - listModel.addElement(peer); - } - }); - } -} \ No newline at end of file diff --git a/pitiupi/src/pitiupi/Main.java b/pitiupi/src/pitiupi/Main.java index 742e741..d6e6493 100644 --- a/pitiupi/src/pitiupi/Main.java +++ b/pitiupi/src/pitiupi/Main.java @@ -34,13 +34,13 @@ public class Main { } } } catch (ClassNotFoundException ex) { - java.util.logging.Logger.getLogger(pitiupi.GUI.MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } catch (InstantiationException ex) { - java.util.logging.Logger.getLogger(pitiupi.GUI.MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { - java.util.logging.Logger.getLogger(pitiupi.GUI.MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } catch (javax.swing.UnsupportedLookAndFeelException ex) { - java.util.logging.Logger.getLogger(pitiupi.GUI.MainWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } java.awt.EventQueue.invokeLater(new Runnable() { diff --git a/pitiupi/src/pitiupi/api/plugin/LegacyPluginWorkspace.java b/pitiupi/src/pitiupi/api/plugin/LegacyPluginWorkspace.java deleted file mode 100644 index 9710750..0000000 --- a/pitiupi/src/pitiupi/api/plugin/LegacyPluginWorkspace.java +++ /dev/null @@ -1,78 +0,0 @@ -package pitiupi.api.plugin; - -import pitiupi.application.invites.InviteInboxService; -import pitiupi.application.peeractions.PeerActionBroker; -import pitiupi.application.peeractions.PluginPeerActionsRegistrar; -import pitiupi.control.ConnectionManager; -import pitiupi.control.GuiManager; -import pitiupi.models.Plugin; - -import javax.swing.JComponent; - -/** - * Adaptador Onda C: expõe workspace e mantém {@link GuiManager} para plugins legados. - */ -public final class LegacyPluginWorkspace implements PluginWorkspace { - - private final String pluginId; - private final ConnectionManager connectionManager; - private final GuiManager guiManager; - private final PeerActionBroker peerActionBroker; - private final InviteInboxService inviteInboxService; - private final Plugin plugin; - private final PluginPeerActionsRegistrar registrar; - - public LegacyPluginWorkspace( - String pluginId, - Plugin plugin, - ConnectionManager connectionManager, - GuiManager guiManager, - PeerActionBroker peerActionBroker, - InviteInboxService inviteInboxService - ) { - this.pluginId = pluginId; - this.plugin = plugin; - this.connectionManager = connectionManager; - this.guiManager = guiManager; - this.peerActionBroker = peerActionBroker; - this.inviteInboxService = inviteInboxService; - this.registrar = new PluginPeerActionsRegistrar(peerActionBroker, pluginId); - peerActionBroker.registerPlugin(pluginId); - } - - public GuiManager guiManager() { - return guiManager; - } - - public String pluginId() { - return pluginId; - } - - @Override - public ConnectionManager connectionManager() { - return connectionManager; - } - - @Override - public PeerActionsRegistrar peerActions() { - return registrar; - } - - @Override - public void openOrFocusTab(String pluginInstanceId, String title, JComponent content) { - peerActionBroker.setActivePlugin(pluginInstanceId); - } - - @Override - public void setStatusMessage(String text) { - } - - @Override - public void deliverInviteAccepted(InviteDetails invite) { - plugin.onInviteAccepted(invite); - } - - public void bindInviteDelivery() { - inviteInboxService.setInviteHandler(pluginId, plugin::onInviteAccepted); - } -} diff --git a/pitiupi/src/pitiupi/application/activity/PeerActivityInbox.java b/pitiupi/src/pitiupi/application/activity/PeerActivityInbox.java index 22a06eb..7e9a051 100644 --- a/pitiupi/src/pitiupi/application/activity/PeerActivityInbox.java +++ b/pitiupi/src/pitiupi/application/activity/PeerActivityInbox.java @@ -6,7 +6,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** - * Pendências de atividade por peer: contagem (chat) ou indicador (screencast). + * Pendências de atividade por peer (núcleo). Badges na lista lateral adiados na v1 — D23. */ public final class PeerActivityInbox { diff --git a/pitiupi/src/pitiupi/control/plugins/PluginManager.java b/pitiupi/src/pitiupi/control/plugins/PluginManager.java index 200129b..e1b7185 100644 --- a/pitiupi/src/pitiupi/control/plugins/PluginManager.java +++ b/pitiupi/src/pitiupi/control/plugins/PluginManager.java @@ -1,7 +1,6 @@ package pitiupi.control.plugins; import pitiupi.Main; -import pitiupi.api.plugin.LegacyPluginWorkspace; import pitiupi.api.plugin.PluginWorkspace; import pitiupi.models.Plugin; import pitiupi.control.ConnectionManager; @@ -111,19 +110,6 @@ public class PluginManager { } } - /** @deprecated use {@link #loadPlugins(ConnectionManager, GuiManager, Function)} com {@link pitiupi.api.plugin.ShellPluginWorkspace} */ - @Deprecated - public void loadPlugins(ConnectionManager connectionManager, GuiManager guiManager) { - loadPlugins(connectionManager, guiManager, plugin -> new LegacyPluginWorkspace( - plugin.getIdentifier(), - plugin, - connectionManager, - guiManager, - connectionManager.getPeerActionBroker(), - connectionManager.getInviteInboxService() - )); - } - public void loadPlugins( ConnectionManager connectionManager, GuiManager guiManager, diff --git a/pitiupi/src/pitiupi/presentation/shell/AboutDialog.java b/pitiupi/src/pitiupi/presentation/shell/AboutDialog.java new file mode 100644 index 0000000..93f40a4 --- /dev/null +++ b/pitiupi/src/pitiupi/presentation/shell/AboutDialog.java @@ -0,0 +1,32 @@ +package pitiupi.presentation.shell; + +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.WindowConstants; + +/** + * Diálogo simples "Sobre" (Ajuda). + */ +public final class AboutDialog extends JFrame { + + public AboutDialog() { + setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); + setTitle("Sobre o Pitiupi"); + setSize(520, 360); + UiGeometry.centerOnScreen(this); + + JEditorPane texto = new JEditorPane(); + texto.setContentType("text/html"); + texto.setEditable(false); + texto.setText(""" + +

Pitiupi

+

Plataforma P2P didática com plugins (chat, compartilhamento de tela, exercício ping).

+

Uso em laboratório: UFSJ / Redes de Computadores.

+ + """); + + add(new JScrollPane(texto)); + } +} diff --git a/pitiupi/src/pitiupi/presentation/shell/ShellMenuBar.java b/pitiupi/src/pitiupi/presentation/shell/ShellMenuBar.java index 1a567bc..8772191 100644 --- a/pitiupi/src/pitiupi/presentation/shell/ShellMenuBar.java +++ b/pitiupi/src/pitiupi/presentation/shell/ShellMenuBar.java @@ -1,7 +1,5 @@ package pitiupi.presentation.shell; -import pitiupi.GUI.About; - import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; @@ -14,7 +12,7 @@ public final class ShellMenuBar extends JMenuBar { public ShellMenuBar() { JMenu help = new JMenu("Ajuda"); JMenuItem about = new JMenuItem("Sobre"); - about.addActionListener(e -> new About().setVisible(true)); + about.addActionListener(e -> new AboutDialog().setVisible(true)); help.add(about); add(help); } diff --git a/pitiupi/src/pitiupi/tools/PluginStoreCli.java b/pitiupi/src/pitiupi/tools/PluginStoreCli.java new file mode 100644 index 0000000..6510508 --- /dev/null +++ b/pitiupi/src/pitiupi/tools/PluginStoreCli.java @@ -0,0 +1,78 @@ +package pitiupi.tools; + +import pitiupi.errors.PitiupiException; +import pitiupi.platform.PitiupiHome; +import pitiupi.plugins.store.InstalledPluginRef; +import pitiupi.plugins.store.PluginStore; + +import java.nio.file.Path; +import java.util.List; + +/** + * CLI mínima para smoke REQ-02 (instalar/listar/remover JAR) sem Swing. + */ +public final class PluginStoreCli { + + private PluginStoreCli() { + } + + public static void main(String[] args) { + if (args.length < 1) { + usage(); + System.exit(1); + } + try { + PitiupiHome.get().ensureLayout(); + PluginStore store = new PluginStore(); + switch (args[0]) { + case "install" -> install(store, args); + case "list" -> list(store); + case "remove" -> remove(store, args); + default -> { + usage(); + System.exit(1); + } + } + } catch (PitiupiException ex) { + System.err.println("ERRO: " + ex.getUserMessage()); + System.exit(2); + } catch (Exception ex) { + System.err.println("ERRO: " + ex.getMessage()); + System.exit(2); + } + } + + private static void install(PluginStore store, String[] args) throws Exception { + if (args.length < 2) { + usage(); + System.exit(1); + } + Path dest = store.installFromFile(Path.of(args[1])); + System.out.println("OK install " + dest.getFileName()); + } + + private static void list(PluginStore store) throws Exception { + List refs = store.listInstalled(); + if (refs.isEmpty()) { + System.out.println("(vazio)"); + return; + } + for (InstalledPluginRef ref : refs) { + System.out.println(ref.getFileName() + "\t" + ref.getJarPath()); + } + } + + private static void remove(PluginStore store, String[] args) throws Exception { + if (args.length < 2) { + usage(); + System.exit(1); + } + Path jar = Path.of(args[1]); + store.remove(jar); + System.out.println("OK remove " + jar.getFileName()); + } + + private static void usage() { + System.err.println("Uso: PluginStoreCli install | list | remove "); + } +} diff --git a/plugins/ping/.gitignore b/plugins/ping/.gitignore index 5c3a65d..3a2f4f1 100644 --- a/plugins/ping/.gitignore +++ b/plugins/ping/.gitignore @@ -1,5 +1,7 @@ /dist/ /build/ /out/ -/nbproject/private/ +/nbproject/ +/src/ +/build.xml *.jar diff --git a/plugins/ping/README.md b/plugins/ping/README.md index 7395fda..558ce0c 100644 --- a/plugins/ping/README.md +++ b/plugins/ping/README.md @@ -1,6 +1,6 @@ # Plugin Ping (exercício de aula) -Material didático para o aluno **implementar** um plugin de Ping/Pong com medição de RTT. Este diretório no repositório oficial contém apenas planejamento e referências — **não** inclua código Java ou JAR da solução no commit. +Material didático para o aluno **implementar** um plugin de Ping/Pong com medição de RTT. No Git ficam só este README e o [tutorial](../../docs/documentation/17_tutorial_ping_aula.md). Código e `build.xml` locais estão no `.gitignore` (`src/`, `nbproject/`, `dist/`). ## Onde está o tutorial @@ -14,8 +14,6 @@ Tutorial canônico (passo a passo): [docs/documentation/17_tutorial_ping_aula.md 4. Instalar em `PITIUPI_HOME/plugins/` e reiniciar o Pitiupi. 5. Abrir o plugin pela aba **Plugins** na galeria (aba não abre sozinha no boot). -Quando o JAR estiver pronto para o time, registre uma entrada em [`plugins.registry.json`](../../plugins.registry.json) na raiz do clone (id `ping`, `dir`: `plugins/ping`, etc.). - ## Pré-requisitos - Núcleo compilado: `../../pitiupi/dist/pitiupi.jar`