Merge pull request #433 from Arthur-Milchior/ints

Constants
This commit is contained in:
Damien Elmes 2020-02-14 08:37:31 +10:00 committed by GitHub
commit f7ebb8c28f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 557 additions and 461 deletions

View file

@ -48,8 +48,8 @@ class Card:
self.id = timestampID(col.db, "cards") self.id = timestampID(col.db, "cards")
self.did = 1 self.did = 1
self.crt = intTime() self.crt = intTime()
self.type = 0 self.type = CARD_TYPE_NEW
self.queue = 0 self.queue = QUEUE_TYPE_NEW
self.ivl = 0 self.ivl = 0
self.factor = 0 self.factor = 0
self.reps = 0 self.reps = 0
@ -88,7 +88,11 @@ class Card:
self.mod = intTime() self.mod = intTime()
self.usn = self.col.usn() self.usn = self.col.usn()
# bug check # bug check
if self.queue == 2 and self.odue and not self.col.decks.isDyn(self.did): if (
self.queue == QUEUE_TYPE_REV
and self.odue
and not self.col.decks.isDyn(self.did)
):
hooks.card_odue_was_invalid() hooks.card_odue_was_invalid()
assert self.due < 4294967296 assert self.due < 4294967296
self.col.db.execute( self.col.db.execute(
@ -120,7 +124,11 @@ insert or replace into cards values
self.mod = intTime() self.mod = intTime()
self.usn = self.col.usn() self.usn = self.col.usn()
# bug checks # bug checks
if self.queue == 2 and self.odue and not self.col.decks.isDyn(self.did): if (
self.queue == QUEUE_TYPE_REV
and self.odue
and not self.col.decks.isDyn(self.did)
):
hooks.card_odue_was_invalid() hooks.card_odue_was_invalid()
assert self.due < 4294967296 assert self.due < 4294967296
self.col.db.execute( self.col.db.execute(

View file

@ -14,6 +14,22 @@ NEW_CARDS_FIRST = 2
NEW_CARDS_RANDOM = 0 NEW_CARDS_RANDOM = 0
NEW_CARDS_DUE = 1 NEW_CARDS_DUE = 1
# Queue types
QUEUE_TYPE_MANUALLY_BURIED = -3
QUEUE_TYPE_SIBLING_BURIED = -2
QUEUE_TYPE_SUSPENDED = -1
QUEUE_TYPE_NEW = 0
QUEUE_TYPE_LRN = 1
QUEUE_TYPE_REV = 2
QUEUE_TYPE_DAY_LEARN_RELEARN = 3
QUEUE_TYPE_PREVIEW = 4
# Card types
CARD_TYPE_NEW = 0
CARD_TYPE_LRN = 1
CARD_TYPE_REV = 2
CARD_TYPE_RELEARNING = 3
# removal types # removal types
REM_CARD = 0 REM_CARD = 0
REM_NOTE = 1 REM_NOTE = 1
@ -27,6 +43,10 @@ COUNT_REMAINING = 1
MEDIA_ADD = 0 MEDIA_ADD = 0
MEDIA_REM = 1 MEDIA_REM = 1
# Kind of decks
DECK_STD = 0
DECK_DYN = 1
# dynamic deck order # dynamic deck order
DYN_OLDEST = 0 DYN_OLDEST = 0
DYN_RANDOM = 1 DYN_RANDOM = 1
@ -55,6 +75,22 @@ SYNC_VER = 9
HELP_SITE = "http://ankisrs.net/docs/manual.html" HELP_SITE = "http://ankisrs.net/docs/manual.html"
# Leech actions
LEECH_SUSPEND = 0
LEECH_TAGONLY = 1
# Buttons
BUTTON_ONE = 1
BUTTON_TWO = 2
BUTTON_THREE = 3
BUTTON_FOUR = 4
# Revlog types
REVLOG_LRN = 0
REVLOG_REV = 1
REVLOG_RELRN = 2
REVLOG_CRAM = 3
# Labels # Labels
########################################################################## ##########################################################################

View file

@ -27,7 +27,7 @@ defaultDeck = {
"conf": 1, "conf": 1,
"usn": 0, "usn": 0,
"desc": "", "desc": "",
"dyn": 0, # anki uses int/bool interchangably here "dyn": DECK_STD, # anki uses int/bool interchangably here
"collapsed": False, "collapsed": False,
# added in beta11 # added in beta11
"extendNew": 10, "extendNew": 10,
@ -40,7 +40,7 @@ defaultDynamicDeck = {
"lrnToday": [0, 0], "lrnToday": [0, 0],
"timeToday": [0, 0], "timeToday": [0, 0],
"collapsed": False, "collapsed": False,
"dyn": 1, "dyn": DECK_DYN,
"desc": "", "desc": "",
"usn": 0, "usn": 0,
"delays": None, "delays": None,
@ -71,7 +71,7 @@ defaultConf = {
"minInt": 1, "minInt": 1,
"leechFails": 8, "leechFails": 8,
# type 0=suspend, 1=tagonly # type 0=suspend, 1=tagonly
"leechAction": 0, "leechAction": LEECH_SUSPEND,
}, },
"rev": { "rev": {
"perDay": 200, "perDay": 200,

View file

@ -240,7 +240,7 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """
elif type == "cardDue": elif type == "cardDue":
sort = "c.type, c.due" sort = "c.type, c.due"
elif type == "cardEase": elif type == "cardEase":
sort = "c.type == 0, c.factor" sort = f"c.type == {CARD_TYPE_NEW}, c.factor"
elif type == "cardLapses": elif type == "cardLapses":
sort = "c.lapses" sort = "c.lapses"
elif type == "cardIvl": elif type == "cardIvl":
@ -271,18 +271,18 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """
if val == "review": if val == "review":
n = 2 n = 2
elif val == "new": elif val == "new":
n = 0 n = CARD_TYPE_NEW
else: else:
return "queue in (1, 3)" return f"queue in ({QUEUE_TYPE_LRN}, {QUEUE_TYPE_DAY_LEARN_RELEARN})"
return "type = %d" % n return "type = %d" % n
elif val == "suspended": elif val == "suspended":
return "c.queue = -1" return "c.queue = -1"
elif val == "buried": elif val == "buried":
return "c.queue in (-2, -3)" return f"c.queue in ({QUEUE_TYPE_SIBLING_BURIED}, {QUEUE_TYPE_MANUALLY_BURIED})"
elif val == "due": elif val == "due":
return """ return f"""
(c.queue in (2,3) and c.due <= %d) or (c.queue in ({QUEUE_TYPE_REV},{QUEUE_TYPE_DAY_LEARN_RELEARN}) and c.due <= %d) or
(c.queue = 1 and c.due <= %d)""" % ( (c.queue = {QUEUE_TYPE_LRN} and c.due <= %d)""" % (
self.col.sched.today, self.col.sched.today,
self.col.sched.dayCutoff, self.col.sched.dayCutoff,
) )
@ -349,7 +349,7 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """
if prop == "due": if prop == "due":
val += self.col.sched.today val += self.col.sched.today
# only valid for review/daily learning # only valid for review/daily learning
q.append("(c.queue in (2,3))") q.append(f"(c.queue in ({QUEUE_TYPE_REV},{QUEUE_TYPE_DAY_LEARN_RELEARN}))")
elif prop == "ease": elif prop == "ease":
prop = "factor" prop = "factor"
val = int(val * 1000) val = int(val * 1000)

View file

@ -6,6 +6,7 @@ import unicodedata
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
from anki.collection import _Collection from anki.collection import _Collection
from anki.consts import *
from anki.importing.base import Importer from anki.importing.base import Importer
from anki.lang import _ from anki.lang import _
from anki.storage import Collection from anki.storage import Collection
@ -343,7 +344,10 @@ class Anki2Importer(Importer):
card[4] = intTime() card[4] = intTime()
card[5] = usn card[5] = usn
# review cards have a due date relative to collection # review cards have a due date relative to collection
if card[7] in (2, 3) or card[6] == 2: if (
card[7] in (QUEUE_TYPE_REV, QUEUE_TYPE_DAY_LEARN_RELEARN)
or card[6] == CARD_TYPE_REV
):
card[8] -= aheadBy card[8] -= aheadBy
# odue needs updating too # odue needs updating too
if card[14]: if card[14]:
@ -356,13 +360,13 @@ class Anki2Importer(Importer):
card[8] = card[14] card[8] = card[14]
card[14] = 0 card[14] = 0
# queue # queue
if card[6] == 1: # type if card[6] == CARD_TYPE_LRN: # type
card[7] = 0 card[7] = QUEUE_TYPE_NEW
else: else:
card[7] = card[6] card[7] = card[6]
# type # type
if card[6] == 1: if card[6] == CARD_TYPE_LRN:
card[6] = 0 card[6] = CARD_TYPE_NEW
cards.append(card) cards.append(card)
# we need to import revlog, rewriting card ids and bumping usn # we need to import revlog, rewriting card ids and bumping usn
for rev in self.src.db.execute("select * from revlog where cid = ?", scid): for rev in self.src.db.execute("select * from revlog where cid = ?", scid):

View file

@ -52,9 +52,12 @@ class ForeignCard:
# If the first field of the model is not in the map, the map is invalid. # If the first field of the model is not in the map, the map is invalid.
# The import mode is one of: # The import mode is one of:
# 0: update if first field matches existing note # UPDATE_MODE: update if first field matches existing note
# 1: ignore if first field matches existing note # IGNORE_MODE: ignore if first field matches existing note
# 2: import even if first field matches existing note # ADD_MODE: import even if first field matches existing note
UPDATE_MODE = 0
IGNORE_MODE = 1
ADD_MODE = 2
class NoteImporter(Importer): class NoteImporter(Importer):
@ -62,7 +65,7 @@ class NoteImporter(Importer):
needMapper = True needMapper = True
needDelimiter = False needDelimiter = False
allowHTML = False allowHTML = False
importMode = 0 importMode = UPDATE_MODE
mapping: Optional[List[str]] mapping: Optional[List[str]]
tagModified: Optional[str] tagModified: Optional[str]
@ -153,7 +156,7 @@ class NoteImporter(Importer):
self.log.append(_("Empty first field: %s") % " ".join(n.fields)) self.log.append(_("Empty first field: %s") % " ".join(n.fields))
continue continue
# earlier in import? # earlier in import?
if fld0 in firsts and self.importMode != 2: if fld0 in firsts and self.importMode != ADD_MODE:
# duplicates in source file; log and ignore # duplicates in source file; log and ignore
self.log.append(_("Appeared twice in file: %s") % fld0) self.log.append(_("Appeared twice in file: %s") % fld0)
continue continue
@ -168,16 +171,16 @@ class NoteImporter(Importer):
if fld0 == sflds[0]: if fld0 == sflds[0]:
# duplicate # duplicate
found = True found = True
if self.importMode == 0: if self.importMode == UPDATE_MODE:
data = self.updateData(n, id, sflds) data = self.updateData(n, id, sflds)
if data: if data:
updates.append(data) updates.append(data)
updateLog.append(updateLogTxt % fld0) updateLog.append(updateLogTxt % fld0)
dupeCount += 1 dupeCount += 1
found = True found = True
elif self.importMode == 1: elif self.importMode == IGNORE_MODE:
dupeCount += 1 dupeCount += 1
elif self.importMode == 2: elif self.importMode == ADD_MODE:
# allow duplicates in this case # allow duplicates in this case
if fld0 not in dupes: if fld0 not in dupes:
# only show message once, no matter how many # only show message once, no matter how many
@ -214,9 +217,9 @@ class NoteImporter(Importer):
ngettext("%d note updated", "%d notes updated", self.updateCount) ngettext("%d note updated", "%d notes updated", self.updateCount)
% self.updateCount % self.updateCount
) )
if self.importMode == 0: if self.importMode == UPDATE_MODE:
unchanged = dupeCount - self.updateCount unchanged = dupeCount - self.updateCount
elif self.importMode == 1: elif self.importMode == IGNORE_MODE:
unchanged = dupeCount unchanged = dupeCount
else: else:
unchanged = 0 unchanged = 0

View file

@ -67,28 +67,28 @@ class Scheduler:
self._burySiblings(card) self._burySiblings(card)
card.reps += 1 card.reps += 1
# former is for logging new cards, latter also covers filt. decks # former is for logging new cards, latter also covers filt. decks
card.wasNew = card.type == 0 card.wasNew = card.type == CARD_TYPE_NEW
wasNewQ = card.queue == 0 wasNewQ = card.queue == QUEUE_TYPE_NEW
if wasNewQ: if wasNewQ:
# came from the new queue, move to learning # came from the new queue, move to learning
card.queue = 1 card.queue = QUEUE_TYPE_LRN
# if it was a new card, it's now a learning card # if it was a new card, it's now a learning card
if card.type == 0: if card.type == CARD_TYPE_NEW:
card.type = 1 card.type = CARD_TYPE_LRN
# init reps to graduation # init reps to graduation
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
# dynamic? # dynamic?
if card.odid and card.type == 2: if card.odid and card.type == CARD_TYPE_REV:
if self._resched(card): if self._resched(card):
# reviews get their ivl boosted on first sight # reviews get their ivl boosted on first sight
card.ivl = self._dynIvlBoost(card) card.ivl = self._dynIvlBoost(card)
card.odue = self.today + card.ivl card.odue = self.today + card.ivl
self._updateStats(card, "new") self._updateStats(card, "new")
if card.queue in (1, 3): if card.queue in (QUEUE_TYPE_LRN, QUEUE_TYPE_DAY_LEARN_RELEARN):
self._answerLrnCard(card, ease) self._answerLrnCard(card, ease)
if not wasNewQ: if not wasNewQ:
self._updateStats(card, "lrn") self._updateStats(card, "lrn")
elif card.queue == 2: elif card.queue == QUEUE_TYPE_REV:
self._answerRevCard(card, ease) self._answerRevCard(card, ease)
self._updateStats(card, "rev") self._updateStats(card, "rev")
else: else:
@ -112,9 +112,9 @@ class Scheduler:
"Return counts over next DAYS. Includes today." "Return counts over next DAYS. Includes today."
daysd = dict( daysd = dict(
self.col.db.all( self.col.db.all(
""" f"""
select due, count() from cards select due, count() from cards
where did in %s and queue = 2 where did in %s and queue = {QUEUE_TYPE_REV}
and due between ? and ? and due between ? and ?
group by due group by due
order by due""" order by due"""
@ -132,20 +132,20 @@ order by due"""
return ret return ret
def countIdx(self, card): def countIdx(self, card):
if card.queue == 3: if card.queue == QUEUE_TYPE_DAY_LEARN_RELEARN:
return 1 return 1
return card.queue return card.queue
def answerButtons(self, card): def answerButtons(self, card):
if card.odue: if card.odue:
# normal review in dyn deck? # normal review in dyn deck?
if card.odid and card.queue == 2: if card.odid and card.queue == QUEUE_TYPE_REV:
return 4 return 4
conf = self._lrnConf(card) conf = self._lrnConf(card)
if card.type in (0, 1) or len(conf["delays"]) > 1: if card.type in (CARD_TYPE_NEW, CARD_TYPE_LRN) or len(conf["delays"]) > 1:
return 3 return 3
return 2 return 2
elif card.queue == 2: elif card.queue == QUEUE_TYPE_REV:
return 4 return 4
else: else:
return 3 return 3
@ -153,18 +153,25 @@ order by due"""
def unburyCards(self): def unburyCards(self):
"Unbury cards." "Unbury cards."
self.col.conf["lastUnburied"] = self.today self.col.conf["lastUnburied"] = self.today
self.col.log(self.col.db.list("select id from cards where queue = -2")) self.col.log(
self.col.db.execute("update cards set queue=type where queue = -2") self.col.db.list(
f"select id from cards where queue = {QUEUE_TYPE_SIBLING_BURIED}"
)
)
self.col.db.execute(
f"update cards set queue=type where queue = {QUEUE_TYPE_SIBLING_BURIED}"
)
def unburyCardsForDeck(self): def unburyCardsForDeck(self):
sids = ids2str(self.col.decks.active()) sids = ids2str(self.col.decks.active())
self.col.log( self.col.log(
self.col.db.list( self.col.db.list(
"select id from cards where queue = -2 and did in %s" % sids f"select id from cards where queue = {QUEUE_TYPE_SIBLING_BURIED} and did in %s"
% sids
) )
) )
self.col.db.execute( self.col.db.execute(
"update cards set mod=?,usn=?,queue=type where queue = -2 and did in %s" f"update cards set mod=?,usn=?,queue=type where queue = {QUEUE_TYPE_SIBLING_BURIED} and did in %s"
% sids, % sids,
intTime(), intTime(),
self.col.usn(), self.col.usn(),
@ -348,9 +355,9 @@ order by due"""
def _resetNewCount(self): def _resetNewCount(self):
cntFn = lambda did, lim: self.col.db.scalar( cntFn = lambda did, lim: self.col.db.scalar(
""" f"""
select count() from (select 1 from cards where select count() from (select 1 from cards where
did = ? and queue = 0 limit ?)""", did = ? and queue = {QUEUE_TYPE_NEW} limit ?)""",
did, did,
lim, lim,
) )
@ -373,8 +380,8 @@ did = ? and queue = 0 limit ?)""",
if lim: if lim:
# fill the queue with the current did # fill the queue with the current did
self._newQueue = self.col.db.list( self._newQueue = self.col.db.list(
""" f"""
select id from cards where did = ? and queue = 0 order by due,ord limit ?""", select id from cards where did = ? and queue = {QUEUE_TYPE_NEW} order by due,ord limit ?""",
did, did,
lim, lim,
) )
@ -436,9 +443,9 @@ did = ? and queue = 0 limit ?)""",
return 0 return 0
lim = min(lim, self.reportLimit) lim = min(lim, self.reportLimit)
return self.col.db.scalar( return self.col.db.scalar(
""" f"""
select count() from select count() from
(select 1 from cards where did = ? and queue = 0 limit ?)""", (select 1 from cards where did = ? and queue = {QUEUE_TYPE_NEW} limit ?)""",
did, did,
lim, lim,
) )
@ -452,9 +459,9 @@ select count() from
def totalNewForCurrentDeck(self): def totalNewForCurrentDeck(self):
return self.col.db.scalar( return self.col.db.scalar(
""" f"""
select count() from cards where id in ( select count() from cards where id in (
select id from cards where did in %s and queue = 0 limit ?)""" select id from cards where did in %s and queue = {QUEUE_TYPE_NEW} limit ?)"""
% ids2str(self.col.decks.active()), % ids2str(self.col.decks.active()),
self.reportLimit, self.reportLimit,
) )
@ -466,9 +473,9 @@ select id from cards where did in %s and queue = 0 limit ?)"""
# sub-day # sub-day
self.lrnCount = ( self.lrnCount = (
self.col.db.scalar( self.col.db.scalar(
""" f"""
select sum(left/1000) from (select left from cards where select sum(left/1000) from (select left from cards where
did in %s and queue = 1 and due < ? limit %d)""" did in %s and queue = {QUEUE_TYPE_LRN} and due < ? limit %d)"""
% (self._deckLimit(), self.reportLimit), % (self._deckLimit(), self.reportLimit),
self.dayCutoff, self.dayCutoff,
) )
@ -476,8 +483,8 @@ did in %s and queue = 1 and due < ? limit %d)"""
) )
# day # day
self.lrnCount += self.col.db.scalar( self.lrnCount += self.col.db.scalar(
""" f"""
select count() from cards where did in %s and queue = 3 select count() from cards where did in %s and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN}
and due <= ? limit %d""" and due <= ? limit %d"""
% (self._deckLimit(), self.reportLimit), % (self._deckLimit(), self.reportLimit),
self.today, self.today,
@ -496,9 +503,9 @@ and due <= ? limit %d"""
if self._lrnQueue: if self._lrnQueue:
return True return True
self._lrnQueue = self.col.db.all( self._lrnQueue = self.col.db.all(
""" f"""
select due, id from cards where select due, id from cards where
did in %s and queue = 1 and due < :lim did in %s and queue = {QUEUE_TYPE_LRN} and due < :lim
limit %d""" limit %d"""
% (self._deckLimit(), self.reportLimit), % (self._deckLimit(), self.reportLimit),
lim=self.dayCutoff, lim=self.dayCutoff,
@ -528,9 +535,9 @@ limit %d"""
did = self._lrnDids[0] did = self._lrnDids[0]
# fill the queue with the current did # fill the queue with the current did
self._lrnDayQueue = self.col.db.list( self._lrnDayQueue = self.col.db.list(
""" f"""
select id from cards where select id from cards where
did = ? and queue = 3 and due <= ? limit ?""", did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
did, did,
self.today, self.today,
self.queueLimit, self.queueLimit,
@ -556,25 +563,25 @@ did = ? and queue = 3 and due <= ? limit ?""",
# ease 1=no, 2=yes, 3=remove # ease 1=no, 2=yes, 3=remove
conf = self._lrnConf(card) conf = self._lrnConf(card)
if card.odid and not card.wasNew: if card.odid and not card.wasNew:
type = 3 type = REVLOG_CRAM
elif card.type == 2: elif card.type == CARD_TYPE_REV:
type = 2 type = REVLOG_RELRN
else: else:
type = 0 type = REVLOG_LRN
leaving = False leaving = False
# lrnCount was decremented once when card was fetched # lrnCount was decremented once when card was fetched
lastLeft = card.left lastLeft = card.left
# immediate graduate? # immediate graduate?
if ease == 3: if ease == BUTTON_THREE:
self._rescheduleAsRev(card, conf, True) self._rescheduleAsRev(card, conf, True)
leaving = True leaving = True
# graduation time? # graduation time?
elif ease == 2 and (card.left % 1000) - 1 <= 0: elif ease == BUTTON_TWO and (card.left % 1000) - 1 <= 0:
self._rescheduleAsRev(card, conf, False) self._rescheduleAsRev(card, conf, False)
leaving = True leaving = True
else: else:
# one step towards graduation # one step towards graduation
if ease == 2: if ease == BUTTON_TWO:
# decrement real left count and recalculate left today # decrement real left count and recalculate left today
left = (card.left % 1000) - 1 left = (card.left % 1000) - 1
card.left = self._leftToday(conf["delays"], left) * 1000 + left card.left = self._leftToday(conf["delays"], left) * 1000 + left
@ -601,7 +608,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
# if the queue is not empty and there's nothing else to do, make # if the queue is not empty and there's nothing else to do, make
# sure we don't put it at the head of the queue and end up showing # sure we don't put it at the head of the queue and end up showing
# it twice in a row # it twice in a row
card.queue = 1 card.queue = QUEUE_TYPE_LRN
if self._lrnQueue and not self.revCount and not self.newCount: if self._lrnQueue and not self.revCount and not self.newCount:
smallestDue = self._lrnQueue[0][0] smallestDue = self._lrnQueue[0][0]
card.due = max(card.due, smallestDue + 1) card.due = max(card.due, smallestDue + 1)
@ -611,7 +618,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
# day learn queue # day learn queue
ahead = ((card.due - self.dayCutoff) // 86400) + 1 ahead = ((card.due - self.dayCutoff) // 86400) + 1
card.due = self.today + ahead card.due = self.today + ahead
card.queue = 3 card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN
self._logLrn(card, ease, conf, leaving, type, lastLeft) self._logLrn(card, ease, conf, leaving, type, lastLeft)
def _delayForGrade(self, conf, left): def _delayForGrade(self, conf, left):
@ -627,13 +634,13 @@ did = ? and queue = 3 and due <= ? limit ?""",
return delay * 60 return delay * 60
def _lrnConf(self, card): def _lrnConf(self, card):
if card.type == 2: if card.type == CARD_TYPE_REV:
return self._lapseConf(card) return self._lapseConf(card)
else: else:
return self._newConf(card) return self._newConf(card)
def _rescheduleAsRev(self, card, conf, early): def _rescheduleAsRev(self, card, conf, early):
lapse = card.type == 2 lapse = card.type == CARD_TYPE_REV
if lapse: if lapse:
if self._resched(card): if self._resched(card):
card.due = max(self.today + 1, card.odue) card.due = max(self.today + 1, card.odue)
@ -642,8 +649,8 @@ did = ? and queue = 3 and due <= ? limit ?""",
card.odue = 0 card.odue = 0
else: else:
self._rescheduleNew(card, conf, early) self._rescheduleNew(card, conf, early)
card.queue = 2 card.queue = QUEUE_TYPE_REV
card.type = 2 card.type = CARD_TYPE_REV
# if we were dynamic, graduating means moving back to the old deck # if we were dynamic, graduating means moving back to the old deck
resched = self._resched(card) resched = self._resched(card)
if card.odid: if card.odid:
@ -652,11 +659,11 @@ did = ? and queue = 3 and due <= ? limit ?""",
card.odid = 0 card.odid = 0
# if rescheduling is off, it needs to be set back to a new card # if rescheduling is off, it needs to be set back to a new card
if not resched and not lapse: if not resched and not lapse:
card.queue = card.type = 0 card.queue = card.type = CARD_TYPE_NEW
card.due = self.col.nextID("pos") card.due = self.col.nextID("pos")
def _startingLeft(self, card): def _startingLeft(self, card):
if card.type == 2: if card.type == CARD_TYPE_REV:
conf = self._lapseConf(card) conf = self._lapseConf(card)
else: else:
conf = self._lrnConf(card) conf = self._lrnConf(card)
@ -678,7 +685,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
return ok + 1 return ok + 1
def _graduatingIvl(self, card, conf, early, adj=True): def _graduatingIvl(self, card, conf, early, adj=True):
if card.type == 2: if card.type == CARD_TYPE_REV:
# lapsed card being relearnt # lapsed card being relearnt
if card.odid: if card.odid:
if conf["resched"]: if conf["resched"]:
@ -736,25 +743,28 @@ did = ? and queue = 3 and due <= ? limit ?""",
extra = " and did in " + ids2str(self.col.decks.allIds()) extra = " and did in " + ids2str(self.col.decks.allIds())
# review cards in relearning # review cards in relearning
self.col.db.execute( self.col.db.execute(
""" f"""
update cards set update cards set
due = odue, queue = 2, mod = %d, usn = %d, odue = 0 due = odue, queue = {QUEUE_TYPE_REV}, mod = %d, usn = %d, odue = 0
where queue in (1,3) and type = 2 where queue in ({QUEUE_TYPE_LRN},{QUEUE_TYPE_DAY_LEARN_RELEARN}) and type = {CARD_TYPE_REV}
%s %s
""" """
% (intTime(), self.col.usn(), extra) % (intTime(), self.col.usn(), extra)
) )
# new cards in learning # new cards in learning
self.forgetCards( self.forgetCards(
self.col.db.list("select id from cards where queue in (1,3) %s" % extra) self.col.db.list(
f"select id from cards where queue in ({QUEUE_TYPE_LRN},{QUEUE_TYPE_DAY_LEARN_RELEARN}) %s"
% extra
)
) )
def _lrnForDeck(self, did): def _lrnForDeck(self, did):
cnt = ( cnt = (
self.col.db.scalar( self.col.db.scalar(
""" f"""
select sum(left/1000) from select sum(left/1000) from
(select left from cards where did = ? and queue = 1 and due < ? limit ?)""", (select left from cards where did = ? and queue = {QUEUE_TYPE_LRN} and due < ? limit ?)""",
did, did,
intTime() + self.col.conf["collapseTime"], intTime() + self.col.conf["collapseTime"],
self.reportLimit, self.reportLimit,
@ -762,9 +772,9 @@ select sum(left/1000) from
or 0 or 0
) )
return cnt + self.col.db.scalar( return cnt + self.col.db.scalar(
""" f"""
select count() from select count() from
(select 1 from cards where did = ? and queue = 3 (select 1 from cards where did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN}
and due <= ? limit ?)""", and due <= ? limit ?)""",
did, did,
self.today, self.today,
@ -786,9 +796,9 @@ and due <= ? limit ?)""",
def _revForDeck(self, did, lim): def _revForDeck(self, did, lim):
lim = min(lim, self.reportLimit) lim = min(lim, self.reportLimit)
return self.col.db.scalar( return self.col.db.scalar(
""" f"""
select count() from select count() from
(select 1 from cards where did = ? and queue = 2 (select 1 from cards where did = ? and queue = {QUEUE_TYPE_REV}
and due <= ? limit ?)""", and due <= ? limit ?)""",
did, did,
self.today, self.today,
@ -798,9 +808,9 @@ and due <= ? limit ?)""",
def _resetRevCount(self): def _resetRevCount(self):
def cntFn(did, lim): def cntFn(did, lim):
return self.col.db.scalar( return self.col.db.scalar(
""" f"""
select count() from (select id from cards where select count() from (select id from cards where
did = ? and queue = 2 and due <= ? limit %d)""" did = ? and queue = {QUEUE_TYPE_REV} and due <= ? limit %d)"""
% lim, % lim,
did, did,
self.today, self.today,
@ -824,9 +834,9 @@ did = ? and queue = 2 and due <= ? limit %d)"""
if lim: if lim:
# fill the queue with the current did # fill the queue with the current did
self._revQueue = self.col.db.list( self._revQueue = self.col.db.list(
""" f"""
select id from cards where select id from cards where
did = ? and queue = 2 and due <= ? limit ?""", did = ? and queue = {QUEUE_TYPE_REV} and due <= ? limit ?""",
did, did,
self.today, self.today,
lim, lim,
@ -861,9 +871,9 @@ did = ? and queue = 2 and due <= ? limit ?""",
def totalRevForCurrentDeck(self): def totalRevForCurrentDeck(self):
return self.col.db.scalar( return self.col.db.scalar(
""" f"""
select count() from cards where id in ( select count() from cards where id in (
select id from cards where did in %s and queue = 2 and due <= ? limit ?)""" select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? limit ?)"""
% ids2str(self.col.decks.active()), % ids2str(self.col.decks.active()),
self.today, self.today,
self.reportLimit, self.reportLimit,
@ -874,7 +884,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
def _answerRevCard(self, card, ease): def _answerRevCard(self, card, ease):
delay = 0 delay = 0
if ease == 1: if ease == BUTTON_ONE:
delay = self._rescheduleLapse(card) delay = self._rescheduleLapse(card)
else: else:
self._rescheduleRev(card, ease) self._rescheduleRev(card, ease)
@ -893,7 +903,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
card.odue = card.due card.odue = card.due
# if suspended as a leech, nothing to do # if suspended as a leech, nothing to do
delay = 0 delay = 0
if self._checkLeech(card, conf) and card.queue == -1: if self._checkLeech(card, conf) and card.queue == QUEUE_TYPE_SUSPENDED:
return delay return delay
# if no relearning steps, nothing to do # if no relearning steps, nothing to do
if not conf["delays"]: if not conf["delays"]:
@ -907,13 +917,13 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# queue 1 # queue 1
if card.due < self.dayCutoff: if card.due < self.dayCutoff:
self.lrnCount += card.left // 1000 self.lrnCount += card.left // 1000
card.queue = 1 card.queue = QUEUE_TYPE_LRN
heappush(self._lrnQueue, (card.due, card.id)) heappush(self._lrnQueue, (card.due, card.id))
else: else:
# day learn queue # day learn queue
ahead = ((card.due - self.dayCutoff) // 86400) + 1 ahead = ((card.due - self.dayCutoff) // 86400) + 1
card.due = self.today + ahead card.due = self.today + ahead
card.queue = 3 card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN
return delay return delay
def _nextLapseIvl(self, card, conf): def _nextLapseIvl(self, card, conf):
@ -946,7 +956,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
card.lastIvl, card.lastIvl,
card.factor, card.factor,
card.timeTaken(), card.timeTaken(),
1, REVLOG_REV,
) )
try: try:
@ -969,11 +979,11 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
ivl4 = self._constrainedIvl( ivl4 = self._constrainedIvl(
(card.ivl + delay) * fct * conf["ease4"], conf, ivl3 (card.ivl + delay) * fct * conf["ease4"], conf, ivl3
) )
if ease == 2: if ease == BUTTON_TWO:
interval = ivl2 interval = ivl2
elif ease == 3: elif ease == BUTTON_THREE:
interval = ivl3 interval = ivl3
elif ease == 4: elif ease == BUTTON_FOUR:
interval = ivl4 interval = ivl4
# interval capped? # interval capped?
return min(interval, conf["maxIvl"]) return min(interval, conf["maxIvl"])
@ -1058,9 +1068,9 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self.col.log(self.col.db.list("select id from cards where %s" % lim)) self.col.log(self.col.db.list("select id from cards where %s" % lim))
# move out of cram queue # move out of cram queue
self.col.db.execute( self.col.db.execute(
""" f"""
update cards set did = odid, queue = (case when type = 1 then 0 update cards set did = odid, queue = (case when type = {CARD_TYPE_LRN} then {QUEUE_TYPE_NEW}
else type end), type = (case when type = 1 then 0 else type end), else type end), type = (case when type = {CARD_TYPE_LRN} then {CARD_TYPE_NEW} else type end),
due = odue, odue = 0, odid = 0, usn = ? where %s""" due = odue, odue = 0, odid = 0, usn = ? where %s"""
% lim, % lim,
self.col.usn(), self.col.usn(),
@ -1088,7 +1098,7 @@ due = odue, odue = 0, odid = 0, usn = ? where %s"""
t = "c.due" t = "c.due"
elif o == DYN_DUEPRIORITY: elif o == DYN_DUEPRIORITY:
t = ( t = (
"(case when queue=2 and due <= %d then (ivl / cast(%d-due+0.001 as real)) else 100000+due end)" f"(case when queue={QUEUE_TYPE_REV} and due <= %d then (ivl / cast(%d-due+0.001 as real)) else 100000+due end)"
% (self.today, self.today) % (self.today, self.today)
) )
else: else:
@ -1106,9 +1116,9 @@ due = odue, odue = 0, odid = 0, usn = ? where %s"""
data.append((did, -100000 + c, u, id)) data.append((did, -100000 + c, u, id))
# due reviews stay in the review queue. careful: can't use # due reviews stay in the review queue. careful: can't use
# "odid or did", as sqlite converts to boolean # "odid or did", as sqlite converts to boolean
queue = """ queue = f"""
(case when type=2 and (case when odue then odue <= %d else due <= %d end) (case when type={CARD_TYPE_REV} and (case when odue then odue <= %d else due <= %d end)
then 2 else 0 end)""" then {QUEUE_TYPE_REV} else {QUEUE_TYPE_NEW} end)"""
queue %= (self.today, self.today) queue %= (self.today, self.today)
self.col.db.executemany( self.col.db.executemany(
""" """
@ -1121,7 +1131,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?"""
) )
def _dynIvlBoost(self, card): def _dynIvlBoost(self, card):
assert card.odid and card.type == 2 assert card.odid and card.type == CARD_TYPE_REV
assert card.factor assert card.factor
elapsed = card.ivl - (card.odue - self.today) elapsed = card.ivl - (card.odue - self.today)
factor = ((card.factor / 1000) + 1.2) / 2 factor = ((card.factor / 1000) + 1.2) / 2
@ -1145,14 +1155,14 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?"""
f.flush() f.flush()
# handle # handle
a = conf["leechAction"] a = conf["leechAction"]
if a == 0: if a == LEECH_SUSPEND:
# if it has an old due, remove it from cram/relearning # if it has an old due, remove it from cram/relearning
if card.odue: if card.odue:
card.due = card.odue card.due = card.odue
if card.odid: if card.odid:
card.did = card.odid card.did = card.odid
card.odue = card.odid = 0 card.odue = card.odid = 0
card.queue = -1 card.queue = QUEUE_TYPE_SUSPENDED
# notify UI # notify UI
hooks.card_did_leech(card) hooks.card_did_leech(card)
return True return True
@ -1311,7 +1321,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
"True if there are any rev cards due." "True if there are any rev cards due."
return self.col.db.scalar( return self.col.db.scalar(
( (
"select 1 from cards where did in %s and queue = 2 " f"select 1 from cards where did in %s and queue = {QUEUE_TYPE_REV} "
"and due <= ? limit 1" "and due <= ? limit 1"
) )
% self._deckLimit(), % self._deckLimit(),
@ -1321,14 +1331,18 @@ To study outside of the normal schedule, click the Custom Study button below."""
def newDue(self): def newDue(self):
"True if there are any new cards due." "True if there are any new cards due."
return self.col.db.scalar( return self.col.db.scalar(
("select 1 from cards where did in %s and queue = 0 " "limit 1") (
f"select 1 from cards where did in %s and queue = {QUEUE_TYPE_NEW} "
"limit 1"
)
% self._deckLimit() % self._deckLimit()
) )
def haveBuried(self): def haveBuried(self):
sdids = ids2str(self.col.decks.active()) sdids = ids2str(self.col.decks.active())
cnt = self.col.db.scalar( cnt = self.col.db.scalar(
"select 1 from cards where queue = -2 and did in %s limit 1" % sdids f"select 1 from cards where queue = {QUEUE_TYPE_SIBLING_BURIED} and did in %s limit 1"
% sdids
) )
return not not cnt return not not cnt
@ -1347,9 +1361,9 @@ To study outside of the normal schedule, click the Custom Study button below."""
def nextIvl(self, card, ease): def nextIvl(self, card, ease):
"Return the next interval for CARD, in seconds." "Return the next interval for CARD, in seconds."
if card.queue in (0, 1, 3): if card.queue in (QUEUE_TYPE_NEW, QUEUE_TYPE_LRN, QUEUE_TYPE_DAY_LEARN_RELEARN):
return self._nextLrnIvl(card, ease) return self._nextLrnIvl(card, ease)
elif ease == 1: elif ease == BUTTON_ONE:
# lapsed # lapsed
conf = self._lapseConf(card) conf = self._lapseConf(card)
if conf["delays"]: if conf["delays"]:
@ -1364,10 +1378,10 @@ To study outside of the normal schedule, click the Custom Study button below."""
if card.queue == 0: if card.queue == 0:
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
conf = self._lrnConf(card) conf = self._lrnConf(card)
if ease == 1: if ease == BUTTON_ONE:
# fail # fail
return self._delayForGrade(conf, len(conf["delays"])) return self._delayForGrade(conf, len(conf["delays"]))
elif ease == 3: elif ease == BUTTON_THREE:
# early removal # early removal
if not self._resched(card): if not self._resched(card):
return 0 return 0
@ -1391,7 +1405,8 @@ To study outside of the normal schedule, click the Custom Study button below."""
self.remFromDyn(ids) self.remFromDyn(ids)
self.removeLrn(ids) self.removeLrn(ids)
self.col.db.execute( self.col.db.execute(
"update cards set queue=-1,mod=?,usn=? where id in " + ids2str(ids), f"update cards set queue={QUEUE_TYPE_SUSPENDED},mod=?,usn=? where id in "
+ ids2str(ids),
intTime(), intTime(),
self.col.usn(), self.col.usn(),
) )
@ -1401,7 +1416,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
self.col.log(ids) self.col.log(ids)
self.col.db.execute( self.col.db.execute(
"update cards set queue=type,mod=?,usn=? " "update cards set queue=type,mod=?,usn=? "
"where queue = -1 and id in " + ids2str(ids), f"where queue = {QUEUE_TYPE_SUSPENDED} and id in " + ids2str(ids),
intTime(), intTime(),
self.col.usn(), self.col.usn(),
) )
@ -1411,8 +1426,8 @@ To study outside of the normal schedule, click the Custom Study button below."""
self.remFromDyn(cids) self.remFromDyn(cids)
self.removeLrn(cids) self.removeLrn(cids)
self.col.db.execute( self.col.db.execute(
""" f"""
update cards set queue=-2,mod=?,usn=? where id in """ update cards set queue={QUEUE_TYPE_SIBLING_BURIED},mod=?,usn=? where id in """
+ ids2str(cids), + ids2str(cids),
intTime(), intTime(),
self.col.usn(), self.col.usn(),
@ -1436,14 +1451,14 @@ update cards set queue=-2,mod=?,usn=? where id in """
buryRev = rconf.get("bury", True) buryRev = rconf.get("bury", True)
# loop through and remove from queues # loop through and remove from queues
for cid, queue in self.col.db.execute( for cid, queue in self.col.db.execute(
""" f"""
select id, queue from cards where nid=? and id!=? select id, queue from cards where nid=? and id!=?
and (queue=0 or (queue=2 and due<=?))""", and (queue={QUEUE_TYPE_NEW} or (queue={QUEUE_TYPE_REV} and due<=?))""",
card.nid, card.nid,
card.id, card.id,
self.today, self.today,
): ):
if queue == 2: if queue == QUEUE_TYPE_REV:
if buryRev: if buryRev:
toBury.append(cid) toBury.append(cid)
# if bury disabled, we still discard to give same-day spacing # if bury disabled, we still discard to give same-day spacing
@ -1462,7 +1477,8 @@ and (queue=0 or (queue=2 and due<=?))""",
# then bury # then bury
if toBury: if toBury:
self.col.db.execute( self.col.db.execute(
"update cards set queue=-2,mod=?,usn=? where id in " + ids2str(toBury), f"update cards set queue={QUEUE_TYPE_SIBLING_BURIED},mod=?,usn=? where id in "
+ ids2str(toBury),
intTime(), intTime(),
self.col.usn(), self.col.usn(),
) )
@ -1475,11 +1491,14 @@ and (queue=0 or (queue=2 and due<=?))""",
"Put cards at the end of the new queue." "Put cards at the end of the new queue."
self.remFromDyn(ids) self.remFromDyn(ids)
self.col.db.execute( self.col.db.execute(
"update cards set type=0,queue=0,ivl=0,due=0,odue=0,factor=?" f"update cards set type={CARD_TYPE_NEW},queue={QUEUE_TYPE_NEW},ivl=0,due=0,odue=0,factor=?"
" where id in " + ids2str(ids), " where id in " + ids2str(ids),
STARTING_FACTOR, STARTING_FACTOR,
) )
pmax = self.col.db.scalar("select max(due) from cards where type=0") or 0 pmax = (
self.col.db.scalar(f"select max(due) from cards where type={CARD_TYPE_NEW}")
or 0
)
# takes care of mod + usn # takes care of mod + usn
self.sortCards(ids, start=pmax + 1) self.sortCards(ids, start=pmax + 1)
self.col.log(ids) self.col.log(ids)
@ -1503,8 +1522,8 @@ and (queue=0 or (queue=2 and due<=?))""",
) )
self.remFromDyn(ids) self.remFromDyn(ids)
self.col.db.executemany( self.col.db.executemany(
""" f"""
update cards set type=2,queue=2,ivl=:ivl,due=:due,odue=0, update cards set type={CARD_TYPE_REV},queue={QUEUE_TYPE_REV},ivl=:ivl,due=:due,odue=0,
usn=:usn,mod=:mod,factor=:fact where id=:id""", usn=:usn,mod=:mod,factor=:fact where id=:id""",
d, d,
) )
@ -1515,11 +1534,12 @@ usn=:usn,mod=:mod,factor=:fact where id=:id""",
sids = ids2str(ids) sids = ids2str(ids)
# we want to avoid resetting due number of existing new cards on export # we want to avoid resetting due number of existing new cards on export
nonNew = self.col.db.list( nonNew = self.col.db.list(
"select id from cards where id in %s and (queue != 0 or type != 0)" % sids f"select id from cards where id in %s and (queue != {QUEUE_TYPE_NEW} or type != {CARD_TYPE_NEW})"
% sids
) )
# reset all cards # reset all cards
self.col.db.execute( self.col.db.execute(
"update cards set reps=0,lapses=0,odid=0,odue=0,queue=0" f"update cards set reps=0,lapses=0,odid=0,odue=0,queue={QUEUE_TYPE_NEW}"
" where id in %s" % sids " where id in %s" % sids
) )
# and forget any non-new cards, changing their due numbers # and forget any non-new cards, changing their due numbers
@ -1553,16 +1573,16 @@ usn=:usn,mod=:mod,factor=:fact where id=:id""",
# shift? # shift?
if shift: if shift:
low = self.col.db.scalar( low = self.col.db.scalar(
"select min(due) from cards where due >= ? and type = 0 " f"select min(due) from cards where due >= ? and type = {CARD_TYPE_NEW} "
"and id not in %s" % scids, "and id not in %s" % scids,
start, start,
) )
if low is not None: if low is not None:
shiftby = high - low + 1 shiftby = high - low + 1
self.col.db.execute( self.col.db.execute(
""" f"""
update cards set mod=?, usn=?, due=due+? where id not in %s update cards set mod=?, usn=?, due=due+? where id not in %s
and due >= ? and queue = 0""" and due >= ? and queue = {QUEUE_TYPE_NEW}"""
% scids, % scids,
now, now,
self.col.usn(), self.col.usn(),
@ -1572,7 +1592,7 @@ and due >= ? and queue = 0"""
# reorder cards # reorder cards
d = [] d = []
for id, nid in self.col.db.execute( for id, nid in self.col.db.execute(
"select id, nid from cards where type = 0 and id in " + scids f"select id, nid from cards where type = {CARD_TYPE_NEW} and id in " + scids
): ):
d.append(dict(now=now, due=due[nid], usn=self.col.usn(), cid=id)) d.append(dict(now=now, due=due[nid], usn=self.col.usn(), cid=id))
self.col.db.executemany( self.col.db.executemany(

View file

@ -22,13 +22,9 @@ from anki.rsbackend import SchedTimingToday
from anki.utils import fmtTimeSpan, ids2str, intTime from anki.utils import fmtTimeSpan, ids2str, intTime
# card types: 0=new, 1=lrn, 2=rev, 3=relrn # card types: 0=new, 1=lrn, 2=rev, 3=relrn
CARD_TYPE_RELEARNING = 3
# queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn, # queue types: 0=new, 1=(re)lrn, 2=rev, 3=day (re)lrn,
# 4=preview, -1=suspended, -2=sibling buried, -3=manually buried # 4=preview, -1=suspended, -2=sibling buried, -3=manually buried
QUEUE_TYPE_PREVIEW = 4
QUEUE_TYPE_DAY_LEARN_RELEARN = 3
QUEUE_TYPE_SIBLING_BURIED = -2
QUEUE_TYPE_MANUALLY_BURIED = -3
# revlog types: 0=lrn, 1=rev, 2=relrn, 3=early review # revlog types: 0=lrn, 1=rev, 2=relrn, 3=early review
# positive revlog intervals are in days (rev), negative in seconds (lrn) # positive revlog intervals are in days (rev), negative in seconds (lrn)
# odue/odid store original due/did when cards moved to filtered deck # odue/odid store original due/did when cards moved to filtered deck
@ -95,18 +91,18 @@ class Scheduler:
card.reps += 1 card.reps += 1
if card.queue == 0: if card.queue == QUEUE_TYPE_NEW:
# came from the new queue, move to learning # came from the new queue, move to learning
card.queue = 1 card.queue = QUEUE_TYPE_LRN
card.type = 1 card.type = CARD_TYPE_LRN
# init reps to graduation # init reps to graduation
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
# update daily limit # update daily limit
self._updateStats(card, "new") self._updateStats(card, "new")
if card.queue in (1, QUEUE_TYPE_DAY_LEARN_RELEARN): if card.queue in (QUEUE_TYPE_LRN, QUEUE_TYPE_DAY_LEARN_RELEARN):
self._answerLrnCard(card, ease) self._answerLrnCard(card, ease)
elif card.queue == 2: elif card.queue == QUEUE_TYPE_REV:
self._answerRevCard(card, ease) self._answerRevCard(card, ease)
# update daily limit # update daily limit
self._updateStats(card, "rev") self._updateStats(card, "rev")
@ -121,12 +117,13 @@ class Scheduler:
def _answerCardPreview(self, card: Card, ease: int) -> None: def _answerCardPreview(self, card: Card, ease: int) -> None:
assert 1 <= ease <= 2 assert 1 <= ease <= 2
if ease == 1: if ease == BUTTON_ONE:
# repeat after delay # repeat after delay
card.queue = QUEUE_TYPE_PREVIEW card.queue = QUEUE_TYPE_PREVIEW
card.due = intTime() + self._previewDelay(card) card.due = intTime() + self._previewDelay(card)
self.lrnCount += 1 self.lrnCount += 1
else: else:
# BUTTON_TWO
# restore original card state and remove from filtered deck # restore original card state and remove from filtered deck
self._restorePreviewCard(card) self._restorePreviewCard(card)
self._removeFromFiltered(card) self._removeFromFiltered(card)
@ -142,9 +139,9 @@ class Scheduler:
"Return counts over next DAYS. Includes today." "Return counts over next DAYS. Includes today."
daysd = dict( daysd = dict(
self.col.db.all( self.col.db.all(
""" f"""
select due, count() from cards select due, count() from cards
where did in %s and queue = 2 where did in %s and queue = {QUEUE_TYPE_REV}
and due between ? and ? and due between ? and ?
group by due group by due
order by due""" order by due"""
@ -368,9 +365,9 @@ order by due"""
def _resetNewCount(self) -> None: def _resetNewCount(self) -> None:
cntFn = lambda did, lim: self.col.db.scalar( cntFn = lambda did, lim: self.col.db.scalar(
""" f"""
select count() from (select 1 from cards where select count() from (select 1 from cards where
did = ? and queue = 0 limit ?)""", did = ? and queue = {QUEUE_TYPE_NEW} limit ?)""",
did, did,
lim, lim,
) )
@ -393,8 +390,8 @@ did = ? and queue = 0 limit ?)""",
if lim: if lim:
# fill the queue with the current did # fill the queue with the current did
self._newQueue = self.col.db.list( self._newQueue = self.col.db.list(
""" f"""
select id from cards where did = ? and queue = 0 order by due,ord limit ?""", select id from cards where did = ? and queue = {QUEUE_TYPE_NEW} order by due,ord limit ?""",
did, did,
lim, lim,
) )
@ -462,9 +459,9 @@ did = ? and queue = 0 limit ?)""",
return 0 return 0
lim = min(lim, self.reportLimit) lim = min(lim, self.reportLimit)
return self.col.db.scalar( return self.col.db.scalar(
""" f"""
select count() from select count() from
(select 1 from cards where did = ? and queue = 0 limit ?)""", (select 1 from cards where did = ? and queue = {QUEUE_TYPE_NEW} limit ?)""",
did, did,
lim, lim,
) )
@ -478,9 +475,9 @@ select count() from
def totalNewForCurrentDeck(self) -> Any: def totalNewForCurrentDeck(self) -> Any:
return self.col.db.scalar( return self.col.db.scalar(
""" f"""
select count() from cards where id in ( select count() from cards where id in (
select id from cards where did in %s and queue = 0 limit ?)""" select id from cards where did in %s and queue = {QUEUE_TYPE_NEW} limit ?)"""
% self._deckLimit(), % self._deckLimit(),
self.reportLimit, self.reportLimit,
) )
@ -504,8 +501,8 @@ select id from cards where did in %s and queue = 0 limit ?)"""
# sub-day # sub-day
self.lrnCount = ( self.lrnCount = (
self.col.db.scalar( self.col.db.scalar(
""" f"""
select count() from cards where did in %s and queue = 1 select count() from cards where did in %s and queue = {QUEUE_TYPE_LRN}
and due < ?""" and due < ?"""
% (self._deckLimit()), % (self._deckLimit()),
self._lrnCutoff, self._lrnCutoff,
@ -545,7 +542,7 @@ select count() from cards where did in %s and queue = {QUEUE_TYPE_PREVIEW}
self._lrnQueue = self.col.db.all( self._lrnQueue = self.col.db.all(
f""" f"""
select due, id from cards where select due, id from cards where
did in %s and queue in (1,{QUEUE_TYPE_PREVIEW}) and due < :lim did in %s and queue in ({QUEUE_TYPE_LRN},{QUEUE_TYPE_PREVIEW}) and due < :lim
limit %d""" limit %d"""
% (self._deckLimit(), self.reportLimit), % (self._deckLimit(), self.reportLimit),
lim=cutoff, lim=cutoff,
@ -606,28 +603,28 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
def _answerLrnCard(self, card: Card, ease: int) -> None: def _answerLrnCard(self, card: Card, ease: int) -> None:
conf = self._lrnConf(card) conf = self._lrnConf(card)
if card.type in (2, CARD_TYPE_RELEARNING): if card.type in (CARD_TYPE_REV, CARD_TYPE_RELEARNING):
type = 2 type = REVLOG_RELRN
else: else:
type = 0 type = REVLOG_LRN
# lrnCount was decremented once when card was fetched # lrnCount was decremented once when card was fetched
lastLeft = card.left lastLeft = card.left
leaving = False leaving = False
# immediate graduate? # immediate graduate?
if ease == 4: if ease == BUTTON_FOUR:
self._rescheduleAsRev(card, conf, True) self._rescheduleAsRev(card, conf, True)
leaving = True leaving = True
# next step? # next step?
elif ease == 3: elif ease == BUTTON_THREE:
# graduation time? # graduation time?
if (card.left % 1000) - 1 <= 0: if (card.left % 1000) - 1 <= 0:
self._rescheduleAsRev(card, conf, False) self._rescheduleAsRev(card, conf, False)
leaving = True leaving = True
else: else:
self._moveToNextStep(card, conf) self._moveToNextStep(card, conf)
elif ease == 2: elif ease == BUTTON_TWO:
self._repeatStep(card, conf) self._repeatStep(card, conf)
else: else:
# back to first step # back to first step
@ -673,7 +670,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
maxExtra = min(300, int(delay * 0.25)) maxExtra = min(300, int(delay * 0.25))
fuzz = random.randrange(0, maxExtra) fuzz = random.randrange(0, maxExtra)
card.due = min(self.dayCutoff - 1, card.due + fuzz) card.due = min(self.dayCutoff - 1, card.due + fuzz)
card.queue = 1 card.queue = QUEUE_TYPE_LRN
if card.due < (intTime() + self.col.conf["collapseTime"]): if card.due < (intTime() + self.col.conf["collapseTime"]):
self.lrnCount += 1 self.lrnCount += 1
# if the queue is not empty and there's nothing else to do, make # if the queue is not empty and there's nothing else to do, make
@ -714,13 +711,13 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
return avg return avg
def _lrnConf(self, card: Card) -> Any: def _lrnConf(self, card: Card) -> Any:
if card.type in (2, CARD_TYPE_RELEARNING): if card.type in (CARD_TYPE_REV, CARD_TYPE_RELEARNING):
return self._lapseConf(card) return self._lapseConf(card)
else: else:
return self._newConf(card) return self._newConf(card)
def _rescheduleAsRev(self, card: Card, conf: Dict[str, Any], early: bool) -> None: def _rescheduleAsRev(self, card: Card, conf: Dict[str, Any], early: bool) -> None:
lapse = card.type in (2, CARD_TYPE_RELEARNING) lapse = card.type in (CARD_TYPE_REV, CARD_TYPE_RELEARNING)
if lapse: if lapse:
self._rescheduleGraduatingLapse(card, early) self._rescheduleGraduatingLapse(card, early)
@ -735,8 +732,8 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
if early: if early:
card.ivl += 1 card.ivl += 1
card.due = self.today + card.ivl card.due = self.today + card.ivl
card.queue = 2 card.queue = QUEUE_TYPE_REV
card.type = 2 card.type = CARD_TYPE_REV
def _startingLeft(self, card: Card) -> int: def _startingLeft(self, card: Card) -> int:
if card.type == CARD_TYPE_RELEARNING: if card.type == CARD_TYPE_RELEARNING:
@ -768,7 +765,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
def _graduatingIvl( def _graduatingIvl(
self, card: Card, conf: Dict[str, Any], early: bool, fuzz: bool = True self, card: Card, conf: Dict[str, Any], early: bool, fuzz: bool = True
) -> Any: ) -> Any:
if card.type in (2, CARD_TYPE_RELEARNING): if card.type in (CARD_TYPE_REV, CARD_TYPE_RELEARNING):
bonus = early and 1 or 0 bonus = early and 1 or 0
return card.ivl + bonus return card.ivl + bonus
if not early: if not early:
@ -786,7 +783,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
card.ivl = self._graduatingIvl(card, conf, early) card.ivl = self._graduatingIvl(card, conf, early)
card.due = self.today + card.ivl card.due = self.today + card.ivl
card.factor = conf["initialFactor"] card.factor = conf["initialFactor"]
card.type = card.queue = 2 card.type = card.queue = QUEUE_TYPE_REV
def _logLrn( def _logLrn(
self, self,
@ -801,7 +798,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
if leaving: if leaving:
ivl = card.ivl ivl = card.ivl
else: else:
if ease == 2: if ease == BUTTON_TWO:
ivl = -self._delayForRepeatingGrade(conf, card.left) ivl = -self._delayForRepeatingGrade(conf, card.left)
else: else:
ivl = -self._delayForGrade(conf, card.left) ivl = -self._delayForGrade(conf, card.left)
@ -830,9 +827,9 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
def _lrnForDeck(self, did: int) -> Any: def _lrnForDeck(self, did: int) -> Any:
cnt = ( cnt = (
self.col.db.scalar( self.col.db.scalar(
""" f"""
select count() from select count() from
(select null from cards where did = ? and queue = 1 and due < ? limit ?)""", (select null from cards where did = ? and queue = {QUEUE_TYPE_LRN} and due < ? limit ?)""",
did, did,
intTime() + self.col.conf["collapseTime"], intTime() + self.col.conf["collapseTime"],
self.reportLimit, self.reportLimit,
@ -883,9 +880,9 @@ and due <= ? limit ?)""",
dids = [did] + self.col.decks.childDids(did, childMap) dids = [did] + self.col.decks.childDids(did, childMap)
lim = min(lim, self.reportLimit) lim = min(lim, self.reportLimit)
return self.col.db.scalar( return self.col.db.scalar(
""" f"""
select count() from select count() from
(select 1 from cards where did in %s and queue = 2 (select 1 from cards where did in %s and queue = {QUEUE_TYPE_REV}
and due <= ? limit ?)""" and due <= ? limit ?)"""
% ids2str(dids), % ids2str(dids),
self.today, self.today,
@ -895,9 +892,9 @@ and due <= ? limit ?)"""
def _resetRevCount(self) -> None: def _resetRevCount(self) -> None:
lim = self._currentRevLimit() lim = self._currentRevLimit()
self.revCount = self.col.db.scalar( self.revCount = self.col.db.scalar(
""" f"""
select count() from (select id from cards where select count() from (select id from cards where
did in %s and queue = 2 and due <= ? limit ?)""" did in %s and queue = {QUEUE_TYPE_REV} and due <= ? limit ?)"""
% self._deckLimit(), % self._deckLimit(),
self.today, self.today,
lim, lim,
@ -916,9 +913,9 @@ did in %s and queue = 2 and due <= ? limit ?)"""
lim = min(self.queueLimit, self._currentRevLimit()) lim = min(self.queueLimit, self._currentRevLimit())
if lim: if lim:
self._revQueue = self.col.db.list( self._revQueue = self.col.db.list(
""" f"""
select id from cards where select id from cards where
did in %s and queue = 2 and due <= ? did in %s and queue = {QUEUE_TYPE_REV} and due <= ?
order by due, random() order by due, random()
limit ?""" limit ?"""
% self._deckLimit(), % self._deckLimit(),
@ -946,9 +943,9 @@ limit ?"""
def totalRevForCurrentDeck(self) -> int: def totalRevForCurrentDeck(self) -> int:
return self.col.db.scalar( return self.col.db.scalar(
""" f"""
select count() from cards where id in ( select count() from cards where id in (
select id from cards where did in %s and queue = 2 and due <= ? limit ?)""" select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? limit ?)"""
% self._deckLimit(), % self._deckLimit(),
self.today, self.today,
self.reportLimit, self.reportLimit,
@ -960,9 +957,9 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
def _answerRevCard(self, card: Card, ease: int) -> None: def _answerRevCard(self, card: Card, ease: int) -> None:
delay = 0 delay = 0
early = bool(card.odid and (card.odue > self.today)) early = bool(card.odid and (card.odue > self.today))
type = early and 3 or 1 type = early and REVLOG_CRAM or REVLOG_REV
if ease == 1: if ease == BUTTON_ONE:
delay = self._rescheduleLapse(card) delay = self._rescheduleLapse(card)
else: else:
self._rescheduleRev(card, ease, early) self._rescheduleRev(card, ease, early)
@ -976,7 +973,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
card.lapses += 1 card.lapses += 1
card.factor = max(1300, card.factor - 200) card.factor = max(1300, card.factor - 200)
suspended = self._checkLeech(card, conf) and card.queue == -1 suspended = self._checkLeech(card, conf) and card.queue == QUEUE_TYPE_SUSPENDED
if conf["delays"] and not suspended: if conf["delays"] and not suspended:
card.type = CARD_TYPE_RELEARNING card.type = CARD_TYPE_RELEARNING
@ -987,7 +984,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self._rescheduleAsRev(card, conf, early=False) self._rescheduleAsRev(card, conf, early=False)
# need to reset the queue after rescheduling # need to reset the queue after rescheduling
if suspended: if suspended:
card.queue = -1 card.queue = QUEUE_TYPE_SUSPENDED
delay = 0 delay = 0
return delay return delay
@ -1047,11 +1044,11 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
else: else:
hardMin = 0 hardMin = 0
ivl2 = self._constrainedIvl(card.ivl * hardFactor, conf, hardMin, fuzz) ivl2 = self._constrainedIvl(card.ivl * hardFactor, conf, hardMin, fuzz)
if ease == 2: if ease == BUTTON_TWO:
return ivl2 return ivl2
ivl3 = self._constrainedIvl((card.ivl + delay // 2) * fct, conf, ivl2, fuzz) ivl3 = self._constrainedIvl((card.ivl + delay // 2) * fct, conf, ivl2, fuzz)
if ease == 3: if ease == BUTTON_THREE:
return ivl3 return ivl3
ivl4 = self._constrainedIvl( ivl4 = self._constrainedIvl(
@ -1101,7 +1098,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# next interval for card when answered early+correctly # next interval for card when answered early+correctly
def _earlyReviewIvl(self, card: Card, ease: int) -> int: def _earlyReviewIvl(self, card: Card, ease: int) -> int:
assert card.odid and card.type == 2 assert card.odid and card.type == CARD_TYPE_REV
assert card.factor assert card.factor
assert ease > 1 assert ease > 1
@ -1113,14 +1110,14 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# early 3/4 reviews shouldn't decrease previous interval # early 3/4 reviews shouldn't decrease previous interval
minNewIvl = 1 minNewIvl = 1
if ease == 2: if ease == BUTTON_TWO:
factor = conf.get("hardFactor", 1.2) factor = conf.get("hardFactor", 1.2)
# hard cards shouldn't have their interval decreased by more than 50% # hard cards shouldn't have their interval decreased by more than 50%
# of the normal factor # of the normal factor
minNewIvl = factor / 2 minNewIvl = factor / 2
elif ease == 3: elif ease == BUTTON_THREE:
factor = card.factor / 1000 factor = card.factor / 1000
else: # ease == 4: else: # ease == BUTTON_FOUR:
factor = card.factor / 1000 factor = card.factor / 1000
ease4 = conf["ease4"] ease4 = conf["ease4"]
# 1.3 -> 1.15 # 1.3 -> 1.15
@ -1213,7 +1210,7 @@ due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? whe
t = "n.id desc" t = "n.id desc"
elif o == DYN_DUEPRIORITY: elif o == DYN_DUEPRIORITY:
t = ( t = (
"(case when queue=2 and due <= %d then (ivl / cast(%d-due+0.001 as real)) else 100000+due end)" f"(case when queue={QUEUE_TYPE_REV} and due <= %d then (ivl / cast(%d-due+0.001 as real)) else 100000+due end)"
% (self.today, self.today) % (self.today, self.today)
) )
else: # DYN_DUE or unknown else: # DYN_DUE or unknown
@ -1231,7 +1228,7 @@ due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? whe
queue = "" queue = ""
if not deck["resched"]: if not deck["resched"]:
queue = ",queue=2" queue = f",queue={QUEUE_TYPE_REV}"
query = ( query = (
""" """
@ -1260,9 +1257,9 @@ where id = ?
# learning and relearning cards may be seconds-based or day-based; # learning and relearning cards may be seconds-based or day-based;
# other types map directly to queues # other types map directly to queues
if card.type in (1, CARD_TYPE_RELEARNING): if card.type in (CARD_TYPE_LRN, CARD_TYPE_RELEARNING):
if card.odue > 1000000000: if card.odue > 1000000000:
card.queue = 1 card.queue = QUEUE_TYPE_LRN
else: else:
card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN
else: else:
@ -1284,8 +1281,8 @@ where id = ?
f.flush() f.flush()
# handle # handle
a = conf["leechAction"] a = conf["leechAction"]
if a == 0: if a == LEECH_SUSPEND:
card.queue = -1 card.queue = QUEUE_TYPE_SUSPENDED
# notify UI # notify UI
hooks.card_did_leech(card) hooks.card_did_leech(card)
return True return True
@ -1509,7 +1506,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
"True if there are any rev cards due." "True if there are any rev cards due."
return self.col.db.scalar( return self.col.db.scalar(
( (
"select 1 from cards where did in %s and queue = 2 " f"select 1 from cards where did in %s and queue = {QUEUE_TYPE_REV} "
"and due <= ? limit 1" "and due <= ? limit 1"
) )
% self._deckLimit(), % self._deckLimit(),
@ -1519,7 +1516,10 @@ To study outside of the normal schedule, click the Custom Study button below."""
def newDue(self) -> Any: def newDue(self) -> Any:
"True if there are any new cards due." "True if there are any new cards due."
return self.col.db.scalar( return self.col.db.scalar(
("select 1 from cards where did in %s and queue = 0 " "limit 1") (
f"select 1 from cards where did in %s and queue = {QUEUE_TYPE_NEW} "
"limit 1"
)
% self._deckLimit() % self._deckLimit()
) )
@ -1557,14 +1557,14 @@ To study outside of the normal schedule, click the Custom Study button below."""
"Return the next interval for CARD, in seconds." "Return the next interval for CARD, in seconds."
# preview mode? # preview mode?
if self._previewingCard(card): if self._previewingCard(card):
if ease == 1: if ease == BUTTON_ONE:
return self._previewDelay(card) return self._previewDelay(card)
return 0 return 0
# (re)learning? # (re)learning?
if card.queue in (0, 1, QUEUE_TYPE_DAY_LEARN_RELEARN): if card.queue in (QUEUE_TYPE_NEW, QUEUE_TYPE_LRN, QUEUE_TYPE_DAY_LEARN_RELEARN):
return self._nextLrnIvl(card, ease) return self._nextLrnIvl(card, ease)
elif ease == 1: elif ease == BUTTON_ONE:
# lapse # lapse
conf = self._lapseConf(card) conf = self._lapseConf(card)
if conf["delays"]: if conf["delays"]:
@ -1580,17 +1580,17 @@ To study outside of the normal schedule, click the Custom Study button below."""
# this isn't easily extracted from the learn code # this isn't easily extracted from the learn code
def _nextLrnIvl(self, card: Card, ease: int) -> Any: def _nextLrnIvl(self, card: Card, ease: int) -> Any:
if card.queue == 0: if card.queue == QUEUE_TYPE_NEW:
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
conf = self._lrnConf(card) conf = self._lrnConf(card)
if ease == 1: if ease == BUTTON_ONE:
# fail # fail
return self._delayForGrade(conf, len(conf["delays"])) return self._delayForGrade(conf, len(conf["delays"]))
elif ease == 2: elif ease == BUTTON_TWO:
return self._delayForRepeatingGrade(conf, card.left) return self._delayForRepeatingGrade(conf, card.left)
elif ease == 4: elif ease == BUTTON_FOUR:
return self._graduatingIvl(card, conf, True, fuzz=False) * 86400 return self._graduatingIvl(card, conf, True, fuzz=False) * 86400
else: # ease == 3 else: # ease == BUTTON_THREE
left = card.left % 1000 - 1 left = card.left % 1000 - 1
if left <= 0: if left <= 0:
# graduate # graduate
@ -1604,7 +1604,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
# learning and relearning cards may be seconds-based or day-based; # learning and relearning cards may be seconds-based or day-based;
# other types map directly to queues # other types map directly to queues
_restoreQueueSnippet = f""" _restoreQueueSnippet = f"""
queue = (case when type in (1,{CARD_TYPE_RELEARNING}) then queue = (case when type in ({CARD_TYPE_LRN},{CARD_TYPE_RELEARNING}) then
(case when (case when odue then odue else due end) > 1000000000 then 1 else (case when (case when odue then odue else due end) > 1000000000 then 1 else
{QUEUE_TYPE_DAY_LEARN_RELEARN} end) {QUEUE_TYPE_DAY_LEARN_RELEARN} end)
else else
@ -1616,7 +1616,8 @@ end)
"Suspend cards." "Suspend cards."
self.col.log(ids) self.col.log(ids)
self.col.db.execute( self.col.db.execute(
"update cards set queue=-1,mod=?,usn=? where id in " + ids2str(ids), f"update cards set queue={QUEUE_TYPE_SUSPENDED},mod=?,usn=? where id in "
+ ids2str(ids),
intTime(), intTime(),
self.col.usn(), self.col.usn(),
) )
@ -1625,7 +1626,9 @@ end)
"Unsuspend cards." "Unsuspend cards."
self.col.log(ids) self.col.log(ids)
self.col.db.execute( self.col.db.execute(
("update cards set %s,mod=?,usn=? " "where queue = -1 and id in %s") (
f"update cards set %s,mod=?,usn=? where queue = {QUEUE_TYPE_SUSPENDED} and id in %s"
)
% (self._restoreQueueSnippet, ids2str(ids)), % (self._restoreQueueSnippet, ids2str(ids)),
intTime(), intTime(),
self.col.usn(), self.col.usn(),
@ -1646,7 +1649,7 @@ update cards set queue=?,mod=?,usn=? where id in """
def buryNote(self, nid) -> None: def buryNote(self, nid) -> None:
"Bury all cards for note until next session." "Bury all cards for note until next session."
cids = self.col.db.list( cids = self.col.db.list(
"select id from cards where nid = ? and queue >= 0", nid f"select id from cards where nid = ? and queue >= {QUEUE_TYPE_NEW}", nid
) )
self.buryCards(cids) self.buryCards(cids)
@ -1654,11 +1657,11 @@ update cards set queue=?,mod=?,usn=? where id in """
"Unbury all buried cards in all decks." "Unbury all buried cards in all decks."
self.col.log( self.col.log(
self.col.db.list( self.col.db.list(
f"select id from cards where queue in (-2, {QUEUE_TYPE_MANUALLY_BURIED})" f"select id from cards where queue in ({QUEUE_TYPE_SIBLING_BURIED}, {QUEUE_TYPE_MANUALLY_BURIED})"
) )
) )
self.col.db.execute( self.col.db.execute(
f"update cards set %s where queue in (-2, {QUEUE_TYPE_MANUALLY_BURIED})" f"update cards set %s where queue in ({QUEUE_TYPE_SIBLING_BURIED}, {QUEUE_TYPE_MANUALLY_BURIED})"
% self._restoreQueueSnippet % self._restoreQueueSnippet
) )
@ -1698,14 +1701,14 @@ update cards set queue=?,mod=?,usn=? where id in """
buryRev = rconf.get("bury", True) buryRev = rconf.get("bury", True)
# loop through and remove from queues # loop through and remove from queues
for cid, queue in self.col.db.execute( for cid, queue in self.col.db.execute(
""" f"""
select id, queue from cards where nid=? and id!=? select id, queue from cards where nid=? and id!=?
and (queue=0 or (queue=2 and due<=?))""", and (queue={QUEUE_TYPE_NEW} or (queue={QUEUE_TYPE_REV} and due<=?))""",
card.nid, card.nid,
card.id, card.id,
self.today, self.today,
): ):
if queue == 2: if queue == QUEUE_TYPE_REV:
if buryRev: if buryRev:
toBury.append(cid) toBury.append(cid)
# if bury disabled, we still discard to give same-day spacing # if bury disabled, we still discard to give same-day spacing
@ -1732,11 +1735,14 @@ and (queue=0 or (queue=2 and due<=?))""",
"Put cards at the end of the new queue." "Put cards at the end of the new queue."
self.remFromDyn(ids) self.remFromDyn(ids)
self.col.db.execute( self.col.db.execute(
"update cards set type=0,queue=0,ivl=0,due=0,odue=0,factor=?" f"update cards set type={CARD_TYPE_NEW},queue={QUEUE_TYPE_NEW},ivl=0,due=0,odue=0,factor=?"
" where id in " + ids2str(ids), " where id in " + ids2str(ids),
STARTING_FACTOR, STARTING_FACTOR,
) )
pmax = self.col.db.scalar("select max(due) from cards where type=0") or 0 pmax = (
self.col.db.scalar(f"select max(due) from cards where type={CARD_TYPE_NEW}")
or 0
)
# takes care of mod + usn # takes care of mod + usn
self.sortCards(ids, start=pmax + 1) self.sortCards(ids, start=pmax + 1)
self.col.log(ids) self.col.log(ids)
@ -1760,8 +1766,8 @@ and (queue=0 or (queue=2 and due<=?))""",
) )
self.remFromDyn(ids) self.remFromDyn(ids)
self.col.db.executemany( self.col.db.executemany(
""" f"""
update cards set type=2,queue=2,ivl=:ivl,due=:due,odue=0, update cards set type={CARD_TYPE_REV},queue={QUEUE_TYPE_REV},ivl=:ivl,due=:due,odue=0,
usn=:usn,mod=:mod,factor=:fact where id=:id""", usn=:usn,mod=:mod,factor=:fact where id=:id""",
d, d,
) )
@ -1772,11 +1778,12 @@ usn=:usn,mod=:mod,factor=:fact where id=:id""",
sids = ids2str(ids) sids = ids2str(ids)
# we want to avoid resetting due number of existing new cards on export # we want to avoid resetting due number of existing new cards on export
nonNew = self.col.db.list( nonNew = self.col.db.list(
"select id from cards where id in %s and (queue != 0 or type != 0)" % sids f"select id from cards where id in %s and (queue != {QUEUE_TYPE_NEW} or type != {CARD_TYPE_NEW})"
% sids
) )
# reset all cards # reset all cards
self.col.db.execute( self.col.db.execute(
"update cards set reps=0,lapses=0,odid=0,odue=0,queue=0" f"update cards set reps=0,lapses=0,odid=0,odue=0,queue={QUEUE_TYPE_NEW}"
" where id in %s" % sids " where id in %s" % sids
) )
# and forget any non-new cards, changing their due numbers # and forget any non-new cards, changing their due numbers
@ -1817,16 +1824,16 @@ usn=:usn,mod=:mod,factor=:fact where id=:id""",
# shift? # shift?
if shift: if shift:
low = self.col.db.scalar( low = self.col.db.scalar(
"select min(due) from cards where due >= ? and type = 0 " f"select min(due) from cards where due >= ? and type = {CARD_TYPE_NEW} "
"and id not in %s" % scids, "and id not in %s" % scids,
start, start,
) )
if low is not None: if low is not None:
shiftby = high - low + 1 shiftby = high - low + 1
self.col.db.execute( self.col.db.execute(
""" f"""
update cards set mod=?, usn=?, due=due+? where id not in %s update cards set mod=?, usn=?, due=due+? where id not in %s
and due >= ? and queue = 0""" and due >= ? and queue = {QUEUE_TYPE_NEW}"""
% scids, % scids,
now, now,
self.col.usn(), self.col.usn(),
@ -1836,7 +1843,7 @@ and due >= ? and queue = 0"""
# reorder cards # reorder cards
d = [] d = []
for id, nid in self.col.db.execute( for id, nid in self.col.db.execute(
"select id, nid from cards where type = 0 and id in " + scids f"select id, nid from cards where type = {CARD_TYPE_NEW} and id in " + scids
): ):
d.append(dict(now=now, due=due[nid], usn=self.col.usn(), cid=id)) d.append(dict(now=now, due=due[nid], usn=self.col.usn(), cid=id))
self.col.db.executemany( self.col.db.executemany(
@ -1874,11 +1881,11 @@ and due >= ? and queue = 0"""
self.col.db.execute( self.col.db.execute(
f""" f"""
update cards set did = odid, queue = (case update cards set did = odid, queue = (case
when type = 1 then 0 when type = {CARD_TYPE_LRN} then {QUEUE_TYPE_NEW}
when type = {CARD_TYPE_RELEARNING} then 2 when type = {CARD_TYPE_RELEARNING} then {QUEUE_TYPE_REV}
else type end), type = (case else type end), type = (case
when type = 1 then 0 when type = {CARD_TYPE_LRN} then {CARD_TYPE_NEW}
when type = {CARD_TYPE_RELEARNING} then 2 when type = {CARD_TYPE_RELEARNING} then {CARD_TYPE_REV}
else type end), else type end),
due = odue, odue = 0, odid = 0, usn = ? where odid != 0""", due = odue, odue = 0, odid = 0, usn = ? where odid != 0""",
self.col.usn(), self.col.usn(),
@ -1890,8 +1897,8 @@ due = odue, odue = 0, odid = 0, usn = ? where odid != 0""",
self.col.db.execute( self.col.db.execute(
f""" f"""
update cards set update cards set
due = odue, queue = 2, type = 2, mod = %d, usn = %d, odue = 0 due = odue, queue = {QUEUE_TYPE_REV}, type = {CARD_TYPE_REV}, mod = %d, usn = %d, odue = 0
where queue in (1,{QUEUE_TYPE_DAY_LEARN_RELEARN}) and type in (2, {CARD_TYPE_RELEARNING}) where queue in ({QUEUE_TYPE_LRN},{QUEUE_TYPE_DAY_LEARN_RELEARN}) and type in ({CARD_TYPE_REV}, {CARD_TYPE_RELEARNING})
""" """
% (intTime(), self.col.usn()) % (intTime(), self.col.usn())
) )
@ -1899,15 +1906,15 @@ due = odue, odue = 0, odid = 0, usn = ? where odid != 0""",
self.col.db.execute( self.col.db.execute(
f""" f"""
update cards set update cards set
due = %d+ivl, queue = 2, type = 2, mod = %d, usn = %d, odue = 0 due = %d+ivl, queue = {QUEUE_TYPE_REV}, type = {CARD_TYPE_REV}, mod = %d, usn = %d, odue = 0
where queue in (1,{QUEUE_TYPE_DAY_LEARN_RELEARN}) and type in (2, {CARD_TYPE_RELEARNING}) where queue in ({QUEUE_TYPE_LRN},{QUEUE_TYPE_DAY_LEARN_RELEARN}) and type in ({CARD_TYPE_REV}, {CARD_TYPE_RELEARNING})
""" """
% (self.today, intTime(), self.col.usn()) % (self.today, intTime(), self.col.usn())
) )
# remove new cards from learning # remove new cards from learning
self.forgetCards( self.forgetCards(
self.col.db.list( self.col.db.list(
f"select id from cards where queue in (1,{QUEUE_TYPE_DAY_LEARN_RELEARN})" f"select id from cards where queue in ({QUEUE_TYPE_LRN},{QUEUE_TYPE_DAY_LEARN_RELEARN})"
) )
) )
@ -1916,13 +1923,13 @@ due = odue, odue = 0, odid = 0, usn = ? where odid != 0""",
self.col.db.execute( self.col.db.execute(
f""" f"""
update cards set type = (case update cards set type = (case
when type = 1 then 0 when type = {CARD_TYPE_LRN} then {CARD_TYPE_NEW}
when type in (2, {CARD_TYPE_RELEARNING}) then 2 when type in ({CARD_TYPE_REV}, {CARD_TYPE_RELEARNING}) then {CARD_TYPE_REV}
else type end), else type end),
due = (case when odue then odue else due end), due = (case when odue then odue else due end),
odue = 0, odue = 0,
mod = %d, usn = %d mod = %d, usn = %d
where queue < 0""" where queue < {QUEUE_TYPE_NEW}"""
% (intTime(), self.col.usn()) % (intTime(), self.col.usn())
) )
@ -1936,7 +1943,9 @@ where queue < 0"""
# adding 'hard' in v2 scheduler means old ease entries need shifting # adding 'hard' in v2 scheduler means old ease entries need shifting
# up or down # up or down
def _remapLearningAnswers(self, sql: str) -> None: def _remapLearningAnswers(self, sql: str) -> None:
self.col.db.execute("update revlog set %s and type in (0,2)" % sql) self.col.db.execute(
f"update revlog set %s and type in ({CARD_TYPE_NEW},{CARD_TYPE_REV})" % sql
)
def moveToV1(self) -> None: def moveToV1(self) -> None:
self._emptyAllFiltered() self._emptyAllFiltered()

View file

@ -6,12 +6,17 @@ import json
import time import time
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
from anki.consts import *
from anki.lang import _, ngettext from anki.lang import _, ngettext
from anki.utils import fmtTimeSpan, ids2str from anki.utils import fmtTimeSpan, ids2str
# Card stats # Card stats
########################################################################## ##########################################################################
PERIOD_MONTH = 0
PERIOD_YEAR = 1
PERIOD_LIFE = 2
class CardStats: class CardStats:
def __init__(self, col, card) -> None: def __init__(self, col, card) -> None:
@ -30,18 +35,18 @@ class CardStats:
if first: if first:
self.addLine(_("First Review"), self.date(first / 1000)) self.addLine(_("First Review"), self.date(first / 1000))
self.addLine(_("Latest Review"), self.date(last / 1000)) self.addLine(_("Latest Review"), self.date(last / 1000))
if c.type in (1, 2): if c.type in (CARD_TYPE_LRN, CARD_TYPE_REV):
if c.odid or c.queue < 0: if c.odid or c.queue < QUEUE_TYPE_NEW:
next = None next = None
else: else:
if c.queue in (2, 3): if c.queue in (QUEUE_TYPE_REV, QUEUE_TYPE_DAY_LEARN_RELEARN):
next = time.time() + ((c.due - self.col.sched.today) * 86400) next = time.time() + ((c.due - self.col.sched.today) * 86400)
else: else:
next = c.due next = c.due
next = self.date(next) next = self.date(next)
if next: if next:
self.addLine(_("Due"), next) self.addLine(_("Due"), next)
if c.queue == 2: if c.queue == QUEUE_TYPE_REV:
self.addLine(_("Interval"), fmt(c.ivl * 86400)) self.addLine(_("Interval"), fmt(c.ivl * 86400))
self.addLine(_("Ease"), "%d%%" % (c.factor / 10.0)) self.addLine(_("Ease"), "%d%%" % (c.factor / 10.0))
self.addLine(_("Reviews"), "%d" % c.reps) self.addLine(_("Reviews"), "%d" % c.reps)
@ -52,7 +57,7 @@ class CardStats:
if cnt: if cnt:
self.addLine(_("Average Time"), self.time(total / float(cnt))) self.addLine(_("Average Time"), self.time(total / float(cnt)))
self.addLine(_("Total Time"), self.time(total)) self.addLine(_("Total Time"), self.time(total))
elif c.queue == 0: elif c.queue == QUEUE_TYPE_NEW:
self.addLine(_("Position"), c.due) self.addLine(_("Position"), c.due)
self.addLine(_("Card Type"), c.template()["name"]) self.addLine(_("Card Type"), c.template()["name"])
self.addLine(_("Note Type"), c.model()["name"]) self.addLine(_("Note Type"), c.model()["name"])
@ -102,14 +107,14 @@ class CollectionStats:
def __init__(self, col) -> None: def __init__(self, col) -> None:
self.col = col self.col = col
self._stats = None self._stats = None
self.type = 0 self.type = PERIOD_MONTH
self.width = 600 self.width = 600
self.height = 200 self.height = 200
self.wholeCollection = False self.wholeCollection = False
# assumes jquery & plot are available in document # assumes jquery & plot are available in document
def report(self, type=0) -> str: def report(self, type=PERIOD_MONTH) -> str:
# 0=days, 1=weeks, 2=months # 0=month, 1=year, 2=deck life
self.type = type self.type = type
from .statsbg import bg from .statsbg import bg
@ -149,13 +154,13 @@ body {background-image: url(data:image/png;base64,%s); }
if lim: if lim:
lim = " and " + lim lim = " and " + lim
cards, thetime, failed, lrn, rev, relrn, filt = self.col.db.first( cards, thetime, failed, lrn, rev, relrn, filt = self.col.db.first(
""" f"""
select count(), sum(time)/1000, select count(), sum(time)/1000,
sum(case when ease = 1 then 1 else 0 end), /* failed */ sum(case when ease = 1 then 1 else 0 end), /* failed */
sum(case when type = 0 then 1 else 0 end), /* learning */ sum(case when type = {REVLOG_LRN} then 1 else 0 end), /* learning */
sum(case when type = 1 then 1 else 0 end), /* review */ sum(case when type = {REVLOG_REV} then 1 else 0 end), /* review */
sum(case when type = 2 then 1 else 0 end), /* relearn */ sum(case when type = {REVLOG_RELRN} then 1 else 0 end), /* relearn */
sum(case when type = 3 then 1 else 0 end) /* filter */ sum(case when type = {REVLOG_CRAM} then 1 else 0 end) /* filter */
from revlog where id > ? """ from revlog where id > ? """
+ lim, + lim,
(self.col.sched.dayCutoff - 86400) * 1000, (self.col.sched.dayCutoff - 86400) * 1000,
@ -215,9 +220,9 @@ from revlog where id > ? """
def get_start_end_chunk(self, by="review") -> Tuple[int, Optional[int], int]: def get_start_end_chunk(self, by="review") -> Tuple[int, Optional[int], int]:
start = 0 start = 0
if self.type == 0: if self.type == PERIOD_MONTH:
end, chunk = 31, 1 end, chunk = 31, 1
elif self.type == 1: elif self.type == PERIOD_YEAR:
end, chunk = 52, 7 end, chunk = 52, 7
else: # self.type == 2: else: # self.type == 2:
end = None end = None
@ -279,8 +284,8 @@ from revlog where id > ? """
self._line(i, _("Total"), ngettext("%d review", "%d reviews", tot) % tot) self._line(i, _("Total"), ngettext("%d review", "%d reviews", tot) % tot)
self._line(i, _("Average"), self._avgDay(tot, num, _("reviews"))) self._line(i, _("Average"), self._avgDay(tot, num, _("reviews")))
tomorrow = self.col.db.scalar( tomorrow = self.col.db.scalar(
""" f"""
select count() from cards where did in %s and queue in (2,3) select count() from cards where did in %s and queue in ({QUEUE_TYPE_REV},{QUEUE_TYPE_DAY_LEARN_RELEARN})
and due = ?""" and due = ?"""
% self._limit(), % self._limit(),
self.col.sched.today + 1, self.col.sched.today + 1,
@ -296,12 +301,12 @@ and due = ?"""
if end is not None: if end is not None:
lim += " and day < %d" % end lim += " and day < %d" % end
return self.col.db.all( return self.col.db.all(
""" f"""
select (due-:today)/:chunk as day, select (due-:today)/:chunk as day,
sum(case when ivl < 21 then 1 else 0 end), -- yng sum(case when ivl < 21 then 1 else 0 end), -- yng
sum(case when ivl >= 21 then 1 else 0 end) -- mtr sum(case when ivl >= 21 then 1 else 0 end) -- mtr
from cards from cards
where did in %s and queue in (2,3) where did in %s and queue in ({QUEUE_TYPE_REV},{QUEUE_TYPE_DAY_LEARN_RELEARN})
%s %s
group by day order by day""" group by day order by day"""
% (self._limit(), lim), % (self._limit(), lim),
@ -396,7 +401,7 @@ group by day order by day"""
(10, colCram, _("Cram")), (10, colCram, _("Cram")),
), ),
) )
if self.type == 0: if self.type == PERIOD_MONTH:
t = _("Minutes") t = _("Minutes")
convHours = False convHours = False
else: else:
@ -513,7 +518,7 @@ group by day order by day"""
lim = "where " + " and ".join(lims) lim = "where " + " and ".join(lims)
else: else:
lim = "" lim = ""
if self.type == 0: if self.type == PERIOD_MONTH:
tf = 60.0 # minutes tf = 60.0 # minutes
else: else:
tf = 3600.0 # hours tf = 3600.0 # hours
@ -543,25 +548,25 @@ group by day order by day"""
lim = "where " + " and ".join(lims) lim = "where " + " and ".join(lims)
else: else:
lim = "" lim = ""
if self.type == 0: if self.type == PERIOD_MONTH:
tf = 60.0 # minutes tf = 60.0 # minutes
else: else:
tf = 3600.0 # hours tf = 3600.0 # hours
return self.col.db.all( return self.col.db.all(
""" f"""
select select
(cast((id/1000.0 - :cut) / 86400.0 as int))/:chunk as day, (cast((id/1000.0 - :cut) / 86400.0 as int))/:chunk as day,
sum(case when type = 0 then 1 else 0 end), -- lrn count sum(case when type = {REVLOG_LRN} then 1 else 0 end), -- lrn count
sum(case when type = 1 and lastIvl < 21 then 1 else 0 end), -- yng count sum(case when type = {REVLOG_REV} and lastIvl < 21 then 1 else 0 end), -- yng count
sum(case when type = 1 and lastIvl >= 21 then 1 else 0 end), -- mtr count sum(case when type = {REVLOG_REV} and lastIvl >= 21 then 1 else 0 end), -- mtr count
sum(case when type = 2 then 1 else 0 end), -- lapse count sum(case when type = {REVLOG_RELRN} then 1 else 0 end), -- lapse count
sum(case when type = 3 then 1 else 0 end), -- cram count sum(case when type = {REVLOG_CRAM} then 1 else 0 end), -- cram count
sum(case when type = 0 then time/1000.0 else 0 end)/:tf, -- lrn time sum(case when type = {REVLOG_LRN} then time/1000.0 else 0 end)/:tf, -- lrn time
-- yng + mtr time -- yng + mtr time
sum(case when type = 1 and lastIvl < 21 then time/1000.0 else 0 end)/:tf, sum(case when type = {REVLOG_REV} and lastIvl < 21 then time/1000.0 else 0 end)/:tf,
sum(case when type = 1 and lastIvl >= 21 then time/1000.0 else 0 end)/:tf, sum(case when type = {REVLOG_REV} and lastIvl >= 21 then time/1000.0 else 0 end)/:tf,
sum(case when type = 2 then time/1000.0 else 0 end)/:tf, -- lapse time sum(case when type = {REVLOG_RELRN} then time/1000.0 else 0 end)/:tf, -- lapse time
sum(case when type = 3 then time/1000.0 else 0 end)/:tf -- cram time sum(case when type = {REVLOG_CRAM} then time/1000.0 else 0 end)/:tf -- cram time
from revlog %s from revlog %s
group by day order by day""" group by day order by day"""
% lim, % lim,
@ -606,9 +611,9 @@ group by day order by day)"""
for (grp, cnt) in ivls: for (grp, cnt) in ivls:
tot += cnt tot += cnt
totd.append((grp, tot / float(all) * 100)) totd.append((grp, tot / float(all) * 100))
if self.type == 0: if self.type == PERIOD_MONTH:
ivlmax = 31 ivlmax = 31
elif self.type == 1: elif self.type == PERIOD_YEAR:
ivlmax = 52 ivlmax = 52
else: else:
ivlmax = max(5, ivls[-1][0]) ivlmax = max(5, ivls[-1][0])
@ -643,9 +648,9 @@ group by day order by day)"""
lim = "and grp <= %d" % end if end else "" lim = "and grp <= %d" % end if end else ""
data = [ data = [
self.col.db.all( self.col.db.all(
""" f"""
select ivl / :chunk as grp, count() from cards select ivl / :chunk as grp, count() from cards
where did in %s and queue = 2 %s where did in %s and queue = {QUEUE_TYPE_REV} %s
group by grp group by grp
order by grp""" order by grp"""
% (self._limit(), lim), % (self._limit(), lim),
@ -656,8 +661,8 @@ order by grp"""
data data
+ list( + list(
self.col.db.first( self.col.db.first(
""" f"""
select count(), avg(ivl), max(ivl) from cards where did in %s and queue = 2""" select count(), avg(ivl), max(ivl) from cards where did in %s and queue = {QUEUE_TYPE_REV}"""
% self._limit() % self._limit()
) )
), ),
@ -675,9 +680,9 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = 2"""
types = ("lrn", "yng", "mtr") types = ("lrn", "yng", "mtr")
eases = self._eases() eases = self._eases()
for (type, ease, cnt) in eases: for (type, ease, cnt) in eases:
if type == 1: if type == CARD_TYPE_LRN:
ease += 5 ease += 5
elif type == 2: elif type == CARD_TYPE_REV:
ease += 10 ease += 10
n = types[type] n = types[type]
d[n].append((ease, cnt)) d[n].append((ease, cnt))
@ -714,7 +719,7 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = 2"""
return txt return txt
def _easeInfo(self, eases) -> str: def _easeInfo(self, eases) -> str:
types = {0: [0, 0], 1: [0, 0], 2: [0, 0]} types = {PERIOD_MONTH: [0, 0], PERIOD_YEAR: [0, 0], PERIOD_LIFE: [0, 0]}
for (type, ease, cnt) in eases: for (type, ease, cnt) in eases:
if ease == 1: if ease == 1:
types[type][0] += cnt types[type][0] += cnt
@ -759,12 +764,12 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = 2"""
else: else:
ease4repl = "ease" ease4repl = "ease"
return self.col.db.all( return self.col.db.all(
""" f"""
select (case select (case
when type in (0,2) then 0 when type in ({REVLOG_LRN},{REVLOG_RELRN}) then 0
when lastIvl < 21 then 1 when lastIvl < 21 then 1
else 2 end) as thetype, else 2 end) as thetype,
(case when type in (0,2) and ease = 4 then %s else ease end), count() from revlog %s (case when type in ({REVLOG_LRN},{REVLOG_RELRN}) and ease = 4 then %s else ease end), count() from revlog %s
group by thetype, ease group by thetype, ease
order by thetype, ease""" order by thetype, ease"""
% (ease4repl, lim) % (ease4repl, lim)
@ -853,13 +858,13 @@ order by thetype, ease"""
if pd: if pd:
lim += " and id > %d" % ((self.col.sched.dayCutoff - (86400 * pd)) * 1000) lim += " and id > %d" % ((self.col.sched.dayCutoff - (86400 * pd)) * 1000)
return self.col.db.all( return self.col.db.all(
""" f"""
select select
23 - ((cast((:cut - id/1000) / 3600.0 as int)) %% 24) as hour, 23 - ((cast((:cut - id/1000) / 3600.0 as int)) %% 24) as hour,
sum(case when ease = 1 then 0 else 1 end) / sum(case when ease = 1 then 0 else 1 end) /
cast(count() as float) * 100, cast(count() as float) * 100,
count() count()
from revlog where type in (0,1,2) %s from revlog where type in ({REVLOG_LRN},{REVLOG_REV},{REVLOG_RELRN}) %s
group by hour having count() > 30 order by hour""" group by hour having count() > 30 order by hour"""
% lim, % lim,
cut=self.col.sched.dayCutoff - (rolloverHour * 3600), cut=self.col.sched.dayCutoff - (rolloverHour * 3600),
@ -929,23 +934,23 @@ when you answer "good" on a review."""
def _factors(self) -> Any: def _factors(self) -> Any:
return self.col.db.first( return self.col.db.first(
""" f"""
select select
min(factor) / 10.0, min(factor) / 10.0,
avg(factor) / 10.0, avg(factor) / 10.0,
max(factor) / 10.0 max(factor) / 10.0
from cards where did in %s and queue = 2""" from cards where did in %s and queue = {QUEUE_TYPE_REV}"""
% self._limit() % self._limit()
) )
def _cards(self) -> Any: def _cards(self) -> Any:
return self.col.db.first( return self.col.db.first(
""" f"""
select select
sum(case when queue=2 and ivl >= 21 then 1 else 0 end), -- mtr sum(case when queue={QUEUE_TYPE_REV} and ivl >= 21 then 1 else 0 end), -- mtr
sum(case when queue in (1,3) or (queue=2 and ivl < 21) then 1 else 0 end), -- yng/lrn sum(case when queue in ({QUEUE_TYPE_LRN},{QUEUE_TYPE_DAY_LEARN_RELEARN}) or (queue={QUEUE_TYPE_REV} and ivl < 21) then 1 else 0 end), -- yng/lrn
sum(case when queue=0 then 1 else 0 end), -- new sum(case when queue={QUEUE_TYPE_NEW} then 1 else 0 end), -- new
sum(case when queue<0 then 1 else 0 end) -- susp sum(case when queue<{QUEUE_TYPE_NEW} then 1 else 0 end) -- susp
from cards where did in %s""" from cards where did in %s"""
% self._limit() % self._limit()
) )

View file

@ -111,7 +111,7 @@ def _upgrade(col, ver) -> None:
if ver < 3: if ver < 3:
# new deck properties # new deck properties
for d in col.decks.all(): for d in col.decks.all():
d["dyn"] = 0 d["dyn"] = DECK_STD
d["collapsed"] = False d["collapsed"] = False
col.decks.save(d) col.decks.save(d)
if ver < 4: if ver < 4:

View file

@ -1,6 +1,7 @@
# coding: utf-8 # coding: utf-8
import pytest import pytest
from anki.consts import *
from anki.find import Finder from anki.find import Finder
from tests.shared import getEmptyCol from tests.shared import getEmptyCol
@ -91,13 +92,13 @@ def test_findCards():
assert len(deck.findCards('"goats are"')) == 1 assert len(deck.findCards('"goats are"')) == 1
# card states # card states
c = f.cards()[0] c = f.cards()[0]
c.queue = c.type = 2 c.queue = c.type = CARD_TYPE_REV
assert deck.findCards("is:review") == [] assert deck.findCards("is:review") == []
c.flush() c.flush()
assert deck.findCards("is:review") == [c.id] assert deck.findCards("is:review") == [c.id]
assert deck.findCards("is:due") == [] assert deck.findCards("is:due") == []
c.due = 0 c.due = 0
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.flush() c.flush()
assert deck.findCards("is:due") == [c.id] assert deck.findCards("is:due") == [c.id]
assert len(deck.findCards("-is:due")) == 4 assert len(deck.findCards("-is:due")) == 4

View file

@ -4,7 +4,7 @@ import copy
import time import time
from anki import hooks from anki import hooks
from anki.consts import STARTING_FACTOR from anki.consts import *
from anki.utils import intTime from anki.utils import intTime
from tests.shared import getEmptyCol as getEmptyColOrig from tests.shared import getEmptyCol as getEmptyColOrig
@ -46,13 +46,13 @@ def test_new():
# fetch it # fetch it
c = d.sched.getCard() c = d.sched.getCard()
assert c assert c
assert c.queue == 0 assert c.queue == QUEUE_TYPE_NEW
assert c.type == 0 assert c.type == CARD_TYPE_NEW
# if we answer it, it should become a learn card # if we answer it, it should become a learn card
t = intTime() t = intTime()
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
assert c.type == 1 assert c.type == CARD_TYPE_LRN
assert c.due >= t assert c.due >= t
# disabled for now, as the learn fudging makes this randomly fail # disabled for now, as the learn fudging makes this randomly fail
@ -163,11 +163,11 @@ def test_learn():
assert c.left % 1000 == 1 assert c.left % 1000 == 1
assert c.left // 1000 == 1 assert c.left // 1000 == 1
# the next pass should graduate the card # the next pass should graduate the card
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
assert c.type == 1 assert c.type == CARD_TYPE_LRN
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
assert c.type == 2 assert c.type == CARD_TYPE_REV
# should be due tomorrow, with an interval of 1 # should be due tomorrow, with an interval of 1
assert c.due == d.sched.today + 1 assert c.due == d.sched.today + 1
assert c.ivl == 1 assert c.ivl == 1
@ -175,27 +175,27 @@ def test_learn():
c.type = 0 c.type = 0
c.queue = 1 c.queue = 1
d.sched.answerCard(c, 3) d.sched.answerCard(c, 3)
assert c.type == 2 assert c.type == CARD_TYPE_REV
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
assert checkRevIvl(d, c, 4) assert checkRevIvl(d, c, 4)
# revlog should have been updated each time # revlog should have been updated each time
assert d.db.scalar("select count() from revlog where type = 0") == 5 assert d.db.scalar("select count() from revlog where type = 0") == 5
# now failed card handling # now failed card handling
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 1 c.queue = 1
c.odue = 123 c.odue = 123
d.sched.answerCard(c, 3) d.sched.answerCard(c, 3)
assert c.due == 123 assert c.due == 123
assert c.type == 2 assert c.type == CARD_TYPE_REV
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
# we should be able to remove manually, too # we should be able to remove manually, too
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 1 c.queue = 1
c.odue = 321 c.odue = 321
c.flush() c.flush()
d.sched.removeLrn() d.sched.removeLrn()
c.load() c.load()
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
assert c.due == 321 assert c.due == 321
@ -247,7 +247,7 @@ def test_learn_day():
# answering it will place it in queue 3 # answering it will place it in queue 3
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
assert c.due == d.sched.today + 1 assert c.due == d.sched.today + 1
assert c.queue == 3 assert c.queue == CARD_TYPE_RELEARNING
assert not d.sched.getCard() assert not d.sched.getCard()
# for testing, move it back a day # for testing, move it back a day
c.due -= 1 c.due -= 1
@ -259,7 +259,7 @@ def test_learn_day():
assert ni(c, 2) == 86400 * 2 assert ni(c, 2) == 86400 * 2
# if we fail it, it should be back in the correct queue # if we fail it, it should be back in the correct queue
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
d.undo() d.undo()
d.reset() d.reset()
c = d.sched.getCard() c = d.sched.getCard()
@ -271,7 +271,7 @@ def test_learn_day():
# the last pass should graduate it into a review card # the last pass should graduate it into a review card
assert ni(c, 2) == 86400 assert ni(c, 2) == 86400
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
assert c.queue == c.type == 2 assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
# if the lapse step is tomorrow, failing it should handle the counts # if the lapse step is tomorrow, failing it should handle the counts
# correctly # correctly
c.due = 0 c.due = 0
@ -281,7 +281,7 @@ def test_learn_day():
d.sched._cardConf(c)["lapse"]["delays"] = [1440] d.sched._cardConf(c)["lapse"]["delays"] = [1440]
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.queue == 3 assert c.queue == CARD_TYPE_RELEARNING
assert d.sched.counts() == (0, 0, 0) assert d.sched.counts() == (0, 0, 0)
@ -294,8 +294,8 @@ def test_reviews():
d.addNote(f) d.addNote(f)
# set the card up as a review card, due 8 days ago # set the card up as a review card, due 8 days ago
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = d.sched.today - 8 c.due = d.sched.today - 8
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
c.reps = 3 c.reps = 3
@ -311,7 +311,7 @@ def test_reviews():
d.reset() d.reset()
d.sched._cardConf(c)["lapse"]["delays"] = [2, 20] d.sched._cardConf(c)["lapse"]["delays"] = [2, 20]
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
# it should be due tomorrow, with an interval of 1 # it should be due tomorrow, with an interval of 1
assert c.odue == d.sched.today + 1 assert c.odue == d.sched.today + 1
assert c.ivl == 1 assert c.ivl == 1
@ -333,7 +333,7 @@ def test_reviews():
c = copy.copy(cardcopy) c = copy.copy(cardcopy)
c.flush() c.flush()
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
# the new interval should be (100 + 8/4) * 1.2 = 122 # the new interval should be (100 + 8/4) * 1.2 = 122
assert checkRevIvl(d, c, 122) assert checkRevIvl(d, c, 122)
assert c.due == d.sched.today + c.ivl assert c.due == d.sched.today + c.ivl
@ -376,9 +376,9 @@ def test_reviews():
hooks.card_did_leech.append(onLeech) hooks.card_did_leech.append(onLeech)
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert hooked assert hooked
assert c.queue == -1 assert c.queue == QUEUE_TYPE_SUSPENDED
c.load() c.load()
assert c.queue == -1 assert c.queue == QUEUE_TYPE_SUSPENDED
def test_button_spacing(): def test_button_spacing():
@ -388,8 +388,8 @@ def test_button_spacing():
d.addNote(f) d.addNote(f)
# 1 day ivl review card due now # 1 day ivl review card due now
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = d.sched.today c.due = d.sched.today
c.reps = 1 c.reps = 1
c.ivl = 1 c.ivl = 1
@ -412,7 +412,7 @@ def test_overdue_lapse():
d.addNote(f) d.addNote(f)
# simulate a review that was lapsed and is now due for its normal review # simulate a review that was lapsed and is now due for its normal review
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 1 c.queue = 1
c.due = -1 c.due = -1
c.odue = -1 c.odue = -1
@ -492,7 +492,7 @@ def test_nextIvl():
assert ni(c, 3) == 4 * 86400 assert ni(c, 3) == 4 * 86400
# lapsed cards # lapsed cards
################################################## ##################################################
c.type = 2 c.type = CARD_TYPE_REV
c.ivl = 100 c.ivl = 100
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
assert ni(c, 1) == 60 assert ni(c, 1) == 60
@ -500,7 +500,7 @@ def test_nextIvl():
assert ni(c, 3) == 100 * 86400 assert ni(c, 3) == 100 * 86400
# review cards # review cards
################################################## ##################################################
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.ivl = 100 c.ivl = 100
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
# failing it should put it at 60s # failing it should put it at 60s
@ -551,20 +551,20 @@ def test_suspend():
# should cope with rev cards being relearnt # should cope with rev cards being relearnt
c.due = 0 c.due = 0
c.ivl = 100 c.ivl = 100
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.flush() c.flush()
d.reset() d.reset()
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.due >= time.time() assert c.due >= time.time()
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
assert c.type == 2 assert c.type == CARD_TYPE_REV
d.sched.suspendCards([c.id]) d.sched.suspendCards([c.id])
d.sched.unsuspendCards([c.id]) d.sched.unsuspendCards([c.id])
c.load() c.load()
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
assert c.type == 2 assert c.type == CARD_TYPE_REV
assert c.due == 1 assert c.due == 1
# should cope with cards in cram decks # should cope with cards in cram decks
c.due = 1 c.due = 1
@ -587,7 +587,8 @@ def test_cram():
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.ivl = 100 c.ivl = 100
c.type = c.queue = 2 c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
# due in 25 days, so it's been waiting 75 days # due in 25 days, so it's been waiting 75 days
c.due = d.sched.today + 25 c.due = d.sched.today + 25
c.mod = 1 c.mod = 1
@ -622,7 +623,7 @@ def test_cram():
# int(75*1.85) = 138 # int(75*1.85) = 138
assert c.ivl == 138 assert c.ivl == 138
assert c.odue == 138 assert c.odue == 138
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
# should be logged as a cram rep # should be logged as a cram rep
assert d.db.scalar("select type from revlog order by id desc limit 1") == 3 assert d.db.scalar("select type from revlog order by id desc limit 1") == 3
# check ivls again # check ivls again
@ -634,7 +635,7 @@ def test_cram():
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
assert c.ivl == 138 assert c.ivl == 138
assert c.due == 138 assert c.due == 138
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
# and it will have moved back to the previous deck # and it will have moved back to the previous deck
assert c.did == 1 assert c.did == 1
# cram the deck again # cram the deck again
@ -702,12 +703,12 @@ def test_cram_rem():
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
# answering the card will put it in the learning queue # answering the card will put it in the learning queue
assert c.type == c.queue == 1 assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
assert c.due != oldDue assert c.due != oldDue
# if we terminate cramming prematurely it should be set back to new # if we terminate cramming prematurely it should be set back to new
d.sched.emptyDyn(did) d.sched.emptyDyn(did)
c.load() c.load()
assert c.type == c.queue == 0 assert c.type == CARD_TYPE_NEW and c.queue == QUEUE_TYPE_NEW
assert c.due == oldDue assert c.due == oldDue
@ -731,10 +732,11 @@ def test_cram_resched():
assert ni(c, 3) == 0 assert ni(c, 3) == 0
assert d.sched.nextIvlStr(c, 3) == "(end)" assert d.sched.nextIvlStr(c, 3) == "(end)"
d.sched.answerCard(c, 3) d.sched.answerCard(c, 3)
assert c.queue == c.type == 0 assert c.type == CARD_TYPE_NEW and c.queue == QUEUE_TYPE_NEW
# undue reviews should also be unaffected # undue reviews should also be unaffected
c.ivl = 100 c.ivl = 100
c.type = c.queue = 2 c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
c.due = d.sched.today + 25 c.due = d.sched.today + 25
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
c.flush() c.flush()
@ -911,8 +913,8 @@ def test_repCounts():
f["Front"] = "three" f["Front"] = "three"
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = d.sched.today c.due = d.sched.today
c.flush() c.flush()
d.reset() d.reset()
@ -929,8 +931,8 @@ def test_timing():
f["Front"] = "num" + str(i) f["Front"] = "num" + str(i)
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = 0 c.due = 0
c.flush() c.flush()
# fail the first one # fail the first one
@ -941,7 +943,7 @@ def test_timing():
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
# the next card should be another review # the next card should be another review
c = d.sched.getCard() c = d.sched.getCard()
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
# but if we wait for a second, the failed card should come back # but if we wait for a second, the failed card should come back
orig_time = time.time orig_time = time.time
@ -950,7 +952,7 @@ def test_timing():
time.time = adjusted_time time.time = adjusted_time
c = d.sched.getCard() c = d.sched.getCard()
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
time.time = orig_time time.time = orig_time
@ -982,7 +984,7 @@ def test_deckDue():
d.addNote(f) d.addNote(f)
# make it a review card # make it a review card
c = f.cards()[0] c = f.cards()[0]
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = 0 c.due = 0
c.flush() c.flush()
# add one more with a new deck # add one more with a new deck
@ -1100,8 +1102,8 @@ def test_forget():
f["Front"] = "one" f["Front"] = "one"
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.type = 2 c.type = CARD_TYPE_REV
c.ivl = 100 c.ivl = 100
c.due = 0 c.due = 0
c.flush() c.flush()
@ -1122,7 +1124,7 @@ def test_resched():
c.load() c.load()
assert c.due == d.sched.today assert c.due == d.sched.today
assert c.ivl == 1 assert c.ivl == 1
assert c.queue == c.type == 2 assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
d.sched.reschedCards([c.id], 1, 1) d.sched.reschedCards([c.id], 1, 1)
c.load() c.load()
assert c.due == d.sched.today + 1 assert c.due == d.sched.today + 1
@ -1136,8 +1138,8 @@ def test_norelearn():
f["Front"] = "one" f["Front"] = "one"
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = 0 c.due = 0
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
c.reps = 3 c.reps = 3
@ -1158,8 +1160,8 @@ def test_failmult():
f["Back"] = "two" f["Back"] = "two"
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.ivl = 100 c.ivl = 100
c.due = d.sched.today - c.ivl c.due = d.sched.today - c.ivl
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR

View file

@ -4,7 +4,7 @@ import copy
import time import time
from anki import hooks from anki import hooks
from anki.consts import STARTING_FACTOR from anki.consts import *
from anki.utils import intTime from anki.utils import intTime
from tests.shared import getEmptyCol as getEmptyColOrig from tests.shared import getEmptyCol as getEmptyColOrig
@ -57,13 +57,13 @@ def test_new():
# fetch it # fetch it
c = d.sched.getCard() c = d.sched.getCard()
assert c assert c
assert c.queue == 0 assert c.queue == QUEUE_TYPE_NEW
assert c.type == 0 assert c.type == CARD_TYPE_NEW
# if we answer it, it should become a learn card # if we answer it, it should become a learn card
t = intTime() t = intTime()
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
assert c.type == 1 assert c.type == CARD_TYPE_LRN
assert c.due >= t assert c.due >= t
# disabled for now, as the learn fudging makes this randomly fail # disabled for now, as the learn fudging makes this randomly fail
@ -176,11 +176,11 @@ def test_learn():
assert c.left % 1000 == 1 assert c.left % 1000 == 1
assert c.left // 1000 == 1 assert c.left // 1000 == 1
# the next pass should graduate the card # the next pass should graduate the card
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
assert c.type == 1 assert c.type == CARD_TYPE_LRN
d.sched.answerCard(c, 3) d.sched.answerCard(c, 3)
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
assert c.type == 2 assert c.type == CARD_TYPE_REV
# should be due tomorrow, with an interval of 1 # should be due tomorrow, with an interval of 1
assert c.due == d.sched.today + 1 assert c.due == d.sched.today + 1
assert c.ivl == 1 assert c.ivl == 1
@ -188,8 +188,8 @@ def test_learn():
c.type = 0 c.type = 0
c.queue = 1 c.queue = 1
d.sched.answerCard(c, 4) d.sched.answerCard(c, 4)
assert c.type == 2 assert c.type == CARD_TYPE_REV
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
assert checkRevIvl(d, c, 4) assert checkRevIvl(d, c, 4)
# revlog should have been updated each time # revlog should have been updated each time
assert d.db.scalar("select count() from revlog where type = 0") == 5 assert d.db.scalar("select count() from revlog where type = 0") == 5
@ -203,20 +203,21 @@ def test_relearn():
c = f.cards()[0] c = f.cards()[0]
c.ivl = 100 c.ivl = 100
c.due = d.sched.today c.due = d.sched.today
c.type = c.queue = 2 c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
c.flush() c.flush()
# fail the card # fail the card
d.reset() d.reset()
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
assert c.type == 3 assert c.type == CARD_TYPE_RELEARNING
assert c.ivl == 1 assert c.ivl == 1
# immediately graduate it # immediately graduate it
d.sched.answerCard(c, 4) d.sched.answerCard(c, 4)
assert c.queue == c.type == 2 assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
assert c.ivl == 2 assert c.ivl == 2
assert c.due == d.sched.today + c.ivl assert c.due == d.sched.today + c.ivl
@ -229,7 +230,8 @@ def test_relearn_no_steps():
c = f.cards()[0] c = f.cards()[0]
c.ivl = 100 c.ivl = 100
c.due = d.sched.today c.due = d.sched.today
c.type = c.queue = 2 c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
c.flush() c.flush()
conf = d.decks.confForDid(1) conf = d.decks.confForDid(1)
@ -240,7 +242,7 @@ def test_relearn_no_steps():
d.reset() d.reset()
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.type == c.queue == 2 assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
def test_learn_collapsed(): def test_learn_collapsed():
@ -291,7 +293,7 @@ def test_learn_day():
# answering it will place it in queue 3 # answering it will place it in queue 3
d.sched.answerCard(c, 3) d.sched.answerCard(c, 3)
assert c.due == d.sched.today + 1 assert c.due == d.sched.today + 1
assert c.queue == 3 assert c.queue == QUEUE_TYPE_DAY_LEARN_RELEARN
assert not d.sched.getCard() assert not d.sched.getCard()
# for testing, move it back a day # for testing, move it back a day
c.due -= 1 c.due -= 1
@ -303,7 +305,7 @@ def test_learn_day():
assert ni(c, 3) == 86400 * 2 assert ni(c, 3) == 86400 * 2
# if we fail it, it should be back in the correct queue # if we fail it, it should be back in the correct queue
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
d.undo() d.undo()
d.reset() d.reset()
c = d.sched.getCard() c = d.sched.getCard()
@ -315,7 +317,7 @@ def test_learn_day():
# the last pass should graduate it into a review card # the last pass should graduate it into a review card
assert ni(c, 3) == 86400 assert ni(c, 3) == 86400
d.sched.answerCard(c, 3) d.sched.answerCard(c, 3)
assert c.queue == c.type == 2 assert c.queue == CARD_TYPE_REV and c.type == QUEUE_TYPE_REV
# if the lapse step is tomorrow, failing it should handle the counts # if the lapse step is tomorrow, failing it should handle the counts
# correctly # correctly
c.due = 0 c.due = 0
@ -325,7 +327,7 @@ def test_learn_day():
d.sched._cardConf(c)["lapse"]["delays"] = [1440] d.sched._cardConf(c)["lapse"]["delays"] = [1440]
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.queue == 3 assert c.queue == QUEUE_TYPE_DAY_LEARN_RELEARN
assert d.sched.counts() == (0, 0, 0) assert d.sched.counts() == (0, 0, 0)
@ -338,8 +340,8 @@ def test_reviews():
d.addNote(f) d.addNote(f)
# set the card up as a review card, due 8 days ago # set the card up as a review card, due 8 days ago
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = d.sched.today - 8 c.due = d.sched.today - 8
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
c.reps = 3 c.reps = 3
@ -355,7 +357,7 @@ def test_reviews():
c.flush() c.flush()
d.reset() d.reset()
d.sched.answerCard(c, 2) d.sched.answerCard(c, 2)
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
# the new interval should be (100) * 1.2 = 120 # the new interval should be (100) * 1.2 = 120
assert checkRevIvl(d, c, 120) assert checkRevIvl(d, c, 120)
assert c.due == d.sched.today + c.ivl assert c.due == d.sched.today + c.ivl
@ -398,9 +400,9 @@ def test_reviews():
hooks.card_did_leech.append(onLeech) hooks.card_did_leech.append(onLeech)
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert hooked assert hooked
assert c.queue == -1 assert c.queue == QUEUE_TYPE_SUSPENDED
c.load() c.load()
assert c.queue == -1 assert c.queue == QUEUE_TYPE_SUSPENDED
def test_review_limits(): def test_review_limits():
@ -432,7 +434,8 @@ def test_review_limits():
# make them reviews # make them reviews
c = f.cards()[0] c = f.cards()[0]
c.queue = c.type = 2 c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
c.due = 0 c.due = 0
c.flush() c.flush()
@ -474,8 +477,8 @@ def test_button_spacing():
d.addNote(f) d.addNote(f)
# 1 day ivl review card due now # 1 day ivl review card due now
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = d.sched.today c.due = d.sched.today
c.reps = 1 c.reps = 1
c.ivl = 1 c.ivl = 1
@ -503,7 +506,7 @@ def test_overdue_lapse():
d.addNote(f) d.addNote(f)
# simulate a review that was lapsed and is now due for its normal review # simulate a review that was lapsed and is now due for its normal review
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 1 c.queue = 1
c.due = -1 c.due = -1
c.odue = -1 c.odue = -1
@ -586,7 +589,7 @@ def test_nextIvl():
assert ni(c, 4) == 4 * 86400 assert ni(c, 4) == 4 * 86400
# lapsed cards # lapsed cards
################################################## ##################################################
c.type = 2 c.type = CARD_TYPE_REV
c.ivl = 100 c.ivl = 100
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
assert ni(c, 1) == 60 assert ni(c, 1) == 60
@ -594,7 +597,7 @@ def test_nextIvl():
assert ni(c, 4) == 101 * 86400 assert ni(c, 4) == 101 * 86400
# review cards # review cards
################################################## ##################################################
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.ivl = 100 c.ivl = 100
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
# failing it should put it at 60s # failing it should put it at 60s
@ -624,25 +627,25 @@ def test_bury():
# burying # burying
d.sched.buryCards([c.id], manual=True) # pylint: disable=unexpected-keyword-arg d.sched.buryCards([c.id], manual=True) # pylint: disable=unexpected-keyword-arg
c.load() c.load()
assert c.queue == -3 assert c.queue == QUEUE_TYPE_MANUALLY_BURIED
d.sched.buryCards([c2.id], manual=False) # pylint: disable=unexpected-keyword-arg d.sched.buryCards([c2.id], manual=False) # pylint: disable=unexpected-keyword-arg
c2.load() c2.load()
assert c2.queue == -2 assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
d.reset() d.reset()
assert not d.sched.getCard() assert not d.sched.getCard()
d.sched.unburyCardsForDeck(type="manual") # pylint: disable=unexpected-keyword-arg d.sched.unburyCardsForDeck(type="manual") # pylint: disable=unexpected-keyword-arg
c.load() c.load()
assert c.queue == 0 assert c.queue == QUEUE_TYPE_NEW
c2.load() c2.load()
assert c2.queue == -2 assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
d.sched.unburyCardsForDeck( # pylint: disable=unexpected-keyword-arg d.sched.unburyCardsForDeck( # pylint: disable=unexpected-keyword-arg
type="siblings" type="siblings"
) )
c2.load() c2.load()
assert c2.queue == 0 assert c2.queue == QUEUE_TYPE_NEW
d.sched.buryCards([c.id, c2.id]) d.sched.buryCards([c.id, c2.id])
d.sched.unburyCardsForDeck(type="all") # pylint: disable=unexpected-keyword-arg d.sched.unburyCardsForDeck(type="all") # pylint: disable=unexpected-keyword-arg
@ -671,21 +674,21 @@ def test_suspend():
# should cope with rev cards being relearnt # should cope with rev cards being relearnt
c.due = 0 c.due = 0
c.ivl = 100 c.ivl = 100
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.flush() c.flush()
d.reset() d.reset()
c = d.sched.getCard() c = d.sched.getCard()
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.due >= time.time() assert c.due >= time.time()
due = c.due due = c.due
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
assert c.type == 3 assert c.type == CARD_TYPE_RELEARNING
d.sched.suspendCards([c.id]) d.sched.suspendCards([c.id])
d.sched.unsuspendCards([c.id]) d.sched.unsuspendCards([c.id])
c.load() c.load()
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
assert c.type == 3 assert c.type == CARD_TYPE_RELEARNING
assert c.due == due assert c.due == due
# should cope with cards in cram decks # should cope with cards in cram decks
c.due = 1 c.due = 1
@ -709,7 +712,8 @@ def test_filt_reviewing_early_normal():
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.ivl = 100 c.ivl = 100
c.type = c.queue = 2 c.queue = CARD_TYPE_REV
c.type = QUEUE_TYPE_REV
# due in 25 days, so it's been waiting 75 days # due in 25 days, so it's been waiting 75 days
c.due = d.sched.today + 25 c.due = d.sched.today + 25
c.mod = 1 c.mod = 1
@ -740,7 +744,7 @@ def test_filt_reviewing_early_normal():
assert c.due == d.sched.today + c.ivl assert c.due == d.sched.today + c.ivl
assert not c.odue assert not c.odue
# should not be in learning # should not be in learning
assert c.queue == 2 assert c.queue == QUEUE_TYPE_REV
# should be logged as a cram rep # should be logged as a cram rep
assert d.db.scalar("select type from revlog order by id desc limit 1") == 3 assert d.db.scalar("select type from revlog order by id desc limit 1") == 3
@ -771,11 +775,11 @@ def test_filt_keep_lrn_state():
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
assert c.type == c.queue == 1 assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
assert c.left == 3003 assert c.left == 3003
d.sched.answerCard(c, 3) d.sched.answerCard(c, 3)
assert c.type == c.queue == 1 assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
# create a dynamic deck and refresh it # create a dynamic deck and refresh it
did = d.decks.newDyn("Cram") did = d.decks.newDyn("Cram")
@ -784,7 +788,7 @@ def test_filt_keep_lrn_state():
# card should still be in learning state # card should still be in learning state
c.load() c.load()
assert c.type == c.queue == 1 assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
assert c.left == 2002 assert c.left == 2002
# should be able to advance learning steps # should be able to advance learning steps
@ -795,7 +799,7 @@ def test_filt_keep_lrn_state():
# emptying the deck preserves learning state # emptying the deck preserves learning state
d.sched.emptyDyn(did) d.sched.emptyDyn(did)
c.load() c.load()
assert c.type == c.queue == 1 assert c.type == CARD_TYPE_LRN and c.queue == QUEUE_TYPE_LRN
assert c.left == 1001 assert c.left == 1001
assert c.due - intTime() > 60 * 60 assert c.due - intTime() > 60 * 60
@ -833,9 +837,9 @@ def test_preview():
# passing it will remove it # passing it will remove it
d.sched.answerCard(c2, 2) d.sched.answerCard(c2, 2)
assert c2.queue == 0 assert c2.queue == QUEUE_TYPE_NEW
assert c2.reps == 0 assert c2.reps == 0
assert c2.type == 0 assert c2.type == CARD_TYPE_NEW
# the other card should appear again # the other card should appear again
c = d.sched.getCard() c = d.sched.getCard()
@ -844,9 +848,9 @@ def test_preview():
# emptying the filtered deck should restore card # emptying the filtered deck should restore card
d.sched.emptyDyn(did) d.sched.emptyDyn(did)
c.load() c.load()
assert c.queue == 0 assert c.queue == QUEUE_TYPE_NEW
assert c.reps == 0 assert c.reps == 0
assert c.type == 0 assert c.type == CARD_TYPE_NEW
def test_ordcycle(): def test_ordcycle():
@ -943,8 +947,8 @@ def test_repCounts():
f["Front"] = "three" f["Front"] = "three"
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = d.sched.today c.due = d.sched.today
c.flush() c.flush()
d.reset() d.reset()
@ -961,8 +965,8 @@ def test_timing():
f["Front"] = "num" + str(i) f["Front"] = "num" + str(i)
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = 0 c.due = 0
c.flush() c.flush()
# fail the first one # fail the first one
@ -971,13 +975,13 @@ def test_timing():
d.sched.answerCard(c, 1) d.sched.answerCard(c, 1)
# the next card should be another review # the next card should be another review
c2 = d.sched.getCard() c2 = d.sched.getCard()
assert c2.queue == 2 assert c2.queue == QUEUE_TYPE_REV
# if the failed card becomes due, it should show first # if the failed card becomes due, it should show first
c.due = time.time() - 1 c.due = time.time() - 1
c.flush() c.flush()
d.reset() d.reset()
c = d.sched.getCard() c = d.sched.getCard()
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
def test_collapse(): def test_collapse():
@ -1008,7 +1012,7 @@ def test_deckDue():
d.addNote(f) d.addNote(f)
# make it a review card # make it a review card
c = f.cards()[0] c = f.cards()[0]
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = 0 c.due = 0
c.flush() c.flush()
# add one more with a new deck # add one more with a new deck
@ -1126,8 +1130,8 @@ def test_forget():
f["Front"] = "one" f["Front"] = "one"
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.type = 2 c.type = CARD_TYPE_REV
c.ivl = 100 c.ivl = 100
c.due = 0 c.due = 0
c.flush() c.flush()
@ -1148,7 +1152,7 @@ def test_resched():
c.load() c.load()
assert c.due == d.sched.today assert c.due == d.sched.today
assert c.ivl == 1 assert c.ivl == 1
assert c.queue == c.type == 2 assert c.queue == QUEUE_TYPE_REV and c.type == CARD_TYPE_REV
d.sched.reschedCards([c.id], 1, 1) d.sched.reschedCards([c.id], 1, 1)
c.load() c.load()
assert c.due == d.sched.today + 1 assert c.due == d.sched.today + 1
@ -1162,8 +1166,8 @@ def test_norelearn():
f["Front"] = "one" f["Front"] = "one"
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.due = 0 c.due = 0
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
c.reps = 3 c.reps = 3
@ -1184,8 +1188,8 @@ def test_failmult():
f["Back"] = "two" f["Back"] = "two"
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.type = 2 c.type = CARD_TYPE_REV
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.ivl = 100 c.ivl = 100
c.due = d.sched.today - c.ivl c.due = d.sched.today - c.ivl
c.factor = STARTING_FACTOR c.factor = STARTING_FACTOR
@ -1217,8 +1221,8 @@ def test_moveVersions():
# the move to v2 should reset it to new # the move to v2 should reset it to new
col.changeSchedulerVer(2) col.changeSchedulerVer(2)
c.load() c.load()
assert c.queue == 0 assert c.queue == QUEUE_TYPE_NEW
assert c.type == 0 assert c.type == CARD_TYPE_NEW
# fail it again, and manually bury it # fail it again, and manually bury it
col.reset() col.reset()
@ -1226,19 +1230,19 @@ def test_moveVersions():
col.sched.answerCard(c, 1) col.sched.answerCard(c, 1)
col.sched.buryCards([c.id]) col.sched.buryCards([c.id])
c.load() c.load()
assert c.queue == -3 assert c.queue == QUEUE_TYPE_MANUALLY_BURIED
# revert to version 1 # revert to version 1
col.changeSchedulerVer(1) col.changeSchedulerVer(1)
# card should have moved queues # card should have moved queues
c.load() c.load()
assert c.queue == -2 assert c.queue == QUEUE_TYPE_SIBLING_BURIED
# and it should be new again when unburied # and it should be new again when unburied
col.sched.unburyCards() col.sched.unburyCards()
c.load() c.load()
assert c.queue == c.type == 0 assert c.type == CARD_TYPE_NEW and c.queue == QUEUE_TYPE_NEW
# make sure relearning cards transition correctly to v1 # make sure relearning cards transition correctly to v1
col.changeSchedulerVer(2) col.changeSchedulerVer(2)
@ -1269,7 +1273,7 @@ def test_negativeDueFilter():
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
c.due = -5 c.due = -5
c.queue = 2 c.queue = QUEUE_TYPE_REV
c.ivl = 5 c.ivl = 5
c.flush() c.flush()

View file

@ -55,18 +55,18 @@ def test_review():
# answer # answer
assert d.sched.counts() == (1, 0, 0) assert d.sched.counts() == (1, 0, 0)
c = d.sched.getCard() c = d.sched.getCard()
assert c.queue == 0 assert c.queue == QUEUE_TYPE_NEW
d.sched.answerCard(c, 3) d.sched.answerCard(c, 3)
assert c.left == 1001 assert c.left == 1001
assert d.sched.counts() == (0, 1, 0) assert d.sched.counts() == (0, 1, 0)
assert c.queue == 1 assert c.queue == QUEUE_TYPE_LRN
# undo # undo
assert d.undoName() assert d.undoName()
d.undo() d.undo()
d.reset() d.reset()
assert d.sched.counts() == (1, 0, 0) assert d.sched.counts() == (1, 0, 0)
c.load() c.load()
assert c.queue == 0 assert c.queue == QUEUE_TYPE_NEW
assert c.left != 1001 assert c.left != 1001
assert not d.undoName() assert not d.undoName()
# we should be able to undo multiple answers too # we should be able to undo multiple answers too

View file

@ -350,11 +350,13 @@ class DataModel(QAbstractTableModel):
def nextDue(self, c, index): def nextDue(self, c, index):
if c.odid: if c.odid:
return _("(filtered)") return _("(filtered)")
elif c.queue == 1: elif c.queue == QUEUE_TYPE_LRN:
date = c.due date = c.due
elif c.queue == 0 or c.type == 0: elif c.queue == QUEUE_TYPE_NEW or c.type == CARD_TYPE_NEW:
return str(c.due) return str(c.due)
elif c.queue in (2, 3) or (c.type == 2 and c.queue < 0): elif c.queue in (QUEUE_TYPE_REV, QUEUE_TYPE_DAY_LEARN_RELEARN) or (
c.type == CARD_TYPE_REV and c.queue < 0
):
date = time.time() + ((c.due - self.col.sched.today) * 86400) date = time.time() + ((c.due - self.col.sched.today) * 86400)
else: else:
return "" return ""
@ -1446,9 +1448,9 @@ border: 1px solid #000; padding: 3px; '>%s</div>"""
import anki.stats as st import anki.stats as st
fmt = "<span style='color:%s'>%s</span>" fmt = "<span style='color:%s'>%s</span>"
if type == 0: if type == CARD_TYPE_NEW:
tstr = fmt % (st.colLearn, tstr) tstr = fmt % (st.colLearn, tstr)
elif type == 1: elif type == CARD_TYPE_LRN:
tstr = fmt % (st.colMature, tstr) tstr = fmt % (st.colMature, tstr)
elif type == 2: elif type == 2:
tstr = fmt % (st.colRelearn, tstr) tstr = fmt % (st.colRelearn, tstr)
@ -1965,7 +1967,8 @@ update cards set usn=?, mod=?, did=? where id in """
def _reposition(self): def _reposition(self):
cids = self.selectedCards() cids = self.selectedCards()
cids2 = self.col.db.list( cids2 = self.col.db.list(
"select id from cards where type = 0 and id in " + ids2str(cids) f"select id from cards where type = {CARD_TYPE_NEW} and id in "
+ ids2str(cids)
) )
if not cids2: if not cids2:
return showInfo(_("Only new cards can be repositioned.")) return showInfo(_("Only new cards can be repositioned."))
@ -1974,7 +1977,7 @@ update cards set usn=?, mod=?, did=? where id in """
frm = aqt.forms.reposition.Ui_Dialog() frm = aqt.forms.reposition.Ui_Dialog()
frm.setupUi(d) frm.setupUi(d)
(pmin, pmax) = self.col.db.first( (pmin, pmax) = self.col.db.first(
"select min(due), max(due) from cards where type=0 and odid=0" f"select min(due), max(due) from cards where type={CARD_TYPE_NEW} and odid=0"
) )
pmin = pmin or 0 pmin = pmin or 0
pmax = pmax or 0 pmax = pmax or 0

View file

@ -32,17 +32,17 @@ class CustomStudy(QDialog):
f.setupUi(self) f.setupUi(self)
self.setWindowModality(Qt.WindowModal) self.setWindowModality(Qt.WindowModal)
self.setupSignals() self.setupSignals()
f.radio1.click() f.radioNew.click()
self.exec_() self.exec_()
def setupSignals(self): def setupSignals(self):
f = self.form f = self.form
f.radio1.clicked.connect(lambda: self.onRadioChange(1)) f.radioNew.clicked.connect(lambda: self.onRadioChange(RADIO_NEW))
f.radio2.clicked.connect(lambda: self.onRadioChange(2)) f.radioRev.clicked.connect(lambda: self.onRadioChange(RADIO_REV))
f.radio3.clicked.connect(lambda: self.onRadioChange(3)) f.radioForgot.clicked.connect(lambda: self.onRadioChange(RADIO_FORGOT))
f.radio4.clicked.connect(lambda: self.onRadioChange(4)) f.radioAhead.clicked.connect(lambda: self.onRadioChange(RADIO_AHEAD))
f.radio5.clicked.connect(lambda: self.onRadioChange(5)) f.radioPreview.clicked.connect(lambda: self.onRadioChange(RADIO_PREVIEW))
f.radio6.clicked.connect(lambda: self.onRadioChange(6)) f.radioCram.clicked.connect(lambda: self.onRadioChange(RADIO_CRAM))
def onRadioChange(self, idx): def onRadioChange(self, idx):
f = self.form f = self.form

View file

@ -17,42 +17,42 @@
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="3" column="0"> <item row="3" column="0">
<widget class="QRadioButton" name="radio4"> <widget class="QRadioButton" name="radioAhead">
<property name="text"> <property name="text">
<string>Review ahead</string> <string>Review ahead</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QRadioButton" name="radio3"> <widget class="QRadioButton" name="radioForgot">
<property name="text"> <property name="text">
<string>Review forgotten cards</string> <string>Review forgotten cards</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QRadioButton" name="radio1"> <widget class="QRadioButton" name="radioNew">
<property name="text"> <property name="text">
<string>Increase today's new card limit</string> <string>Increase today's new card limit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QRadioButton" name="radio2"> <widget class="QRadioButton" name="radioRev">
<property name="text"> <property name="text">
<string>Increase today's review card limit</string> <string>Increase today's review card limit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="5" column="0">
<widget class="QRadioButton" name="radio6"> <widget class="QRadioButton" name="radioCram">
<property name="text"> <property name="text">
<string>Study by card state or tag</string> <string>Study by card state or tag</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="4" column="0">
<widget class="QRadioButton" name="radio5"> <widget class="QRadioButton" name="radioPreview">
<property name="text"> <property name="text">
<string>Preview new cards</string> <string>Preview new cards</string>
</property> </property>
@ -163,11 +163,12 @@
</layout> </layout>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>radio1</tabstop> <tabstop>radioNew</tabstop>
<tabstop>radio2</tabstop> <tabstop>radioRev</tabstop>
<tabstop>radio3</tabstop> <tabstop>radioForgot</tabstop>
<tabstop>radio4</tabstop> <tabstop>radioAhead</tabstop>
<tabstop>radio6</tabstop> <tabstop>radioPreview</tabstop>
<tabstop>radioCram</tabstop>
<tabstop>spin</tabstop> <tabstop>spin</tabstop>
<tabstop>buttonBox</tabstop> <tabstop>buttonBox</tabstop>
</tabstops> </tabstops>