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