diff --git a/anki/collection.py b/anki/collection.py
index e2e00b5fa..3daf6be9e 100644
--- a/anki/collection.py
+++ b/anki/collection.py
@@ -57,8 +57,6 @@ class _Collection(object):
d += datetime.timedelta(hours=4)
self.crt = int(time.mktime(d.timetuple()))
self.sched = Scheduler(self)
- # check for improper shutdown
- self.cleanup()
def name(self):
n = os.path.splitext(os.path.basename(self.path))[0]
@@ -71,7 +69,7 @@ class _Collection(object):
(self.crt,
self.mod,
self.scm,
- self.dty,
+ self.dty, # no longer used
self._usn,
self.ls,
self.conf,
@@ -131,7 +129,6 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
def close(self, save=True):
"Disconnect from DB."
if self.db:
- self.cleanup()
if save:
self.save()
else:
@@ -166,16 +163,6 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
"True if schema changed since last sync."
return self.scm > self.ls
- def setDirty(self):
- "Signal there are temp. suspended cards that need cleaning up on close."
- self.dty = True
-
- def cleanup(self):
- "Unsuspend any temporarily suspended cards."
- if self.dty:
- self.sched.unburyCards()
- self.dty = False
-
def usn(self):
return self._usn if self.server else -1
@@ -610,6 +597,10 @@ where c.nid == f.id
"select id from revlog where cid = ? "
"order by id desc limit 1", c.id)
self.db.execute("delete from revlog where id = ?", last)
+ # restore any siblings
+ self.db.execute(
+ "update cards set queue=type,mod=?,usn=? where queue=-2 and nid=?",
+ intTime(), self.usn(), c.nid)
# and finally, update daily counts
n = 1 if c.queue == 3 else c.queue
type = ("new", "lrn", "rev")[n]
diff --git a/anki/decks.py b/anki/decks.py
index 0e54424a1..41fe720ed 100644
--- a/anki/decks.py
+++ b/anki/decks.py
@@ -53,6 +53,8 @@ defaultConf = {
'separate': True,
'order': NEW_CARDS_DUE,
'perDay': 20,
+ # may not be set on old decks
+ 'bury': True,
},
'lapse': {
'delays': [10],
@@ -66,7 +68,7 @@ defaultConf = {
'perDay': 100,
'ease4': 1.3,
'fuzz': 0.05,
- 'minSpace': 1,
+ 'minSpace': 1, # not currently used
'ivlFct': 1,
'maxIvl': 36500,
},
diff --git a/anki/find.py b/anki/find.py
index d944ec898..fab15c675 100644
--- a/anki/find.py
+++ b/anki/find.py
@@ -257,7 +257,7 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
return "queue in (1, 3)"
return "type = %d" % n
elif val == "suspended":
- return "c.queue = -1"
+ return "c.queue in (-1, -2)"
elif val == "due":
return """
(c.queue in (2,3) and c.due <= %d) or
diff --git a/anki/sched.py b/anki/sched.py
index 813dab073..96aab629d 100644
--- a/anki/sched.py
+++ b/anki/sched.py
@@ -51,6 +51,7 @@ class Scheduler(object):
def answerCard(self, card, ease):
assert ease >= 1 and ease <= 4
self.col.markReview(card)
+ self._burySiblings(card)
card.reps += 1
# former is for logging new cards, latter also covers filt. decks
card.wasNew = card.type == 0
@@ -132,11 +133,11 @@ order by due""" % self._deckLimit(),
return 3
def unburyCards(self):
- "Unbury cards when closing."
- mod = self.col.db.mod
+ "Unbury cards."
+ self.col.conf['lastUnburied'] = self.today
+ self.col.setMod()
self.col.db.execute(
"update cards set queue = type where queue = -2")
- self.col.db.mod = mod
# Rev/lrn/time daily stats
##########################################################################
@@ -298,7 +299,9 @@ order by due""" % self._deckLimit(),
return c
# new first, or time for one?
if self._timeForNewCard():
- return self._getNewCard()
+ c = self._getNewCard()
+ if c:
+ return c
# card due for review?
c = self._getRevCard()
if c:
@@ -339,30 +342,20 @@ did = ? and queue = 0 limit ?)""", did, lim)
lim = min(self.queueLimit, self._deckNewLimit(did))
if lim:
# fill the queue with the current did
- self._newQueue = self.col.db.all("""
-select id, due from cards where did = ? and queue = 0 limit ?""", did, lim)
+ self._newQueue = self.col.db.list("""
+select id from cards where did = ? and queue = 0 limit ?""", did, lim)
if self._newQueue:
self._newQueue.reverse()
return True
# nothing left in the deck; move to next
self._newDids.pop(0)
+ # if count>0 but queue empty, the other cards were buried
+ self.newCount = 0
def _getNewCard(self):
- if not self._fillNew():
- return
- (id, due) = self._newQueue.pop()
- # move any siblings to the end?
- conf = self.col.decks.confForDid(self._newDids[0])
- if conf['dyn'] or conf['new']['separate']:
- n = len(self._newQueue)
- while self._newQueue and self._newQueue[-1][1] == due:
- self._newQueue.insert(0, self._newQueue.pop())
- n -= 1
- if not n:
- # we only have one note in the queue; stop rotating
- break
- self.newCount -= 1
- return self.col.getCard(id)
+ if self._fillNew():
+ self.newCount -= 1
+ return self.col.getCard(self._newQueue.pop())
def _updateNewCardRatio(self):
if self.col.conf['newSpread'] == NEW_CARDS_DISTRIBUTE:
@@ -761,6 +754,8 @@ did = ? and queue = 2 and due <= ? limit ?""",
return True
# nothing left in the deck; move to next
self._revDids.pop(0)
+ # if count>0 but queue empty, the other cards were buried
+ self.revCount = 0
def _getRevCard(self):
if self._fillRev():
@@ -904,37 +899,13 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
return max(0, self.today - due)
def _updateRevIvl(self, card, ease):
- "Update CARD's interval, trying to avoid siblings."
idealIvl = self._nextRevIvl(card, ease)
card.ivl = self._adjRevIvl(card, idealIvl)
def _adjRevIvl(self, card, idealIvl):
- "Given IDEALIVL, return an IVL away from siblings."
if self._spreadRev:
idealIvl = self._fuzzedIvl(idealIvl)
- idealDue = self.today + idealIvl
- conf = self._revConf(card)
- # find sibling positions
- dues = self.col.db.list(
- "select due from cards where nid = ? and type = 2"
- " and id != ?", card.nid, card.id)
- if not dues or idealDue not in dues:
- return idealIvl
- else:
- leeway = max(conf['minSpace'], int(idealIvl * conf['fuzz']))
- fudge = 0
- # do we have any room to adjust the interval?
- if leeway:
- # loop through possible due dates for an empty one
- for diff in range(1, leeway+1):
- # ensure we're due at least tomorrow
- if idealIvl - diff >= 1 and (idealDue - diff) not in dues:
- fudge = -diff
- break
- elif (idealDue + diff) not in dues:
- fudge = diff
- break
- return idealIvl + fudge
+ return idealIvl
# Dynamic deck handling
##########################################################################
@@ -1136,6 +1107,10 @@ did = ?, queue = %s, due = ?, mod = ?, usn = ? where id = ?""" % queue, data)
g[key] = [self.today, 0]
for deck in self.col.decks.all():
update(deck)
+ # unbury if the day has rolled over
+ unburied = self.col.conf.get("lastUnburied", 0)
+ if unburied < self.today:
+ self.unburyCards()
def _checkDay(self):
# check if the day has rolled over
@@ -1152,17 +1127,22 @@ did = ?, queue = %s, due = ?, mod = ?, usn = ? where id = ?""" % queue, data)
def _nextDueMsg(self):
line = []
+ # the new line replacements are so we don't break translations
+ # in a point release
if self.revDue():
line.append(_("""\
Today's review limit has been reached, but there are still cards
waiting to be reviewed. For optimum memory, consider increasing
-the daily limit in the options."""))
+the daily limit in the options.""").replace("\n", " "))
if self.newDue():
line.append(_("""\
There are more new cards available, but the daily limit has been
reached. You can increase the limit in the options, but please
bear in mind that the more new cards you introduce, the higher
-your short-term review workload will become."""))
+your short-term review workload will become.""").replace("\n", " "))
+ if self.haveBuried():
+ line.append(_("""\
+Some related or buried cards were delayed until tomorrow.""").replace("\n", " "))
if self.haveCustomStudy and not self.col.decks.current()['dyn']:
line.append(_("""\
To study outside of the normal schedule, click the Custom Study button below."""))
@@ -1181,6 +1161,12 @@ To study outside of the normal schedule, click the Custom Study button below."""
("select 1 from cards where did in %s and queue = 0 "
"limit 1") % self._deckLimit())
+ def haveBuried(self):
+ sdids = ids2str(self.col.decks.active())
+ cnt = self.col.db.scalar(
+ "select 1 from cards where queue = -2 and did in %s limit 1" % sdids)
+ return not not cnt
+
# Next time reports
##########################################################################
@@ -1251,11 +1237,44 @@ To study outside of the normal schedule, click the Custom Study button below."""
def buryNote(self, nid):
"Bury all cards for note until next session."
- self.col.setDirty()
cids = self.col.db.list(
"select id from cards where nid = ? and queue >= 0", nid)
self.removeLrn(cids)
- self.col.db.execute("update cards set queue = -2 where id in "+ids2str(cids))
+ self.col.db.execute("""
+update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
+ intTime(), self.col.usn())
+
+ # Sibling spacing
+ ##########################################################################
+
+ def _burySiblings(self, card):
+ toBury = []
+ conf = self._newConf(card)
+ buryNew = conf.get("bury", True)
+ # loop through and remove from queues
+ for cid,queue in self.col.db.execute("""
+select id, queue from cards where nid=? and id!=?
+and (queue=0 or (queue=2 and due<=?))""",
+ card.nid, card.id, self.today):
+ if queue == 2:
+ toBury.append(cid)
+ try:
+ self._revQueue.remove(cid)
+ except ValueError:
+ pass
+ else:
+ # if bury disabled, we still discard to give same-day spacing
+ if buryNew:
+ toBury.append(cid)
+ try:
+ self._newQueue.remove(cid)
+ except ValueError:
+ pass
+ queue = "(queue = 0 or %s)" % queue
+ # then bury
+ self.col.db.execute(
+ "update cards set queue=-2,mod=?,usn=? where id in "+ids2str(toBury),
+ intTime(), self.col.usn())
# Resetting
##########################################################################
diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py
index b4ff09d47..04357d969 100644
--- a/aqt/deckbrowser.py
+++ b/aqt/deckbrowser.py
@@ -137,7 +137,6 @@ body { margin: 1em; -webkit-user-select: none; }
def _renderPage(self, reuse=False):
css = self.mw.sharedCSS + self._css
if not reuse:
- self.mw.col.sched.unburyCards()
self._dueTree = self.mw.col.sched.deckDueTree()
tree = self._renderDeckTree(self._dueTree)
stats = self._renderStats()
diff --git a/aqt/deckconf.py b/aqt/deckconf.py
index c2c7ce4e4..5e4e442f6 100644
--- a/aqt/deckconf.py
+++ b/aqt/deckconf.py
@@ -180,13 +180,11 @@ class DeckConf(QDialog):
f.lrnFactor.setValue(c['initialFactor']/10.0)
f.newOrder.setCurrentIndex(c['order'])
f.newPerDay.setValue(c['perDay'])
- f.separate.setChecked(c['separate'])
+ f.bury.setChecked(c.get("bury", True))
f.newplim.setText(self.parentLimText('new'))
# rev
c = self.conf['rev']
f.revPerDay.setValue(c['perDay'])
- f.revSpace.setValue(c['fuzz']*100)
- f.revMinSpace.setValue(c['minSpace'])
f.easyBonus.setValue(c['ease4']*100)
f.fi1.setValue(c['ivlFct']*100)
f.maxIvl.setValue(c['maxIvl'])
@@ -259,7 +257,7 @@ class DeckConf(QDialog):
c['initialFactor'] = f.lrnFactor.value()*10
c['order'] = f.newOrder.currentIndex()
c['perDay'] = f.newPerDay.value()
- c['separate'] = f.separate.isChecked()
+ c['bury'] = f.bury.isChecked()
if self._origNewOrder != c['order']:
# order of current deck has changed, so have to resort
if c['order'] == NEW_CARDS_RANDOM:
@@ -269,8 +267,6 @@ class DeckConf(QDialog):
# rev
c = self.conf['rev']
c['perDay'] = f.revPerDay.value()
- c['fuzz'] = f.revSpace.value()/100.0
- c['minSpace'] = f.revMinSpace.value()
c['ease4'] = f.easyBonus.value()/100.0
c['ivlFct'] = f.fi1.value()/100.0
c['maxIvl'] = f.maxIvl.value()
diff --git a/aqt/overview.py b/aqt/overview.py
index fa2b578e9..1112901c4 100644
--- a/aqt/overview.py
+++ b/aqt/overview.py
@@ -56,6 +56,9 @@ class Overview(object):
openLink(aqt.appShared+"info/%s?v=%s"%(self.sid, self.sidVer))
elif url == "studymore":
self.onStudyMore()
+ elif url == "unbury":
+ self.mw.col.sched.unburyCards()
+ self.mw.reset()
elif url.lower().startswith("http"):
openLink(url)
@@ -72,6 +75,9 @@ class Overview(object):
self.mw.reset()
if key == "c" and not cram:
self.onStudyMore()
+ if key == "u":
+ self.mw.col.sched.unburyCards()
+ self.mw.reset()
# HTML
############################################################
@@ -185,6 +191,8 @@ text-align: center;
else:
links.append(["C", "studymore", _("Custom Study")])
#links.append(["F", "cram", _("Filter/Cram")])
+ if self.mw.col.sched.haveBuried():
+ links.append(["U", "unbury", _("Unbury")])
buf = ""
for b in links:
if b[0]:
diff --git a/designer/dconf.ui b/designer/dconf.ui
index f43040f08..11d6b0160 100644
--- a/designer/dconf.ui
+++ b/designer/dconf.ui
@@ -6,8 +6,8 @@
0
0
- 481
- 419
+ 494
+ 454
@@ -83,6 +83,51 @@
-
+
-
+
+
+ %
+
+
+
+ -
+
+
+ Starting ease
+
+
+
+ -
+
+
+ 130
+
+
+ 999
+
+
+
+ -
+
+
+ Order
+
+
+
+ -
+
+
+ 1
+
+
+
+ -
+
+
+ 1
+
+
+
-
@@ -132,9 +177,9 @@
-
-
+
- Try not to show sibling cards next to each other
+ Bury related new cards until the next day
@@ -158,51 +203,6 @@
- -
-
-
- 1
-
-
-
- -
-
-
- 1
-
-
-
- -
-
-
- Order
-
-
-
- -
-
-
- 130
-
-
- 999
-
-
-
- -
-
-
- Starting ease
-
-
-
- -
-
-
- %
-
-
-
-
@@ -227,58 +227,14 @@
-
-
-
+
-
Easy bonus
- -
-
-
- days
-
-
-
- -
-
-
- Minimum sibling range
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- %
-
-
-
-
-
-
- 100
-
-
- 5
-
-
-
- -
-
-
- Space siblings by up to
-
-
-
- -
100
@@ -291,23 +247,20 @@
- -
+
-
%
- -
+
-
%
- -
-
-
-
@@ -325,7 +278,7 @@
- -
+
-
Interval modifier
@@ -339,7 +292,7 @@
- -
+
-
0
@@ -358,14 +311,14 @@
- -
+
-
Maximum interval
- -
+
-
1
@@ -375,7 +328,7 @@
- -
+
-
days
@@ -658,10 +611,8 @@
lrnGradInt
lrnEasyInt
lrnFactor
- separate
+ bury
revPerDay
- revSpace
- revMinSpace
easyBonus
fi1
maxIvl
diff --git a/tests/test_sched.py b/tests/test_sched.py
index 70d38663b..9876d5807 100644
--- a/tests/test_sched.py
+++ b/tests/test_sched.py
@@ -769,76 +769,6 @@ def test_cram_resched():
# d.sched.answerCard(c, 2)
# print c.__dict__
-def test_adjIvl():
- d = getEmptyDeck()
- d.sched._spreadRev = False
- # add two more templates and set second active
- m = d.models.current(); mm = d.models
- t = mm.newTemplate("Reverse")
- t['qfmt'] = "{{Back}}"
- t['afmt'] = "{{Front}}"
- mm.addTemplate(m, t)
- mm.save(m)
- t = d.models.newTemplate(m)
- t['name'] = "f2"
- t['qfmt'] = "{{Front}}"
- t['afmt'] = "{{Back}}"
- d.models.addTemplate(m, t)
- t = d.models.newTemplate(m)
- t['name'] = "f3"
- t['qfmt'] = "{{Front}}"
- t['afmt'] = "{{Back}}"
- d.models.addTemplate(m, t)
- d.models.save(m)
- # create a new note; it should have 4 cards
- f = d.newNote()
- f['Front'] = "1"; f['Back'] = "1"
- d.addNote(f)
- assert d.cardCount() == 4
- d.reset()
- # immediately remove first; it should get ideal ivl
- c = d.sched.getCard()
- d.sched.answerCard(c, 3)
- assert c.ivl == 4
- # with the default settings, second card should be -1
- c = d.sched.getCard()
- d.sched.answerCard(c, 3)
- assert c.ivl == 3
- # and third +1
- c = d.sched.getCard()
- d.sched.answerCard(c, 3)
- assert c.ivl == 5
- # fourth exceeds default settings, so gets ideal again
- c = d.sched.getCard()
- d.sched.answerCard(c, 3)
- assert c.ivl == 4
- # try again with another note
- f = d.newNote()
- f['Front'] = "2"; f['Back'] = "2"
- d.addNote(f)
- d.reset()
- # set a minSpacing of 0
- conf = d.sched._cardConf(c)
- conf['rev']['minSpace'] = 0
- # first card gets ideal
- c = d.sched.getCard()
- d.sched.answerCard(c, 3)
- assert c.ivl == 4
- # and second too, because it's below the threshold
- c = d.sched.getCard()
- d.sched.answerCard(c, 3)
- assert c.ivl == 4
- # if we increase the ivl minSpace isn't needed
- conf['new']['ints'][1] = 20
- # ideal..
- c = d.sched.getCard()
- d.sched.answerCard(c, 3)
- assert c.ivl == 20
- # adjusted
- c = d.sched.getCard()
- d.sched.answerCard(c, 3)
- assert c.ivl == 19
-
def test_ordcycle():
d = getEmptyDeck()
# add two more templates and set second active
diff --git a/tests/test_undo.py b/tests/test_undo.py
index b2d1d25a0..322f4314a 100644
--- a/tests/test_undo.py
+++ b/tests/test_undo.py
@@ -61,6 +61,7 @@ def test_review():
assert c.left != 1001
assert not d.undoName()
# we should be able to undo multiple answers too
+ f = d.newNote()
f['Front'] = u"two"
d.addNote(f)
d.reset()