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('modified', Float, nullable=False, default=time.time),
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")),
# syncing
Column('syncName', UnicodeText),
@ -141,37 +141,21 @@ class Deck(object):
# card due for review?
if self.revCount:
return self.s.scalar("select id from revCards limit 1")
# new card last?
if self.newCardSpacing == NEW_CARDS_LAST:
id = self._maybeGetNewCard()
if id:
return id
# new cards left?
id = self._maybeGetNewCard()
if id:
return id
# display failed cards early
if self.collapseTime:
# display failed cards early
id = self.s.scalar(
"select id from failedCardsSoon limit 1")
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
##########################################################################
def _timeForNewCard(self):
"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
if self.s.scalar(
"select 1 from cards where type = 1 and isDue = 1 "
@ -213,6 +197,65 @@ reps, successive from """
card.startTimer()
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
##########################################################################
@ -382,10 +425,10 @@ strftime("%s", "now")+1 from decks)"""))
def _nextInterval(self, interval, factor, delay, ease):
# if interval is less than mid interval, use presets
if interval < self.hardIntervalMin:
if ease < 2:
interval = NEW_INTERVAL
elif ease == 2:
if ease == 1:
interval = NEW_INTERVAL
elif interval == 0:
if ease == 2:
interval = random.uniform(self.hardIntervalMin,
self.hardIntervalMax)
elif ease == 3:
@ -394,36 +437,35 @@ strftime("%s", "now")+1 from decks)"""))
elif ease == 4:
interval = random.uniform(self.easyIntervalMin,
self.easyIntervalMax)
elif ease == 0:
interval = NEW_INTERVAL
else:
# otherwise, multiply the old interval by a factor
if ease == 1:
factor = 1 / factor / 2.0
interval = interval * factor
elif ease == 2:
factor = 1.2
interval = (interval + delay/4) * factor
if ease == 2:
interval = (interval + delay/4) * 1.2
elif ease == 3:
factor = factor
interval = (interval + delay/2) * factor
elif ease == 4:
factor = factor * self.factorFour
interval = (interval + delay) * factor
interval = (interval + delay) * factor * self.factorFour
fuzz = random.uniform(0.95, 1.05)
interval *= fuzz
if self.maxScheduleTime:
interval = min(interval, self.maxScheduleTime)
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):
"Return time when CARD will expire given EASE."
if ease == 0:
if ease == 1:
due = self.delay0
elif ease == 1 and oldState != 'mature':
due = self.delay1
elif ease == 1:
due = self.delay2
else:
due = card.interval * 86400.0
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
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):
return _('''
<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
stats['new'] = self.newCountToday
stats['failed'] = self.failedSoonCount
stats['successive'] = self.revCount
stats['rev'] = self.revCount
if stats['dAverageTime']:
if self.newCardSpacing == NEW_CARDS_DISTRIBUTE:
count = stats['successive'] + stats['new']
count = stats['rev'] + stats['new']
elif self.newCardSpacing == NEW_CARDS_LAST:
count = stats['successive'] or stats['new']
count = stats['rev'] or stats['new']
count += stats['failed']
stats['timeLeft'] = anki.utils.fmtTimeSpan(
stats['dAverageTime'] * count, pad=0, point=1)
@ -753,7 +771,7 @@ and due < :now""", now=time.time())
self.newCount += len(cards)
# keep track of last used tags for convenience
self.lastTags = fact.tags
self.setModified()
self.flushMod()
return cards
def availableCardModels(self, fact):
@ -812,14 +830,10 @@ where factId = :fid and cardModelId = :cmid""",
self.s.statement("insert into cardsDeleted select id, :time "
"from cards where factId = :factId",
time=time.time(), factId=factId)
self.cardCount -= self.s.statement(
"delete from cards where factId = :id", id=factId).rowcount
self.s.statement(
"delete from cards where factId = :id", id=factId)
# and then the fact
self.factCount -= self.s.statement(
"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.deleteFacts([factId])
self.setModified()
def deleteFacts(self, ids):
@ -829,11 +843,11 @@ where factId = :fid and cardModelId = :cmid""",
self.s.flush()
now = time.time()
strids = ids2str(ids)
self.factCount -= self.s.statement(
"delete from facts where id in %s" % strids).rowcount
self.s.statement("delete from facts where id in %s" % strids)
self.s.statement("delete from fields where factId in %s" % strids)
data = [{'id': id, 'time': now} for id in ids]
self.s.statements("insert into factsDeleted values (:id, :time)", data)
self.rebuildCounts()
self.setModified()
def deleteDanglingFacts(self):
@ -849,15 +863,7 @@ where facts.id not in (select factId from cards)""")
def deleteCard(self, id):
"Delete a card given its id. Delete any unused facts. Don't flush."
self.s.flush()
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()
self.deleteCards([id])
def deleteCards(self, ids):
"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"
% strids)
# drop from cards
self.cardCount -= self.s.statement(
"delete from cards where id in %s" % strids).rowcount
self.s.statement("delete from cards where id in %s" % strids)
# note deleted
data = [{'id': id, 'time': now} for id in ids]
self.s.statements("insert into cardsDeleted values (:id, :time)", data)
# remove any dangling facts
self.deleteDanglingFacts()
self.rebuildCounts()
self.setModified()
# Models
@ -1883,6 +1889,11 @@ alter table models add column source integer not null default 0""")
for m in deck.models:
deck.updateCardsFromModel(m)
deck.version = 13
if deck.version < 14:
deck.s.statement("""
update cards set interval = 0
where interval < 1""")
deck.version = 14
deck.s.commit()
return deck
_upgradeDeck = staticmethod(_upgradeDeck)

View file

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