| 15 | #[async_trait] |
| 16 | impl Command for CancelCommand { |
| 17 | async fn execute(&self, ctx: &AppContext) -> Result<(), Box<dyn Error>> { |
| 18 | if self.task_ids.is_empty() { |
| 19 | return Err("No task IDs provided".into()); |
| 20 | } |
| 21 | |
| 22 | let storage = ctx.task_storage(); |
| 23 | let docker_client: Arc<dyn DockerClient> = match &self.docker_client_override { |
| 24 | Some(client) => Arc::clone(client), |
| 25 | None => Arc::new( |
| 26 | DefaultDockerClient::new(&ctx.tsk_config().container_engine) |
| 27 | .map_err(|e| -> Box<dyn Error> { e.into() })?, |
| 28 | ), |
| 29 | }; |
| 30 | |
| 31 | let mut successful_cancels = 0; |
| 32 | let mut failed_cancels = 0; |
| 33 | |
| 34 | for task_id in &self.task_ids { |
| 35 | let task = match storage.get_task(task_id).await { |
| 36 | Ok(Some(task)) => task, |
| 37 | Ok(None) => { |
| 38 | eprintln!("Task {task_id} not found"); |
| 39 | failed_cancels += 1; |
| 40 | continue; |
| 41 | } |
| 42 | Err(e) => { |
| 43 | eprintln!("Failed to look up {task_id}: {e}"); |
| 44 | failed_cancels += 1; |
| 45 | continue; |
| 46 | } |
| 47 | }; |
| 48 | |
| 49 | match task.status { |
| 50 | TaskStatus::Queued | TaskStatus::Running => { |
| 51 | let was_running = task.status == TaskStatus::Running; |
| 52 | let is_interactive = task.is_interactive; |
| 53 | |
| 54 | match storage.mark_cancelled(task_id).await { |
| 55 | Ok(_) => { |
| 56 | println!("Cancelled {task_id}"); |
| 57 | successful_cancels += 1; |
| 58 | |
| 59 | if was_running { |
| 60 | let container_name = if is_interactive { |
| 61 | format!("tsk-interactive-{task_id}") |
| 62 | } else { |
| 63 | format!("tsk-{task_id}") |
| 64 | }; |
| 65 | if let Err(e) = docker_client.kill_container(&container_name).await |
| 66 | { |
| 67 | eprintln!( |
| 68 | "Note: Could not kill container {container_name}: {e}" |
| 69 | ); |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | Err(e) => { |
| 74 | eprintln!("Failed to cancel {task_id}: {e}"); |