Mountain/
Binary.rs

1// File: Mountain/Source/Binary.rs
2// Role: Main entry point for the Mountain native host application.
3// Responsibilities:
4//   - Orchestrate the entire application lifecycle.
5//   - Initialize logging (via Tauri plugin), the Echo scheduler,
6//   ApplicationState, and ApplicationRunTime.
7//   - Serve assets via http://localhost (determined by portpicker) to support
8//   Service Workers.
9//   - Parse command-line arguments to open a workspace.
10//   - Bootstrap native command registration.
11//   - Set up the Vine gRPC server and spawn the Cocoon sidecar process.
12//   - Create and customize the main Tauri application window.
13//   - Manage the main application event loop and graceful shutdown.
14//   - [NEW] Manage System Tray and native OS integration events.
15//
16// Logging strategy:
17//   - Release default: Info (low noise) unless RUST_LOG overrides.
18//   - Debug default: Debug (high fidelity) unless RUST_LOG overrides.
19//   - Very noisy deps are capped using level_for(...) and filter(...).
20//
21//
22// NOTE (Webview logs):
23//   - To see Rust logs in the Webview console, enable TargetKind::Webview and
24//   call attachConsole() in the frontend.
25
26//! # Mountain Binary Entry Point
27//!
28//! This file orchestrates the entire application lifecycle. It is responsible
29//! for setting up logging, initializing the `Echo` scheduler, the core
30//! `ApplicationState`, the `ApplicationRunTime`, the `Vine` gRPC server, the
31//! `Cocoon` sidecar process, and the Tauri application window and event loop.
32
33#![allow(non_snake_case, non_camel_case_types)]
34
35use std::{
36	path::PathBuf,
37	sync::{Arc, Mutex},
38};
39
40use Echo::Scheduler::SchedulerBuilder::SchedulerBuilder;
41use log::{LevelFilter, debug, error, info, trace, warn};
42use tauri::{
43	AppHandle,
44	Manager,
45	RunEvent,
46	Wry,
47	image::Image,
48	menu::{MenuBuilder, MenuItem},
49	tray::{MouseButton, TrayIconBuilder, TrayIconEvent},
50};
51use tauri_plugin_log::{RotationStrategy, Target, TargetKind, TimezoneStrategy};
52
53use crate::{
54	ApplicationState::{
55		ApplicationState::{ApplicationState, MapLockError},
56		Internal::ScanAndPopulateExtensions,
57	},
58	Command,
59	Environment::{ConfigurationProvider::InitializeAndMergeConfigurations, MountainEnvironment::MountainEnvironment},
60	ProcessManagement::{CocoonManagement::InitializeCocoon, InitializationData},
61	RunTime::ApplicationRunTime::ApplicationRunTime,
62	Vine,
63};
64
65// =============================================================================
66// Debug Helpers (Highly Verbose, Low Intrusion)
67// =============================================================================
68
69/// Logs a checkpoint message at TRACE level (for "every step" tracing).
70macro_rules! TraceStep {
71	($($arg:tt)*) => {{
72		trace!($($arg)*);
73	}};
74}
75
76// =============================================================================
77// IPC Bridge Commands
78// =============================================================================
79
80/// A Tauri command to provide the initial workbench configuration to the Sky
81/// frontend.
82#[tauri::command]
83async fn MountainGetWorkbenchConfiguration(
84	ApplicationHandle:AppHandle<Wry>,
85	State:tauri::State<'_, Arc<ApplicationState>>,
86) -> Result<serde_json::Value, String> {
87	info!("[IPC] [WorkbenchConfig] Request received.");
88
89	debug!("[IPC] [WorkbenchConfig] Constructing sandbox configuration...");
90
91	let Config = InitializationData::ConstructSandboxConfiguration(&ApplicationHandle, &State)
92		.await
93		.map_err(|Error| {
94			error!("[IPC] [WorkbenchConfig] Failed: {}", Error);
95
96			Error.to_string()
97		})?;
98
99	debug!("[IPC] [WorkbenchConfig] Success. Returning payload.");
100
101	Ok(Config)
102}
103
104/// Dynamically switches the tray icon based on the theme (Light/Dark).
105/// Can be invoked from the frontend when the theme changes.
106#[tauri::command]
107fn SwitchTrayIcon(App:AppHandle, IsDarkMode:bool) {
108	debug!("[UI] [Tray] Switching icon. IsDarkMode: {}", IsDarkMode);
109
110	const DARK_ICON_BYTES:&[u8] = include_bytes!("../icons/32x32.png");
111
112	const LIGHT_ICON_BYTES:&[u8] = include_bytes!("../icons/32x32.png");
113
114	let IconBytes = if IsDarkMode { DARK_ICON_BYTES } else { LIGHT_ICON_BYTES };
115
116	if let Some(Tray) = App.tray_by_id("tray") {
117		match Image::from_bytes(IconBytes) {
118			Ok(IconImage) => {
119				if let Err(e) = Tray.set_icon(Some(IconImage)) {
120					error!("[UI] [Tray] Failed to set icon: {}", e);
121				}
122			},
123			Err(e) => error!("[UI] [Tray] Failed to load icon bytes: {}", e),
124		}
125	} else {
126		warn!("[UI] [Tray] Tray with ID 'tray' not found.");
127	}
128}
129
130// =============================================================================
131// Tray Initialization Logic
132// =============================================================================
133
134/// Configures and builds the system tray with menu and event handling.
135fn EnableTray(Application:&mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
136	let Handle = Application.handle();
137
138	// Create menu items
139	let OpenItem = MenuItem::with_id(Handle, "open", "Open Mountain", true, None::<&str>)?;
140
141	let HideItem = MenuItem::with_id(Handle, "hide", "Hide Mountain", true, None::<&str>)?;
142
143	let Separator = tauri::menu::PredefinedMenuItem::separator(Handle)?;
144
145	let QuitItem = MenuItem::with_id(Handle, "quit", "Quit", true, None::<&str>)?;
146
147	// Build menu structure
148	let TrayMenu = MenuBuilder::new(Handle)
149		.item(&OpenItem)
150		.item(&HideItem)
151		.item(&Separator)
152		.item(&QuitItem)
153		.build()?;
154
155	// Load initial icon (Defaulting to 32x32 from your icons folder)
156	let IconBytes = include_bytes!("../icons/32x32.png");
157
158	let TrayIconImage = Image::from_bytes(IconBytes)?;
159
160	// Build the Tray
161	TrayIconBuilder::with_id("tray")
162		.icon(TrayIconImage)
163		.menu(&TrayMenu)
164		.tooltip("Mountain")
165		// Handle Menu Item Clicks
166		.on_menu_event(|AppHandle, Event| match Event.id.as_ref() {
167			"open" => {
168				if let Some(Window) = AppHandle.get_webview_window("main") {
169					let _ = Window.show();
170
171					let _ = Window.set_focus();
172
173				}
174			},
175			"hide" => {
176				if let Some(Window) = AppHandle.get_webview_window("main") {
177					let _ = Window.hide();
178
179				}
180			},
181			"quit" => AppHandle.exit(0),
182			_ => warn!("[UI] [Tray] Unhandled menu item: {:?}", Event.id),
183		})
184		// Handle Native Tray Events (Left Click to Toggle)
185		.on_tray_icon_event(|Tray, Event| {
186			if let TrayIconEvent::Click { button: MouseButton::Left, .. } = Event {
187				let App = Tray.app_handle();
188
189				if let Some(Window) = App.get_webview_window("main") {
190					if Window.is_visible().unwrap_or(false) {
191						let _ = Window.hide();
192					} else {
193						let _ = Window.show();
194
195						let _ = Window.set_focus();
196					}
197				}
198			}
199		})
200		.build(Application)?;
201
202	info!("[UI] [Tray] System tray enabled successfully.");
203
204	Ok(())
205}
206
207// =============================================================================
208// Binary Entrypoint
209// =============================================================================
210
211/// The main asynchronous function that sets up and runs the application.
212pub fn Fn() {
213	// -------------------------------------------------------------------------
214	// [Boot] [Runtime] Tokio runtime creation
215	// -------------------------------------------------------------------------
216	TraceStep!("[Boot] [Runtime] Building Tokio runtime...");
217
218	let Runtime = tokio::runtime::Builder::new_multi_thread()
219		.enable_all()
220		.build()
221		.expect("FATAL: Cannot build Tokio runtime.");
222
223	TraceStep!("[Boot] [Runtime] Tokio runtime built.");
224
225	Runtime.block_on(async {
226		// ---------------------------------------------------------------------
227		// [Boot] [Args] CLI parsing
228		// ---------------------------------------------------------------------
229		debug!("[Boot] [Args] Collecting CLI args...");
230
231		let CliArgs:Vec<String> = std::env::args().collect();
232
233		debug!("[Boot] [Args] CLI Args: {:?}", CliArgs);
234
235		let WorkSpacePathArgument = CliArgs.iter().find(|Arg| Arg.ends_with(".code-workspace"));
236
237		TraceStep!("[Boot] [Args] Workspace arg present: {}", WorkSpacePathArgument.is_some());
238
239		let (InitialFolders, WorkSpaceConfigurationPath) = if let Some(PathString) = WorkSpacePathArgument {
240			let Path = PathBuf::from(PathString);
241
242			println!("[Boot] [Args] Found workspace argument: {}", Path.display());
243
244			debug!("[Boot] [Workspace] Reading workspace file: {}", Path.display());
245
246			match std::fs::read_to_string(&Path) {
247				Ok(Content) => {
248					debug!("[Boot] [Workspace] Workspace file read ok ({} bytes).", Content.len());
249
250					crate::WorkSpace::WorkSpaceFileService::ParseWorkSpaceFile(&Path, &Content)
251						.map(|Folders| {
252							debug!("[Boot] [Workspace] Parsed workspace ok. folder_count={}", Folders.len());
253
254							(Folders, Some(Path))
255						})
256						.unwrap_or_else(|Error| {
257							error!("[Boot] [Workspace] Parse failed: {}. Continuing without workspace.", Error);
258
259							(Vec::new(), None)
260						})
261				},
262				Err(Error) => {
263					error!("[Boot] [Workspace] Read failed: {}. Continuing without workspace.", Error);
264
265					(Vec::new(), None)
266				},
267			}
268		} else {
269			debug!("[Boot] [Workspace] No workspace provided. Starting empty.");
270
271			(Vec::new(), None)
272		};
273
274		// ---------------------------------------------------------------------
275		// [Boot] [State] ApplicationState
276		// ---------------------------------------------------------------------
277		debug!("[Boot] [State] Building ApplicationState...");
278
279		let AppState = Arc::new(ApplicationState {
280			WorkSpaceFolders:Arc::new(Mutex::new(InitialFolders)),
281			WorkSpaceConfigurationPath:Arc::new(Mutex::new(WorkSpaceConfigurationPath)),
282			..ApplicationState::default()
283		});
284
285		debug!("[Boot] [State] ApplicationState created and managed.");
286
287		// ---------------------------------------------------------------------
288		// [Boot] [Echo] Scheduler
289		// ---------------------------------------------------------------------
290		let NumberOfWorkers = num_cpus::get().max(2);
291
292		debug!("[Boot] [Echo] Creating scheduler. workers={}", NumberOfWorkers);
293
294		let Scheduler = SchedulerBuilder::Create().WithWorkerCount(NumberOfWorkers).Build();
295
296		debug!("[Boot] [Echo] Scheduler built.");
297
298		let SchedulerForShutdown = Arc::new(Scheduler);
299
300		let SchedulerForRunTime = SchedulerForShutdown.clone();
301
302		TraceStep!("[Boot] [Echo] Scheduler handles prepared.");
303
304		// ---------------------------------------------------------------------
305		// [Boot] [Localhost] Port selection (for Service Workers & stable origin)
306		// ---------------------------------------------------------------------
307		debug!("[Boot] [Localhost] Selecting unused port...");
308
309		let ServerPort =
310			portpicker::pick_unused_port().expect("FATAL: Failed to find a free port for Localhost Server");
311
312		debug!("[Boot] [Localhost] Selected port={}", ServerPort);
313
314		let LocalhostUrl = format!("http://localhost:{}", ServerPort);
315
316		println!("[Boot] [Localhost] Selected: {} ({})", ServerPort, LocalhostUrl);
317
318		// ---------------------------------------------------------------------
319		// [Boot] [Logging] Log level resolution
320		// ---------------------------------------------------------------------
321		// Supported by tauri_plugin_log:
322		// - level(...) sets the max log level
323		// - level_for(...) overrides max level per module
324		// - filter(...) discards by metadata
325		// - format(...) custom line formatting
326		// - targets([...]) configures outputs (Stdout/Webview/File)
327		// - timezone_strategy/rotation_strategy for file behavior
328		let EnvLogLevel = std::env::var("RUST_LOG").ok().and_then(|s| s.parse::<LevelFilter>().ok());
329
330		let DefaultLogLevel = if cfg!(debug_assertions) { LevelFilter::Debug } else { LevelFilter::Info };
331
332		let LogLevel = EnvLogLevel.unwrap_or(DefaultLogLevel);
333
334		// Log *very early* using stderr/stdout (logger not yet installed).
335		// Once the plugin is installed, subsequent logs will go through it.
336		eprintln!(
337			"[Boot] [Logging] Resolved LogLevel={:?} (env={:?}, default={:?})",
338			LogLevel, EnvLogLevel, DefaultLogLevel
339		);
340
341		// ---------------------------------------------------------------------
342		// [Boot] [Tauri] Builder
343		// ---------------------------------------------------------------------
344		#[allow(unused_mut)]
345		let mut Builder = tauri::Builder::default();
346
347		#[cfg(any(windows, target_os = "linux"))]
348		{
349			Builder = Builder.any_thread();
350		}
351
352		Builder
353			// -----------------------------------------------------------------
354			// [Boot] [Tauri] [Plugins] Logging
355			// -----------------------------------------------------------------
356			.plugin(
357				tauri_plugin_log::Builder::new()
358					.targets([
359						Target::new(TargetKind::Stdout),
360						Target::new(TargetKind::LogDir {
361							file_name: Some("Mountain.log".into()),
362						}),
363						Target::new(TargetKind::Webview),
364					])
365					.timezone_strategy(TimezoneStrategy::UseLocal)
366					.rotation_strategy(RotationStrategy::KeepAll)
367					.level(LogLevel)
368					// Cap common noisy deps even when RUST_LOG=trace.
369					.level_for("hyper", LevelFilter::Info)
370					.level_for("mio", LevelFilter::Info)
371					.level_for("tao", LevelFilter::Info)
372					.level_for("tracing", LevelFilter::Info)
373					// Drop noise by target metadata.
374					.filter(|Metadata| {
375						!Metadata.target().starts_with("polling")
376							&& !Metadata.target().starts_with("tokio_reactor")
377							&& !Metadata.target().starts_with("want")
378					})
379					// Add category-like formatting: DATE [LEVEL] [TARGET] message.
380					.format(|out, message, record| {
381						out.finish(format_args!(
382							"[{:<5}] [{}] {}",
383							record.level(),
384							record.target(),
385							message
386						))
387					})
388					.build(),
389			)
390			// -----------------------------------------------------------------
391			// [Boot] [Tauri] [Plugins] Localhost server (pre-selected port)
392			// -----------------------------------------------------------------
393			.plugin(tauri_plugin_localhost::Builder::new(ServerPort)
394			.on_request(|_, Response| {
395				Response.add_header("Access-Control-Allow-Origin", "*");
396
397				Response.add_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD");
398
399				Response.add_header("Access-Control-Allow-Headers", "Content-Type, Authorization, Origin, Accept");
400			})
401			.build())
402			// -----------------------------------------------------------------
403			// [Boot] [DI] Global state
404			// -----------------------------------------------------------------
405			.manage(AppState.clone())
406			// -----------------------------------------------------------------
407			// [Lifecycle] Setup hook
408			// -----------------------------------------------------------------
409			.setup({
410				let LocalhostUrl = LocalhostUrl.clone();
411
412				move |Application| {
413					info!("[Lifecycle] [Setup] Setup hook started.");
414
415					debug!("[Lifecycle] [Setup] LocalhostUrl={}", LocalhostUrl);
416
417					let ApplicationHandle = Application.handle().clone();
418
419					TraceStep!("[Lifecycle] [Setup] AppHandle acquired.");
420
421					// ---------------------------------------------------------
422					// [UI] [Tray] Initialize System Tray
423					// ---------------------------------------------------------
424					debug!("[UI] [Tray] Initializing system tray...");
425
426					if let Err(Error) = EnableTray(Application) {
427						error!("[UI] [Tray] Failed to enable tray: {}", Error);
428
429						// We do not crash the app if tray fails, but we log it.
430					}
431
432					// ---------------------------------------------------------
433					// [Lifecycle] [Commands] Bootstrap native commands
434					// ---------------------------------------------------------
435					debug!("[Lifecycle] [Commands] Registering native commands...");
436
437					Command::Bootstrap::RegisterNativeCommands(&ApplicationHandle, &AppState)
438						.expect("FATAL: Failed to register native commands.");
439
440					debug!("[Lifecycle] [Commands] Native commands registered.");
441
442					// ---------------------------------------------------------
443					// [UI] [Window] Main window creation
444					// ---------------------------------------------------------
445					debug!("[UI] [Window] Building init script...");
446
447					let InitScript = format!("window.__MOUNTAIN_BASE_URL__ = '{}';", LocalhostUrl);
448
449					TraceStep!("[UI] [Window] InitScript bytes={}", InitScript.len());
450
451					debug!("[UI] [Window] Creating window builder...");
452
453					let mut WindowBuilder = tauri::WebviewWindowBuilder::new(
454						Application,
455						"main",
456						tauri::WebviewUrl::External(
457							format!("{}/Application/index.html", LocalhostUrl).parse().unwrap(),
458						),
459					)
460					.use_https_scheme(false)
461					.initialization_script(&InitScript)
462					.zoom_hotkeys_enabled(true)
463					.browser_extensions_enabled(false);
464
465					#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
466					{
467						WindowBuilder = WindowBuilder
468							.title("Mountain")
469							.maximized(true)
470							.decorations(false)
471							.shadow(true);
472					}
473
474					debug!("[UI] [Window] Building main window...");
475
476					#[allow(unused_variables)]
477					let MainWindow = WindowBuilder.build().expect("FATAL: Main window build failed");
478
479					info!("[UI] [Window] Main window ready.");
480
481					#[cfg(debug_assertions)]
482					{
483						debug!("[UI] [Window] Debug build: opening DevTools.");
484
485						MainWindow.open_devtools();
486					}
487
488					// ---------------------------------------------------------
489					// [Backend] [Env] Mountain environment
490					// ---------------------------------------------------------
491					debug!("[Backend] [Env] Creating MountainEnvironment...");
492
493					let Environment = Arc::new(MountainEnvironment::Create(ApplicationHandle.clone()));
494
495					info!("[Backend] [Env] MountainEnvironment ready.");
496
497					// ---------------------------------------------------------
498					// [Backend] [Runtime] ApplicationRunTime
499					// ---------------------------------------------------------
500					debug!("[Backend] [Runtime] Creating ApplicationRunTime...");
501
502					let RunTime = Arc::new(ApplicationRunTime::Create(
503						SchedulerForRunTime.clone(),
504						Environment.clone(),
505					));
506
507					ApplicationHandle.manage(RunTime);
508
509					info!("[Backend] [Runtime] ApplicationRunTime managed.");
510
511					// ---------------------------------------------------------
512					// [Lifecycle] [PostSetup] Async initialization work
513					// ---------------------------------------------------------
514					let PostSetupApplicationHandle = ApplicationHandle.clone();
515
516					let PostSetupEnvironment = Environment.clone();
517
518					tauri::async_runtime::spawn(async move {
519						info!("[Lifecycle] [PostSetup] Starting...");
520
521						let AppStateForSetup = PostSetupEnvironment.ApplicationState.clone();
522
523						TraceStep!("[Lifecycle] [PostSetup] AppState cloned.");
524
525						// [Config]
526						debug!("[Config] InitializeAndMergeConfigurations starting...");
527
528						if let Err(Error) = InitializeAndMergeConfigurations(&PostSetupEnvironment).await {
529							error!("[Config] InitializeAndMergeConfigurations failed: {}", Error);
530						} else {
531							info!("[Config] InitializeAndMergeConfigurations done.");
532						}
533
534						// [Extensions] [ScanPaths]
535						{
536							debug!("[Extensions] [ScanPaths] Locking ExtensionScanPaths...");
537
538							let mut ScanPathsGuard = AppStateForSetup
539								.ExtensionScanPaths
540								.lock()
541								.map_err(MapLockError)
542								.expect("FATAL: Failed to lock ExtensionScanPaths");
543
544							debug!("[Extensions] [ScanPaths] Adding default scan paths...");
545
546							if let Ok(ExecutableDirectory) = std::env::current_exe() {
547								if let Some(Parent) = ExecutableDirectory.parent() {
548									let ResourcesPath = Parent.join("../Resources/extensions");
549
550									let LocalPath = Parent.join("extensions");
551
552									debug!(
553										"[Extensions] [ScanPaths] + {}",
554										ResourcesPath.display()
555									);
556
557									ScanPathsGuard.push(ResourcesPath);
558
559									debug!(
560										"[Extensions] [ScanPaths] + {}",
561										LocalPath.display()
562									);
563
564									ScanPathsGuard.push(LocalPath);
565
566								}
567							}
568
569							info!(
570								"[Extensions] [ScanPaths] Initialized: {:?}",
571								*ScanPathsGuard
572							);
573						}
574
575						// [Extensions] [Scan]
576						debug!("[Extensions] [Scan] ScanAndPopulateExtensions starting...");
577
578						if let Err(Error) =
579							ScanAndPopulateExtensions(PostSetupApplicationHandle.clone(), &AppStateForSetup).await
580						{
581							error!("[Extensions] [Scan] Failed: {}", Error);
582						} else {
583							info!("[Extensions] [Scan] Completed.");
584						}
585
586						// [Vine] [gRPC]
587						debug!("[Vine] [Init] Starting Vine gRPC server...");
588
589						if let Err(Error) = Vine::Server::Initialize::Initialize(
590							PostSetupApplicationHandle.clone(),
591							"[::1]:50051".to_string(),
592						) {
593							error!("[Vine] [Init] Failed: {}", Error);
594						} else {
595							info!("[Vine] [Init] Ready.");
596						}
597
598						// [Cocoon] [Sidecar]
599						debug!("[Cocoon] [Init] InitializeCocoon starting...");
600
601						if let Err(Error) = InitializeCocoon(&PostSetupApplicationHandle, &PostSetupEnvironment).await {
602							error!("[Cocoon] [Init] Failed: {}", Error);
603						} else {
604							info!("[Cocoon] [Init] Ready.");
605						}
606
607						info!("[Lifecycle] [PostSetup] Complete. System ready.");
608					});
609
610					Ok(())
611				}
612			})
613			// ---------------------------------------------------------------------
614			// [Tauri] [Plugins] Standard
615			// ---------------------------------------------------------------------
616			.plugin(tauri_plugin_dialog::init())
617			.plugin(tauri_plugin_fs::init())
618			// ---------------------------------------------------------------------
619			// [IPC] Command routing
620			// ---------------------------------------------------------------------
621			.invoke_handler(tauri::generate_handler![
622				SwitchTrayIcon,
623				MountainGetWorkbenchConfiguration,
624				Command::TreeView::GetTreeViewChildren,
625				Command::LanguageFeature::MountainProvideHover,
626				Command::LanguageFeature::MountainProvideCompletions,
627				Command::LanguageFeature::MountainProvideDefinition,
628				Command::LanguageFeature::MountainProvideReferences,
629				Command::SourceControlManagement::GetAllSourceControlManagementState,
630				Command::Keybinding::GetResolvedKeybinding,
631				crate::Track::DispatchLogic::DispatchFrontendCommand,
632				crate::Track::DispatchLogic::ResolveUIRequest,
633			])
634			// ---------------------------------------------------------------------
635			// [Tauri] Build & run loop
636			// ---------------------------------------------------------------------
637			.build(tauri::generate_context!())
638			.expect("FATAL: Error while building Mountain Tauri application")
639			.run(move |ApplicationHandle, Event| {
640				// Debug-only: log selected lifecycle events (but avoid super-noisy ones).
641				if cfg!(debug_assertions) {
642					match &Event {
643						RunEvent::MainEventsCleared => {},
644						RunEvent::WindowEvent { .. } => {},
645						_ => debug!("[Lifecycle] [RunEvent] {:?}", Event),
646					}
647				}
648
649				if let RunEvent::ExitRequested { api, .. } = Event {
650					warn!("[Lifecycle] [Shutdown] Exit requested. Starting graceful shutdown...");
651
652					api.prevent_exit();
653
654					let SchedulerHandle = SchedulerForShutdown.clone();
655
656					let ApplicationHandleClone = ApplicationHandle.clone();
657
658					tokio::spawn(async move {
659						debug!("[Lifecycle] [Shutdown] Shutting down ApplicationRunTime...");
660
661						if let Some(RunTime) = ApplicationHandleClone.try_state::<Arc<ApplicationRunTime>>() {
662							RunTime.inner().clone().Shutdown().await;
663
664							info!("[Lifecycle] [Shutdown] ApplicationRunTime stopped.");
665						} else {
666							error!("[Lifecycle] [Shutdown] ApplicationRunTime not found.");
667						}
668
669						debug!("[Lifecycle] [Shutdown] Stopping Echo scheduler...");
670
671						if let Ok(mut Scheduler) = Arc::try_unwrap(SchedulerHandle) {
672							Scheduler.Stop().await;
673
674							info!("[Lifecycle] [Shutdown] Echo scheduler stopped.");
675						} else {
676							error!("[Lifecycle] [Shutdown] Scheduler not exclusively owned; cannot stop cleanly.");
677						}
678
679						info!("[Lifecycle] [Shutdown] Done. Exiting process.");
680
681						ApplicationHandleClone.exit(0);
682					});
683
684				}
685			});
686
687		info!("[Lifecycle] [Exit] Mountain application has shut down.");
688	});
689}