mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
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:
parent
8b84368e3a
commit
a4d61fe7d9
14 changed files with 118 additions and 19 deletions
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
|
|
60
rslib/src/storage/card/data.rs
Normal file
60
rslib/src/storage/card/data.rs
Normal 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
|
||||
}
|
|
@ -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(())
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue