mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -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;
|
sint32 original_due = 15;
|
||||||
int64 original_deck_id = 16;
|
int64 original_deck_id = 16;
|
||||||
uint32 flags = 17;
|
uint32 flags = 17;
|
||||||
string data = 18;
|
generic.UInt32 original_position = 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateCardsRequest {
|
message UpdateCardsRequest {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import anki.collection
|
||||||
import anki.decks
|
import anki.decks
|
||||||
import anki.notes
|
import anki.notes
|
||||||
import anki.template
|
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._legacy import DeprecatedNamesMixin, deprecated
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.models import NotetypeDict, TemplateDict
|
from anki.models import NotetypeDict, TemplateDict
|
||||||
|
@ -89,7 +89,9 @@ class Card(DeprecatedNamesMixin):
|
||||||
self.odue = card.original_due
|
self.odue = card.original_due
|
||||||
self.odid = anki.decks.DeckId(card.original_deck_id)
|
self.odid = anki.decks.DeckId(card.original_deck_id)
|
||||||
self.flags = card.flags
|
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:
|
def _to_backend_card(self) -> cards_pb2.Card:
|
||||||
# mtime & usn are set by backend
|
# mtime & usn are set by backend
|
||||||
|
@ -109,7 +111,9 @@ class Card(DeprecatedNamesMixin):
|
||||||
original_due=self.odue,
|
original_due=self.odue,
|
||||||
original_deck_id=self.odid,
|
original_deck_id=self.odid,
|
||||||
flags=self.flags,
|
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:
|
def flush(self) -> None:
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl TryFrom<pb::Card> for Card {
|
||||||
original_due: c.original_due,
|
original_due: c.original_due,
|
||||||
original_deck_id: DeckId(c.original_deck_id),
|
original_deck_id: DeckId(c.original_deck_id),
|
||||||
flags: c.flags as u8,
|
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_due: c.original_due,
|
||||||
original_deck_id: c.original_deck_id.0,
|
original_deck_id: c.original_deck_id.0,
|
||||||
flags: c.flags as u32,
|
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 {
|
impl From<i64> for pb::Int64 {
|
||||||
fn from(val: i64) -> Self {
|
fn from(val: i64) -> Self {
|
||||||
pb::Int64 { val }
|
pb::Int64 { val }
|
||||||
|
|
|
@ -77,7 +77,8 @@ pub struct Card {
|
||||||
pub(crate) original_due: i32,
|
pub(crate) original_due: i32,
|
||||||
pub(crate) original_deck_id: DeckId,
|
pub(crate) original_deck_id: DeckId,
|
||||||
pub(crate) flags: u8,
|
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 {
|
impl Default for Card {
|
||||||
|
@ -100,7 +101,7 @@ impl Default for Card {
|
||||||
original_due: 0,
|
original_due: 0,
|
||||||
original_deck_id: DeckId(0),
|
original_deck_id: DeckId(0),
|
||||||
flags: 0,
|
flags: 0,
|
||||||
data: "".to_string(),
|
original_position: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ impl CardStateUpdater {
|
||||||
self.card.ctype = CardType::New;
|
self.card.ctype = CardType::New;
|
||||||
self.card.queue = CardQueue::New;
|
self.card.queue = CardQueue::New;
|
||||||
self.card.due = next.position as i32;
|
self.card.due = next.position as i32;
|
||||||
|
self.card.original_position = None;
|
||||||
|
|
||||||
RevlogEntryPartial::new(current, next.into(), 0.0, self.secs_until_rollover())
|
RevlogEntryPartial::new(current, next.into(), 0.0, self.secs_until_rollover())
|
||||||
}
|
}
|
||||||
|
@ -30,6 +31,9 @@ impl CardStateUpdater {
|
||||||
) -> RevlogEntryPartial {
|
) -> RevlogEntryPartial {
|
||||||
self.card.remaining_steps = next.remaining_steps;
|
self.card.remaining_steps = next.remaining_steps;
|
||||||
self.card.ctype = CardType::Learn;
|
self.card.ctype = CardType::Learn;
|
||||||
|
if let Some(position) = current.new_position() {
|
||||||
|
self.card.original_position = Some(position)
|
||||||
|
}
|
||||||
|
|
||||||
let interval = next
|
let interval = next
|
||||||
.interval_kind()
|
.interval_kind()
|
||||||
|
|
|
@ -18,6 +18,9 @@ impl CardStateUpdater {
|
||||||
self.card.ctype = CardType::Relearn;
|
self.card.ctype = CardType::Relearn;
|
||||||
self.card.lapses = next.review.lapses;
|
self.card.lapses = next.review.lapses;
|
||||||
self.card.ease_factor = (next.review.ease_factor * 1000.0).round() as u16;
|
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
|
let interval = next
|
||||||
.interval_kind()
|
.interval_kind()
|
||||||
|
|
|
@ -20,6 +20,9 @@ impl CardStateUpdater {
|
||||||
self.card.ease_factor = (next.ease_factor * 1000.0).round() as u16;
|
self.card.ease_factor = (next.ease_factor * 1000.0).round() as u16;
|
||||||
self.card.lapses = next.lapses;
|
self.card.lapses = next.lapses;
|
||||||
self.card.remaining_steps = 0;
|
self.card.remaining_steps = 0;
|
||||||
|
if let Some(position) = current.new_position() {
|
||||||
|
self.card.original_position = Some(position)
|
||||||
|
}
|
||||||
|
|
||||||
RevlogEntryPartial::new(
|
RevlogEntryPartial::new(
|
||||||
current,
|
current,
|
||||||
|
|
|
@ -21,6 +21,7 @@ impl Card {
|
||||||
self.queue = CardQueue::New;
|
self.queue = CardQueue::New;
|
||||||
self.interval = 0;
|
self.interval = 0;
|
||||||
self.ease_factor = 0;
|
self.ease_factor = 0;
|
||||||
|
self.original_position = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the card is new, change its position, and return true.
|
/// If the card is new, change its position, and return true.
|
||||||
|
|
|
@ -65,6 +65,17 @@ impl CardState {
|
||||||
pub(crate) fn leeched(self) -> bool {
|
pub(crate) fn leeched(self) -> bool {
|
||||||
self.review_state().map(|r| r.leeched).unwrap_or_default()
|
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.
|
/// Info required during state transitions.
|
||||||
|
|
|
@ -25,7 +25,7 @@ impl Collection {
|
||||||
let revlog = self.storage.get_revlog_entries_for_card(card.id)?;
|
let revlog = self.storage.get_revlog_entries_for_card(card.id)?;
|
||||||
|
|
||||||
let (average_secs, total_secs) = average_and_total_secs_strings(&revlog);
|
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 {
|
Ok(pb::CardStatsResponse {
|
||||||
card_id: card.id.into(),
|
card_id: card.id.into(),
|
||||||
|
@ -52,7 +52,7 @@ impl Collection {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn due_date_and_position_strings(
|
fn due_date_and_position(
|
||||||
&mut self,
|
&mut self,
|
||||||
card: &Card,
|
card: &Card,
|
||||||
) -> Result<(Option<pb::generic::Int64>, Option<pb::generic::Int32>)> {
|
) -> Result<(Option<pb::generic::Int64>, Option<pb::generic::Int32>)> {
|
||||||
|
@ -67,7 +67,7 @@ impl Collection {
|
||||||
Some(pb::generic::Int64 {
|
Some(pb::generic::Int64 {
|
||||||
val: TimestampSecs::now().0,
|
val: TimestampSecs::now().0,
|
||||||
}),
|
}),
|
||||||
None,
|
card.original_position.map(|u| (u as i32).into()),
|
||||||
),
|
),
|
||||||
CardQueue::Review | CardQueue::DayLearn => (
|
CardQueue::Review | CardQueue::DayLearn => (
|
||||||
{
|
{
|
||||||
|
@ -81,7 +81,7 @@ impl Collection {
|
||||||
Some(pb::generic::Int64 { val: due.0 })
|
Some(pb::generic::Int64 { val: due.0 })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None,
|
card.original_position.map(|u| (u as i32).into()),
|
||||||
),
|
),
|
||||||
_ => (None, None),
|
_ => (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
|
// 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
|
||||||
|
|
||||||
|
pub(crate) mod data;
|
||||||
pub(crate) mod filtered;
|
pub(crate) mod filtered;
|
||||||
|
|
||||||
use std::{collections::HashSet, convert::TryFrom, result};
|
use std::{collections::HashSet, convert::TryFrom, result};
|
||||||
|
@ -11,6 +12,7 @@ use rusqlite::{
|
||||||
OptionalExtension, Row,
|
OptionalExtension, Row,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::data::CardData;
|
||||||
use super::ids_to_string;
|
use super::ids_to_string;
|
||||||
use crate::{
|
use crate::{
|
||||||
card::{Card, CardId, CardQueue, CardType},
|
card::{Card, CardId, CardQueue, CardType},
|
||||||
|
@ -47,6 +49,7 @@ impl FromSql for CardQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn row_to_card(row: &Row) -> result::Result<Card, rusqlite::Error> {
|
fn row_to_card(row: &Row) -> result::Result<Card, rusqlite::Error> {
|
||||||
|
let data: CardData = row.get(17)?;
|
||||||
Ok(Card {
|
Ok(Card {
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
note_id: row.get(1)?,
|
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_due: row.get(14).ok().unwrap_or_default(),
|
||||||
original_deck_id: row.get(15)?,
|
original_deck_id: row.get(15)?,
|
||||||
flags: row.get(16)?,
|
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_due,
|
||||||
card.original_deck_id,
|
card.original_deck_id,
|
||||||
card.flags,
|
card.flags,
|
||||||
card.data,
|
CardData::from_card(card),
|
||||||
card.id,
|
card.id,
|
||||||
])?;
|
])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -136,7 +139,7 @@ impl super::SqliteStorage {
|
||||||
card.original_due,
|
card.original_due,
|
||||||
card.original_deck_id,
|
card.original_deck_id,
|
||||||
card.flags,
|
card.flags,
|
||||||
card.data,
|
CardData::from_card(card),
|
||||||
])?;
|
])?;
|
||||||
card.id = CardId(self.db.last_insert_rowid());
|
card.id = CardId(self.db.last_insert_rowid());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -163,7 +166,7 @@ impl super::SqliteStorage {
|
||||||
card.original_due,
|
card.original_due,
|
||||||
card.original_deck_id,
|
card.original_deck_id,
|
||||||
card.flags,
|
card.flags,
|
||||||
card.data,
|
CardData::from_card(card),
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -26,7 +26,10 @@ use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
revlog::RevlogEntry,
|
revlog::RevlogEntry,
|
||||||
serde::{default_on_invalid, deserialize_int_from_number},
|
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},
|
tags::{join_tags, split_tags, Tag},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1097,7 +1100,7 @@ impl From<CardEntry> for Card {
|
||||||
original_due: e.odue,
|
original_due: e.odue,
|
||||||
original_deck_id: e.odid,
|
original_deck_id: e.odid,
|
||||||
flags: e.flags,
|
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,
|
odue: e.original_due,
|
||||||
odid: e.original_deck_id,
|
odid: e.original_deck_id,
|
||||||
flags: e.flags,
|
flags: e.flags,
|
||||||
data: e.data,
|
data: card_data_string(&e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue