From 32fde2a072f94d2d148df95b3c60d44ad44c287f Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 20 May 2012 13:55:49 +0900 Subject: [PATCH] encode daily steps in left When a user has learning steps that extend past the daily cutoff, we end up counting them all instead of only the ones that would be done today. In order to avoid this without expensive calculations or db schema changes, we calculate the number of steps until the daily cutoff and pack it into the left column, as totalLeft + leftToday*1000. --- anki/consts.py | 4 ++-- anki/sched.py | 39 +++++++++++++++++++++++++++++---------- anki/storage.py | 4 ++++ anki/upgrade.py | 2 +- tests/test_sched.py | 11 +++++++---- tests/test_undo.py | 4 ++-- 6 files changed, 45 insertions(+), 19 deletions(-) diff --git a/anki/consts.py b/anki/consts.py index b3595dc9b..107e6bf6e 100644 --- a/anki/consts.py +++ b/anki/consts.py @@ -42,10 +42,10 @@ MODEL_STD = 0 MODEL_CLOZE = 1 # deck schema & syncing vars -SCHEMA_VERSION = 9 +SCHEMA_VERSION = 10 SYNC_ZIP_SIZE = int(2.5*1024*1024) SYNC_URL = os.environ.get("SYNC_URL") or "https://beta.ankiweb.net/sync/" -SYNC_VER = 3 +SYNC_VER = 4 # Labels ########################################################################## diff --git a/anki/sched.py b/anki/sched.py index 997d9fd2e..0803bf112 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -88,7 +88,7 @@ class Scheduler(object): if card: idx = self.countIdx(card) if idx == 1: - counts[1] += card.left + counts[1] += card.left/1000 else: counts[idx] += 1 return tuple(counts) @@ -388,7 +388,7 @@ select count() from def _resetLrnCount(self): self.lrnCount = self.col.db.scalar(""" -select sum(left) 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)""" % ( self._deckLimit(), self.reportLimit), self.dayCutoff) or 0 @@ -418,7 +418,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff) if self._lrnQueue[0][0] < cutoff: id = heappop(self._lrnQueue)[1] card = self.col.getCard(id) - self.lrnCount -= card.left + self.lrnCount -= card.left/1000 return card def _answerLrnCard(self, card, ease): @@ -438,13 +438,15 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff) self._rescheduleAsRev(card, conf, True) leaving = True # graduation time? - elif ease == 2 and card.left-1 <= 0: + elif ease == 2 and (card.left%1000)-1 <= 0: self._rescheduleAsRev(card, conf, False) leaving = True else: # one step towards graduation if ease == 2: - card.left -= 1 + # decrement real left count and recalculate left today + left = (card.left % 1000) - 1 + card.left = self._leftToday(conf['delays'], left)*1000 + left # failed else: card.left = self._startingLeft(card) @@ -462,7 +464,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff) delay *= random.uniform(1, 1.25) card.due = int(time.time() + delay) if card.due < self.dayCutoff: - self.lrnCount += card.left + self.lrnCount += card.left/1000 # 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 # it twice in a row @@ -473,6 +475,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff) self._logLrn(card, ease, conf, leaving, type, lastLeft) def _delayForGrade(self, conf, left): + left = left % 1000 try: delay = conf['delays'][-left] except IndexError: @@ -501,7 +504,22 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff) def _startingLeft(self, card): conf = self._lrnConf(card) - return len(conf['delays']) + tot = len(conf['delays']) + tod = self._leftToday(conf['delays'], tot) + return tot + tod*1000 + + def _leftToday(self, delays, left, now=None): + "The number of steps that can be completed by the day cutoff." + if not now: + now = intTime() + delays = delays[-left:] + ok = 0 + for i in range(len(delays)): + now += delays[i]*60 + if now > self.dayCutoff: + break + ok = i + return ok+1 def _graduatingIvl(self, card, conf, early, adj=True): if card.type == 2: @@ -565,7 +583,7 @@ where queue = 1 and type = 2 def _lrnForDeck(self, did): return self.col.db.scalar( """ -select sum(left) from +select sum(left/1000) from (select left from cards where did = ? and queue = 1 and due < ? limit ?)""", did, intTime() + self.col.conf['collapseTime'], self.reportLimit) or 0 @@ -662,8 +680,9 @@ did = ? and queue = 2 and due <= ? limit ?""", card.odue = card.due card.due = int(self._delayForGrade(conf, 0) + time.time()) card.left = len(conf['delays']) + card.left += self._leftToday(conf['delays'], card.left)*1000 card.queue = 1 - self.lrnCount += card.left + self.lrnCount += card.left/1000 # leech? if not self._checkLeech(card, conf) and conf['delays']: heappush(self._lrnQueue, (card.due, card.id)) @@ -1024,7 +1043,7 @@ your short-term review workload will become.""")) # early removal return self._graduatingIvl(card, conf, True, adj=False) * 86400 else: - left = card.left - 1 + left = card.left%1000 - 1 if left <= 0: # graduate return self._graduatingIvl(card, conf, False, adj=False) * 86400 diff --git a/anki/storage.py b/anki/storage.py index 2abf12884..5696eb729 100644 --- a/anki/storage.py +++ b/anki/storage.py @@ -135,6 +135,10 @@ def _upgrade(col, ver): if changed: col.media.db.commit() col.db.execute("update col set ver = 9") + if ver < 10: + col.db.execute(""" +update cards set left = left + left*1000 where queue = 1""") + col.db.execute("update col set ver = 10") def _upgradeClozeModel(col, m): m['type'] = MODEL_CLOZE diff --git a/anki/upgrade.py b/anki/upgrade.py index 2143535cb..c6dbcbb6c 100644 --- a/anki/upgrade.py +++ b/anki/upgrade.py @@ -682,7 +682,7 @@ and ord = ? limit 1""", m['id'], t['ord']): for t in ("cards", "notes", "models", "media"): col.db.execute("drop table if exists %sDeleted" % t) # and failed cards - left = len(col.decks.confForDid(1)['new']['delays']) + left = len(col.decks.confForDid(1)['new']['delays'])*1001 col.db.execute("update cards set odue = ?, left=? where type = 1", col.sched.today+1, left) # and due cards diff --git a/tests/test_sched.py b/tests/test_sched.py index f0aa38ed1..a563ad275 100644 --- a/tests/test_sched.py +++ b/tests/test_sched.py @@ -112,7 +112,8 @@ def test_learn(): # fail it d.sched.answerCard(c, 1) # it should have three reps left to graduation - assert c.left == 3 + assert c.left%1000 == 3 + assert c.left/1000 == 3 # it should by due in 30 seconds t = round(c.due - time.time()) assert t >= 25 and t <= 40 @@ -120,7 +121,8 @@ def test_learn(): d.sched.answerCard(c, 2) # it should by due in 3 minutes assert round(c.due - time.time()) in (179, 180) - assert c.left == 2 + assert c.left%1000 == 2 + assert c.left/1000 == 2 # check log is accurate log = d.db.first("select * from revlog order by id desc") assert log[3] == 2 @@ -130,7 +132,8 @@ def test_learn(): d.sched.answerCard(c, 2) # it should by due in 10 minutes assert round(c.due - time.time()) in (599, 600) - assert c.left == 1 + assert c.left%1000 == 1 + assert c.left/1000 == 1 # the next pass should graduate the card assert c.queue == 1 assert c.type == 1 @@ -299,7 +302,7 @@ def test_overdue_lapse(): c.due = -1 c.odue = -1 c.factor = 2500 - c.left = 2 + c.left = 2002 c.ivl = 0 c.flush() d.sched._clearOverdue = False diff --git a/tests/test_undo.py b/tests/test_undo.py index ff0886645..abd91909b 100644 --- a/tests/test_undo.py +++ b/tests/test_undo.py @@ -48,7 +48,7 @@ def test_review(): c = d.sched.getCard() assert c.queue == 0 d.sched.answerCard(c, 2) - assert c.left == 1 + assert c.left == 1001 assert d.sched.counts() == (0, 1, 0) assert c.queue == 1 # undo @@ -58,7 +58,7 @@ def test_review(): assert d.sched.counts() == (1, 0, 0) c.load() assert c.queue == 0 - assert c.left != 1 + assert c.left != 1001 assert not d.undoName() # we should be able to undo multiple answers too f['Front'] = u"two"