mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 08:46:37 -04:00
start on exposing notes and individual note type methods
changes to note: - add_note() now takes a provided deck id instead of looking it up in the notetype - note type use counts fetched using a single table scan - make sure note type changes are persisted - expose optionalness of ords in templates and fields json
This commit is contained in:
parent
09db596009
commit
6cc2bdbf87
10 changed files with 346 additions and 65 deletions
|
@ -69,11 +69,20 @@ message BackendInput {
|
|||
SetConfigJson set_config_json = 53;
|
||||
bytes set_all_config = 54;
|
||||
Empty get_all_config = 55;
|
||||
Empty get_all_notetypes = 56;
|
||||
bytes set_all_notetypes = 57;
|
||||
int32 get_changed_notetypes = 56;
|
||||
AddOrUpdateNotetypeIn add_or_update_notetype = 57;
|
||||
Empty get_all_decks = 58;
|
||||
bytes set_all_decks = 59;
|
||||
Empty all_stock_notetypes = 60;
|
||||
int64 get_notetype_legacy = 61;
|
||||
Empty get_notetype_names = 62;
|
||||
Empty get_notetype_names_and_counts = 63;
|
||||
string get_notetype_id_by_name = 64;
|
||||
int64 remove_notetype = 65;
|
||||
int64 new_note = 66;
|
||||
AddNoteIn add_note = 67;
|
||||
Note update_note = 68;
|
||||
int64 get_note = 69;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,10 +132,19 @@ message BackendOutput {
|
|||
Empty set_config_json = 53;
|
||||
Empty set_all_config = 54;
|
||||
bytes get_all_config = 55;
|
||||
bytes get_all_notetypes = 56;
|
||||
Empty set_all_notetypes = 57;
|
||||
bytes get_changed_notetypes = 56;
|
||||
int64 add_or_update_notetype = 57;
|
||||
bytes get_all_decks = 58;
|
||||
Empty set_all_decks = 59;
|
||||
bytes get_notetype_legacy = 61;
|
||||
NoteTypeNames get_notetype_names = 62;
|
||||
NoteTypeUseCounts get_notetype_names_and_counts = 63;
|
||||
int64 get_notetype_id_by_name = 64;
|
||||
Empty remove_notetype = 65;
|
||||
Note new_note = 66;
|
||||
int64 add_note = 67;
|
||||
Empty update_note = 68;
|
||||
Note get_note = 69;
|
||||
|
||||
BackendError error = 2047;
|
||||
}
|
||||
|
@ -147,6 +165,7 @@ message BackendError {
|
|||
Empty interrupted = 8;
|
||||
string json_error = 9;
|
||||
string proto_error = 10;
|
||||
Empty not_found_error = 11;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -569,3 +588,43 @@ enum StockNoteType {
|
|||
message AllStockNotetypesOut {
|
||||
repeated NoteType notetypes = 1;
|
||||
}
|
||||
|
||||
message NoteTypeNames {
|
||||
repeated NoteTypeNameID entries = 1;
|
||||
}
|
||||
|
||||
message NoteTypeUseCounts {
|
||||
repeated NoteTypeNameIDUseCount entries = 1;
|
||||
}
|
||||
|
||||
message NoteTypeNameID {
|
||||
int64 id = 1;
|
||||
string name = 2;
|
||||
|
||||
}
|
||||
|
||||
message NoteTypeNameIDUseCount {
|
||||
int64 id = 1;
|
||||
string name = 2;
|
||||
uint32 use_count = 3;
|
||||
}
|
||||
|
||||
message AddOrUpdateNotetypeIn {
|
||||
bytes json = 1;
|
||||
bool preserve_usn_and_mtime = 2;
|
||||
}
|
||||
|
||||
message Note {
|
||||
int64 id = 1;
|
||||
string guid = 2;
|
||||
int64 ntid = 3;
|
||||
uint32 mtime_secs = 4;
|
||||
int32 usn = 5;
|
||||
repeated string tags = 6;
|
||||
repeated string fields = 7;
|
||||
}
|
||||
|
||||
message AddNoteIn {
|
||||
Note note = 1;
|
||||
int64 deck_id = 2;
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ use crate::{
|
|||
media::check::MediaChecker,
|
||||
media::sync::MediaSyncProgress,
|
||||
media::MediaManager,
|
||||
notes::NoteID,
|
||||
notetype::{all_stock_notetypes, NoteTypeID, NoteTypeSchema11},
|
||||
notes::{Note, NoteID},
|
||||
notetype::{all_stock_notetypes, NoteType, NoteTypeID, NoteTypeSchema11},
|
||||
sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today},
|
||||
sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span},
|
||||
search::SortMode,
|
||||
|
@ -77,6 +77,7 @@ fn anki_error_to_proto_error(err: AnkiError, i18n: &I18n) -> pb::BackendError {
|
|||
AnkiError::CollectionAlreadyOpen => V::InvalidInput(pb::Empty {}),
|
||||
AnkiError::JSONError { info } => V::JsonError(info),
|
||||
AnkiError::ProtoError { info } => V::ProtoError(info),
|
||||
AnkiError::NotFound => V::NotFoundError(Empty {}),
|
||||
};
|
||||
|
||||
pb::BackendError {
|
||||
|
@ -304,10 +305,8 @@ impl Backend {
|
|||
pb::Empty {}
|
||||
}),
|
||||
Value::GetAllConfig(_) => OValue::GetAllConfig(self.get_all_config()?),
|
||||
Value::GetAllNotetypes(_) => OValue::GetAllNotetypes(self.get_all_notetypes()?),
|
||||
Value::SetAllNotetypes(bytes) => {
|
||||
self.set_all_notetypes(&bytes)?;
|
||||
OValue::SetAllNotetypes(pb::Empty {})
|
||||
Value::GetChangedNotetypes(_) => {
|
||||
OValue::GetChangedNotetypes(self.get_changed_notetypes()?)
|
||||
}
|
||||
Value::GetAllDecks(_) => OValue::GetAllDecks(self.get_all_decks()?),
|
||||
Value::SetAllDecks(bytes) => {
|
||||
|
@ -320,6 +319,31 @@ impl Backend {
|
|||
.map(Into::into)
|
||||
.collect(),
|
||||
}),
|
||||
Value::GetNotetypeLegacy(id) => {
|
||||
OValue::GetNotetypeLegacy(self.get_notetype_legacy(id)?)
|
||||
}
|
||||
Value::GetNotetypeNames(_) => OValue::GetNotetypeNames(self.get_notetype_names()?),
|
||||
Value::GetNotetypeNamesAndCounts(_) => {
|
||||
OValue::GetNotetypeNamesAndCounts(self.get_notetype_use_counts()?)
|
||||
}
|
||||
|
||||
Value::GetNotetypeIdByName(name) => {
|
||||
OValue::GetNotetypeIdByName(self.get_notetype_id_by_name(name)?)
|
||||
}
|
||||
Value::AddOrUpdateNotetype(input) => {
|
||||
OValue::AddOrUpdateNotetype(self.add_or_update_notetype_legacy(input)?)
|
||||
}
|
||||
Value::RemoveNotetype(id) => {
|
||||
self.remove_notetype(id)?;
|
||||
OValue::RemoveNotetype(pb::Empty {})
|
||||
}
|
||||
Value::NewNote(ntid) => OValue::NewNote(self.new_note(ntid)?),
|
||||
Value::AddNote(input) => OValue::AddNote(self.add_note(input)?),
|
||||
Value::UpdateNote(note) => {
|
||||
self.update_note(note)?;
|
||||
OValue::UpdateNote(pb::Empty {})
|
||||
}
|
||||
Value::GetNote(nid) => OValue::GetNote(self.get_note(nid)?),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -856,21 +880,12 @@ impl Backend {
|
|||
})
|
||||
}
|
||||
|
||||
fn set_all_notetypes(&self, json: &[u8]) -> Result<()> {
|
||||
let val: HashMap<NoteTypeID, NoteTypeSchema11> = serde_json::from_slice(json)?;
|
||||
self.with_col(|col| {
|
||||
col.transact(None, |col| {
|
||||
col.storage.set_schema11_notetypes(val)?;
|
||||
col.storage.upgrade_notetypes_to_schema15()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_all_notetypes(&self) -> Result<Vec<u8>> {
|
||||
self.with_col(|col| {
|
||||
let nts = col.storage.get_all_notetypes_as_schema11()?;
|
||||
serde_json::to_vec(&nts).map_err(Into::into)
|
||||
})
|
||||
fn get_changed_notetypes(&self) -> Result<Vec<u8>> {
|
||||
todo!("filter by usn");
|
||||
// self.with_col(|col| {
|
||||
// let nts = col.storage.get_all_notetypes_as_schema11()?;
|
||||
// serde_json::to_vec(&nts).map_err(Into::into)
|
||||
// })
|
||||
}
|
||||
|
||||
fn set_all_decks(&self, json: &[u8]) -> Result<()> {
|
||||
|
@ -884,6 +899,104 @@ impl Backend {
|
|||
serde_json::to_vec(&decks).map_err(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_notetype_names(&self) -> Result<pb::NoteTypeNames> {
|
||||
self.with_col(|col| {
|
||||
let entries: Vec<_> = col
|
||||
.storage
|
||||
.get_all_notetype_names()?
|
||||
.into_iter()
|
||||
.map(|(id, name)| pb::NoteTypeNameId { id: id.0, name })
|
||||
.collect();
|
||||
Ok(pb::NoteTypeNames { entries })
|
||||
})
|
||||
}
|
||||
|
||||
fn get_notetype_use_counts(&self) -> Result<pb::NoteTypeUseCounts> {
|
||||
self.with_col(|col| {
|
||||
let entries: Vec<_> = col
|
||||
.storage
|
||||
.get_notetype_use_counts()?
|
||||
.into_iter()
|
||||
.map(|(id, name, use_count)| pb::NoteTypeNameIdUseCount {
|
||||
id: id.0,
|
||||
name,
|
||||
use_count,
|
||||
})
|
||||
.collect();
|
||||
Ok(pb::NoteTypeUseCounts { entries })
|
||||
})
|
||||
}
|
||||
|
||||
fn get_notetype_legacy(&self, id: i64) -> Result<Vec<u8>> {
|
||||
self.with_col(|col| {
|
||||
let schema11: NoteTypeSchema11 = col
|
||||
.storage
|
||||
.get_notetype(NoteTypeID(id))?
|
||||
.ok_or(AnkiError::NotFound)?
|
||||
.into();
|
||||
Ok(serde_json::to_vec(&schema11)?)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_notetype_id_by_name(&self, name: String) -> Result<i64> {
|
||||
self.with_col(|col| {
|
||||
col.storage
|
||||
.get_notetype_id(&name)
|
||||
.map(|nt| nt.unwrap_or(NoteTypeID(0)).0)
|
||||
})
|
||||
}
|
||||
|
||||
fn add_or_update_notetype_legacy(&self, input: pb::AddOrUpdateNotetypeIn) -> Result<i64> {
|
||||
self.with_col(|col| {
|
||||
let legacy: NoteTypeSchema11 = serde_json::from_slice(&input.json)?;
|
||||
let mut nt: NoteType = legacy.into();
|
||||
if nt.id.0 == 0 {
|
||||
col.add_notetype(&mut nt)?;
|
||||
} else {
|
||||
col.update_notetype(&mut nt, input.preserve_usn_and_mtime)?;
|
||||
}
|
||||
Ok(nt.id.0)
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_notetype(&self, _id: i64) -> Result<()> {
|
||||
println!("fixme: remove notetype");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_note(&self, ntid: i64) -> Result<pb::Note> {
|
||||
self.with_col(|col| {
|
||||
let nt = col
|
||||
.get_notetype(NoteTypeID(ntid))?
|
||||
.ok_or(AnkiError::NotFound)?;
|
||||
Ok(nt.new_note().into())
|
||||
})
|
||||
}
|
||||
|
||||
fn add_note(&self, input: pb::AddNoteIn) -> Result<i64> {
|
||||
self.with_col(|col| {
|
||||
let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into();
|
||||
col.add_note(&mut note, DeckID(input.deck_id))
|
||||
.map(|_| note.id.0)
|
||||
})
|
||||
}
|
||||
|
||||
fn update_note(&self, pbnote: pb::Note) -> Result<()> {
|
||||
self.with_col(|col| {
|
||||
let mut note: Note = pbnote.into();
|
||||
col.update_note(&mut note)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_note(&self, nid: i64) -> Result<pb::Note> {
|
||||
self.with_col(|col| {
|
||||
col.storage
|
||||
.get_note(NoteID(nid))?
|
||||
.ok_or(AnkiError::NotFound)
|
||||
.map(Into::into)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
|
||||
|
|
|
@ -45,6 +45,9 @@ pub enum AnkiError {
|
|||
|
||||
#[fail(display = "Close the existing collection first.")]
|
||||
CollectionAlreadyOpen,
|
||||
|
||||
#[fail(display = "A requested item was not found.")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
// error helpers
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use crate::{
|
||||
backend_proto as pb,
|
||||
collection::Collection,
|
||||
decks::DeckID,
|
||||
define_newtype,
|
||||
err::{AnkiError, Result},
|
||||
notetype::{CardGenContext, NoteField, NoteType, NoteTypeID},
|
||||
|
@ -105,6 +107,36 @@ impl Note {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Note> for pb::Note {
|
||||
fn from(n: Note) -> Self {
|
||||
pb::Note {
|
||||
id: n.id.0,
|
||||
guid: n.guid,
|
||||
ntid: n.ntid.0,
|
||||
mtime_secs: n.mtime.0 as u32,
|
||||
usn: n.usn.0,
|
||||
tags: n.tags,
|
||||
fields: n.fields,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pb::Note> for Note {
|
||||
fn from(n: pb::Note) -> Self {
|
||||
Note {
|
||||
id: NoteID(n.id),
|
||||
guid: n.guid,
|
||||
ntid: NoteTypeID(n.ntid),
|
||||
mtime: TimestampSecs(n.mtime_secs as i64),
|
||||
usn: Usn(n.usn),
|
||||
tags: n.tags,
|
||||
fields: n.fields,
|
||||
sort_field: None,
|
||||
checksum: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Text must be passed to strip_html_preserving_image_filenames() by
|
||||
/// caller prior to passing in here.
|
||||
pub(crate) fn field_checksum(text: &str) -> u32 {
|
||||
|
@ -130,20 +162,37 @@ fn anki_base91(mut n: u64) -> String {
|
|||
}
|
||||
|
||||
impl Collection {
|
||||
pub fn add_note(&mut self, note: &mut Note) -> Result<()> {
|
||||
fn canonify_note_tags(&self, note: &mut Note, usn: Usn) -> Result<()> {
|
||||
// fixme: avoid the excess split/join
|
||||
note.tags = self
|
||||
.canonify_tags(¬e.tags.join(" "), usn)?
|
||||
.0
|
||||
.split(' ')
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_note(&mut self, note: &mut Note, did: DeckID) -> Result<()> {
|
||||
self.transact(None, |col| {
|
||||
let nt = col
|
||||
.get_notetype(note.ntid)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("missing note type"))?;
|
||||
let ctx = CardGenContext::new(&nt, col.usn()?);
|
||||
col.add_note_inner(&ctx, note)
|
||||
col.add_note_inner(&ctx, note, did)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn add_note_inner(&mut self, ctx: &CardGenContext, note: &mut Note) -> Result<()> {
|
||||
pub(crate) fn add_note_inner(
|
||||
&mut self,
|
||||
ctx: &CardGenContext,
|
||||
note: &mut Note,
|
||||
did: DeckID,
|
||||
) -> Result<()> {
|
||||
self.canonify_note_tags(note, ctx.usn)?;
|
||||
note.prepare_for_update(&ctx.notetype, ctx.usn)?;
|
||||
self.storage.add_note(note)?;
|
||||
self.generate_cards_for_new_note(ctx, note)
|
||||
self.generate_cards_for_new_note(ctx, note, did)
|
||||
}
|
||||
|
||||
pub fn update_note(&mut self, note: &mut Note) -> Result<()> {
|
||||
|
@ -161,6 +210,7 @@ impl Collection {
|
|||
ctx: &CardGenContext,
|
||||
note: &mut Note,
|
||||
) -> Result<()> {
|
||||
self.canonify_note_tags(note, ctx.usn)?;
|
||||
note.prepare_for_update(ctx.notetype, ctx.usn)?;
|
||||
self.generate_cards_for_existing_note(ctx, note)?;
|
||||
self.storage.update_note(note)?;
|
||||
|
@ -198,7 +248,7 @@ mod test {
|
|||
|
||||
let mut note = nt.new_note();
|
||||
// if no cards are generated, 1 card is added
|
||||
col.add_note(&mut note).unwrap();
|
||||
col.add_note(&mut note, DeckID(1)).unwrap();
|
||||
let existing = col.storage.existing_cards_for_note(note.id)?;
|
||||
assert_eq!(existing.len(), 1);
|
||||
assert_eq!(existing[0].ord, 0);
|
||||
|
@ -220,7 +270,7 @@ mod test {
|
|||
// cloze cards also generate card 0 if no clozes are found
|
||||
let nt = col.get_notetype_by_name("cloze")?.unwrap();
|
||||
let mut note = nt.new_note();
|
||||
col.add_note(&mut note).unwrap();
|
||||
col.add_note(&mut note, DeckID(1)).unwrap();
|
||||
let existing = col.storage.existing_cards_for_note(note.id)?;
|
||||
assert_eq!(existing.len(), 1);
|
||||
assert_eq!(existing[0].ord, 0);
|
||||
|
|
|
@ -204,8 +204,9 @@ impl Collection {
|
|||
&mut self,
|
||||
ctx: &CardGenContext,
|
||||
note: &Note,
|
||||
target_deck_id: DeckID,
|
||||
) -> Result<()> {
|
||||
self.generate_cards_for_note(ctx, note, false)
|
||||
self.generate_cards_for_note(ctx, note, &[], Some(target_deck_id))
|
||||
}
|
||||
|
||||
pub(crate) fn generate_cards_for_existing_note(
|
||||
|
@ -213,25 +214,22 @@ impl Collection {
|
|||
ctx: &CardGenContext,
|
||||
note: &Note,
|
||||
) -> Result<()> {
|
||||
self.generate_cards_for_note(ctx, note, true)
|
||||
let existing = self.storage.existing_cards_for_note(note.id)?;
|
||||
self.generate_cards_for_note(ctx, note, &existing, None)
|
||||
}
|
||||
|
||||
fn generate_cards_for_note(
|
||||
&mut self,
|
||||
ctx: &CardGenContext,
|
||||
note: &Note,
|
||||
check_existing: bool,
|
||||
existing: &[AlreadyGeneratedCardInfo],
|
||||
target_deck_id: Option<DeckID>,
|
||||
) -> Result<()> {
|
||||
let existing = if check_existing {
|
||||
self.storage.existing_cards_for_note(note.id)?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let cards = ctx.new_cards_required(note, &existing);
|
||||
if cards.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.add_generated_cards(ctx, note.id, &cards)
|
||||
self.add_generated_cards(ctx, note.id, &cards, target_deck_id)
|
||||
}
|
||||
|
||||
pub(crate) fn generate_cards_for_notetype(&mut self, ctx: &CardGenContext) -> Result<()> {
|
||||
|
@ -246,7 +244,7 @@ impl Collection {
|
|||
continue;
|
||||
}
|
||||
let note = self.storage.get_note(nid)?.unwrap();
|
||||
self.generate_cards_for_note(ctx, ¬e, true)?;
|
||||
self.generate_cards_for_note(ctx, ¬e, &existing_cards, None)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -257,11 +255,16 @@ impl Collection {
|
|||
ctx: &CardGenContext,
|
||||
nid: NoteID,
|
||||
cards: &[CardToGenerate],
|
||||
target_deck_id: Option<DeckID>,
|
||||
) -> Result<()> {
|
||||
let mut next_pos = None;
|
||||
for c in cards {
|
||||
// fixme: deal with case where invalid deck pointed to
|
||||
let did = c.did.unwrap_or_else(|| ctx.notetype.target_deck_id());
|
||||
// fixme: deprecated note type deck id
|
||||
let did = c
|
||||
.did
|
||||
.or(target_deck_id)
|
||||
.unwrap_or_else(|| ctx.notetype.target_deck_id());
|
||||
let due = c.due.unwrap_or_else(|| {
|
||||
if next_pos.is_none() {
|
||||
next_pos = Some(self.get_and_update_next_card_position().unwrap_or(0));
|
||||
|
|
|
@ -168,16 +168,23 @@ impl NoteType {
|
|||
|
||||
pub(crate) fn prepare_for_adding(&mut self) -> Result<()> {
|
||||
// defaults to 0
|
||||
if self.config.target_deck_id == 0 {
|
||||
self.config.target_deck_id = 1;
|
||||
}
|
||||
if self.fields.is_empty() {
|
||||
return Err(AnkiError::invalid_input("1 field required"));
|
||||
}
|
||||
if self.templates.is_empty() {
|
||||
return Err(AnkiError::invalid_input("1 template required"));
|
||||
}
|
||||
self.prepare_for_update()
|
||||
}
|
||||
|
||||
pub(crate) fn prepare_for_update(&mut self) -> Result<()> {
|
||||
self.normalize_names();
|
||||
self.ensure_names_unique();
|
||||
self.update_requirements();
|
||||
// fixme: deal with duplicate note type names on update
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -205,15 +212,34 @@ impl From<NoteType> for NoteTypeProto {
|
|||
}
|
||||
|
||||
impl Collection {
|
||||
/// Add a new notetype, and allocate it an ID.
|
||||
pub fn add_notetype(&mut self, nt: &mut NoteType) -> Result<()> {
|
||||
nt.prepare_for_adding()?;
|
||||
self.transact(None, |col| col.storage.add_new_notetype(nt))
|
||||
}
|
||||
|
||||
/// Saves changes to a note type. This will force a full sync if templates
|
||||
/// or fields have been added/removed/reordered.
|
||||
pub fn update_notetype(&mut self, nt: &mut NoteType) -> Result<()> {
|
||||
pub fn update_notetype(&mut self, nt: &mut NoteType, preserve_usn: bool) -> Result<()> {
|
||||
nt.prepare_for_update()?;
|
||||
if !preserve_usn {
|
||||
nt.mtime_secs = TimestampSecs::now();
|
||||
nt.usn = self.usn()?;
|
||||
}
|
||||
self.transact(None, |col| {
|
||||
if !preserve_usn {
|
||||
let existing_notetype = col
|
||||
.get_notetype(nt.id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("no such notetype"))?;
|
||||
col.update_notes_for_changed_fields(nt, existing_notetype.fields.len())?;
|
||||
col.update_cards_for_changed_templates(nt, existing_notetype.templates.len())?;
|
||||
}
|
||||
|
||||
col.storage.update_notetype_config(&nt)?;
|
||||
col.storage.update_notetype_fields(nt.id, &nt.fields)?;
|
||||
col.storage
|
||||
.update_notetype_templates(nt.id, &nt.templates)?;
|
||||
|
||||
// fixme: update cache instead of clearing
|
||||
col.state.notetype_cache.remove(&nt.id);
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ impl From<CardRequirement> for CardRequirementSchema11 {
|
|||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct NoteFieldSchema11 {
|
||||
pub(crate) name: String,
|
||||
pub(crate) ord: u16,
|
||||
pub(crate) ord: Option<u16>,
|
||||
#[serde(deserialize_with = "deserialize_bool_from_anything")]
|
||||
pub(crate) sticky: bool,
|
||||
#[serde(deserialize_with = "deserialize_bool_from_anything")]
|
||||
|
@ -210,7 +210,7 @@ impl Default for NoteFieldSchema11 {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::new(),
|
||||
ord: 0,
|
||||
ord: None,
|
||||
sticky: false,
|
||||
rtl: false,
|
||||
font: "Arial".to_string(),
|
||||
|
@ -223,7 +223,7 @@ impl Default for NoteFieldSchema11 {
|
|||
impl From<NoteFieldSchema11> for NoteField {
|
||||
fn from(f: NoteFieldSchema11) -> Self {
|
||||
NoteField {
|
||||
ord: Some(f.ord as u32),
|
||||
ord: f.ord.map(|o| o as u32),
|
||||
name: f.name,
|
||||
config: NoteFieldConfig {
|
||||
sticky: f.sticky,
|
||||
|
@ -243,7 +243,7 @@ impl From<NoteField> for NoteFieldSchema11 {
|
|||
let conf = p.config;
|
||||
NoteFieldSchema11 {
|
||||
name: p.name,
|
||||
ord: p.ord.unwrap() as u16,
|
||||
ord: p.ord.map(|o| o as u16),
|
||||
sticky: conf.sticky,
|
||||
rtl: conf.rtl,
|
||||
font: conf.font_name,
|
||||
|
@ -256,7 +256,7 @@ impl From<NoteField> for NoteFieldSchema11 {
|
|||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct CardTemplateSchema11 {
|
||||
pub(crate) name: String,
|
||||
pub(crate) ord: u16,
|
||||
pub(crate) ord: Option<u16>,
|
||||
pub(crate) qfmt: String,
|
||||
#[serde(default)]
|
||||
pub(crate) afmt: String,
|
||||
|
@ -277,7 +277,7 @@ pub struct CardTemplateSchema11 {
|
|||
impl From<CardTemplateSchema11> for CardTemplate {
|
||||
fn from(t: CardTemplateSchema11) -> Self {
|
||||
CardTemplate {
|
||||
ord: Some(t.ord as u32),
|
||||
ord: t.ord.map(|t| t as u32),
|
||||
name: t.name,
|
||||
mtime_secs: TimestampSecs(0),
|
||||
usn: Usn(0),
|
||||
|
@ -302,7 +302,7 @@ impl From<CardTemplate> for CardTemplateSchema11 {
|
|||
let conf = p.config;
|
||||
CardTemplateSchema11 {
|
||||
name: p.name,
|
||||
ord: p.ord.unwrap() as u16,
|
||||
ord: p.ord.map(|o| o as u16),
|
||||
qfmt: conf.q_format,
|
||||
afmt: conf.a_format,
|
||||
bqfmt: conf.q_format_browser,
|
||||
|
|
|
@ -123,7 +123,7 @@ impl Collection {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{ords_changed, TemplateOrdChanges};
|
||||
use crate::{collection::open_test_collection, err::Result, search::SortMode};
|
||||
use crate::{collection::open_test_collection, decks::DeckID, err::Result, search::SortMode};
|
||||
|
||||
#[test]
|
||||
fn ord_changes() {
|
||||
|
@ -190,10 +190,10 @@ mod test {
|
|||
let mut note = nt.new_note();
|
||||
assert_eq!(note.fields.len(), 2);
|
||||
note.fields = vec!["one".into(), "two".into()];
|
||||
col.add_note(&mut note)?;
|
||||
col.add_note(&mut note, DeckID(1))?;
|
||||
|
||||
nt.add_field("three");
|
||||
col.update_notetype(&mut nt)?;
|
||||
col.update_notetype(&mut nt, false)?;
|
||||
|
||||
let note = col.storage.get_note(note.id)?.unwrap();
|
||||
assert_eq!(
|
||||
|
@ -202,7 +202,7 @@ mod test {
|
|||
);
|
||||
|
||||
nt.fields.remove(1);
|
||||
col.update_notetype(&mut nt)?;
|
||||
col.update_notetype(&mut nt, false)?;
|
||||
|
||||
let note = col.storage.get_note(note.id)?.unwrap();
|
||||
assert_eq!(note.fields, vec!["one".to_string(), "".into()]);
|
||||
|
@ -220,7 +220,7 @@ mod test {
|
|||
let mut note = nt.new_note();
|
||||
assert_eq!(note.fields.len(), 2);
|
||||
note.fields = vec!["one".into(), "two".into()];
|
||||
col.add_note(&mut note)?;
|
||||
col.add_note(&mut note, DeckID(1))?;
|
||||
|
||||
assert_eq!(
|
||||
col.search_cards(&format!("nid:{}", note.id), SortMode::NoOrder)
|
||||
|
@ -231,7 +231,7 @@ mod test {
|
|||
|
||||
// add an extra card template
|
||||
nt.add_template("card 2", "{{Front}}", "");
|
||||
col.update_notetype(&mut nt)?;
|
||||
col.update_notetype(&mut nt, false)?;
|
||||
|
||||
assert_eq!(
|
||||
col.search_cards(&format!("nid:{}", note.id), SortMode::NoOrder)
|
||||
|
|
15
rslib/src/storage/notetype/get_use_counts.sql
Normal file
15
rslib/src/storage/notetype/get_use_counts.sql
Normal file
|
@ -0,0 +1,15 @@
|
|||
select
|
||||
mid,
|
||||
(
|
||||
select
|
||||
name
|
||||
from notetypes nt
|
||||
where
|
||||
nt.id = mid
|
||||
) as name,
|
||||
count(id)
|
||||
from notes
|
||||
group by
|
||||
mid
|
||||
order by
|
||||
name
|
|
@ -105,7 +105,19 @@ impl SqliteStorage {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn update_notetype_fields(&self, ntid: NoteTypeID, fields: &[NoteField]) -> Result<()> {
|
||||
/// Returns list of (id, name, use_count)
|
||||
pub fn get_notetype_use_counts(&self) -> Result<Vec<(NoteTypeID, String, u32)>> {
|
||||
self.db
|
||||
.prepare_cached(include_str!("get_use_counts.sql"))?
|
||||
.query_and_then(NO_PARAMS, |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn update_notetype_fields(
|
||||
&self,
|
||||
ntid: NoteTypeID,
|
||||
fields: &[NoteField],
|
||||
) -> Result<()> {
|
||||
self.db
|
||||
.prepare_cached("delete from fields where ntid=?")?
|
||||
.execute(&[ntid])?;
|
||||
|
@ -119,7 +131,7 @@ impl SqliteStorage {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn update_notetype_templates(
|
||||
pub(crate) fn update_notetype_templates(
|
||||
&self,
|
||||
ntid: NoteTypeID,
|
||||
templates: &[CardTemplate],
|
||||
|
@ -146,7 +158,7 @@ impl SqliteStorage {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn update_notetype_config(&self, nt: &NoteType) -> Result<()> {
|
||||
pub(crate) fn update_notetype_config(&self, nt: &NoteType) -> Result<()> {
|
||||
assert!(nt.id.0 != 0);
|
||||
let mut stmt = self
|
||||
.db
|
||||
|
|
Loading…
Reference in a new issue