diff --git a/anki/deck.py b/anki/deck.py
index 300fbede8..f439dcaae 100644
--- a/anki/deck.py
+++ b/anki/deck.py
@@ -50,7 +50,7 @@ decksTable = Table(
Column('created', Float, nullable=False, default=time.time),
Column('modified', Float, nullable=False, default=time.time),
Column('description', UnicodeText, nullable=False, default=u""),
- Column('version', Integer, nullable=False, default=20),
+ Column('version', Integer, nullable=False, default=21),
Column('currentModelId', Integer, ForeignKey("models.id")),
# syncing
Column('syncName', UnicodeText),
@@ -121,6 +121,9 @@ class Deck(object):
self.sessionStartReps = 0
self.sessionStartTime = 0
self.lastSessionStart = 0
+ self.reviewedAheadCards = []
+ self.extraNewCards = 0
+ self.reviewEarly = False
def modifiedSinceSave(self):
return self.modified > self.lastLoaded
@@ -156,11 +159,31 @@ class Deck(object):
id = self._maybeGetNewCard()
if id:
return id
- # display failed cards early
- if self.collapseTime or not self.delay0:
+ # review ahead?
+ if self.reviewEarly:
+ id = self.getCardIdAhead()
+ if id:
+ return id
+ else:
+ self.resetAfterReviewEarly()
+ self.checkDue()
+ # display failed cards early/last
+ if self._showFailedLast():
id = self.s.scalar(
"select id from failedCards limit 1")
- return id
+ if id:
+ return id
+
+ def getCardIdAhead(self):
+ "Return the first card that would become due."
+ t = time.time()
+ id = self.s.scalar("""
+select id from cards
+where priority in (1,2,3,4)
+order by priority desc, combinedDue
+limit 1""")
+ #print "ahead", time.time() -t, id
+ return id
# Get card: helper functions
##########################################################################
@@ -207,6 +230,9 @@ class Deck(object):
return self.s.scalar(
"select id from %s limit 1" % self.revCardTable())
+ def _showFailedLast(self):
+ return self.collapseTime or not self.delay0
+
def cardFromId(self, id, orm=False):
"Given a card ID, return a card, and start the card timer."
if orm:
@@ -293,10 +319,15 @@ where factId in (select factId from %s limit 60))""" % (new, new))
self.setUndoStart(undoName)
now = time.time()
oldState = self.cardState(card)
- lastDelay = max(0, (time.time() - card.due) / 86400.0)
+ lastDelaySecs = time.time() - card.combinedDue
+ lastDelay = lastDelaySecs / 86400.0
+ oldSuc = card.successive
# update card details
- card.lastInterval = card.interval
+ last = card.interval
card.interval = self.nextInterval(card, ease)
+ if lastDelay >= 0:
+ # keep last interval if reviewing early
+ card.lastInterval = last
card.lastDue = card.due
card.due = self.nextDue(card, ease, oldState)
card.isDue = 0
@@ -337,6 +368,11 @@ isDue = 0
where id != :id and factId = :factId""",
id=card.id, space=space, now=now, factId=card.factId)
card.spaceUntil = 0
+ # temp suspend if learning ahead
+ if lastDelay < 0:
+ if oldSuc or lastDelaySecs > self.delay0 or not self._showFailedLast():
+ card.priority = 0
+ self.reviewedAheadCards.append(card.id)
# card stats
anki.cards.Card.updateStats(card, ease, oldState)
card.toDB(self.s)
@@ -348,6 +384,9 @@ where id != :id and factId = :factId""",
entry.writeSQL(self.s)
self.modified = now
self.setUndoEnd(undoName)
+ # decrease card boost
+ if self.extraNewCards:
+ self.extraNewCards -= 1
# Interval management
##########################################################################
@@ -355,9 +394,16 @@ where id != :id and factId = :factId""",
def nextInterval(self, card, ease):
"Return the next interval for CARD given EASE."
delay = self._adjustedDelay(card, ease)
- return self._nextInterval(card.interval, card.factor, delay, ease)
+ return self._nextInterval(card, delay, ease)
- def _nextInterval(self, interval, factor, delay, ease):
+ def _nextInterval(self, card, delay, ease):
+ interval = card.interval
+ factor = card.factor
+ if delay < 0:
+ interval = card.lastInterval + ((interval - abs(delay)) / 2.0)
+ delay = 0
+ if interval < self.midIntervalMin:
+ interval = 0
# if interval is less than mid interval, use presets
if ease == 1:
interval *= self.delay2
@@ -430,7 +476,10 @@ where id != :id and factId = :factId""",
"Return an adjusted delay value for CARD based on EASE."
if self.cardIsNew(card):
return 0
- return max(0, (time.time() - card.due) / 86400.0)
+ if card.combinedDue <= time.time():
+ return (time.time() - card.due) / 86400.0
+ else:
+ return (time.time() - card.combinedDue) / 86400.0
def resetCards(self, ids):
"Reset progress on cards in IDS."
@@ -502,7 +551,7 @@ type = 0 and isDue = 1 and combinedDue <= :now""", now=time.time())
stmt % 2, now=time.time()).rowcount
self.newCountToday = max(min(
self.newCount, self.newCardsPerDay -
- self.newCardsToday()), 0)
+ self.newCardsToday()), 0) + self.extraNewCards
def rebuildQueue(self):
"Update relative delays based on current time."
@@ -538,6 +587,17 @@ type = 0 and isDue = 1 and combinedDue <= :now""", now=time.time())
if genToday(self) != self._dailyStats.day:
self._dailyStats = dailyStats(self)
+ def cardsDueSoon(self, ratio=0.1, minInt=0, maxInt=0):
+ "Return ids of cards near their expiration date."
+ #FIXME: implement
+ pass
+
+ def resetAfterReviewEarly(self):
+ self.updatePriorities(self.reviewedAheadCards)
+ self.reviewedAheadCards = []
+ self.reviewEarly = False
+ self.flushMod()
+
# Times
##########################################################################
@@ -547,15 +607,15 @@ type = 0 and isDue = 1 and combinedDue <= :now""", now=time.time())
newCardsTomorrow = min(self.newCount, self.newCardsPerDay)
msg = _('''\
At the same time tomorrow:
-- There will be %(wait)d cards waiting for review
-- There will be %(new)d
-
-new cards waiting''') % {
+There will be %(wait)d cards waiting for review.
+There will be %(new)d new cards waiting.''') % {
'new': newCardsTomorrow,
'wait': self.cardsDueBy(time.time() + 86400)
}
- if next - time.time() > 86400 and not newCardsTomorrow:
- msg = (_("The next card will be shown in %s") %
+ if self.spacedCardCount():
+ msg = _("Spaced cards will be shown soon.")
+ elif next - time.time() > 86400 and not newCardsTomorrow:
+ msg = (_("The next card will be shown in %s.") %
self.earliestTimeStr())
else:
msg = _("No cards are due.")
@@ -586,20 +646,31 @@ select count(id) from cards where combinedDue < :time
and priority in (1,2,3,4) and type in (0, 1)""", time=time)
def deckFinishedMsg(self):
+ spaceSusp = ""
+ c = self.spacedCardCount()
+ if c:
+ spaceSusp += '''
+There are %d
+
+spaced cards.''' % c
+ c2 = self.suspendedCardCount()
+ if c2:
+ if c:
+ spaceSusp += "
"
+ spaceSusp += '''
+There are %d
+
+suspended cards.''' % c2
+ if spaceSusp:
+ spaceSusp = "
" + spaceSusp
return _('''\