use backend for genCards() and updateFieldCache()

This commit is contained in:
Damien Elmes 2020-05-06 13:14:57 +10:00
parent 05ca797ee6
commit a7a485d550
10 changed files with 132 additions and 134 deletions

View file

@ -92,6 +92,7 @@ message BackendInput {
Empty deck_tree_legacy = 77;
FieldNamesForNotesIn field_names_for_notes = 78;
FindAndReplaceIn find_and_replace = 79;
AfterNoteUpdatesIn after_note_updates = 80;
}
}
@ -163,6 +164,7 @@ message BackendOutput {
bytes deck_tree_legacy = 77;
FieldNamesForNotesOut field_names_for_notes = 78;
uint32 find_and_replace = 79;
Empty after_note_updates = 80;
BackendError error = 2047;
}
@ -732,3 +734,9 @@ message FindAndReplaceIn {
bool match_case = 5;
string field_name = 6;
}
message AfterNoteUpdatesIn {
repeated int64 nids = 1;
bool mark_notes_modified = 2;
bool generate_cards = 3;
}

View file

@ -14,7 +14,7 @@ import time
import traceback
import unicodedata
import weakref
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union
from typing import Any, Iterable, List, Optional, Sequence, Tuple, Union
import anki.find
import anki.latex # sets up hook
@ -34,16 +34,7 @@ from anki.rsbackend import TR, RustBackend
from anki.sched import Scheduler as V1Scheduler
from anki.schedv2 import Scheduler as V2Scheduler
from anki.tags import TagManager
from anki.utils import (
devMode,
fieldChecksum,
ids2str,
intTime,
joinFields,
maxID,
splitFields,
stripHTMLMedia,
)
from anki.utils import devMode, ids2str, intTime, joinFields
# this is initialized by storage.Collection
@ -359,81 +350,6 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?""",
ok.append(t)
return ok
def genCards(self, nids: List[int]) -> List[int]:
"Generate cards for non-empty templates, return ids to remove."
# build map of (nid,ord) so we don't create dupes
snids = ids2str(nids)
have: Dict[int, Dict[int, int]] = {}
dids: Dict[int, Optional[int]] = {}
dues: Dict[int, int] = {}
for id, nid, ord, did, due, odue, odid, type in self.db.execute(
"select id, nid, ord, did, due, odue, odid, type from cards where nid in "
+ snids
):
# existing cards
if nid not in have:
have[nid] = {}
have[nid][ord] = id
# if in a filtered deck, add new cards to original deck
if odid != 0:
did = odid
# and their dids
if nid in dids:
if dids[nid] and dids[nid] != did:
# cards are in two or more different decks; revert to
# model default
dids[nid] = None
else:
# first card or multiple cards in same deck
dids[nid] = did
# save due
if odid != 0:
due = odue
if nid not in dues and type == 0:
# Add due to new card only if it's the due of a new sibling
dues[nid] = due
# build cards for each note
data = []
ts = maxID(self.db)
now = intTime()
rem = []
usn = self.usn()
for nid, mid, flds in self.db.execute(
"select id, mid, flds from notes where id in " + snids
):
model = self.models.get(mid)
assert model
avail = self.models.availOrds(model, flds)
did = dids.get(nid) or model["did"]
due = dues.get(nid)
# add any missing cards
for t in self._tmplsFromOrds(model, avail):
doHave = nid in have and t["ord"] in have[nid]
if not doHave:
# check deck is not a cram deck
did = t["did"] or did
if self.decks.isDyn(did):
did = 1
# if the deck doesn't exist, use default instead
did = self.decks.get(did)["id"]
# use sibling due# if there is one, else use a new id
if due is None:
due = self.nextID("pos")
data.append((ts, nid, did, t["ord"], now, usn, due))
ts += 1
# note any cards that need removing
if nid in have:
for ord, id in list(have[nid].items()):
if ord not in avail:
rem.append(id)
# bulk update
self.db.executemany(
"""
insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
data,
)
return rem
# type is no longer used
def previewCards(
self, note: Note, type: int = 0, did: Optional[int] = None
@ -535,31 +451,24 @@ select id from notes where id in %s and id not in (select nid from cards)"""
print("emptyCids() will go away")
return []
# Field checksums and sorting fields
# Card generation & field checksums/sort fields
##########################################################################
def _fieldData(self, snids: str) -> Any:
return self.db.execute("select id, mid, flds from notes where id in " + snids)
def after_note_updates(self, nids: List[int], mark_modified: bool, generate_cards: bool = True) -> None:
self.backend.after_note_updates(
nids=nids, generate_cards=generate_cards, mark_notes_modified=mark_modified
)
# legacy
def updateFieldCache(self, nids: List[int]) -> None:
"Update field checksums and sort cache, after find&replace, etc."
snids = ids2str(nids)
r = []
for (nid, mid, flds) in self._fieldData(snids):
fields = splitFields(flds)
model = self.models.get(mid)
if not model:
# note points to invalid model
continue
r.append(
(
stripHTMLMedia(fields[self.models.sortIdx(model)]),
fieldChecksum(fields[0]),
nid,
)
)
# apply, relying on calling code to bump usn+mod
self.db.executemany("update notes set sfld=?, csum=? where id=?", r)
self.after_note_updates(nids, mark_modified=False, generate_cards=False)
# this also updates field cache
def genCards(self, nids: List[int]) -> List[int]:
self.after_note_updates(nids, mark_modified=False, generate_cards=True)
# previously returned empty cards, no longer does
return []
# Finding cards
##########################################################################
@ -909,7 +818,7 @@ select id from cards where odid > 0 and did in %s"""
self.tags.registerNotes()
# field cache
for m in self.models.all():
self.updateFieldCache(self.models.nids(m))
self.after_note_updates(self.models.nids(m), mark_modified=False, generate_cards=False)
# new cards can't have a due position > 32 bits, so wrap items over
# 2 million back to 1 million
self.db.execute(

View file

@ -197,11 +197,8 @@ class NoteImporter(Importer):
firsts[fld0] = True
self.addNew(new)
self.addUpdates(updates)
# make sure to update sflds, etc
self.col.updateFieldCache(self._ids)
# generate cards
if self.col.genCards(self._ids):
self.log.insert(0, _("Empty cards found. Please run Tools>Empty Cards."))
# generate cards + update field cache
self.col.after_note_updates(self._ids, mark_modified=False)
# apply scheduling updates
self.updateCards()
# we randomize or order here, to ensure that siblings

View file

@ -448,7 +448,7 @@ class ModelManager:
self._changeNotes(nids, newModel, fmap)
if cmap:
self._changeCards(nids, m, newModel, cmap)
self.col.genCards(nids)
self.col.after_note_updates(nids, mark_modified=True)
def _changeNotes(
self, nids: List[int], newModel: NoteType, map: Dict[int, Union[None, int]]
@ -470,7 +470,6 @@ class ModelManager:
self.col.db.executemany(
"update notes set flds=?,mid=?,mod=?,usn=? where id = ?", d
)
self.col.updateFieldCache(nids)
def _changeCards(
self,
@ -518,7 +517,7 @@ class ModelManager:
# Required field/text cache
##########################################################################
# fixme: genCards(), clayout, importing, cards.isEmpty
# fixme: clayout, importing, cards.isEmpty
def availOrds(self, m: NoteType, flds: str) -> List:
"Given a joined field string, return available template ordinals."
if m["type"] == MODEL_CLOZE:

View file

@ -778,6 +778,20 @@ class RustBackend:
release_gil=True,
).find_and_replace
def after_note_updates(
self, nids: List[int], generate_cards: bool, mark_notes_modified: bool
) -> None:
self._run_command(
pb.BackendInput(
after_note_updates=pb.AfterNoteUpdatesIn(
nids=nids,
generate_cards=generate_cards,
mark_notes_modified=mark_notes_modified,
)
),
release_gil=True
)
def translate_string_in(
key: TR, **kwargs: Union[str, int, float]

View file

@ -366,6 +366,9 @@ impl Backend {
OValue::FieldNamesForNotes(self.field_names_for_notes(input)?)
}
Value::FindAndReplace(input) => OValue::FindAndReplace(self.find_and_replace(input)?),
Value::AfterNoteUpdates(input) => {
OValue::AfterNoteUpdates(self.after_note_updates(input)?)
}
})
}
@ -1094,6 +1097,21 @@ impl Backend {
col.find_and_replace(FindReplaceContext::new(nids, &search, &repl, field_name)?)
})
}
fn after_note_updates(&self, input: pb::AfterNoteUpdatesIn) -> Result<pb::Empty> {
self.with_col(|col| {
col.transact(None, |col| {
let nids: Vec<_> = input.nids.into_iter().map(NoteID).collect();
col.after_note_updates(
&nids,
col.usn()?,
input.generate_cards,
input.mark_notes_modified,
)?;
Ok(pb::Empty {})
})
})
}
}
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {

View file

@ -77,7 +77,7 @@ impl Collection {
}
}
if changed {
self.update_note_inner(&genctx, &mut note)?;
self.update_note_inner_generating_cards(&genctx, &mut note, true)?;
total_changed += 1;
}
}

