1pub use job_queue::config::{self, QueueConfig, QueueConfigBuilder};
17pub use job_queue::db;
18pub use job_queue::error::{self, QueueError};
19pub use job_queue::events::{self, *};
20pub use job_queue::executor;
21pub use job_queue::queue::{self, QueueManager};
22pub use job_queue::types::{self, JobResult, QueueJob, QueueJobStatus, QueuePriority};
23pub use job_queue::{JobContext, JobHandler, QueueEventEmitter};
24
25use serde::{Deserialize, Serialize};
26use std::collections::{HashMap, VecDeque};
27use std::sync::{Arc, Mutex};
28use std::time::{Duration, Instant};
29use tauri::{AppHandle, Emitter};
30
31pub use stack_ids::TraceCtx;
33
34pub fn trace_ctx_from_event_trace_id(trace_id: &Option<String>) -> Option<TraceCtx> {
41 trace_id.as_deref().map(TraceCtx::from_legacy_trace_id)
42}
43
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
46pub enum DropPolicy {
47 DropOldest,
49 #[default]
51 DropNewest,
52 Block,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct EmitterConfig {
63 pub buffer_size: usize,
66 pub drop_policy: DropPolicy,
68 pub coalesce_interval_ms: u64,
72 #[deprecated(since = "0.3.0", note = "Use include_trace_ctx instead")]
83 pub include_trace_id: bool,
84 pub include_trace_ctx: bool,
98}
99
100#[allow(deprecated)]
101impl Default for EmitterConfig {
102 fn default() -> Self {
103 Self {
104 buffer_size: 256,
105 drop_policy: DropPolicy::default(),
106 coalesce_interval_ms: 50,
107 include_trace_id: false,
108 include_trace_ctx: true,
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn trace_ctx_bridge_preserves_legacy_trace_id() {
119 let trace = Some("ui-trace-123".to_string());
120 let ctx = trace_ctx_from_event_trace_id(&trace).expect("trace ctx expected");
121 assert_eq!(ctx.trace_id, "ui-trace-123");
122 }
123
124 #[test]
125 fn trace_ctx_bridge_handles_missing_trace_id() {
126 assert!(trace_ctx_from_event_trace_id(&None).is_none());
127 }
128}
129
130impl EmitterConfig {
131 #[allow(deprecated)]
135 fn should_keep_trace(&self) -> bool {
136 self.include_trace_ctx || self.include_trace_id
137 }
138}
139
140pub struct CoalescingEmitter {
145 inner: Arc<dyn QueueEventEmitter>,
146 config: EmitterConfig,
147 progress_state: Mutex<ProgressState>,
150}
151
152#[derive(Default)]
153struct ProgressState {
154 last_progress: HashMap<String, Instant>,
155 pending_progress: HashMap<String, JobProgressEvent>,
156 pending_order: VecDeque<String>,
157}
158
159impl ProgressState {
160 fn take_pending(&mut self, job_id: &str) -> Option<JobProgressEvent> {
161 let event = self.pending_progress.remove(job_id);
162 if event.is_some() {
163 self.pending_order
164 .retain(|queued_job_id| queued_job_id != job_id);
165 }
166 event
167 }
168
169 fn clear_job(&mut self, job_id: &str) -> Option<JobProgressEvent> {
170 let pending = self.take_pending(job_id);
171 self.last_progress.remove(job_id);
172 pending
173 }
174
175 fn enqueue_progress(
176 &mut self,
177 event: JobProgressEvent,
178 config: &EmitterConfig,
179 ) -> Option<JobProgressEvent> {
180 if self.pending_progress.contains_key(&event.job_id) {
181 self.pending_progress.insert(event.job_id.clone(), event);
182 return None;
183 }
184
185 if self.pending_progress.len() >= config.buffer_size.max(1) {
186 match config.drop_policy {
187 DropPolicy::DropNewest => return None,
188 DropPolicy::DropOldest => {
189 if let Some(oldest_job_id) = self.pending_order.pop_front() {
190 self.pending_progress.remove(&oldest_job_id);
191 }
192 }
193 DropPolicy::Block => {
194 let flushed = self
195 .pending_order
196 .pop_front()
197 .and_then(|oldest_job_id| self.pending_progress.remove(&oldest_job_id));
198 self.pending_order.push_back(event.job_id.clone());
199 self.pending_progress.insert(event.job_id.clone(), event);
200 return flushed;
201 }
202 }
203 }
204
205 self.pending_order.push_back(event.job_id.clone());
206 self.pending_progress.insert(event.job_id.clone(), event);
207 None
208 }
209}
210
211impl CoalescingEmitter {
212 pub fn new(inner: Arc<dyn QueueEventEmitter>, config: EmitterConfig) -> Self {
213 Self {
214 inner,
215 config,
216 progress_state: Mutex::new(ProgressState::default()),
217 }
218 }
219
220 pub fn arc(
221 inner: Arc<dyn QueueEventEmitter>,
222 config: EmitterConfig,
223 ) -> Arc<dyn QueueEventEmitter> {
224 Arc::new(Self::new(inner, config))
225 }
226
227 fn prepare_progress_emit(&self, event: JobProgressEvent) -> Vec<JobProgressEvent> {
228 let mut state = self
229 .progress_state
230 .lock()
231 .unwrap_or_else(|e| e.into_inner());
232 let interval = Duration::from_millis(self.config.coalesce_interval_ms);
233 let now = Instant::now();
234
235 if let Some(last) = state.last_progress.get(&event.job_id) {
236 if now.duration_since(*last) < interval {
237 return state
238 .enqueue_progress(event, &self.config)
239 .into_iter()
240 .collect();
241 }
242 }
243
244 state.last_progress.insert(event.job_id.clone(), now);
245 let mut to_emit = Vec::with_capacity(2);
246 if let Some(pending) = state.take_pending(&event.job_id) {
247 to_emit.push(pending);
248 }
249 to_emit.push(event);
250 to_emit
251 }
252
253 #[allow(deprecated)]
255 fn normalize_progress_event(&self, mut event: JobProgressEvent) -> JobProgressEvent {
256 if !self.config.should_keep_trace() {
257 event.trace_id = None;
258 event.trace_ctx = None;
259 }
260 event
261 }
262
263 fn take_terminal_pending(&self, job_id: &str) -> Option<JobProgressEvent> {
264 let mut state = self
265 .progress_state
266 .lock()
267 .unwrap_or_else(|e| e.into_inner());
268 state.clear_job(job_id)
269 }
270}
271
272#[allow(deprecated)]
273impl QueueEventEmitter for CoalescingEmitter {
274 fn emit_job_started(&self, event: JobStartedEvent) {
275 let mut event = event;
276 if !self.config.should_keep_trace() {
277 event.trace_id = None;
278 event.trace_ctx = None;
279 }
280 self.inner.emit_job_started(event);
281 }
282
283 fn emit_job_completed(&self, event: JobCompletedEvent) {
284 let mut event = event;
285 if !self.config.should_keep_trace() {
286 event.trace_id = None;
287 event.trace_ctx = None;
288 }
289 if let Some(pending) = self.take_terminal_pending(&event.job_id) {
290 self.inner.emit_job_progress(pending);
291 }
292 self.inner.emit_job_completed(event);
293 }
294
295 fn emit_job_failed(&self, event: JobFailedEvent) {
296 let mut event = event;
297 if !self.config.should_keep_trace() {
298 event.trace_id = None;
299 event.trace_ctx = None;
300 }
301 if let Some(pending) = self.take_terminal_pending(&event.job_id) {
302 self.inner.emit_job_progress(pending);
303 }
304 self.inner.emit_job_failed(event);
305 }
306
307 fn emit_job_progress(&self, event: JobProgressEvent) {
308 let event = self.normalize_progress_event(event);
309 for event in self.prepare_progress_emit(event) {
310 self.inner.emit_job_progress(event);
311 }
312 }
313
314 fn emit_job_cancelled(&self, event: JobCancelledEvent) {
315 let mut event = event;
316 if !self.config.should_keep_trace() {
317 event.trace_id = None;
318 event.trace_ctx = None;
319 }
320 if let Some(pending) = self.take_terminal_pending(&event.job_id) {
321 self.inner.emit_job_progress(pending);
322 }
323 self.inner.emit_job_cancelled(event);
324 }
325}
326
327pub struct TauriEventEmitter {
331 app_handle: AppHandle,
332}
333
334impl TauriEventEmitter {
335 pub fn new(app_handle: AppHandle) -> Self {
337 Self { app_handle }
338 }
339
340 pub fn arc(app_handle: AppHandle) -> Arc<dyn QueueEventEmitter> {
342 Arc::new(Self::new(app_handle))
343 }
344}
345
346impl QueueEventEmitter for TauriEventEmitter {
347 fn emit_job_started(&self, event: JobStartedEvent) {
348 if let Err(e) = self.app_handle.emit("queue:job_started", event) {
349 tracing::debug!(error = %e, "Failed to emit queue:job_started event");
350 }
351 }
352
353 fn emit_job_completed(&self, event: JobCompletedEvent) {
354 if let Err(e) = self.app_handle.emit("queue:job_completed", event) {
355 tracing::debug!(error = %e, "Failed to emit queue:job_completed event");
356 }
357 }
358
359 fn emit_job_failed(&self, event: JobFailedEvent) {
360 if let Err(e) = self.app_handle.emit("queue:job_failed", event) {
361 tracing::debug!(error = %e, "Failed to emit queue:job_failed event");
362 }
363 }
364
365 fn emit_job_progress(&self, event: JobProgressEvent) {
366 if let Err(e) = self.app_handle.emit("queue:job_progress", event) {
367 tracing::debug!(error = %e, "Failed to emit queue:job_progress event");
368 }
369 }
370
371 fn emit_job_cancelled(&self, event: JobCancelledEvent) {
372 if let Err(e) = self.app_handle.emit("queue:job_cancelled", event) {
373 tracing::debug!(error = %e, "Failed to emit queue:job_cancelled event");
374 }
375 }
376}