|
@@ -30,8 +30,8 @@ const state = {
|
|
lastScan: {
|
|
lastScan: {
|
|
workers: Date.now() - 100000,
|
|
workers: Date.now() - 100000,
|
|
ports: Date.now() - 100000,
|
|
ports: Date.now() - 100000,
|
|
- connections: Date.now() - 100000,
|
|
|
|
- },
|
|
|
|
|
|
+ connections: Date.now() - 100000
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
const typeDefs = `
|
|
const typeDefs = `
|
|
@@ -79,7 +79,7 @@ const typeDefs = `
|
|
type Connection {
|
|
type Connection {
|
|
id: ID!
|
|
id: ID!
|
|
port: Port!
|
|
port: Port!
|
|
- workerProcess: WorkerProcess
|
|
|
|
|
|
+ workerProcess: WorkerProcess!
|
|
}
|
|
}
|
|
|
|
|
|
input ConnectionCommand {
|
|
input ConnectionCommand {
|
|
@@ -90,8 +90,8 @@ const typeDefs = `
|
|
|
|
|
|
extend type Query {
|
|
extend type Query {
|
|
interfaces: [Interface]!
|
|
interfaces: [Interface]!
|
|
- interface(id: ID, interfaceName: String): Interface!
|
|
|
|
- ports(interfaceName: String, force: Boolean): [Port]!
|
|
|
|
|
|
+ interface(id: ID!): Interface!
|
|
|
|
+ ports: [Port]!
|
|
port(id: ID!): Port!
|
|
port(id: ID!): Port!
|
|
connections: [Connection]!
|
|
connections: [Connection]!
|
|
connection(id: ID!): Connection!
|
|
connection(id: ID!): Connection!
|
|
@@ -108,7 +108,7 @@ const typeDefs = `
|
|
/**
|
|
/**
|
|
* INTERFACE SECTION
|
|
* INTERFACE SECTION
|
|
*/
|
|
*/
|
|
-async function findInterfaces() {
|
|
|
|
|
|
+async function findInterfaces () {
|
|
// 1. Don't check more frequently than once per second.
|
|
// 1. Don't check more frequently than once per second.
|
|
// if (state.lastScan.workers + 1000 > Date.now()) return null
|
|
// if (state.lastScan.workers + 1000 > Date.now()) return null
|
|
// state.lastScan.workers = Date.now()
|
|
// state.lastScan.workers = Date.now()
|
|
@@ -131,8 +131,9 @@ async function findInterfaces() {
|
|
)
|
|
)
|
|
if (foundInterface) {
|
|
if (foundInterface) {
|
|
// b. If it was modified, save the modification time.
|
|
// b. If it was modified, save the modification time.
|
|
- if (foundInterface.workerScript.mtime < mtime)
|
|
|
|
|
|
+ if (foundInterface.workerScript.mtime < mtime) {
|
|
foundInterface.workerScript.updated = mtime
|
|
foundInterface.workerScript.updated = mtime
|
|
|
|
+ }
|
|
return
|
|
return
|
|
}
|
|
}
|
|
// c. Spawn a new worker connection.
|
|
// c. Spawn a new worker connection.
|
|
@@ -145,30 +146,19 @@ async function findInterfaces() {
|
|
interfaceName,
|
|
interfaceName,
|
|
workerScript,
|
|
workerScript,
|
|
workerProcess,
|
|
workerProcess,
|
|
- ports: [],
|
|
|
|
- connections: [],
|
|
|
|
- options: await options(workerProcess),
|
|
|
|
|
|
+ options: await options(workerProcess)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
await Promise.all(workerPromises)
|
|
await Promise.all(workerPromises)
|
|
}
|
|
}
|
|
|
|
|
|
-async function interfaces(parent, args, context, info) {
|
|
|
|
|
|
+async function interfaces (parent, args, context, info) {
|
|
await findInterfaces()
|
|
await findInterfaces()
|
|
- return state.interfaces.map(iface => ({
|
|
|
|
- ...iface,
|
|
|
|
- ports: ports(iface.interfaceName, args, context, info),
|
|
|
|
- // Serialize worker process
|
|
|
|
- workerProcess: workerProcess(iface.workerProcess, args, context, info),
|
|
|
|
- }))
|
|
|
|
|
|
+ return state.interfaces
|
|
}
|
|
}
|
|
|
|
|
|
-async function interface(parent, args, context, info) {
|
|
|
|
|
|
+async function iface (parent, { id }, context, info) {
|
|
await findInterfaces()
|
|
await findInterfaces()
|
|
- const { id, interfaceName } = args
|
|
|
|
- if (!id && !interfaceName) {
|
|
|
|
- throw new Error('Either id or interfaceName needs to be provided!')
|
|
|
|
- }
|
|
|
|
const iface = state.interfaces.find(
|
|
const iface = state.interfaces.find(
|
|
iface => iface.id === id || iface.interfaceName === interfaceName
|
|
iface => iface.id === id || iface.interfaceName === interfaceName
|
|
)
|
|
)
|
|
@@ -177,41 +167,41 @@ async function interface(parent, args, context, info) {
|
|
}
|
|
}
|
|
return {
|
|
return {
|
|
...iface,
|
|
...iface,
|
|
- workerProcess: workerProcess(iface.workerProcess, args, context, info),
|
|
|
|
|
|
+ workerProcess
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-async function options(workerProcess) {
|
|
|
|
|
|
+async function options (workerProcess) {
|
|
const { data, error } = await workerProcess.send({
|
|
const { data, error } = await workerProcess.send({
|
|
- type: 'options',
|
|
|
|
|
|
+ type: 'options'
|
|
})
|
|
})
|
|
if (error) throw new Error(error)
|
|
if (error) throw new Error(error)
|
|
return data
|
|
return data
|
|
}
|
|
}
|
|
|
|
|
|
-function workerProcess(parent, args, ctx, info) {
|
|
|
|
|
|
+function workerProcess (parent, args, context, info) {
|
|
const {
|
|
const {
|
|
killed,
|
|
killed,
|
|
exitCode,
|
|
exitCode,
|
|
signalCode,
|
|
signalCode,
|
|
spawnargs,
|
|
spawnargs,
|
|
spawnfile,
|
|
spawnfile,
|
|
- pid,
|
|
|
|
- } = parent.pythonShell
|
|
|
|
|
|
+ pid
|
|
|
|
+ } = parent.workerProcess.pythonShell
|
|
return {
|
|
return {
|
|
pid,
|
|
pid,
|
|
killed,
|
|
killed,
|
|
exitCode,
|
|
exitCode,
|
|
signalCode,
|
|
signalCode,
|
|
spawnfile,
|
|
spawnfile,
|
|
- spawnargs,
|
|
|
|
|
|
+ spawnargs
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* PORTS SECTION
|
|
* PORTS SECTION
|
|
*/
|
|
*/
|
|
-async function findPorts() {
|
|
|
|
|
|
+async function findPorts () {
|
|
// 1. Make sure, workers are updated.
|
|
// 1. Make sure, workers are updated.
|
|
await findInterfaces()
|
|
await findInterfaces()
|
|
|
|
|
|
@@ -222,8 +212,8 @@ async function findPorts() {
|
|
// 3. Loop through all workers to find available ports.
|
|
// 3. Loop through all workers to find available ports.
|
|
const portsPromises = state.interfaces.map(async iface => {
|
|
const portsPromises = state.interfaces.map(async iface => {
|
|
// a) Ask interface for ports.
|
|
// a) Ask interface for ports.
|
|
- const { data, error, pythonError } = await iface.workerProcess.send({
|
|
|
|
- type: 'ports',
|
|
|
|
|
|
+ const { data, error } = await iface.workerProcess.send({
|
|
|
|
+ type: 'ports'
|
|
})
|
|
})
|
|
if (error) throw new Error(error)
|
|
if (error) throw new Error(error)
|
|
// b) Add all ports that are not in the list.
|
|
// b) Add all ports that are not in the list.
|
|
@@ -232,35 +222,31 @@ async function findPorts() {
|
|
if (state.ports.find(port => port.id === id)) return null
|
|
if (state.ports.find(port => port.id === id)) return null
|
|
const newPort = {
|
|
const newPort = {
|
|
id,
|
|
id,
|
|
|
|
+ interface: iface,
|
|
interfaceName: iface.interfaceName,
|
|
interfaceName: iface.interfaceName,
|
|
host: HOST,
|
|
host: HOST,
|
|
- ...port,
|
|
|
|
|
|
+ ...port
|
|
}
|
|
}
|
|
state.ports.push(newPort)
|
|
state.ports.push(newPort)
|
|
- iface.ports.push(newPort)
|
|
|
|
})
|
|
})
|
|
})
|
|
})
|
|
await Promise.all(portsPromises)
|
|
await Promise.all(portsPromises)
|
|
}
|
|
}
|
|
|
|
|
|
-async function ports(parent, args, ctx, info) {
|
|
|
|
|
|
+async function ports (parent, args, context, info) {
|
|
await findPorts()
|
|
await findPorts()
|
|
- const { interfaceName } = args
|
|
|
|
- const ifName = interfaceName || parent
|
|
|
|
|
|
|
|
- if (ifName) {
|
|
|
|
- const iface = state.interfaces.find(iface => iface.interfaceName === ifName)
|
|
|
|
- if (!iface) throw new Error(`Interface ${ifName} not found.`)
|
|
|
|
- return iface.ports
|
|
|
|
|
|
+ if (parent) {
|
|
|
|
+ return state.ports.filter(
|
|
|
|
+ port => port.interfaceName === parent.interfaceName
|
|
|
|
+ )
|
|
} else {
|
|
} else {
|
|
return state.ports
|
|
return state.ports
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-async function port(parent, args, ctx, info) {
|
|
|
|
|
|
+async function port (parent, { id }, context, info) {
|
|
await findPorts()
|
|
await findPorts()
|
|
- const { id } = args
|
|
|
|
- if (!id) throw new Error('Need an id.')
|
|
|
|
const port = state.ports.find(port => port.id === id)
|
|
const port = state.ports.find(port => port.id === id)
|
|
return port
|
|
return port
|
|
}
|
|
}
|
|
@@ -268,102 +254,86 @@ async function port(parent, args, ctx, info) {
|
|
/**
|
|
/**
|
|
* CONNECTION SECTION
|
|
* CONNECTION SECTION
|
|
*/
|
|
*/
|
|
-async function connect(parent, args, ctx, info) {
|
|
|
|
|
|
+async function connect (parent, { portId }, ctx, info) {
|
|
await findPorts()
|
|
await findPorts()
|
|
- const { portId } = args
|
|
|
|
const port = state.ports.find(port => port.id === portId)
|
|
const port = state.ports.find(port => port.id === portId)
|
|
if (!port) throw new Error(`Port ${portId} not found`)
|
|
if (!port) throw new Error(`Port ${portId} not found`)
|
|
- const iface = state.interfaces.find(
|
|
|
|
- iface => iface.interfaceName === port.interfaceName
|
|
|
|
- )
|
|
|
|
- const id = md5(iface.interfaceName + iface.device)
|
|
|
|
- if (iface.connections.find(connection => connection.id === id)) {
|
|
|
|
|
|
+
|
|
|
|
+ const id = md5(port.interfaceName + port.device)
|
|
|
|
+ if (state.connections.find(connection => connection.id === id)) {
|
|
throw new Error('already connected.')
|
|
throw new Error('already connected.')
|
|
}
|
|
}
|
|
- const pythonWorker = new PythonWorker(iface.workerScript)
|
|
|
|
- const spawnData = await pythonWorker.spawn()
|
|
|
|
|
|
+ const workerProcess = new PythonWorker(port.interface.workerScript)
|
|
|
|
+ const spawnData = await workerProcess.spawn()
|
|
if (spawnData.error) throw new Error(spawnData.error)
|
|
if (spawnData.error) throw new Error(spawnData.error)
|
|
const connection = {
|
|
const connection = {
|
|
id,
|
|
id,
|
|
port,
|
|
port,
|
|
- workerProcess: pythonWorker,
|
|
|
|
|
|
+ workerProcess
|
|
}
|
|
}
|
|
- const connectionData = await connection.workerProcess.send({
|
|
|
|
|
|
+ const connectionData = await workerProcess.send({
|
|
type: 'connect',
|
|
type: 'connect',
|
|
- device: port.device,
|
|
|
|
|
|
+ device: port.device
|
|
})
|
|
})
|
|
if (connectionData.error) throw new Error(connectionData.error)
|
|
if (connectionData.error) throw new Error(connectionData.error)
|
|
- iface.connections.push(connection)
|
|
|
|
state.connections.push(connection)
|
|
state.connections.push(connection)
|
|
- return {
|
|
|
|
- ...connection,
|
|
|
|
- workerProcess: workerProcess(pythonWorker),
|
|
|
|
- }
|
|
|
|
|
|
+ return connection
|
|
}
|
|
}
|
|
|
|
|
|
-async function connections(parent, args, ctx, info) {
|
|
|
|
|
|
+async function connections (parent, args, ctx, info) {
|
|
if (parent) {
|
|
if (parent) {
|
|
- const iface = state.interfaces.find(iface => iface.interfaceName === parent)
|
|
|
|
- return iface.connections.map(connection => {
|
|
|
|
- return {
|
|
|
|
- ...connection,
|
|
|
|
- workerProcess: workerProcess(connection.workerProcess),
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
|
|
+ return state.connections.filter(
|
|
|
|
+ connection => connection.port.interfaceName === parent.interfaceName
|
|
|
|
+ )
|
|
} else {
|
|
} else {
|
|
- return state.connections.map(connection => {
|
|
|
|
- return {
|
|
|
|
- ...connection,
|
|
|
|
- workerProcess: workerProcess(connection.workerProcess),
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
|
|
+ return state.connections
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-async function connection(parent, args, context, info) {
|
|
|
|
- const connection = state.connections.find(
|
|
|
|
- connection => connection.id === args.id
|
|
|
|
- )
|
|
|
|
- return {
|
|
|
|
- ...connection,
|
|
|
|
- workerProcess: workerProcess(connection.workerProcess),
|
|
|
|
- }
|
|
|
|
|
|
+async function connection (parent, { id }, context, info) {
|
|
|
|
+ const connection = state.connections.find(connection => connection.id === id)
|
|
|
|
+ return connection
|
|
}
|
|
}
|
|
|
|
|
|
-async function sendCommand(parent, args, ctx, info) {
|
|
|
|
- const { connectionId, command } = args
|
|
|
|
|
|
+async function sendCommand (parent, { connectionId, command }, ctx, info) {
|
|
const connection = state.connections.find(
|
|
const connection = state.connections.find(
|
|
connection => connection.id === connectionId
|
|
connection => connection.id === connectionId
|
|
)
|
|
)
|
|
|
|
+ if (!connection) throw new Error('Connection not found.')
|
|
const { data, error } = await connection.workerProcess.send({ ...command })
|
|
const { data, error } = await connection.workerProcess.send({ ...command })
|
|
if (error) throw new Error(JSON.stringify(error))
|
|
if (error) throw new Error(JSON.stringify(error))
|
|
- return data.response
|
|
|
|
|
|
+ if (!data.response) {
|
|
|
|
+ return JSON.stringify(data)
|
|
|
|
+ } else {
|
|
|
|
+ return data.response
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
-// TODO Also find connections in interfaces.
|
|
|
|
-async function endConnection(parent, args, ctx, info) {
|
|
|
|
- const { connectionId } = args
|
|
|
|
|
|
+async function endConnection (parent, { connectionId }, ctx, info) {
|
|
const connectionIndex = state.connections.findIndex(
|
|
const connectionIndex = state.connections.findIndex(
|
|
connection => connection.id === connectionId
|
|
connection => connection.id === connectionId
|
|
)
|
|
)
|
|
|
|
+ if (connectionIndex < 0) throw new Error('Connection not found.')
|
|
const connection = state.connections[connectionIndex]
|
|
const connection = state.connections[connectionIndex]
|
|
- const iface = state.interfaces.find(
|
|
|
|
- iface => (iface.interfaceName = connection.interfaceName)
|
|
|
|
- )
|
|
|
|
|
|
+
|
|
const { data, error } = await connection.workerProcess.end()
|
|
const { data, error } = await connection.workerProcess.end()
|
|
if (error) throw new Error(JSON.stringify(error))
|
|
if (error) throw new Error(JSON.stringify(error))
|
|
|
|
+ if (data) throw new Error(`Process ended with exit code ${data}`)
|
|
state.connections.splice(connectionIndex, 1)
|
|
state.connections.splice(connectionIndex, 1)
|
|
return connection
|
|
return connection
|
|
}
|
|
}
|
|
|
|
|
|
-async function killConnection(parent, args, ctx, info) {
|
|
|
|
- const { connectionId } = args
|
|
|
|
|
|
+async function killConnection (parent, { connectionId, signal = 9 }, ctx, info) {
|
|
const connectionIndex = state.connections.findIndex(
|
|
const connectionIndex = state.connections.findIndex(
|
|
connection => connection.id === connectionId
|
|
connection => connection.id === connectionId
|
|
)
|
|
)
|
|
|
|
+ if (connectionIndex < 0) throw new Error('Connection not found.')
|
|
const connection = state.connections[connectionIndex]
|
|
const connection = state.connections[connectionIndex]
|
|
- const { data, error } = await connection.workerProcess.kill()
|
|
|
|
|
|
+
|
|
|
|
+ const { data, error } = await connection.workerProcess.kill(signal)
|
|
|
|
+ console.log({ data, error })
|
|
if (error) throw new Error(JSON.stringify(error))
|
|
if (error) throw new Error(JSON.stringify(error))
|
|
|
|
+ if (data) throw new Error(`Received code ${data}`)
|
|
state.connections.splice(connectionIndex, 1)
|
|
state.connections.splice(connectionIndex, 1)
|
|
return connection
|
|
return connection
|
|
}
|
|
}
|
|
@@ -371,18 +341,26 @@ async function killConnection(parent, args, ctx, info) {
|
|
const resolvers = {
|
|
const resolvers = {
|
|
Query: {
|
|
Query: {
|
|
interfaces,
|
|
interfaces,
|
|
- interface,
|
|
|
|
|
|
+ interface: iface,
|
|
ports,
|
|
ports,
|
|
port,
|
|
port,
|
|
connections,
|
|
connections,
|
|
- connection,
|
|
|
|
|
|
+ connection
|
|
},
|
|
},
|
|
Mutation: {
|
|
Mutation: {
|
|
connect,
|
|
connect,
|
|
sendCommand,
|
|
sendCommand,
|
|
endConnection,
|
|
endConnection,
|
|
- killConnection,
|
|
|
|
|
|
+ killConnection
|
|
},
|
|
},
|
|
|
|
+ Interface: {
|
|
|
|
+ ports,
|
|
|
|
+ connections,
|
|
|
|
+ workerProcess
|
|
|
|
+ },
|
|
|
|
+ Connection: {
|
|
|
|
+ workerProcess
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
module.exports = { typeDefs, resolvers }
|
|
module.exports = { typeDefs, resolvers }
|