Original position (#1677)

* Replace Card.data with .original_position

* Use and update original position in v3

* Show original position in card info

* Revert restoring original position for now

* Fix pb card to/from pylib card

* Try original_position as the last pb field

* minor wording tweaks (dae)
This commit is contained in:
RumovZ 2022-02-22 13:48:21 +01:00 committed by GitHub
parent 8b84368e3a
commit a4d61fe7d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 118 additions and 19 deletions

View file

@ -41,7 +41,7 @@ message Card {
sint32 original_due = 15;
int64 original_deck_id = 16;
uint32 flags = 17;
string data = 18;
generic.UInt32 original_position = 18;
}
message UpdateCardsRequest {

View file

@ -11,7 +11,7 @@ import anki.collection
import anki.decks
import anki.notes
import anki.template
from anki import cards_pb2, hooks
from anki import cards_pb2, generic_pb2, hooks
from anki._legacy import DeprecatedNamesMixin, deprecated
from anki.consts import *
from anki.models import NotetypeDict, TemplateDict
@ -89,7 +89,9 @@ class Card(DeprecatedNamesMixin):
self.odue = card.original_due
self.odid = anki.decks.DeckId(card.original_deck_id)
self.flags = card.flags
self.data = card.data
self.original_position = (
card.original_position.val if card.HasField("original_position") else None
)
def _to_backend_card(self) -> cards_pb2.Card:
# mtime & usn are set by backend
@ -109,7 +111,9 @@ class Card(DeprecatedNamesMixin):
original_due=self.odue,
original_deck_id=self.odid,
flags=self.flags,
data=self.data,
original_position=generic_pb2.UInt32(val=self.original_position)
if self.original_position is not None
else None,
)
def flush(self) -> None:

View file

@ -86,7 +86,7 @@ impl TryFrom<pb::Card> for Card {
original_due: c.original_due,
original_deck_id: DeckId(c.original_deck_id),
flags: c.flags as u8,
data: c.data,
original_position: c.original_position.map(|pos| pos.val),
})
}
}
@ -111,7 +111,7 @@ impl From<Card> for pb::Card {
original_due: c.original_due,
original_deck_id: c.original_deck_id.0,
flags: c.flags as u32,
data: c.data,
original_position: c.original_position.map(Into::into),
}
}
}

View file

