feat(fls): Java's view of the e-mail programs

The Java view now reads what FILIUS 2.14 keeps for its e-mail programs:
the server's domain, switch and bean accounts with their mailboxes, and the
accounts of /mailserver/konten.txt, which Java reads at every simulation
start and which so win over the bean list; the client's three folders and
address book, and its account from /konten.txt (no bean holds it).

javaEmail.ts ports the bean setters and both file readers
(stringZuListeBenutzerkonten, EmailKonto(String)); javaString.ts Java's
trim, split and StringTokenizer; javaFileSystem.ts the file lookup, now
shared with the DNS server, with the four non-ASCII units Java's
equalsIgnoreCase also takes for an ASCII letter (measured over the BMP).

Checked against Java on the corpus (the oracle dumps the e-mail state) and
on 43 account files read by EmailServer.kontenLaden and
EmailAnwendung.laden (EmailFileCases). Found: older files keep recipients
as strings in the typed lists; a getter copy of the sender is filled and
set back; one short server line makes Java throw for the whole file; a
message with an empty text is lost on reading; a broken last client line
empties the account.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 07:06:55 +00:00
co-authored by Claude Opus 5.5
parent c80d212c90
commit ec5523d7c7
22 changed files with 2900 additions and 94 deletions
+54 -4
View File
@@ -27,6 +27,21 @@
// not know Java's constructors and sees a placeholder per path; they are
// merged here.
import { JAVA_DOMAIN } from './dnsHosts.js';
import {
EMAIL_CLIENT_FILE,
EMAIL_SERVER_FILE,
javaContacts,
javaEmailAccounts,
javaEmails,
readJavaEmailClientFile,
readJavaEmailServerFile,
type JavaContact,
type JavaEmail,
type JavaEmailAccount,
type JavaFailure,
} from './javaEmail.js';
import { javaFileContent } from './javaFileSystem.js';
import {
isJavaObject,
isUnresolved,
@@ -114,12 +129,12 @@ const SERVER_APPLICATIONS = new Set<string>([
'filius.software.www.WebServer',
]);
/** An installed application with the settings the model reads; e-mail contents are a later layer. */
/** An installed application with the settings the model reads. */
export interface FlsApplication {
/** The key in installierteAnwendungen — the class name in every corpus file. */
key: string | null;
javaClass: FlsApplicationClass;
/** Server applications: switched on (Java's aktiv) and the port. */
/** Server applications: switched on (Java's aktiv) and the port; EmailServer: switched on. */
isActive?: boolean;
port?: number;
/** ClientBaustein: the server it talks to (Java's zielIPAdresse, zielPort). */
@@ -136,6 +151,23 @@ export interface FlsApplication {
maxPeers?: number | null;
/** Firewall: the personal firewall. */
firewall?: FlsFirewall;
/** EmailServer: its domain (Java keeps "filius.de" for one its pattern rejects). */
mailDomain?: string;
/** EmailServer: the accounts of the bean list. */
beanAccounts?: JavaEmailAccount[];
/**
* EmailServer: the accounts of /mailserver/konten.txt, which Java reads at
* every simulation start — null without the file (Java then starts with
* none), a failure where Java throws.
*/
fileAccounts?: JavaEmailAccount[] | JavaFailure | null;
/** EmailAnwendung: its folders (Java's empfangene, gesendete, erstellte Nachrichten). */
received?: JavaEmail[];
sent?: JavaEmail[];
drafts?: JavaEmail[];
addressBook?: JavaContact[];
/** EmailAnwendung: the account of /konten.txt, null without one. */
fileAccount?: JavaEmailAccount | null;
/** The application's JavaBean, for the e-mail layer. */
source: JavaObject;
}
@@ -188,7 +220,7 @@ export function readSystemSoftware(device: JavaObject, javaClass: FlsNodeClass,
case 'Notebook':
result.ssid = optionalString(value('ssid'), 'ssid', warn) ?? null;
result.fileSystem = fileSystem(value('dateisystem'), warn);
result.applications = applications(value('installierteAnwendungen'), warn);
result.applications = applications(value('installierteAnwendungen'), result.fileSystem, warn);
break;
case 'Vermittlungsrechner':
result.ripEnabled = boolean(value('ripEnabled'), false, 'ripEnabled', warn);
@@ -234,7 +266,7 @@ function modemAddress(value: string | null | undefined): string | null {
* a project's own application, compiled into projekt/anwendungen — is left
* out: this reader cannot run Java classes.
*/
function applications(map: JavaValue | undefined, warn: Warn): FlsApplication[] {
function applications(map: JavaValue | undefined, root: FlsFileTree, warn: Warn): FlsApplication[] {
const result: FlsApplication[] = [];
for (const [key, application] of mapEntries(map)) {
if (!isJavaObject(application)) {
@@ -278,6 +310,24 @@ function applications(map: JavaValue | undefined, warn: Warn): FlsApplication[]
case 'filius.software.firewall.Firewall':
entry.firewall = firewallOf([application], true, warn);
break;
case 'filius.software.email.EmailServer': {
entry.isActive = boolean(at('aktiv'), false, 'EmailServer aktiv', warn);
const domain = optionalString(at('mailDomain'), 'EmailServer mailDomain', warn);
entry.mailDomain = typeof domain === 'string' && JAVA_DOMAIN.test(domain) ? domain : 'filius.de';
entry.beanAccounts = javaEmailAccounts(at('listeBenutzerkonten'), warn);
const file = javaFileContent(root, EMAIL_SERVER_FILE);
entry.fileAccounts = file === undefined ? null : readJavaEmailServerFile(file);
break;
}
case 'filius.software.email.EmailAnwendung': {
entry.received = javaEmails(at('empfangeneNachrichten'), warn);
entry.sent = javaEmails(at('gesendeteNachrichten'), warn);
entry.drafts = javaEmails(at('erstellteNachrichten'), warn);
entry.addressBook = javaContacts(at('adressbuch'), warn);
const file = javaFileContent(root, EMAIL_CLIENT_FILE);
entry.fileAccount = file === undefined ? null : readJavaEmailClientFile(file);
break;
}
}
result.push(entry);
}