use backend to get card

This commit is contained in:
Damien Elmes 2020-03-26 17:47:43 +10:00
parent 8abba00496
commit 7ddaf93f7a
9 changed files with 222 additions and 22 deletions

View file

@ -47,6 +47,7 @@ message BackendInput {
Empty restore_trash = 35; Empty restore_trash = 35;
OpenCollectionIn open_collection = 36; OpenCollectionIn open_collection = 36;
Empty close_collection = 37; Empty close_collection = 37;
int64 get_card = 38;
} }
} }
@ -77,6 +78,7 @@ message BackendOutput {
Empty restore_trash = 35; Empty restore_trash = 35;
Empty open_collection = 36; Empty open_collection = 36;
Empty close_collection = 37; Empty close_collection = 37;
GetCardOut get_card = 38;
BackendError error = 2047; BackendError error = 2047;
} }
@ -367,3 +369,28 @@ enum BuiltinSortKind {
CARD_DECK = 11; CARD_DECK = 11;
CARD_TEMPLATE = 12; CARD_TEMPLATE = 12;
} }
message GetCardOut {
Card card = 1;
}
message Card {
int64 id = 1;
int64 nid = 2;
int64 did = 3;
uint32 ord = 4;
int64 mtime = 5;
sint32 usn = 6;
uint32 ctype = 7;
sint32 queue = 8;
int64 due = 9;
int64 ivl = 10;
uint32 factor = 11;
int64 reps = 12;
int64 lapses = 13;
int64 left = 14;
int64 odue = 15;
int64 odid = 16;
int64 flags = 17;
string data = 18;
}

View file

@ -34,7 +34,7 @@ class Card:
ord: int ord: int
def __init__( def __init__(
self, col: anki.collection._Collection, id: Optional[int] = None self, col: anki.storage._Collection, id: Optional[int] = None
) -> None: ) -> None:
self.col = col.weakref() self.col = col.weakref()
self.timerStarted = None self.timerStarted = None
@ -61,28 +61,27 @@ class Card:
self.data = "" self.data = ""
def load(self) -> None: def load(self) -> None:
(
self.id,
self.nid,
self.did,
self.ord,
self.mod,
self.usn,
self.type,
self.queue,
self.due,
self.ivl,
self.factor,
self.reps,
self.lapses,
self.left,
self.odue,
self.odid,
self.flags,
self.data,
) = self.col.db.first("select * from cards where id = ?", self.id)
self._render_output = None self._render_output = None
self._note = None self._note = None
c = self.col.backend.get_card(self.id)
assert c
self.nid = c.nid
self.did = c.did
self.ord = c.ord
self.mod = c.mtime
self.usn = c.usn
self.type = c.ctype
self.queue = c.queue
self.due = c.due
self.ivl = c.ivl
self.factor = c.factor
self.reps = c.reps
self.lapses = c.lapses
self.left = c.left
self.odue = c.odue
self.odid = c.odid
self.flags = c.flags
self.data = c.data
def _preFlush(self) -> None: def _preFlush(self) -> None:
hooks.card_will_flush(self) hooks.card_will_flush(self)

View file

