coalesce note updates; avoid unnecessary saving due to mtime changes
This commit is contained in:
parent
ecea2161e3
commit
d4765a301f
@ -449,7 +449,7 @@ class Editor:
|
|||||||
self.note.fields[ord] = self.mungeHTML(txt)
|
self.note.fields[ord] = self.mungeHTML(txt)
|
||||||
|
|
||||||
if not self.addMode:
|
if not self.addMode:
|
||||||
self.note.flush()
|
self._save_current_note()
|
||||||
self.mw.requireReset(reason=ResetReason.EditorBridgeCmd, context=self)
|
self.mw.requireReset(reason=ResetReason.EditorBridgeCmd, context=self)
|
||||||
if type == "blur":
|
if type == "blur":
|
||||||
self.currentField = None
|
self.currentField = None
|
||||||
@ -542,6 +542,10 @@ class Editor:
|
|||||||
js = gui_hooks.editor_will_load_note(js, self.note, self)
|
js = gui_hooks.editor_will_load_note(js, self.note, self)
|
||||||
self.web.evalWithCallback(js, oncallback)
|
self.web.evalWithCallback(js, oncallback)
|
||||||
|
|
||||||
|
def _save_current_note(self) -> None:
|
||||||
|
"Call after note is updated with data from webview."
|
||||||
|
self.mw.col.update_note(self.note)
|
||||||
|
|
||||||
def fonts(self) -> List[Tuple[str, int, bool]]:
|
def fonts(self) -> List[Tuple[str, int, bool]]:
|
||||||
return [
|
return [
|
||||||
(gui_hooks.editor_will_use_font_for_field(f["font"]), f["size"], f["rtl"])
|
(gui_hooks.editor_will_use_font_for_field(f["font"]), f["size"], f["rtl"])
|
||||||
@ -633,7 +637,7 @@ class Editor:
|
|||||||
)
|
)
|
||||||
self.note.fields[field] = html
|
self.note.fields[field] = html
|
||||||
if not self.addMode:
|
if not self.addMode:
|
||||||
self.note.flush()
|
self._save_current_note()
|
||||||
self.loadNote(focusTo=field)
|
self.loadNote(focusTo=field)
|
||||||
saveGeom(d, "htmlEditor")
|
saveGeom(d, "htmlEditor")
|
||||||
|
|
||||||
@ -673,7 +677,7 @@ class Editor:
|
|||||||
return
|
return
|
||||||
self.note.tags = self.mw.col.tags.split(self.tags.text())
|
self.note.tags = self.mw.col.tags.split(self.tags.text())
|
||||||
if not self.addMode:
|
if not self.addMode:
|
||||||
self.note.flush()
|
self._save_current_note()
|
||||||
gui_hooks.editor_did_update_tags(self.note)
|
gui_hooks.editor_did_update_tags(self.note)
|
||||||
|
|
||||||
def saveAddModeVars(self) -> None:
|
def saveAddModeVars(self) -> None:
|
||||||
|
@ -334,7 +334,7 @@ impl Collection {
|
|||||||
op: Option<UndoableOpKind>,
|
op: Option<UndoableOpKind>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut existing_note = self.storage.get_note(note.id)?.ok_or(AnkiError::NotFound)?;
|
let mut existing_note = self.storage.get_note(note.id)?.ok_or(AnkiError::NotFound)?;
|
||||||
if !note_modified(&mut existing_note, note) {
|
if !note_differs_from_db(&mut existing_note, note) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -382,7 +382,7 @@ impl Collection {
|
|||||||
if mark_note_modified {
|
if mark_note_modified {
|
||||||
note.set_modified(usn);
|
note.set_modified(usn);
|
||||||
}
|
}
|
||||||
self.update_note_undoable(note, original)
|
self.update_note_undoable(note, original, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove provided notes, and any cards that use them.
|
/// Remove provided notes, and any cards that use them.
|
||||||
@ -537,9 +537,12 @@ impl Collection {
|
|||||||
/// The existing note pulled from the DB will have sfld and csum set, but the
|
/// The existing note pulled from the DB will have sfld and csum set, but the
|
||||||
/// note we receive from the frontend won't. Temporarily zero them out and
|
/// note we receive from the frontend won't. Temporarily zero them out and
|
||||||
/// compare, then restore them again.
|
/// compare, then restore them again.
|
||||||
fn note_modified(existing_note: &mut Note, note: &Note) -> bool {
|
/// Also set mtime to existing, since the frontend may have a stale mtime, and
|
||||||
|
/// we'll bump it as we save in any case.
|
||||||
|
fn note_differs_from_db(existing_note: &mut Note, note: &mut Note) -> bool {
|
||||||
let sort_field = existing_note.sort_field.take();
|
let sort_field = existing_note.sort_field.take();
|
||||||
let checksum = existing_note.checksum.take();
|
let checksum = existing_note.checksum.take();
|
||||||
|
note.mtime = existing_note.mtime;
|
||||||
let notes_differ = existing_note != note;
|
let notes_differ = existing_note != note;
|
||||||
existing_note.sort_field = sort_field;
|
existing_note.sort_field = sort_field;
|
||||||
existing_note.checksum = checksum;
|
existing_note.checksum = checksum;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, undo::UndoableChange};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum UndoableNoteChange {
|
pub(crate) enum UndoableNoteChange {
|
||||||
@ -16,12 +16,12 @@ impl Collection {
|
|||||||
pub(crate) fn undo_note_change(&mut self, change: UndoableNoteChange) -> Result<()> {
|
pub(crate) fn undo_note_change(&mut self, change: UndoableNoteChange) -> Result<()> {
|
||||||
match change {
|
match change {
|
||||||
UndoableNoteChange::Added(note) => self.remove_note_without_grave(*note),
|
UndoableNoteChange::Added(note) => self.remove_note_without_grave(*note),
|
||||||
UndoableNoteChange::Updated(mut note) => {
|
UndoableNoteChange::Updated(note) => {
|
||||||
let current = self
|
let current = self
|
||||||
.storage
|
.storage
|
||||||
.get_note(note.id)?
|
.get_note(note.id)?
|
||||||
.ok_or_else(|| AnkiError::invalid_input("note disappeared"))?;
|
.ok_or_else(|| AnkiError::invalid_input("note disappeared"))?;
|
||||||
self.update_note_undoable(&mut *note, ¤t)
|
self.update_note_undoable(¬e, ¤t, false)
|
||||||
}
|
}
|
||||||
UndoableNoteChange::Removed(note) => self.restore_deleted_note(*note),
|
UndoableNoteChange::Removed(note) => self.restore_deleted_note(*note),
|
||||||
UndoableNoteChange::GraveAdded(e) => self.remove_note_grave(e.0, e.1),
|
UndoableNoteChange::GraveAdded(e) => self.remove_note_grave(e.0, e.1),
|
||||||
@ -31,8 +31,17 @@ impl Collection {
|
|||||||
|
|
||||||
/// Saves in the undo queue, and commits to DB.
|
/// Saves in the undo queue, and commits to DB.
|
||||||
/// No validation, card generation or normalization is done.
|
/// No validation, card generation or normalization is done.
|
||||||
pub(super) fn update_note_undoable(&mut self, note: &mut Note, original: &Note) -> Result<()> {
|
/// If `coalesce_updates` is true, successive updates within a 1 minute
|
||||||
self.save_undo(UndoableNoteChange::Updated(Box::new(original.clone())));
|
/// period will not result in further undo entries.
|
||||||
|
pub(super) fn update_note_undoable(
|
||||||
|
&mut self,
|
||||||
|
note: &Note,
|
||||||
|
original: &Note,
|
||||||
|
coalesce_updates: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
if !coalesce_updates || !self.note_was_just_updated(note) {
|
||||||
|
self.save_undo(UndoableNoteChange::Updated(Box::new(original.clone())));
|
||||||
|
}
|
||||||
self.storage.update_note(note)?;
|
self.storage.update_note(note)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -77,4 +86,22 @@ impl Collection {
|
|||||||
self.save_undo(UndoableNoteChange::GraveRemoved(Box::new((nid, usn))));
|
self.save_undo(UndoableNoteChange::GraveRemoved(Box::new((nid, usn))));
|
||||||
self.storage.remove_note_grave(nid)
|
self.storage.remove_note_grave(nid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True only if the last operation was UpdateNote, and the same note was just updated less than
|
||||||
|
/// a minute ago.
|
||||||
|
fn note_was_just_updated(&self, before_change: &Note) -> bool {
|
||||||
|
self.previous_undo_op()
|
||||||
|
.map(|op| {
|
||||||
|
if let Some(UndoableChange::Note(UndoableNoteChange::Updated(note))) =
|
||||||
|
op.changes.last()
|
||||||
|
{
|
||||||
|
note.id == before_change.id
|
||||||
|
&& op.kind == UndoableOpKind::UpdateNote
|
||||||
|
&& op.timestamp.elapsed_secs() < 60
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,10 @@ use std::collections::VecDeque;
|
|||||||
const UNDO_LIMIT: usize = 30;
|
const UNDO_LIMIT: usize = 30;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct UndoableOp {
|
pub(crate) struct UndoableOp {
|
||||||
kind: UndoableOpKind,
|
pub kind: UndoableOpKind,
|
||||||
changes: Vec<UndoableChange>,
|
pub timestamp: TimestampSecs,
|
||||||
|
pub changes: Vec<UndoableChange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -61,17 +62,20 @@ impl UndoManager {
|
|||||||
}
|
}
|
||||||
self.current_step = op.map(|op| UndoableOp {
|
self.current_step = op.map(|op| UndoableOp {
|
||||||
kind: op,
|
kind: op,
|
||||||
|
timestamp: TimestampSecs::now(),
|
||||||
changes: vec![],
|
changes: vec![],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_step(&mut self) {
|
fn end_step(&mut self) {
|
||||||
if let Some(step) = self.current_step.take() {
|
if let Some(step) = self.current_step.take() {
|
||||||
if self.mode == UndoMode::Undoing {
|
if !step.changes.is_empty() {
|
||||||
self.redo_steps.push(step);
|
if self.mode == UndoMode::Undoing {
|
||||||
} else {
|
self.redo_steps.push(step);
|
||||||
self.undo_steps.truncate(UNDO_LIMIT - 1);
|
} else {
|
||||||
self.undo_steps.push_front(step);
|
self.undo_steps.truncate(UNDO_LIMIT - 1);
|
||||||
|
self.undo_steps.push_front(step);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("ended, undo steps count now {}", self.undo_steps.len());
|
println!("ended, undo steps count now {}", self.undo_steps.len());
|
||||||
@ -91,6 +95,10 @@ impl UndoManager {
|
|||||||
fn can_redo(&self) -> Option<UndoableOpKind> {
|
fn can_redo(&self) -> Option<UndoableOpKind> {
|
||||||
self.redo_steps.last().map(|s| s.kind)
|
self.redo_steps.last().map(|s| s.kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn previous_op(&self) -> Option<&UndoableOp> {
|
||||||
|
self.undo_steps.front()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
@ -170,6 +178,10 @@ impl Collection {
|
|||||||
pub(crate) fn save_undo(&mut self, item: impl Into<UndoableChange>) {
|
pub(crate) fn save_undo(&mut self, item: impl Into<UndoableChange>) {
|
||||||
self.state.undo.save(item.into());
|
self.state.undo.save(item.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn previous_undo_op(&self) -> Option<&UndoableOp> {
|
||||||
|
self.state.undo.previous_op()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
Loading…
Reference in New Issue
Block a user