Files
collabvm-1.2.ts/src/QMPClient.ts

153 lines
4.4 KiB
TypeScript
Raw Normal View History

2023-01-31 22:00:30 -05:00
import EventEmitter from "events";
import { Socket } from "net";
import { Mutex } from "async-mutex";
2023-02-24 22:54:28 -05:00
import log from "./log.js";
2023-04-06 21:59:37 +02:00
import { EOL } from "os";
2023-01-31 22:00:30 -05:00
export default class QMPClient extends EventEmitter {
socketfile : string;
sockettype: string;
2023-01-31 22:00:30 -05:00
socket : Socket;
connected : boolean;
sentConnected : boolean;
cmdMutex : Mutex; // So command outputs don't get mixed up
constructor(socketfile : string, sockettype: string) {
2023-01-31 22:00:30 -05:00
super();
this.sockettype = sockettype;
2023-01-31 22:00:30 -05:00
this.socketfile = socketfile;
this.socket = new Socket();
this.connected = false;
this.sentConnected = false;
this.cmdMutex = new Mutex();
2023-01-31 22:00:30 -05:00
}
connect() : Promise<void> {
return new Promise((res, rej) => {
if (this.connected) {res(); return;}
try {
if(this.sockettype == "tcp:") {
let _sock = this.socketfile.split(':');
this.socket.connect(parseInt(_sock[1]), _sock[0]);
}else{
this.socket.connect(this.socketfile);
}
} catch (e) {
this.onClose();
}
this.connected = true;
2023-02-24 22:54:28 -05:00
this.socket.on('error', () => false); // Disable throwing if QMP errors
2023-04-06 21:59:37 +02:00
this.socket.on('data', (data) => {
data.toString().split(EOL).forEach(instr => this.onData(instr));
});
this.socket.on('close', () => this.onClose());
this.once('connected', () => {res();});
})
}
disconnect() {
this.connected = false;
this.socket.destroy();
2023-01-31 22:00:30 -05:00
}
2023-04-06 21:59:37 +02:00
private async onData(data : string) {
let msg;
2023-02-07 22:38:41 -05:00
try {
2023-04-06 21:59:37 +02:00
msg = JSON.parse(data);
} catch {
return;
}
if (msg.QMP !== undefined) {
if (this.sentConnected)
return;
await this.execute({ execute: "qmp_capabilities" });
2023-04-05 20:34:15 +02:00
this.emit('connected');
this.sentConnected = true;
2023-04-05 20:34:15 +02:00
}
2023-04-06 21:59:37 +02:00
if (msg.return !== undefined && Object.keys(msg.return).length)
this.emit("qmpreturn", msg.return);
else if(msg.event !== undefined) {
switch(msg.event) {
case "STOP":
{
log("INFO", "The VM was shut down, restarting...");
this.reboot();
break;
}
case "RESET":
{
2023-04-07 20:36:56 +02:00
log("INFO", "QEMU reset event occured");
this.resume();
break;
};
2023-04-06 21:59:37 +02:00
default: break;
}
}else
// for now just return an empty string.
// This is a giant hack but avoids a deadlock
this.emit("qmpreturn", '');
2023-01-31 22:00:30 -05:00
}
private onClose() {
this.connected = false;
this.sentConnected = false;
2023-02-07 22:38:41 -05:00
if (this.socket.readyState === 'open')
this.socket.destroy();
2023-02-07 22:38:41 -05:00
this.cmdMutex.cancel();
this.cmdMutex.release();
this.socket = new Socket();
2023-01-31 22:00:30 -05:00
this.emit('close');
}
async reboot() {
2023-02-07 22:38:41 -05:00
if (!this.connected)
return;
await this.execute({"execute": "system_reset"});
}
async resume() {
if (!this.connected)
return;
await this.execute({"execute": "cont"});
}
async ExitQEMU() {
2023-02-07 22:38:41 -05:00
if (!this.connected)
return;
await this.execute({"execute": "quit"});
}
execute(args : object) {
return new Promise(async (res, rej) => {
var result:any;
try {
result = await this.cmdMutex.runExclusive(() => {
// I kinda hate having two promises but IDK how else to do it /shrug
return new Promise((reso, reje) => {
this.once('qmpreturn', (e) => {
reso(e);
});
this.socket.write(JSON.stringify(args));
});
});
2023-02-07 22:38:41 -05:00
} catch {
res({});
}
res(result);
});
}
runMonitorCmd(command : string) {
return new Promise(async (res, rej) => {
2023-02-07 22:38:41 -05:00
res(await this.execute({execute: "human-monitor-command", arguments: {"command-line": command}}));
});
}
2023-04-07 20:36:56 +02:00
}