use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::Result;
use common::long_operation::{OperationCompletion, OperationStatus};
use frontend::AppContext;
const COMPLETION_BACKSTOP: Duration = Duration::from_secs(1);
type ResultFn<T> = Box<dyn Fn(&AppContext, &str) -> Option<Result<T>> + Send>;
pub(crate) struct OperationState {
ctx: AppContext,
}
impl OperationState {
pub fn new(ctx: &AppContext) -> Self {
Self { ctx: ctx.clone() }
}
}
pub struct Operation<T> {
id: String,
state: OperationState,
result_fn: ResultFn<T>,
}
impl<T> Operation<T> {
pub(crate) fn new(id: String, ctx: &AppContext, result_fn: ResultFn<T>) -> Self {
Self {
id,
state: OperationState::new(ctx),
result_fn,
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn progress(&self) -> Option<(f64, String)> {
let mgr = self.state.ctx.long_operation_manager.lock();
mgr.get_operation_progress(&self.id)
.map(|p| (p.percentage as f64, p.message.unwrap_or_default()))
}
pub fn is_done(&self) -> bool {
self.completion().is_finished(&self.id)
}
fn completion(&self) -> Arc<OperationCompletion> {
self.state
.ctx
.long_operation_manager
.lock()
.completion_signal()
}
fn terminal_error(&self) -> crate::DocumentError {
let status = self
.state
.ctx
.long_operation_manager
.lock()
.get_operation_status(&self.id);
match status {
Some(OperationStatus::Failed(err)) => anyhow::anyhow!("{err}").into(),
Some(OperationStatus::Cancelled) => anyhow::anyhow!("operation was cancelled").into(),
_ => anyhow::anyhow!("operation finished without producing a result").into(),
}
}
pub fn cancel(&self) {
self.state
.ctx
.long_operation_manager
.lock()
.cancel_operation(&self.id);
}
pub fn wait(self) -> Result<T> {
let completion = self.completion();
loop {
let finished = completion.is_finished(&self.id);
if let Some(result) = (self.result_fn)(&self.state.ctx, &self.id) {
return result;
}
if finished {
return Err(self.terminal_error());
}
completion.wait_for(&self.id, Some(COMPLETION_BACKSTOP));
}
}
pub fn wait_timeout(self, timeout: Duration) -> Option<Result<T>> {
let completion = self.completion();
let deadline = Instant::now() + timeout;
loop {
let finished = completion.is_finished(&self.id);
if let Some(result) = (self.result_fn)(&self.state.ctx, &self.id) {
return Some(result);
}
if finished {
return Some(Err(self.terminal_error()));
}
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return None;
}
completion.wait_for(&self.id, Some(remaining.min(COMPLETION_BACKSTOP)));
}
}
pub fn try_result(&mut self) -> Option<Result<T>> {
(self.result_fn)(&self.state.ctx, &self.id)
}
}
#[derive(Debug, Clone)]
pub struct MarkdownImportResult {
pub block_count: usize,
}
#[derive(Debug, Clone)]
pub struct HtmlImportResult {
pub block_count: usize,
}
#[derive(Debug, Clone)]
pub struct DjotImportResult {
pub block_count: usize,
}
#[derive(Debug, Clone)]
pub struct DocxExportResult {
pub file_path: String,
pub paragraph_count: usize,
}
#[derive(Debug, Clone)]
pub struct EpubExportResult {
pub file_path: String,
pub chapter_count: usize,
}
#[derive(Debug, Clone)]
pub struct PdfExportResult {
pub file_path: String,
pub page_count: usize,
}