From: Olaf Wintermann Date: Tue, 16 Jun 2026 20:28:46 +0000 (+0200) Subject: remove OpenNoteHandle, share note updates via broadcast X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=refs%2Fheads%2Fmain;p=note.git remove OpenNoteHandle, share note updates via broadcast --- diff --git a/application/src/backend.rs b/application/src/backend.rs index ce601ec..da08f47 100644 --- a/application/src/backend.rs +++ b/application/src/backend.rs @@ -80,7 +80,7 @@ impl Clone for BackendHandle { pub enum BroadcastMessage { NotebookStructureUpdate(Vec), NoteTitleUpdate(NoteTitleUpdate), - InsertNote(NoteUpdate) + NoteUpdate(NoteUpdate), } #[derive(Clone, PartialEq, Eq)] @@ -110,6 +110,7 @@ pub struct NoteTitleUpdate { #[derive(Clone)] pub struct NoteUpdate { + pub from: u64, pub id: NoteId, pub model: note::Model } @@ -381,7 +382,7 @@ impl BackendHandle { let _ = self.tx.send(cmd); } - pub fn save_note(&self, id: NoteId, note: note::ActiveModel, content: Option, callback: F) + pub fn save_note(&self, initiator: u64, id: NoteId, note: note::ActiveModel, content: Option, callback: F) where F: FnOnce(SaveNoteResult) + Send + 'static { let bhandle = self.clone(); let cmd = Box::pin(async move { @@ -461,10 +462,11 @@ impl BackendHandle { if result.is_ok() { // broadcast new note let update = NoteUpdate { + from: initiator, id: id.clone(), model: note }; - _ = bhandle.send_broadcast(BroadcastMessage::InsertNote(update)); + _ = bhandle.send_broadcast(BroadcastMessage::NoteUpdate(update)); } callback(result); }, diff --git a/application/src/lockmanager.rs b/application/src/lockmanager.rs index 9e7e4a9..7746664 100644 --- a/application/src/lockmanager.rs +++ b/application/src/lockmanager.rs @@ -25,23 +25,18 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -use std::collections::{HashMap, HashSet}; +use std::collections::{HashSet}; use std::sync::Arc; -use std::sync::atomic::{AtomicU64, Ordering}; -static NHANDLE_ID: AtomicU64 = AtomicU64::new(1); - pub struct LockManager { locks: Arc>>, - versions: Arc>>, } impl LockManager { pub fn new() -> Self { LockManager { locks: Arc::new(std::sync::Mutex::new(HashSet::new())), - versions: Arc::new(std::sync::Mutex::new(HashMap::new())), } } @@ -54,15 +49,6 @@ impl LockManager { }) } - fn versions(&self) -> std::sync::MutexGuard<'_, HashMap> { - self.versions.lock().unwrap_or_else(|e| { - // Very unlikely that the mutex gets poisoned (probably only by OOM), and if it does, who cares - println!("Error: LockManager revisions poisoned"); - self.versions.clear_poison(); - e.into_inner() - }) - } - pub fn lock_note(&self, note_id: i32) -> Option { let mut locks = self.locks(); if locks.contains(¬e_id) { @@ -78,61 +64,8 @@ impl LockManager { }; Some(note_lock) } - - pub fn try_open_note(&self, note_id: i32, version: i64) -> Option { - let mut versions = self.versions(); - let handles = versions.get_mut(¬e_id); - match handles { - Some(hl) => { - // find the highest version in all open handles for this note - if hl.version > version { - // version is outdated - return None; - } else if version > hl.version { - hl.version = version; - } - // ok - let id = NHANDLE_ID.fetch_add(1, Ordering::Relaxed); - let handle = OpenNoteHandle { - versions: self.versions.clone(), - note_id: note_id, - handle_id: id - }; - hl.handles.push(id); - Some(handle) - }, - None => { - let mut v = Vec::new(); - let id = NHANDLE_ID.fetch_add(1, Ordering::Relaxed); - let handle = OpenNoteHandle { - versions: self.versions.clone(), - note_id: note_id, - handle_id: id - }; - v.push(id); - let hl = NoteHandleList { - version: version, - handles: v - }; - versions.insert(note_id, hl); - Some(handle) - } - } - } - - pub fn update_note_version(&self, note_id: i32, version: i64) { - let mut versions = self.versions(); - let handles = versions.get_mut(¬e_id); - if let Some(hl) = handles { - hl.version = version; - } - } } -struct NoteHandleList { - handles: Vec, - version: i64 -} pub struct NoteLock { locks: Arc>>, @@ -147,25 +80,3 @@ impl Drop for NoteLock { } } - -pub struct OpenNoteHandle { - versions: Arc>>, - note_id: i32, - handle_id: u64, -} - -impl Drop for OpenNoteHandle { - fn drop(&mut self) { - let mut versions = self.versions.lock().unwrap_or_else(|e|{ e.into_inner()}); - let handles = versions.get_mut(&self.note_id); - if let Some(hl) = handles { - // find this OpenNote id in the handle list - if let Some(pos) = hl.handles.iter().position(|n| *n == self.handle_id) { - hl.handles.remove(pos); - } - if hl.handles.len() == 0 { - versions.remove(&self.note_id); - } - } // else: should not happen - } -} diff --git a/application/src/note.rs b/application/src/note.rs index 51aa015..4ed00bd 100644 --- a/application/src/note.rs +++ b/application/src/note.rs @@ -35,7 +35,7 @@ use ui_rs::{action, ui_actions, UiModel}; use ui_rs::ui::*; use crate::AppStates; use crate::backend::{BackendHandle, BroadcastMessage, NoteId, NoteTitleUpdate, SaveNoteResult}; -use crate::lockmanager::{NoteLock, OpenNoteHandle}; +use crate::lockmanager::{NoteLock}; use crate::window::NoteTypeTabView; static TMP_ID: AtomicU64 = AtomicU64::new(1); @@ -44,10 +44,10 @@ static TMP_ID: AtomicU64 = AtomicU64::new(1); pub struct Note { pub doc: UiDocRef, pub backend: Rc, + pub notebook_instance_id: u64, pub id: NoteId, pub lock: Option, - pub vhandle: Option, pub collection_id: i32, pub content_id: i32, @@ -73,15 +73,15 @@ pub struct Note { #[ui_actions] impl Note { - pub fn new(id: NoteId, collection_id: i32, backend: Rc) -> Self { + pub fn new(notebook_instance_id: u64, id: NoteId, collection_id: i32, backend: Rc) -> Self { Note { doc: Default::default(), - backend: backend, - collection_id: collection_id, + backend, + notebook_instance_id, + collection_id, - id: id, + id, lock: None, - vhandle: None, content_id: 0, version: 0, kind: NoteType::PlainTextNote, @@ -104,7 +104,7 @@ impl Note { d.ctx.suppress_state(AppStates::NoteEnableNew as i32); } else { n.init_from_model(model); - n.load_content(d); + n.load_content(); } // make sure we receive an event, when this note is attached or detached d.ctx.on_attachment_status_change_action("attachment_status_changed"); @@ -133,17 +133,6 @@ impl Note { } else { doc.ctx.unset_state(AppStates::NoteShowInfo as i32); } - - // also try to get an OpenNoteHandle, which checks the current note version - let h = self.backend.backend.lockmgr.try_open_note(note_id, self.version); - if h.is_some() { - // ok - self.vhandle = h; - } else { - println!("note: {} version {} is outdated", note_id, self.version); - - // TOOD: reload note - } } } else { // clean self.lock, however don't clean self.vhandle @@ -175,11 +164,15 @@ impl Note { self.update_title(content.content.as_str(), false); } - pub fn load_content(&mut self, doc: &UiDoc) { + pub fn load_content(&mut self) { let NoteId::Id(note_id) = self.id else { return; }; + let Some(doc) = self.doc.get_doc() else { + return; + }; + let proxy = doc.doc_proxy(); self.backend.get_note_content(note_id, move|result|{ proxy.call_mainthread(move |_doc, note|{ @@ -264,7 +257,7 @@ impl Note { }; let proxy = doc.doc_proxy(); - self.backend.save_note(self.id.clone(), note, Some(notecontent), |result|{ + self.backend.save_note(self.notebook_instance_id, self.id.clone(), note, Some(notecontent), |result|{ proxy.call_mainthread(move |doc, note|{ match result { SaveNoteResult::Ok((notemodel, content_id)) => { @@ -273,7 +266,6 @@ impl Note { note.version = notemodel.version; println!("Note saved: note_id: {}, content_id: {}, version: {}", notemodel.note_id, content_id, note.version); - note.backend.backend.lockmgr.update_note_version(notemodel.note_id, note.version); // make sure the "new note" button is always enabled within this note doc.ctx.unsuppress_state(AppStates::NoteEnableNew as i32); }, @@ -290,6 +282,18 @@ impl Note { self.modified = false; } + #[action(u64)] + pub fn changed_by(&mut self, _event: &ActionEvent, _from: &u64) { + // The from id is currently unused, however it may be important when it is possible + // to bind multiple text views to the same text buffer. Then this textview already + // has the updated version, because it shares the text buffer with the save initiator. + // The from value can probably be used to check if we share the buffer with the + // save initiator or if this change came from outside. + println!("note changed by {}", _from); + + self.load_content(); + } + /// Event before the text is changed: detect if the change affects the title #[action] pub fn note_text_change(&mut self, event: &ActionEvent) { diff --git a/application/src/notebook.rs b/application/src/notebook.rs index 8a9f36e..9c5ca4b 100644 --- a/application/src/notebook.rs +++ b/application/src/notebook.rs @@ -28,6 +28,7 @@ use std::any::Any; use std::rc::Rc; +use std::sync::atomic::AtomicU64; use ui_rs::{action, ui_actions, UiModel}; use ui_rs::ui::*; @@ -36,11 +37,16 @@ use crate::backend::{BackendHandle, BroadcastMessage, NoteId, NoteTitleUpdate, N use crate::note::new_note_id; use crate::window::NavigationItem; +/// Each Notebook view model gets a unique instance id, that is mostly used to identify, +/// who initiated a broadcast message. +static INSTANCE_ID: AtomicU64 = AtomicU64::new(1); + /// UI model for a notebook #[derive(UiModel)] pub struct Notebook { pub doc_ref: UiDocRef, pub backend: Rc, + pub instance_id: u64, pub broadcast_rx: tokio::sync::broadcast::Receiver, pub collection_id: i32, @@ -56,6 +62,7 @@ impl Notebook { Notebook { doc_ref: Default::default(), backend: Rc::new(backend.clone()), + instance_id: INSTANCE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed), broadcast_rx: backend.btx.subscribe(), collection_id: id, selected_note: None, @@ -151,7 +158,7 @@ impl Notebook { doc.clone() } else { // Create the new note - let note_data = crate::note::Note::new(note.id.clone(), self.collection_id, self.backend.clone()); + let note_data = crate::note::Note::new(self.instance_id, note.id.clone(), self.collection_id, self.backend.clone()); note_data.into_doc(¬e.data) }; note.model = Some(doc.clone()); @@ -259,12 +266,28 @@ impl Notebook { } } - pub fn insert_note(&mut self, update: NoteUpdate) { + pub fn update_note(&mut self, update: NoteUpdate) { if let Some(index) = self.find_note(&update.id) { // update existing item if let Some(elm) = self.notes.data_mut().get_mut(index) { elm.data = update.model; elm.id = NoteId::Id(elm.data.note_id); // id no longer temporary + + if update.from != self.instance_id { + // The note was updated from another view model + // If this note was already opened, it needs a refresh + if let Some(selection) = &self.selected_note && selection.index == index { + // The note is also currently attached + // Send a message to the note, that it needs an update + let arg = Box::new(update.from); + selection.doc.ctx.call_action_with_parameter("changed_by", arg); + } else { + // Note view model is not attached, we can just discard it and the next + // time the note is opened, it is recreated and the content reloaded + elm.model = None; + } + } + self.notes.update_row(index); } } else { @@ -289,9 +312,9 @@ impl Notebook { self.update_note_title(&update); } }, - BroadcastMessage::InsertNote(update) => { + BroadcastMessage::NoteUpdate(update) => { if update.model.collection_id == self.collection_id { - self.insert_note(update); + self.update_note(update); } }, _ => {