1use crate::args_parser::WrapperOptions;
11use crate::execution_attach::{attach_execution, ExecutionAttachResult};
12use crate::execution_control::{control_execution, ControlAction, ExecutionControlResult};
13use crate::execution_resume::{resume_execution_with_options, ExecutionResumeResult};
14use crate::execution_resume_all::{resume_all_executions, ExecutionResumeAllResult};
15use crate::execution_store::{CleanupOptions, ExecutionStore};
16use crate::log_uploader::upload_execution_log;
17use crate::status_formatter::{list_executions_filtered, query_status, StatusQueryResult};
18
19pub struct QueryOutcome {
21 pub success: bool,
22 pub output: Option<String>,
23 pub error: Option<String>,
24 pub exit_code: Option<i32>,
25}
26
27impl From<StatusQueryResult> for QueryOutcome {
28 fn from(result: StatusQueryResult) -> Self {
29 QueryOutcome {
30 success: result.success,
31 output: result.output,
32 error: result.error,
33 exit_code: None,
34 }
35 }
36}
37
38impl From<ExecutionControlResult> for QueryOutcome {
39 fn from(result: ExecutionControlResult) -> Self {
40 QueryOutcome {
41 success: result.success,
42 output: result.output,
43 error: result.error,
44 exit_code: None,
45 }
46 }
47}
48
49impl From<ExecutionAttachResult> for QueryOutcome {
50 fn from(result: ExecutionAttachResult) -> Self {
51 QueryOutcome {
52 success: result.success,
53 output: result.output,
54 error: result.error,
55 exit_code: result.exit_code,
56 }
57 }
58}
59
60impl From<ExecutionResumeResult> for QueryOutcome {
61 fn from(result: ExecutionResumeResult) -> Self {
62 QueryOutcome {
63 success: result.success,
64 output: result.output,
65 error: result.error,
66 exit_code: result.exit_code,
67 }
68 }
69}
70
71impl From<ExecutionResumeAllResult> for QueryOutcome {
72 fn from(result: ExecutionResumeAllResult) -> Self {
73 QueryOutcome {
74 success: result.success,
75 output: result.output,
76 error: result.error,
77 exit_code: None,
78 }
79 }
80}
81
82pub fn report_result(result: QueryOutcome) -> i32 {
84 if result.success {
85 if let Some(output) = result.output {
86 println!("{}", output);
87 }
88 return result.exit_code.unwrap_or(0);
89 }
90 if let Some(error) = result.error {
91 eprintln!("Error: {}", error);
92 }
93 result.exit_code.unwrap_or(1)
94}
95
96pub fn handle_upload_log_query(store: Option<&ExecutionStore>, identifier: &str) -> i32 {
98 match upload_execution_log(store, identifier) {
99 Ok(code) => code,
100 Err(error) => {
101 eprintln!("Error: {}", error);
102 1
103 }
104 }
105}
106
107pub fn handle_cleanup(store: Option<&ExecutionStore>, dry_run: bool) -> i32 {
112 handle_cleanup_target(store, dry_run, None)
113}
114
115pub fn handle_cleanup_target(
116 store: Option<&ExecutionStore>,
117 dry_run: bool,
118 target: Option<&str>,
119) -> i32 {
120 if let Some(target) = target {
121 let result = crate::execution_cleanup::cleanup_execution(
122 store,
123 target,
124 dry_run,
125 &crate::execution_control::SystemCommandRunner,
126 );
127 if !result.success {
128 if let Some(output) = &result.output {
129 println!("{}", output);
130 }
131 }
132 return report_result(result);
133 }
134 let Some(store) = store else {
135 eprintln!("Error: Execution tracking is disabled.");
136 return 1;
137 };
138
139 let result = store.cleanup_stale(CleanupOptions {
140 dry_run,
141 ..Default::default()
142 });
143
144 for error in &result.errors {
145 eprintln!("Error: {}", error);
146 }
147
148 if result.records.is_empty() {
149 println!("No stale records found.");
150 return 0;
151 }
152
153 if dry_run {
154 println!(
155 "Found {} stale record(s) that would be cleaned up:\n",
156 result.records.len()
157 );
158 } else {
159 println!("Cleaned up {} stale record(s):\n", result.cleaned);
160 }
161
162 for record in &result.records {
163 let start_time_display = chrono::DateTime::parse_from_rfc3339(&record.start_time)
164 .map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
165 .unwrap_or_else(|_| record.start_time.clone());
166
167 println!(" UUID: {}", record.uuid);
168 println!(" Command: {}", record.command);
169 println!(" Started: {}", start_time_display);
170 println!(
171 " PID: {}",
172 record
173 .pid
174 .map(|p| p.to_string())
175 .unwrap_or("N/A".to_string())
176 );
177 println!();
178 }
179
180 if dry_run {
181 println!("Run with --cleanup to actually clean up these records.");
182 }
183 0
184}
185
186pub fn dispatch_query_command(
191 options: &WrapperOptions,
192 store: Option<&ExecutionStore>,
193 command: &str,
194) -> Option<i32> {
195 let output_format = options.output_format.as_deref();
196
197 if let Some(ref identifier) = options.status {
198 return Some(report_result(
199 query_status(store, identifier, output_format).into(),
200 ));
201 }
202 if options.list {
203 return Some(report_result(
204 list_executions_filtered(store, output_format, options.running).into(),
205 ));
206 }
207 if let Some(ref identifier) = options.upload_log {
208 return Some(
209 match crate::log_uploader::upload_execution_log_with_options(
210 store,
211 identifier,
212 options.no_sanitize,
213 std::env::var("START_VERBOSE").is_ok_and(|value| value == "1" || value == "true"),
214 ) {
215 Ok(code) => code,
216 Err(error) => {
217 eprintln!("Error: {}", error);
218 1
219 }
220 },
221 );
222 }
223 if let Some(ref identifier) = options.stop {
224 return Some(report_result(
225 control_execution(store, identifier, ControlAction::Stop).into(),
226 ));
227 }
228 if let Some(ref identifier) = options.terminate {
229 return Some(report_result(
230 control_execution(store, identifier, ControlAction::Terminate).into(),
231 ));
232 }
233 if let Some(ref identifier) = options.attach {
234 return Some(report_result(
235 attach_execution(store, identifier, options.read_only).into(),
236 ));
237 }
238 if let Some(ref identifier) = options.resume {
239 let new_command = if command.trim().is_empty() {
240 None
241 } else {
242 Some(command)
243 };
244 return Some(report_result(
245 resume_execution_with_options(store, identifier, new_command, output_format, options)
246 .into(),
247 ));
248 }
249 if options.resume_all {
250 return Some(report_result(
251 resume_all_executions(store, output_format).into(),
252 ));
253 }
254 if options.cleanup {
255 return Some(handle_cleanup_target(
256 store,
257 options.cleanup_dry_run,
258 options.cleanup_target.as_deref(),
259 ));
260 }
261 let naming = options
262 .rename
263 .as_deref()
264 .map(|id| (id, true))
265 .or_else(|| options.alias.as_deref().map(|id| (id, false)));
266 if let Some((identifier, rename)) = naming {
267 let name = options.new_name.as_deref().unwrap_or("");
268 let result = crate::execution_identity::name_execution(store, identifier, name, rename);
269 let success = result.is_ok();
270 let (output, error) = match result {
271 Ok(output) => (Some(output), None),
272 Err(error) => (None, Some(error)),
273 };
274 return Some(report_result(QueryOutcome {
275 success,
276 output,
277 error,
278 exit_code: None,
279 }));
280 }
281
282 None
283}