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:
@@ -1565,3 +1565,36 @@ Gemessen (03.10.2026):
|
||||
je Verbindung einen eigenen `TCPSocket`. Javas Echo-Server verträgt keine
|
||||
sofort wieder geschlossene Verbindung (die folgenden laufen in die
|
||||
Zeitüberschreitung) — die Sonde hält sie eine halbe Sekunde.
|
||||
|
||||
### Gnutella (F4.6g4)
|
||||
|
||||
`GnutellaWindowRun` öffnet Javas `GUIApplicationPeerToPeerAnwendungWindow`
|
||||
auf dem ersten Rechner von `terminal-net`, neben der Kette aus
|
||||
`GnutellaRun`s Geschichte „search“ (.30 und 10.0.0.2 sind .31 beigetreten,
|
||||
.31 teilt `notizen.txt`, 10.0.0.2 „Lied der Woche.txt“), und benutzt es wie
|
||||
eine Person: Adresse (auch keine) und „Netz beitreten“, Suchbegriff mit
|
||||
„Suchen“ und „Stopp!“, ein Ergebnis gewählt und „Herunterladen“, „Liste
|
||||
leeren“, die Zahl der Teilnehmer mit „Speichern“ (auch keine Zahl), „Netz
|
||||
verlassen“. Javas Knöpfe hören auf `mousePressed`, die Felder prüfen bei
|
||||
`keyReleased` — die Sonde schickt beides. Je Schritt: was das Fenster zeigt
|
||||
(Adressfeld und ob es rot ist, die drei Tabellen, das Feld der Teilnehmer)
|
||||
und was das Programm hält (Zahl, Teilnehmer, Ergebnisse, Dateien von
|
||||
`/peer2peer`) (`packages/core/test/gnutellaWindow.java.json`).
|
||||
|
||||
Gemessen (03.10.2026):
|
||||
|
||||
* Das Adressfeld beginnt mit 192.168.0.1; keine Adresse wird rot, „Netz
|
||||
beitreten“ meldet dann „Dies ist keine gültige IP-Adresse“. Die eigene
|
||||
Adresse tut nichts. Das Feld der Teilnehmer zeigt die Zahl des Programms.
|
||||
* „Stopp!“ kurz nach „Suchen“: kein Treffer kommt an; eine neue Suche
|
||||
findet wieder. Ohne gewählte Zeile tut „Herunterladen“ nichts; ein zweites
|
||||
Herunterladen überschreibt die Datei.
|
||||
* „Netz verlassen“ leert die Nachbarn, nicht die Ergebnisse.
|
||||
* Javas Ungenauigkeiten:
|
||||
* Die Prüfung der Teilnehmerzahl (`musterNurZahl` = `\d`) nimmt nur eine
|
||||
Ziffer: 12 wird mit „Es sind nur Zahlen erlaubt!“ abgelehnt, 0 genommen.
|
||||
* Nach einer Suche ohne Treffer zeigt die Ergebnistabelle weiter die
|
||||
Ergebnisse der vorigen (das Programm hat keine mehr), bis etwas anderes
|
||||
das Fenster auffrischt.
|
||||
* Die Dateitabelle zeigt eine heruntergeladene Datei erst, wenn das
|
||||
Programm das nächste Mal Bescheid gibt; eine daneben angelegte gar nicht.
|
||||
|
||||
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
|
||||
fi
|
||||
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
|
||||
mkdir -p "$here/.build"
|
||||
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java" "$here/src/InstallRun.java" "$here/src/BrowserWindowRun.java" "$here/src/MailWindowRun.java" "$here/src/SaveAfterRunRun.java" "$here/src/TextEditorWindowRun.java" "$here/src/FileExplorerWindowRun.java" "$here/src/EchoWindowRun.java" "$here/src/DnsWindowRun.java" "$here/src/FirewallWindowRun.java"
|
||||
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java" "$here/src/InstallRun.java" "$here/src/BrowserWindowRun.java" "$here/src/MailWindowRun.java" "$here/src/SaveAfterRunRun.java" "$here/src/TextEditorWindowRun.java" "$here/src/FileExplorerWindowRun.java" "$here/src/EchoWindowRun.java" "$here/src/DnsWindowRun.java" "$here/src/FirewallWindowRun.java" "$here/src/GnutellaWindowRun.java"
|
||||
# FILIUS writes its settings below $HOME; keep them out of the real one.
|
||||
mkdir -p "$here/.build/home"
|
||||
# Removed first: a crashed run must not leave the previous result standing.
|
||||
@@ -453,6 +453,17 @@ HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -
|
||||
"$root/packages/core/test/scenarios/firewall-pc.scenario.json" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at ' || true
|
||||
test -s "$output"
|
||||
|
||||
# FILIUS' Gnutella window used as a person does — joining (also with what is
|
||||
# no address), searching, "Stopp!", downloading, emptying the list, the number
|
||||
# of peers (also what is no number), leaving — beside the line of
|
||||
# GnutellaRun's story "search" (GnutellaWindowRun): the Gnutella window
|
||||
# (packages/core/test/gnutellaWindow.test.ts).
|
||||
output="$root/packages/core/test/gnutellaWindow.java.json"
|
||||
rm -f "$output"
|
||||
HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -cp "$classpath:$here/.build" GnutellaWindowRun \
|
||||
"$root/packages/core/test/scenarios/terminal-net.scenario.json" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at \|scrollpane' || true
|
||||
test -s "$output"
|
||||
|
||||
# FILIUS' own examples as Java FILIUS runs them, opened and started as FILIUS
|
||||
# does (ExampleRun): the acceptance of F2 (packages/fls/test/examplesJava.test.ts)
|
||||
# and the classics of F3 (web, email, gnutella; packages/fls/test/classicsJava.test.ts).
|
||||
|
||||
@@ -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