pub enum BroadcastMessage {
NotebookStructureUpdate(Vec<Node>),
NoteTitleUpdate(NoteTitleUpdate),
- InsertNote(NoteUpdate)
+ NoteUpdate(NoteUpdate),
}
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone)]
pub struct NoteUpdate {
+ pub from: u64,
pub id: NoteId,
pub model: note::Model
}
let _ = self.tx.send(cmd);
}
- pub fn save_note<F>(&self, id: NoteId, note: note::ActiveModel, content: Option<notecontent::ActiveModel>, callback: F)
+ pub fn save_note<F>(&self, initiator: u64, id: NoteId, note: note::ActiveModel, content: Option<notecontent::ActiveModel>, callback: F)
where F: FnOnce(SaveNoteResult) + Send + 'static {
let bhandle = self.clone();
let cmd = Box::pin(async move {
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);
},
* 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<std::sync::Mutex<HashSet<i32>>>,
- versions: Arc<std::sync::Mutex<HashMap<i32, NoteHandleList>>>,
}
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())),
}
}
})
}
- 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.versions.clear_poison();
- e.into_inner()
- })
- }
-
pub fn lock_note(&self, note_id: i32) -> Option<NoteLock> {
let mut locks = self.locks();
if locks.contains(¬e_id) {
};
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 {
locks: Arc<std::sync::Mutex<HashSet<i32>>>,
}
}
-
-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::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);
pub struct Note {
pub doc: UiDocRef<Note>,
pub backend: Rc<BackendHandle>,
+ pub notebook_instance_id: u64,
pub id: NoteId,
pub lock: Option<NoteLock>,
- pub vhandle: Option<OpenNoteHandle>,
pub collection_id: i32,
pub content_id: i32,
#[ui_actions]
impl Note {
- pub fn new(id: NoteId, collection_id: i32, backend: Rc<BackendHandle>) -> Self {
+ pub fn new(notebook_instance_id: u64, id: NoteId, collection_id: i32, backend: Rc<BackendHandle>) -> 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,
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");
} 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
self.update_title(content.content.as_str(), false);
}
- pub fn load_content(&mut self, doc: &UiDoc<Note>) {
+ 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|{
};
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)) => {
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);
},
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) {
use std::any::Any;
use std::rc::Rc;
+use std::sync::atomic::AtomicU64;
use ui_rs::{action, ui_actions, UiModel};
use ui_rs::ui::*;
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<Notebook>,
pub backend: Rc<BackendHandle>,
+ pub instance_id: u64,
pub broadcast_rx: tokio::sync::broadcast::Receiver<BroadcastMessage>,
pub collection_id: i32,
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,
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());
}
}
- 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 {
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);
}
},
_ => {