systemquery/src/lib/SystemQuery.mjs

124 lines
3.8 KiB
JavaScript
Raw Normal View History

"use strict";
import { once, EventEmitter } from 'events';
import log from './io/NamespacedLog.mjs'; const l = log("systemquery");
import Agent from './agent/Agent.mjs';
import InfoBroker from './core/InfoBroker.mjs';
import HttpSubsystem from './agent/subsystems/http/HttpSubsystem.mjs';
import ItemQueue from './async/ItemQueue.mjs';
class SystemQuery extends EventEmitter {
constructor(config, mode = "agent") {
super();
// The operating mode. Possible values: agent [default], query_client
// TODO: Is this the best way of doing this? Maybe we should have a separate class for this? I'm not sure.
this.mode = mode;
this.config = config;
this.info = new InfoBroker();
this.http = new HttpSubsystem(this);
}
async init() {
///
// 1: Create agent
///
this.agent = new Agent(this.config);
this.http.init(
this.config.net.http.port,
this.config.net.http.bind_address
);
///
// 2: Attach listeners
///
this.agent.on("message-query", this.handle_query.bind(this));
this.agent.on("message-query-response", this.handle_query_response.bind(this));
///
// 3: Start agent
///
await this.agent.init();
}
async handle_query(peer, msg) {
// 1: Validate input
if(typeof msg.name !== "string"
|| !this.info.is_valid_table(msg.name)) return;
// 2: Fetch system info
let table = await this.info.fetch_table(msg.name);
if(table === null) return;
// 3: Return to requester
await peer.send("query-response", { name: msg.name, table });
}
async handle_query_response(peer, msg) {
l.log(`query-response from ${peer.id_short}`, msg);
}
async capture_query_response(event_name, ac) {
return await once(this.agent, `message-${event_name}`, { signal: ac });
}
async *fetch_table(name) {
// If it isn't valid for us, it ain't gonna be valid for anyone else....
if(!this.info.is_valid_table(name)) return null;
l.log(`fetch table ${name} start`)
const queue = new ItemQueue();
const handle_response = (peer, msg) => {
// TODO: Validate response. Is it the right table? Is it even a table? Note that multiple fetch_table calls may be running in parallel, so we should not make too much of a fuss if we get the wrong table by accident.
// TODO: It would be seriously cool to have fetch_table() be an async generator that yields pairs of peer ids and tables as they come in.
if(typeof msg !== "object"
|| typeof msg.table !== "object"
|| typeof msg.name !== "string") {
l.debug(`Discarding invalid table from peer ${peer.id_short}`);
return;
}
// If it's not the right table, ignore it
if(msg.name !== name) return;
queue.enqueue({ peer, table: msg.table });
};
this.agent.on("message-query-response", handle_response);
// Only *after* we have our listeners in place do we then broadcast the
// query. Note that despite not having entered the below while loop yet,
// we do not drop any messages due to the use of the ItemQueue.
this.agent.broadcast(`query`, { name });
let peers_seen = [];
while(peers_seen.length < this.agent.connected_peers.length) {
l.info(`peers_seen:`, peers_seen, `connected peers:`, this.agent.connected_peers.length);
let next = await queue.dequeue(this.config.net.table_timeout * 1000);
if(typeof next === "undefined") // We timed out
break;
l.log(`fetch table DEBUG`, next);
if(!peers_seen.includes(next.peer.id))
peers_seen.push(next.peer.id);
yield next;
}
this.agent.off("message-query-response", handle_response);
l.log(`fetch table END`);
// FUTURE: Add a cache here? Note that we also do not listen for query responses unless we've asked for a table.
}
}
SystemQuery.Create = async function(config) {
let result = new SystemQuery(config);
await result.init();
return result;
}
export default SystemQuery;