]> uap-core.de Git - note.git/commitdiff
implement check for external note content changes, when a note is attached
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Mon, 20 Jul 2026 17:38:17 +0000 (19:38 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Mon, 20 Jul 2026 17:38:17 +0000 (19:38 +0200)
Cargo.lock
application/backend/src/backend.rs
application/backend/src/notify.rs
application/note/Cargo.toml
application/note/src/main.rs
application/note/src/note.rs
application/note/src/notebook.rs
application/note/src/window.rs
ui-rs/src/ui/container.rs

index d63a028e6e5e188945ff42b7a92371b10f91bbb2..9c387f4523e22af34d46f3f4e1ef2862f7b65ef7 100644 (file)
@@ -1577,6 +1577,7 @@ version = "0.1.0"
 dependencies = [
  "backend",
  "entity",
+ "notify",
  "sea-orm",
  "tokio",
  "ui-rs",
index 6a3e800ccc77d2dc31cef85e082f2ab2970467cc..4b81ae69b19f98c7634c8dc8e0799087d96e8bb1 100644 (file)
@@ -309,9 +309,9 @@ impl Backend {
         });
 
         // start fs notification
-        let fs_btx = broadcast_tx.clone();
+        let notify_backend = backend_clone.clone();
         let cmd = Box::pin(async move {
-            let ret = fs_notify(nrx, fs_btx).await;
+            let ret = fs_notify(nrx, notify_backend).await;
             if let Err(e) = ret {
                 println!("fs_noitify task failed {:?}", e);
             }
@@ -328,16 +328,20 @@ impl Backend {
 
         (backend_handle, join)
     }
-}
 
-impl BackendHandle {
     pub fn send_broadcast(&self, msg: BroadcastMessage)  -> Result<usize, SendError<BroadcastMessage>> {
-        let result = self.btx.send(msg);
-        if let Some(notify) = &self.backend.broadcast_notify.as_ref() {
+        let result = self.broadcast.send(msg);
+        if let Some(notify) = &self.broadcast_notify.as_ref() {
             notify();
         }
         result
     }
+}
+
+impl BackendHandle {
+    pub fn send_broadcast(&self, msg: BroadcastMessage)  -> Result<usize, SendError<BroadcastMessage>> {
+        self.backend.send_broadcast(msg)
+    }
     
     /// Reloads the notebook structure/collections. On success, it sends a NotebookStructureUpdate
     /// to the broadcast channel.
index c2297b564ccaef28a072bdcde0221a3c641c9840..2245399a6c5298e90bc7573e5abb47386fdc8cd2 100644 (file)
@@ -29,22 +29,23 @@ use std::collections::HashMap;
 use std::path::{Path, PathBuf};
 use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
 use tokio::sync::mpsc::{*};
-use crate::backend::BroadcastMessage;
+use crate::backend::{Backend, BroadcastMessage};
 use std::collections::hash_map::Entry;
+use std::sync::Arc;
 
 pub enum FSWatch {
     Add(PathBuf),
     Remove(PathBuf),
 }
 
-pub async fn fs_notify(mut rx: UnboundedReceiver<FSWatch>, btx: tokio::sync::broadcast::Sender<BroadcastMessage>) -> notify::Result<()> {
+pub async fn fs_notify(mut rx: UnboundedReceiver<FSWatch>, backend: Arc<Backend>) -> notify::Result<()> {
     let mut path_map = HashMap::<PathBuf, u32>::new();
 
     let mut watcher = RecommendedWatcher::new(
         move |res: Result<Event, notify::Error>| {
             match res {
                 Ok(event) => {
-                    _ = btx.send(BroadcastMessage::Notify(event));
+                    _ = backend.send_broadcast(BroadcastMessage::Notify(event));
                 },
                 Err(e) => {
                     println!("Notify Error: {:?}", e);
index c95d867f1b9dc63972fcf36aedbe0432dacd255f..4a7e28f42b378ef11ee4c606665298249b6c2647 100644 (file)
@@ -9,3 +9,4 @@ entity = { path = "../../entity" }
 ui-rs = { path = "../../ui-rs" }
 tokio = { version = "1.52.1", features = ["rt-multi-thread"] }
 sea-orm = { version = "2.0.0-rc", features = [ "sqlx-sqlite", "sqlx-postgres", "runtime-tokio-native-tls", "macros" ] }
+notify = "9.0.0-rc.4"
index aed51c2c2057ef4f640b2bee60f330a9612a40d2..7c6d9262cfc846971533d787c65642541e2d6fcd 100644 (file)
@@ -176,6 +176,7 @@ pub enum AppStates {
     Null = 0,
     NoteEnableNew,
     NoteShowInfo,
+    NoteShowExtModInfo,
     NoteMaximized
 }
 
index d1c627ebad9dd9d96507ab8399560f7e2ce7d0a9..dee819486f9487f860f898042875516a5e35da59 100644 (file)
@@ -38,6 +38,7 @@ use ui_rs::{action, doc_cast, ui_actions, UiModel};
 use ui_rs::ui::*;
 use crate::AppStates;
 use crate::window::NoteTypeTabView;
+use notify::{Event};
 
 static TMP_ID: AtomicU64 = AtomicU64::new(1);
 
@@ -67,6 +68,7 @@ pub struct Note {
     title_end: i32,
 
     modified: bool,
+    is_saving: bool,
 
     pub local_path: Option<PathBuf>,
     pub local_lastmodified: Option<SystemTime>,
@@ -100,6 +102,7 @@ impl Note {
             title_start: -1,
             title_end: -1,
             modified: false,
+            is_saving: false,
             local_path: None,
             local_lastmodified: None,
 
@@ -164,7 +167,7 @@ impl Note {
             return;
         };
 
-        if let Some(path) = &self.local_path && let Some(lastmodified) = self.local_lastmodified {
+        if let Some(path) = &self.local_path && self.local_lastmodified.is_some() {
             let proxy = doc.doc_proxy();
             self.backend.get_file_metadata(path.as_path(), |result| {
                 proxy.call_mainthread(|_doc, note| {
@@ -265,6 +268,34 @@ impl Note {
         }
     }
 
+    #[action(Event)]
+    pub fn filesystem_notify(&mut self, _event: &ActionEvent, event: &Event) {
+        // for now we only care about modify events
+        if !event.kind.is_modify() {
+            return;
+        }
+
+        // ignore when the note is currently saving
+        if self.is_saving {
+            return;
+        }
+
+        let Some(local_path) = &self.local_path else {
+            return;
+        };
+
+        // event is not for any file relevant for this note
+        if !event.paths.contains(local_path) {
+            return;
+        }
+
+        println!("note file modified");
+        let Some(doc) = self.doc.get_doc() else {
+            return;
+        };
+        doc.ctx.set_state(AppStates::NoteShowExtModInfo as i32);
+    }
+
     #[action]
     pub fn save(&mut self, _event: &ActionEvent) {
         self.save_note();
@@ -351,6 +382,20 @@ impl Note {
         obj.splitview_set_visible(0, event.intval == 0);
         doc.ctx.set_state(AppStates::NoteMaximized as i32);
     }
+
+    #[action]
+    pub fn note_reload(&mut self, _event: &mut ActionEvent) {
+        self.load_content();
+        self.note_extmod_close(_event);
+    }
+
+    #[action]
+    pub fn note_extmod_close(&mut self, _event: &mut ActionEvent) {
+        let Some(doc) = self.doc.get_doc() else {
+            return;
+        };
+        doc.ctx.unset_state(AppStates::NoteShowExtModInfo as i32);
+    }
 }
 
 impl NoteViewModel for Note {
@@ -391,9 +436,12 @@ impl NoteViewModel for Note {
             content: Set(content),
         };
 
+        self.is_saving = true;
+
         let proxy = doc.doc_proxy();
         self.backend.save_note(self.notebook_instance_id, self.id.clone(), note, Some(notecontent), |result|{
             proxy.call_mainthread(move |doc, note|{
+                note.is_saving = false;
                 match result {
                     SaveNoteResult::Ok(ret) => {
                         note.id = NoteId::Id(ret.model.note_id);
index 2fd1b3da6e2bd7e0d8b30e237203a665325ab600..b9b969a96f1716e211d6257a6ac17da50eff1c09 100644 (file)
@@ -346,11 +346,14 @@ impl Notebook {
                     }
                 },
                 BroadcastMessage::Notify(event) => {
-                    println!("fs notify event");
+                    if let Some(note) = &self.selected_note {
+                        let arg: Box<dyn Any> = Box::new(event);
+                        note.doc.ctx.call_action_with_parameter("filesystem_notify", arg);
+                    }
                 },
                 _ => {
 
-                },
+                }
             }
         }
     }
index 17af135460454841a68c9265030bb8829727d28b..6c48d819cfcc409a663e9d67259f70f8317165fb 100644 (file)
@@ -256,6 +256,24 @@ pub fn create_window(app: &App, ctx: &AppContext<MainWindow>) -> UiObject<MainWi
                             l.visibility_states(&[AppStates::NoteShowInfo as i32]);
                         });
 
+                        obj.hbox_builder()
+                            .visibility_states(&[AppStates::NoteShowExtModInfo as i32])
+                            .spacing(4)
+                            .create(|obj|{
+                            obj.label(|l|{
+                                l.label("The note has been modified by an external program");
+                                l.fill(true);
+                            });
+                            obj.button(|b|{
+                                b.label("Reload");
+                                b.action("note_reload");
+                            });
+                            obj.button(|b|{
+                                b.label("Close");
+                                b.action("note_extmod_close");
+                            });
+                        });
+
                         let textarea = obj.textarea(|b|{
                            b.fill(true);
                             b.varname("note_text");
index 261db104ceececb9c4af0fc735857546d00e8e06..e8f5c6c8b87a30caa1e408866dbf12d711ebff93 100644 (file)
@@ -310,6 +310,13 @@ impl<'a, T> ContainerBuilder<'a, T> {
         }
         self
     }
+
+    pub fn visibility_states(&mut self, states: &[i32]) -> &mut Self {
+        unsafe {
+            ui_container_args_set_visibility_states(self.args, states.as_ptr(), states.len() as c_int);
+        }
+        self
+    }
 }
 
 impl<'a, T> Drop for ContainerBuilder<'a, T> {