update_card()

This commit is contained in:
Damien Elmes 2020-03-26 18:54:20 +10:00
parent 1acfe7d3cc
commit 47504245dc
6 changed files with 115 additions and 30 deletions

View file

@ -48,6 +48,7 @@ message BackendInput {
OpenCollectionIn open_collection = 36; OpenCollectionIn open_collection = 36;
Empty close_collection = 37; Empty close_collection = 37;
int64 get_card = 38; int64 get_card = 38;
Card update_card = 39;
} }
} }
@ -79,6 +80,7 @@ message BackendOutput {
Empty open_collection = 36; Empty open_collection = 36;
Empty close_collection = 37; Empty close_collection = 37;
GetCardOut get_card = 38; GetCardOut get_card = 38;
Empty update_card = 39;
BackendError error = 2047; BackendError error = 2047;
} }

View file

@ -12,6 +12,7 @@ from anki import hooks
from anki.consts import * from anki.consts import *
from anki.models import NoteType, Template from anki.models import NoteType, Template
from anki.notes import Note from anki.notes import Note
from anki.rsbackend import BackendCard
from anki.sound import AVTag from anki.sound import AVTag
from anki.utils import intTime, joinFields, timestampID from anki.utils import intTime, joinFields, timestampID
@ -33,9 +34,7 @@ class Card:
lastIvl: int lastIvl: int
ord: int ord: int
def __init__( def __init__(self, col: anki.storage._Collection, id: Optional[int] = None) -> None:
self, col: anki.storage._Collection, id: Optional[int] = None
) -> None:
self.col = col.weakref() self.col = col.weakref()
self.timerStarted = None self.timerStarted = None
self._render_output: Optional[anki.template.TemplateRenderOutput] = None self._render_output: Optional[anki.template.TemplateRenderOutput] = None
@ -98,30 +97,27 @@ class Card:
def flush(self) -> None: def flush(self) -> None:
self._preFlush() self._preFlush()
self.col.db.execute( card = BackendCard(
""" id=self.id,
insert or replace into cards values nid=self.nid,
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", did=self.did,
self.id, ord=self.ord,
self.nid, mtime=self.mod,
self.did, usn=self.usn,
self.ord, ctype=self.type,
self.mod, queue=self.queue,
self.usn, due=self.due,
self.type, ivl=self.ivl,
self.queue, factor=self.factor,
self.due, reps=self.reps,
self.ivl, lapses=self.lapses,
self.factor, left=self.left,
self.reps, odue=self.odue,
self.lapses, odid=self.odid,
self.left, flags=self.flags,
self.odue, data=self.data,
self.odid,
self.flags,
self.data,
) )
self.col.log(self) self.col.backend.update_card(card)
def question(self, reload: bool = False, browser: bool = False) -> str: def question(self, reload: bool = False, browser: bool = False) -> str:
return self.css() + self.render_output(reload, browser).question_text return self.css() + self.render_output(reload, browser).question_text

View file

@ -36,6 +36,7 @@ assert ankirspy.buildhash() == anki.buildinfo.buildhash
SchedTimingToday = pb.SchedTimingTodayOut SchedTimingToday = pb.SchedTimingTodayOut
BuiltinSortKind = pb.BuiltinSortKind BuiltinSortKind = pb.BuiltinSortKind
BackendCard = pb.Card
try: try:
import orjson import orjson
@ -481,9 +482,11 @@ class RustBackend:
).search_notes.note_ids ).search_notes.note_ids
def get_card(self, cid: int) -> Optional[pb.Card]: def get_card(self, cid: int) -> Optional[pb.Card]:
return self._run_command( return self._run_command(pb.BackendInput(get_card=cid)).get_card.card
pb.BackendInput(get_card=cid)
).get_card.card def update_card(self, card: BackendCard) -> None:
self._run_command(pb.BackendInput(update_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

@ -5,8 +5,10 @@ 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::card::{Card, CardID};
use crate::card::{CardQueue, CardType};
use crate::collection::{open_collection, Collection}; use crate::collection::{open_collection, Collection};
use crate::config::SortKind; use crate::config::SortKind;
use crate::decks::DeckID;
use crate::err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind}; use crate::err::{AnkiError, NetworkErrorKind, Result, SyncErrorKind};
use crate::i18n::{tr_args, FString, I18n}; use crate::i18n::{tr_args, FString, I18n};
use crate::latex::{extract_latex, extract_latex_expanding_clozes, ExtractedLatex}; use crate::latex::{extract_latex, extract_latex_expanding_clozes, ExtractedLatex};
@ -14,6 +16,7 @@ use crate::log::{default_logger, Logger};
use crate::media::check::MediaChecker; use crate::media::check::MediaChecker;
use crate::media::sync::MediaSyncProgress; use crate::media::sync::MediaSyncProgress;
use crate::media::MediaManager; use crate::media::MediaManager;
use crate::notes::NoteID;
use crate::sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today}; use crate::sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today};
use crate::sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span}; use crate::sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span};
use crate::search::{search_cards, search_notes, SortMode}; use crate::search::{search_cards, search_notes, SortMode};
@ -22,10 +25,13 @@ use crate::template::{
RenderedNode, RenderedNode,
}; };
use crate::text::{extract_av_tags, strip_av_tags, AVTag}; use crate::text::{extract_av_tags, strip_av_tags, AVTag};
use crate::timestamp::TimestampSecs;
use crate::types::Usn;
use crate::{backend_proto as pb, log}; use crate::{backend_proto as pb, log};
use fluent::FluentValue; use fluent::FluentValue;
use prost::Message; use prost::Message;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
@ -250,6 +256,10 @@ 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)?), Value::GetCard(cid) => OValue::GetCard(self.get_card(cid)?),
Value::UpdateCard(card) => {
self.update_card(card)?;
OValue::UpdateCard(pb::Empty {})
}
}) })
} }
@ -625,6 +635,11 @@ impl Backend {
card: card.map(card_to_pb), card: card.map(card_to_pb),
}) })
} }
fn update_card(&self, pbcard: pb::Card) -> Result<()> {
let card = pbcard_to_native(pbcard);
self.with_col(|col| col.with_ctx(|ctx| ctx.storage.update_card(&card)))
}
} }
fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue { fn translate_arg_to_fluent_val(arg: &pb::TranslateArgValue) -> FluentValue {
@ -740,3 +755,26 @@ fn card_to_pb(c: Card) -> pb::Card {
data: c.data, data: c.data,
} }
} }
fn pbcard_to_native(c: pb::Card) -> Card {
Card {
id: CardID(c.id),
nid: NoteID(c.nid),
did: DeckID(c.did),
ord: c.ord as u16,
mtime: TimestampSecs(c.mtime),
usn: Usn(c.usn),
ctype: CardType::try_from(c.ctype as u8).unwrap_or(CardType::New),
queue: CardQueue::try_from(c.queue as i8).unwrap_or(CardQueue::New),
due: c.due,
ivl: c.ivl,
factor: c.factor as u16,
reps: c.reps,
lapses: c.lapses,
left: c.left,
odue: c.odue,
odid: DeckID(c.odid),
flags: c.flags,
data: c.data,
}
}

View file

@ -3,7 +3,7 @@
use crate::cached_sql; use crate::cached_sql;
use crate::card::{Card, CardID, CardQueue, CardType}; use crate::card::{Card, CardID, CardQueue, CardType};
use crate::err::Result; use crate::err::{AnkiError, Result};
use rusqlite::params; use rusqlite::params;
use rusqlite::{ use rusqlite::{
types::{FromSql, FromSqlError, ValueRef}, types::{FromSql, FromSqlError, ValueRef},
@ -69,4 +69,48 @@ flags, data from cards where id=?"
.optional() .optional()
.map_err(Into::into) .map_err(Into::into)
} }
pub(crate) fn update_card(&mut self, card: &Card) -> Result<()> {
if card.id.0 == 0 {
return Err(AnkiError::invalid_input("card id not set"));
}
self.flush_card(card)
}
fn flush_card(&mut self, card: &Card) -> Result<()> {
let stmt = cached_sql!(
self.update_card_stmt,
self.db,
"
insert or replace into cards
(id, nid, did, ord, mod, usn, type, queue, due, ivl, factor,
reps, lapses, left, odue, odid, flags, data)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"
);
stmt.execute(params![
card.id,
card.nid,
card.did,
card.ord,
card.mtime,
card.usn,
card.ctype as u8,
card.queue as i8,
card.due,
card.ivl,
card.factor,
card.reps,
card.lapses,
card.left,
card.odue,
card.odid,
card.flags,
card.data,
])?;
Ok(())
}
} }

View file

@ -224,6 +224,7 @@ pub(crate) struct StorageContext<'a> {
// cards // cards
pub(super) get_card_stmt: Option<rusqlite::CachedStatement<'a>>, pub(super) get_card_stmt: Option<rusqlite::CachedStatement<'a>>,
pub(super) update_card_stmt: Option<rusqlite::CachedStatement<'a>>,
} }
impl StorageContext<'_> { impl StorageContext<'_> {
@ -234,6 +235,7 @@ impl StorageContext<'_> {
usn: None, usn: None,
timing_today: None, timing_today: None,
get_card_stmt: None, get_card_stmt: None,
update_card_stmt: None,
} }
} }