@ -21,6 +21,12 @@ impl From<bool> for pb::Bool {
}
}
impl From<i32> for pb::Int32 {
fn from(val: i32) -> Self {
pb::Int32 { val }
}
}
impl From<i64> for pb::Int64 {
fn from(val: i64) -> Self {
pb::Int64 { val }

View file

@ -77,7 +77,8 @@ pub struct Card {
pub(crate) original_due: i32,
pub(crate) original_deck_id: DeckId,
pub(crate) flags: u8,
pub(crate) data: String,
/// The position in the new queue before leaving it.
pub(crate) original_position: Option<u32>,
}
impl Default for Card {
@ -100,7 +101,7 @@ impl Default for Card {
original_due: 0,
original_deck_id: DeckId(0),
flags: 0,
data: "".to_string(),
original_position: None,
}
}
}

View file

@ -19,6 +19,7 @@ impl CardStateUpdater {
self.card.ctype = CardType::New;
self.card.queue = CardQueue::New;
self.card.due = next.position as i32;
self.card.original_position = None;
RevlogEntryPartial::new(current, next.into(), 0.0, self.secs_until_rollover())
}
@ -30,6 +31,9 @@ impl CardStateUpdater {
) -> RevlogEntryPartial {
self.card.remaining_steps = next.remaining_steps;
self.card.ctype = CardType::Learn;
if let Some(position) = current.new_position() {
self.card.original_position = Some(position)
}
let interval = next
.interval_kind()

View file

@ -18,6 +18,9 @@ impl CardStateUpdater {
self.card.ctype = CardType::Relearn;
self.card.lapses = next.review.lapses;
self.card.ease_factor = (next.review.ease_factor * 1000.0).round() as u16;
if let Some(position) = current.new_position() {
self.card.original_position = Some(position)
}
let interval = next
.interval_kind()

View file

@ -20,6 +20,9 @@ impl CardStateUpdater {
self.card.ease_factor = (next.ease_factor * 1000.0).round() as u16;
self.card.lapses = next.lapses;
self.card.remaining_steps = 0;
if let Some(position) = current.new_position() {
self.card.original_position = Some(position)
}
RevlogEntryPartial::new(
current,

View file

@ -21,6 +21,7 @@ impl Card {
self.queue = CardQueue::New;
self.interval = 0;
self.ease_factor = 0;
self.original_position = None;
}
/// If the card is new, change its position, and return true.

View file

@ -65,6 +65,17 @@ impl CardState {
pub(crate) fn leeched(self) -> bool {
self.review_state().map(|r| r.leeched).unwrap_or_default()
}
/// Returns the position if it's a [NewState].
pub(super) fn new_position(&self) -> Option<u32> {
match self {
Self::Normal(NormalState::New(NewState { position }))
| Self::Filtered(FilteredState::Rescheduling(ReschedulingFilterState {
original_state: NormalState::New(NewState { position }),
})) => Some(*position),
_ => None,
}
}
}
/// Info required during state transitions.

View file

@ -25,7 +25,7 @@ impl Collection {
let revlog = self.storage.get_revlog_entries_for_card(card.id)?;
let (average_secs, total_secs) = average_and_total_secs_strings(&revlog);
let (due_date, due_position) = self.due_date_and_position_strings(&card)?;
let (due_date, due_position) = self.due_date_and_position(&card)?;
Ok(pb::CardStatsResponse {
card_id: card.id.into(),
@ -52,7 +52,7 @@ impl Collection {
})
}
fn due_date_and_position_strings(
fn due_date_and_position(
&mut self,
card: &Card,
) -> Result<(Option<pb::generic::Int64>, Option<pb::generic::Int32>)> {
@ -67,7 +67,7 @@ impl Collection {
Some(pb::generic::Int64 {
val: TimestampSecs::now().0,
}),
None,
card.original_position.map(|u| (u as i32).into()),
),
CardQueue::Review | CardQueue::DayLearn => (
{
@ -81,7 +81,7 @@ impl Collection {
Some(pb::generic::Int64 { val: due.0 })
}
},
None,
card.original_position.map(|u| (u as i32).into()),
),
_ => (None, None),
})

View file

@ -0,0 +1,60 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use rusqlite::{
types::{FromSql, FromSqlError, ToSqlOutput, ValueRef},
ToSql,
};
use serde_derive::{Deserialize, Serialize};
use crate::{prelude::*, serde::default_on_invalid};
/// Helper for serdeing the card data column.
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
#[serde(default)]
pub(super) struct CardData {
#[serde(
skip_serializing_if = "Option::is_none",
rename = "pos",
deserialize_with = "default_on_invalid"
)]
pub(crate) original_position: Option<u32>,
}
impl CardData {
pub(super) fn from_card(card: &Card) -> Self {
Self {
original_position: card.original_position,
}
}
}
impl FromSql for CardData {
/// Infallible; invalid/missing data results in the default value.
fn column_result(value: ValueRef<'_>) -> std::result::Result<Self, FromSqlError> {
if let ValueRef::Text(s) = value {
Ok(serde_json::from_slice(s).unwrap_or_default())
} else {
Ok(Self::default())
}
}
}
impl ToSql for CardData {
fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
Ok(ToSqlOutput::Owned(
serde_json::to_string(self).unwrap().into(),
))
}
}
/// Serialize the JSON `data` for a card.
pub(crate) fn card_data_string(card: &Card) -> String {
serde_json::to_string(&CardData::from_card(card)).unwrap()
}
/// Extract original position from JSON `data`.
pub(crate) fn original_position_from_card_data(card_data: &str) -> Option<u32> {
let data: CardData = serde_json::from_str(card_data).unwrap_or_default();
data.original_position
}

View file

@ -1,6 +1,7 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
pub(crate) mod data;
pub(crate) mod filtered;
use std::{collections::HashSet, convert::TryFrom, result};
@ -11,6 +12,7 @@ use rusqlite::{
OptionalExtension, Row,
};
use self::data::CardData;
use super::ids_to_string;
use crate::{
card::{Card, CardId, CardQueue, CardType},
@ -47,6 +49,7 @@ impl FromSql for CardQueue {
}
fn row_to_card(row: &Row) -> result::Result<Card, rusqlite::Error> {
let data: CardData = row.get(17)?;
Ok(Card {
id: row.get(0)?,
note_id: row.get(1)?,
@ -65,7 +68,7 @@ fn row_to_card(row: &Row) -> result::Result<Card, rusqlite::Error> {
original_due: row.get(14).ok().unwrap_or_default(),
original_deck_id: row.get(15)?,
flags: row.get(16)?,
data: row.get(17)?,
original_position: data.original_position,
})
}
@ -109,7 +112,7 @@ impl super::SqliteStorage {
card.original_due,
card.original_deck_id,
card.flags,
card.data,
CardData::from_card(card),
card.id,
])?;
Ok(())
@ -136,7 +139,7 @@ impl super::SqliteStorage {
card.original_due,
card.original_deck_id,
card.flags,
card.data,
CardData::from_card(card),
])?;
card.id = CardId(self.db.last_insert_rowid());
Ok(())
@ -163,7 +166,7 @@ impl super::SqliteStorage {
card.original_due,
card.original_deck_id,
card.flags,
card.data,
CardData::from_card(card),
])?;
Ok(())

View file

@ -26,7 +26,10 @@ use crate::{
prelude::*,
revlog::RevlogEntry,
serde::{default_on_invalid, deserialize_int_from_number},
storage::open_and_check_sqlite_file,
storage::{
card::data::{card_data_string, original_position_from_card_data},
open_and_check_sqlite_file,
},
tags::{join_tags, split_tags, Tag},
};
@ -1097,7 +1100,7 @@ impl From<CardEntry> for Card {
original_due: e.odue,
original_deck_id: e.odid,
flags: e.flags,
data: e.data,
original_position: original_position_from_card_data(&e.data),
}
}
}
@ -1122,7 +1125,7 @@ impl From<Card> for CardEntry {
odue: e.original_due,
odid: e.original_deck_id,
flags: e.flags,
data: e.data,
data: card_data_string(&e),
}
}
}