// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use crate::card::Card; use crate::card::CardId; use crate::card::CardQueue; use crate::card::CardType; use crate::card::FsrsMemoryState; 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::NoteId; use crate::prelude::TimestampSecs; use crate::prelude::Usn; impl crate::services::CardsService for Collection { fn get_card( &mut self, input: anki_proto::cards::CardId, ) -> error::Result { let cid = input.into(); self.storage .get_card(cid) .and_then(|opt| opt.or_not_found(cid)) .map(Into::into) } fn update_cards( &mut self, input: anki_proto::cards::UpdateCardsRequest, ) -> error::Result { let cards = input .cards .into_iter() .map(TryInto::try_into) .collect::, AnkiError>>()?; for card in &cards { card.validate_custom_data()?; } self.update_cards_maybe_undoable(cards, !input.skip_undo_entry) .map(Into::into) } fn remove_cards(&mut self, input: anki_proto::cards::RemoveCardsRequest) -> error::Result<()> { self.transact_no_undo(|col| { col.remove_cards_and_orphaned_notes( &input .card_ids .into_iter() .map(Into::into) .collect::>(), )?; Ok(()) }) } fn set_deck( &mut self, input: anki_proto::cards::SetDeckRequest, ) -> error::Result { let cids: Vec<_> = input.card_ids.into_iter().map(CardId).collect(); let deck_id = input.deck_id.into(); self.set_deck(&cids, deck_id).map(Into::into) } fn set_flag( &mut self, input: anki_proto::cards::SetFlagRequest, ) -> error::Result { self.set_card_flag(&to_card_ids(input.card_ids), input.flag) .map(Into::into) } } impl TryFrom for Card { type Error = AnkiError; fn try_from(c: anki_proto::cards::Card) -> error::Result { let ctype = CardType::try_from(c.ctype as u8).or_invalid("invalid card type")?; let queue = CardQueue::try_from(c.queue as i8).or_invalid("invalid card queue")?; Ok(Card { id: CardId(c.id), note_id: NoteId(c.note_id), deck_id: DeckId(c.deck_id), template_idx: c.template_idx as u16, mtime: TimestampSecs(c.mtime_secs), usn: Usn(c.usn), ctype, queue, due: c.due, interval: c.interval, ease_factor: c.ease_factor as u16, reps: c.reps, lapses: c.lapses, remaining_steps: c.remaining_steps, original_due: c.original_due, original_deck_id: DeckId(c.original_deck_id), flags: c.flags as u8, original_position: c.original_position, memory_state: c.memory_state.map(Into::into), custom_data: c.custom_data, }) } } impl From for anki_proto::cards::Card { fn from(c: Card) -> Self { anki_proto::cards::Card { id: c.id.0, note_id: c.note_id.0, deck_id: c.deck_id.0, template_idx: c.template_idx as u32, mtime_secs: c.mtime.0, usn: c.usn.0, ctype: c.ctype as u32, queue: c.queue as i32, due: c.due, interval: c.interval, ease_factor: c.ease_factor as u32, reps: c.reps, lapses: c.lapses, remaining_steps: c.remaining_steps, original_due: c.original_due, original_deck_id: c.original_deck_id.0, flags: c.flags as u32, original_position: c.original_position.map(Into::into), memory_state: c.memory_state.map(Into::into), custom_data: c.custom_data, } } } fn to_card_ids(v: Vec) -> Vec { v.into_iter().map(CardId).collect() } impl From for CardId { fn from(cid: anki_proto::cards::CardId) -> Self { CardId(cid.cid) } } impl From for FsrsMemoryState { fn from(value: anki_proto::cards::FsrsMemoryState) -> Self { FsrsMemoryState { stability: value.stability, difficulty: value.difficulty, } } } impl From for anki_proto::cards::FsrsMemoryState { fn from(value: FsrsMemoryState) -> Self { anki_proto::cards::FsrsMemoryState { stability: value.stability, difficulty: value.difficulty, } } }