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:
@@ -155,6 +155,28 @@ export function gnutellaPacket(text: string): GnutellaPacket | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A search result as the window's table shows it (updateErgebnisTabelle):
|
||||
* the address before its first "/", the name up to the last ":" of what
|
||||
* follows, the size after it (" 8 B", with Java's space).
|
||||
*/
|
||||
export function gnutellaResultRow(result: string): readonly [string, string, string] {
|
||||
const [address = '', rest = ''] = result.split('/').filter((part) => part !== '');
|
||||
const colon = rest.lastIndexOf(':');
|
||||
return [address, colon < 0 ? rest : rest.slice(0, colon), colon < 0 ? '' : rest.slice(colon + 1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of peers typed into the window's settings, as it takes it: a
|
||||
* whole number of Java's int; undefined for anything else. Java takes a
|
||||
* single digit only (its pattern musterNurZahl is "\d"): 12 is refused.
|
||||
*/
|
||||
export function gnutellaPeerCountOfText(text: string): number | undefined {
|
||||
if (!/^[0-9]+$/.test(text)) return undefined;
|
||||
const count = Number(text);
|
||||
return count <= 2_147_483_647 ? count : undefined;
|
||||
}
|
||||
|
||||
/** What the program tells its window: its known peers, or its search results ("address/name: size B"), whenever they change. */
|
||||
export type GnutellaLogEntry = { readonly kind: 'peers'; readonly peers: readonly string[] } | { readonly kind: 'results'; readonly results: readonly string[] };
|
||||
|
||||
@@ -221,7 +243,9 @@ export class GnutellaPeer {
|
||||
readonly log: ProgramLog<GnutellaLogEntry>;
|
||||
private peersValue: string[] = [];
|
||||
private resultsValue: string[] = [];
|
||||
private readonly maximumKnownPeers: number;
|
||||
private maximumKnownPeersValue: number;
|
||||
/** The connections of the program's own pings, queries and downloads (mitarbeiterEigeneAnfragen) while they are open. */
|
||||
private readonly ownSockets = new Set<Id>();
|
||||
/**
|
||||
* The ids of the program's own pings and queries (eigeneAnfragen), and of
|
||||
* those it sent on (schonmalVerschicktListe). Java also keeps others'
|
||||
@@ -238,7 +262,7 @@ export class GnutellaPeer {
|
||||
configuration: GnutellaConfiguration,
|
||||
) {
|
||||
this.log = new ProgramLog(engine);
|
||||
this.maximumKnownPeers = configuration.maximumKnownPeers;
|
||||
this.maximumKnownPeersValue = configuration.maximumKnownPeers;
|
||||
this.server = new GnutellaServer(engine, nodeId, this);
|
||||
}
|
||||
|
||||
@@ -252,6 +276,26 @@ export class GnutellaPeer {
|
||||
return this.resultsValue;
|
||||
}
|
||||
|
||||
/** How many peers the program keeps at most (getMaxTeilnehmerZahl). */
|
||||
get maximumKnownPeers(): number {
|
||||
return this.maximumKnownPeersValue;
|
||||
}
|
||||
|
||||
/** setMaxTeilnehmerZahl: for the peers to come; those known stay. */
|
||||
setMaximumKnownPeers(count: number): void {
|
||||
this.maximumKnownPeersValue = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* abbrechenSuche: every connection of the program's own requests closed —
|
||||
* its searches' (hits still on their way are lost), its joins', its
|
||||
* downloads' — as Java ends each worker and closes its socket.
|
||||
*/
|
||||
cancelSearch(): void {
|
||||
// Each leaves the set when its thread ends.
|
||||
for (const socketId of this.ownSockets) this.engine.requestTcpClose(socketId);
|
||||
}
|
||||
|
||||
/** The node's first address (Betriebssystem.primaryIPAdresse). */
|
||||
private get ownAddress(): string {
|
||||
return this.engine.networkInterfaces(this.nodeId)[0]?.ipAddress ?? '';
|
||||
@@ -313,7 +357,7 @@ export class GnutellaPeer {
|
||||
|
||||
/** hinzuTeilnehmer: a new peer while there is room. */
|
||||
private addPeer(address: string): void {
|
||||
if (this.peersValue.includes(address) || this.peersValue.length >= this.maximumKnownPeers) return;
|
||||
if (this.peersValue.includes(address) || this.peersValue.length >= this.maximumKnownPeersValue) return;
|
||||
this.peersValue = [...this.peersValue, address];
|
||||
this.log.write({ kind: 'peers', peers: this.peersValue });
|
||||
}
|
||||
@@ -420,8 +464,10 @@ export class GnutellaPeer {
|
||||
private *fetchFile(owner: string, name: string): Steps {
|
||||
const engine = this.engine;
|
||||
const socketId = engine.openTcpClientSocket(this.nodeId, owner, GNUTELLA_PORT);
|
||||
if (socketId === undefined || (yield* engine.connectTcpSteps(socketId)) !== 'connected') {
|
||||
if (socketId !== undefined) engine.requestTcpClose(socketId);
|
||||
if (socketId === undefined) return;
|
||||
this.ownSockets.add(socketId);
|
||||
if ((yield* engine.connectTcpSteps(socketId)) !== 'connected') {
|
||||
this.closeOwn(socketId);
|
||||
return;
|
||||
}
|
||||
yield* engine.sendTcpSteps(socketId, new TextEncoder().encode(formatHttpRequest({ method: HTTP_GET, path: name, host: owner })));
|
||||
@@ -431,7 +477,13 @@ export class GnutellaPeer {
|
||||
const path = `${GNUTELLA_FOLDER}/${name}`;
|
||||
this.keepFile(path, fileContentOfJavaType(response.contentType ?? '', response.body ?? '', path));
|
||||
}
|
||||
engine.requestTcpClose(socketId);
|
||||
this.closeOwn(socketId);
|
||||
}
|
||||
|
||||
/** A connection closed, and no longer one of the program's own (it may be so already). */
|
||||
private closeOwn(socketId: Id): void {
|
||||
this.engine.requestTcpClose(socketId);
|
||||
this.ownSockets.delete(socketId);
|
||||
}
|
||||
|
||||
/** speicherDatei: over a file of that name in any case, keeping its name (as the file system does). */
|
||||
@@ -461,6 +513,17 @@ export class GnutellaPeer {
|
||||
});
|
||||
}
|
||||
|
||||
/** The files of /peer2peer as the window's table shows them (updateDateiTabelle): name and type. */
|
||||
fileRows(): readonly (readonly [string, string])[] {
|
||||
const files = this.engine.state.topologySnapshot.fileSystemsByNodeId.get(this.nodeId);
|
||||
if (files === undefined || !files.contains(GNUTELLA_FOLDER)) return [];
|
||||
return files.entriesInCreationOrder(GNUTELLA_FOLDER).flatMap((entry) => {
|
||||
const content = entry.content;
|
||||
const name = entry.path.slice(entry.path.lastIndexOf('/') + 1);
|
||||
return content.kind === 'directory' ? [] : [[name, javaFileTypeName(name, content)] as const];
|
||||
});
|
||||
}
|
||||
|
||||
/** A file at the path, found in any case (Dateisystem.holeDatei); none for a folder or nothing. */
|
||||
private sharedFile(path: string): { readonly path: string; readonly name: string; readonly content: Exclude<VirtualFileContent, { kind: 'directory' }> } | undefined {
|
||||
const files = this.engine.state.topologySnapshot.fileSystemsByNodeId.get(this.nodeId);
|
||||
@@ -480,8 +543,10 @@ export class GnutellaPeer {
|
||||
private *sendTo(address: string, packet: GnutellaPacket): Steps {
|
||||
const engine = this.engine;
|
||||
const socketId = engine.openTcpClientSocket(this.nodeId, address, GNUTELLA_PORT);
|
||||
if (socketId === undefined || (yield* engine.connectTcpSteps(socketId)) !== 'connected') {
|
||||
if (socketId !== undefined) engine.requestTcpClose(socketId);
|
||||
if (socketId === undefined) return;
|
||||
if (this.ownRequests.has(packet.guid)) this.ownSockets.add(socketId);
|
||||
if ((yield* engine.connectTcpSteps(socketId)) !== 'connected') {
|
||||
this.closeOwn(socketId);
|
||||
return;
|
||||
}
|
||||
yield* engine.sendTcpSteps(socketId, new TextEncoder().encode(gnutellaText(packet)));
|
||||
@@ -492,6 +557,6 @@ export class GnutellaPeer {
|
||||
if (answer?.kind === 'pong') this.takePong(answer);
|
||||
else if (answer?.kind === 'queryHit') this.takeQueryHit(answer);
|
||||
}
|
||||
engine.requestTcpClose(socketId);
|
||||
this.closeOwn(socketId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user