Skip to main content

text_document_io/
document_io_controller.rs

1// Generated by Qleany v1.5.1 from feature_controller.tera
2
3use crate::ExportDjotDto;
4use crate::ExportDocxDto;
5use crate::ExportDocxResultDto;
6use crate::ExportEpubDto;
7use crate::ExportEpubResultDto;
8use crate::ExportHtmlDto;
9use crate::ExportLatexDto;
10use crate::ExportLatexResultDto;
11use crate::ExportMarkdownDto;
12use crate::ExportPdfDto;
13use crate::ExportPdfResultDto;
14use crate::ExportPlainTextDto;
15use crate::ImportDjotDto;
16use crate::ImportDjotResultDto;
17use crate::ImportHtmlDto;
18use crate::ImportHtmlResultDto;
19use crate::ImportMarkdownDto;
20use crate::ImportMarkdownResultDto;
21use crate::ImportPlainTextDto;
22use crate::units_of_work::export_djot_uow::ExportDjotUnitOfWorkFactory;
23use crate::units_of_work::export_docx_uow::ExportDocxUnitOfWorkFactory;
24use crate::units_of_work::export_epub_uow::ExportEpubUnitOfWorkFactory;
25use crate::units_of_work::export_html_uow::ExportHtmlUnitOfWorkFactory;
26use crate::units_of_work::export_latex_uow::ExportLatexUnitOfWorkFactory;
27use crate::units_of_work::export_markdown_uow::ExportMarkdownUnitOfWorkFactory;
28#[cfg(feature = "pdf")]
29use crate::units_of_work::export_pdf_uow::ExportPdfUnitOfWorkFactory;
30use crate::units_of_work::export_plain_text_uow::ExportPlainTextUnitOfWorkFactory;
31use crate::units_of_work::import_djot_uow::ImportDjotUnitOfWorkFactory;
32use crate::units_of_work::import_html_uow::ImportHtmlUnitOfWorkFactory;
33use crate::units_of_work::import_markdown_uow::ImportMarkdownUnitOfWorkFactory;
34use crate::units_of_work::import_plain_text_uow::ImportPlainTextUnitOfWorkFactory;
35use crate::use_cases::export_djot_uc::ExportDjotUseCase;
36use crate::use_cases::export_docx_uc::ExportDocxUseCase;
37use crate::use_cases::export_epub_uc::ExportEpubUseCase;
38use crate::use_cases::export_html_uc::ExportHtmlUseCase;
39use crate::use_cases::export_latex_uc::ExportLatexUseCase;
40use crate::use_cases::export_markdown_uc::ExportMarkdownUseCase;
41#[cfg(feature = "pdf")]
42use crate::use_cases::export_pdf_uc::ExportPdfUseCase;
43use crate::use_cases::export_plain_text_uc::ExportPlainTextUseCase;
44use crate::use_cases::import_djot_uc::ImportDjotUseCase;
45use crate::use_cases::import_html_uc::ImportHtmlUseCase;
46use crate::use_cases::import_markdown_uc::ImportMarkdownUseCase;
47use crate::use_cases::import_plain_text_uc::ImportPlainTextUseCase;
48use anyhow::Result;
49use common::event::{Event, Origin};
50use common::parser_tools::DjotExportOptions;
51
52use common::event::DocumentIoEvent::ExportDjot;
53use common::event::DocumentIoEvent::ExportHtml;
54use common::event::DocumentIoEvent::ExportLatex;
55use common::event::DocumentIoEvent::ExportMarkdown;
56use common::event::DocumentIoEvent::ExportPlainText;
57use common::event::DocumentIoEvent::ImportDjot;
58use common::event::DocumentIoEvent::ImportPlainText;
59
60use common::long_operation::{LongOperation, LongOperationManager, OperationProgress};
61use common::{database::db_context::DbContext, event::EventHub};
62use std::sync::Arc;
63
64pub fn import_plain_text(
65    db_context: &DbContext,
66    event_hub: &Arc<EventHub>,
67    dto: &ImportPlainTextDto,
68) -> Result<()> {
69    let uow_context = ImportPlainTextUnitOfWorkFactory::new(db_context, event_hub);
70    let mut uc = ImportPlainTextUseCase::new(Box::new(uow_context));
71    uc.execute(dto)?;
72    // Notify that the handling manifest has been loaded
73    event_hub.send_event(Event {
74        origin: Origin::DocumentIo(ImportPlainText),
75        ids: vec![],
76        data: None,
77    });
78    Ok(())
79}
80
81pub fn export_plain_text(
82    db_context: &DbContext,
83    event_hub: &Arc<EventHub>,
84) -> Result<ExportPlainTextDto> {
85    let uow_context = ExportPlainTextUnitOfWorkFactory::new(db_context);
86    let mut uc = ExportPlainTextUseCase::new(Box::new(uow_context));
87    let return_dto = uc.execute()?;
88    // Notify that the handling manifest has been loaded
89    event_hub.send_event(Event {
90        origin: Origin::DocumentIo(ExportPlainText),
91        ids: vec![],
92        data: None,
93    });
94    Ok(return_dto)
95}
96
97pub fn import_markdown(
98    db_context: &DbContext,
99    event_hub: &Arc<EventHub>,
100    long_operation_manager: &mut LongOperationManager,
101    dto: &ImportMarkdownDto,
102) -> Result<String> {
103    let uow_context = ImportMarkdownUnitOfWorkFactory::new(db_context, event_hub);
104    let uc = ImportMarkdownUseCase::new(Box::new(uow_context), dto);
105    let operation_id = long_operation_manager.start_operation(uc);
106    Ok(operation_id)
107}
108
109pub fn get_import_markdown_progress(
110    long_operation_manager: &LongOperationManager,
111    operation_id: &str,
112) -> Option<OperationProgress> {
113    long_operation_manager.get_operation_progress(operation_id)
114}
115
116pub fn get_import_markdown_result(
117    long_operation_manager: &LongOperationManager,
118    operation_id: &str,
119) -> Result<Option<ImportMarkdownResultDto>> {
120    // Get the operation result as a JSON string
121    let result_json = long_operation_manager.get_operation_result(operation_id);
122
123    // If there's no result, return None
124    if result_json.is_none() {
125        return Ok(None);
126    }
127    // Parse the JSON string into a ImportMarkdownResultDto
128    let result_dto: ImportMarkdownResultDto = serde_json::from_str(&result_json.unwrap())?;
129
130    Ok(Some(result_dto))
131}
132
133pub fn export_markdown(
134    db_context: &DbContext,
135    event_hub: &Arc<EventHub>,
136) -> Result<ExportMarkdownDto> {
137    let uow_context = ExportMarkdownUnitOfWorkFactory::new(db_context);
138    let mut uc = ExportMarkdownUseCase::new(Box::new(uow_context));
139    let return_dto = uc.execute()?;
140    // Notify that the handling manifest has been loaded
141    event_hub.send_event(Event {
142        origin: Origin::DocumentIo(ExportMarkdown),
143        ids: vec![],
144        data: None,
145    });
146    Ok(return_dto)
147}
148
149pub fn import_djot(
150    db_context: &DbContext,
151    event_hub: &Arc<EventHub>,
152    long_operation_manager: &mut LongOperationManager,
153    dto: &ImportDjotDto,
154) -> Result<String> {
155    let uow_context = ImportDjotUnitOfWorkFactory::new(db_context, event_hub);
156    let uc = ImportDjotUseCase::new(Box::new(uow_context), dto);
157    let operation_id = long_operation_manager.start_operation(uc);
158    Ok(operation_id)
159}
160
161/// Import Djot **synchronously**, on the calling thread.
162///
163/// [`import_djot`] hands the use case to the `LongOperationManager`, which spawns a
164/// thread for it. That is right for a UI import (it must not block the frame loop),
165/// and wrong for a *batch* caller — a backend that has to parse and rewrite hundreds
166/// of documents in one operation would spawn hundreds of threads to do work it wants
167/// to do inline anyway.
168///
169/// The use case itself is thread-agnostic: it only needs a progress sink and a cancel
170/// flag. So this hands it a no-op sink and a never-set flag and calls it directly. No
171/// thread, no operation id, no polling — the result is simply returned.
172///
173/// (Cancellation is the caller's business here: a synchronous caller can just stop
174/// calling. The flag exists to satisfy the `LongOperation` signature, not to be used.)
175pub fn import_djot_sync(
176    db_context: &DbContext,
177    event_hub: &Arc<EventHub>,
178    dto: &ImportDjotDto,
179) -> Result<ImportDjotResultDto> {
180    let uow_context = ImportDjotUnitOfWorkFactory::new(db_context, event_hub);
181    let uc = ImportDjotUseCase::new(Box::new(uow_context), dto);
182    let result = uc.execute(
183        Box::new(|_progress| {}),
184        Arc::new(std::sync::atomic::AtomicBool::new(false)),
185    )?;
186    event_hub.send_event(Event {
187        origin: Origin::DocumentIo(ImportDjot),
188        ids: vec![],
189        data: None,
190    });
191    Ok(result)
192}
193
194pub fn get_import_djot_progress(
195    long_operation_manager: &LongOperationManager,
196    operation_id: &str,
197) -> Option<OperationProgress> {
198    long_operation_manager.get_operation_progress(operation_id)
199}
200
201pub fn get_import_djot_result(
202    long_operation_manager: &LongOperationManager,
203    operation_id: &str,
204) -> Result<Option<ImportDjotResultDto>> {
205    let result_json = long_operation_manager.get_operation_result(operation_id);
206    if result_json.is_none() {
207        return Ok(None);
208    }
209    let result_dto: ImportDjotResultDto = serde_json::from_str(&result_json.unwrap())?;
210    Ok(Some(result_dto))
211}
212
213pub fn export_djot(
214    db_context: &DbContext,
215    event_hub: &Arc<EventHub>,
216    options: &DjotExportOptions,
217) -> Result<ExportDjotDto> {
218    let uow_context = ExportDjotUnitOfWorkFactory::new(db_context);
219    let mut uc = ExportDjotUseCase::new(Box::new(uow_context));
220    let return_dto = uc.execute(options)?;
221    event_hub.send_event(Event {
222        origin: Origin::DocumentIo(ExportDjot),
223        ids: vec![],
224        data: None,
225    });
226    Ok(return_dto)
227}
228
229pub fn import_html(
230    db_context: &DbContext,
231    event_hub: &Arc<EventHub>,
232    long_operation_manager: &mut LongOperationManager,
233    dto: &ImportHtmlDto,
234) -> Result<String> {
235    let uow_context = ImportHtmlUnitOfWorkFactory::new(db_context, event_hub);
236    let uc = ImportHtmlUseCase::new(Box::new(uow_context), dto);
237    let operation_id = long_operation_manager.start_operation(uc);
238    Ok(operation_id)
239}
240
241pub fn get_import_html_progress(
242    long_operation_manager: &LongOperationManager,
243    operation_id: &str,
244) -> Option<OperationProgress> {
245    long_operation_manager.get_operation_progress(operation_id)
246}
247
248pub fn get_import_html_result(
249    long_operation_manager: &LongOperationManager,
250    operation_id: &str,
251) -> Result<Option<ImportHtmlResultDto>> {
252    // Get the operation result as a JSON string
253    let result_json = long_operation_manager.get_operation_result(operation_id);
254
255    // If there's no result, return None
256    if result_json.is_none() {
257        return Ok(None);
258    }
259    // Parse the JSON string into a ImportHtmlResultDto
260    let result_dto: ImportHtmlResultDto = serde_json::from_str(&result_json.unwrap())?;
261
262    Ok(Some(result_dto))
263}
264
265pub fn export_html(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Result<ExportHtmlDto> {
266    let uow_context = ExportHtmlUnitOfWorkFactory::new(db_context);
267    let mut uc = ExportHtmlUseCase::new(Box::new(uow_context));
268    let return_dto = uc.execute()?;
269    // Notify that the handling manifest has been loaded
270    event_hub.send_event(Event {
271        origin: Origin::DocumentIo(ExportHtml),
272        ids: vec![],
273        data: None,
274    });
275    Ok(return_dto)
276}
277
278pub fn export_latex(
279    db_context: &DbContext,
280    event_hub: &Arc<EventHub>,
281    dto: &ExportLatexDto,
282) -> Result<ExportLatexResultDto> {
283    let uow_context = ExportLatexUnitOfWorkFactory::new(db_context);
284    let mut uc = ExportLatexUseCase::new(Box::new(uow_context));
285    let return_dto = uc.execute(dto)?;
286    // Notify that the handling manifest has been loaded
287    event_hub.send_event(Event {
288        origin: Origin::DocumentIo(ExportLatex),
289        ids: vec![],
290        data: None,
291    });
292    Ok(return_dto)
293}
294
295pub fn export_docx(
296    db_context: &DbContext,
297    _event_hub: &Arc<EventHub>,
298    long_operation_manager: &mut LongOperationManager,
299    dto: &ExportDocxDto,
300) -> Result<String> {
301    let uow_context = ExportDocxUnitOfWorkFactory::new(db_context);
302    let uc = ExportDocxUseCase::new(Box::new(uow_context), dto);
303    let operation_id = long_operation_manager.start_operation(uc);
304    Ok(operation_id)
305}
306
307/// Build the in-memory DOCX document for `db_context` without writing a file.
308///
309/// This runs the exact same builder used by [`export_docx`] but returns the
310/// assembled [`docx_rs::Docx`] instead of packing it to disk, so callers can
311/// inspect the produced structure. Intended for tests; the type is re-exported
312/// as [`crate::docx_rs`] so callers can name it.
313#[doc(hidden)]
314pub fn build_docx_document(db_context: &DbContext, dto: &ExportDocxDto) -> Result<docx_rs::Docx> {
315    let uow_context = ExportDocxUnitOfWorkFactory::new(db_context);
316    let uc = ExportDocxUseCase::new(Box::new(uow_context), dto);
317    let (docx, _paragraph_count) = uc.build_document()?;
318    Ok(docx)
319}
320
321pub fn get_export_docx_progress(
322    long_operation_manager: &LongOperationManager,
323    operation_id: &str,
324) -> Option<OperationProgress> {
325    long_operation_manager.get_operation_progress(operation_id)
326}
327
328pub fn get_export_docx_result(
329    long_operation_manager: &LongOperationManager,
330    operation_id: &str,
331) -> Result<Option<ExportDocxResultDto>> {
332    // Get the operation result as a JSON string
333    let result_json = long_operation_manager.get_operation_result(operation_id);
334
335    // If there's no result, return None
336    if result_json.is_none() {
337        return Ok(None);
338    }
339    // Parse the JSON string into a ExportDocxResultDto
340    let result_dto: ExportDocxResultDto = serde_json::from_str(&result_json.unwrap())?;
341
342    Ok(Some(result_dto))
343}
344
345pub fn export_epub(
346    db_context: &DbContext,
347    _event_hub: &Arc<EventHub>,
348    long_operation_manager: &mut LongOperationManager,
349    dto: &ExportEpubDto,
350) -> Result<String> {
351    let uow_context = ExportEpubUnitOfWorkFactory::new(db_context);
352    let uc = ExportEpubUseCase::new(Box::new(uow_context), dto);
353    let operation_id = long_operation_manager.start_operation(uc);
354    Ok(operation_id)
355}
356
357/// Build the packaged EPUB bytes for `db_context` without writing a file.
358///
359/// This runs the exact same builder used by [`export_epub`] but returns the assembled `.epub`
360/// zip archive as bytes instead of writing it to disk, so callers (tests, notably) can inspect
361/// the produced package directly — e.g. with the `zip` crate.
362#[doc(hidden)]
363pub fn build_epub_document(db_context: &DbContext, dto: &ExportEpubDto) -> Result<Vec<u8>> {
364    let uow_context = ExportEpubUnitOfWorkFactory::new(db_context);
365    let uc = ExportEpubUseCase::new(Box::new(uow_context), dto);
366    let (epub_bytes, _chapter_count) = uc.build_document()?;
367    Ok(epub_bytes)
368}
369
370pub fn get_export_epub_progress(
371    long_operation_manager: &LongOperationManager,
372    operation_id: &str,
373) -> Option<OperationProgress> {
374    long_operation_manager.get_operation_progress(operation_id)
375}
376
377pub fn get_export_epub_result(
378    long_operation_manager: &LongOperationManager,
379    operation_id: &str,
380) -> Result<Option<ExportEpubResultDto>> {
381    // Get the operation result as a JSON string
382    let result_json = long_operation_manager.get_operation_result(operation_id);
383
384    // If there's no result, return None
385    if result_json.is_none() {
386        return Ok(None);
387    }
388    // Parse the JSON string into a ExportEpubResultDto
389    let result_dto: ExportEpubResultDto = serde_json::from_str(&result_json.unwrap())?;
390
391    Ok(Some(result_dto))
392}
393
394// ─── PDF export (via embedded Typst) — dual-cfg ────────────────────────────
395//
396// `export_pdf`/`get_export_pdf_progress`/`get_export_pdf_result` exist with the SAME signature
397// regardless of whether the `pdf` cargo feature is enabled on `text-document-io`. This is what
398// lets `frontend`'s command wrappers and `public_api`'s `to_pdf`/`to_pdf_with_options` call these
399// symbols unconditionally (no `#[cfg]` in that consumer code) — the feature only decides which
400// body is compiled: a real `LongOperation`-backed export, or an immediate "unsupported" error.
401// Cargo feature unification means any crate in the build graph enabling `pdf` turns it on
402// globally for that build, so a stable always-present API surface that fails at *runtime* is
403// strictly better here than two divergent source trees.
404
405#[cfg(feature = "pdf")]
406pub fn export_pdf(
407    db_context: &DbContext,
408    _event_hub: &Arc<EventHub>,
409    long_operation_manager: &mut LongOperationManager,
410    dto: &ExportPdfDto,
411) -> Result<String> {
412    let uow_context = ExportPdfUnitOfWorkFactory::new(db_context);
413    let uc = ExportPdfUseCase::new(Box::new(uow_context), dto);
414    let operation_id = long_operation_manager.start_operation(uc);
415    Ok(operation_id)
416}
417
418#[cfg(not(feature = "pdf"))]
419pub fn export_pdf(
420    _db_context: &DbContext,
421    _event_hub: &Arc<EventHub>,
422    _long_operation_manager: &mut LongOperationManager,
423    _dto: &ExportPdfDto,
424) -> Result<String> {
425    Err(anyhow::anyhow!(
426        "PDF export requires the `pdf` cargo feature on the `text-document-io` crate"
427    ))
428}
429
430/// Build the PDF bytes for `db_context` without writing a file.
431///
432/// This runs the exact same builder used by [`export_pdf`] but returns the compiled PDF bytes
433/// instead of writing them to disk, so callers (tests, notably) can inspect the produced document
434/// directly.
435#[cfg(feature = "pdf")]
436#[doc(hidden)]
437pub fn build_pdf_document(db_context: &DbContext, dto: &ExportPdfDto) -> Result<Vec<u8>> {
438    let uow_context = ExportPdfUnitOfWorkFactory::new(db_context);
439    let uc = ExportPdfUseCase::new(Box::new(uow_context), dto);
440    let (pdf_bytes, _page_count) = uc.build_document()?;
441    Ok(pdf_bytes)
442}
443
444#[cfg(feature = "pdf")]
445pub fn get_export_pdf_progress(
446    long_operation_manager: &LongOperationManager,
447    operation_id: &str,
448) -> Option<OperationProgress> {
449    long_operation_manager.get_operation_progress(operation_id)
450}
451
452#[cfg(not(feature = "pdf"))]
453pub fn get_export_pdf_progress(
454    _long_operation_manager: &LongOperationManager,
455    _operation_id: &str,
456) -> Option<OperationProgress> {
457    None
458}
459
460#[cfg(feature = "pdf")]
461pub fn get_export_pdf_result(
462    long_operation_manager: &LongOperationManager,
463    operation_id: &str,
464) -> Result<Option<ExportPdfResultDto>> {
465    // Get the operation result as a JSON string
466    let result_json = long_operation_manager.get_operation_result(operation_id);
467
468    // If there's no result, return None
469    if result_json.is_none() {
470        return Ok(None);
471    }
472    // Parse the JSON string into a ExportPdfResultDto
473    let result_dto: ExportPdfResultDto = serde_json::from_str(&result_json.unwrap())?;
474
475    Ok(Some(result_dto))
476}
477
478#[cfg(not(feature = "pdf"))]
479pub fn get_export_pdf_result(
480    _long_operation_manager: &LongOperationManager,
481    _operation_id: &str,
482) -> Result<Option<ExportPdfResultDto>> {
483    Ok(None)
484}