From 3d6655e73643bb56d9fd0ec5347779459ef07cc8 Mon Sep 17 00:00:00 2001 From: Julian Kiedaisch Date: Sat, 3 Oct 2026 17:05:11 +0000 Subject: [PATCH] feat(sim): F4.6f1 what a run leaves goes back into the document The oracle SaveAfterRunRun saves Java's project after a run. The room writes what a run leaves that Java keeps - files, running servers, virtual hosts, mail servers' domains and accounts, mail programs' accounts and folders, firewalls, DHCP clients' addresses - into the document as origin 'sim', at most once a second, after each action and at the stop; writeRunState writes only what differs. The command line now goes on in a folder made before a restart, as Java's does. Co-Authored-By: Claude Opus 5.5 --- CHANGES.md | 5 + packages/core/src/model/virtualFileSystem.ts | 10 + packages/core/test/virtualFileSystem.test.ts | 21 ++ packages/doc/src/index.ts | 1 + packages/doc/src/simulation.ts | 122 +++++++++ packages/editor/browser/simulation.spec.ts | 31 +++ packages/fls/src/projectSnapshot.ts | 15 +- packages/sim/src/room.ts | 71 ++++- packages/sim/test/fixtures/after-run-dhcp.fls | Bin 0 -> 109331 bytes .../sim/test/fixtures/after-run-email.fls | Bin 0 -> 11193 bytes packages/sim/test/terminalWindow.test.ts | 21 +- packages/sim/test/writeBack.test.ts | 242 ++++++++++++++++++ tools/java-oracle/README.md | 25 ++ tools/java-oracle/run.sh | 14 +- tools/java-oracle/src/SaveAfterRunRun.java | 149 +++++++++++ 15 files changed, 705 insertions(+), 22 deletions(-) create mode 100644 packages/doc/src/simulation.ts create mode 100644 packages/sim/test/fixtures/after-run-dhcp.fls create mode 100644 packages/sim/test/fixtures/after-run-email.fls create mode 100644 packages/sim/test/writeBack.test.ts create mode 100644 tools/java-oracle/src/SaveAfterRunRun.java diff --git a/CHANGES.md b/CHANGES.md index 7cce994..ee45df3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand | `src/runtime/engine.ts` (Installation), `src/runtime/apps/installation.ts`, `test/installation.test.ts` | Java `InternetKnotenBetriebssystem.installAndStartAppIfAvailable/entferneSoftware`, `WebServer.erzeugeStandardVerzeichnis`, `PeerToPeerAnwendung`/`EmailServer`/`DNSServer.setSystemSoftware/starten`, `tmpl/webserver_index_*`, `gfx/allgemein/splashscreen-mini.png` | neu (F4.6c): was ein Programm bei der Installation in die Dateien legt (Javas Startseite je Sprache, Bild, `/peer2peer`, `/mailserver/konten.txt`, `/dns/hosts`); `installProgram`/`removeProgram` im laufenden Betrieb — Einstellungen und fehlende Dateien aus dem Schnappschuss des Dokuments, Programm sofort gestartet; entfernt wird ein Server abgeschaltet, die Dateien bleiben. Die Programmerzeugung beim Start läuft über dieselbe `startProgram` in der bisherigen Reihenfolge | | `src/runtime/apps/web.ts` (F4.6d), `test/web.test.ts` | Java `WebBrowser.retrieveImages`, `GUIApplicationWebBrowserWindow.hyperLinkListener`, `WebServer.changeVHostTable/saveVHosts/setUseVHost` (gemessen mit `BrowserWindowRun`) | erweitert (F4.6d): die Zeile einer Seite nennt Host und Pfad, die eines Bildes trägt seine Daten (für das Fenster); `pageReference` und `linkTarget` — relative Bilder und Links zählen vom Ordner der Seite (Java vom Wurzelordner des Servers; für eine Seite im Wurzelordner sind die Anfragen Javas), eine Marke (`#…`) und andere Schemata führen nirgends hin; der Webserver schaltet seine virtuellen Hosts um und schreibt ihre Tabelle, nur mit vollständigen Zeilen (Java schreibt jede Zeile, und beim Lesen rückt eine Zelle einer unvollständigen in die Zeile davor) | | `src/runtime/apps/emailClient.ts`, `src/runtime/apps/emailServer.ts`, `src/runtime/emailAccountsFile.ts`, `src/runtime/engine.ts` (F4.6e) | Java `GUIApplicationEmailAnwendungWindow.kontoSpeichern/emailLoeschen`, `EmailAnwendung.setzeKonto/speichern/removeReceivedMail/removeSentMail`, `GUIApplicationEmailServerWindow`, `EmailServer.setMailDomain/benutzerHinzufuegen/kontoLoeschen` (gemessen mit `MailWindowRun`) | erweitert (F4.6e): das E-Mail-Programm setzt sein Konto (`accountOfForm` mit Javas Mustern für Ports und Benutzernamen, `splitName` wie Javas Dialog: letztes Wort Nachname) und schreibt es nach `/konten.txt` (`clientAccountFileText`, jetzt im Kern statt im Export), löscht eine Mail eines Ordners, prüft ein Formular wie Javas Fenster (`mailFormProblem`) und sendet mit dem Konto, mit dem die Mail geschrieben wurde; der Server setzt seine Domain (nicht leer, Javas Muster), legt ein Konto an (einen vorhandenen Namen in jeder Schreibweise nicht — Javas Fenster meldet es als angelegt) und entfernt eines, sein Startknopf schreibt die eigene Zeile des Fensters; die neue Domain steht sofort in Schnappschuss und Kontendatei (Java erst bei der nächsten Kontenänderung) | +| `src/model/virtualFileSystem.ts` (`revision`) | — | erweitert (F4.6f1): ein Zähler, der sich mit jeder Änderung ändert (auch einem Löschen), damit der Raum ein Dateisystem nur zurückschreibt, wenn es sich geändert hat | | `src/runtime/threads.ts` | Javas Threads der Programme (`Anwendung extends Thread`, `ServerMitarbeiter`) | neu (F3.1): ein Programm-Thread als Generator, der liefert, worauf er wartet (Bedingung, Zeit oder beides); die Engine setzt nach jedem Ereignis fort, wessen Warten vorbei ist, in der Reihenfolge, in der die Threads begannen; nichts blockiert die Engine. Abweichend von Java: ein Thread wacht sofort, wenn seine Bedingung gilt (Java sieht oft nur alle 100 ms nach) | | `src/runtime/apps/program.ts` | Java `filius.software.Anwendung` | neu (F3.1): die Zeilen im Fenster eines Programms (strukturiert, damit der Editor sie übersetzt) und der Befehls-Thread (`ausfuehren`) | | `src/runtime/apps/server.ts` | Java `ServerAnwendung`, `ServerMitarbeiter` | neu (F3.2a, aus `echo.ts` herausgelöst): was jeder Server teilt — ein Thread nimmt Verbindungen an, solange er eingeschaltet ist, je Verbindung einer beantwortet jede Nachricht; ausgeschaltet schließt er Socket und Verbindungen; Port belegt → er schaltet sich aus; seit F3.3a kann ein Server das Gespräch einer Verbindung selbst führen (`converse`: Begrüßung, keine oder mehrere Antworten, Schließen) | @@ -99,6 +100,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand | `src/javaBeansWriter.ts` | — | neu: Schreiber für das `XMLDecoder`-Format, Gegenstück zu `javabeans.ts`; ausdrücklich typisierte Eingabe, geteilte Objekte über `id`/`idref`, was XML nicht fassen kann (und CR) als ``; von Java selbst geprüft (`BeansDump`) | | `test/javaBeansWriter.test.ts`, `test/beans/` | — | neu: 11 Dateien des Schreibers, von Javas `XMLDecoder` gelesen und beschrieben; Erwartungen von Hand | | `src/projectSnapshot.ts` | Swift `TopologyNetworkRuntimeTopologySnapshot(editorState:)` | neu (Abnahme F2): das importierte Projekt als Schnappschuss des Kerns; nur die Kabel verbinden (Swifts Verbindung über die SSID nicht übernommen), nur eingeschaltete DNS-Server laufen; seit F3.1 jeder installierte Echo-Server (ein oder aus, er lässt sich zur Laufzeit schalten) und einfache Client; seit F3.2a jeder Webserver und Browser und die Dateisysteme; seit F3.2c der Rückweg `projectWithSimulationChanges`: die Firewalls aus dem Schnappschuss des Laufs ins Projekt, wie Java sie beim Speichern hat; seit F3.3a die installierten E-Mail-Server (ein oder aus) und auf dem Rückweg ihre Postfächer; seit F3.3b die installierten E-Mail-Programme und auf dem Rückweg ihre Ordner; seit F3.4a die Knoten mit der Befehlszeile (ihre Dateien sind die des Projekts) | +| `src/projectSnapshot.ts` (`emailServerAfterRun`) | Java `EmailServer` (gespeichert nach einem Lauf, gemessen mit `SaveAfterRunRun`) | erweitert (F4.6f1): der Rückweg nimmt auch die Domain eines E-Mail-Servers mit; als eigene Funktion für den Rückweg des Raums ins Dokument | | `test/examplesJava.test.ts`, `test/examples/*.java.json` | — | neu (Abnahme F2): fünf Beispiele von Filius (`internetwork_komplett`, `einfaches_rechnernetz_komplett`, `dns_server`, `nat_with_mapped_port`, `Internet_Komplett_…_DE`) mit dem eigenen Leser eingelesen und im Kern gespielt, gegen Java-Filius (`ExampleRun`): je Karte Frames, Zeiten und Tabellenzeilen, die Aufrufe, die Adressen am Ende; gleichzeitig startende DHCP-Clients je Client; zwei benannte Java-Fehler (`JAVA_BUGS`: Ping ohne Gateway wirft eine `NullPointerException`; NAT-Eintrag nur für die erste Gegenstelle) | | `test/classicsJava.test.ts`, `test/exampleRun.ts`, `test/examples/{web,email,gnutella}.java.json` | — | neu (Abnahme F3): die Unterrichtsklassiker auf Beispielen von Filius gegen Java-Filius (`ExampleRun`): Webseite über ihren DNS-Namen (`internetwork_komplett`), E-Mail zwischen zwei Netzen durch NAT und Weitergabe von Server zu Server (`Internet_Komplett_…_DE`), Datei per Gnutella über einen Router finden und holen, Ping, Pong und Query weitergereicht (`zwei_rechnernetze_komplett`, Programme vom Test eingesetzt); je Karte Frames, Zeiten und Tabellenzeilen (ohne RIP), die Zeilen jedes Programms, Mails, Teilnehmer, Ergebnisse und Dateien am Ende; Javas Fehler aus F3.6b und `closedPort` benannt (`JAVA_BUGS`); gleich lange RIP-Wege nach Javas Wahl (`equalRipTies`). `exampleRun.ts`: was beide Beispiel-Tests teilen (Datei öffnen, Javas Namen, Karten nach MAC) | | `src/importProject.ts` (`modemPair`, `modemPort`) | — | zusammengelegt (F4.5a): das Modem-Paar eines Ports an einer Stelle, für Import, Export und das Geräte-Blatt | @@ -136,6 +138,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand | `src/view.ts` (`nodeLabel`) | Java `Knoten.holeAnzeigeName` | herausgelöst (F4.5b): die Beschriftung eines Geräts, auch für „Verbunden mit …“ | | `src/view.ts` (`desktopView`) | Java `GUIDesktopWindow.updateTitle`, `GUIDesktopPanel.updateAppPane` | neu (F4.6a): was der Desktop eines Rechners vom Dokument braucht — Art, Beschriftung, ob sie die Adresse ist (Javas Titel lässt die Adresse dann weg), installierte Programme | | `src/software.ts`, `test/install.test.ts`, `test/fixtures/installed-programs.fls` | Java `GUIInstallationsDialog.aenderungenSpeichern` (gemessen mit `InstallRun`), `config/Desktop_*.txt` | neu (F4.6c): `setSoftware` — installieren (aus, Javas Voreinstellungen, fehlende Dateien; DNS-Einträge, E-Mail-Konten und das Konto des E-Mail-Programms aus vorhandenen Dateien) und entfernen (mit Einstellungen, Dateien bleiben) in einem Schritt, in der Reihenfolge von Javas Liste der Sprache (`SOFTWARE_ORDER`). Gegen Javas gespeicherte Installation geprüft; benannt: Javas Gnutella würfelt 3–5 Teilnehmer, hier 3 wie in einer Datei ohne Angabe | +| `src/simulation.ts` | Plan §6.1 (`origin: 'sim'`); Java `SzenarioVerwaltung.speichern` nach einem Lauf (gemessen mit `SaveAfterRunRun`) | neu (F4.6f1): `writeRunState` schreibt, was ein Lauf von jedem Knoten hinterlässt, und nur, was sich unterscheidet — Dateien (neue hinter die anderen, geänderte an ihrem Platz, ein Text nur an der geänderten Stelle, verschwundene entfernt), ob Programme laufen, Einstellungen, die der Knoten hat (Feldreihenfolge egal); zweimal geschrieben ändert es nichts; `SIM_ORIGIN` landet in keinem Rückgängig-Stapel | | `test/sheet.test.ts`, `test/sheet.cases.txt`, `test/sheet.java.json` | — | neu (F4.5a): 77 in Javas Felder getippte Texte, jede Regel dagegen samt der benannten Abweichungen; die Befehle, die Funkverbindungen, Rückgängig je Änderung, alles über eine gespeicherte `.fls` | | `test/devices.test.ts`, `test/fixtures/new-devices.fls` | — | neu (F4.4a): neue Geräte jeder Art gegen Javas gespeicherte (Zufall maskiert), Kabelregeln, ein selbst gebautes Netz pingt im Kern, Rückgängig nur der eigenen Änderungen über zwei Replikate | | `test/projectDoc.test.ts` | — | neu (F4.1): alle 23 Korpusdateien → Dokument → Yjs-Update → Projekt: dasselbe Projekt, derselbe Schnappschuss, dieselbe `.fls`, dieselbe Reihenfolge je Ordner; Kennungen absichtlich gegen die Dateireihenfolge | @@ -165,6 +168,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand | `src/room.ts` (Installation) | Java `GUIInstallationsDialog` (Programme starten sofort) | erweitert (F4.6c): im laufenden Betrieb beobachtet der Raum die installierten Programme des Dokuments und installiert oder entfernt sie in der Engine; eine neue Befehlszeile bekommt ihr Fenster | | `src/browserWindow.ts`, `src/room.ts` (Browser, Webserver), `test/browserWindow.test.ts`, `test/browserWindow.java.json` | Java `GUIApplicationWebBrowserWindow`, `GUIApplicationWebServerWindow` (gemessen mit `BrowserWindowRun`) | neu (F4.6d): das Browserfenster im Raum, eines je Rechner für alle, über Stopps behalten — Adresse öffnen (keine Adresse: die Zeile zurück auf „http://“), Links und Formulare der gezeigten Seite folgen, was das Programm bekam, in den Bildschirmstand; der Raum schickt Bildschirmstände (Browser statt Zeilen, Webserver zu seinen Zeilen), nimmt die Aktionen `open`/`follow` und `setActive`/`useVirtualHosts`/`setVirtualHost` an und startet in der Sprache der Person. Gegen Javas Fenster geprüft; benannt: Javas Fehlerseite findet ihre Vorlage nie, Bilder und Links einer Seite im Ordner, eine Marke lädt in Java die Startseite, die Datei der virtuellen Hosts | | `src/room.ts` (E-Mail), `test/mailWindow.test.ts`, `test/mailWindow.java.json` | Java `GUIApplicationEmailAnwendungWindow`, `GUIApplicationEmailServerWindow` (gemessen mit `MailWindowRun`) | erweitert (F4.6e): der Raum schickt die Bildschirmstände von E-Mail-Programm und -Server zu ihren Zeilen und nimmt `setAccount`/`send`/`fetch`/`removeMail` und `setActive`/`setDomain` (nur bei angehaltenem Server)/`addAccount`/`removeAccount` an. Gegen Javas Fenster geprüft; benannt: ein vorhandener Benutzername, Löschen im falschen Ordner, die Domain in der Kontendatei | +| `src/room.ts` (Rückweg), `test/writeBack.test.ts`, `test/fixtures/after-run-*.fls`, `test/terminalWindow.test.ts` | Java `GUIMainMenu.stopSimulation`, `SzenarioVerwaltung.speichern` (gemessen mit `SaveAfterRunRun`) | erweitert (F4.6f1): der Raum schreibt, was ein Lauf hinterlässt (Dateien, laufende Server, virtuelle Hosts, Domain und Konten der E-Mail-Server, Konto und Ordner der E-Mail-Programme, Firewalls, die Adresse eines DHCP-Clients), als `origin: 'sim'` ins Dokument: höchstens einmal je Sekunde (ab dem Start gezählt), nach jeder Aktion in einem Fenster und beim Stopp. Gegen Javas nach dem Lauf gespeichertes Projekt geprüft; die Befehlszeile geht nach einem Neustart im vorher angelegten Ordner weiter wie Javas (die benannte Abweichung aus F4.6b ist weg) | | `browser/m9.spec.ts`, `browser/traceDigest.ts`, `browser/*Entry.ts`, `/playwright.config.ts` | Plan §13 M9 | neu (F4.2): M9 in Chromium — 17 Beispiele, je 30 s mit Eingaben, ergeben Zeichen für Zeichen denselben Mitschnitt wie in Node; der Raum in einem echten Worker | ## packages/editor @@ -198,6 +202,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand | `browser/simulation.spec.ts`, `test/programs.test.ts` | — | neu (F4.6a): Moduswechsel, Sperren, Geschwindigkeit, Kabelaktivität, Desktop mit Titel, Programmen, Netzwerk-Info, Verschieben und Stapeln, Handy, Englisch; die Programmliste gegen Javas `Desktop_*.txt`; seit F4.6b die Befehlszeile (Ping über drei Router, Verlauf, Chips, Strg+C, Behalten, exit) und der Fehlerfall des Raums; seit F4.6c die Software-Installation (sofort lauffähig, Doppelklick, Abwählen) | | `browser/web.spec.ts`, `browser/fixtures/web-pages.fls`, `test/webPagesFixture.test.ts`, `test/serverLines.test.ts`, `src/simulation/useSimulation.ts` (`views`), `src/Editor.tsx` (Sprache beim Start), `src/desktop/DesktopWindow.tsx` | — | neu (F4.6d): Browser und Webserver in Chromium auf einer eigenen Projektdatei (mit den Befehlen des Dokuments gebaut und bei jedem Lauf verglichen): Adresszeile, Titel, Sandbox, Bilder, Marke, Links im Ordner, beide Formulare bis in die Zeilen des Servers, Fehlerseite, nicht erreichbarer Server, Knopf, Kästchen und Tabelle, die Routerseiten auf Englisch; die Zeilen des Servers gegen Javas Fenster | | `browser/mail.spec.ts`, `test/mailLines.test.ts` | — | neu (F4.6e): E-Mail in Chromium auf Javas Beispiel `email_komplett` — Prüfungen und Dialoge, Senden, Abrufen, Antworten, Löschen im gewählten Ordner, Konto-Dialog, ein Fehler als Dialog; Konten anlegen und entfernen, Domain nur angehalten; die Zeilen des Server-Fensters gegen Javas Fenster | +| `browser/simulation.spec.ts` (Rückweg) | — | erweitert (F4.6f1): ein in der Befehlszeile angelegter Ordner ist nach Entwurf und Neustart da, ein entfernter weg | | `src/sheet/ServicePanels.tsx`, `src/sheet/RowsEditor.tsx`, `src/sheet/SheetTabs.tsx`, `src/sheet/DeviceSheet.tsx` (Seiten) | Java `JDHCPKonfiguration` (`jdhcpkonfiguration_msg1`–`15`), `JPortForwardingDialog` (`jportforwarding_msg4`–`7`, `jgatewayconfiguration_msg19`) | neu (F4.5c): Javas Dialoge als Seiten des Blatts mit „Zurück“ — DHCP-Server (Grundeinstellungen, statische Adresszuweisung; beim Rechner, gesperrt während DHCP die Karte einrichtet, und auf dem LAN-Reiter des Heimrouters), Portfreigaben (Heimrouter); jedes Feld beim Verlassen übernommen statt mit Javas OK-Knopf. Die Zeilentabellen (Weiterleitung, Zuordnungen, Portfreigaben) teilen sich `RowsEditor`: eine neue Zeile, sobald alle Felder gültig sind, Zeile wählen, bearbeiten, löschen. Javas Tooltip zu „Manuelle Einstellungen“ als Text unter dem Kästchen (ein Finger hat kein Überfahren) | | `src/sheet/RouterSheets.tsx`, `src/sheet/NameField.tsx`, `src/canvas/Canvas.tsx` (`highlightedLink`), `src/theme/tokens.css` (`--canvas-cable-active`) | Java `JVermittlungsrechnerKonfiguration`, `JGatewayConfiguration`, `JWeiterleitungsTabelle` (Wörter `jvermittlungsrechnerkonfiguration_msg6`–`26`, `jweiterleitungstabelle_msg3`–`6`), `JCablePanel.farbeBlinken`, `Kabel.setAktiv` | neu (F4.5b): Blatt des Vermittlungsrechners — Reiter Allgemein (Name, Gateway, Automatisches Routing, IP-Weiterleitung nur angezeigt), je Karte (Reiter mit ihrer Adresse, „Verbunden mit …“, Adresse, Maske, MAC) und Weiterleitungstabelle (alle Einträge oder nur die manuellen, Javas Zeilen grau, eine neue Zeile, die mit vier gültigen Feldern übernommen wird, Zelle bearbeiten, Eintrag löschen; bei RIP gesperrt); Blatt des Heimrouters mit Allgemein, LAN, WAN (DHCP sperrt die WAN-Adresse). Das Kabel der offenen Karte leuchtet grün wie bei Java. Javas „Eintrag Löschen“ klein geschrieben | diff --git a/packages/core/src/model/virtualFileSystem.ts b/packages/core/src/model/virtualFileSystem.ts index 897b874..936f524 100644 --- a/packages/core/src/model/virtualFileSystem.ts +++ b/packages/core/src/model/virtualFileSystem.ts @@ -213,11 +213,17 @@ export function fileContentOfJavaType(rawType: string, content: string, path: st export class VirtualFileSystem { private store: Store; + private revisionValue = 0; constructor() { this.store = new Map([[keyOf('/'), { path: '/', content: { kind: 'directory' } }]]); } + /** Counts the changes: different after each one (the simulation room writes a file system back when it changed). */ + get revision(): number { + return this.revisionValue; + } + /** `init(entries:)`: shallow entries first; each parent must exist. */ static fromEntries(entries: readonly VirtualFileEntry[]): VirtualFileSystem { const fileSystem = new VirtualFileSystem(); @@ -460,10 +466,12 @@ export class VirtualFileSystem { const key = keyOf(path); const existing = this.store.get(key); this.store.set(key, { path: existing?.path ?? path, content }); + this.revisionValue += 1; } private delete(path: string): void { this.store.delete(keyOf(path)); + this.revisionValue += 1; } private transaction(change: (candidate: VirtualFileSystem) => void): void { @@ -471,6 +479,7 @@ export class VirtualFileSystem { change(candidate); candidate.validateQuotas(); this.store = candidate.store; + this.revisionValue += 1; } private persistedEntryCount(): number { @@ -538,6 +547,7 @@ export class VirtualFileSystem { candidate.set(destination, content); candidate.validateQuotas(); this.store = candidate.store; + this.revisionValue += 1; } /** The subtree at `sourcePath`, shallow first. */ diff --git a/packages/core/test/virtualFileSystem.test.ts b/packages/core/test/virtualFileSystem.test.ts index 850249b..6fda85d 100644 --- a/packages/core/test/virtualFileSystem.test.ts +++ b/packages/core/test/virtualFileSystem.test.ts @@ -164,3 +164,24 @@ describe('virtual file system', () => { expect(fileSystem.upgradeLegacyDefaultImages()).toBe(0); }); }); + +describe("a file system's revision", () => { + it('changes with every change, a removal too, and not with a reading', () => { + const files = new VirtualFileSystem(); + const seen = [files.revision]; + files.createDirectory('/a'); + seen.push(files.revision); + files.writeTextFile('/a/b.txt', 'x'); + seen.push(files.revision); + files.renameInPlace('/a/b.txt', 'c.txt'); + seen.push(files.revision); + files.deleteItem('/a/c.txt'); + seen.push(files.revision); + files.allEntries(); + files.contains('/a'); + seen.push(files.revision); + expect(new Set(seen.slice(0, 5)).size).toBe(5); + expect(seen[5]).toBe(seen[4]); + }); +}); + diff --git a/packages/doc/src/index.ts b/packages/doc/src/index.ts index 434e9d1..21fec60 100644 --- a/packages/doc/src/index.ts +++ b/packages/doc/src/index.ts @@ -4,6 +4,7 @@ export * from './fields.js'; export * from './janitor.js'; export * from './projectDoc.js'; export * from './settings.js'; +export * from './simulation.js'; export * from './sheet.js'; export * from './snapshot.js'; export * from './values.js'; diff --git a/packages/doc/src/simulation.ts b/packages/doc/src/simulation.ts new file mode 100644 index 0000000..ef10239 --- /dev/null +++ b/packages/doc/src/simulation.ts @@ -0,0 +1,122 @@ +// What a run leaves in the project, written back into the document (plan +// §6.1: the room writes it as `origin: 'sim'`), as Java's project holds it +// when it is saved after a run (measured with tools/java-oracle's +// SaveAfterRunRun): each computer's files as its programs left them, which +// servers run, and the settings a run changes — a mail server's domain and +// accounts, a mail program's account and folders, a web server's virtual +// hosts, a firewall changed through a router's page, the address a DHCP +// client got. Only what differs is written, so that a write changes nothing +// the second time and a text edited meanwhile keeps what it did not touch. + +import * as Y from 'yjs'; +import type { Id, InstallableProgram, VirtualFileContent, VirtualFileSystem } from '@mdg-filius/core'; +import { fileEntry, NODE_SETTINGS, ROOTS } from './projectDoc.js'; + +export { emailServerAfterRun } from '@mdg-filius/fls'; +import { fromY, toY, type PlainValue } from './values.js'; + +/** The origin of the room's writes (plan §6.1); a person's undo never takes them. */ +export const SIM_ORIGIN = 'sim'; + +/** A node's settings key in the document (NODE_SETTINGS' values). */ +export type NodeSettingsKey = (typeof NODE_SETTINGS)[keyof typeof NODE_SETTINGS]; + +/** What a run left of a node. */ +export interface NodeRunState { + readonly files?: VirtualFileSystem; + readonly active?: Partial>; + readonly settings?: Partial>; +} + +/** Plain values alike, whatever the order of an object's fields; a field that is undefined is none. */ +function same(left: unknown, right: unknown): boolean { + if (left instanceof Uint8Array || right instanceof Uint8Array) return left instanceof Uint8Array && right instanceof Uint8Array && sameBytes(left, right); + if (Array.isArray(left) || Array.isArray(right)) { + return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((each, index) => same(each, right[index])); + } + if (typeof left === 'object' && left !== null && typeof right === 'object' && right !== null) { + const fields = (value: object) => Object.entries(value).filter(([, field]) => field !== undefined); + const leftFields = fields(left); + const rightFields = new Map(fields(right)); + return leftFields.length === rightFields.size && leftFields.every(([key, field]) => rightFields.has(key) && same(field, rightFields.get(key))); + } + return left === right; +} + +function sameBytes(left: Uint8Array, right: Uint8Array): boolean { + return left.length === right.length && left.every((byte, index) => byte === right[index]); +} + +/** A text changed where it changed: the common start and end stay (and with them what another person typed there). */ +function replaceText(text: Y.Text, next: string): void { + const current = text.toString(); + if (current === next) return; + let start = 0; + while (start < current.length && start < next.length && current[start] === next[start]) start += 1; + let end = 0; + while (end < current.length - start && end < next.length - start && current[current.length - 1 - end] === next[next.length - 1 - end]) end += 1; + if (current.length - start - end > 0) text.delete(start, current.length - start - end); + if (next.length - start - end > 0) text.insert(start, next.slice(start, next.length - end)); +} + +/** Whether a document entry holds the content. */ +function holds(entry: Y.Map, content: VirtualFileContent): boolean { + if (entry.get('kind') !== content.kind) return false; + if (content.kind === 'directory' || content.kind === 'text') return true; + return sameBytes(entry.get('data') as Uint8Array, content.data) && entry.get('mediaType') === content.mediaType; +} + +/** A node's files as the run left them: new ones after the others, changed ones in their place, gone ones removed. */ +function writeFiles(node: Y.Map, fileSystem: VirtualFileSystem): void { + let files = node.get('files') as Y.Map> | undefined; + const wanted: [string, VirtualFileContent][] = []; + const walk = (folder: string): void => { + for (const entry of fileSystem.entriesInCreationOrder(folder)) { + wanted.push([entry.path, entry.content]); + if (entry.content.kind === 'directory') walk(entry.path); + } + }; + walk('/'); + if (files === undefined) { + if (wanted.length === 0) return; + files = new Y.Map>(); + node.set('files', files); + } + const paths = new Set(wanted.map(([path]) => path)); + for (const path of [...files.keys()]) if (!paths.has(path)) files.delete(path); + let order = Math.max(-1, ...[...files.values()].map((entry) => entry.get('order') as number)) + 1; + for (const [path, content] of wanted) { + const entry = files.get(path); + if (entry === undefined) { + files.set(path, fileEntry(order++, content)); + } else if (!holds(entry, content)) { + files.set(path, fileEntry(entry.get('order') as number, content)); + } else if (content.kind === 'text') { + replaceText(entry.get('text') as Y.Text, content.text); + } + } +} + +/** Writes what the runs left of the nodes, what differs only, in one transaction; nodes the document no longer has are left out. */ +export function writeRunState(doc: Y.Doc, states: ReadonlyMap, origin: unknown = SIM_ORIGIN): void { + const nodes = doc.getMap>(ROOTS.nodes); + doc.transact(() => { + for (const [id, state] of states) { + const node = nodes.get(id); + if (node === undefined) continue; + if (state.files !== undefined) writeFiles(node, state.files); + const programs = node.get('programs') as Y.Map>; + for (const [program, active] of Object.entries(state.active ?? {})) { + const entry = programs.get(program); + if (entry !== undefined && entry.get('active') !== active) entry.set('active', active); + } + const settings = node.get('settings') as Y.Map; + // Only a setting the node has (a router's own web server has none). + for (const [key, value] of Object.entries(state.settings ?? {})) { + const current = settings.get(key); + if (value === undefined || current === undefined) continue; + if (!same(fromY(current), value)) settings.set(key, toY(value as PlainValue)); + } + } + }, origin); +} diff --git a/packages/editor/browser/simulation.spec.ts b/packages/editor/browser/simulation.spec.ts index c837422..c165004 100644 --- a/packages/editor/browser/simulation.spec.ts +++ b/packages/editor/browser/simulation.spec.ts @@ -362,6 +362,37 @@ test('keeps the command line over leaving it, over a return to the design, and e await expect(window).toBeVisible(); }); +test('keeps what a run made in the files over a return to the design, as Java\'s project keeps it', async ({ page }) => { + await openEditor(page, server.base, 'internetwork_komplett.fls'); + await start(page); + const { input, text } = await commandLine(page, 'Notebook 1.10'); + await input.fill('mkdir ordner'); + await input.press('Enter'); + await input.fill('ls'); + await input.press('Enter'); + await expect(text).toContainText('ordner'); + await designButton(page).click(); + await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'design'); + await start(page); + await input.fill('cls'); + await input.press('Enter'); + await input.fill('ls'); + await input.press('Enter'); + await expect(text).toContainText('\n/> ls\n'); + await expect(text).toContainText('ordner'); + // Removed in the run, it is gone from the project too. + await input.fill('rm ordner'); + await input.press('Enter'); + await designButton(page).click(); + await start(page); + await input.fill('cls'); + await input.press('Enter'); + await input.fill('ls'); + await input.press('Enter'); + await expect(text).toContainText('\n/> ls\n'); + await expect(text).not.toContainText('ordner'); +}); + test("installs and removes programs as Java's installation does, and a program installed runs at once", async ({ page }) => { await openEditor(page, server.base, 'internetwork_komplett.fls'); const id = await deviceNamed(page, 'Rechner 3.10'); diff --git a/packages/fls/src/projectSnapshot.ts b/packages/fls/src/projectSnapshot.ts index 9a76ea4..022cd04 100644 --- a/packages/fls/src/projectSnapshot.ts +++ b/packages/fls/src/projectSnapshot.ts @@ -17,7 +17,7 @@ // /mailserver/konten.txt at once), and an e-mail program's inbox and sent // mails. -import { portSnapshot, topologySnapshot, type DnsServerConfiguration, type EchoServerSnapshot, type EmailServerSnapshot, type Id, type WebServerSnapshot, type TopologySnapshot } from '@mdg-filius/core'; +import { portSnapshot, topologySnapshot, type DnsServerConfiguration, type EchoServerSnapshot, type EmailServerConfiguration, type EmailServerSnapshot, type Id, type WebServerSnapshot, type TopologySnapshot } from '@mdg-filius/core'; import type { ImportedProject } from './importProject.js'; export function projectSnapshot(project: ImportedProject): TopologySnapshot { @@ -72,18 +72,23 @@ export function projectSnapshot(project: ImportedProject): TopologySnapshot { }); } +/** An e-mail server's settings as a run left them: its domain and accounts, the next message's number after their mails. */ +export function emailServerAfterRun(configuration: EmailServerConfiguration, server: EmailServerSnapshot): EmailServerConfiguration { + const messages = server.accounts.reduce((count, account) => count + account.mailbox.length, 0); + return { ...configuration, domain: server.domain, accounts: server.accounts, nextMessageID: messages + 1 }; +} + /** * The project with what the simulation changed that Java keeps, as the run's - * snapshot holds it: the firewalls, the e-mail servers' mailboxes, and the - * e-mail programs' inboxes and sent mails. + * snapshot holds it: the firewalls, the e-mail servers' domains and + * mailboxes, and the e-mail programs' accounts, inboxes and sent mails. */ export function projectWithSimulationChanges(project: ImportedProject, snapshot: TopologySnapshot): ImportedProject { const emailServers = new Map(project.emailServerConfigurations); for (const [nodeId, server] of snapshot.emailServersByNodeId) { const configuration = emailServers.get(nodeId); if (configuration === undefined) continue; - const messages = server.accounts.reduce((count, account) => count + account.mailbox.length, 0); - emailServers.set(nodeId, { ...configuration, accounts: server.accounts, nextMessageID: messages + 1 }); + emailServers.set(nodeId, emailServerAfterRun(configuration, server)); } const emailClients = new Map(project.emailClientConfigurations); for (const [nodeId, client] of snapshot.emailClientsByNodeId) { diff --git a/packages/sim/src/room.ts b/packages/sim/src/room.ts index 8f7605e..08de975 100644 --- a/packages/sim/src/room.ts +++ b/packages/sim/src/room.ts @@ -27,6 +27,12 @@ // installed or removed on the desktop (Java's software installation) is // installed or removed in the running engine at once, as Java starts or ends // it; any other change comes in at the next start. +// +// What a run leaves that Java's project keeps (files, which servers run, +// accounts, mails, a DHCP client's address …) goes back into the document, +// as `origin: 'sim'`: at most once a second while it runs, after each action +// in a window and when it stops (writeRunState). So the next start, and every +// client, sees it — as Java's project saved after a run holds it. import * as Y from 'yjs'; import { @@ -44,7 +50,7 @@ import { type PageLanguage, type TopologySnapshot, } from '@mdg-filius/core'; -import { docSnapshot, installedSoftware, ROOTS } from '@mdg-filius/doc'; +import { docSnapshot, emailServerAfterRun, fromY, installedSoftware, ROOTS, SIM_ORIGIN, writeRunState, type NodeRunState } from '@mdg-filius/doc'; import { parseClientMessage, SIM_PROTOCOL_VERSION, @@ -64,6 +70,8 @@ import { BrowserWindow } from './browserWindow.js'; import { TerminalWindow } from './terminalWindow.js'; export const TICK_MILLISECONDS = 200; +/** How often a running simulation writes what it left into the document. */ +export const WRITE_BACK_MILLISECONDS = 1_000; export const MAXIMUM_CATCH_UP_MILLISECONDS = 2_000; export interface RoomOptions { @@ -71,6 +79,8 @@ export interface RoomOptions { readonly now: () => number; /** A seed for each start. */ readonly seed: () => number; + /** How often a running simulation writes what it left into the document; WRITE_BACK_MILLISECONDS by default. */ + readonly writeBackMilliseconds?: number; } interface Client { @@ -111,6 +121,10 @@ export class SimulationRoom { private readonly browserWindows = new Map(); /** The language of the last start (the pages the devices write themselves). */ private language: PageLanguage | undefined; + /** Each node's file system and its revision when it was last written back. */ + private writtenFiles = new Map(); + /** The wall clock's time of the last write-back. */ + private lastWriteBack = 0; /** Each computer's programs as the running engine has them. */ private software = new Map(); @@ -157,6 +171,56 @@ export class SimulationRoom { this.flush([...this.clients.values()], false); } + /** + * What the run left of each node that Java's project keeps: its files (when + * they changed since the last write), whether its servers run, a web + * server's virtual hosts, an e-mail server's domain and accounts, an + * e-mail program's account and folders, a firewall, a DHCP client's address. + */ + private runState(): Map { + const snapshot = this.engine.state.topologySnapshot; + const states = new Map(); + const nodes = this.doc.getMap>(ROOTS.nodes); + for (const { id } of snapshot.nodes) { + const node = nodes.get(id); + if (node === undefined) continue; + const setting = (key: string) => fromY((node.get('settings') as Y.Map).get(key)); + const active: NonNullable = {}; + const settings: Record = {}; + const web = this.engine.webServer(id); + const webSettings = snapshot.webServersByNodeId.get(id); + if (web !== undefined && webSettings !== undefined) { + active.webServer = web.isActive; + settings.webServer = { port: web.port, documentRoot: webSettings.documentRoot, virtualHosts: web.useVirtualHosts }; + } + const mailServer = this.engine.emailServer(id); + const mailServerSettings = snapshot.emailServersByNodeId.get(id); + const configured = setting('emailServer'); + if (mailServer !== undefined && mailServerSettings !== undefined && configured !== undefined) { + active.emailServer = mailServer.isActive; + settings.emailServer = emailServerAfterRun(configured as never, mailServerSettings); + } + const echo = this.engine.echoServer(id); + if (echo !== undefined) active.echoServer = echo.isActive; + settings.emailClient = snapshot.emailClientsByNodeId.get(id); + settings.firewall = snapshot.firewallConfigurationsByNodeId.get(id); + if (snapshot.dhcpClientConfigurationsByNodeId.get(id)?.isEnabled === true) settings.device = snapshot.deviceConfigurations.get(id); + const files = snapshot.fileSystemsByNodeId.get(id); + const written = this.writtenFiles.get(id); + const changed = files !== undefined && (written?.files !== files || written.revision !== files.revision); + if (files !== undefined && changed) this.writtenFiles.set(id, { files, revision: files.revision }); + states.set(id, { ...(changed ? { files } : {}), active, settings }); + } + return states; + } + + /** Writes what the run left into the document (origin 'sim'). */ + private writeBack(): void { + if (this.engine.state.phase !== 'running') return; + this.lastWriteBack = this.options.now(); + writeRunState(this.doc, this.runState(), SIM_ORIGIN); + } + /** The document as the engine takes it, in the language of the start. */ private snapshot(): TopologySnapshot { const snapshot = docSnapshot(this.doc); @@ -246,6 +310,7 @@ export class SimulationRoom { case 'input': { let failure: ErrorCode | undefined = 'engineFailed'; if (!this.guarded(() => (failure = this.act(input)))) return; + if (failure === undefined) this.guarded(() => this.writeBack()); client.send(failure === undefined ? message({ kind: 'ack', reqId: input.reqId }) : message({ kind: 'error', code: failure, reqId: input.reqId })); this.flush([...this.clients.values()], false); return; @@ -272,12 +337,15 @@ export class SimulationRoom { this.syncWindows(); this.mode = 'simulation'; this.lastWall = this.options.now(); + this.lastWriteBack = this.lastWall; this.broadcast(this.state()); this.flush([...this.clients.values()], false); } stop(): void { if (this.mode === 'design') return; + // A failure already stopped the engine and went back to the design. + if (!this.guarded(() => this.writeBack())) return; this.engine.handle({ type: 'stop' }); this.mode = 'design'; this.lastWall = undefined; @@ -295,6 +363,7 @@ export class SimulationRoom { this.lastWall = behind > MAXIMUM_CATCH_UP_MILLISECONDS ? now : (this.lastWall ?? now) + elapsed; if (!this.guarded(() => this.engine.handle({ type: 'advance', toMilliseconds: this.engine.state.currentTimeMilliseconds + elapsed }))) return; this.flush([...this.clients.values()], false); + if (now - this.lastWriteBack >= (this.options.writeBackMilliseconds ?? WRITE_BACK_MILLISECONDS)) this.guarded(() => this.writeBack()); } /** diff --git a/packages/sim/test/fixtures/after-run-dhcp.fls b/packages/sim/test/fixtures/after-run-dhcp.fls new file mode 100644 index 0000000000000000000000000000000000000000..2b3a6202964c66c0a4b24d23f270fc27f2fd23c2 GIT binary patch literal 109331 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 N&8G(lMW0g*@LyTqf`$M9 literal 0 HcmV?d00001 diff --git a/packages/sim/test/fixtures/after-run-email.fls b/packages/sim/test/fixtures/after-run-email.fls new file mode 100644 index 0000000000000000000000000000000000000000..bdc3383d9381591604d1059dc4605d8855e3e27a GIT binary patch literal 11193 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 literal 0 HcmV?d00001 diff --git a/packages/sim/test/terminalWindow.test.ts b/packages/sim/test/terminalWindow.test.ts index 3245b41..f12cffa 100644 --- a/packages/sim/test/terminalWindow.test.ts +++ b/packages/sim/test/terminalWindow.test.ts @@ -1,9 +1,8 @@ // The room's command line window against Java's (tools/java-oracle // TerminalWindowRun): the same keys typed into a computer's window, the same -// text, prompt and history after each, over a stop and a start. One -// difference is named: what a run made in the files is not yet written back -// into the document (F4.6f), so after the restart the folder made before is -// gone and the window goes on at the root, where Java's goes on in it. +// text, prompt and history after each, over a stop and a start — the +// folder made before the stop is in the project afterwards (written back, +// F4.6f), and the window goes on in it, as Java's does. import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; @@ -59,9 +58,6 @@ function setUp(programs: readonly string[] = ['commandPrompt']) { return { doc, room, send, run, read, got, connect: (listener: (message: HostMessage) => void) => room.connect(listener) }; } -/** Until the files of a run are written back (F4.6f): the folder made before the restart is gone, the window goes on at the root. */ -const FILES_NOT_KEPT_YET = (text: string) => text.replaceAll('/dir', '/'); - describe("a command line's window", () => { it("shows what Java's window shows, key by key, over a stop and a start", () => { const { send, run, read } = setUp(); @@ -70,7 +66,6 @@ describe("a command line's window", () => { run(TICK_MILLISECONDS); let pointer = -1; let input = ''; - let restartedAt: number | undefined; for (const step of java.steps) { if (step.step.startsWith('enter:')) { send({ kind: 'input', node: 'pc', app: 'terminal', action: 'enter', args: { line: step.step.slice('enter:'.length) }, reqId: step.step }); @@ -85,13 +80,9 @@ describe("a command line's window", () => { send({ kind: 'stop' }); send({ kind: 'start' }); run(400); - restartedAt = terminalScreen(read()).text.length; } const screen = terminalScreen(read()); - const expected = - restartedAt === undefined - ? step - : { ...step, text: step.text.slice(0, restartedAt) + FILES_NOT_KEPT_YET(step.text.slice(restartedAt)), prompt: FILES_NOT_KEPT_YET(step.prompt) }; + const expected = step; expect({ text: screen.text, prompt: screen.prompt, promptShown: screen.promptShown, input }, step.step).toEqual({ text: expected.text, prompt: expected.prompt, @@ -120,9 +111,9 @@ describe("a command line's window", () => { send({ kind: 'start' }); run(400); expect(terminalScreen(read()).prompt).toBe('/docs> '); - // The folder gone from the project: the next start goes on at the root. - files.delete('/docs'); + // The folder gone from the project (in the design): the next start goes on at the root. send({ kind: 'stop' }); + files.delete('/docs'); send({ kind: 'start' }); run(400); expect(terminalScreen(read()).prompt).toBe('/> '); diff --git a/packages/sim/test/writeBack.test.ts b/packages/sim/test/writeBack.test.ts new file mode 100644 index 0000000..c562c4c --- /dev/null +++ b/packages/sim/test/writeBack.test.ts @@ -0,0 +1,242 @@ +// What a run leaves in the document, against what Java's project holds when +// it is saved after the same run (tools/java-oracle SaveAfterRunRun, +// fixtures/after-run-*.fls; both read with the same reader): the files a +// command line made, the accounts and mails, which servers run, the virtual +// hosts, a new domain; the addresses DHCP clients got. Named: the virtual +// hosts' file holds the complete rows only (F4.6d). + +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; +import * as Y from 'yjs'; +import { VirtualFileSystem as VirtualFileSystemOf, type Id, type VirtualFileSystem } from '@mdg-filius/core'; +import { createUndoManager, readProject, ROOTS, setSoftware, SIM_ORIGIN, writeProject, writeRunState } from '@mdg-filius/doc'; +import { openProjectArchive, type ImportedProject } from '@mdg-filius/fls'; +import type { HostMessage } from '@mdg-filius/protocol'; +import { SimulationRoom, TICK_MILLISECONDS, WRITE_BACK_MILLISECONDS } from '../src/index.js'; + +const here = dirname(fileURLToPath(import.meta.url)); +const corpus = join(here, '../../../corpus/java-examples'); + +function open(path: string): ImportedProject { + let ids = 0; + return openProjectArchive(new Uint8Array(readFileSync(path)), () => `00000000-0000-0000-0000-${(++ids).toString(16).padStart(12, '0')}`); +} + +function setUp(file: string, writeBackMilliseconds?: number) { + const doc = new Y.Doc(); + writeProject(doc, open(join(corpus, file))); + let now = 1_000_000; + const room = new SimulationRoom(doc, { now: () => now, seed: () => 5, ...(writeBackMilliseconds === undefined ? {} : { writeBackMilliseconds }) }); + const got: HostMessage[] = []; + const client = room.connect((message) => got.push(message)); + const id = (name: string) => readProject(doc).graph.nodes.find((node) => node.displayName === name)!.id; + const act = (node: string, app: string, action: string, args: object = {}) => room.receive(client, { v: 1, kind: 'input', node: id(node), app, action, args, reqId: `${app}.${action}` }); + const run = (milliseconds: number) => { + for (let elapsed = 0; elapsed < milliseconds; elapsed += TICK_MILLISECONDS) { + now += TICK_MILLISECONDS; + room.tick(); + } + }; + return { doc, room, got, id, act, run, send: (message: object) => room.receive(client, { v: 1, ...message }) }; +} + +/** A computer's files: text as it is, the rest by kind. */ +function filesOf(files: VirtualFileSystem | undefined): Record { + return Object.fromEntries((files?.allEntries() ?? []).map((entry) => [entry.path, entry.content.kind === 'text' ? entry.content.text : entry.content.kind])); +} + +/** What a run leaves of a node, as the model holds it. */ +function slice(project: ImportedProject, name: string) { + const id = project.graph.nodes.find((node) => node.displayName === name)!.id; + return { + files: filesOf(project.virtualFileSystems.get(id)), + programs: [...(project.installedPrograms.get(id) ?? [])].sort(), + active: [...(project.activePrograms.get(id) ?? [])].sort(), + webServer: project.webServerConfigurations.get(id), + emailServer: project.emailServerConfigurations.get(id), + emailClient: project.emailClientConfigurations.get(id), + firewall: project.firewallConfigurationsByNodeId.get(id), + }; +} + +const ACCOUNT = (user: string, last: string) => ({ name: `Rechner ${last}`, email: `${user}@filius.de`, pop3Host: '141.99.50.232', pop3Port: '110', smtpHost: '141.99.50.232', smtpPort: '25', username: user, password: user }); + +describe("what a run leaves in the document, against Java's project saved after it", () => { + it('keeps the files, accounts, mails, servers, virtual hosts and domain of a run', () => { + const { doc, id, act, run, send } = setUp('email_komplett.fls'); + setSoftware(doc, id('Rechner 1'), { install: ['commandPrompt'], remove: [] }, 'de', 'me'); + send({ kind: 'start' }); + run(2_000); + act('Rechner 1', 'terminal', 'enter', { line: 'mkdir ordner' }); + run(500); + act('Rechner 1', 'terminal', 'enter', { line: 'touch ordner/notiz.txt' }); + run(500); + act('Rechner 1', 'emailClient', 'setAccount', ACCOUNT('rechner1', '1')); + act('Rechner 2', 'emailClient', 'setAccount', ACCOUNT('rechner2', '2')); + act('Rechner 1', 'emailClient', 'send', { to: 'rechner2@filius.de', cc: '', bcc: '', subject: 'Nach dem Lauf', text: 'Erste Zeile\nZweite Zeile' }); + run(8_000); + act('Rechner 2', 'emailClient', 'fetch'); + run(8_000); + act('Mail-Server (filius.de)', 'emailServer', 'addAccount', { username: 'neu', password: 'pw', firstName: 'Vorname', lastName: 'Nachname' }); + act('Web-Server', 'webServer', 'setActive', { active: false }); + act('Web-Server', 'webServer', 'useVirtualHosts', { use: true }); + act('Web-Server', 'webServer', 'setVirtualHost', { row: 0, column: 0, value: 'a.de' }); + act('Web-Server', 'webServer', 'setVirtualHost', { row: 0, column: 1, value: 'ordner' }); + act('Mail-Server (senior.de)', 'emailServer', 'setActive', { active: false }); + act('Mail-Server (senior.de)', 'emailServer', 'setDomain', { domain: 'senior.org' }); + run(1_000); + send({ kind: 'stop' }); + + const ours = readProject(doc); + const java = open(join(here, 'fixtures/after-run-email.fls')); + for (const name of ['Rechner 1', 'Rechner 2', 'Rechner 3', 'Mail-Server (filius.de)', 'Mail-Server (senior.de)', 'DNS-Server']) { + expect(slice(ours, name), name).toEqual(slice(java, name)); + } + // The virtual hosts' file holds the complete rows (Java's every row, the empty ones as empty lines). + const web = slice(java, 'Web-Server'); + expect(web.files['/www.conf/vhosts']).toBe('a.de\nordner\n\n\n\n\n\n\n\n\n'); + expect(slice(ours, 'Web-Server')).toEqual({ ...web, files: { ...web.files, '/www.conf/vhosts': 'a.de\nordner\n' } }); + }); + + it('keeps the addresses DHCP clients got', () => { + const { doc, run, send } = setUp('einfaches_rechnernetz_komplett.fls'); + send({ kind: 'start' }); + run(20_000); + // Written while it runs, not only at the stop. + const running = readProject(doc); + send({ kind: 'stop' }); + expect(readProject(doc).deviceConfigurations).toEqual(running.deviceConfigurations); + const devices = (project: ImportedProject) => + project.graph.nodes + .filter((node) => project.dhcpClientConfigurations.get(node.id)?.isEnabled === true) + .map((node) => project.deviceConfigurations.get(node.id)!) + // Which client gets which address is the threads' race in Java. + .map(({ ipAddress, ...rest }) => ({ ...rest, ipAddress: ipAddress.startsWith('192.168.0.') ? 'leased' : ipAddress })); + const java = open(join(here, 'fixtures/after-run-dhcp.fls')); + const before = open(join(corpus, 'einfaches_rechnernetz_komplett.fls')); + expect(devices(readProject(doc))).toEqual(devices(java)); + expect(devices(before)).not.toEqual(devices(java)); + const addresses = (project: ImportedProject) => + project.graph.nodes.filter((node) => project.dhcpClientConfigurations.get(node.id)?.isEnabled === true).map((node) => project.deviceConfigurations.get(node.id)!.ipAddress).sort(); + expect(addresses(readProject(doc))).toEqual(addresses(java)); + }); +}); + +describe("the room's write-back", () => { + it('writes while the simulation runs, at most once a second, after an action and at the stop, as the room', () => { + const { doc, id, act, run, send } = setUp('email_komplett.fls'); + const origins: unknown[] = []; + doc.on('update', (_update: Uint8Array, origin: unknown) => origins.push(origin)); + const node = id('Mail-Server (filius.de)'); + const accounts = () => (readProject(doc).emailServerConfigurations.get(node)?.accounts ?? []).map((account) => account.username); + send({ kind: 'start' }); + run(TICK_MILLISECONDS); + act('Mail-Server (filius.de)', 'emailServer', 'addAccount', { username: 'neu', password: 'pw', firstName: 'A', lastName: 'B' }); + expect(accounts()).toContain('neu'); + expect(origins.length).toBeGreaterThan(0); + expect(new Set(origins)).toEqual(new Set([SIM_ORIGIN])); + // Nothing new: nothing is written. + const written = origins.length; + run(3 * WRITE_BACK_MILLISECONDS); + expect(origins.length).toBe(written); + send({ kind: 'stop' }); + expect(origins.length).toBe(written); + }); + + it('writes at the stop what it left since the last write', () => { + const { doc, id, run, send } = setUp('einfaches_rechnernetz_komplett.fls', 60_000); + const notebook = id('Notebook 1'); + const before = readProject(doc).deviceConfigurations.get(notebook); + send({ kind: 'start' }); + run(20_000); + expect(readProject(doc).deviceConfigurations.get(notebook)).toEqual(before); + send({ kind: 'stop' }); + expect(readProject(doc).deviceConfigurations.get(notebook)!.ipAddress).toMatch(/^192\.168\.0\./); + }); + + it('removes from the document what a command line removed', () => { + const { doc, id, act, run, send } = setUp('email_komplett.fls'); + const node = id('Rechner 1'); + setSoftware(doc, node, { install: ['commandPrompt'], remove: [] }, 'de', 'me'); + const files = () => Object.keys(Object.fromEntries((readProject(doc).virtualFileSystems.get(node)?.allEntries() ?? []).map((entry) => [entry.path, true]))); + expect(files()).toContain('/konten.txt'); + send({ kind: 'start' }); + run(TICK_MILLISECONDS); + act('Rechner 1', 'terminal', 'enter', { line: 'rm konten.txt' }); + expect(files()).not.toContain('/konten.txt'); + }); + + it("is never a person's step to take back", () => { + const { doc, id, act, run, send } = setUp('email_komplett.fls'); + const undo = createUndoManager(doc, 'me'); + send({ kind: 'start' }); + run(TICK_MILLISECONDS); + act('Web-Server', 'webServer', 'setActive', { active: false }); + send({ kind: 'stop' }); + expect(readProject(doc).activePrograms.get(id('Web-Server'))).toBeUndefined(); + expect(undo.undoStack).toHaveLength(0); + }); + + it('writes a text where it changed, keeps a file another wrote meanwhile, and removes a file the run removed', () => { + const doc = new Y.Doc(); + const { doc: source } = setUp('email_komplett.fls'); + writeProject(doc, readProject(source)); + const nodeId = readProject(doc).graph.nodes.find((node) => node.displayName === 'Rechner 1')!.id as Id; + const files = () => doc.getMap>(ROOTS.nodes).get(nodeId)!.get('files') as Y.Map>; + const fileSystem = readProject(doc).virtualFileSystems.get(nodeId)!; + fileSystem.createDirectory('/neu'); + fileSystem.writeTextFile('/neu/a.txt', 'Anfang Mitte Ende'); + writeRunState(doc, new Map([[nodeId, { files: fileSystem }]])); + const text = files().get('/neu/a.txt')!.get('text') as Y.Text; + expect(text.toString()).toBe('Anfang Mitte Ende'); + const order = files().get('/neu/a.txt')!.get('order'); + // The run changes the middle; the entry and its text stay the same objects. + fileSystem.writeTextFile('/neu/a.txt', 'Anfang neue Mitte Ende'); + const changes: string[] = []; + text.observe((event) => changes.push(JSON.stringify(event.delta))); + writeRunState(doc, new Map([[nodeId, { files: fileSystem }]])); + expect([files().get('/neu/a.txt')!.get('text'), text.toString(), files().get('/neu/a.txt')!.get('order')]).toEqual([text, 'Anfang neue Mitte Ende', order]); + expect(changes).toEqual([JSON.stringify([{ retain: 7 }, { insert: 'neue ' }])]); + // A file turned into a folder, and one removed. + fileSystem.deleteItem('/neu/a.txt'); + fileSystem.createDirectory('/neu/a.txt'); + fileSystem.deleteItem('/konten.txt'); + writeRunState(doc, new Map([[nodeId, { files: fileSystem }]])); + expect(files().get('/neu/a.txt')!.get('kind')).toBe('directory'); + expect(files().get('/neu/a.txt')!.get('order')).toBe(order); + expect(files().has('/konten.txt')).toBe(false); + // An image's bytes and type. + fileSystem.writeImageFile('/bild.png', new Uint8Array([1, 2]), 'image/png'); + writeRunState(doc, new Map([[nodeId, { files: fileSystem }]])); + fileSystem.writeImageFile('/bild.png', new Uint8Array([1, 3]), 'image/png'); + writeRunState(doc, new Map([[nodeId, { files: fileSystem }]])); + expect(files().get('/bild.png')!.get('data')).toEqual(new Uint8Array([1, 3])); + fileSystem.writeImageFile('/bild.png', new Uint8Array([1, 3]), 'image/gif'); + writeRunState(doc, new Map([[nodeId, { files: fileSystem }]])); + expect(files().get('/bild.png')!.get('mediaType')).toBe('image/gif'); + // A setting written again alike, its fields in another order or one undefined: nothing; with a field less: written. + const settingsDoc = new Y.Doc(); + writeProject(settingsDoc, readProject(source)); + const server = readProject(settingsDoc).graph.nodes.find((node) => node.displayName === 'Web-Server')!.id; + const web = readProject(settingsDoc).webServerConfigurations.get(server)!; + const updates: unknown[] = []; + settingsDoc.on('update', (update: Uint8Array) => updates.push(update)); + writeRunState(settingsDoc, new Map([[server, { settings: { webServer: { virtualHosts: web.virtualHosts, documentRoot: web.documentRoot, port: web.port, extra: undefined } } }]])); + expect(updates).toHaveLength(0); + writeRunState(settingsDoc, new Map([[server, { settings: { webServer: { port: web.port, documentRoot: web.documentRoot } } }]])); + expect(updates).toHaveLength(1); + expect(readProject(settingsDoc).webServerConfigurations.get(server)).toEqual({ port: web.port, documentRoot: web.documentRoot }); + writeRunState(settingsDoc, new Map([[server, { settings: { webServer: { port: web.port, documentRoot: web.documentRoot, virtualHosts: true } } }]])); + expect(updates).toHaveLength(2); + // A node without files whose run has none: no files. + const switchId = readProject(settingsDoc).graph.nodes.find((node) => node.kind === 'networkSwitch')!.id; + writeRunState(settingsDoc, new Map([[switchId, { files: new VirtualFileSystemOf() }]])); + expect(settingsDoc.getMap>(ROOTS.nodes).get(switchId)!.has('files')).toBe(false); + // A node the document no longer has, and settings it has not: nothing. + writeRunState(doc, new Map([['niemand', { files: fileSystem }]])); + writeRunState(doc, new Map([[nodeId, { settings: { webServer: { port: 80 } } }]])); + expect(readProject(doc).webServerConfigurations.has(nodeId)).toBe(false); + }); +}); diff --git a/tools/java-oracle/README.md b/tools/java-oracle/README.md index 4622ef6..afc58a0 100644 --- a/tools/java-oracle/README.md +++ b/tools/java-oracle/README.md @@ -1368,3 +1368,28 @@ Gemessen (03.10.2026): * Löschen löscht in Javas Fenster die Zeile der zuletzt **angeklickten** Liste: War zuvor eine gesendete Mail gewählt und ist im Posteingang schon eine markiert, geht die gesendete. + +### Was ein Lauf im Projekt hinterlässt (F4.6f1) + +`SaveAfterRunRun` öffnet ein Beispiel, wie FILIUS es öffnet, startet es, +spielt eine Geschichte über die Programme (wie ihre Fenster sie aufrufen), +stoppt wie der Entwurfsmodus (`stopSimulation`: `beenden` jedes Knotens) und +speichert (`SzenarioVerwaltung.speichern`; Java speichert nur im Entwurf): +`packages/sim/test/fixtures/after-run-email.fls` (email_komplett: Ordner +und Datei aus der Befehlszeile, Konten gesetzt, eine Mail gesendet und +abgerufen, ein neues Konto, Webserver aus und virtuelle Hosts an, ein +E-Mail-Server aus mit neuer Domain) und `after-run-dhcp.fls` +(einfaches_rechnernetz_komplett, 10 s DHCP). + +Gemessen (03.10.2026): + +* Java speichert die Dateien, wie die Programme sie hinterließen (auch + `/www.conf/vhosts`, die Kontendateien), ob jeder Server läuft, das + Kästchen der virtuellen Hosts (nach jedem Start an, wenn die Datei da + ist), Domain und Konten der E-Mail-Server, Konto, Posteingang und + Gesendete der E-Mail-Programme (eine abgerufene Mail mit „\n“ am Ende). +* Die Adressen, die DHCP-Clients bekamen, stehen danach in ihren Karten, + mit Gateway „0.0.0.0“, wo der Server keines vergibt. +* Die Konten der Programme in email_komplett liegen nur in der alten + `kontoListe`, die FILIUS 2.14 nicht liest; das Orakel setzt sie, wie man + sie in den Dialog tippt (`setzeKonto`, `speichern`). diff --git a/tools/java-oracle/run.sh b/tools/java-oracle/run.sh index 9933229..cb9d334 100755 --- a/tools/java-oracle/run.sh +++ b/tools/java-oracle/run.sh @@ -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" +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" # 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. @@ -389,6 +389,18 @@ HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE - "$root/packages/core/test/scenarios/mail-lan.scenario.json" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at \|AWT-EventQueue' || true test -s "$output" +# What a run leaves in FILIUS' project: an example started, a story played, +# stopped as the design mode stops it and saved (SaveAfterRunRun): the room's +# write-back into the document (packages/sim/test/writeBack.test.ts). +for run in email:email_komplett dhcp:einfaches_rechnernetz_komplett; do + story="${run%%:*}" + output="$root/packages/sim/test/fixtures/after-run-$story.fls" + rm -f "$output" + HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -cp "$classpath:$here/.build" SaveAfterRunRun \ + "$root/corpus/java-examples/${run##*:}.fls" "$story" "$output" 2>&1 | grep -v 'logback\|^$\|^\s*at ' || true + test -s "$output" +done + # 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). diff --git a/tools/java-oracle/src/SaveAfterRunRun.java b/tools/java-oracle/src/SaveAfterRunRun.java new file mode 100644 index 0000000..0b4e4f8 --- /dev/null +++ b/tools/java-oracle/src/SaveAfterRunRun.java @@ -0,0 +1,149 @@ +// What a run leaves in FILIUS' project: an example opened and started as +// FILIUS does (ScenarioNetwork.fromFile), a story played through the +// programs as their windows call them, the simulation stopped as the design +// mode stops it (GUIMainMenu.stopSimulation: every node's beenden) and the +// project saved (SzenarioVerwaltung.speichern; Java saves only in the design +// mode). The reference for the room's write-back into the document +// (packages/sim/test/writeBack.test.ts). +// +// SaveAfterRunRun +// +// email email_komplett, with FILIUS' command line installed on Rechner 1 +// before the start: Rechner 1 and 2 set their accounts (as typed +// into the program's dialog), Rechner 1 types "mkdir ordner" and +// "touch ordner/notiz.txt", writes to rechner2@filius.de; Rechner 2 +// fetches; Mail-Server (filius.de) gets the account "neu"; the +// Web-Server is switched off, its virtual hosts on with the row +// a.de → ordner; Mail-Server (senior.de) is switched off and its +// domain set to senior.org +// dhcp einfaches_rechnernetz_komplett: the notebooks get their +// addresses from the server (10 s) + +import filius.gui.GUIContainer; +import filius.gui.netzwerksicht.GUIKnotenItem; +import filius.hardware.knoten.Knoten; +import filius.rahmenprogramm.SzenarioVerwaltung; +import filius.software.email.Email; +import filius.software.email.EmailAnwendung; +import filius.software.email.EmailKonto; +import filius.software.email.EmailServer; +import filius.software.email.EmailUtils; +import filius.software.lokal.Terminal; +import filius.software.system.Betriebssystem; +import filius.software.system.InternetKnotenBetriebssystem; +import filius.software.www.WebServer; + +import java.util.Arrays; +import java.util.StringTokenizer; + +public class SaveAfterRunRun { + static ScenarioNetwork network; + + static InternetKnotenBetriebssystem system(String name) { + Knoten knoten = network.nodes.get(name); + if (knoten == null) throw new IllegalStateException("no node " + name); + return (InternetKnotenBetriebssystem) knoten.getSystemSoftware(); + } + + /** GUIApplicationTerminalWindow on Enter: the line handed to the program. */ + static void type(Terminal terminal, String line) throws Exception { + StringTokenizer tokens = new StringTokenizer(line, " "); + String command = tokens.nextToken(); + String[] parameters = new String[3 + tokens.countTokens()]; + Arrays.fill(parameters, ""); + for (int index = 0; tokens.hasMoreTokens(); index++) parameters[index] = tokens.nextToken(); + terminal.terminalEingabeAuswerten(command, parameters); + Thread.sleep(500); + } + + /** + * The account as one types it into the program's dialog and saves it + * (kontoSpeichern: setzeKonto, speichern): the file keeps it only in the + * older kontoListe, which FILIUS 2.14 no longer reads. + */ + static EmailKonto account(EmailAnwendung program, String user, String server, String domain, String lastName) { + EmailKonto account = new EmailKonto(); + account.setVorname("Rechner"); + account.setNachname(lastName); + account.setBenutzername(user); + account.setPasswort(user); + account.setPop3port("110"); + account.setPop3server(server); + account.setSmtpport("25"); + account.setSmtpserver(server); + account.setEmailAdresse(user + "@" + domain); + program.setzeKonto(account); + program.speichern(); + return account; + } + + 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 { + network = ScenarioNetwork.fromFile(args[0]); + String story = args[1]; + if (story.equals("email")) { + ((Betriebssystem) system("Rechner 1")).installAndStartAppIfAvailable("filius.software.lokal.Terminal"); + } + network.start(); + Thread.sleep(2000); + switch (story) { + case "email": { + Terminal terminal = (Terminal) system("Rechner 1").holeSoftware("filius.software.lokal.Terminal"); + type(terminal, "mkdir ordner"); + type(terminal, "touch ordner/notiz.txt"); + EmailAnwendung one = (EmailAnwendung) system("Rechner 1").holeSoftware("filius.software.email.EmailAnwendung"); + EmailKonto account = account(one, "rechner1", "141.99.50.232", "filius.de", "1"); + Email mail = new Email(); + mail.setAbsender(account.getVorname() + (!account.getNachname().isEmpty() ? (" " + account.getNachname()) : "") + " <" + account.getEmailAdresse() + ">"); + mail.setEmpfaenger(EmailUtils.stringToAddressEntryList("rechner2@filius.de")); + mail.setCc(EmailUtils.stringToAddressEntryList("")); + mail.setBcc(EmailUtils.stringToAddressEntryList("")); + mail.setBetreff("Nach dem Lauf"); + mail.setText("Erste Zeile\nZweite Zeile"); + one.versendeEmail(account.getSmtpserver(), mail, account.getEmailAdresse()); + Thread.sleep(8000); + EmailAnwendung two = (EmailAnwendung) system("Rechner 2").holeSoftware("filius.software.email.EmailAnwendung"); + EmailKonto other = account(two, "rechner2", "141.99.50.232", "filius.de", "2"); + two.emailsAbholenEmails(other.getBenutzername(), other.getPasswort(), other.getPop3port(), other.getPop3server()); + Thread.sleep(8000); + ((EmailServer) system("Mail-Server (filius.de)").holeSoftware("filius.software.email.EmailServer")).benutzerHinzufuegen("neu", "pw", "Nachname", "Vorname"); + WebServer web = (WebServer) system("Web-Server").holeSoftware("filius.software.www.WebServer"); + web.setAktiv(false); + web.setUseVHost(true); + web.changeVHostTable(0, 0, "a.de"); + web.changeVHostTable(0, 1, "ordner"); + EmailServer senior = (EmailServer) system("Mail-Server (senior.de)").holeSoftware("filius.software.email.EmailServer"); + senior.setAktiv(false); + senior.setMailDomain("senior.org"); + Thread.sleep(1000); + break; + } + case "dhcp": + Thread.sleep(10000); + break; + default: + throw new IllegalArgumentException("unknown story " + story); + } + // GUIMainMenu.stopSimulation, then the design mode's save. + for (GUIKnotenItem item : GUIContainer.getGUIContainer().getKnotenItems()) { + try { + item.getKnoten().getSystemSoftware().beenden(); + } catch (Exception error) {} + } + Thread.sleep(1000); + GUIContainer gui = GUIContainer.getGUIContainer(); + if (!SzenarioVerwaltung.getInstance().speichern(args[2], gui.getKnotenItems(), gui.getCableItems(), gui.getDocuItems())) { + throw new IllegalStateException("FILIUS could not save " + args[2]); + } + System.out.println("saved after the run: " + story); + } +}