View file

@ -404,7 +404,8 @@ where
&self.mgr.media_folder,
)? {
// note was modified, needs saving
note.prepare_for_update(nt, Some(usn))?;
note.prepare_for_update(nt)?;
note.set_modified(usn);
self.ctx.storage.update_note(&note)?;
collection_modified = true;
}

View file

@ -12,6 +12,7 @@ use crate::{
timestamp::TimestampSecs,
types::Usn,
};
use itertools::Itertools;
use num_integer::Integer;
use std::{collections::HashSet, convert::TryInto};
@ -63,8 +64,8 @@ impl Note {
Ok(())
}
/// Prepare note for saving to the database. If usn is provided, mtime will be bumped.
pub fn prepare_for_update(&mut self, nt: &NoteType, usn: Option<Usn>) -> Result<()> {
/// Prepare note for saving to the database. Does not mark it as modified.
pub fn prepare_for_update(&mut self, nt: &NoteType) -> Result<()> {
assert!(nt.id == self.ntid);
if nt.fields.len() != self.fields.len() {
return Err(AnkiError::invalid_input(format!(
@ -88,12 +89,13 @@ impl Note {
};
self.sort_field = Some(sort_field.into());
self.checksum = Some(checksum);
if let Some(usn) = usn {
Ok(())
}
pub(crate) fn set_modified(&mut self, usn: Usn) {
self.mtime = TimestampSecs::now();
self.usn = usn;
}
Ok(())
}
pub(crate) fn nonempty_fields<'a>(&self, fields: &'a [NoteField]) -> HashSet<&'a str> {
self.fields
@ -193,7 +195,8 @@ impl Collection {
did: DeckID,
) -> Result<()> {
self.canonify_note_tags(note, ctx.usn)?;
note.prepare_for_update(&ctx.notetype, Some(ctx.usn))?;
note.prepare_for_update(&ctx.notetype)?;
note.set_modified(ctx.usn);
self.storage.add_note(note)?;
self.generate_cards_for_new_note(ctx, note, did)
}
@ -204,21 +207,33 @@ impl Collection {
.get_notetype(note.ntid)?
.ok_or_else(|| AnkiError::invalid_input("missing note type"))?;
let ctx = CardGenContext::new(&nt, col.usn()?);
col.update_note_inner(&ctx, note)
col.update_note_inner_generating_cards(&ctx, note, true)
})
}
pub(crate) fn update_note_inner(
pub(crate) fn update_note_inner_generating_cards(
&mut self,
ctx: &CardGenContext,
note: &mut Note,
mark_note_modified: bool,
) -> Result<()> {
self.canonify_note_tags(note, ctx.usn)?;
note.prepare_for_update(ctx.notetype, Some(ctx.usn))?;
self.generate_cards_for_existing_note(ctx, note)?;
self.storage.update_note(note)?;
self.update_note_inner_without_cards(note, ctx.notetype, ctx.usn, mark_note_modified)?;
self.generate_cards_for_existing_note(ctx, note)
}
Ok(())
pub(crate) fn update_note_inner_without_cards(
&mut self,
note: &mut Note,
nt: &NoteType,
usn: Usn,
mark_note_modified: bool,
) -> Result<()> {
self.canonify_note_tags(note, usn)?;
note.prepare_for_update(nt)?;
if mark_note_modified {
note.set_modified(usn);
}
self.storage.update_note(note)
}
/// Remove a note. Cards must already have been deleted.
@ -230,6 +245,42 @@ impl Collection {
}
Ok(())
}
/// Update cards and field cache after notes modified externally.
/// If gencards is false, skip card generation.
pub(crate) fn after_note_updates(
&mut self,
nids: &[NoteID],
usn: Usn,
generate_cards: bool,
mark_notes_modified: bool,
) -> Result<()> {
let nids_by_notetype = self.storage.note_ids_by_notetype(nids)?;
for (ntid, group) in &nids_by_notetype.into_iter().group_by(|tup| tup.0) {
let nt = self
.get_notetype(ntid)?
.ok_or_else(|| AnkiError::invalid_input("missing note type"))?;
let genctx = CardGenContext::new(&nt, usn);
for (_, nid) in group {
let mut note = self.storage.get_note(nid)?.unwrap();
if generate_cards {
self.update_note_inner_generating_cards(
&genctx,
&mut note,
mark_notes_modified,
)?;
} else {
self.update_note_inner_without_cards(
&mut note,
&genctx.notetype,
usn,
mark_notes_modified,
)?;
}
}
}
Ok(())
}
}
#[cfg(test)]

View file

@ -63,7 +63,7 @@ impl Collection {
let nids = self.search_notes_only(&format!("mid:{}", nt.id))?;
for nid in nids {
let mut note = self.storage.get_note(nid)?.unwrap();
note.prepare_for_update(nt, None)?;
note.prepare_for_update(nt)?;
self.storage.update_note(&note)?;
}
} else {
@ -92,7 +92,8 @@ impl Collection {
})
.map(Into::into)
.collect();
note.prepare_for_update(nt, Some(usn))?;
note.prepare_for_update(nt)?;
note.set_modified(usn);
self.storage.update_note(&note)?;
}
Ok(())