DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_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
52const MAX_ENV_VARS:usize = 1000;
54
55#[derive(Clone, Serialize, Deserialize)]
64pub struct TerminalStateDTO {
65 pub Identifier:u64,
68
69 #[serde(skip_serializing_if = "String::is_empty")]
71 pub Name:String,
72
73 pub OSProcessIdentifier:Option<u32>,
75
76 #[serde(skip_serializing_if = "String::is_empty")]
79 pub ShellPath:String,
80
81 #[serde(skip_serializing_if = "Vec::is_empty")]
83 pub ShellArguments:Vec<String>,
84
85 pub CurrentWorkingDirectory:Option<PathBuf>,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub EnvironmentVariables:Option<HashMap<String, Option<String>>>,
91
92 pub IsPTY:bool,
94
95 #[serde(skip)]
98 pub PTYInputTransmitter:Option<TokioMPSC::Sender<String>>,
99
100 #[serde(skip)]
102 pub ReaderTaskHandle:Option<Arc<TokioMutex<Option<JoinHandle<()>>>>>,
103
104 #[serde(skip)]
106 pub ProcessWaitHandle:Option<Arc<TokioMutex<Option<JoinHandle<()>>>>>,
107
108 #[serde(skip)]
111 pub PTYMaster:Option<PtyMasterHandle>,
112}
113
114impl TerminalStateDTO {
115 pub fn Create(Identifier:u64, Name:String, OptionsValue:&Value, DefaultShellPath:String) -> Result<Self, String> {
127 if Name.len() > MAX_TERMINAL_NAME_LENGTH {
129 return Err(format!(
130 "Terminal name exceeds maximum length of {} bytes",
131 MAX_TERMINAL_NAME_LENGTH
132 ));
133 }
134
135 let ShellPath = OptionsValue
136 .get("shellPath")
137 .and_then(Value::as_str)
138 .unwrap_or(&DefaultShellPath)
139 .to_string();
140
141 if ShellPath.len() > MAX_SHELL_PATH_LENGTH {
143 return Err(format!("Shell path exceeds maximum length of {} bytes", MAX_SHELL_PATH_LENGTH));
144 }
145
146 let ShellArguments = match OptionsValue.get("shellArgs") {
147 Some(Value::Array(Array)) => {
148 let Args:Vec<String> = Array.iter().filter_map(Value::as_str).map(String::from).collect();
149
150 if Args.len() > MAX_SHELL_ARGUMENTS {
152 return Err(format!("Shell arguments exceed maximum count of {}", MAX_SHELL_ARGUMENTS));
153 }
154
155 for Arg in &Args {
157 if Arg.len() > MAX_ARGUMENT_LENGTH {
158 return Err(format!(
159 "Shell argument exceeds maximum length of {} bytes",
160 MAX_ARGUMENT_LENGTH
161 ));
162 }
163 }
164
165 Args
166 },
167
168 _ => Vec::new(),
169 };
170
171 let CWD = OptionsValue.get("cwd").and_then(Value::as_str).map(PathBuf::from);
172
173 let EnvVars = None;
175
176 Ok(Self {
177 Identifier,
178 Name,
179 ShellPath,
180 ShellArguments,
181 CurrentWorkingDirectory:CWD,
182 EnvironmentVariables:EnvVars,
183 OSProcessIdentifier:None,
184 IsPTY:true,
185 PTYInputTransmitter:None,
186 ReaderTaskHandle:None,
187 ProcessWaitHandle:None,
188 PTYMaster:None,
189 })
190 }
191
192 pub fn IsRunning(&self) -> bool { self.OSProcessIdentifier.is_some() }
194
195 pub fn HasInputChannel(&self) -> bool { self.PTYInputTransmitter.is_some() }
197
198 pub fn GetWorkingDirectory(&self) -> String {
200 self.CurrentWorkingDirectory
201 .as_ref()
202 .and_then(|Path| Path.to_str())
203 .unwrap_or("")
204 .to_string()
205 }
206
207 pub fn ClearHandles(&mut self) {
209 self.PTYInputTransmitter = None;
210
211 self.ReaderTaskHandle = None;
212
213 self.ProcessWaitHandle = None;
214
215 self.PTYMaster = None;
216 }
217}
218
219impl std::fmt::Debug for TerminalStateDTO {
220 fn fmt(&self, Formatter:&mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 Formatter
222 .debug_struct("TerminalStateDTO")
223 .field("Identifier", &self.Identifier)
224 .field("Name", &self.Name)
225 .field("OSProcessIdentifier", &self.OSProcessIdentifier)
226 .field("ShellPath", &self.ShellPath)
227 .field("ShellArguments", &self.ShellArguments)
228 .field("CurrentWorkingDirectory", &self.CurrentWorkingDirectory)
229 .field("EnvironmentVariables", &self.EnvironmentVariables)
230 .field("IsPTY", &self.IsPTY)
231 .field("PTYInputTransmitter", &self.PTYInputTransmitter.as_ref().map(|_| "<channel>"))
232 .field("ReaderTaskHandle", &self.ReaderTaskHandle.as_ref().map(|_| "<task>"))
233 .field("ProcessWaitHandle", &self.ProcessWaitHandle.as_ref().map(|_| "<task>"))
234 .field("PTYMaster", &self.PTYMaster.as_ref().map(|_| "<master-pty>"))
235 .finish()
236 }
237}