text-document-common 1.4.0

Shared entities, database, events, and undo/redo infrastructure for text-document
Documentation
// Generated by Qleany v1.5.1 from common_event.tera

use crate::types::EntityId;
use flume::{Receiver, Sender, unbounded};
use serde::Serialize;
use std::{
    sync::{Arc, Mutex, atomic::AtomicBool},
    thread,
};

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum EntityEvent {
    Created,
    Updated,
    Removed,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum AllEvent {
    Reset,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum UndoRedoEvent {
    Undone,
    Redone,
    BeginComposite,
    EndComposite,
    CancelComposite,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum LongOperationEvent {
    Started,
    Progress,
    Cancelled,
    Completed,
    Failed,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum DirectAccessEntity {
    All(AllEvent),

    Root(EntityEvent),
    Document(EntityEvent),
    Frame(EntityEvent),
    Block(EntityEvent),
    InlineElement(EntityEvent),
    List(EntityEvent),
    Resource(EntityEvent),
    Table(EntityEvent),
    TableCell(EntityEvent),
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum DocumentEditingEvent {
    InsertText,
    DeleteText,
    InsertBlock,
    InsertImage,
    InsertFrame,
    InsertFormattedText,
    CreateList,
    InsertList,
    InsertFragment,
    InsertHtmlAtPosition,
    InsertMarkdownAtPosition,
    InsertTable,
    RemoveTable,
    InsertTableRow,
    InsertTableColumn,
    RemoveTableRow,
    RemoveTableColumn,
    MergeTableCells,
    SplitTableCell,
    AddBlockToList,
    RemoveBlockFromList,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum DocumentFormattingEvent {
    SetTextFormat,
    MergeTextFormat,
    SetBlockFormat,
    SetFrameFormat,
    SetTableFormat,
    SetTableCellFormat,
    SetListFormat,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum DocumentIoEvent {
    ImportPlainText,
    ExportPlainText,
    ImportMarkdown,
    ExportMarkdown,
    ImportHtml,
    ExportHtml,
    ExportLatex,
    ExportDocx,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum DocumentSearchEvent {
    FindText,
    FindAll,
    ReplaceText,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum DocumentInspectionEvent {
    GetDocumentStats,
    GetTextAtPosition,
    GetBlockAtPosition,
    ExtractFragment,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub enum Origin {
    DirectAccess(DirectAccessEntity),
    UndoRedo(UndoRedoEvent),
    LongOperation(LongOperationEvent),

    DocumentEditing(DocumentEditingEvent),
    DocumentFormatting(DocumentFormattingEvent),
    DocumentIo(DocumentIoEvent),
    DocumentSearch(DocumentSearchEvent),
    DocumentInspection(DocumentInspectionEvent),
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
pub struct Event {
    pub origin: Origin,
    pub ids: Vec<EntityId>,
    pub data: Option<String>,
}

impl Event {
    pub fn origin_string(&self) -> String {
        match &self.origin {
            Origin::DirectAccess(entity) => match entity {
                DirectAccessEntity::All(event) => format!("direct_access_all_{:?}", event),
                // entities
                DirectAccessEntity::Root(event) => format!("direct_access_root_{:?}", event),
                DirectAccessEntity::Document(event) => {
                    format!("direct_access_document_{:?}", event)
                }
                DirectAccessEntity::Frame(event) => format!("direct_access_frame_{:?}", event),
                DirectAccessEntity::Block(event) => format!("direct_access_block_{:?}", event),
                DirectAccessEntity::InlineElement(event) => {
                    format!("direct_access_inline_element_{:?}", event)
                }
                DirectAccessEntity::List(event) => format!("direct_access_list_{:?}", event),
                DirectAccessEntity::Resource(event) => {
                    format!("direct_access_resource_{:?}", event)
                }
                DirectAccessEntity::Table(event) => format!("direct_access_table_{:?}", event),
                DirectAccessEntity::TableCell(event) => {
                    format!("direct_access_table_cell_{:?}", event)
                }
            },
            Origin::UndoRedo(event) => format!("undo_redo_{:?}", event),
            Origin::LongOperation(event) => format!("long_operation_{:?}", event),
            // features
            Origin::DocumentEditing(event) => format!("document_editing_{:?}", event),
            Origin::DocumentFormatting(event) => format!("document_formatting_{:?}", event),
            Origin::DocumentIo(event) => format!("document_io_{:?}", event),
            Origin::DocumentSearch(event) => format!("document_search_{:?}", event),
            Origin::DocumentInspection(event) => format!("document_inspection_{:?}", event),
        }
        .to_lowercase()
    }
}
/// Thread-safe event buffer for deferring event emissions during transactions.
///
/// Repositories push events into this buffer instead of sending them directly
/// to the EventHub. On commit(), the UoW drains the buffer and sends all events.
/// On rollback(), the buffer is discarded. This prevents the UI from seeing
/// phantom state from failed transactions.
///
/// This is the Rust equivalent of SignalBuffer in the C++/Qt target.
pub struct EventBuffer {
    buffering: bool,
    pending: Vec<Event>,
}

impl EventBuffer {
    pub fn new() -> Self {
        Self {
            buffering: false,
            pending: Vec::new(),
        }
    }

    /// Start buffering. Clears any stale events from a previous cycle.
    pub fn begin_buffering(&mut self) {
        self.buffering = true;
        self.pending.clear();
    }

    /// Queue an event for deferred delivery.
    ///
    /// If buffering is not active, the event is silently dropped.
    /// (Callers should only push during an active transaction.)
    pub fn push(&mut self, event: Event) {
        if self.buffering {
            self.pending.push(event);
        }
    }

    /// Drain all pending events and stop buffering.
    /// The caller is responsible for sending them to the EventHub.
    pub fn flush(&mut self) -> Vec<Event> {
        self.buffering = false;
        std::mem::take(&mut self.pending)
    }

    /// Discard all pending events and stop buffering.
    pub fn discard(&mut self) {
        self.buffering = false;
        self.pending.clear();
    }

    pub fn is_buffering(&self) -> bool {
        self.buffering
    }
}

impl Default for EventBuffer {
    fn default() -> Self {
        Self::new()
    }
}

pub type Queue = Arc<Mutex<Vec<Event>>>;

/// Central event hub for managing subscriptions and dispatching events
pub struct EventHub {
    sender: Sender<Event>,
    receiver: Receiver<Event>,
    queue: Queue,
}

impl Default for EventHub {
    fn default() -> Self {
        Self::new()
    }
}

impl EventHub {
    /// Create a new event hub
    pub fn new() -> Self {
        let (sender, receiver) = unbounded();
        EventHub {
            sender,
            receiver,
            queue: Arc::new(Mutex::new(Vec::new())),
        }
    }

    /// Start the event processing loop
    pub fn start_event_loop(&self, stop_signal: Arc<AtomicBool>) {
        let receiver = self.receiver.clone();
        let queue = self.queue.clone();
        thread::spawn(move || {
            loop {
                if stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
                    break;
                }

                match receiver.recv() {
                    Ok(event) => {
                        let mut queue = queue.lock().unwrap();
                        queue.push(event.clone());
                    }
                    Err(_) => {
                        //println!("EventHub receiver dropped");
                        break;
                    }
                };
            }
        });
    }

    /// Send an event to the queue
    pub fn send_event(&self, event: Event) {
        if let Err(e) = self.sender.send(event) {
            eprintln!("EventHub: failed to send event (receiver dropped): {e}");
        }
    }

    pub fn get_queue(&self) -> Queue {
        self.queue.clone()
    }

    /// Get a direct event receiver.
    ///
    /// The receiver blocks on `recv()` until an event arrives — no polling needed.
    /// **Important**: flume uses MPMC semantics — each event is delivered to exactly
    /// one receiver. Multiple cloned receivers compete for events rather than each
    /// receiving a copy. Ensure only one consumer calls `subscribe_receiver()`.
    pub fn subscribe_receiver(&self) -> Receiver<Event> {
        self.receiver.clone()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_event_hub_send_and_receive() {
        let event_hub = EventHub::new();
        let stop_signal = Arc::new(AtomicBool::new(false));
        event_hub.start_event_loop(stop_signal.clone());

        let event = Event {
            origin: Origin::DirectAccess(DirectAccessEntity::All(AllEvent::Reset)),
            ids: vec![EntityId::default()],
            data: Some("test_data".to_string()),
        };

        event_hub.send_event(event.clone());

        thread::sleep(std::time::Duration::from_millis(100));

        let queue = event_hub.get_queue();
        let queue = queue.lock().unwrap();
        assert_eq!(queue.len(), 1);
        assert_eq!(queue[0], event);

        stop_signal.store(true, std::sync::atomic::Ordering::Relaxed);
    }
}