Skip to main content

common/
event.rs

1// Generated by Qleany v1.7.3 from common_event.tera
2
3use crate::types::EntityId;
4use flume::{Receiver, Sender, unbounded};
5use serde::Serialize;
6use std::{
7    sync::{Arc, Mutex, atomic::AtomicBool},
8    thread,
9};
10
11#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
12pub enum EntityEvent {
13    Created,
14    Updated,
15    Removed,
16}
17
18#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
19pub enum AllEvent {
20    Reset,
21}
22
23#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
24pub enum UndoRedoEvent {
25    Undone,
26    Redone,
27    BeginComposite,
28    EndComposite,
29    CancelComposite,
30}
31
32#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
33pub enum LongOperationEvent {
34    Started,
35    Progress,
36    Cancelled,
37    Completed,
38    Failed,
39}
40
41#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
42pub enum DirectAccessEntity {
43    All(AllEvent),
44
45    Root(EntityEvent),
46    Document(EntityEvent),
47    Frame(EntityEvent),
48    Block(EntityEvent),
49    InlineElement(EntityEvent),
50    List(EntityEvent),
51    Resource(EntityEvent),
52    Table(EntityEvent),
53    TableCell(EntityEvent),
54}
55
56#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
57pub enum DocumentEditingEvent {
58    InsertText,
59    DeleteText,
60    InsertBlock,
61    InsertImage,
62    InsertFrame,
63    InsertFormattedText,
64    CreateList,
65    InsertList,
66    AddBlockToList,
67    RemoveBlockFromList,
68    InsertFragment,
69    InsertHtmlAtPosition,
70    InsertMarkdownAtPosition,
71    InsertTable,
72    RemoveTable,
73    InsertTableRow,
74    InsertTableColumn,
75    RemoveTableRow,
76    RemoveTableColumn,
77    MergeTableCells,
78    SplitTableCell,
79}
80
81#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
82pub enum DocumentFormattingEvent {
83    SetTextFormat,
84    MergeTextFormat,
85    SetBlockFormat,
86    SetFrameFormat,
87    SetTableFormat,
88    SetTableCellFormat,
89    SetListFormat,
90}
91
92#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
93pub enum DocumentIoEvent {
94    ImportPlainText,
95    ExportPlainText,
96    ImportMarkdown,
97    ExportMarkdown,
98    ImportHtml,
99    ExportHtml,
100    ExportLatex,
101    ExportDocx,
102}
103
104#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
105pub enum DocumentSearchEvent {
106    FindText,
107    FindAll,
108    ReplaceText,
109}
110
111#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
112pub enum DocumentInspectionEvent {
113    GetDocumentStats,
114    GetTextAtPosition,
115    GetBlockAtPosition,
116    ExtractFragment,
117}
118
119#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
120pub enum Origin {
121    DirectAccess(DirectAccessEntity),
122    UndoRedo(UndoRedoEvent),
123    LongOperation(LongOperationEvent),
124
125    DocumentEditing(DocumentEditingEvent),
126    DocumentFormatting(DocumentFormattingEvent),
127    DocumentIo(DocumentIoEvent),
128    DocumentSearch(DocumentSearchEvent),
129    DocumentInspection(DocumentInspectionEvent),
130}
131
132#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
133pub struct Event {
134    pub origin: Origin,
135    pub ids: Vec<EntityId>,
136    pub data: Option<String>,
137}
138
139impl Event {
140    pub fn origin_string(&self) -> String {
141        match &self.origin {
142            Origin::DirectAccess(entity) => match entity {
143                DirectAccessEntity::All(event) => format!("direct_access_all_{:?}", event),
144                // entities
145                DirectAccessEntity::Root(event) => format!("direct_access_root_{:?}", event),
146                DirectAccessEntity::Document(event) => {
147                    format!("direct_access_document_{:?}", event)
148                }
149                DirectAccessEntity::Frame(event) => format!("direct_access_frame_{:?}", event),
150                DirectAccessEntity::Block(event) => format!("direct_access_block_{:?}", event),
151                DirectAccessEntity::InlineElement(event) => {
152                    format!("direct_access_inline_element_{:?}", event)
153                }
154                DirectAccessEntity::List(event) => format!("direct_access_list_{:?}", event),
155                DirectAccessEntity::Resource(event) => {
156                    format!("direct_access_resource_{:?}", event)
157                }
158                DirectAccessEntity::Table(event) => format!("direct_access_table_{:?}", event),
159                DirectAccessEntity::TableCell(event) => {
160                    format!("direct_access_table_cell_{:?}", event)
161                }
162            },
163            Origin::UndoRedo(event) => format!("undo_redo_{:?}", event),
164            Origin::LongOperation(event) => format!("long_operation_{:?}", event),
165            // features
166            Origin::DocumentEditing(event) => format!("document_editing_{:?}", event),
167            Origin::DocumentFormatting(event) => format!("document_formatting_{:?}", event),
168            Origin::DocumentIo(event) => format!("document_io_{:?}", event),
169            Origin::DocumentSearch(event) => format!("document_search_{:?}", event),
170            Origin::DocumentInspection(event) => format!("document_inspection_{:?}", event),
171        }
172        .to_lowercase()
173    }
174}
175/// Thread-safe event buffer for deferring event emissions during transactions.
176///
177/// Repositories push events into this buffer instead of sending them directly
178/// to the EventHub. On commit(), the UoW drains the buffer and sends all events.
179/// On rollback(), the buffer is discarded. This prevents the UI from seeing
180/// phantom state from failed transactions.
181///
182/// This is the Rust equivalent of SignalBuffer in the C++/Qt target.
183pub struct EventBuffer {
184    buffering: bool,
185    pending: Vec<Event>,
186}
187
188impl EventBuffer {
189    pub fn new() -> Self {
190        Self {
191            buffering: false,
192            pending: Vec::new(),
193        }
194    }
195
196    /// Start buffering. Clears any stale events from a previous cycle.
197    pub fn begin_buffering(&mut self) {
198        self.buffering = true;
199        self.pending.clear();
200    }
201
202    /// Queue an event for deferred delivery.
203    ///
204    /// If buffering is not active, the event is silently dropped.
205    /// (Callers should only push during an active transaction.)
206    pub fn push(&mut self, event: Event) {
207        if self.buffering {
208            self.pending.push(event);
209        }
210    }
211
212    /// Drain all pending events and stop buffering.
213    /// The caller is responsible for sending them to the EventHub.
214    pub fn flush(&mut self) -> Vec<Event> {
215        self.buffering = false;
216        std::mem::take(&mut self.pending)
217    }
218
219    /// Discard all pending events and stop buffering.
220    pub fn discard(&mut self) {
221        self.buffering = false;
222        self.pending.clear();
223    }
224
225    pub fn is_buffering(&self) -> bool {
226        self.buffering
227    }
228}
229
230impl Default for EventBuffer {
231    fn default() -> Self {
232        Self::new()
233    }
234}
235
236pub type Queue = Arc<Mutex<Vec<Event>>>;
237
238/// Central event hub for managing subscriptions and dispatching events
239#[derive(Debug)]
240pub struct EventHub {
241    sender: Sender<Event>,
242    receiver: Receiver<Event>,
243    queue: Queue,
244}
245
246impl Default for EventHub {
247    fn default() -> Self {
248        Self::new()
249    }
250}
251
252impl EventHub {
253    /// Create a new event hub
254    pub fn new() -> Self {
255        let (sender, receiver) = unbounded();
256        EventHub {
257            sender,
258            receiver,
259            queue: Arc::new(Mutex::new(Vec::new())),
260        }
261    }
262
263    /// Start the event processing loop.
264    ///
265    /// Returns a `JoinHandle` so the caller can join the thread on shutdown.
266    /// The loop checks `stop_signal` between receives via a timeout, ensuring
267    /// it will exit even if no events arrive.
268    pub fn start_event_loop(&self, stop_signal: Arc<AtomicBool>) -> thread::JoinHandle<()> {
269        let receiver = self.receiver.clone();
270        let queue = self.queue.clone();
271        thread::spawn(move || {
272            loop {
273                if stop_signal.load(std::sync::atomic::Ordering::Relaxed) {
274                    break;
275                }
276
277                match receiver.recv_timeout(std::time::Duration::from_millis(100)) {
278                    Ok(event) => {
279                        let mut queue = queue.lock().unwrap();
280                        queue.push(event.clone());
281                    }
282                    Err(flume::RecvTimeoutError::Timeout) => {
283                        // Check stop_signal on next iteration
284                        continue;
285                    }
286                    Err(flume::RecvTimeoutError::Disconnected) => {
287                        break;
288                    }
289                };
290            }
291        })
292    }
293
294    /// Send an event to the queue
295    pub fn send_event(&self, event: Event) {
296        if let Err(e) = self.sender.send(event) {
297            eprintln!("EventHub: failed to send event (receiver dropped): {e}");
298        }
299    }
300
301    pub fn get_queue(&self) -> Queue {
302        self.queue.clone()
303    }
304
305    /// Get a direct event receiver.
306    ///
307    /// The receiver blocks on `recv()` until an event arrives — no polling needed.
308    /// **Important**: flume uses MPMC semantics — each event is delivered to exactly
309    /// one receiver. Multiple cloned receivers compete for events rather than each
310    /// receiving a copy. Ensure only one consumer calls `subscribe_receiver()`.
311    pub fn subscribe_receiver(&self) -> Receiver<Event> {
312        self.receiver.clone()
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    #[test]
321    fn test_event_hub_send_and_receive() {
322        let event_hub = EventHub::new();
323        let stop_signal = Arc::new(AtomicBool::new(false));
324        let _handle = event_hub.start_event_loop(stop_signal.clone());
325
326        let event = Event {
327            origin: Origin::DirectAccess(DirectAccessEntity::All(AllEvent::Reset)),
328            ids: vec![EntityId::default()],
329            data: Some("test_data".to_string()),
330        };
331
332        event_hub.send_event(event.clone());
333
334        thread::sleep(std::time::Duration::from_millis(100));
335
336        let queue = event_hub.get_queue();
337        let queue = queue.lock().unwrap();
338        assert_eq!(queue.len(), 1);
339        assert_eq!(queue[0], event);
340
341        stop_signal.store(true, std::sync::atomic::Ordering::Relaxed);
342    }
343}