text-document-frontend 1.12.1

Frontend integration layer and command wrappers for text-document
Documentation
// Generated by Qleany v1.7.3 from frontend_app_context.tera

use common::database::db_context::DbContext;
use common::event::EventHub;
use common::long_operation::LongOperationManager;
use common::undo_redo::UndoRedoManager;
use flume::{Receiver, Sender};
use parking_lot::Mutex;
use std::sync::Arc;

/// Application context that holds all shared state.
///
/// Shutdown lifecycle: when [`AppContext::shutdown`] is called the shared
/// `shutdown_tx` is dropped, which makes every cloned `shutdown_rx`
/// (held by `EventHubClient::start` threads) see `Disconnected` on its
/// next `recv()` and exit. No polling and no atomic flag needed.
#[derive(Clone, Debug)]
pub struct AppContext {
    pub db_context: DbContext,
    pub event_hub: Arc<EventHub>,
    /// Receiver clones are handed to background event-hub threads.
    /// Threads exit when the matching sender is dropped via
    /// [`AppContext::shutdown`].
    pub shutdown_rx: Receiver<()>,
    /// Shared single sender. `shutdown()` takes it out, dropping
    /// the only live `Sender` and unblocking every receiver clone.
    /// Wrapped in `Mutex<Option<…>>` so `shutdown()` stays `&self`
    /// (the alternative — store the sender directly in `AppContext`
    /// — would force every clone to hold its own sender clone and
    /// `Disconnect` would only fire after the last clone dropped,
    /// defeating the explicit `shutdown()` API).
    shutdown_tx: Arc<Mutex<Option<Sender<()>>>>,
    pub undo_redo_manager: Arc<Mutex<UndoRedoManager>>,
    pub long_operation_manager: Arc<Mutex<LongOperationManager>>,
}

impl AppContext {
    pub fn new() -> Self {
        let db_context = DbContext::new().expect("Failed to create database context");

        let event_hub = Arc::new(EventHub::new());
        // Bounded(1) is enough — we only ever drop the sender, never
        // actually send a unit on this channel. The bound keeps the
        // allocation tiny.
        let (shutdown_tx, shutdown_rx) = flume::bounded(1);

        let undo_redo_manager = Arc::new(Mutex::new(UndoRedoManager::new()));
        let long_operation_manager = Arc::new(Mutex::new(LongOperationManager::new()));

        // Inject event hub into long_operation_manager
        {
            let mut lom = long_operation_manager.lock();
            lom.set_event_hub(&event_hub);
        }

        // And into undo_redo_manager, for the same reason and at the same
        // moment. It used to be injected lazily, by whichever undo/redo command
        // happened to run first — which meant the *push* events never reached
        // anyone: nothing pushes through those commands, so on a context where
        // the user had only ever edited, `StackChanged` was emitted into a
        // `None` hub and dropped. A subscriber cannot learn that "can undo" just
        // became true from an event that is only delivered after the first undo.
        {
            let mut urm = undo_redo_manager.lock();
            urm.set_event_hub(&event_hub);
        }

        Self {
            db_context,
            event_hub,
            shutdown_rx,
            shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))),
            undo_redo_manager,
            long_operation_manager,
        }
    }

    /// A context that shares another's event hub, shutdown channel and
    /// long-operation manager, with a **private** store and undo stack.
    ///
    /// What may be shared and what may not is decided by undo. Every
    /// repository's `snapshot`/`restore` takes and puts back the *whole* store
    /// (see `Transaction::snapshot_store`), so two documents in one store would
    /// undo and roll each other back. The store and the undo manager therefore
    /// stay private to each document.
    ///
    /// The event hub can be shared, and that is where the cost is: draining one
    /// costs an OS thread, so a hub per document is a thread per document. A
    /// stream over a book-length manuscript opens more than a hundred.
    ///
    /// Because the hub is shared, so is the shutdown channel: a document that
    /// stopped the pump on its own way out would stop it for every sibling. The
    /// owner of the shared context is what decides when the pump ends.
    pub fn new_sharing(other: &AppContext) -> Self {
        let db_context = DbContext::new().expect("Failed to create database context");
        let undo_redo_manager = Arc::new(Mutex::new(UndoRedoManager::new()));
        {
            let mut urm = undo_redo_manager.lock();
            urm.set_event_hub(&other.event_hub);
        }
        Self {
            db_context,
            event_hub: Arc::clone(&other.event_hub),
            shutdown_rx: other.shutdown_rx.clone(),
            shutdown_tx: Arc::clone(&other.shutdown_tx),
            undo_redo_manager,
            long_operation_manager: Arc::clone(&other.long_operation_manager),
        }
    }

    /// Signal background event-hub threads to stop. Idempotent: the second call
    /// is a no-op because the sender has already been taken.
    pub fn shutdown(&self) {
        let _ = self.shutdown_tx.lock().take();
    }
}

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