| 281 | } |
| 282 | |
| 283 | func (tm *TaskManager) CancelTask(taskID string) error { |
| 284 | // First check queues and remove if found (fast operation, keep lock) |
| 285 | tm.mu.Lock() |
| 286 | for key, queue := range tm.queue { |
| 287 | for i, task := range queue { |
| 288 | if task.ID == taskID { |
| 289 | // Remove from queue |
| 290 | tm.queue[key] = append(queue[:i], queue[i+1:]...) |
| 291 | if len(tm.queue[key]) == 0 { |
| 292 | delete(tm.queue, key) |
| 293 | } |
| 294 | task.Status = StatusCancelled |
| 295 | task.FinishedAt = time.Now() |
| 296 | tm.mu.Unlock() |
| 297 | |
| 298 | if tm.updateNotify != nil { |
| 299 | go tm.updateNotify() |
| 300 | } |
| 301 | return nil |
| 302 | } |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | // Check active tasks |
| 307 | var targetUPID string |
| 308 | var targetNode string |
| 309 | var found bool |
| 310 | |
| 311 | for _, task := range tm.activeTasks { |
| 312 | if task.ID == taskID { |
| 313 | if task.UPID != "" { |
| 314 | targetUPID = task.UPID |
| 315 | targetNode = task.TargetNode |
| 316 | found = true |
| 317 | } else { |
| 318 | tm.mu.Unlock() |
| 319 | return fmt.Errorf("task is starting, cannot cancel yet") |
| 320 | } |
| 321 | break |
| 322 | } |
| 323 | } |
| 324 | tm.mu.Unlock() |
| 325 | |
| 326 | if found { |
| 327 | client, err := tm.clientResolver(targetNode) |
| 328 | if err != nil { |
| 329 | return err |
| 330 | } |
| 331 | // Try to stop in Proxmox (network call outside lock) |
| 332 | if err := client.StopTask(targetNode, targetUPID); err != nil { |
| 333 | return err |
| 334 | } |
| 335 | // The polling loop will see it stopped |
| 336 | return nil |
| 337 | } |
| 338 | |
| 339 | return fmt.Errorf("task not found") |
| 340 | } |