*/
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())),
}
}
})
}
- 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()
})
}
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(¬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<u64>,
+ version: i64
}
pub struct 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
+ }
}
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);
pub id: NoteId,
pub lock: Option<NoteLock>,
+ pub vhandle: Option<OpenNoteHandle>,
pub collection_id: i32,
pub content_id: i32,
id: id,
lock: None,
+ vhandle: None,
content_id: 0,
version: 0,
kind: NoteType::PlainTextNote,
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);
}
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");