From a8e84fa2c9326f1bb7a8c844a89504bd7e2829e3 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Sun, 14 Jun 2026 18:46:58 +0200 Subject: [PATCH] add note version check, when trying to open a note --- application/src/lockmanager.rs | 91 ++++++++++++++++++++++++++++++---- application/src/note.rs | 26 ++++++++-- 2 files changed, 105 insertions(+), 12 deletions(-) diff --git a/application/src/lockmanager.rs b/application/src/lockmanager.rs index 7afb736..9e7e4a9 100644 --- a/application/src/lockmanager.rs +++ b/application/src/lockmanager.rs @@ -27,17 +27,21 @@ */ 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>>, - revisions: Arc>>, + versions: Arc>>, } 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> { - self.revisions.lock().unwrap_or_else(|e| { + 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.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 { + 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 { @@ -91,8 +147,25 @@ impl Drop for NoteLock { } } -pub struct NoteRevision { - revisions: Arc>>, - pub note_id: i32, - pub revision: u64 + +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 5aea4cc..78c7c64 100644 --- a/application/src/note.rs +++ b/application/src/note.rs @@ -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, + pub vhandle: Option, 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"); -- 2.52.0