new bulk card handling, fix count bugs, next int generation

This commit is contained in:
Damien Elmes 2008-11-13 03:19:19 +09:00
parent ca413a1a88
commit 07956d9e24
2 changed files with 109 additions and 103 deletions

View file

@ -45,7 +45,7 @@ decksTable = Table(
Column('created', Float, nullable=False, default=time.time), Column('created', Float, nullable=False, default=time.time),
Column('modified', Float, nullable=False, default=time.time), Column('modified', Float, nullable=False, default=time.time),
Column('description', UnicodeText, nullable=False, default=u""), Column('description', UnicodeText, nullable=False, default=u""),
Column('version', Integer, nullable=False, default=13), Column('version', Integer, nullable=False, default=14),
Column('currentModelId', Integer, ForeignKey("models.id")), Column('currentModelId', Integer, ForeignKey("models.id")),
# syncing # syncing
Column('syncName', UnicodeText), Column('syncName', UnicodeText),
@ -141,37 +141,21 @@ class Deck(object):
# card due for review? # card due for review?
if self.revCount: if self.revCount:
return self.s.scalar("select id from revCards limit 1") return self.s.scalar("select id from revCards limit 1")
# new card last? # new cards left?
if self.newCardSpacing == NEW_CARDS_LAST: id = self._maybeGetNewCard()
id = self._maybeGetNewCard() if id:
if id: return id
return id # display failed cards early
if self.collapseTime: if self.collapseTime:
# display failed cards early
id = self.s.scalar( id = self.s.scalar(
"select id from failedCardsSoon limit 1") "select id from failedCardsSoon limit 1")
return id return id
def getCards(self):
sel = """
select id, factId, modified, question, answer, cardModelId,
reps, successive from """
if self.newCardOrder == 0:
new = "acqCardsRandom"
else:
new = "acqCardsOrdered"
return {'failed': self.s.all(sel + "failedCardsNow limit 30"),
'rev': self.s.all(sel + "revCards limit 30"),
'new': self.s.all(sel + new + " limit 30")}
# Get card: helper functions # Get card: helper functions
########################################################################## ##########################################################################
def _timeForNewCard(self): def _timeForNewCard(self):
"True if it's time to display a new card when distributing." "True if it's time to display a new card when distributing."
# no cards for review, so force new
if not self.revCount:
return True
# force old if there are very high priority cards # force old if there are very high priority cards
if self.s.scalar( if self.s.scalar(
"select 1 from cards where type = 1 and isDue = 1 " "select 1 from cards where type = 1 and isDue = 1 "
@ -213,6 +197,65 @@ reps, successive from """
card.startTimer() card.startTimer()
return card return card
# Getting cards in bulk
##########################################################################
def getCards(self, extraMunge=None):
"Get a number of cards and related data for client display."
d = self._getCardTables()
def munge(row):
row = list(row)
row[0] = str(row[0])
row[1] = str(row[1])
row[2] = int(row[2])
row[5] = hexifyID(row[5])
if extraMunge:
return extraMunge(row)
return row
for type in ('fail', 'rev', 'acq'):
d[type] = [munge(x) for x in d[type]]
if d['fail'] or d['rev'] or d['acq']:
d['stats'] = self.getStats()
d['status'] = 'cardsAvailable'
d['initialIntervals'] = (
self.hardIntervalMin,
self.hardIntervalMax,
self.midIntervalMin,
self.midIntervalMax,
self.easyIntervalMin,
self.easyIntervalMax,
)
d['newCardSpacing'] = self.newCardSpacing
d['newCardModulus'] = self.newCardModulus
return d
else:
if self.isEmpty():
fin = ""
else:
fin = self.deckFinishedMsg()
return {"status": "deckFinished",
"finishedMsg": fin}
def _getCardTables(self):
self.checkDue()
sel = """
select id, factId, modified, question, answer, cardModelId,
type, due, interval, factor, priority from """
if self.newCardOrder == 0:
new = "acqCardsRandom"
else:
new = "acqCardsOrdered"
d = {}
d['fail'] = self.s.all(sel + "failedCardsNow limit 100")
d['rev'] = self.s.all(sel + "revCards limit 30")
if self.newCountToday:
d['acq'] = self.s.all(sel + new + " limit 30")
else:
d['acq'] = []
if (not d['fail'] and not d['rev'] and not d['acq']):
d['fail'] = self.s.all(sel + "failedCardsSoon limit 100")
return d
# Answering a card # Answering a card
########################################################################## ##########################################################################
@ -382,10 +425,10 @@ strftime("%s", "now")+1 from decks)"""))
def _nextInterval(self, interval, factor, delay, ease): def _nextInterval(self, interval, factor, delay, ease):
# if interval is less than mid interval, use presets # if interval is less than mid interval, use presets
if interval < self.hardIntervalMin: if ease == 1:
if ease < 2: interval = NEW_INTERVAL
interval = NEW_INTERVAL elif interval == 0:
elif ease == 2: if ease == 2:
interval = random.uniform(self.hardIntervalMin, interval = random.uniform(self.hardIntervalMin,
self.hardIntervalMax) self.hardIntervalMax)
elif ease == 3: elif ease == 3:
@ -394,36 +437,35 @@ strftime("%s", "now")+1 from decks)"""))
elif ease == 4: elif ease == 4:
interval = random.uniform(self.easyIntervalMin, interval = random.uniform(self.easyIntervalMin,
self.easyIntervalMax) self.easyIntervalMax)
elif ease == 0:
interval = NEW_INTERVAL
else: else:
# otherwise, multiply the old interval by a factor # otherwise, multiply the old interval by a factor
if ease == 1: if ease == 2:
factor = 1 / factor / 2.0 interval = (interval + delay/4) * 1.2
interval = interval * factor
elif ease == 2:
factor = 1.2
interval = (interval + delay/4) * factor
elif ease == 3: elif ease == 3:
factor = factor
interval = (interval + delay/2) * factor interval = (interval + delay/2) * factor
elif ease == 4: elif ease == 4:
factor = factor * self.factorFour interval = (interval + delay) * factor * self.factorFour
interval = (interval + delay) * factor
fuzz = random.uniform(0.95, 1.05) fuzz = random.uniform(0.95, 1.05)
interval *= fuzz interval *= fuzz
if self.maxScheduleTime: if self.maxScheduleTime:
interval = min(interval, self.maxScheduleTime) interval = min(interval, self.maxScheduleTime)
return interval return interval
def nextIntervalStr(self, card, ease, short=False):
"Return the next interval for CARD given EASE as a string."
if card.interval == 0 and ease == 2:
if short:
return _("tom.")
else:
return _("tomorrow")
else:
int = self.nextInterval(card, ease)
return anki.utils.fmtTimeSpan(int*86400, short=short)
def nextDue(self, card, ease, oldState): def nextDue(self, card, ease, oldState):
"Return time when CARD will expire given EASE." "Return time when CARD will expire given EASE."
if ease == 0: if ease == 1:
due = self.delay0 due = self.delay0
elif ease == 1 and oldState != 'mature':
due = self.delay1
elif ease == 1:
due = self.delay2
else: else:
due = card.interval * 86400.0 due = card.interval * 86400.0
return due + time.time() return due + time.time()
@ -512,30 +554,6 @@ type in (0, 1) order by combinedDue limit 1""")
select count(id) from cards where combinedDue < :time select count(id) from cards where combinedDue < :time
and priority in (1,2,3,4) and type in (0, 1)""", time=time) and priority in (1,2,3,4) and type in (0, 1)""", time=time)
def nextIntervalStr(self, card, ease, short=False):
"Return the next interval for CARD given EASE as a string."
delay = self._adjustedDelay(card, ease)
if card.due > time.time() and ease < 2:
# the card is not yet due, and we are in the final drill
return _("a short time")
if ease < 2:
interval = self.nextDue(card, ease, self.cardState(card)) - time.time()
elif card.interval < self.hardIntervalMin:
if ease == 2:
interval = [self.hardIntervalMin, self.hardIntervalMax]
elif ease == 3:
interval = [self.midIntervalMin, self.midIntervalMax]
else:
interval = [self.easyIntervalMin, self.easyIntervalMax]
interval[0] = interval[0] * 86400.0
interval[1] = interval[1] * 86400.0
if interval[0] != interval[1]:
return anki.utils.fmtTimeSpanPair(interval[0], interval[1], short=short)
interval = interval[0]
else:
interval = self.nextInterval(card, ease) * 86400.0
return anki.utils.fmtTimeSpan(interval, short=short)
def deckFinishedMsg(self): def deckFinishedMsg(self):
return _(''' return _('''
<h1>Congratulations!</h1>You have finished the deck for now.<br><br> <h1>Congratulations!</h1>You have finished the deck for now.<br><br>
@ -692,12 +710,12 @@ and due < :now""", now=time.time())
# add scheduling related stats # add scheduling related stats
stats['new'] = self.newCountToday stats['new'] = self.newCountToday
stats['failed'] = self.failedSoonCount stats['failed'] = self.failedSoonCount
stats['successive'] = self.revCount stats['rev'] = self.revCount
if stats['dAverageTime']: if stats['dAverageTime']:
if self.newCardSpacing == NEW_CARDS_DISTRIBUTE: if self.newCardSpacing == NEW_CARDS_DISTRIBUTE:
count = stats['successive'] + stats['new'] count = stats['rev'] + stats['new']
elif self.newCardSpacing == NEW_CARDS_LAST: elif self.newCardSpacing == NEW_CARDS_LAST:
count = stats['successive'] or stats['new'] count = stats['rev'] or stats['new']
count += stats['failed'] count += stats['failed']
stats['timeLeft'] = anki.utils.fmtTimeSpan( stats['timeLeft'] = anki.utils.fmtTimeSpan(
stats['dAverageTime'] * count, pad=0, point=1) stats['dAverageTime'] * count, pad=0, point=1)
@ -753,7 +771,7 @@ and due < :now""", now=time.time())
self.newCount += len(cards) self.newCount += len(cards)
# keep track of last used tags for convenience # keep track of last used tags for convenience
self.lastTags = fact.tags self.lastTags = fact.tags
self.setModified() self.flushMod()
return cards return cards
def availableCardModels(self, fact): def availableCardModels(self, fact):
@ -812,14 +830,10 @@ where factId = :fid and cardModelId = :cmid""",
self.s.statement("insert into cardsDeleted select id, :time " self.s.statement("insert into cardsDeleted select id, :time "
"from cards where factId = :factId", "from cards where factId = :factId",
time=time.time(), factId=factId) time=time.time(), factId=factId)
self.cardCount -= self.s.statement( self.s.statement(
"delete from cards where factId = :id", id=factId).rowcount "delete from cards where factId = :id", id=factId)
# and then the fact # and then the fact
self.factCount -= self.s.statement( self.deleteFacts([factId])
"delete from facts where id = :id", id=factId).rowcount
self.s.statement("delete from fields where factId = :id", id=factId)
self.s.statement("insert into factsDeleted values (:id, :time)",
id=factId, time=time.time())
self.setModified() self.setModified()
def deleteFacts(self, ids): def deleteFacts(self, ids):
@ -829,11 +843,11 @@ where factId = :fid and cardModelId = :cmid""",
self.s.flush() self.s.flush()
now = time.time() now = time.time()
strids = ids2str(ids) strids = ids2str(ids)
self.factCount -= self.s.statement( self.s.statement("delete from facts where id in %s" % strids)
"delete from facts where id in %s" % strids).rowcount
self.s.statement("delete from fields where factId in %s" % strids) self.s.statement("delete from fields where factId in %s" % strids)
data = [{'id': id, 'time': now} for id in ids] data = [{'id': id, 'time': now} for id in ids]
self.s.statements("insert into factsDeleted values (:id, :time)", data) self.s.statements("insert into factsDeleted values (:id, :time)", data)
self.rebuildCounts()
self.setModified() self.setModified()
def deleteDanglingFacts(self): def deleteDanglingFacts(self):
@ -849,15 +863,7 @@ where facts.id not in (select factId from cards)""")
def deleteCard(self, id): def deleteCard(self, id):
"Delete a card given its id. Delete any unused facts. Don't flush." "Delete a card given its id. Delete any unused facts. Don't flush."
self.s.flush() self.deleteCards([id])
factId = self.s.scalar("select factId from cards where id=:id", id=id)
self.cardCount -= self.s.statement(
"delete from cards where id = :id", id=id).rowcount
self.s.statement("insert into cardsDeleted values (:id, :time)",
id=id, time=time.time())
if factId and not self.factUseCount(factId):
self.deleteFact(factId)
self.setModified()
def deleteCards(self, ids): def deleteCards(self, ids):
"Bulk delete cards by ID." "Bulk delete cards by ID."
@ -870,13 +876,13 @@ where facts.id not in (select factId from cards)""")
factIds = self.s.column0("select factId from cards where id in %s" factIds = self.s.column0("select factId from cards where id in %s"
% strids) % strids)
# drop from cards # drop from cards
self.cardCount -= self.s.statement( self.s.statement("delete from cards where id in %s" % strids)
"delete from cards where id in %s" % strids).rowcount
# note deleted # note deleted
data = [{'id': id, 'time': now} for id in ids] data = [{'id': id, 'time': now} for id in ids]
self.s.statements("insert into cardsDeleted values (:id, :time)", data) self.s.statements("insert into cardsDeleted values (:id, :time)", data)
# remove any dangling facts # remove any dangling facts
self.deleteDanglingFacts() self.deleteDanglingFacts()
self.rebuildCounts()
self.setModified() self.setModified()
# Models # Models
@ -1883,6 +1889,11 @@ alter table models add column source integer not null default 0""")
for m in deck.models: for m in deck.models:
deck.updateCardsFromModel(m) deck.updateCardsFromModel(m)
deck.version = 13 deck.version = 13
if deck.version < 14:
deck.s.statement("""
update cards set interval = 0
where interval < 1""")
deck.version = 14
deck.s.commit() deck.s.commit()
return deck return deck
_upgradeDeck = staticmethod(_upgradeDeck) _upgradeDeck = staticmethod(_upgradeDeck)

View file

@ -425,11 +425,9 @@ where factId in %s""" % factIds))
'spaceUntil': f[5], 'spaceUntil': f[5],
'lastCardId': f[6] 'lastCardId': f[6]
} for f in facts] } for f in facts]
t = time.time()
self.deck.factCount += (len(facts) - self.deck.s.scalar( self.deck.factCount += (len(facts) - self.deck.s.scalar(
"select count(*) from facts where id in %s" % "select count(*) from facts where id in %s" %
ids2str([f[0] for f in facts]))) ids2str([f[0] for f in facts])))
#print "sync check", time.time() - t
self.deck.s.execute(""" self.deck.s.execute("""
insert or ignore into facts insert or ignore into facts
(id, modelId, created, modified, tags, spaceUntil, lastCardId) (id, modelId, created, modified, tags, spaceUntil, lastCardId)
@ -470,7 +468,7 @@ priority, interval, lastInterval, due, lastDue, factor,
firstAnswered, reps, successive, averageTime, reviewTime, youngEase0, firstAnswered, reps, successive, averageTime, reviewTime, youngEase0,
youngEase1, youngEase2, youngEase3, youngEase4, matureEase0, youngEase1, youngEase2, youngEase3, youngEase4, matureEase0,
matureEase1, matureEase2, matureEase3, matureEase4, yesCount, noCount, matureEase1, matureEase2, matureEase3, matureEase4, yesCount, noCount,
question, answer, lastFactor, spaceUntil, isDue, type, combinedDue question, answer, lastFactor, spaceUntil, type, combinedDue
from cards where id in %s""" % ids2str(ids))) from cards where id in %s""" % ids2str(ids)))
def updateCards(self, cards): def updateCards(self, cards):
@ -510,15 +508,12 @@ from cards where id in %s""" % ids2str(ids)))
'answer': c[31], 'answer': c[31],
'lastFactor': c[32], 'lastFactor': c[32],
'spaceUntil': c[33], 'spaceUntil': c[33],
'isDue': c[34], 'type': c[34],
'type': c[35], 'combinedDue': c[35],
'combinedDue': c[36],
} for c in cards] } for c in cards]
t = time.time()
self.deck.cardCount += (len(cards) - self.deck.s.scalar( self.deck.cardCount += (len(cards) - self.deck.s.scalar(
"select count(*) from cards where id in %s" % "select count(*) from cards where id in %s" %
ids2str([c[0] for c in cards]))) ids2str([c[0] for c in cards])))
#print "sync check cards", time.time() - t
self.deck.s.execute(""" self.deck.s.execute("""
insert or replace into cards insert or replace into cards
(id, factId, cardModelId, created, modified, tags, ordinal, (id, factId, cardModelId, created, modified, tags, ordinal,
@ -526,16 +521,16 @@ priority, interval, lastInterval, due, lastDue, factor,
firstAnswered, reps, successive, averageTime, reviewTime, youngEase0, firstAnswered, reps, successive, averageTime, reviewTime, youngEase0,
youngEase1, youngEase2, youngEase3, youngEase4, matureEase0, youngEase1, youngEase2, youngEase3, youngEase4, matureEase0,
matureEase1, matureEase2, matureEase3, matureEase4, yesCount, noCount, matureEase1, matureEase2, matureEase3, matureEase4, yesCount, noCount,
question, answer, lastFactor, spaceUntil, isDue, type, combinedDue, question, answer, lastFactor, spaceUntil, type, combinedDue,
relativeDelay) relativeDelay, isDue)
values values
(:id, :factId, :cardModelId, :created, :modified, :tags, :ordinal, (:id, :factId, :cardModelId, :created, :modified, :tags, :ordinal,
:priority, :interval, :lastInterval, :due, :lastDue, :factor, :priority, :interval, :lastInterval, :due, :lastDue, :factor,
:firstAnswered, :reps, :successive, :averageTime, :reviewTime, :youngEase0, :firstAnswered, :reps, :successive, :averageTime, :reviewTime, :youngEase0,
:youngEase1, :youngEase2, :youngEase3, :youngEase4, :matureEase0, :youngEase1, :youngEase2, :youngEase3, :youngEase4, :matureEase0,
:matureEase1, :matureEase2, :matureEase3, :matureEase4, :yesCount, :matureEase1, :matureEase2, :matureEase3, :matureEase4, :yesCount,
:noCount, :question, :answer, :lastFactor, :spaceUntil, :isDue, :noCount, :question, :answer, :lastFactor, :spaceUntil,
:type, :combinedDue, 0)""", dlist) :type, :combinedDue, 0, 0)""", dlist)
self.deck.s.statement( self.deck.s.statement(
"delete from cardsDeleted where cardId in %s" % "delete from cardsDeleted where cardId in %s" %
ids2str([c[0] for c in cards])) ids2str([c[0] for c in cards]))
@ -811,7 +806,7 @@ values
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, "", "", 2.5, 0, 1, 2, :t, 0)""", dlist) 0, "", "", 2.5, 0, 0, 2, :t, 0)""", dlist)
# update q/as # update q/as
models = dict(self.deck.s.all(""" models = dict(self.deck.s.all("""
select cards.id, models.id select cards.id, models.id