Mountain/ApplicationState/DTO/
TerminalStateDTO.rs1use std::{
22 collections::HashMap,
23 path::PathBuf,
24 sync::{Arc, Mutex as StandardMutex},
25};
26
27use portable_pty::MasterPty;
28use serde::{Deserialize, Serialize};
29use serde_json::Value;
30use tokio::{
31 sync::{Mutex as TokioMutex, mpsc as TokioMPSC},
32 task::JoinHandle,
33};
34
35pub type PtyMasterHandle = Arc<StandardMutex<Box<dyn MasterPty + Send>>>;
39
40const MAX_TERMINAL_NAME_LENGTH:usize = 128;
42
43const MAX_SHELL_PATH_LENGTH:usize = 1024;
45
46const MAX_SHELL_ARGUMENTS:usize = 100;
48
49const MAX_ARGUMENT_LENGTH:usize = 4096;
51
52#[allow(dead_code)]
54const MAX_ENV_VARS:usize = 1000;
55
56#[derive(Clone, Serialize, Deserialize)]
65pub struct TerminalStateDTO {
66 pub Identifier:u64,
69
70 #[serde(skip_serializing_if = "String::is_empty")]
72 pub Name:String,
73
74 pub OSProcessIdentifier:Option<u32>,
76
77 #[serde(skip_serializing_if = "String::is_empty")]
80 pub ShellPath:String,
81
82 #[serde(skip_serializing_if = "Vec::is_empty")]
84 pub ShellArguments:Vec<String>,
85
86 pub CurrentWorkingDirectory:Option<PathBuf>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub EnvironmentVariables:Option<HashMap<String, Option<String>>>,
92
93 pub IsPTY:bool,
95
96 #[serde(skip)]
99 pub PTYInputTransmitter:Option<TokioMPSC::Sender<String>>,
100
101 #[serde(skip)]
103 pub ReaderTaskHandle:Option<Arc<TokioMutex<Option<JoinHandle<()>>>>>,
104
105 #[serde(skip)]
107 pub ProcessWaitHandle:Option<Arc<TokioMutex<Option<JoinHandle<()>>>>>,
108
109 #[serde(skip)]
112 pub PTYMaster:Option<PtyMasterHandle>,
113}
114
115impl TerminalStateDTO {
116 pub fn Create(Identifier:u64, Name:String, OptionsValue:&Value, DefaultShellPath:String) -> Result<Self, String> {
128 if Name.len() > MAX_TERMINAL_NAME_LENGTH {
130 return Err(format!(
131 "Terminal name exceeds maximum length of {} bytes",
132 MAX_TERMINAL_NAME_LENGTH
133 ));
134 }
135
136 let ShellPath = OptionsValue
137 .get("shellPath")
138 .and_then(Value::as_str)
139 .unwrap_or(&DefaultShellPath)
140 .to_string();
141
142 if ShellPath.len() > MAX_SHELL_PATH_LENGTH {
144 return Err(format!("Shell path exceeds maximum length of {} bytes", MAX_SHELL_PATH_LENGTH));
145 }
146
147 let ShellArguments = match OptionsValue.get("shellArgs") {
148 Some(Value::Array(Array)) => {
149 let Args:Vec<String> = Array.iter().filter_map(Value::as_str).map(String::from).collect();
150
151 if Args.len() > MAX_SHELL_ARGUMENTS {
153 return Err(format!("Shell arguments exceed maximum count of {}", MAX_SHELL_ARGUMENTS));
154 }
155
156 for Arg in &Args {
158 if Arg.len() > MAX_ARGUMENT_LENGTH {
159 return Err(format!(
160 "Shell argument exceeds maximum length of {} bytes",
161 MAX_ARGUMENT_LENGTH
162 ));
163 }
164 }
165
166 Args
167 },
168
169 _ => Vec::new(),
170 };
171
172 let CWD = OptionsValue.get("cwd").and_then(Value::as_str).map(PathBuf::from);
173
174 let EnvVars = None;
176
177 Ok(Self {
178 Identifier,
179 Name,
180 ShellPath,
181 ShellArguments,
182 CurrentWorkingDirectory:CWD,
183 EnvironmentVariables:EnvVars,
184 OSProcessIdentifier:None,
185 IsPTY:true,
186 PTYInputTransmitter:None,
187 ReaderTaskHandle:None,
188 ProcessWaitHandle:None,
189 PTYMaster:None,
190 })
191 }
192
193 pub fn IsRunning(&self) -> bool { self.OSProcessIdentifier.is_some() }
195
196 pub fn HasInputChannel(&self) -> bool { self.PTYInputTransmitter.is_some() }
198
199 pub fn GetWorkingDirectory(&self) -> String {
201 self.CurrentWorkingDirectory
202 .as_ref()
203 .and_then(|Path| Path.to_str())
204 .unwrap_or("")
205 .to_string()
206 }
207
208 pub fn ClearHandles(&mut self) {
210 self.PTYInputTransmitter = None;
211 self.ReaderTaskHandle = None;
212 self.ProcessWaitHandle = None;
213 self.PTYMaster = None;
214 }
215}
216
217impl std::fmt::Debug for TerminalStateDTO {
218 fn fmt(&self, Formatter:&mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 Formatter
220 .debug_struct("TerminalStateDTO")
221 .field("Identifier", &self.Identifier)
222 .field("Name", &self.Name)
223 .field("OSProcessIdentifier", &self.OSProcessIdentifier)
224 .field("ShellPath", &self.ShellPath)
225 .field("ShellArguments", &self.ShellArguments)
226 .field("CurrentWorkingDirectory", &self.CurrentWorkingDirectory)
227 .field("EnvironmentVariables", &self.EnvironmentVariables)
228 .field("IsPTY", &self.IsPTY)
229 .field("PTYInputTransmitter", &self.PTYInputTransmitter.as_ref().map(|_| "<channel>"))
230 .field("ReaderTaskHandle", &self.ReaderTaskHandle.as_ref().map(|_| "<task>"))
231 .field("ProcessWaitHandle", &self.ProcessWaitHandle.as_ref().map(|_| "<task>"))
232 .field("PTYMaster", &self.PTYMaster.as_ref().map(|_| "<master-pty>"))
233 .finish()
234 }
235}