improve handling of invalid deck trees

- move checking code out of the schedulers and into the deck manager
- ensure we can fix the problem in one loop - the previous recursive
approach could lead to stack overflows if the top level of a large
deck tree was missing. this was also the cause of the sqlite
'interrupted' error that some users were seeing
This commit is contained in:
Damien Elmes 2018-11-09 15:04:35 +10:00
parent 5e5d35e9c1
commit 9c85376b3e
3 changed files with 35 additions and 37 deletions

View file

@ -432,13 +432,45 @@ class DeckManager:
return self.col.db.list("select id from cards where did in "+
ids2str(dids))
def recoverOrphans(self):
def _recoverOrphans(self):
dids = list(self.decks.keys())
mod = self.col.db.mod
self.col.db.execute("update cards set did = 1 where did not in "+
ids2str(dids))
self.col.db.mod = mod
def _checkDeckTree(self):
decks = self.col.decks.all()
decks.sort(key=operator.itemgetter('name'))
names = set()
for deck in decks:
# two decks with the same name?
if deck['name'] in names:
print("fix duplicate deck name", deck['name'])
deck['name'] += "%d" % intTime(1000)
self.save(deck)
# ensure no sections are blank
if not all(deck['name'].split("::")):
print("fix deck with missing sections", deck['name'])
deck['name'] = "recovered%d" % intTime(1000)
self.save(deck)
# immediate parent must exist
if "::" in deck['name']:
immediateParent = "::".join(deck['name'].split("::")[:-1])
if immediateParent not in names:
print("fix deck with missing parent", deck['name'])
self._ensureParents(deck['name'])
names.add(immediateParent)
names.add(deck['name'])
def checkIntegrity(self):
self._recoverOrphans()
self._checkDeckTree()
# Deck selection
#############################################################

View file

@ -216,7 +216,7 @@ order by due""" % self._deckLimit(),
def deckDueList(self):
"Returns [deckname, did, rev, lrn, new]"
self._checkDay()
self.col.decks.recoverOrphans()
self.col.decks.checkIntegrity()
decks = self.col.decks.all()
decks.sort(key=itemgetter('name'))
lims = {}
@ -228,27 +228,10 @@ order by due""" % self._deckLimit(),
parts = parts[:-1]
return "::".join(parts)
for deck in decks:
# if we've already seen the exact same deck name, rename the
# invalid duplicate and reload
if deck['name'] in lims:
deck['name'] += "1"
self.col.decks.save(deck)
return self.deckDueList()
# ensure no sections are blank
if not all(deck['name'].split("::")):
deck['name'] = "recovered"
self.col.decks.save(deck)
return self.deckDueList()
p = parent(deck['name'])
# new
nlim = self._deckNewLimitSingle(deck)
if p:
if p not in lims:
# if parent was missing, this deck is invalid
deck['name'] = "recovered"
self.col.decks.save(deck)
return self.deckDueList()
nlim = min(nlim, lims[p][0])
new = self._newForDeck(deck['id'], nlim)
# learning

View file

@ -205,7 +205,7 @@ order by due""" % self._deckLimit(),
def deckDueList(self):
"Returns [deckname, did, rev, lrn, new]"
self._checkDay()
self.col.decks.recoverOrphans()
self.col.decks.checkIntegrity()
decks = self.col.decks.all()
decks.sort(key=itemgetter('name'))
lims = {}
@ -218,27 +218,10 @@ order by due""" % self._deckLimit(),
return "::".join(parts)
childMap = self.col.decks.childMap()
for deck in decks:
# if we've already seen the exact same deck name, rename the
# invalid duplicate and reload
if deck['name'] in lims:
deck['name'] += "1"
self.col.decks.save(deck)
return self.deckDueList()
# ensure no sections are blank
if not all(deck['name'].split("::")):
deck['name'] = "recovered"
self.col.decks.save(deck)
return self.deckDueList()
p = parent(deck['name'])
# new
nlim = self._deckNewLimitSingle(deck)
if p:
if p not in lims:
# if parent was missing, this deck is invalid
deck['name'] = "recovered"
self.col.decks.save(deck)
return self.deckDueList()
nlim = min(nlim, lims[p][0])
new = self._newForDeck(deck['id'], nlim)
# learning