libsurfer/
batch_commands.rs1use camino::Utf8PathBuf;
2use eyre::Context as _;
3use futures::FutureExt as _;
4use tracing::{error, info, trace};
5
6use crate::{
7 SystemState,
8 async_util::perform_async_work,
9 channels::checked_send,
10 command_parser::get_parser,
11 fzcmd::parse_command,
12 message::Message,
13 wave_source::{LoadProgress, LoadProgressStatus},
14};
15
16impl SystemState {
17 pub(crate) fn handle_batch_commands(&mut self) {
19 let mut should_exit = false;
20 while self.can_start_batch_command() {
22 if let Some(cmd) = self.batch_messages.pop_front() {
23 if matches!(cmd, Message::Exit) {
24 should_exit = true;
25 }
26 info!("Applying batch command: {cmd:?}");
27 self.update(cmd);
28 } else {
29 break; }
31 }
32
33 if !self.batch_messages_completed
35 && self.batch_messages.is_empty()
36 && self.can_start_batch_command()
37 {
38 self.batch_messages_completed = true;
39
40 if should_exit {
41 info!("Exiting due to batch command");
42 let sender = self.channels.msg_sender.clone();
43 checked_send(&sender, Message::Exit);
44 }
45 }
46 }
47
48 pub(crate) fn can_start_batch_command(&self) -> bool {
50 self.progress_tracker.is_none()
52 }
53
54 pub fn batch_commands_completed(&self) -> bool {
56 debug_assert!(
57 self.batch_messages_completed || !self.batch_messages.is_empty(),
58 "completed implies no commands"
59 );
60 self.batch_messages_completed
61 }
62
63 pub fn add_batch_commands<I: IntoIterator<Item = String>>(&mut self, commands: I) {
64 let messages = self.parse_batch_commands(commands);
65 self.add_batch_messages(messages);
66 }
67
68 pub fn add_batch_messages<I: IntoIterator<Item = Message>>(&mut self, messages: I) {
69 for msg in messages {
70 self.batch_messages.push_back(msg);
71 self.batch_messages_completed = false;
72 }
73 }
74
75 pub fn add_batch_message(&mut self, msg: Message) {
76 self.add_batch_messages([msg]);
77 }
78
79 pub fn parse_batch_commands<I: IntoIterator<Item = String>>(
80 &mut self,
81 cmds: I,
82 ) -> Vec<Message> {
83 trace!("Parsing batch commands");
84
85 cmds
86 .into_iter()
87 .enumerate()
89 .map(|(no, line)| {
91 trace!("{no: >2} {line}");
92 (no, line)
93 })
94 .map(|(no, line)| (no + 1, line))
96 .map(|(no, line)| (no, line.trim().to_string()))
97 .map(|(no, line)| (no, line.split('#').next().unwrap().to_string()))
99 .filter(|(_no, line)| !line.is_empty())
100 .flat_map(|(no, line)| {
101 line.split(';')
102 .map(|cmd| (no, cmd.trim().to_string()))
103 .collect::<Vec<_>>()
104 })
105 .filter_map(|(no, command)| {
106 if command.starts_with("run_command_file ") {
107 #[cfg(not(target_arch = "wasm32"))]
111 {
112 if let Some(path_str) = command.split_ascii_whitespace().nth(1) {
113 match Utf8PathBuf::from_path_buf(path_str.into()) {
114 Ok(utf8_path) => {
115 self.add_batch_commands(read_command_file(&utf8_path));
116 }
117 Err(_) => {
118 error!("Invalid UTF-8 path in run_command_file on line {no}: {path_str}");
119 }
120 }
121 } else {
122 error!("Missing file path in run_command_file command on line {no}");
123 }
124 }
125 #[cfg(target_arch = "wasm32")]
126 error!("Cannot use run_command_file in command files running on WASM");
127 None
128 } else {
129 parse_command(&command, get_parser(self))
130 .map_err(|e| {
131 error!("Error on batch commands line {no}: {e:#?}");
132 e
133 })
134 .ok()
135 }
136 })
137 .collect::<Vec<_>>()
138 }
139
140 pub fn load_commands_from_url(&mut self, url: String) {
141 let sender = self.channels.msg_sender.clone();
142 let url_ = url.clone();
143 perform_async_work(async move {
144 let maybe_response = reqwest::get(&url)
145 .map(|e| e.with_context(|| format!("Failed fetch download {url}")))
146 .await;
147 let response: reqwest::Response = match maybe_response {
148 Ok(r) => r,
149 Err(e) => {
150 checked_send(&sender, Message::Error(e));
151 return;
152 }
153 };
154
155 let bytes = response
157 .bytes()
158 .map(|e| e.with_context(|| format!("Failed to download {url}")))
159 .await;
160
161 let msg = match bytes {
162 Ok(b) => Message::CommandFileDownloaded(url, b),
163 Err(e) => Message::Error(e),
164 };
165 checked_send(&sender, msg);
166 });
167
168 self.progress_tracker = Some(LoadProgress::new(LoadProgressStatus::Downloading(url_)));
169 }
170}
171
172#[must_use]
173pub fn read_command_file(cmd_file: &Utf8PathBuf) -> Vec<String> {
174 std::fs::read_to_string(cmd_file)
175 .map_err(|e| error!("Failed to read commands from {cmd_file}. {e:#?}"))
176 .ok()
177 .map(|file_content| file_content.lines().map(str::to_string).collect())
178 .unwrap_or_default()
179}
180
181#[must_use]
182pub fn read_command_bytes(bytes: Vec<u8>) -> Vec<String> {
183 String::from_utf8(bytes)
184 .map_err(|e| error!("Failed to read commands from file. {e:#?}"))
185 .ok()
186 .map(|file_content| file_content.lines().map(str::to_string).collect())
187 .unwrap_or_default()
188}