diff --git a/anki/sched.py b/anki/sched.py
index a0ea9c1da..ad0981075 100644
--- a/anki/sched.py
+++ b/anki/sched.py
@@ -7,7 +7,7 @@ from operator import itemgetter
from heapq import *
#from anki.cards import Card
from anki.utils import parseTags, ids2str, intTime
-from anki.lang import _
+from anki.lang import _, ngettext
from anki.consts import *
from anki.hooks import runHook
@@ -58,18 +58,6 @@ class Scheduler(object):
"Does not include fetched but unanswered."
return (self.learnCount, self.revCount, self.newCount)
- def timeToday(self):
- "Time spent learning today, in seconds."
- return self.deck.db.scalar(
- "select sum(taken/1000.0) from revlog where time > ?*1000",
- self.dayCutoff-86400) or 0
-
- def repsToday(self):
- "Number of cards answered today."
- return self.deck.db.scalar(
- "select count() from revlog where time > ?*1000",
- self.dayCutoff-86400)
-
def cardQueue(self, card):
return card.queue
@@ -476,206 +464,96 @@ queue = 1 %s and due <= :lim order by %s limit %d""" % (
self.updateCutoff()
self.reset()
- # Times
+ # Deck finished state
##########################################################################
- def nextDueMsg(self):
- next = self.earliestTime()
- if next:
- # all new cards except suspended
- newCount = self.newCardsDueBy(self.dayCutoff + 86400)
- newCardsTomorrow = min(newCount, self.newCardsPerDay)
- cards = self.cardsDueBy(self.dayCutoff + 86400)
- msg = _('''\
-
-At this time tomorrow:
-%(wait)s
-%(new)s''') % {
- 'new': ngettext("There will be %d new card.",
- "There will be %d new cards.",
- newCardsTomorrow) % newCardsTomorrow,
- 'wait': ngettext("There will be %s review.",
- "There will be %s reviews.", cards) % cards,
- }
- if next > (self.dayCutoff+86400) and not newCardsTomorrow:
- msg = (_("The next review is in %s.") %
- self.earliestTimeStr())
+ def finishedMsg(self):
+ return (
+ "
"+_("Congratulations!")+"
"+
+ self._finishedSubtitle()+
+ "
"+
+ self._nextDueMsg())
+
+ def _finishedSubtitle(self):
+ if self.deck.activeGroups("rev") or self.deck.activeGroups("new"):
+ return _("You have finished the selected groups for now.")
else:
- msg = _("No cards are due.")
- return msg
+ return _("You have finished the deck for now.")
- def earliestTime(self):
- """Return the time of the earliest card.
-This may be in the past if the deck is not finished.
-If the deck has no (enabled) cards, return None.
-Ignore new cards."""
- earliestRev = self.db.scalar(self.cardLimit("revActive", "revInactive", """
-select due from cards c where queue = 1
-order by due
-limit 1"""))
- earliestFail = self.db.scalar(self.cardLimit("revActive", "revInactive", """
-select due+%d from cards c where queue = 0
-order by due
-limit 1""" % self.delay0))
- if earliestRev and earliestFail:
- return min(earliestRev, earliestFail)
- elif earliestRev:
- return earliestRev
+ def _nextDueMsg(self):
+ line = []
+ rev = self.revTomorrow() + self.lrnTomorrow()
+ if rev:
+ line.append(
+ ngettext("There will be %s review.",
+ "There will be %s reviews.", rev) % rev)
+ new = self.newTomorrow()
+ if new:
+ line.append(
+ ngettext("There will be %d new card.",
+ "There will be %d new cards.", new) % new)
+ if line:
+ line.insert(0, _("At this time tomorrow:"))
+ buf = "
".join(line)
else:
- return earliestFail
+ buf = _("No cards are due tomorrow.")
+ buf = '' + buf
+ return buf
- def earliestTimeStr(self, next=None):
- """Return the relative time to the earliest card as a string."""
- if next == None:
- next = self.earliestTime()
- if not next:
- return _("unknown")
- diff = next - time.time()
- return anki.utils.fmtTimeSpan(diff)
-
- def cardsDueBy(self, time):
- "Number of cards due at TIME. Ignore new cards"
+ def lrnTomorrow(self):
+ "Number of cards in the learning queue due tomorrow."
return self.db.scalar(
- self.cardLimit(
- "revActive", "revInactive",
- "select count(*) from cards c where queue between 0 and 1 "
- "and due < :lim"), lim=time)
+ "select count() from cards where queue = 0 and due < ?",
+ self.dayCutoff+86400)
- def newCardsDueBy(self, time):
- "Number of new cards due at TIME."
+ def revTomorrow(self):
+ "Number of reviews due tomorrow."
return self.db.scalar(
- self.cardLimit(
- "newActive", "newInactive",
- "select count(*) from cards c where queue = 2 "
- "and due < :lim"), lim=time)
+ "select count() from cards where queue = 1 and due = ?"+
+ self._groupLimit("rev"),
+ self.today+1)
- def deckFinishedMsg(self):
- spaceSusp = ""
- c= self.spacedCardCount()
- if c:
- spaceSusp += ngettext(
- 'There is %d delayed card.',
- 'There are %d delayed cards.', c) % c
- c2 = self.hiddenCards()
- if c2:
- if spaceSusp:
- spaceSusp += "
"
- spaceSusp += _(
- "Some cards are inactive or suspended.")
- if spaceSusp:
- spaceSusp = "
" + spaceSusp
- return _('''\
-
-
Congratulations!
You have finished for now.
-%(next)s
-%(spaceSusp)s
-''') % {
- "next": self.nextDueMsg(),
- "spaceSusp": spaceSusp,
- }
+ def newTomorrow(self):
+ "Number of new cards tomorrow."
+ lim = self.deck.qconf['newPerDay']
+ return self.db.scalar(
+ "select count() from (select id from cards where "
+ "queue = 2 limit %d)" % lim)
# Suspending
##########################################################################
def suspendCards(self, ids):
"Suspend cards."
- self.db.execute("""
-update cards
-set queue = -1, mod = :t
-where id in %s""" % ids2str(ids), t=time.time())
+ self.db.execute(
+ "update cards set queue = -1, mod = ? where id in "+
+ ids2str(ids), intTime())
def unsuspendCards(self, ids):
"Unsuspend cards."
- self.db.execute("""
-update cards set queue = type, mod=:t
-where queue = -1 and id in %s""" %
- ids2str(ids), t=time.time())
+ self.db.execute(
+ "update cards set queue = type, mod = ? "
+ "where queue = -1 and id in "+ ids2str(ids),
+ intTime())
- def buryFact(self, fact):
+ def buryFact(self, fid):
"Bury all cards for fact until next session."
- for card in fact.cards:
- if card.queue in (0,1,2):
- card.queue = -2
+ self.db.execute("update cards set queue = -2 where fid = ?", fid)
# Counts
##########################################################################
- def hiddenCards(self):
- "Assumes queue finished. True if some due cards have not been shown."
- return self.db.scalar("""
-select 1 from cards where due < :now
-and queue between 0 and 1 limit 1""", now=self.dayCutoff)
-
- def spacedCardCount(self):
- "Number of spaced cards."
- print "spacedCardCount"
- return 0
- return self.db.scalar("""
-select count(cards.id) from cards where
-due > :now and due < :now""", now=time.time())
-
- def isEmpty(self):
- return not self.cardCount
-
- def matureCardCount(self):
- return self.db.scalar(
- "select count(id) from cards where interval >= :t ",
- t=MATURE_THRESHOLD)
-
- def youngCardCount(self):
- return self.db.scalar(
- "select count(id) from cards where interval < :t "
- "and reps != 0", t=MATURE_THRESHOLD)
-
- def newCountAll(self):
- "All new cards, including spaced."
- return self.db.scalar(
- "select count(id) from cards where type = 2")
-
- def seenCardCount(self):
- return self.db.scalar(
- "select count(id) from cards where type between 0 and 1")
-
- # Card predicates
- ##########################################################################
-
- def cardState(self, card):
- if self.cardIsNew(card):
- return "new"
- elif card.interval > MATURE_THRESHOLD:
- return "mature"
- return "young"
-
- def cardIsNew(self, card):
- "True if a card has never been seen before."
- return card.reps == 0
-
- def cardIsYoung(self, card):
- "True if card is not new and not mature."
- return (not self.cardIsNew(card) and
- not self.cardIsMature(card))
-
- def cardIsMature(self, card):
- return card.interval >= MATURE_THRESHOLD
-
- # Stats
- ##########################################################################
-
- def getETA(self, stats):
- # rev + new cards first, account for failures
- import traceback; traceback.print_stack()
- count = stats['rev'] + stats['new']
- count *= 1 + stats['gYoungNo%'] / 100.0
- left = count * stats['dAverageTime']
- # failed - higher time per card for higher amount of cards
- failedBaseMulti = 1.5
- failedMod = 0.07
- failedBaseCount = 20
- factor = (failedBaseMulti +
- (failedMod * (stats['failed'] - failedBaseCount)))
- left += stats['failed'] * stats['dAverageTime'] * factor
- return left
+ def timeToday(self):
+ "Time spent learning today, in seconds."
+ return self.deck.db.scalar(
+ "select sum(taken/1000.0) from revlog where time > ?*1000",
+ self.dayCutoff-86400) or 0
+ def repsToday(self):
+ "Number of cards answered today."
+ return self.deck.db.scalar(
+ "select count() from revlog where time > ?*1000",
+ self.dayCutoff-86400)
# Dynamic indices
##########################################################################
diff --git a/anki/stats.py b/anki/stats.py
index 887fb7428..26dd18c0c 100644
--- a/anki/stats.py
+++ b/anki/stats.py
@@ -64,6 +64,21 @@ class DeckStats(object):
def __init__(self, deck):
self.deck = deck
+ def matureCardCount(self):
+ return self.deck.db.scalar(
+ "select count(id) from cards where interval >= :t ",
+ t=MATURE_THRESHOLD)
+
+ def youngCardCount(self):
+ return self.deck.db.scalar(
+ "select count(id) from cards where interval < :t "
+ "and reps != 0", t=MATURE_THRESHOLD)
+
+ def newCountAll(self):
+ "All new cards, including spaced."
+ return self.deck.db.scalar(
+ "select count(id) from cards where type = 2")
+
def report(self):
"Return an HTML string with a report."
fmtPerc = anki.utils.fmtPercentage
diff --git a/tests/test_sched.py b/tests/test_sched.py
index c2cd4cb4f..f6c818683 100644
--- a/tests/test_sched.py
+++ b/tests/test_sched.py
@@ -215,3 +215,19 @@ def test_reviews():
assert c.queue == -1
c.load()
assert c.queue == -1
+
+def test_finished():
+ d = getEmptyDeck()
+ # nothing due
+ assert "No cards are due" in d.sched.finishedMsg()
+ f = d.newFact()
+ f['Front'] = u"one"; f['Back'] = u"two"
+ d.addFact(f)
+ # have a new card
+ assert "1 new" in d.sched.finishedMsg()
+ # turn it into a review
+ c = f.cards()[0]
+ c.startTimer()
+ d.sched.answerCard(c, 3)
+ # nothing should be due tomorrow, as it's due in a week
+ assert "No cards are due" in d.sched.finishedMsg()