Skip to main content

text_document/
operation.rs

1//! Typed long operation handle.
2
3use std::thread;
4use std::time::Duration;
5
6use crate::Result;
7
8use frontend::AppContext;
9
10/// Function that polls the long-operation manager for a result.
11type ResultFn<T> = Box<dyn Fn(&AppContext, &str) -> Option<Result<T>> + Send>;
12
13/// Shared state for a single long operation.
14pub(crate) struct OperationState {
15    ctx: AppContext,
16}
17
18impl OperationState {
19    pub fn new(ctx: &AppContext) -> Self {
20        Self { ctx: ctx.clone() }
21    }
22}
23
24/// A handle to a running long operation (Markdown/HTML import, DOCX export).
25///
26/// Provides typed access to progress, cancellation, and the result.
27/// Progress events are also emitted via [`DocumentEvent::LongOperationProgress`](crate::DocumentEvent::LongOperationProgress)
28/// and [`DocumentEvent::LongOperationFinished`](crate::DocumentEvent::LongOperationFinished)
29/// for the callback/polling path.
30///
31/// Retrieve the result via [`wait()`](Self::wait) (blocking, consumes the handle)
32/// or [`try_result()`](Self::try_result) (non-blocking, can be called repeatedly).
33pub struct Operation<T> {
34    id: String,
35    state: OperationState,
36    result_fn: ResultFn<T>,
37}
38
39impl<T> Operation<T> {
40    pub(crate) fn new(id: String, ctx: &AppContext, result_fn: ResultFn<T>) -> Self {
41        Self {
42            id,
43            state: OperationState::new(ctx),
44            result_fn,
45        }
46    }
47
48    /// The operation ID (for matching with [`DocumentEvent`](crate::DocumentEvent) variants).
49    pub fn id(&self) -> &str {
50        &self.id
51    }
52
53    /// Get the current progress, if available.
54    /// Returns `(percent, message)` where percent is 0.0–100.0.
55    pub fn progress(&self) -> Option<(f64, String)> {
56        let mgr = self.state.ctx.long_operation_manager.lock();
57        mgr.get_operation_progress(&self.id)
58            .map(|p| (p.percentage as f64, p.message.unwrap_or_default()))
59    }
60
61    /// Returns `true` if the operation has finished (success or failure).
62    pub fn is_done(&self) -> bool {
63        (self.result_fn)(&self.state.ctx, &self.id).is_some()
64    }
65
66    /// Cancel the operation. No-op if already finished.
67    pub fn cancel(&self) {
68        self.state
69            .ctx
70            .long_operation_manager
71            .lock()
72            .cancel_operation(&self.id);
73    }
74
75    /// Block the calling thread until the operation completes and return
76    /// the typed result. Consumes the handle.
77    pub fn wait(self) -> Result<T> {
78        loop {
79            if let Some(result) = (self.result_fn)(&self.state.ctx, &self.id) {
80                return result;
81            }
82            thread::sleep(Duration::from_millis(50));
83        }
84    }
85
86    /// Block until the operation completes or the timeout expires.
87    /// Returns `None` if the timeout elapsed before the operation finished.
88    pub fn wait_timeout(self, timeout: Duration) -> Option<Result<T>> {
89        let deadline = std::time::Instant::now() + timeout;
90        loop {
91            if let Some(result) = (self.result_fn)(&self.state.ctx, &self.id) {
92                return Some(result);
93            }
94            if std::time::Instant::now() >= deadline {
95                return None;
96            }
97            let remaining = deadline.saturating_duration_since(std::time::Instant::now());
98            thread::sleep(remaining.min(Duration::from_millis(50)));
99        }
100    }
101
102    /// Non-blocking: returns the result if the operation has completed,
103    /// `None` if still running. Can be called repeatedly.
104    pub fn try_result(&mut self) -> Option<Result<T>> {
105        (self.result_fn)(&self.state.ctx, &self.id)
106    }
107}
108
109// ── Result types ────────────────────────────────────────────────
110
111/// Result of a Markdown import (`set_markdown`).
112#[derive(Debug, Clone)]
113pub struct MarkdownImportResult {
114    pub block_count: usize,
115}
116
117/// Result of an HTML import (`set_html`).
118#[derive(Debug, Clone)]
119pub struct HtmlImportResult {
120    pub block_count: usize,
121}
122
123/// Result of a DOCX export (`to_docx`).
124#[derive(Debug, Clone)]
125pub struct DocxExportResult {
126    pub file_path: String,
127    pub paragraph_count: usize,
128}