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