Add a backend method to add notes in bulk (#2659)

* Add a backend method to add notes in bulk

* i -> idx

* Remove duplicate assignment

* Allow add_notes to work with multiple deck IDs

* Rename note_deck_id to requests
This commit is contained in:
Abdo 2023-09-16 06:51:32 +03:00 committed by GitHub
parent 8b0d663fd0
commit 6f0bf58d49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 22 deletions

View File

@ -15,6 +15,7 @@ import "anki/cards.proto";
service NotesService {
rpc NewNote(notetypes.NotetypeId) returns (Note);
rpc AddNote(AddNoteRequest) returns (AddNoteResponse);
rpc AddNotes(AddNotesRequest) returns (AddNotesResponse);
rpc DefaultsForAdding(DefaultsForAddingRequest) returns (DeckAndNotetype);
rpc DefaultDeckForNotetype(notetypes.NotetypeId) returns (decks.DeckId);
rpc UpdateNotes(UpdateNotesRequest) returns (collection.OpChanges);
@ -62,6 +63,15 @@ message AddNoteResponse {
int64 note_id = 2;
}
message AddNotesRequest {
repeated AddNoteRequest requests = 1;
}
message AddNotesResponse {
collection.OpChanges changes = 1;
repeated int64 nids = 2;
}
message UpdateNotesRequest {
repeated Note notes = 1;
bool skip_undo_entry = 2;

View File

@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Any, Generator, Literal, Sequence, Union, cast
from typing import Any, Generator, Iterable, Literal, Sequence, Union, cast
from anki import (
ankiweb_pb2,
@ -14,6 +14,7 @@ from anki import (
image_occlusion_pb2,
import_export_pb2,
links_pb2,
notes_pb2,
search_pb2,
stats_pb2,
sync_pb2,
@ -127,6 +128,12 @@ class CardIdsLimit:
ExportLimit = Union[DeckIdLimit, NoteIdsLimit, CardIdsLimit, None]
@dataclass
class AddNoteRequest:
note: Note
deck_id: DeckId
class Collection(DeprecatedNamesMixin):
sched: V1Scheduler | V2Scheduler | V3Scheduler
@ -580,6 +587,22 @@ class Collection(DeprecatedNamesMixin):
note.id = NoteId(out.note_id)
return out.changes
def add_notes(self, requests: Iterable[AddNoteRequest]) -> OpChanges:
for request in requests:
hooks.note_will_be_added(self, request.note, request.deck_id)
out = self._backend.add_notes(
requests=[
notes_pb2.AddNoteRequest(
note=request.note._to_backend_note(), deck_id=request.deck_id
)
for request in requests
]
)
for idx, request in enumerate(requests):
request.note.id = NoteId(out.nids[idx])
return out.changes
def remove_notes(self, note_ids: Sequence[NoteId]) -> OpChangesWithCount:
hooks.notes_will_be_deleted(self, note_ids)
return self._backend.remove_notes(note_ids=note_ids, card_ids=[])

View File

@ -13,7 +13,6 @@ use anki_proto::image_occlusion::GetImageOcclusionNoteResponse;
use regex::Regex;
use crate::media::MediaManager;
use crate::notetype::CardGenContext;
use crate::prelude::*;
impl Collection {
@ -66,11 +65,7 @@ impl Collection {
note.set_field(2, header)?;
note.set_field(3, back_extra)?;
note.tags = tags;
let last_deck = col.get_last_deck_added_to_for_notetype(note.notetype_id);
let ctx = CardGenContext::new(nt.as_ref(), last_deck, col.usn()?);
let norm = col.get_config_bool(BoolKey::NormalizeNoteText);
col.add_note_inner(&ctx, &mut note, current_deck.id, norm)?;
col.add_note_inner(&mut note, current_deck.id)?;
Ok(())
})

View File

@ -15,6 +15,9 @@ use sha1::Sha1;
use crate::cloze::contains_cloze;
use crate::define_newtype;
use crate::error;
use crate::error::AnkiError;
use crate::error::OrInvalid;
use crate::notetype::CardGenContext;
use crate::notetype::NoteField;
use crate::ops::StateChanges;
@ -66,16 +69,35 @@ impl Note {
}
}
#[derive(Debug, Clone)]
pub struct AddNoteRequest {
pub note: Note,
pub deck_id: DeckId,
}
impl TryFrom<anki_proto::notes::AddNoteRequest> for AddNoteRequest {
type Error = AnkiError;
fn try_from(request: anki_proto::notes::AddNoteRequest) -> error::Result<Self, Self::Error> {
Ok(Self {
note: request.note.or_invalid("no note provided")?.into(),
deck_id: DeckId(request.deck_id),
})
}
}
impl Collection {
pub fn add_note(&mut self, note: &mut Note, did: DeckId) -> Result<OpOutput<()>> {
self.transact(Op::AddNote, |col| col.add_note_inner(note, did))
}
pub fn add_notes(&mut self, requests: &mut [AddNoteRequest]) -> Result<OpOutput<()>> {
self.transact(Op::AddNote, |col| {
let nt = col
.get_notetype(note.notetype_id)?
.or_invalid("missing note type")?;
let last_deck = col.get_last_deck_added_to_for_notetype(note.notetype_id);
let ctx = CardGenContext::new(nt.as_ref(), last_deck, col.usn()?);
let norm = col.get_config_bool(BoolKey::NormalizeNoteText);
col.add_note_inner(&ctx, note, did, norm)
for request in requests {
col.add_note_inner(&mut request.note, request.deck_id)?;
}
Ok(())
})
}
@ -333,18 +355,18 @@ impl Collection {
Ok(())
}
pub(crate) fn add_note_inner(
&mut self,
ctx: &CardGenContext<&Notetype>,
note: &mut Note,
did: DeckId,
normalize_text: bool,
) -> Result<()> {
pub(crate) fn add_note_inner(&mut self, note: &mut Note, did: DeckId) -> Result<()> {
let nt = self
.get_notetype(note.notetype_id)?
.or_invalid("missing note type")?;
let last_deck = self.get_last_deck_added_to_for_notetype(note.notetype_id);
let ctx = CardGenContext::new(nt.as_ref(), last_deck, self.usn()?);
let normalize_text = self.get_config_bool(BoolKey::NormalizeNoteText);
self.canonify_note_tags(note, ctx.usn)?;
note.prepare_for_update(ctx.notetype, normalize_text)?;
note.set_modified(ctx.usn);
self.add_note_only_undoable(note)?;
self.generate_cards_for_new_note(ctx, note, did)?;
self.generate_cards_for_new_note(&ctx, note, did)?;
self.set_last_deck_for_notetype(note.notetype_id, did)?;
self.set_last_notetype_for_deck(did, note.notetype_id)?;
self.set_current_notetype_id(note.notetype_id)

View File

@ -6,8 +6,10 @@ use crate::cloze::add_cloze_numbers_in_string;
use crate::collection::Collection;
use crate::decks::DeckId;
use crate::error;
use crate::error::AnkiError;
use crate::error::OrInvalid;
use crate::error::OrNotFound;
use crate::notes::AddNoteRequest;
use crate::notes::Note;
use crate::notes::NoteId;
use crate::prelude::IntoNewtypeVec;
@ -39,6 +41,22 @@ impl crate::services::NotesService for Collection {
})
}
fn add_notes(
&mut self,
input: anki_proto::notes::AddNotesRequest,
) -> error::Result<anki_proto::notes::AddNotesResponse> {
let mut requests = input
.requests
.into_iter()
.map(TryInto::try_into)
.collect::<error::Result<Vec<AddNoteRequest>, AnkiError>>()?;
let changes = self.add_notes(&mut requests)?;
Ok(anki_proto::notes::AddNotesResponse {
nids: requests.iter().map(|r| r.note.id.0).collect(),
changes: Some(changes.into()),
})
}
fn defaults_for_adding(
&mut self,
input: anki_proto::notes::DefaultsForAddingRequest,