@ -480,6 +480,10 @@ class RustBackend:
pb.BackendInput(search_notes=pb.SearchNotesIn(search=search)) pb.BackendInput(search_notes=pb.SearchNotesIn(search=search))
).search_notes.note_ids ).search_notes.note_ids
def get_card(self, cid: int) -> Optional[pb.Card]:
return self._run_command(
pb.BackendInput(get_card=cid)
).get_card.card
def translate_string_in( def translate_string_in(
key: TR, **kwargs: Union[str, int, float] key: TR, **kwargs: Union[str, int, float]

View file

@ -318,7 +318,7 @@ def test_modelChange():
try: try:
c1.load() c1.load()
assert 0 assert 0
except TypeError: except AssertionError:
pass pass
# but we have two cards, as a new one was generated # but we have two cards, as a new one was generated
assert len(f.cards()) == 2 assert len(f.cards()) == 2

View file

@ -4,6 +4,7 @@
use crate::backend::dbproxy::db_command_bytes; use crate::backend::dbproxy::db_command_bytes;
use crate::backend_proto::backend_input::Value; use crate::backend_proto::backend_input::Value;
use crate::backend_proto::{BuiltinSortKind, Empty, RenderedTemplateReplacement, SyncMediaIn}; use crate::backend_proto::{BuiltinSortKind, Empty, RenderedTemplateReplacement, SyncMediaIn};
use crate::card::{Card, CardID};
use crate::collection::{open_collection, Collection}; use crate::collection::{open_collection, Collection};
use crate::config::SortKind; use crate::config::SortKind;
use crate::err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind}; use crate::err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind};
@ -248,6 +249,7 @@ impl Backend {
} }
Value::SearchCards(input) => OValue::SearchCards(self.search_cards(input)?), Value::SearchCards(input) => OValue::SearchCards(self.search_cards(input)?),
Value::SearchNotes(input) => OValue::SearchNotes(self.search_notes(input)?), Value::SearchNotes(input) => OValue::SearchNotes(self.search_notes(input)?),
Value::GetCard(cid) => OValue::GetCard(self.get_card(cid)?),
}) })
} }
@ -616,6 +618,13 @@ impl Backend {
}) })
}) })
} }
fn get_card(&self, cid: i64) -> Result<pb::GetCardOut> {
let card = self.with_col(|col| col.with_ctx(|ctx| ctx.storage.get_card(CardID(cid))))?;
Ok(pb::GetCardOut {
card: card.map(card_to_pb),
})
}
} }
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue { fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
@ -708,3 +717,26 @@ fn sort_kind_from_pb(kind: i32) -> SortKind {
_ => SortKind::NoteCreation, _ => SortKind::NoteCreation,
} }
} }
fn card_to_pb(c: Card) -> pb::Card {
pb::Card {
id: c.id.0,
nid: c.nid.0,
did: c.did.0,
ord: c.ord as u32,
mtime: c.mtime.0,
usn: c.usn.0,
ctype: c.ctype as u32,
queue: c.queue as i32,
due: c.due,
ivl: c.ivl,
factor: c.factor as u32,
reps: c.reps,
lapses: c.lapses,
left: c.left,
odue: c.odue,
odid: c.odid.0,
flags: c.flags,
data: c.data,
}
}

View file

