diff --git a/anki/deck.py b/anki/deck.py index 6aa9babe2..1bc5d0c74 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -160,7 +160,7 @@ qconf=?, conf=?, data=?""", return anki.models.Template(self, self.deck.db.first( "select * from templates where id = ?", id)) - # unsorted + # Utils ########################################################################## def nextID(self, type): @@ -170,217 +170,14 @@ qconf=?, conf=?, data=?""", return id def reset(self): + "Rebuild the queue and reload data after DB modified." self.sched.reset() - # Times - ########################################################################## - - 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()) - else: - msg = _("No cards are due.") - return msg - - 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 - else: - return earliestFail - - 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" - return self.db.scalar( - self.cardLimit( - "revActive", "revInactive", - "select count(*) from cards c where queue between 0 and 1 " - "and due < :lim"), lim=time) - - def newCardsDueBy(self, time): - "Number of new cards due at TIME." - return self.db.scalar( - self.cardLimit( - "newActive", "newInactive", - "select count(*) from cards c where queue = 2 " - "and due < :lim"), lim=time) - - 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, - } - - # Suspending - ########################################################################## - - def suspendCards(self, ids): - "Suspend cards." - self.startProgress() - self.db.execute(""" -update cards -set queue = -1, mod = :t -where id in %s""" % ids2str(ids), t=time.time()) - self.finishProgress() - - def unsuspendCards(self, ids): - "Unsuspend cards." - self.startProgress() - self.db.execute(""" -update cards set queue = type, mod=:t -where queue = -1 and id in %s""" % - ids2str(ids), t=time.time()) - self.finishProgress() - - def buryFact(self, fact): - "Bury all cards for fact until next session." - for card in fact.cards: - if card.queue in (0,1,2): - card.queue = -2 - - # 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 - # Facts ########################################################################## def factCount(self): - return self.db.scalar("select count() from facts") + return self.db.scalar("select count() from facts where crt != 0") def newFact(self): "Return a new fact with the current model." @@ -517,9 +314,7 @@ select id from facts where id not in (select distinct fid from cards)""") ########################################################################## def cardCount(self): - all = self.db.scalar("select count() from cards") - trash = self.db.scalar("select count() from cards where queue = -4") - return all - trash + return self.db.scalar("select count() from cards where crt != 0") def deleteCard(self, id): "Delete a card given its id. Delete any unused facts." diff --git a/anki/sched.py b/anki/sched.py index debc72b1c..af3f482ac 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -587,3 +587,207 @@ order by due limit %d""" % self.queueLimit), lim=self.dayCutoff) def _updateLearnMoreCountToday(self): self.newCount = self.newAvail + + # Times + ########################################################################## + + 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()) + else: + msg = _("No cards are due.") + return msg + + 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 + else: + return earliestFail + + 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" + return self.db.scalar( + self.cardLimit( + "revActive", "revInactive", + "select count(*) from cards c where queue between 0 and 1 " + "and due < :lim"), lim=time) + + def newCardsDueBy(self, time): + "Number of new cards due at TIME." + return self.db.scalar( + self.cardLimit( + "newActive", "newInactive", + "select count(*) from cards c where queue = 2 " + "and due < :lim"), lim=time) + + 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, + } + + # Suspending + ########################################################################## + + def suspendCards(self, ids): + "Suspend cards." + self.startProgress() + self.db.execute(""" +update cards +set queue = -1, mod = :t +where id in %s""" % ids2str(ids), t=time.time()) + self.finishProgress() + + def unsuspendCards(self, ids): + "Unsuspend cards." + self.startProgress() + self.db.execute(""" +update cards set queue = type, mod=:t +where queue = -1 and id in %s""" % + ids2str(ids), t=time.time()) + self.finishProgress() + + def buryFact(self, fact): + "Bury all cards for fact until next session." + for card in fact.cards: + if card.queue in (0,1,2): + card.queue = -2 + + # 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