separate queue building and card answering
This commit is contained in:
parent
b8ad694006
commit
c659898ee1
@ -51,10 +51,46 @@ class Scheduler:
|
|||||||
self._lrnCutoff = 0
|
self._lrnCutoff = 0
|
||||||
self._updateCutoff()
|
self._updateCutoff()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
# Daily cutoff
|
||||||
d = dict(self.__dict__)
|
##########################################################################
|
||||||
del d["col"]
|
|
||||||
return f"{super().__repr__()} {pprint.pformat(d, width=300)}"
|
def _updateCutoff(self) -> None:
|
||||||
|
timing = self._timing_today()
|
||||||
|
self.today = timing.days_elapsed
|
||||||
|
self.dayCutoff = timing.next_day_at
|
||||||
|
|
||||||
|
def _checkDay(self) -> None:
|
||||||
|
# check if the day has rolled over
|
||||||
|
if time.time() > self.dayCutoff:
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def _timing_today(self) -> SchedTimingToday:
|
||||||
|
return self.col._backend.sched_timing_today()
|
||||||
|
|
||||||
|
# Fetching the next card
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
self.col.decks.update_active()
|
||||||
|
self._updateCutoff()
|
||||||
|
self._reset_counts()
|
||||||
|
self._resetLrn()
|
||||||
|
self._resetRev()
|
||||||
|
self._resetNew()
|
||||||
|
self._haveQueues = True
|
||||||
|
|
||||||
|
def _reset_counts(self) -> None:
|
||||||
|
tree = self.deck_due_tree(self.col.decks.selected())
|
||||||
|
node = self.col.decks.find_deck_in_tree(tree, int(self.col.conf["curDeck"]))
|
||||||
|
if not node:
|
||||||
|
# current deck points to a missing deck
|
||||||
|
self.newCount = 0
|
||||||
|
self.revCount = 0
|
||||||
|
self._immediate_learn_count = 0
|
||||||
|
else:
|
||||||
|
self.newCount = node.new_count
|
||||||
|
self.revCount = node.review_count
|
||||||
|
self._immediate_learn_count = node.learn_count
|
||||||
|
|
||||||
def getCard(self) -> Optional[Card]:
|
def getCard(self) -> Optional[Card]:
|
||||||
"""Pop the next card from the queue. None if finished."""
|
"""Pop the next card from the queue. None if finished."""
|
||||||
@ -71,119 +107,6 @@ class Scheduler:
|
|||||||
return card
|
return card
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def reset(self) -> None:
|
|
||||||
self.col.decks.update_active()
|
|
||||||
self._updateCutoff()
|
|
||||||
self._reset_counts()
|
|
||||||
self._resetLrn()
|
|
||||||
self._resetRev()
|
|
||||||
self._resetNew()
|
|
||||||
self._haveQueues = True
|
|
||||||
|
|
||||||
def answerCard(self, card: Card, ease: int) -> None:
|
|
||||||
self.col.log()
|
|
||||||
assert 1 <= ease <= 4
|
|
||||||
assert 0 <= card.queue <= 4
|
|
||||||
self.col.markReview(card)
|
|
||||||
if self._burySiblingsOnAnswer:
|
|
||||||
self._burySiblings(card)
|
|
||||||
|
|
||||||
self._answerCard(card, ease)
|
|
||||||
|
|
||||||
card.mod = intTime()
|
|
||||||
card.usn = self.col.usn()
|
|
||||||
card.flush()
|
|
||||||
|
|
||||||
def _answerCard(self, card: Card, ease: int) -> None:
|
|
||||||
if self._previewingCard(card):
|
|
||||||
self._answerCardPreview(card, ease)
|
|
||||||
return
|
|
||||||
|
|
||||||
card.reps += 1
|
|
||||||
|
|
||||||
new_delta = 0
|
|
||||||
review_delta = 0
|
|
||||||
|
|
||||||
if card.queue == QUEUE_TYPE_NEW:
|
|
||||||
# came from the new queue, move to learning
|
|
||||||
card.queue = QUEUE_TYPE_LRN
|
|
||||||
card.type = CARD_TYPE_LRN
|
|
||||||
# init reps to graduation
|
|
||||||
card.left = self._startingLeft(card)
|
|
||||||
new_delta = +1
|
|
||||||
|
|
||||||
if card.queue in (QUEUE_TYPE_LRN, QUEUE_TYPE_DAY_LEARN_RELEARN):
|
|
||||||
self._answerLrnCard(card, ease)
|
|
||||||
elif card.queue == QUEUE_TYPE_REV:
|
|
||||||
self._answerRevCard(card, ease)
|
|
||||||
review_delta = +1
|
|
||||||
else:
|
|
||||||
raise Exception(f"Invalid queue '{card}'")
|
|
||||||
|
|
||||||
self.update_stats(
|
|
||||||
card.did,
|
|
||||||
new_delta=new_delta,
|
|
||||||
review_delta=review_delta,
|
|
||||||
milliseconds_delta=+card.timeTaken(),
|
|
||||||
)
|
|
||||||
|
|
||||||
# once a card has been answered once, the original due date
|
|
||||||
# no longer applies
|
|
||||||
if card.odue:
|
|
||||||
card.odue = 0
|
|
||||||
|
|
||||||
# note: when adding revlog entries in the future, make sure undo
|
|
||||||
# code deletes the entries
|
|
||||||
def _answerCardPreview(self, card: Card, ease: int) -> None:
|
|
||||||
assert 1 <= ease <= 2
|
|
||||||
|
|
||||||
if ease == BUTTON_ONE:
|
|
||||||
# repeat after delay
|
|
||||||
card.queue = QUEUE_TYPE_PREVIEW
|
|
||||||
card.due = intTime() + self._previewDelay(card)
|
|
||||||
self.lrnCount += 1
|
|
||||||
else:
|
|
||||||
# BUTTON_TWO
|
|
||||||
# restore original card state and remove from filtered deck
|
|
||||||
self._restorePreviewCard(card)
|
|
||||||
self._removeFromFiltered(card)
|
|
||||||
|
|
||||||
def _reset_counts(self) -> None:
|
|
||||||
tree = self.deck_due_tree(self.col.decks.selected())
|
|
||||||
node = self.col.decks.find_deck_in_tree(tree, int(self.col.conf["curDeck"]))
|
|
||||||
if not node:
|
|
||||||
# current deck points to a missing deck
|
|
||||||
self.newCount = 0
|
|
||||||
self.revCount = 0
|
|
||||||
self._immediate_learn_count = 0
|
|
||||||
else:
|
|
||||||
self.newCount = node.new_count
|
|
||||||
self.revCount = node.review_count
|
|
||||||
self._immediate_learn_count = node.learn_count
|
|
||||||
|
|
||||||
# Rev/lrn/time daily stats
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def update_stats(
|
|
||||||
self,
|
|
||||||
deck_id: int,
|
|
||||||
new_delta: int = 0,
|
|
||||||
review_delta: int = 0,
|
|
||||||
milliseconds_delta: int = 0,
|
|
||||||
) -> None:
|
|
||||||
self.col._backend.update_stats(
|
|
||||||
deck_id=deck_id,
|
|
||||||
new_delta=new_delta,
|
|
||||||
review_delta=review_delta,
|
|
||||||
millisecond_delta=milliseconds_delta,
|
|
||||||
)
|
|
||||||
|
|
||||||
def counts_for_deck_today(self, deck_id: int) -> CountsForDeckToday:
|
|
||||||
return self.col._backend.counts_for_deck_today(deck_id)
|
|
||||||
|
|
||||||
# Getting the next card
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def _getCard(self) -> Optional[Card]:
|
def _getCard(self) -> Optional[Card]:
|
||||||
"""Return the next due card, or None."""
|
"""Return the next due card, or None."""
|
||||||
# learning card due?
|
# learning card due?
|
||||||
@ -223,7 +146,7 @@ class Scheduler:
|
|||||||
# collapse or finish
|
# collapse or finish
|
||||||
return self._getLrnCard(collapse=True)
|
return self._getLrnCard(collapse=True)
|
||||||
|
|
||||||
# New cards
|
# Fetching new cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _resetNew(self) -> None:
|
def _resetNew(self) -> None:
|
||||||
@ -339,7 +262,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_NEW} limit ?)"""
|
|||||||
self.reportLimit,
|
self.reportLimit,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Learning queues
|
# Fetching learning cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
# scan for any newly due learning cards every minute
|
# scan for any newly due learning cards every minute
|
||||||
@ -460,6 +383,190 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
|
|||||||
return self.col.getCard(self._lrnDayQueue.pop())
|
return self.col.getCard(self._lrnDayQueue.pop())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Fetching reviews
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
def _currentRevLimit(self) -> int:
|
||||||
|
d = self.col.decks.get(self.col.decks.selected(), default=False)
|
||||||
|
return self._deckRevLimitSingle(d)
|
||||||
|
|
||||||
|
def _deckRevLimitSingle(
|
||||||
|
self, d: Dict[str, Any], parentLimit: Optional[int] = None
|
||||||
|
) -> int:
|
||||||
|
# invalid deck selected?
|
||||||
|
if not d:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if d["dyn"]:
|
||||||
|
return self.dynReportLimit
|
||||||
|
|
||||||
|
c = self.col.decks.confForDid(d["id"])
|
||||||
|
lim = max(0, c["rev"]["perDay"] - self.counts_for_deck_today(d["id"]).review)
|
||||||
|
|
||||||
|
if parentLimit is not None:
|
||||||
|
lim = min(parentLimit, lim)
|
||||||
|
elif "::" in d["name"]:
|
||||||
|
for parent in self.col.decks.parents(d["id"]):
|
||||||
|
# pass in dummy parentLimit so we don't do parent lookup again
|
||||||
|
lim = min(lim, self._deckRevLimitSingle(parent, parentLimit=lim))
|
||||||
|
return hooks.scheduler_review_limit_for_single_deck(lim, d)
|
||||||
|
|
||||||
|
def _revForDeck(
|
||||||
|
self, did: int, lim: int, childMap: DeckManager.childMapNode
|
||||||
|
) -> Any:
|
||||||
|
dids = [did] + self.col.decks.childDids(did, childMap)
|
||||||
|
lim = min(lim, self.reportLimit)
|
||||||
|
return self.col.db.scalar(
|
||||||
|
f"""
|
||||||
|
select count() from
|
||||||
|
(select 1 from cards where did in %s and queue = {QUEUE_TYPE_REV}
|
||||||
|
and due <= ? limit ?)"""
|
||||||
|
% ids2str(dids),
|
||||||
|
self.today,
|
||||||
|
lim,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _resetRev(self) -> None:
|
||||||
|
self._revQueue: List[int] = []
|
||||||
|
|
||||||
|
def _fillRev(self, recursing: bool = False) -> bool:
|
||||||
|
"True if a review card can be fetched."
|
||||||
|
if self._revQueue:
|
||||||
|
return True
|
||||||
|
if not self.revCount:
|
||||||
|
return False
|
||||||
|
|
||||||
|
lim = min(self.queueLimit, self._currentRevLimit())
|
||||||
|
if lim:
|
||||||
|
self._revQueue = self.col.db.list(
|
||||||
|
f"""
|
||||||
|
select id from cards where
|
||||||
|
did in %s and queue = {QUEUE_TYPE_REV} and due <= ?
|
||||||
|
order by due, random()
|
||||||
|
limit ?"""
|
||||||
|
% self._deckLimit(),
|
||||||
|
self.today,
|
||||||
|
lim,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._revQueue:
|
||||||
|
# preserve order
|
||||||
|
self._revQueue.reverse()
|
||||||
|
return True
|
||||||
|
|
||||||
|
if recursing:
|
||||||
|
print("bug: fillRev2()")
|
||||||
|
return False
|
||||||
|
self._reset_counts()
|
||||||
|
self._resetRev()
|
||||||
|
return self._fillRev(recursing=True)
|
||||||
|
|
||||||
|
def _getRevCard(self) -> Optional[Card]:
|
||||||
|
if self._fillRev():
|
||||||
|
self.revCount -= 1
|
||||||
|
return self.col.getCard(self._revQueue.pop())
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Answering a card
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
def answerCard(self, card: Card, ease: int) -> None:
|
||||||
|
self.col.log()
|
||||||
|
assert 1 <= ease <= 4
|
||||||
|
assert 0 <= card.queue <= 4
|
||||||
|
self.col.markReview(card)
|
||||||
|
if self._burySiblingsOnAnswer:
|
||||||
|
self._burySiblings(card)
|
||||||
|
|
||||||
|
self._answerCard(card, ease)
|
||||||
|
|
||||||
|
card.mod = intTime()
|
||||||
|
card.usn = self.col.usn()
|
||||||
|
card.flush()
|
||||||
|
|
||||||
|
def _answerCard(self, card: Card, ease: int) -> None:
|
||||||
|
if self._previewingCard(card):
|
||||||
|
self._answerCardPreview(card, ease)
|
||||||
|
return
|
||||||
|
|
||||||
|
card.reps += 1
|
||||||
|
|
||||||
|
new_delta = 0
|
||||||
|
review_delta = 0
|
||||||
|
|
||||||
|
if card.queue == QUEUE_TYPE_NEW:
|
||||||
|
# came from the new queue, move to learning
|
||||||
|
card.queue = QUEUE_TYPE_LRN
|
||||||
|
card.type = CARD_TYPE_LRN
|
||||||
|
# init reps to graduation
|
||||||
|
card.left = self._startingLeft(card)
|
||||||
|
new_delta = +1
|
||||||
|
|
||||||
|
if card.queue in (QUEUE_TYPE_LRN, QUEUE_TYPE_DAY_LEARN_RELEARN):
|
||||||
|
self._answerLrnCard(card, ease)
|
||||||
|
elif card.queue == QUEUE_TYPE_REV:
|
||||||
|
self._answerRevCard(card, ease)
|
||||||
|
review_delta = +1
|
||||||
|
else:
|
||||||
|
raise Exception(f"Invalid queue '{card}'")
|
||||||
|
|
||||||
|
self.update_stats(
|
||||||
|
card.did,
|
||||||
|
new_delta=new_delta,
|
||||||
|
review_delta=review_delta,
|
||||||
|
milliseconds_delta=+card.timeTaken(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# once a card has been answered once, the original due date
|
||||||
|
# no longer applies
|
||||||
|
if card.odue:
|
||||||
|
card.odue = 0
|
||||||
|
|
||||||
|
def _cardConf(self, card: Card) -> DeckConfig:
|
||||||
|
return self.col.decks.confForDid(card.did)
|
||||||
|
|
||||||
|
def _deckLimit(self) -> str:
|
||||||
|
return ids2str(self.col.decks.active())
|
||||||
|
|
||||||
|
# Answering (re)learning cards
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
def _newConf(self, card: Card) -> Any:
|
||||||
|
conf = self._cardConf(card)
|
||||||
|
# normal deck
|
||||||
|
if not card.odid:
|
||||||
|
return conf["new"]
|
||||||
|
# dynamic deck; override some attributes, use original deck for others
|
||||||
|
oconf = self.col.decks.confForDid(card.odid)
|
||||||
|
return dict(
|
||||||
|
# original deck
|
||||||
|
ints=oconf["new"]["ints"],
|
||||||
|
initialFactor=oconf["new"]["initialFactor"],
|
||||||
|
bury=oconf["new"].get("bury", True),
|
||||||
|
delays=oconf["new"]["delays"],
|
||||||
|
# overrides
|
||||||
|
order=NEW_CARDS_DUE,
|
||||||
|
perDay=self.reportLimit,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _lapseConf(self, card: Card) -> Any:
|
||||||
|
conf = self._cardConf(card)
|
||||||
|
# normal deck
|
||||||
|
if not card.odid:
|
||||||
|
return conf["lapse"]
|
||||||
|
# dynamic deck; override some attributes, use original deck for others
|
||||||
|
oconf = self.col.decks.confForDid(card.odid)
|
||||||
|
return dict(
|
||||||
|
# original deck
|
||||||
|
minInt=oconf["lapse"]["minInt"],
|
||||||
|
leechFails=oconf["lapse"]["leechFails"],
|
||||||
|
leechAction=oconf["lapse"]["leechAction"],
|
||||||
|
mult=oconf["lapse"]["mult"],
|
||||||
|
delays=oconf["lapse"]["delays"],
|
||||||
|
# overrides
|
||||||
|
resched=conf["resched"],
|
||||||
|
)
|
||||||
|
|
||||||
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 (CARD_TYPE_REV, CARD_TYPE_RELEARNING):
|
if card.type in (CARD_TYPE_REV, CARD_TYPE_RELEARNING):
|
||||||
@ -683,125 +790,61 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
|
|||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
log()
|
log()
|
||||||
|
|
||||||
def _lrnForDeck(self, did: int) -> int:
|
# note: when adding revlog entries in the future, make sure undo
|
||||||
cnt = (
|
# code deletes the entries
|
||||||
self.col.db.scalar(
|
def _answerCardPreview(self, card: Card, ease: int) -> None:
|
||||||
f"""
|
assert 1 <= ease <= 2
|
||||||
select count() from
|
|
||||||
(select null from cards where did = ? and queue = {QUEUE_TYPE_LRN} and due < ? limit ?)""",
|
|
||||||
did,
|
|
||||||
intTime() + self.col.conf["collapseTime"],
|
|
||||||
self.reportLimit,
|
|
||||||
)
|
|
||||||
or 0
|
|
||||||
)
|
|
||||||
return cnt + self.col.db.scalar(
|
|
||||||
f"""
|
|
||||||
select count() from
|
|
||||||
(select null from cards where did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN}
|
|
||||||
and due <= ? limit ?)""",
|
|
||||||
did,
|
|
||||||
self.today,
|
|
||||||
self.reportLimit,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Reviews
|
if ease == BUTTON_ONE:
|
||||||
##########################################################################
|
# repeat after delay
|
||||||
|
card.queue = QUEUE_TYPE_PREVIEW
|
||||||
|
card.due = intTime() + self._previewDelay(card)
|
||||||
|
self.lrnCount += 1
|
||||||
|
else:
|
||||||
|
# BUTTON_TWO
|
||||||
|
# restore original card state and remove from filtered deck
|
||||||
|
self._restorePreviewCard(card)
|
||||||
|
self._removeFromFiltered(card)
|
||||||
|
|
||||||
def _currentRevLimit(self) -> int:
|
def _previewingCard(self, card: Card) -> Any:
|
||||||
d = self.col.decks.get(self.col.decks.selected(), default=False)
|
conf = self._cardConf(card)
|
||||||
return self._deckRevLimitSingle(d)
|
return conf["dyn"] and not conf["resched"]
|
||||||
|
|
||||||
def _deckRevLimitSingle(
|
def _previewDelay(self, card: Card) -> Any:
|
||||||
self, d: Dict[str, Any], parentLimit: Optional[int] = None
|
return self._cardConf(card).get("previewDelay", 10) * 60
|
||||||
) -> int:
|
|
||||||
# invalid deck selected?
|
|
||||||
if not d:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if d["dyn"]:
|
def _removeFromFiltered(self, card: Card) -> None:
|
||||||
return self.dynReportLimit
|
if card.odid:
|
||||||
|
card.did = card.odid
|
||||||
|
card.odue = 0
|
||||||
|
card.odid = 0
|
||||||
|
|
||||||
c = self.col.decks.confForDid(d["id"])
|
def _restorePreviewCard(self, card: Card) -> None:
|
||||||
lim = max(0, c["rev"]["perDay"] - self.counts_for_deck_today(d["id"]).review)
|
assert card.odid
|
||||||
|
|
||||||
if parentLimit is not None:
|
card.due = card.odue
|
||||||
lim = min(parentLimit, lim)
|
|
||||||
elif "::" in d["name"]:
|
|
||||||
for parent in self.col.decks.parents(d["id"]):
|
|
||||||
# pass in dummy parentLimit so we don't do parent lookup again
|
|
||||||
lim = min(lim, self._deckRevLimitSingle(parent, parentLimit=lim))
|
|
||||||
return hooks.scheduler_review_limit_for_single_deck(lim, d)
|
|
||||||
|
|
||||||
def _revForDeck(
|
# learning and relearning cards may be seconds-based or day-based;
|
||||||
self, did: int, lim: int, childMap: DeckManager.childMapNode
|
# other types map directly to queues
|
||||||
) -> Any:
|
if card.type in (CARD_TYPE_LRN, CARD_TYPE_RELEARNING):
|
||||||
dids = [did] + self.col.decks.childDids(did, childMap)
|
if card.odue > 1000000000:
|
||||||
lim = min(lim, self.reportLimit)
|
card.queue = QUEUE_TYPE_LRN
|
||||||
return self.col.db.scalar(
|
else:
|
||||||
f"""
|
card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN
|
||||||
select count() from
|
else:
|
||||||
(select 1 from cards where did in %s and queue = {QUEUE_TYPE_REV}
|
card.queue = card.type
|
||||||
and due <= ? limit ?)"""
|
|
||||||
% ids2str(dids),
|
|
||||||
self.today,
|
|
||||||
lim,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _resetRev(self) -> None:
|
|
||||||
self._revQueue: List[int] = []
|
|
||||||
|
|
||||||
def _fillRev(self, recursing: bool = False) -> bool:
|
|
||||||
"True if a review card can be fetched."
|
|
||||||
if self._revQueue:
|
|
||||||
return True
|
|
||||||
if not self.revCount:
|
|
||||||
return False
|
|
||||||
|
|
||||||
lim = min(self.queueLimit, self._currentRevLimit())
|
|
||||||
if lim:
|
|
||||||
self._revQueue = self.col.db.list(
|
|
||||||
f"""
|
|
||||||
select id from cards where
|
|
||||||
did in %s and queue = {QUEUE_TYPE_REV} and due <= ?
|
|
||||||
order by due, random()
|
|
||||||
limit ?"""
|
|
||||||
% self._deckLimit(),
|
|
||||||
self.today,
|
|
||||||
lim,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._revQueue:
|
|
||||||
# preserve order
|
|
||||||
self._revQueue.reverse()
|
|
||||||
return True
|
|
||||||
|
|
||||||
if recursing:
|
|
||||||
print("bug: fillRev2()")
|
|
||||||
return False
|
|
||||||
self._reset_counts()
|
|
||||||
self._resetRev()
|
|
||||||
return self._fillRev(recursing=True)
|
|
||||||
|
|
||||||
def _getRevCard(self) -> Optional[Card]:
|
|
||||||
if self._fillRev():
|
|
||||||
self.revCount -= 1
|
|
||||||
return self.col.getCard(self._revQueue.pop())
|
|
||||||
return None
|
|
||||||
|
|
||||||
def totalRevForCurrentDeck(self) -> int:
|
|
||||||
return self.col.db.scalar(
|
|
||||||
f"""
|
|
||||||
select count() from cards where id in (
|
|
||||||
select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? limit ?)"""
|
|
||||||
% self._deckLimit(),
|
|
||||||
self.today,
|
|
||||||
self.reportLimit,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Answering a review card
|
# Answering a review card
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
def _revConf(self, card: Card) -> QueueConfig:
|
||||||
|
conf = self._cardConf(card)
|
||||||
|
# normal deck
|
||||||
|
if not card.odid:
|
||||||
|
return conf["rev"]
|
||||||
|
# dynamic deck
|
||||||
|
return self.col.decks.confForDid(card.odid)["rev"]
|
||||||
|
|
||||||
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))
|
||||||
@ -878,9 +921,6 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
|||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
log()
|
log()
|
||||||
|
|
||||||
# Interval management
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def _nextRevIvl(self, card: Card, ease: int, fuzz: bool) -> int:
|
def _nextRevIvl(self, card: Card, ease: int, fuzz: bool) -> int:
|
||||||
"Next review interval for CARD, given EASE."
|
"Next review interval for CARD, given EASE."
|
||||||
delay = self._daysLate(card)
|
delay = self._daysLate(card)
|
||||||
@ -980,105 +1020,25 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
|||||||
|
|
||||||
return ivl
|
return ivl
|
||||||
|
|
||||||
# Filtered deck handling
|
# Daily limits
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _removeFromFiltered(self, card: Card) -> None:
|
def update_stats(
|
||||||
if card.odid:
|
self,
|
||||||
card.did = card.odid
|
deck_id: int,
|
||||||
card.odue = 0
|
new_delta: int = 0,
|
||||||
card.odid = 0
|
review_delta: int = 0,
|
||||||
|
milliseconds_delta: int = 0,
|
||||||
def _restorePreviewCard(self, card: Card) -> None:
|
) -> None:
|
||||||
assert card.odid
|
self.col._backend.update_stats(
|
||||||
|
deck_id=deck_id,
|
||||||
card.due = card.odue
|
new_delta=new_delta,
|
||||||
|
review_delta=review_delta,
|
||||||
# learning and relearning cards may be seconds-based or day-based;
|
millisecond_delta=milliseconds_delta,
|
||||||
# other types map directly to queues
|
|
||||||
if card.type in (CARD_TYPE_LRN, CARD_TYPE_RELEARNING):
|
|
||||||
if card.odue > 1000000000:
|
|
||||||
card.queue = QUEUE_TYPE_LRN
|
|
||||||
else:
|
|
||||||
card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN
|
|
||||||
else:
|
|
||||||
card.queue = card.type
|
|
||||||
|
|
||||||
# Tools
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def _cardConf(self, card: Card) -> DeckConfig:
|
|
||||||
return self.col.decks.confForDid(card.did)
|
|
||||||
|
|
||||||
def _newConf(self, card: Card) -> Any:
|
|
||||||
conf = self._cardConf(card)
|
|
||||||
# normal deck
|
|
||||||
if not card.odid:
|
|
||||||
return conf["new"]
|
|
||||||
# dynamic deck; override some attributes, use original deck for others
|
|
||||||
oconf = self.col.decks.confForDid(card.odid)
|
|
||||||
return dict(
|
|
||||||
# original deck
|
|
||||||
ints=oconf["new"]["ints"],
|
|
||||||
initialFactor=oconf["new"]["initialFactor"],
|
|
||||||
bury=oconf["new"].get("bury", True),
|
|
||||||
delays=oconf["new"]["delays"],
|
|
||||||
# overrides
|
|
||||||
order=NEW_CARDS_DUE,
|
|
||||||
perDay=self.reportLimit,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _lapseConf(self, card: Card) -> Any:
|
def counts_for_deck_today(self, deck_id: int) -> CountsForDeckToday:
|
||||||
conf = self._cardConf(card)
|
return self.col._backend.counts_for_deck_today(deck_id)
|
||||||
# normal deck
|
|
||||||
if not card.odid:
|
|
||||||
return conf["lapse"]
|
|
||||||
# dynamic deck; override some attributes, use original deck for others
|
|
||||||
oconf = self.col.decks.confForDid(card.odid)
|
|
||||||
return dict(
|
|
||||||
# original deck
|
|
||||||
minInt=oconf["lapse"]["minInt"],
|
|
||||||
leechFails=oconf["lapse"]["leechFails"],
|
|
||||||
leechAction=oconf["lapse"]["leechAction"],
|
|
||||||
mult=oconf["lapse"]["mult"],
|
|
||||||
delays=oconf["lapse"]["delays"],
|
|
||||||
# overrides
|
|
||||||
resched=conf["resched"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def _revConf(self, card: Card) -> QueueConfig:
|
|
||||||
conf = self._cardConf(card)
|
|
||||||
# normal deck
|
|
||||||
if not card.odid:
|
|
||||||
return conf["rev"]
|
|
||||||
# dynamic deck
|
|
||||||
return self.col.decks.confForDid(card.odid)["rev"]
|
|
||||||
|
|
||||||
def _deckLimit(self) -> str:
|
|
||||||
return ids2str(self.col.decks.active())
|
|
||||||
|
|
||||||
def _previewingCard(self, card: Card) -> Any:
|
|
||||||
conf = self._cardConf(card)
|
|
||||||
return conf["dyn"] and not conf["resched"]
|
|
||||||
|
|
||||||
def _previewDelay(self, card: Card) -> Any:
|
|
||||||
return self._cardConf(card).get("previewDelay", 10) * 60
|
|
||||||
|
|
||||||
# Daily cutoff
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def _updateCutoff(self) -> None:
|
|
||||||
timing = self._timing_today()
|
|
||||||
self.today = timing.days_elapsed
|
|
||||||
self.dayCutoff = timing.next_day_at
|
|
||||||
|
|
||||||
def _checkDay(self) -> None:
|
|
||||||
# check if the day has rolled over
|
|
||||||
if time.time() > self.dayCutoff:
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def _timing_today(self) -> SchedTimingToday:
|
|
||||||
return self.col._backend.sched_timing_today()
|
|
||||||
|
|
||||||
# Next times
|
# Next times
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@ -1227,7 +1187,7 @@ and (queue={QUEUE_TYPE_NEW} or (queue={QUEUE_TYPE_REV} and due<=?))""",
|
|||||||
If top_deck_id provided, counts are limited to that node."""
|
If top_deck_id provided, counts are limited to that node."""
|
||||||
return self.col._backend.deck_tree(top_deck_id=top_deck_id, now=intTime())
|
return self.col._backend.deck_tree(top_deck_id=top_deck_id, now=intTime())
|
||||||
|
|
||||||
# Deck finished state
|
# Deck finished state & custom study
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def congratulations_info(self) -> CongratsInfo:
|
def congratulations_info(self) -> CongratsInfo:
|
||||||
@ -1251,6 +1211,16 @@ and (queue={QUEUE_TYPE_NEW} or (queue={QUEUE_TYPE_REV} and due<=?))""",
|
|||||||
"Don't use this, it is a stop-gap until this code is refactored."
|
"Don't use this, it is a stop-gap until this code is refactored."
|
||||||
return not any((self.newCount, self.revCount, self._immediate_learn_count))
|
return not any((self.newCount, self.revCount, self._immediate_learn_count))
|
||||||
|
|
||||||
|
def totalRevForCurrentDeck(self) -> int:
|
||||||
|
return self.col.db.scalar(
|
||||||
|
f"""
|
||||||
|
select count() from cards where id in (
|
||||||
|
select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? limit ?)"""
|
||||||
|
% self._deckLimit(),
|
||||||
|
self.today,
|
||||||
|
self.reportLimit,
|
||||||
|
)
|
||||||
|
|
||||||
# Filtered deck handling
|
# Filtered deck handling
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
@ -1359,6 +1329,13 @@ and (queue={QUEUE_TYPE_NEW} or (queue={QUEUE_TYPE_REV} and due<=?))""",
|
|||||||
if conf["new"]["order"] == NEW_CARDS_RANDOM:
|
if conf["new"]["order"] == NEW_CARDS_RANDOM:
|
||||||
self.randomizeCards(did)
|
self.randomizeCards(did)
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
d = dict(self.__dict__)
|
||||||
|
del d["col"]
|
||||||
|
return f"{super().__repr__()} {pprint.pformat(d, width=300)}"
|
||||||
|
|
||||||
# Legacy aliases and helpers
|
# Legacy aliases and helpers
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user