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 parking_lot::Mutex;
6use serde::Serialize;
7use std::{sync::Arc, thread};
8
9#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
10pub enum EntityEvent {
11    Created,
12    Updated,
13    Removed,
14}
15
16#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
17pub enum AllEvent {
18    Reset,
19}
20
21#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
22pub enum UndoRedoEvent {
23    Undone,
24    Redone,
25    BeginComposite,
26    EndComposite,
27    CancelComposite,
28}
29
30#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
31pub enum LongOperationEvent {
32    Started,
33    Progress,
34    Cancelled,
35    Completed,
36    Failed,
37}
38
39#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
40pub enum DirectAccessEntity {
41    All(AllEvent),
42
43    Root(EntityEvent),
44    Document(EntityEvent),
45    Frame(EntityEvent),
46    Block(EntityEvent),
47    List(EntityEvent),
48    Resource(EntityEvent),
49    Table(EntityEvent),
50    TableCell(EntityEvent),
51}
52
53#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize)]
54pub enum DocumentEditingEvent {
55    InsertText,
56    DeleteText,
57    InsertBlock,
58    InsertImage,
59    InsertFrame,
60    InsertFormattedText,
61    CreateList,
62    InsertList,
63    AddBlockToList,
64    RemoveBlockFromList,
65    InsertFragment,
66    InsertHtmlAtPosition,
67    InsertMarkdownAtPosition,
68    InsertTable,
69    RemoveTable,
70    InsertTableRow,
71    InsertTableColumn,
72    RemoveTableRow,
73    RemoveTableColumn,
74    MergeTableCells,
75    SplitTableCell,
76    WrapBlocksInFrame,
77    UnwrapFrame,
78    UnwrapBlockFromFrame,
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::List(event) => format!("direct_access_list_{:?}", event),
152                DirectAccessEntity::Resource(event) => {
153                    format!("direct_access_resource_{:?}", event)
154                }
155                DirectAccessEntity::Table(event) => format!("direct_access_table_{:?}", event),
156                DirectAccessEntity::TableCell(event) => {
157                    format!("direct_access_table_cell_{:?}", event)
158                }
159            },
160            Origin::UndoRedo(event) => format!("undo_redo_{:?}", event),
161            Origin::LongOperation(event) => format!("long_operation_{:?}", event),
162            // features
163            Origin::DocumentEditing(event) => format!("document_editing_{:?}", event),
164            Origin::DocumentFormatting(event) => format!("document_formatting_{:?}", event),
165            Origin::DocumentIo(event) => format!("document_io_{:?}", event),
166            Origin::DocumentSearch(event) => format!("document_search_{:?}", event),
167            Origin::DocumentInspection(event) => format!("document_inspection_{:?}", event),
168        }
169        .to_lowercase()
170    }
171}
172/// Thread-safe event buffer for deferring event emissions during transactions.
173///
174/// Repositories push events into this buffer instead of sending them directly
175/// to the EventHub. On commit(), the UoW drains the buffer and sends all events.
176/// On rollback(), the buffer is discarded. This prevents the UI from seeing
177/// phantom state from failed transactions.
178///
179/// This is the Rust equivalent of SignalBuffer in the C++/Qt target.
180pub struct EventBuffer {
181    buffering: bool,
182    pending: Vec<Event>,
183}
184
185impl EventBuffer {
186    pub fn new() -> Self {
187        Self {
188            buffering: false,
189            pending: Vec::new(),
190        }
191    }
192
193    /// Start buffering. Clears any stale events from a previous cycle.
194    pub fn begin_buffering(&mut self) {
195        self.buffering = true;
196        self.pending.clear();
197    }
198
199    /// Queue an event for deferred delivery.
200    ///
201    /// If buffering is not active, the event is silently dropped.
202    /// (Callers should only push during an active transaction.)
203    pub fn push(&mut self, event: Event) {
204        if self.buffering {
205            self.pending.push(event);
206        }
207    }
208
209    /// Drain all pending events and stop buffering.
210    /// The caller is responsible for sending them to the EventHub.
211    pub fn flush(&mut self) -> Vec<Event> {
212        self.buffering = false;
213        std::mem::take(&mut self.pending)
214    }
215
216    /// Discard all pending events and stop buffering.
217    pub fn discard(&mut self) {
218        self.buffering = false;
219        self.pending.clear();
220    }
221
222    pub fn is_buffering(&self) -> bool {
223        self.buffering
224    }
225}
226
227impl Default for EventBuffer {
228    fn default() -> Self {
229        Self::new()
230    }
231}
232
233pub type Queue = Arc<Mutex<Vec<Event>>>;
234
235/// Central event hub for managing subscriptions and dispatching events
236#[derive(Debug)]
237pub struct EventHub {
238    sender: Sender<Event>,
239    receiver: Receiver<Event>,
240    queue: Queue,
241}
242
243impl Default for EventHub {
244    fn default() -> Self {
245        Self::new()
246    }
247}
248
249impl EventHub {
250    /// Create a new event hub
251    pub fn new() -> Self {
252        let (sender, receiver) = unbounded();
253        EventHub {
254            sender,
255            receiver,
256            queue: Arc::new(Mutex::new(Vec::new())),
257        }
258    }
259
260    /// Start the event processing loop.
261    ///
262    /// Returns a `JoinHandle` so the caller can join the thread on
263    /// shutdown. The thread blocks on a `flume::Selector` that wakes
264    /// only when an event arrives or `shutdown_rx` disconnects — zero
265    /// CPU while idle. Drop the matching `Sender<()>` to stop the
266    /// thread.
267    pub fn start_event_loop(&self, shutdown_rx: Receiver<()>) -> thread::JoinHandle<()> {
268        let receiver = self.receiver.clone();
269        let queue = self.queue.clone();
270        thread::spawn(move || {
271            loop {
272                let outcome: Result<Option<Event>, ()> = flume::Selector::new()
273                    .recv(&receiver, |r| r.map(Some).map_err(|_| ()))
274                    .recv(&shutdown_rx, |_| Ok(None))
275                    .wait();
276                match outcome {
277                    Ok(Some(event)) => {
278                        let mut queue = queue.lock();
279                        queue.push(event);
280                    }
281                    Ok(None) | Err(()) => break,
282                }
283            }
284        })
285    }
286
287    /// Send an event to the queue
288    pub fn send_event(&self, event: Event) {
289        if let Err(e) = self.sender.send(event) {
290            eprintln!("EventHub: failed to send event (receiver dropped): {e}");
291        }
292    }
293
294    pub fn get_queue(&self) -> Queue {
295        self.queue.clone()
296    }
297
298    /// Get a direct event receiver.
299    ///
300    /// The receiver blocks on `recv()` until an event arrives — no polling needed.
301    /// **Important**: flume uses MPMC semantics — each event is delivered to exactly
302    /// one receiver. Multiple cloned receivers compete for events rather than each
303    /// receiving a copy. Ensure only one consumer calls `subscribe_receiver()`.
304    pub fn subscribe_receiver(&self) -> Receiver<Event> {
305        self.receiver.clone()
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn test_event_hub_send_and_receive() {
315        let event_hub = EventHub::new();
316        let (shutdown_tx, shutdown_rx) = flume::bounded::<()>(1);
317        let handle = event_hub.start_event_loop(shutdown_rx);
318
319        let event = Event {
320            origin: Origin::DirectAccess(DirectAccessEntity::All(AllEvent::Reset)),
321            ids: vec![EntityId::default()],
322            data: Some("test_data".to_string()),
323        };
324
325        event_hub.send_event(event.clone());
326
327        thread::sleep(std::time::Duration::from_millis(100));
328
329        let queue = event_hub.get_queue();
330        let queue = queue.lock();
331        assert_eq!(queue.len(), 1);
332        assert_eq!(queue[0], event);
333
334        // Drop the sender to wake the loop thread and exit cleanly.
335        drop(shutdown_tx);
336        handle.join().unwrap();
337    }
338}