]> uap-core.de Git - note.git/commitdiff
add note version check, when trying to open a note main
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 14 Jun 2026 16:46:58 +0000 (18:46 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 14 Jun 2026 16:46:58 +0000 (18:46 +0200)
application/src/lockmanager.rs
application/src/note.rs

index 7afb7365f6417848870e22714f32f3a029282166..9e7e4a9a322b829c8c554948764d7fa4d6931596 100644 (file)
  */
 use std::collections::{HashMap, HashSet};
 use std::sync::Arc;
+use std::sync::atomic::{AtomicU64, Ordering};
+
+
+static NHANDLE_ID: AtomicU64 = AtomicU64::new(1);
 
 pub struct LockManager {
     locks: Arc<std::sync::Mutex<HashSet<i32>>>,
-    revisions: Arc<std::sync::Mutex<HashMap<i32, u64>>>,
+    versions: Arc<std::sync::Mutex<HashMap<i32, NoteHandleList>>>,
 }
 
 impl LockManager {
     pub fn new() -> Self {
         LockManager {
             locks: Arc::new(std::sync::Mutex::new(HashSet::new())),
-            revisions: Arc::new(std::sync::Mutex::new(HashMap::new())),
+            versions: Arc::new(std::sync::Mutex::new(HashMap::new())),
         }
     }
 
@@ -50,11 +54,11 @@ impl LockManager {
         })
     }
 
-    fn revisions(&self) -> std::sync::MutexGuard<'_, HashMap<i32, u64>> {
-        self.revisions.lock().unwrap_or_else(|e| {
+    fn versions(&self) -> std::sync::MutexGuard<'_, HashMap<i32, NoteHandleList>> {
+        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.revisions.clear_poison();
+            self.versions.clear_poison();
             e.into_inner()
         })
     }
@@ -75,7 +79,59 @@ impl LockManager {
         Some(note_lock)
     }
 
+    pub fn try_open_note(&self, note_id: i32, version: i64) -> Option<OpenNoteHandle> {
+        let mut versions = self.versions();
+        let handles = versions.get_mut(&note_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(&note_id);
+        if let Some(hl) = handles {
+            hl.version = version;
+        }
+    }
+}
 
+struct NoteHandleList {
+    handles: Vec<u64>,
+    version: i64
 }
 
 pub struct NoteLock {
@@ -91,8 +147,25 @@ impl Drop for NoteLock {
     }
 }
 
-pub struct NoteRevision {
-    revisions: Arc<std::sync::Mutex<HashMap<i32, u64>>>,
-    pub note_id: i32,
-    pub revision: u64
+
+pub struct OpenNoteHandle {
+    versions: Arc<std::sync::Mutex<HashMap<i32, NoteHandleList>>>,
+    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
+    }
 }
index 5aea4cc196b44d3905e92b6a5b51d5eb4539e66e..78c7c64a2742b46a6185e6b509220e98bb9e0cfa 100644 (file)
@@ -34,7 +34,7 @@ use entity::note::NoteType;
 use ui_rs::{action, ui_actions, UiModel};
 use ui_rs::ui::*;
 use crate::backend::{BackendHandle, BroadcastMessage, NoteId, NoteTitleUpdate, SaveNoteResult};
-use crate::lockmanager::NoteLock;
+use crate::lockmanager::{NoteLock, OpenNoteHandle};
 use crate::window::NoteTypeTabView;
 
 static TMP_ID: AtomicU64 = AtomicU64::new(1);
@@ -46,6 +46,7 @@ pub struct Note {
 
     pub id: NoteId,
     pub lock: Option<NoteLock>,
+    pub vhandle: Option<OpenNoteHandle>,
 
     pub collection_id: i32,
     pub content_id: i32,
@@ -76,6 +77,7 @@ impl Note {
 
             id: id,
             lock: None,
+            vhandle: None,
             content_id: 0,
             version: 0,
             kind: NoteType::PlainTextNote,
@@ -110,15 +112,31 @@ impl Note {
         let doc = self.doc.get_doc()?;
 
         if doc.ctx.is_attached_to_obj() {
-
-
+            // attached to an obj also means, this note is visible and the editor should
+            // require the lock for this note
+            // self.lock should always be none here
+            debug_assert!(self.lock.is_none());
             if let NoteId::Id(note_id) = self.id {
+                // try to lock the note
                 self.lock = self.backend.backend.lockmgr.lock_note(note_id);
                 if self.lock.is_none() {
+                    // the note is already opened in another window
                     self.text.set_readonly(true);
                 }
+
+                // 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
             self.lock = None;
             self.text.set_readonly(false);
         }
@@ -244,6 +262,8 @@ impl Note {
                         note.content_id = content_id;
                         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);
                     },
                     SaveNoteResult::VersionConflict => {
                         println!("Failed to save note: version conflict");