const { app } = require('electron'); const path = require('path'); const { spawn } = require('child_process'); // Helper function to run python commands via the bundled backend function runPythonCommand(command, kwargs, event) { const buildDir = path.resolve(__dirname, '..', '..', 'build'); let backendPath; // Determine executable name based on platform const executableName = process.platform === 'win32' ? 'fourdst-backend.exe' : 'fourdst-backend'; // Initialize args first let args = [command, JSON.stringify(kwargs)]; if (app.isPackaged) { // In packaged app, backend is in resources/backend/ directory backendPath = path.join(process.resourcesPath, 'backend', executableName); } else { // In development, use the Python bridge.py directly for faster iteration backendPath = 'python'; // Update args to include the bridge.py path as the first argument const bridgePath = path.join(__dirname, '..', 'bridge.py'); args.unshift(bridgePath); } console.log(`[MAIN_PROCESS] Spawning backend: ${backendPath}`); console.log(`[MAIN_PROCESS] With args: [${args.join(', ')}]`); return new Promise((resolve) => { const process = spawn(backendPath, args); let stdoutBuffer = ''; let errorOutput = ''; process.stderr.on('data', (data) => { const stderrChunk = data.toString(); errorOutput += stderrChunk; console.error('Backend STDERR:', stderrChunk.trim()); // For fill_bundle, forward stderr to frontend for terminal display if (isStreaming && event && command === 'fill_bundle') { // Parse stderr lines and send them as progress updates const lines = stderrChunk.split('\n').filter(line => line.trim()); lines.forEach(line => { const trimmedLine = line.trim(); // Check if this is a structured progress message if (trimmedLine.startsWith('[PROGRESS] {')) { try { // Extract JSON from [PROGRESS] prefix const jsonStr = trimmedLine.substring('[PROGRESS] '.length); const progressData = JSON.parse(jsonStr); console.log(`[MAIN_PROCESS] Parsed progress data:`, progressData); // Send as proper progress update event.sender.send('fill-bundle-progress', progressData); } catch (e) { console.error(`[MAIN_PROCESS] Failed to parse progress JSON: ${trimmedLine}`, e); // Fallback to stderr if JSON parsing fails event.sender.send('fill-bundle-progress', { type: 'stderr', stderr: trimmedLine }); } } else { // Only skip very specific system messages, include everything else as stderr const shouldSkip = trimmedLine.includes('[BRIDGE_INFO]') || trimmedLine.includes('--- Python backend bridge') || trimmedLine.startsWith('[PROGRESS]') || // Skip non-JSON progress messages trimmedLine === ''; if (!shouldSkip) { console.log(`[MAIN_PROCESS] Forwarding stderr to frontend: ${trimmedLine}`); event.sender.send('fill-bundle-progress', { type: 'stderr', stderr: trimmedLine }); } } }); } }); const isStreaming = command === 'fill_bundle'; process.stdout.on('data', (data) => { const chunk = data.toString(); stdoutBuffer += chunk; if (isStreaming && event) { // Process buffer line by line for streaming commands let newlineIndex; while ((newlineIndex = stdoutBuffer.indexOf('\n')) >= 0) { const line = stdoutBuffer.substring(0, newlineIndex).trim(); stdoutBuffer = stdoutBuffer.substring(newlineIndex + 1); if (line) { try { const parsed = JSON.parse(line); if (parsed.type === 'progress') { event.sender.send('fill-bundle-progress', parsed.data); } else { // Not a progress update, put it back in the buffer for final processing stdoutBuffer = line + '\n' + stdoutBuffer; break; // Stop processing lines } } catch (e) { // Ignore parsing errors for intermediate lines in a stream } } } } }); process.on('close', (code) => { console.log(`[MAIN_PROCESS] Backend process exited with code ${code}`); console.log(`[MAIN_PROCESS] Backend path used: ${backendPath}`); console.log(`[MAIN_PROCESS] App packaged: ${app.isPackaged}`); console.log(`[MAIN_PROCESS] Resources path: ${process.resourcesPath || 'N/A'}`); console.log(`[MAIN_PROCESS] Raw stdout buffer length: ${stdoutBuffer.length}`); console.log(`[MAIN_PROCESS] Raw stdout first 200 chars: "${stdoutBuffer.substring(0, 200)}"`); console.log(`[MAIN_PROCESS] Error output: "${errorOutput}"`); let resultData = null; try { // Core functions now return clean JSON directly const finalJson = JSON.parse(stdoutBuffer.trim()); resultData = finalJson; // Use the JSON response directly } catch (e) { console.error(`[MAIN_PROCESS] Could not parse backend output as JSON: ${e}`); console.error(`[MAIN_PROCESS] Raw output: "${stdoutBuffer}"`); // If parsing fails, return a structured error response resultData = { success: false, error: `JSON parsing failed: ${e.message}`, raw_output: stdoutBuffer, backend_path: backendPath, is_packaged: app.isPackaged, exit_code: code, stderr_output: errorOutput }; } const finalError = errorOutput.trim(); if (finalError && !resultData) { resolve({ success: false, error: finalError }); } else if (resultData) { resolve(resultData); } else { const errorMessage = finalError || `The script finished without returning a result (exit code: ${code})`; resolve({ success: false, error: errorMessage }); } }); process.on('error', (err) => { resolve({ success: false, error: `Failed to start backend process: ${err.message}` }); }); }); } module.exports = { runPythonCommand };