Skip to main content

text_document_common/
event.rs

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