1use anyhow::Result;
2use quick_xml::events::Event;
3use quick_xml::Reader;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
7pub struct JobSummary {
8 pub job_id: String,
9 pub url: String,
10}
11
12#[derive(Debug, Clone)]
13pub struct JobStatus {
14 pub job_id: String,
15 pub job_stage: String,
16 pub failed: bool,
17 pub date_submitted: Option<String>,
18 pub self_uri: String,
19 pub results_uri: Option<String>,
20 pub messages: Vec<JobMessage>,
21}
22
23#[derive(Debug, Clone)]
24pub struct JobMessage {
25 pub stage: String,
26 pub text: String,
27 pub timestamp: Option<String>,
28}
29
30#[derive(Debug, Clone)]
31pub struct OutputFile {
32 pub filename: String,
33 pub download_uri: String,
34 pub size: u64,
35}
36
37#[derive(Debug, Clone)]
38pub struct DownloadedFile {
39 pub filename: String,
40 pub path: PathBuf,
41 pub size: u64,
42}
43
44pub fn parse_job_list(xml: &str) -> Result<Vec<JobSummary>> {
45 let mut reader = Reader::from_str(xml);
46 reader.config_mut().trim_text(true);
47
48 let mut jobs = Vec::new();
49 let mut buf = Vec::new();
50 let mut current_url = None;
51 let mut current_title = None;
52 let mut in_self_uri = false;
53
54 loop {
55 match reader.read_event_into(&mut buf) {
56 Ok(Event::Start(e)) if e.name().as_ref() == b"selfUri" => {
57 in_self_uri = true;
58 }
59 Ok(Event::End(e)) if e.name().as_ref() == b"selfUri" => {
60 in_self_uri = false;
61 if let (Some(url), Some(title)) = (current_url.take(), current_title.take()) {
62 jobs.push(JobSummary { job_id: title, url });
63 }
64 }
65 Ok(Event::Start(e)) if in_self_uri && e.name().as_ref() == b"url" => {
66 if let Ok(Event::Text(t)) = reader.read_event_into(&mut buf) {
67 current_url = reader
68 .decoder()
69 .decode(t.as_ref())
70 .ok()
71 .map(|s| s.to_string());
72 }
73 }
74 Ok(Event::Start(e)) if in_self_uri && e.name().as_ref() == b"title" => {
75 if let Ok(Event::Text(t)) = reader.read_event_into(&mut buf) {
76 current_title = reader
77 .decoder()
78 .decode(t.as_ref())
79 .ok()
80 .map(|s| s.to_string());
81 }
82 }
83 Ok(Event::Eof) => break,
84 Err(e) => {
85 return Err(anyhow::anyhow!(
86 "XML parse error at position {}: {}",
87 reader.buffer_position(),
88 e
89 ))
90 }
91 _ => {}
92 }
93 buf.clear();
94 }
95
96 Ok(jobs)
97}
98
99pub fn parse_job_status(xml: &str) -> Result<JobStatus> {
100 let mut reader = Reader::from_str(xml);
101 reader.config_mut().trim_text(true);
102
103 let mut buf = Vec::new();
104 let mut job_id = String::new();
105 let mut job_stage = String::new();
106 let mut failed = false;
107 let mut date_submitted = None;
108 let mut self_uri = String::new();
109 let mut results_uri = None;
110 let mut messages = Vec::new();
111
112 let mut current_tag = String::new();
113 let mut in_results_uri = false;
114 let mut in_message = false;
115 let mut current_message_stage = String::new();
116 let mut current_message_text = String::new();
117 let mut current_message_timestamp = None;
118
119 loop {
120 match reader.read_event_into(&mut buf) {
121 Ok(Event::Start(e)) => {
122 current_tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
123 match current_tag.as_str() {
124 "resultsUri" => in_results_uri = true,
125 "message" => {
126 in_message = true;
127 current_message_stage.clear();
128 current_message_text.clear();
129 current_message_timestamp = None;
130 }
131 _ => {}
132 }
133 }
134 Ok(Event::End(e)) => {
135 let tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
136 match tag.as_str() {
137 "resultsUri" => in_results_uri = false,
138 "message" => {
139 if in_message {
140 messages.push(JobMessage {
141 stage: current_message_stage.clone(),
142 text: current_message_text.clone(),
143 timestamp: current_message_timestamp.clone(),
144 });
145 in_message = false;
146 }
147 }
148 _ => {}
149 }
150 current_tag.clear();
151 }
152 Ok(Event::Text(e)) => {
153 let text = reader
154 .decoder()
155 .decode(e.as_ref())
156 .map(|s| s.to_string())
157 .unwrap_or_default();
158 match current_tag.as_str() {
159 "jobHandle" => job_id = text,
160 "jobStage" => job_stage = text,
161 "failed" => failed = text == "true",
162 "dateSubmitted" => date_submitted = Some(text),
163 "url" if in_results_uri => results_uri = Some(text),
164 "url" if !in_results_uri && self_uri.is_empty() => self_uri = text,
165 "stage" if in_message => current_message_stage = text,
166 "text" if in_message => current_message_text = text,
167 "timestamp" if in_message => current_message_timestamp = Some(text),
168 _ => {}
169 }
170 }
171 Ok(Event::Eof) => break,
172 Err(e) => return Err(anyhow::anyhow!("XML parse error: {}", e)),
173 _ => {}
174 }
175 buf.clear();
176 }
177
178 if job_id.is_empty() {
179 anyhow::bail!("Failed to parse job status: missing job ID");
180 }
181
182 Ok(JobStatus {
183 job_id,
184 job_stage,
185 failed,
186 date_submitted,
187 self_uri,
188 results_uri,
189 messages,
190 })
191}
192
193pub fn parse_output_files(xml: &str) -> Result<Vec<OutputFile>> {
194 let mut reader = Reader::from_str(xml);
195 reader.config_mut().trim_text(true);
196
197 let mut files = Vec::new();
198 let mut buf = Vec::new();
199
200 let mut in_jobfile = false;
201 let mut in_download_uri = false;
202 let mut current_filename = None;
203 let mut current_download_uri = None;
204 let mut current_size = None;
205 let mut current_tag = String::new();
206
207 loop {
208 match reader.read_event_into(&mut buf) {
209 Ok(Event::Start(e)) => {
210 let tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
211 match tag.as_str() {
212 "jobfile" => in_jobfile = true,
213 "downloadUri" => in_download_uri = true,
214 _ => current_tag = tag,
215 }
216 }
217 Ok(Event::End(e)) => {
218 let tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
219 match tag.as_str() {
220 "jobfile" => {
221 if let (Some(filename), Some(download_uri), Some(size)) = (
222 current_filename.take(),
223 current_download_uri.take(),
224 current_size.take(),
225 ) {
226 files.push(OutputFile {
227 filename,
228 download_uri,
229 size,
230 });
231 }
232 in_jobfile = false;
233 }
234 "downloadUri" => in_download_uri = false,
235 _ => {}
236 }
237 current_tag.clear();
238 }
239 Ok(Event::Text(e)) => {
240 let text = reader
241 .decoder()
242 .decode(e.as_ref())
243 .map(|s| s.to_string())
244 .unwrap_or_default();
245 if in_jobfile {
246 match current_tag.as_str() {
247 "filename" => current_filename = Some(text),
248 "length" => current_size = text.parse().ok(),
249 "url" if in_download_uri => current_download_uri = Some(text),
250 _ => {}
251 }
252 }
253 }
254 Ok(Event::Eof) => break,
255 Err(e) => return Err(anyhow::anyhow!("XML parse error: {}", e)),
256 _ => {}
257 }
258 buf.clear();
259 }
260
261 Ok(files)
262}