1pub use crate::runtime::ProgressState;
2use awa_model::{AwaError, CallbackConfig, JobRow};
3use sqlx::PgPool;
4use std::any::Any;
5use std::collections::HashMap;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::Arc;
8use std::time::Duration;
9
10pub struct CallbackToken {
15 pub id: uuid::Uuid,
16}
17
18pub struct JobContext {
23 pub job: JobRow,
25 cancelled: Arc<AtomicBool>,
27 state: Arc<HashMap<std::any::TypeId, Box<dyn Any + Send + Sync>>>,
29 pool: PgPool,
31 progress: Arc<std::sync::Mutex<ProgressState>>,
33}
34
35impl JobContext {
36 pub fn new(
37 job: JobRow,
38 cancelled: Arc<AtomicBool>,
39 state: Arc<HashMap<std::any::TypeId, Box<dyn Any + Send + Sync>>>,
40 pool: PgPool,
41 progress: Arc<std::sync::Mutex<ProgressState>>,
42 ) -> Self {
43 Self {
44 job,
45 cancelled,
46 state,
47 pool,
48 progress,
49 }
50 }
51
52 pub fn is_cancelled(&self) -> bool {
54 self.cancelled.load(Ordering::SeqCst)
55 }
56
57 pub fn cancellation_flag(&self) -> Arc<AtomicBool> {
59 self.cancelled.clone()
60 }
61
62 pub fn cancel(&self) {
64 self.cancelled.store(true, Ordering::SeqCst);
65 }
66
67 pub fn extract<T: Any + Send + Sync + Clone>(&self) -> Option<T> {
71 self.state
72 .get(&std::any::TypeId::of::<T>())
73 .and_then(|v| v.downcast_ref::<T>())
74 .cloned()
75 }
76
77 pub fn pool(&self) -> &PgPool {
79 &self.pool
80 }
81
82 pub async fn register_callback(&self, timeout: Duration) -> Result<CallbackToken, AwaError> {
92 let callback_id = awa_model::admin::register_callback(
93 &self.pool,
94 self.job.id,
95 self.job.run_lease,
96 timeout,
97 )
98 .await?;
99 Ok(CallbackToken { id: callback_id })
100 }
101
102 pub async fn register_callback_with_config(
106 &self,
107 timeout: Duration,
108 config: &CallbackConfig,
109 ) -> Result<CallbackToken, AwaError> {
110 let callback_id = awa_model::admin::register_callback_with_config(
111 &self.pool,
112 self.job.id,
113 self.job.run_lease,
114 timeout,
115 config,
116 )
117 .await?;
118 Ok(CallbackToken { id: callback_id })
119 }
120
121 pub fn set_progress(&self, percent: u8, message: Option<&str>) {
125 let mut guard = self.progress.lock().expect("progress lock poisoned");
126 guard.set_progress(percent, message);
127 }
128
129 pub fn update_metadata(&self, updates: serde_json::Value) -> Result<(), AwaError> {
134 let obj = updates
135 .as_object()
136 .ok_or_else(|| AwaError::Validation("update_metadata requires a JSON object".into()))?;
137
138 let mut guard = self.progress.lock().expect("progress lock poisoned");
139 if !guard.merge_metadata(obj) {
140 return Err(AwaError::Validation(
141 "progress.metadata is not a JSON object; cannot merge".into(),
142 ));
143 }
144 Ok(())
145 }
146
147 pub async fn flush_progress(&self) -> Result<(), AwaError> {
152 let (snapshot, target_generation) = {
153 let guard = self.progress.lock().expect("progress lock poisoned");
154 match guard.pending_snapshot() {
155 Some(pair) => pair,
156 None => return Ok(()),
157 }
158 };
159
160 let result = sqlx::query(
161 r#"
162 UPDATE awa.jobs_hot
163 SET progress = $2
164 WHERE id = $1 AND state = 'running' AND run_lease = $3
165 "#,
166 )
167 .bind(self.job.id)
168 .bind(&snapshot)
169 .bind(self.job.run_lease)
170 .execute(&self.pool)
171 .await?;
172
173 if result.rows_affected() == 0 {
174 return Ok(());
176 }
177
178 let mut guard = self.progress.lock().expect("progress lock poisoned");
179 guard.ack(target_generation);
180
181 Ok(())
182 }
183
184 pub fn progress_buffer(&self) -> Arc<std::sync::Mutex<ProgressState>> {
186 self.progress.clone()
187 }
188}