@ -1,11 +1,15 @@
// 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::decks::DeckID;
use crate::define_newtype; use crate::define_newtype;
use crate::notes::NoteID;
use crate::{timestamp::TimestampSecs, types::Usn};
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
define_newtype!(CardID, i64); define_newtype!(CardID, i64);
#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, TryFromPrimitive, Clone, Copy)] #[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, TryFromPrimitive, Clone, Copy)]
#[repr(u8)] #[repr(u8)]
pub enum CardType { pub enum CardType {
@ -33,3 +37,50 @@ pub enum CardQueue {
UserBuried = -2, UserBuried = -2,
SchedBuried = -3, SchedBuried = -3,
} }
#[derive(Debug, Clone)]
pub struct Card {
pub(crate) id: CardID,
pub(crate) nid: NoteID,
pub(crate) did: DeckID,
pub(crate) ord: u16,
pub(crate) mtime: TimestampSecs,
pub(crate) usn: Usn,
pub(crate) ctype: CardType,
pub(crate) queue: CardQueue,
pub(crate) due: i64,
pub(crate) ivl: i64,
pub(crate) factor: u16,
pub(crate) reps: i64,
pub(crate) lapses: i64,
pub(crate) left: i64,
pub(crate) odue: i64,
pub(crate) odid: DeckID,
pub(crate) flags: i64,
pub(crate) data: String,
}
impl Default for Card {
fn default() -> Self {
Self {
id: CardID(0),
nid: NoteID(0),
did: DeckID(0),
ord: 0,
mtime: TimestampSecs(0),
usn: Usn(0),
ctype: CardType::New,
queue: CardQueue::New,
due: 0,
ivl: 0,
factor: 0,
reps: 0,
lapses: 0,
left: 0,
odue: 0,
odid: DeckID(0),
flags: 0,
data: "".to_string(),
}
}
}

72
rslib/src/storage/card.rs Normal file
View file

@ -0,0 +1,72 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::cached_sql;
use crate::card::{Card, CardID, CardQueue, CardType};
use crate::err::Result;
use rusqlite::params;
use rusqlite::{
types::{FromSql, FromSqlError, ValueRef},
OptionalExtension,
};
use std::convert::TryFrom;
impl FromSql for CardType {
fn column_result(value: ValueRef<'_>) -> std::result::Result<Self, FromSqlError> {
if let ValueRef::Integer(i) = value {
Ok(Self::try_from(i as u8).map_err(|_| FromSqlError::InvalidType)?)
} else {
Err(FromSqlError::InvalidType)
}
}
}
impl FromSql for CardQueue {
fn column_result(value: ValueRef<'_>) -> std::result::Result<Self, FromSqlError> {
if let ValueRef::Integer(i) = value {
Ok(Self::try_from(i as i8).map_err(|_| FromSqlError::InvalidType)?)
} else {
Err(FromSqlError::InvalidType)
}
}
}
impl super::StorageContext<'_> {
pub fn get_card(&mut self, cid: CardID) -> Result<Option<Card>> {
// the casts are required as Anki didn't prevent add-ons from
// storing strings or floats in columns before
let stmt = cached_sql!(
self.get_card_stmt,
self.db,
"
select nid, did, ord, cast(mod as integer), usn, type, queue, due,
cast(ivl as integer), factor, reps, lapses, left, odue, odid,
flags, data from cards where id=?"
);
stmt.query_row(params![cid], |row| {
Ok(Card {
id: cid,
nid: row.get(0)?,
did: row.get(1)?,
ord: row.get(2)?,
mtime: row.get(3)?,
usn: row.get(4)?,
ctype: row.get(5)?,
queue: row.get(6)?,
due: row.get(7)?,
ivl: row.get(8)?,
factor: row.get(9)?,
reps: row.get(10)?,
lapses: row.get(11)?,
left: row.get(12)?,
odue: row.get(13)?,
odid: row.get(14)?,
flags: row.get(15)?,
data: row.get(16)?,
})
})
.optional()
.map_err(Into::into)
}
}

View file

@ -1,3 +1,4 @@
mod card;
mod sqlite; mod sqlite;
pub(crate) use sqlite::{SqliteStorage, StorageContext}; pub(crate) use sqlite::{SqliteStorage, StorageContext};

View file

@ -42,6 +42,16 @@ pub struct SqliteStorage {
path: PathBuf, path: PathBuf,
} }
#[macro_export]
macro_rules! cached_sql {
( $label:expr, $db:expr, $sql:expr ) => {{
if $label.is_none() {
$label = Some($db.prepare_cached($sql)?);
}
$label.as_mut().unwrap()
}};
}
fn open_or_create_collection_db(path: &Path) -> Result<Connection> { fn open_or_create_collection_db(path: &Path) -> Result<Connection> {
let mut db = Connection::open(path)?; let mut db = Connection::open(path)?;
@ -211,6 +221,9 @@ pub(crate) struct StorageContext<'a> {
usn: Option<Usn>, usn: Option<Usn>,
timing_today: Option<SchedTimingToday>, timing_today: Option<SchedTimingToday>,
// cards
pub(super) get_card_stmt: Option<rusqlite::CachedStatement<'a>>,
} }
impl StorageContext<'_> { impl StorageContext<'_> {
@ -220,6 +233,7 @@ impl StorageContext<'_> {
server, server,
usn: None, usn: None,
timing_today: None, timing_today: None,
get_card_stmt: None,
} }
} }