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