Mountain/ApplicationState/DTO/
TerminalStateDTO.rs1use std::{collections::HashMap, path::PathBuf, sync::Arc};
22
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25use tokio::{
26 sync::{Mutex as TokioMutex, mpsc as TokioMPSC},
27 task::JoinHandle,
28};
29
30const MAX_TERMINAL_NAME_LENGTH:usize = 128;
32
33const MAX_SHELL_PATH_LENGTH:usize = 1024;
35
36const MAX_SHELL_ARGUMENTS:usize = 100;
38
39const MAX_ARGUMENT_LENGTH:usize = 4096;
41
42const MAX_ENV_VARS:usize = 1000;
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct TerminalStateDTO {
50 pub Identifier:u64,
53
54 #[serde(skip_serializing_if = "String::is_empty")]
56 pub Name:String,
57
58 pub OSProcessIdentifier:Option<u32>,
60
61 #[serde(skip_serializing_if = "String::is_empty")]
64 pub ShellPath:String,
65
66 #[serde(skip_serializing_if = "Vec::is_empty")]
68 pub ShellArguments:Vec<String>,
69
70 pub CurrentWorkingDirectory:Option<PathBuf>,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub EnvironmentVariables:Option<HashMap<String, Option<String>>>,
76
77 pub IsPTY:bool,
79
80 #[serde(skip)]
83 pub PTYInputTransmitter:Option<TokioMPSC::Sender<String>>,
84
85 #[serde(skip)]
87 pub ReaderTaskHandle:Option<Arc<TokioMutex<Option<JoinHandle<()>>>>>,
88
89 #[serde(skip)]
91 pub ProcessWaitHandle:Option<Arc<TokioMutex<Option<JoinHandle<()>>>>>,
92}
93
94impl TerminalStateDTO {
95 pub fn Create(Identifier:u64, Name:String, OptionsValue:&Value, DefaultShellPath:String) -> Result<Self, String> {
107 if Name.len() > MAX_TERMINAL_NAME_LENGTH {
109 return Err(format!(
110 "Terminal name exceeds maximum length of {} bytes",
111 MAX_TERMINAL_NAME_LENGTH
112 ));
113 }
114
115 let ShellPath = OptionsValue
116 .get("shellPath")
117 .and_then(Value::as_str)
118 .unwrap_or(&DefaultShellPath)
119 .to_string();
120
121 if ShellPath.len() > MAX_SHELL_PATH_LENGTH {
123 return Err(format!("Shell path exceeds maximum length of {} bytes", MAX_SHELL_PATH_LENGTH));
124 }
125
126 let ShellArguments = match OptionsValue.get("shellArgs") {
127 Some(Value::Array(Array)) => {
128 let Args:Vec<String> = Array.iter().filter_map(Value::as_str).map(String::from).collect();
129
130 if Args.len() > MAX_SHELL_ARGUMENTS {
132 return Err(format!("Shell arguments exceed maximum count of {}", MAX_SHELL_ARGUMENTS));
133 }
134
135 for Arg in &Args {
137 if Arg.len() > MAX_ARGUMENT_LENGTH {
138 return Err(format!(
139 "Shell argument exceeds maximum length of {} bytes",
140 MAX_ARGUMENT_LENGTH
141 ));
142 }
143 }
144
145 Args
146 },
147
148 _ => Vec::new(),
149 };
150
151 let CWD = OptionsValue.get("cwd").and_then(Value::as_str).map(PathBuf::from);
152
153 let EnvVars = None;
155
156 Ok(Self {
157 Identifier,
158 Name,
159 ShellPath,
160 ShellArguments,
161 CurrentWorkingDirectory:CWD,
162 EnvironmentVariables:EnvVars,
163 OSProcessIdentifier:None,
164 IsPTY:true,
165 PTYInputTransmitter:None,
166 ReaderTaskHandle:None,
167 ProcessWaitHandle:None,
168 })
169 }
170
171 pub fn IsRunning(&self) -> bool { self.OSProcessIdentifier.is_some() }
173
174 pub fn HasInputChannel(&self) -> bool { self.PTYInputTransmitter.is_some() }
176
177 pub fn GetWorkingDirectory(&self) -> String {
179 self.CurrentWorkingDirectory
180 .as_ref()
181 .and_then(|Path| Path.to_str())
182 .unwrap_or("")
183 .to_string()
184 }
185
186 pub fn ClearHandles(&mut self) {
188 self.PTYInputTransmitter = None;
189 self.ReaderTaskHandle = None;
190 self.ProcessWaitHandle = None;
191 }
192}