Skip to main content

Mountain/Environment/OutputProvider/
ChannelContent.rs

1//! # Output Channel Content Helpers
2//!
3//! Internal helper functions for output channel content manipulation.
4//! These are not public API - they are called by the main provider
5//! implementation.
6
7use CommonLibrary::Error::CommonError::CommonError;
8use serde_json::json;
9use tauri::Emitter;
10
11use crate::{Environment::Utility, dev_log};
12
13/// Appends text to an output channel.
14/// Includes buffer size validation to prevent memory exhaustion.
15pub(super) async fn append_to_channel(
16	env:&crate::Environment::MountainEnvironment::MountainEnvironment,
17	channel_identifier:String,
18	value:String,
19) -> Result<(), CommonError> {
20	dev_log!("output", "[OutputProvider] Appending to channel: '{}'", channel_identifier);
21
22	// Validate input size to prevent memory exhaustion
23	if value.len() > 1_048_576 {
24		// 1MB limit per append
25		return Err(CommonError::InvalidArgument {
26			ArgumentName:"Value".into(),
27			Reason:"Append value exceeds maximum size of 1MB".into(),
28		});
29	}
30
31	let mut channels_guard = env
32		.ApplicationState
33		.Feature
34		.OutputChannels
35		.OutputChannels
36		.lock()
37		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
38
39	if let Some(channel_state) = channels_guard.get_mut(&channel_identifier) {
40		// Enforce total buffer size limit of 10MB per channel to prevent
41		// unbounded memory growth from excessive output accumulation.
42		const MAX_BUFFER_SIZE:usize = 10 * 1_048_576;
43		if channel_state.Buffer.len() + value.len() > MAX_BUFFER_SIZE {
44			// Trim from beginning to make room for new content.
45			// Keep 1MB headroom to avoid frequent reallocation.
46			let trim_size:usize = value.len() + 1_048_576;
47			if channel_state.Buffer.len() > trim_size {
48				let _ = channel_state.Buffer.drain(..trim_size);
49			}
50		}
51
52		channel_state.Buffer.push_str(&value);
53
54		let event_payload = json!({ "Id": channel_identifier, "AppendedText": value });
55
56		env.ApplicationHandle
57			.emit("sky://output/append", event_payload)
58			.map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
59	} else {
60		dev_log!(
61			"output",
62			"warn: [OutputProvider] Channel '{}' not found for append.",
63			channel_identifier
64		);
65	}
66
67	Ok(())
68}
69
70/// Replaces the entire content of an output channel.
71pub(super) async fn replace_channel_content(
72	env:&crate::Environment::MountainEnvironment::MountainEnvironment,
73	channel_identifier:String,
74	value:String,
75) -> Result<(), CommonError> {
76	dev_log!(
77		"output",
78		"[OutputProvider] Replacing content of channel: '{}'",
79		channel_identifier
80	);
81
82	let mut channels_guard = env
83		.ApplicationState
84		.Feature
85		.OutputChannels
86		.OutputChannels
87		.lock()
88		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
89
90	if let Some(channel_state) = channels_guard.get_mut(&channel_identifier) {
91		channel_state.Buffer = value.clone();
92
93		let event_payload = json!({ "Id": channel_identifier, "Content": value });
94
95		env.ApplicationHandle
96			.emit("sky://output/replace", event_payload)
97			.map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
98	} else {
99		dev_log!(
100			"output",
101			"warn: [OutputProvider] Channel '{}' not found for replace.",
102			channel_identifier
103		);
104	}
105
106	Ok(())
107}
108
109/// Clears all content from an output channel.
110pub(super) async fn clear_channel(
111	env:&crate::Environment::MountainEnvironment::MountainEnvironment,
112	channel_identifier:String,
113) -> Result<(), CommonError> {
114	dev_log!("output", "[OutputProvider] Clearing channel: '{}'", channel_identifier);
115
116	let mut channels_guard = env
117		.ApplicationState
118		.Feature
119		.OutputChannels
120		.OutputChannels
121		.lock()
122		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
123
124	if let Some(channel_state) = channels_guard.get_mut(&channel_identifier) {
125		channel_state.Buffer.clear();
126
127		env.ApplicationHandle
128			.emit("sky://output/clear", json!({ "Id": channel_identifier }))
129			.map_err(|Error| CommonError::UserInterfaceInteraction { Reason:Error.to_string() })?;
130	} else {
131		dev_log!(
132			"output",
133			"warn: [OutputProvider] Channel '{}' not found for clear.",
134			channel_identifier
135		);
136	}
137
138	Ok(())
139}