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