par_term_scripting/
manager.rs1use std::collections::HashMap;
7
8use super::process::ScriptProcess;
9use super::protocol::{ScriptCommand, ScriptEvent};
10use par_term_config::ScriptConfig;
11
12pub type ScriptId = u64;
14
15pub const DEFAULT_WRITE_TEXT_RATE: u32 = 10;
17pub const DEFAULT_RUN_COMMAND_RATE: u32 = 1;
19
20pub struct ScriptManager {
26 next_id: ScriptId,
28 processes: HashMap<ScriptId, ScriptProcess>,
30 panels: HashMap<ScriptId, (String, String)>,
32 write_text_times: HashMap<ScriptId, std::time::Instant>,
34 run_command_times: HashMap<ScriptId, std::time::Instant>,
36}
37
38impl ScriptManager {
39 pub fn new() -> Self {
41 Self {
42 next_id: 1,
43 processes: HashMap::new(),
44 panels: HashMap::new(),
45 write_text_times: HashMap::new(),
46 run_command_times: HashMap::new(),
47 }
48 }
49
50 pub fn start_script(&mut self, config: &ScriptConfig) -> Result<ScriptId, String> {
60 let (command, args) = if config.script_path.ends_with(".py") {
61 let mut full_args = vec![config.script_path.as_str()];
62 let arg_refs: Vec<&str> = config.args.iter().map(String::as_str).collect();
63 full_args.extend(arg_refs);
64 (
65 "python3".to_string(),
66 full_args.into_iter().map(String::from).collect::<Vec<_>>(),
67 )
68 } else {
69 let arg_refs: Vec<String> = config.args.to_vec();
70 (config.script_path.clone(), arg_refs)
71 };
72
73 let arg_strs: Vec<&str> = args.iter().map(String::as_str).collect();
74 let process = ScriptProcess::spawn(&command, &arg_strs, &config.env_vars)?;
75
76 let id = self.next_id;
77 self.next_id += 1;
78 self.processes.insert(id, process);
79
80 Ok(id)
81 }
82
83 pub fn is_running(&mut self, id: ScriptId) -> bool {
87 self.processes.get_mut(&id).is_some_and(|p| p.is_running())
88 }
89
90 pub fn send_event(&mut self, id: ScriptId, event: &ScriptEvent) -> Result<(), String> {
95 let process = self
96 .processes
97 .get_mut(&id)
98 .ok_or_else(|| format!("No script with id {}", id))?;
99 process.send_event(event)
100 }
101
102 pub fn broadcast_event(&mut self, event: &ScriptEvent) {
107 for process in self.processes.values_mut() {
108 let _ = process.send_event(event);
109 }
110 }
111
112 pub fn read_commands(&self, id: ScriptId) -> Vec<ScriptCommand> {
116 self.processes
117 .get(&id)
118 .map(|p| p.read_commands())
119 .unwrap_or_default()
120 }
121
122 pub fn read_errors(&self, id: ScriptId) -> Vec<String> {
126 self.processes
127 .get(&id)
128 .map(|p| p.read_errors())
129 .unwrap_or_default()
130 }
131
132 pub fn stop_script(&mut self, id: ScriptId) {
137 if let Some(mut process) = self.processes.remove(&id) {
138 process.stop();
139 }
140 self.panels.remove(&id);
141 self.write_text_times.remove(&id);
142 self.run_command_times.remove(&id);
143 }
144
145 pub fn stop_all(&mut self) {
147 for (_, mut process) in self.processes.drain() {
148 process.stop();
149 }
150 self.panels.clear();
151 self.write_text_times.clear();
152 self.run_command_times.clear();
153 }
154
155 pub fn check_write_text_rate(&mut self, id: ScriptId, limit_per_sec: u32) -> bool {
161 let rate = if limit_per_sec == 0 {
162 DEFAULT_WRITE_TEXT_RATE
163 } else {
164 limit_per_sec
165 };
166 let min_interval_ms = 1000u64 / rate as u64;
167 let now = std::time::Instant::now();
168 if self
169 .write_text_times
170 .get(&id)
171 .is_some_and(|last| (now.duration_since(*last).as_millis() as u64) < min_interval_ms)
172 {
173 return false;
174 }
175 self.write_text_times.insert(id, now);
176 true
177 }
178
179 pub fn check_run_command_rate(&mut self, id: ScriptId, limit_per_sec: u32) -> bool {
185 let rate = if limit_per_sec == 0 {
186 DEFAULT_RUN_COMMAND_RATE
187 } else {
188 limit_per_sec
189 };
190 let min_interval_ms = 1000u64 / rate as u64;
191 let now = std::time::Instant::now();
192 if self
193 .run_command_times
194 .get(&id)
195 .is_some_and(|last| (now.duration_since(*last).as_millis() as u64) < min_interval_ms)
196 {
197 return false;
198 }
199 self.run_command_times.insert(id, now);
200 true
201 }
202
203 pub fn get_panel(&self, id: ScriptId) -> Option<&(String, String)> {
207 self.panels.get(&id)
208 }
209
210 pub fn set_panel(&mut self, id: ScriptId, title: String, content: String) {
212 self.panels.insert(id, (title, content));
213 }
214
215 pub fn clear_panel(&mut self, id: ScriptId) {
217 self.panels.remove(&id);
218 }
219
220 pub fn script_ids(&self) -> Vec<ScriptId> {
222 self.processes.keys().copied().collect()
223 }
224}
225
226impl Default for ScriptManager {
227 fn default() -> Self {
228 Self::new()
229 }
230}
231
232impl Drop for ScriptManager {
233 fn drop(&mut self) {
234 self.stop_all();
235 }
236}