diff --git a/server/server.js b/server/server.js index c260dac..107e284 100755 --- a/server/server.js +++ b/server/server.js @@ -346,64 +346,84 @@ const PYTHON_BIN = process.env.PYTHON_BIN || 'python3'; const calibScriptPath = path.join(__dirname, '..', 'scripts', 'callibriate.py'); app.post('/api/calibration/compute', async (req, res) => { - const { camera } = req.body ?? {}; - if (!camera) return res.status(400).json({ error: '"camera" parameter fehlt' }); + try { + const { camera } = req.body ?? {}; + if (!camera) return res.status(400).json({ error: '"camera" parameter fehlt' }); - const session = await findLatestCalibSession(); - if (!session) return res.status(400).json({ error: 'Keine Kalibrierungs-Session vorhanden' }); + const session = await findLatestCalibSession(); + if (!session) return res.status(400).json({ error: 'Keine Kalibrierungs-Session vorhanden' }); - const sessionDir = path.join(calibDataDir, session); + const sessionDir = path.join(calibDataDir, session); - // SSE-Header - res.setHeader('Content-Type', 'text/event-stream'); - res.setHeader('Cache-Control', 'no-cache'); - res.setHeader('Connection', 'keep-alive'); - res.flushHeaders(); + // SSE-Header – erst NACH den Validierungen senden + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + res.flushHeaders(); - const send = (obj) => res.write(`data: ${JSON.stringify(obj)}\n\n`); + // Schreibt nur wenn die Verbindung noch offen ist + const send = (obj) => { + if (!res.writableEnded) res.write(`data: ${JSON.stringify(obj)}\n\n`); + }; - send({ type: 'log', text: `▶ Session: ${session}` }); - send({ type: 'log', text: `▶ Kamera: ${camera}` }); - send({ type: 'log', text: `▶ Script: ${calibScriptPath}` }); - send({ type: 'log', text: '' }); + send({ type: 'log', text: `▶ Session: ${session}` }); + send({ type: 'log', text: `▶ Kamera: ${camera}` }); + send({ type: 'log', text: `▶ Script: ${calibScriptPath}` }); + send({ type: 'log', text: '' }); - const proc = spawn(PYTHON_BIN, [ - calibScriptPath, - '--camera', camera, - '--input-dir', sessionDir, - '--output-dir', sessionDir, - ]); + // -u = unbuffered (Python gibt jede Zeile sofort aus) + const proc = spawn(PYTHON_BIN, [ + '-u', + calibScriptPath, + '--camera', camera, + '--input-dir', sessionDir, + '--output-dir', sessionDir, + ]); - // stdout zeilenweise weiterleiten - let stdoutBuf = ''; - proc.stdout.on('data', (chunk) => { - stdoutBuf += chunk.toString(); - const lines = stdoutBuf.split('\n'); - stdoutBuf = lines.pop(); // letztes (unvollständiges) Fragment behalten - for (const line of lines) send({ type: 'log', text: line }); - }); + let stdoutBuf = ''; + proc.stdout.on('data', (chunk) => { + stdoutBuf += chunk.toString(); + const lines = stdoutBuf.split('\n'); + stdoutBuf = lines.pop(); + for (const line of lines) send({ type: 'log', text: line }); + }); - // stderr als Warnung weiterleiten - let stderrBuf = ''; - proc.stderr.on('data', (chunk) => { - stderrBuf += chunk.toString(); - const lines = stderrBuf.split('\n'); - stderrBuf = lines.pop(); - for (const line of lines) send({ type: 'log', text: `[stderr] ${line}` }); - }); + let stderrBuf = ''; + proc.stderr.on('data', (chunk) => { + stderrBuf += chunk.toString(); + const lines = stderrBuf.split('\n'); + stderrBuf = lines.pop(); + for (const line of lines) send({ type: 'log', text: `[stderr] ${line}` }); + }); - proc.on('error', (err) => { - send({ type: 'log', text: `Fehler beim Starten: ${err.message}` }); - send({ type: 'done', exitCode: -1 }); - res.end(); - }); + proc.on('error', (err) => { + console.error('calibration/compute spawn error:', err); + send({ type: 'log', text: `Fehler beim Starten: ${err.message}` }); + send({ type: 'done', exitCode: -1 }); + if (!res.writableEnded) res.end(); + }); - proc.on('close', (code) => { - if (stdoutBuf) send({ type: 'log', text: stdoutBuf }); // Rest ausgeben - if (stderrBuf) send({ type: 'log', text: `[stderr] ${stderrBuf}` }); - send({ type: 'done', exitCode: code }); - res.end(); - }); + proc.on('close', (code) => { + if (stdoutBuf) send({ type: 'log', text: stdoutBuf }); + if (stderrBuf) send({ type: 'log', text: `[stderr] ${stderrBuf}` }); + send({ type: 'done', exitCode: code ?? -1 }); + if (!res.writableEnded) res.end(); + }); + + } catch (err) { + // Fehler VOR flushHeaders → normaler JSON-Fehler + // Fehler NACH flushHeaders → SSE-Fehlerevent + close + console.error('calibration/compute error:', err); + if (!res.headersSent) { + res.status(500).json({ error: String(err) }); + } else { + try { + res.write(`data: ${JSON.stringify({ type: 'log', text: `Server-Fehler: ${err.message}` })}\n\n`); + res.write(`data: ${JSON.stringify({ type: 'done', exitCode: -1 })}\n\n`); + res.end(); + } catch { /* Verbindung bereits geschlossen */ } + } + } }); async function checkServiceReachability(name, url) {