|
@@ -1,4 +1,5 @@
|
|
|
const fs = require('fs')
|
|
|
+const os = require('os')
|
|
|
const md5 = require('md5')
|
|
|
const { promisify } = require('util')
|
|
|
const PythonWorker = require('./pythonWorker')
|
|
@@ -22,26 +23,37 @@ const typeDefs = `
|
|
|
|
|
|
type Port {
|
|
|
id: String!
|
|
|
- device: String!
|
|
|
interfaceName: String!
|
|
|
+ host: String!
|
|
|
+ device: String!
|
|
|
name: String
|
|
|
description: String
|
|
|
}
|
|
|
|
|
|
+ type Worker {
|
|
|
+ pid: Int
|
|
|
+ killed: Boolean!
|
|
|
+ signalCode: String
|
|
|
+ exitCode: Int
|
|
|
+ spawnfile: String!
|
|
|
+ spawnargs: [String]!
|
|
|
+ error: Int!
|
|
|
+ data: Int!
|
|
|
+ }
|
|
|
+
|
|
|
type Connection {
|
|
|
id: ID!
|
|
|
interfaceName: String!
|
|
|
+ host: String!
|
|
|
device: String!
|
|
|
- }
|
|
|
-
|
|
|
- type Worker {
|
|
|
- interfaceName: String!
|
|
|
- workerScript: String!
|
|
|
+ workerInfo: Worker
|
|
|
}
|
|
|
|
|
|
type Interface {
|
|
|
interfaceName: String!
|
|
|
+ host: String!
|
|
|
workerScript: String!
|
|
|
+ workerInfo: Worker
|
|
|
ports: [Port]!
|
|
|
connections: [Connection]!
|
|
|
options: [Option]!
|
|
@@ -49,17 +61,21 @@ const typeDefs = `
|
|
|
|
|
|
extend type Query {
|
|
|
interfaces(force: Boolean): [Interface]!
|
|
|
- ports(interfaceName: String!, force: Boolean): [Port]!
|
|
|
+ ports(interfaceName: String, force: Boolean): [Port]!
|
|
|
connections: [Connection]!
|
|
|
+ connection(id: ID!): Connection!
|
|
|
}
|
|
|
|
|
|
extend type Mutation {
|
|
|
connect(interfaceName: String!, device: String!): Connection!
|
|
|
+ spawnWorker(id: ID!): Worker!
|
|
|
+ endWorker(id: ID!): Worker!
|
|
|
+ killWorker(id: ID!): Worker!
|
|
|
connectionCommand(connectionId: ID!, type: String!, string: String!, options: String): String!
|
|
|
}
|
|
|
`
|
|
|
|
|
|
-async function findWorkers () {
|
|
|
+async function findWorkers() {
|
|
|
// Find all files in ./python_workers that end in _worker.py
|
|
|
const fileNames = await readdir(`${__dirname}/python_workers`)
|
|
|
const workerFiles = fileNames.filter(fileName => fileName.includes('_worker.py'))
|
|
@@ -76,36 +92,46 @@ async function findWorkers () {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-async function findInterfaces () {
|
|
|
+async function findInterfaces() {
|
|
|
// Try to identify workers if necessary
|
|
|
- if (!state.workers.length) await findWorkers()
|
|
|
+ //if (!state.workers.length)
|
|
|
+ await findWorkers()
|
|
|
|
|
|
// Generate an interface for each worker
|
|
|
- state.workers.forEach(worker => {
|
|
|
+ const workerPromises = state.workers.map(async worker => {
|
|
|
const { workerScript, interfaceName } = worker
|
|
|
// Skip existing interfaces
|
|
|
if (state.interfaces.find(iface => iface.interfaceName === interfaceName)) return null
|
|
|
+ const pythonWorker = new PythonWorker(workerScript)
|
|
|
+ const { res, error } = await pythonWorker.spawn()
|
|
|
+ if (error) {
|
|
|
+ return
|
|
|
+ }
|
|
|
state.interfaces.push({
|
|
|
interfaceName,
|
|
|
workerScript,
|
|
|
- worker: new PythonWorker(workerScript),
|
|
|
+ worker: pythonWorker,
|
|
|
ports: [],
|
|
|
connections: [],
|
|
|
options: []
|
|
|
})
|
|
|
})
|
|
|
+ await Promise.all(workerPromises)
|
|
|
}
|
|
|
|
|
|
-async function interfaces (parent, args, ctx, info) {
|
|
|
+async function interfaces(parent, args, ctx, info) {
|
|
|
const { force } = args
|
|
|
// Try to identify interfaces if necessary
|
|
|
- if (!state.interfaces.length || force) await findInterfaces()
|
|
|
+ //if (!state.interfaces.length || force)
|
|
|
+ await findInterfaces()
|
|
|
|
|
|
return state.interfaces.map(iface => {
|
|
|
- const { workerScript, interfaceName } = iface
|
|
|
+ const { workerScript, interfaceName, worker } = iface
|
|
|
return {
|
|
|
interfaceName,
|
|
|
+ host: os.hostname(),
|
|
|
workerScript,
|
|
|
+ workerInfo: workerInfo(worker, args, ctx, info),
|
|
|
ports: ports(interfaceName, args, ctx, info),
|
|
|
connections: connections(interfaceName, args, ctx, info),
|
|
|
options: options(interfaceName, args, ctx, info)
|
|
@@ -113,12 +139,13 @@ async function interfaces (parent, args, ctx, info) {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-async function findPorts (interfaceName) {
|
|
|
+async function findPorts(interfaceName) {
|
|
|
// Generate all ports for the interface
|
|
|
const iface = state.interfaces.find(iface => iface.interfaceName === interfaceName)
|
|
|
|
|
|
- const { data, error } = await iface.worker.send({ type: 'ports' })
|
|
|
- if (error) throw new Error(JSON.stringify(error))
|
|
|
+ const { data, error, pythonError } = await iface.worker.send({ type: 'ports' })
|
|
|
+ if (error) throw new Error(error)
|
|
|
+ if (typeof pythonError !== "undefined") throw new Error(pythonError)
|
|
|
data.forEach(port => {
|
|
|
const id = port.name || port.device
|
|
|
// Skip existing ports
|
|
@@ -126,6 +153,7 @@ async function findPorts (interfaceName) {
|
|
|
const newPort = {
|
|
|
id,
|
|
|
interfaceName,
|
|
|
+ host: os.hostname(),
|
|
|
...port
|
|
|
}
|
|
|
state.ports.push(newPort)
|
|
@@ -133,7 +161,7 @@ async function findPorts (interfaceName) {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-async function ports (parent, args, ctx, info) {
|
|
|
+async function ports(parent, args, ctx, info) {
|
|
|
const { force, interfaceName } = args
|
|
|
const ifName = interfaceName || parent
|
|
|
|
|
@@ -148,14 +176,15 @@ async function ports (parent, args, ctx, info) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function findOptions (interfaceName) {
|
|
|
+async function findOptions(interfaceName) {
|
|
|
const iface = state.interfaces.find(iface => iface.interfaceName === interfaceName)
|
|
|
- const { data, error } = await iface.worker.send({ type: 'options' })
|
|
|
- if (error) throw new Error(JSON.stringify(error))
|
|
|
+ const { data, error, pythonError } = await iface.worker.send({ type: 'options' })
|
|
|
+ if (error) throw new Error(error)
|
|
|
+ if (typeof pythonError !== "undefined") throw new Error(pythonError)
|
|
|
iface.options.push(...data)
|
|
|
}
|
|
|
|
|
|
-async function options (parent, args, ctx, info) {
|
|
|
+async function options(parent, args, ctx, info) {
|
|
|
const iface = state.interfaces.find(iface => iface.interfaceName === parent)
|
|
|
|
|
|
// Try to find options if necessary
|
|
@@ -163,7 +192,21 @@ async function options (parent, args, ctx, info) {
|
|
|
return iface.options
|
|
|
}
|
|
|
|
|
|
-async function connections (parent, args, ctx, info) {
|
|
|
+function workerInfo(parent, args, ctx, info) {
|
|
|
+ const { killed, exitCode, signalCode, spawnargs, spawnfile, pid } = parent.pythonShell
|
|
|
+ return {
|
|
|
+ pid,
|
|
|
+ killed,
|
|
|
+ exitCode,
|
|
|
+ signalCode,
|
|
|
+ spawnfile,
|
|
|
+ spawnargs,
|
|
|
+ error: parent.error.length,
|
|
|
+ data: parent.data.length
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function connections(parent, args, ctx, info) {
|
|
|
if (parent) {
|
|
|
const iface = state.interfaces.find(iface => iface.interfaceName === parent)
|
|
|
return iface.connections
|
|
@@ -172,40 +215,88 @@ async function connections (parent, args, ctx, info) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-async function connect (parent, args, ctx, info) {
|
|
|
+async function connection(parent, args, context, info) {
|
|
|
+ const connection = state.connections.find(connection => connection.id === args.id)
|
|
|
+ return connection
|
|
|
+}
|
|
|
+
|
|
|
+async function connect(parent, args, ctx, info) {
|
|
|
const { interfaceName, device } = args
|
|
|
const iface = state.interfaces.find(iface => iface.interfaceName === interfaceName)
|
|
|
const id = md5(interfaceName + device)
|
|
|
if (iface.connections.find(connection => connection.id === id)) throw new Error('already connected.')
|
|
|
+ const pythonWorker = new PythonWorker(iface.workerScript)
|
|
|
+ const spawnData = await pythonWorker.spawn()
|
|
|
+ if (spawnData.error) throw new Error(spawnData.error)
|
|
|
const connection = {
|
|
|
id,
|
|
|
device,
|
|
|
interfaceName,
|
|
|
- worker: new PythonWorker(iface.workerScript)
|
|
|
+ host: os.hostname(),
|
|
|
+ worker: pythonWorker,
|
|
|
+ workerInfo: (parent, args, context, info) => workerInfo(pythonWorker, args, context, info)
|
|
|
}
|
|
|
- const { error } = await connection.worker.send({ type: 'connect', device })
|
|
|
- if (error) throw new Error(error)
|
|
|
+ const connectionData = await connection.worker.send({ type: 'connect', device })
|
|
|
+ if (spawnData.error) throw new Error(spawnData.error)
|
|
|
+ if (spawnData.pythonError) throw new Error(spawnData.pythonError)
|
|
|
iface.connections.push(connection)
|
|
|
state.connections.push(connection)
|
|
|
return connection
|
|
|
}
|
|
|
|
|
|
-async function connectionCommand (parent, args, ctx, info) {
|
|
|
+async function connectionCommand(parent, args, ctx, info) {
|
|
|
const { connectionId, type, string, options } = args
|
|
|
const connection = state.connections.find(connection => connection.id === connectionId)
|
|
|
- const { data } = await connection.worker.send({ type, string, options })
|
|
|
+ const { data, error, pythonError } = await connection.worker.send({ type, string, options })
|
|
|
+ if (error) throw new Error(JSON.stringify(error))
|
|
|
+ if (typeof pythonError !== "undefined") throw new Error(pythonError)
|
|
|
return data.response
|
|
|
}
|
|
|
|
|
|
+//TODO Also find connections in interfaces.
|
|
|
+async function endWorker(parent, args, ctx, info) {
|
|
|
+ const { id } = args
|
|
|
+ const connection = state.connections.find(connection => connection.id === id)
|
|
|
+ const { data, error, pythonError } = await connection.worker.end()
|
|
|
+ console.log(data, error, pythonError)
|
|
|
+ if (error) throw new Error(JSON.stringify(error))
|
|
|
+ if (typeof pythonError !== "undefined") throw new Error(pythonError)
|
|
|
+ return connection.workerInfo
|
|
|
+}
|
|
|
+
|
|
|
+async function killWorker(parent, args, ctx, info) {
|
|
|
+ const { id } = args
|
|
|
+ const connection = state.connections.find(connection => connection.id === id)
|
|
|
+ const { data, error, pythonError } = await connection.worker.kill()
|
|
|
+ console.log(data, error, pythonError)
|
|
|
+ if (error) throw new Error(JSON.stringify(error))
|
|
|
+ if (typeof pythonError !== "undefined") throw new Error(pythonError)
|
|
|
+ return connection.workerInfo
|
|
|
+}
|
|
|
+
|
|
|
+async function spawnWorker(parent, args, ctx, info) {
|
|
|
+ const { id } = args
|
|
|
+ const connection = state.connections.find(connection => connection.id === id)
|
|
|
+ const { data, error, pythonError } = await connection.worker.spawn()
|
|
|
+ console.log(data, error, pythonError)
|
|
|
+ if (error) throw new Error(error)
|
|
|
+ if (typeof pythonError !== "undefined") throw new Error(error)
|
|
|
+ return connection.workerInfo
|
|
|
+}
|
|
|
+
|
|
|
const resolvers = {
|
|
|
Query: {
|
|
|
interfaces,
|
|
|
ports,
|
|
|
- connections
|
|
|
+ connections,
|
|
|
+ connection
|
|
|
},
|
|
|
Mutation: {
|
|
|
connect,
|
|
|
- connectionCommand
|
|
|
+ connectionCommand,
|
|
|
+ killWorker,
|
|
|
+ endWorker,
|
|
|
+ spawnWorker
|
|
|
}
|
|
|
}
|
|
|
|