1- import 'dart:convert' ;
21import 'dart:io' ;
32
4- import 'package:http/http.dart' as http;
3+ import 'package:sentracore_dashboard/services/engine_port_resolver.dart' ;
4+ import 'package:sentracore_dashboard/services/engine_service.dart' ;
55
66/// Outcome of trying to ensure the packaged engine is running (Windows install).
77class EngineBootstrapOutcome {
88 final bool success;
99 final String ? message;
10+ /// Port where [GET /api/v1/health] succeeded (or fallback when skipped).
11+ final int activePort;
1012
11- const EngineBootstrapOutcome ({required this .success, this .message});
13+ const EngineBootstrapOutcome ({
14+ required this .success,
15+ this .message,
16+ this .activePort = EngineService .defaultPort,
17+ });
1218}
1319
1420/// Starts [SentraCoreEngine.exe] next to the dashboard when installed via Inno Setup,
15- /// then waits until the HTTP API reports the engine is initialized.
16- ///
17- /// Skipped when:
18- /// - Not Windows
19- /// - [host] is not a local loopback address (remote engine)
20- /// - No `SentraCoreEngine.exe` beside this process (typical `flutter run` dev layout)
21+ /// then waits until the HTTP API is up on whatever port the engine chose (8740+).
2122class EngineBundledLauncher {
2223 EngineBundledLauncher ._();
2324
24- static bool _isLocalHost (String host) {
25- final h = host.toLowerCase ().trim ();
26- return h == '127.0.0.1' || h == 'localhost' || h == '::1' ;
27- }
28-
2925 /// Path to the packaged engine exe, or null if not present (e.g. dev builds).
3026 static String ? bundledEngineExecutablePath () {
3127 if (! Platform .isWindows) return null ;
@@ -35,73 +31,52 @@ class EngineBundledLauncher {
3531 return candidate.existsSync () ? candidate.path : null ;
3632 }
3733
38- static String _healthUrl (String host, int port) {
39- final h = host.trim ();
40- final authority = h == '::1' ? '[::1]:$port ' : '$h :$port ' ;
41- return 'http://$authority /api/v1/health' ;
42- }
43-
44- static Future <bool > _engineHealthy (String host, int port) async {
45- try {
46- final r = await http
47- .get (Uri .parse (_healthUrl (host, port)))
48- .timeout (const Duration (milliseconds: 900 ));
49- if (r.statusCode != 200 ) return false ;
50- final j = jsonDecode (r.body) as Map <String , dynamic >;
51- return j['engine' ] == true ;
52- } catch (_) {
53- return false ;
54- }
55- }
56-
57- /// Ensures the local engine process is up and responding, starting it if needed.
58- static Future <EngineBootstrapOutcome > ensureReady ({
59- required String host,
60- required int port,
61- }) async {
62- if (! _isLocalHost (host)) {
63- return const EngineBootstrapOutcome (success: true );
64- }
34+ /// Discover a running engine, optionally starting the Windows bundled exe first.
35+ static Future <EngineBootstrapOutcome > ensureReady ({int ? preferredPort}) async {
6536 if (! Platform .isWindows) {
66- return const EngineBootstrapOutcome (success: true );
37+ final p = await EnginePortResolver .discoverPort (preferredPort: preferredPort);
38+ return EngineBootstrapOutcome (
39+ success: true ,
40+ activePort: p ?? EngineService .defaultPort,
41+ );
6742 }
6843
69- if (await _engineHealthy (host, port)) {
70- return const EngineBootstrapOutcome (success: true );
44+ var port = await EnginePortResolver .discoverPort (preferredPort: preferredPort);
45+ if (port != null ) {
46+ return EngineBootstrapOutcome (success: true , activePort: port);
7147 }
7248
7349 final exe = bundledEngineExecutablePath ();
74- if (exe == null ) {
75- return const EngineBootstrapOutcome (success: true );
76- }
50+ if (exe != null ) {
51+ try {
52+ await Process .start (
53+ exe,
54+ const < String > [],
55+ workingDirectory: File (exe).parent.path,
56+ mode: ProcessStartMode .detached,
57+ );
58+ } catch (e) {
59+ return EngineBootstrapOutcome (
60+ success: false ,
61+ message: 'Could not start SentraCoreEngine.exe: $e ' ,
62+ activePort: EngineService .defaultPort,
63+ );
64+ }
65+
66+ port = await EnginePortResolver .discoverPort (preferredPort: preferredPort);
67+ if (port != null ) {
68+ return EngineBootstrapOutcome (success: true , activePort: port);
69+ }
7770
78- try {
79- await Process .start (
80- exe,
81- const < String > [],
82- workingDirectory: File (exe).parent.path,
83- mode: ProcessStartMode .detached,
84- );
85- } catch (e) {
8671 return EngineBootstrapOutcome (
8772 success: false ,
88- message: 'Could not start SentraCoreEngine.exe: $e ' ,
73+ message:
74+ 'Engine did not become ready. It may have crashed, or ports are blocked.' ,
75+ activePort: EngineService .defaultPort,
8976 );
9077 }
9178
92- const step = Duration (milliseconds: 450 );
93- final deadline = DateTime .now ().add (const Duration (seconds: 45 ));
94- while (DateTime .now ().isBefore (deadline)) {
95- if (await _engineHealthy (host, port)) {
96- return const EngineBootstrapOutcome (success: true );
97- }
98- await Future <void >.delayed (step);
99- }
100-
101- return EngineBootstrapOutcome (
102- success: false ,
103- message: 'Engine did not become ready on port $port . '
104- 'It may have crashed, or another program is using that port.' ,
105- );
79+ // Windows dev: no bundled exe — try default port only (same as legacy skip).
80+ return EngineBootstrapOutcome (success: true , activePort: EngineService .defaultPort);
10681 }
10782}
0 commit comments