feat: F4.6g4 the Gnutella window, measured against Java's
GnutellaWindowRun uses Java's Gnutella window on terminal-net beside the line of GnutellaRun's story "search". The core cancels a search as Java does (every connection of the program's own requests closed, passed-on queries kept), lets the number of peers change and lists /peer2peer's files; the room takes the window's actions and sends its view; the editor shows Java's four tabs with Java's symbols. Not adopted from Java: a number of peers of one digit only, a results table that keeps the last search's rows, a files table that lags behind the folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
// Uses FILIUS' own Gnutella window (GUIApplicationPeerToPeerAnwendungWindow
|
||||
// on a GUIDesktopPanel) on terminal-net's first computer as a person does:
|
||||
// the address field (also with what is no address) and "Netz beitreten",
|
||||
// the search field with "Suchen", "Stopp!", a result chosen and
|
||||
// "Herunterladen", "Liste leeren", the field of the number of peers with
|
||||
// "Speichern" (also with what is no number), "Netz verlassen". The other
|
||||
// computers form the line of GnutellaRun's story "search" through their
|
||||
// programs. Java's message dialogs are answered and written down. After each
|
||||
// step it writes what the window shows — the address field (and whether it
|
||||
// is red), the three tables, the field of the number of peers — and what the
|
||||
// program keeps: the number, its peers, its results, the files of
|
||||
// /peer2peer. The reference for the Gnutella window
|
||||
// (packages/core/test/gnutellaWindow.test.ts).
|
||||
//
|
||||
// Usage: java -cp filius.jar:lib/*:classes GnutellaWindowRun <scenario.json> <output json>
|
||||
|
||||
import filius.gui.anwendungssicht.GUIApplicationPeerToPeerAnwendungWindow;
|
||||
import filius.gui.anwendungssicht.GUIDesktopPanel;
|
||||
import filius.rahmenprogramm.EingabenUeberpruefung;
|
||||
import filius.software.dateiaustausch.PeerToPeerAnwendung;
|
||||
import filius.software.system.Betriebssystem;
|
||||
import filius.software.system.Datei;
|
||||
import filius.software.system.Dateisystem;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.io.FileOutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
public class GnutellaWindowRun {
|
||||
static final String CLIENT = "00000000-0000-0000-0000-000000000401";
|
||||
static final String WEB = "00000000-0000-0000-0000-000000000403";
|
||||
static final String ECHO = "00000000-0000-0000-0000-000000000404";
|
||||
static final String FAR = "00000000-0000-0000-0000-000000000406";
|
||||
static final String[] PEERS = { CLIENT, WEB, ECHO, FAR };
|
||||
static final String CLASS = "filius.software.dateiaustausch.PeerToPeerAnwendung";
|
||||
|
||||
static final List<Object> steps = new ArrayList<>();
|
||||
static final List<String> dialogs = new ArrayList<>();
|
||||
static GUIApplicationPeerToPeerAnwendungWindow window;
|
||||
static PeerToPeerAnwendung program;
|
||||
|
||||
static Object field(String name) throws Exception {
|
||||
Field field = GUIApplicationPeerToPeerAnwendungWindow.class.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return field.get(window);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> T find(Container container, Class<T> kind) {
|
||||
for (Component component : container.getComponents()) {
|
||||
if (kind.isInstance(component)) return (T) component;
|
||||
if (component instanceof Container) {
|
||||
T found = find((Container) component, kind);
|
||||
if (found != null) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static void watchDialogs() {
|
||||
Thread watcher = new Thread(() -> {
|
||||
while (true) {
|
||||
for (Window each : Window.getWindows()) {
|
||||
if (!(each instanceof JDialog) || !each.isVisible()) continue;
|
||||
JOptionPane pane = find((Container) each, JOptionPane.class);
|
||||
if (pane == null) continue;
|
||||
synchronized (dialogs) {
|
||||
dialogs.add(String.valueOf(pane.getMessage()));
|
||||
}
|
||||
pane.setValue(JOptionPane.OK_OPTION);
|
||||
each.setVisible(false);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
watcher.setDaemon(true);
|
||||
watcher.start();
|
||||
}
|
||||
|
||||
static void onEdt(Runnable action) throws Exception {
|
||||
SwingUtilities.invokeLater(action);
|
||||
Thread.sleep(500);
|
||||
}
|
||||
|
||||
static List<List<String>> rows(JTable table) {
|
||||
List<List<String>> rows = new ArrayList<>();
|
||||
TableModel model = table.getModel();
|
||||
for (int row = 0; row < model.getRowCount(); row++) {
|
||||
List<String> cells = new ArrayList<>();
|
||||
for (int column = 0; column < model.getColumnCount(); column++) cells.add(String.valueOf(model.getValueAt(row, column)));
|
||||
rows.add(cells);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
static Map<String, Object> textField(String name) throws Exception {
|
||||
JTextField each = (JTextField) field(name);
|
||||
Map<String, Object> shown = new LinkedHashMap<>();
|
||||
shown.put("text", each.getText());
|
||||
shown.put("red", each.getForeground().equals(EingabenUeberpruefung.farbeFalsch));
|
||||
return shown;
|
||||
}
|
||||
|
||||
static void step(String name) throws Exception {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("step", name);
|
||||
result.put("address", textField("networkIpField"));
|
||||
result.put("peers", rows((JTable) field("netzwerkTabelle")));
|
||||
result.put("search", ((JTextField) field("searchField")).getText());
|
||||
result.put("results", rows((JTable) field("ergebnisTabelle")));
|
||||
result.put("files", rows((JTable) field("dateiTabelle")));
|
||||
result.put("maximum", textField("maxClientsField"));
|
||||
result.put("programMaximum", program.getMaxTeilnehmerZahl());
|
||||
result.put("programPeers", new ArrayList<>(program.holeBekanntePeerToPeerTeilnehmer()));
|
||||
result.put("programResults", new ArrayList<>(program.holeErgebnisse()));
|
||||
List<List<String>> folder = new ArrayList<>();
|
||||
for (Datei each : program.getSystemSoftware().getDateisystem().holeDateien(program.holeVerzeichnis())) {
|
||||
List<String> file = new ArrayList<>();
|
||||
file.add(each.getName());
|
||||
file.add(each.getDateiTyp());
|
||||
file.add(each.getDateiInhalt());
|
||||
folder.add(file);
|
||||
}
|
||||
result.put("programFiles", folder);
|
||||
synchronized (dialogs) {
|
||||
result.put("dialogs", new ArrayList<>(dialogs));
|
||||
dialogs.clear();
|
||||
}
|
||||
steps.add(result);
|
||||
}
|
||||
|
||||
/** Typed as a person types: the text, then a key released (the window checks the field then). */
|
||||
static void type(String name, String text) throws Exception {
|
||||
JTextField each = (JTextField) field(name);
|
||||
onEdt(() -> {
|
||||
each.setText(text);
|
||||
KeyEvent event = new KeyEvent(each, KeyEvent.KEY_RELEASED, System.currentTimeMillis(), 0, KeyEvent.VK_A, 'a');
|
||||
for (KeyListener listener : each.getKeyListeners()) listener.keyReleased(event);
|
||||
});
|
||||
}
|
||||
|
||||
/** Java's buttons here listen for the mouse pressed, not an action. */
|
||||
static void press(String text, long wait) throws Exception {
|
||||
List<JButton> found = new ArrayList<>();
|
||||
collect(window, text, found);
|
||||
JButton button = found.get(0);
|
||||
onEdt(() -> {
|
||||
MouseEvent event = new MouseEvent(button, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), 0, 5, 5, 1, false);
|
||||
for (MouseListener listener : button.getMouseListeners()) listener.mousePressed(event);
|
||||
});
|
||||
Thread.sleep(wait);
|
||||
}
|
||||
|
||||
static void collect(Container container, String text, List<JButton> found) {
|
||||
for (Component component : container.getComponents()) {
|
||||
if (component instanceof JButton && text.equals(((JButton) component).getText())) found.add((JButton) component);
|
||||
if (component instanceof Container) collect((Container) component, text, found);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
try {
|
||||
run(args);
|
||||
} catch (Throwable error) {
|
||||
error.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
static void run(String[] args) throws Exception {
|
||||
ScenarioNetwork network = ScenarioNetwork.read(args[0]);
|
||||
Map<String, PeerToPeerAnwendung> programs = new LinkedHashMap<>();
|
||||
for (String host : PEERS) {
|
||||
network.system(host).installApp(CLASS, false);
|
||||
PeerToPeerAnwendung each = (PeerToPeerAnwendung) network.system(host).holeSoftware(CLASS);
|
||||
each.setMaxTeilnehmerZahl(host.equals(CLIENT) || host.equals(FAR) ? 1 : 2);
|
||||
programs.put(host, each);
|
||||
}
|
||||
program = programs.get(CLIENT);
|
||||
network.system(ECHO).getDateisystem().speicherDatei("/peer2peer", new Datei("notizen.txt", "txt", "Notiz"));
|
||||
network.system(FAR).getDateisystem().speicherDatei("/peer2peer", new Datei("Lied der Woche.txt", "txt", "La la la"));
|
||||
network.start();
|
||||
Thread.sleep(500);
|
||||
watchDialogs();
|
||||
// The line .20 - .30 - .31 - 10.0.0.2: the others join first, as in GnutellaRun's story.
|
||||
programs.get(WEB).beitretenNetzwerk("192.168.0.31");
|
||||
Thread.sleep(2500);
|
||||
programs.get(FAR).beitretenNetzwerk("192.168.0.31");
|
||||
Thread.sleep(2500);
|
||||
|
||||
JFrame frame = new JFrame();
|
||||
GUIDesktopPanel desktop = new GUIDesktopPanel((Betriebssystem) network.system(CLIENT));
|
||||
frame.getContentPane().add(desktop);
|
||||
window = (GUIApplicationPeerToPeerAnwendungWindow) desktop.starteAnwendung(CLASS);
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
});
|
||||
Thread.sleep(500);
|
||||
step("open");
|
||||
|
||||
type("networkIpField", "192.168.0.300");
|
||||
step("type an address that is none");
|
||||
press("Netz beitreten", 1000);
|
||||
step("join with an address that is none");
|
||||
type("networkIpField", "192.168.0.20");
|
||||
press("Netz beitreten", 2500);
|
||||
step("join itself");
|
||||
type("networkIpField", "192.168.0.30");
|
||||
press("Netz beitreten", 3000);
|
||||
step("join 192.168.0.30");
|
||||
|
||||
type("searchField", "woche");
|
||||
press("Suchen", 3000);
|
||||
step("search woche");
|
||||
JTable results = (JTable) field("ergebnisTabelle");
|
||||
onEdt(() -> results.setRowSelectionInterval(0, 0));
|
||||
press("Herunterladen", 3000);
|
||||
step("download row 0");
|
||||
press("Herunterladen", 3000);
|
||||
step("download row 0 again");
|
||||
type("searchField", "xyz");
|
||||
press("Suchen", 3000);
|
||||
step("search xyz");
|
||||
press("Liste leeren", 500);
|
||||
step("empty the list");
|
||||
type("searchField", "notiz");
|
||||
press("Suchen", 0);
|
||||
press("Stopp!", 3000);
|
||||
step("search notiz and abort at once");
|
||||
press("Suchen", 3000);
|
||||
step("search notiz again");
|
||||
onEdt(() -> results.clearSelection());
|
||||
press("Herunterladen", 2000);
|
||||
step("download with no row chosen");
|
||||
|
||||
// Another tab, as a person looks at the files: Java fills its tables anew.
|
||||
JTabbedPane tabs = find(window, JTabbedPane.class);
|
||||
onEdt(() -> tabs.setSelectedIndex(2));
|
||||
step("files tab");
|
||||
|
||||
type("maxClientsField", "abc");
|
||||
step("type a number of peers that is none");
|
||||
press("Speichern", 500);
|
||||
step("take a number of peers that is none");
|
||||
type("maxClientsField", "0");
|
||||
press("Speichern", 500);
|
||||
step("take 0 peers");
|
||||
type("maxClientsField", "99999999999");
|
||||
press("Speichern", 500);
|
||||
step("take a number beyond Java's int");
|
||||
type("maxClientsField", "12");
|
||||
press("Speichern", 500);
|
||||
step("take 12 peers");
|
||||
type("maxClientsField", "4");
|
||||
press("Speichern", 500);
|
||||
step("take 4 peers");
|
||||
|
||||
press("Netz verlassen", 1000);
|
||||
step("leave the network");
|
||||
// A file added to the folder beside the window: the table only shows it when it is filled anew.
|
||||
Dateisystem files = network.system(CLIENT).getDateisystem();
|
||||
onEdt(() -> files.speicherDatei("/peer2peer", new Datei("neu.txt", "txt", "neu")));
|
||||
step("a file added beside the window");
|
||||
|
||||
Map<String, Object> output = new LinkedHashMap<>();
|
||||
output.put("steps", steps);
|
||||
try (FileOutputStream out = new FileOutputStream(args[1])) {
|
||||
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
network.stop();
|
||||
System.out.println("gnutella window steps: " + steps.size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user