]> uap-core.de Git - note.git/commitdiff
simplify backend initialization and commands main
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 17 May 2026 13:58:34 +0000 (15:58 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 17 May 2026 13:58:34 +0000 (15:58 +0200)
application/src/backend.rs
application/src/main.rs

index a2bf39df3bcdb9d731aa29f0d7e84340194c93ed..c7f18c1e1beb20f1140835b4951a6fea03b04d09 100644 (file)
@@ -15,19 +15,17 @@ pub struct Backend {
     rt: Arc<Runtime>,
     db: DatabaseConnection,
 
-    pub current_profile: Option<profile::Model>,
-    pub broadcast: Option<tokio::sync::broadcast::Sender<BroadcastMessage>>
-}
+    pub current_profile: profile::Model,
+    pub broadcast: tokio::sync::broadcast::Sender<BroadcastMessage>,
 
-type CmdFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
-pub trait Cmd: Send {
-    fn run(self: Box<Self>, backend: Arc<Backend>) -> CmdFuture;
+    pub broadcast_notify: Option<Box<dyn Fn() + Send + Sync>>
 }
 
-pub type DynCmd = Box<dyn Cmd>;
+type CmdFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
 
 pub struct BackendHandle {
-    pub tx: tokio::sync::mpsc::UnboundedSender<DynCmd>,
+    pub backend: Arc<Backend>,
+    pub tx: tokio::sync::mpsc::UnboundedSender<CmdFuture>,
     pub btx: tokio::sync::broadcast::Sender<BroadcastMessage>,
     pub brx: tokio::sync::broadcast::Receiver<BroadcastMessage>
 }
@@ -35,6 +33,7 @@ pub struct BackendHandle {
 impl Clone for BackendHandle {
     fn clone(&self) -> Self {
         BackendHandle {
+            backend: self.backend.clone(),
             tx: self.tx.clone(),
             btx: self.btx.clone(),
             brx: self.btx.subscribe()
@@ -49,7 +48,7 @@ pub enum BroadcastMessage {
 
 
 impl Backend {
-    pub fn new() -> Result<Self, DbErr> {
+    pub fn new(profile: Option<i32>, host: &str, user: &str) -> Result<Self, DbErr> {
         let rt = Arc::new(
             tokio::runtime::Builder::new_multi_thread()
                 .enable_all()
@@ -64,24 +63,26 @@ impl Backend {
             Database::connect(connection_str).await
         })?;
 
+        rt.block_on(async {
+            Migrator::up(&db, None).await
+        })?;
+
+        let profile = Backend::load_profile(&rt, &db, profile, host, user)?;
+
+        let (tx, _) = broadcast::channel::<BroadcastMessage>(16);
+
         Ok(Self {
             rt,
             db,
-            current_profile: None,
-            broadcast: None
+            current_profile: profile,
+            broadcast: tx,
+            broadcast_notify: None
         })
     }
 
-    pub fn migrate(&self) -> Result<(), DbErr> {
-        self.rt.block_on(async {
-            Migrator::up(&self.db, None).await
-        })?;
-
-        Ok(())
-    }
-
     pub fn load_profile(
-        &self,
+        rt: &Arc<Runtime>,
+        db: &DatabaseConnection,
         profile_id: Option<i32>,
         host: &str,
         user: &str,
@@ -90,12 +91,12 @@ impl Backend {
         let host = host.to_string();
         let user = user.to_string();
 
-        let profile: profile::Model = self.rt.block_on(async {
+        let profile: profile::Model = rt.block_on(async {
             // If a profile id was specified, we are trying to use that,
             // but if it doesn't exist, an error is returned
             if let Some(id) = profile_id {
                 if let Some(model) = Profile::find_by_id(id)
-                    .one(&self.db)
+                    .one(db)
                     .await?
                 {
                     return Ok::<profile::Model, DbErr>(model);
@@ -109,7 +110,7 @@ impl Backend {
             if let Some(model) = Profile::find()
                 .filter(profile::Column::Host.eq(host.clone()))
                 .filter(profile::Column::User.eq(user.clone()))
-                .one(&self.db)
+                .one(db)
                 .await?
             {
                 return Ok::<profile::Model, DbErr>(model);
@@ -125,7 +126,7 @@ impl Backend {
                 ..Default::default()
             };
 
-            let inserted = active.insert(&self.db).await?;
+            let inserted = active.insert(db).await?;
 
             // create some initial notebooks
             let insert_notebooks = collection::ActiveModel {
@@ -137,7 +138,7 @@ impl Backend {
 
                 ..Default::default()
             };
-            insert_notebooks.insert(&self.db).await?;
+            insert_notebooks.insert(db).await?;
 
             let insert_notes = collection::ActiveModel {
                 profile_id: Set(inserted.id),
@@ -148,7 +149,7 @@ impl Backend {
 
                 ..Default::default()
             };
-            insert_notes.insert(&self.db).await?;
+            insert_notes.insert(db).await?;
 
             Ok::<profile::Model, DbErr>(inserted)
         })?;
@@ -156,60 +157,59 @@ impl Backend {
         Ok(profile)
     }
 
-    pub fn load_collections(&self, profile_id: i32) -> Result<Vec<collection::Model>, DbErr> {
+    /// Synchronous function for loading the notebook structure, used at startup
+    /// before any window is opened
+    pub fn load_collections(&self) -> Result<Vec<collection::Model>, DbErr> {
         self.rt.block_on(async {
             Collection::find()
-                .filter(collection::Column::ProfileId.eq(profile_id))
+                .filter(collection::Column::ProfileId.eq(self.current_profile.id))
                 .order_by_asc(collection::Column::Parent)
                 .all(&self.db).await
         })
     }
 
-    pub fn start(mut self) -> BackendHandle {
-        let (tx, mut rx) = mpsc::unbounded_channel::<DynCmd>();
-        let (btx, brx) = broadcast::channel::<BroadcastMessage>(16);
-        self.broadcast = Some(btx.clone());
+    /// Starts the backend thread
+    ///
+    /// This function returns a cloneable BackendHandle, that can be used to send
+    /// messages to the backend via the tx Sender.
+    ///
+    /// The handle also contains a broadcast sender/receiver (btx, brx).
+    pub fn start(self) -> BackendHandle {
+        let (tx, mut rx) = mpsc::unbounded_channel::<CmdFuture>();
+        let broadcast_tx = self.broadcast.clone();
+        let broadcast_rx = broadcast_tx.subscribe();
 
         let backend = Arc::new(self);
+        let backend_clone = backend.clone();
 
         let rt = backend.rt.clone();
 
         std::thread::spawn(move || {
             rt.block_on(async move {
                 while let Some(cmd) = rx.recv().await {
-                    let backend = backend.clone();
-
                     tokio::spawn(async move {
-                        cmd.run(backend).await;
+                        cmd.await
                     });
                 }
             });
         });
 
         BackendHandle {
+            backend: backend_clone,
             tx: tx,
-            btx: btx,
-            brx: brx,
+            btx: broadcast_tx,
+            brx: broadcast_rx,
         }
     }
 }
 
 impl BackendHandle {
+    /// Reloads the notebook structure/collections. On success, it sends a NotebookStructureUpdate
+    /// to the broadcast channel.
     pub fn reload_collections(&self) {
-        let cmd: DynCmd = Box::new(ReloadCollectionsCmd);
-        let _ = self.tx.send(cmd);
-    }
-}
-
-
-struct ReloadCollectionsCmd;
-
-impl Cmd for ReloadCollectionsCmd {
-    fn run(self: Box<Self>, backend: Arc<Backend>) -> CmdFuture {
-        Box::pin(async move {
-            println!("reloading collections...");
-
-            let profile_id = backend.current_profile.as_ref().unwrap().id;
+        let backend = self.backend.clone();
+        let cmd = Box::pin(async move {
+            let profile_id = backend.current_profile.id;
 
             let result = Collection::find()
                 .filter(collection::Column::ProfileId.eq(profile_id))
@@ -218,11 +218,13 @@ impl Cmd for ReloadCollectionsCmd {
 
             if let Ok(collection) = result {
                 let nodes = create_notebook_hierarchy(collection);
-                if let Some(tx) = backend.broadcast.as_ref() {
-                    let _ = tx.send(BroadcastMessage::NotebookStructureUpdate(nodes));
-                    ui::broadcast_action("message");
+                let _ = backend.broadcast.send(BroadcastMessage::NotebookStructureUpdate(nodes));
+
+                if let Some(notify) = backend.broadcast_notify.as_ref() {
+                    notify();
                 }
             }
-        })
+        });
+        let _ = self.tx.send(cmd);
     }
-}
\ No newline at end of file
+}
index 4683cee7aeba04f2fd683e51b3ce859b0a36d6b8..4c957ea230c802d3830f85f04b5f21f8c0bb3bf8 100644 (file)
@@ -46,9 +46,8 @@ fn main() {
             return;
         }
     };
-    let profile_id = backend.current_profile.as_ref().unwrap().id;
 
-    let notebooks = match backend.load_collections(profile_id) {
+    let notebooks = match backend.load_collections() {
         Ok(notebooks) => create_notebook_hierarchy(notebooks),
         Err(e) => {
             let msg = format!("Error loading collections: {:?}", e);
@@ -78,10 +77,14 @@ struct ErrMsg {
 ///  - Connecting to the database
 ///  - Migrating the database
 ///  - Getting the user profile / settings
+///  - Setup UI broadcasting
 ///
 /// If no profile exists yet, a new profile is created
 fn init_backend() -> Result<Backend, ErrMsg> {
-    let mut backend = match Backend::new() {
+    let host = hostname();
+    let user = username();
+
+    let mut backend = match Backend::new(None, host.as_str(), user.as_str()) {
         Ok(backend) => backend,
         Err(e) => return Err( ErrMsg {
                 title: "Backend initialization failed".to_string(),
@@ -89,25 +92,7 @@ fn init_backend() -> Result<Backend, ErrMsg> {
             })
     };
 
-    match backend.migrate() {
-        Ok(_) => (),
-        Err(e) => return Err(ErrMsg {
-                title: "Database migration failed".to_string(),
-                message: e.to_string()
-            })
-    };
-
-    let host = hostname();
-    let user = username();
-
-    let profile = match backend.load_profile(None, host.as_str(), user.as_str()) {
-        Ok(profile) => profile,
-        Err(e) => return Err(ErrMsg {
-            title: "Cannot load user settings".to_string(),
-            message: e.to_string()
-        })
-    };
-    backend.current_profile = Some(profile);
+    backend.broadcast_notify = Some(Box::new(|| ui::broadcast_action("message") ));
 
     Ok(backend)
 }