mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
refactor cards
Cards had developed quite a lot of cruft from incremental changes, and a number of important attributes were stored in names that had no bearing to their actual use. Added: - position, which new cards will be sorted on in the future - flags, which is reserved for future use Renamed: - type to queue - relativeDelay to type - noCount to lapses Removed: - all new/young/matureEase counts; the information is in the revlog - firstAnswered, lastDue, lastFactor, averageTime and totalTime for the same reason - isDue, spaceUntil and combinedDue, because they are no longer used. Spaced cards will be implemented differently in a coming commit. - priority - yesCount, because it can be inferred from reps & lapses - tags; they've been stored in facts for a long time now Also compatibility with deck versions less than 65 has been dropped, so decks will need to be upgraded to 1.2 before they can be upgraded by the dev code. All shared decks are on 1.2, so this should hopefully not be a problem.
This commit is contained in:
parent
f828393de3
commit
9aa2f8dc40
8 changed files with 235 additions and 440 deletions
214
anki/cards.py
214
anki/cards.py
|
@ -14,72 +14,50 @@ MAX_TIMER = 60
|
|||
# Cards
|
||||
##########################################################################
|
||||
|
||||
# Type: 0=lapsed, 1=due, 2=new, 3=drilled
|
||||
# Queue: under normal circumstances, same as type.
|
||||
# -1=suspended, -2=user buried, -3=sched buried (rev early, etc)
|
||||
# Ordinal: card template # for fact
|
||||
# Position: sorting position, only for new cards
|
||||
# Flags: unused; reserved for future use
|
||||
|
||||
cardsTable = Table(
|
||||
'cards', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('factId', Integer, ForeignKey("facts.id"), nullable=False),
|
||||
Column('cardModelId', Integer, ForeignKey("cardModels.id"), nullable=False),
|
||||
# general
|
||||
Column('created', Float, nullable=False, default=time.time),
|
||||
Column('modified', Float, nullable=False, default=time.time),
|
||||
Column('tags', UnicodeText, nullable=False, default=u""),
|
||||
Column('ordinal', Integer, nullable=False),
|
||||
# q/a cached - changed on fact update
|
||||
Column('question', UnicodeText, nullable=False, default=u""),
|
||||
Column('answer', UnicodeText, nullable=False, default=u""),
|
||||
Column('priority', Integer, nullable=False, default=2), # obsolete
|
||||
Column('interval', Float, nullable=False, default=0),
|
||||
Column('flags', Integer, nullable=False, default=0),
|
||||
# ordering
|
||||
Column('ordinal', Integer, nullable=False),
|
||||
Column('position', Integer, nullable=False),
|
||||
# scheduling data
|
||||
Column('type', Integer, nullable=False, default=2),
|
||||
Column('queue', Integer, nullable=False, default=2),
|
||||
Column('lastInterval', Float, nullable=False, default=0),
|
||||
Column('due', Float, nullable=False, default=time.time),
|
||||
Column('lastDue', Float, nullable=False, default=0),
|
||||
Column('interval', Float, nullable=False, default=0),
|
||||
Column('due', Float, nullable=False),
|
||||
Column('factor', Float, nullable=False, default=2.5),
|
||||
Column('lastFactor', Float, nullable=False, default=2.5),
|
||||
Column('firstAnswered', Float, nullable=False, default=0),
|
||||
# stats
|
||||
# counters
|
||||
Column('reps', Integer, nullable=False, default=0),
|
||||
Column('successive', Integer, nullable=False, default=0),
|
||||
Column('averageTime', Float, nullable=False, default=0),
|
||||
Column('reviewTime', Float, nullable=False, default=0),
|
||||
Column('youngEase0', Integer, nullable=False, default=0),
|
||||
Column('youngEase1', Integer, nullable=False, default=0),
|
||||
Column('youngEase2', Integer, nullable=False, default=0),
|
||||
Column('youngEase3', Integer, nullable=False, default=0),
|
||||
Column('youngEase4', Integer, nullable=False, default=0),
|
||||
Column('matureEase0', Integer, nullable=False, default=0),
|
||||
Column('matureEase1', Integer, nullable=False, default=0),
|
||||
Column('matureEase2', Integer, nullable=False, default=0),
|
||||
Column('matureEase3', Integer, nullable=False, default=0),
|
||||
Column('matureEase4', Integer, nullable=False, default=0),
|
||||
# this duplicates the above data, because there's no way to map imported
|
||||
# data to the above
|
||||
Column('yesCount', Integer, nullable=False, default=0),
|
||||
Column('noCount', Integer, nullable=False, default=0),
|
||||
# obsolete
|
||||
Column('spaceUntil', Float, nullable=False, default=0),
|
||||
# relativeDelay is reused as type without scheduling (ie, it remains 0-2
|
||||
# even if card is suspended, etc)
|
||||
Column('relativeDelay', Float, nullable=False, default=0),
|
||||
Column('isDue', Boolean, nullable=False, default=0), # obsolete
|
||||
Column('type', Integer, nullable=False, default=2),
|
||||
Column('combinedDue', Integer, nullable=False, default=0))
|
||||
Column('lapses', Integer, nullable=False, default=0))
|
||||
|
||||
class Card(object):
|
||||
"A card."
|
||||
|
||||
def __init__(self, fact=None, cardModel=None, created=None):
|
||||
self.tags = u""
|
||||
self.id = genID()
|
||||
# new cards start as new & due
|
||||
self.type = 2
|
||||
self.relativeDelay = self.type
|
||||
self.timerStarted = False
|
||||
self.timerStopped = False
|
||||
self.modified = time.time()
|
||||
if created:
|
||||
self.created = created
|
||||
self.due = created
|
||||
else:
|
||||
self.due = self.modified
|
||||
self.combinedDue = self.due
|
||||
self.position = self.due
|
||||
if fact:
|
||||
self.fact = fact
|
||||
if cardModel:
|
||||
|
@ -87,13 +65,27 @@ class Card(object):
|
|||
# for non-orm use
|
||||
self.cardModelId = cardModel.id
|
||||
self.ordinal = cardModel.ordinal
|
||||
# timer
|
||||
self.timerStarted = None
|
||||
|
||||
def setModified(self):
|
||||
self.modified = time.time()
|
||||
|
||||
def startTimer(self):
|
||||
self.timerStarted = time.time()
|
||||
|
||||
def userTime(self):
|
||||
return min(time.time() - self.timerStarted, MAX_TIMER)
|
||||
|
||||
# Questions and answers
|
||||
##########################################################################
|
||||
|
||||
def rebuildQA(self, deck, media=True):
|
||||
# format qa
|
||||
d = {}
|
||||
for f in self.fact.model.fieldModels:
|
||||
d[f.name] = (f.id, self.fact[f.name])
|
||||
qa = formatQA(None, self.fact.modelId, d, self.splitTags(),
|
||||
qa = formatQA(None, self.fact.modelId, d, self._splitTags(),
|
||||
self.cardModel, deck)
|
||||
# find old media references
|
||||
files = {}
|
||||
|
@ -119,25 +111,6 @@ class Card(object):
|
|||
updateMediaCount(deck, f, cnt)
|
||||
self.setModified()
|
||||
|
||||
def setModified(self):
|
||||
self.modified = time.time()
|
||||
|
||||
def startTimer(self):
|
||||
self.timerStarted = time.time()
|
||||
|
||||
def stopTimer(self):
|
||||
self.timerStopped = time.time()
|
||||
|
||||
def thinkingTime(self):
|
||||
return (self.timerStopped or time.time()) - self.timerStarted
|
||||
|
||||
def totalTime(self):
|
||||
return time.time() - self.timerStarted
|
||||
|
||||
def genFuzz(self):
|
||||
"Generate a random offset to spread intervals."
|
||||
self.fuzz = random.uniform(0.95, 1.05)
|
||||
|
||||
def htmlQuestion(self, type="question", align=True):
|
||||
div = '''<div class="card%s" id="cm%s%s">%s</div>''' % (
|
||||
type[0], type[0], hexifyID(self.cardModelId),
|
||||
|
@ -158,52 +131,14 @@ class Card(object):
|
|||
def htmlAnswer(self, align=True):
|
||||
return self.htmlQuestion(type="answer", align=align)
|
||||
|
||||
def updateStats(self, ease, state):
|
||||
self.reps += 1
|
||||
if ease > 1:
|
||||
self.successive += 1
|
||||
else:
|
||||
self.successive = 0
|
||||
delay = min(self.totalTime(), MAX_TIMER)
|
||||
self.reviewTime += delay
|
||||
if self.averageTime:
|
||||
self.averageTime = (self.averageTime + delay) / 2.0
|
||||
else:
|
||||
self.averageTime = delay
|
||||
# we don't track first answer for cards
|
||||
if state == "new":
|
||||
state = "young"
|
||||
# update ease and yes/no count
|
||||
attr = state + "Ease%d" % ease
|
||||
setattr(self, attr, getattr(self, attr) + 1)
|
||||
if ease < 2:
|
||||
self.noCount += 1
|
||||
else:
|
||||
self.yesCount += 1
|
||||
if not self.firstAnswered:
|
||||
self.firstAnswered = time.time()
|
||||
self.setModified()
|
||||
|
||||
def splitTags(self):
|
||||
def _splitTags(self):
|
||||
return (self.fact.tags, self.fact.model.tags, self.cardModel.name)
|
||||
|
||||
def allTags(self):
|
||||
"Non-canonified string of all tags."
|
||||
return (self.fact.tags + "," +
|
||||
self.fact.model.tags)
|
||||
|
||||
def hasTag(self, tag):
|
||||
return findTag(tag, parseTags(self.allTags()))
|
||||
# Non-ORM
|
||||
##########################################################################
|
||||
|
||||
def fromDB(self, s, id):
|
||||
r = s.first("""select
|
||||
id, factId, cardModelId, created, modified, tags, ordinal, question, answer,
|
||||
priority, interval, lastInterval, due, lastDue, factor,
|
||||
lastFactor, firstAnswered, reps, successive, averageTime, reviewTime,
|
||||
youngEase0, youngEase1, youngEase2, youngEase3, youngEase4,
|
||||
matureEase0, matureEase1, matureEase2, matureEase3, matureEase4,
|
||||
yesCount, noCount, spaceUntil, isDue, type, combinedDue
|
||||
from cards where id = :id""", id=id)
|
||||
r = s.first("""select * from cards where id = :id""", id=id)
|
||||
if not r:
|
||||
return
|
||||
(self.id,
|
||||
|
@ -211,74 +146,42 @@ from cards where id = :id""", id=id)
|
|||
self.cardModelId,
|
||||
self.created,
|
||||
self.modified,
|
||||
self.tags,
|
||||
self.ordinal,
|
||||
self.question,
|
||||
self.answer,
|
||||
self.priority,
|
||||
self.interval,
|
||||
self.flags,
|
||||
self.ordinal,
|
||||
self.position,
|
||||
self.type,
|
||||
self.queue,
|
||||
self.lastInterval,
|
||||
self.interval,
|
||||
self.due,
|
||||
self.lastDue,
|
||||
self.factor,
|
||||
self.lastFactor,
|
||||
self.firstAnswered,
|
||||
self.reps,
|
||||
self.successive,
|
||||
self.averageTime,
|
||||
self.reviewTime,
|
||||
self.youngEase0,
|
||||
self.youngEase1,
|
||||
self.youngEase2,
|
||||
self.youngEase3,
|
||||
self.youngEase4,
|
||||
self.matureEase0,
|
||||
self.matureEase1,
|
||||
self.matureEase2,
|
||||
self.matureEase3,
|
||||
self.matureEase4,
|
||||
self.yesCount,
|
||||
self.noCount,
|
||||
self.spaceUntil,
|
||||
self.isDue,
|
||||
self.type,
|
||||
self.combinedDue) = r
|
||||
self.lapses) = r
|
||||
return True
|
||||
|
||||
def toDB(self, s):
|
||||
"Write card to DB."
|
||||
s.execute("""update cards set
|
||||
factId=:factId,
|
||||
cardModelId=:cardModelId,
|
||||
created=:created,
|
||||
modified=:modified,
|
||||
tags=:tags,
|
||||
interval=:interval,
|
||||
question=:question,
|
||||
answer=:answer,
|
||||
flags=:flags,
|
||||
ordinal=:ordinal,
|
||||
position=:position,
|
||||
type=:type,
|
||||
queue=:queue,
|
||||
lastInterval=:lastInterval,
|
||||
interval=:interval,
|
||||
due=:due,
|
||||
lastDue=:lastDue,
|
||||
factor=:factor,
|
||||
lastFactor=:lastFactor,
|
||||
firstAnswered=:firstAnswered,
|
||||
reps=:reps,
|
||||
successive=:successive,
|
||||
averageTime=:averageTime,
|
||||
reviewTime=:reviewTime,
|
||||
youngEase0=:youngEase0,
|
||||
youngEase1=:youngEase1,
|
||||
youngEase2=:youngEase2,
|
||||
youngEase3=:youngEase3,
|
||||
youngEase4=:youngEase4,
|
||||
matureEase0=:matureEase0,
|
||||
matureEase1=:matureEase1,
|
||||
matureEase2=:matureEase2,
|
||||
matureEase3=:matureEase3,
|
||||
matureEase4=:matureEase4,
|
||||
yesCount=:yesCount,
|
||||
noCount=:noCount,
|
||||
spaceUntil = :spaceUntil,
|
||||
isDue = 0,
|
||||
type = :type,
|
||||
combinedDue = :combinedDue,
|
||||
relativeDelay = :relativeDelay,
|
||||
priority = :priority
|
||||
lapses=:lapses
|
||||
where id=:id""", self.__dict__)
|
||||
|
||||
mapper(Card, cardsTable, properties={
|
||||
|
@ -292,7 +195,6 @@ mapper(Fact, factsTable, properties={
|
|||
'fields': relation(Field, backref="fact", order_by=Field.ordinal),
|
||||
})
|
||||
|
||||
|
||||
# Card deletions
|
||||
##########################################################################
|
||||
|
||||
|
|
216
anki/deck.py
216
anki/deck.py
|
@ -14,7 +14,7 @@ from anki.utils import parseTags, tidyHTML, genID, ids2str, hexifyID, \
|
|||
from anki.revlog import logReview
|
||||
from anki.models import Model, CardModel, formatQA
|
||||
from anki.fonts import toPlatformFont
|
||||
from anki.tags import initTagTables, tagIds
|
||||
from anki.tags import initTagTables, tagIds, tagId
|
||||
from operator import itemgetter
|
||||
from itertools import groupby
|
||||
from anki.hooks import runHook, hookEmpty
|
||||
|
@ -242,22 +242,22 @@ where time > :t""", t=self.failedCutoff-86400)
|
|||
self.failedSoonCount = self.db.scalar(
|
||||
self.cardLimit(
|
||||
"revActive", "revInactive",
|
||||
"select count(*) from cards c where type = 0 "
|
||||
"and combinedDue < :lim"), lim=self.failedCutoff)
|
||||
"select count(*) from cards c where queue = 0 "
|
||||
"and due < :lim"), lim=self.failedCutoff)
|
||||
|
||||
def _rebuildRevCount(self):
|
||||
self.revCount = self.db.scalar(
|
||||
self.cardLimit(
|
||||
"revActive", "revInactive",
|
||||
"select count(*) from cards c where type = 1 "
|
||||
"and combinedDue < :lim"), lim=self.dueCutoff)
|
||||
"select count(*) from cards c where queue = 1 "
|
||||
"and due < :lim"), lim=self.dueCutoff)
|
||||
|
||||
def _rebuildNewCount(self):
|
||||
self.newAvail = self.db.scalar(
|
||||
self.cardLimit(
|
||||
"newActive", "newInactive",
|
||||
"select count(*) from cards c where type = 2 "
|
||||
"and combinedDue < :lim"), lim=self.dueCutoff)
|
||||
"select count(*) from cards c where queue = 2 "
|
||||
"and due < :lim"), lim=self.dueCutoff)
|
||||
self.updateNewCountToday()
|
||||
self.spacedCards = []
|
||||
|
||||
|
@ -271,8 +271,8 @@ where time > :t""", t=self.failedCutoff-86400)
|
|||
self.failedQueue = self.db.all(
|
||||
self.cardLimit(
|
||||
"revActive", "revInactive", """
|
||||
select c.id, factId, combinedDue from cards c where
|
||||
type = 0 and combinedDue < :lim order by combinedDue
|
||||
select c.id, factId, due from cards c where
|
||||
queue = 0 and due < :lim order by due
|
||||
limit %d""" % self.queueLimit), lim=self.failedCutoff)
|
||||
self.failedQueue.reverse()
|
||||
|
||||
|
@ -282,7 +282,7 @@ limit %d""" % self.queueLimit), lim=self.failedCutoff)
|
|||
self.cardLimit(
|
||||
"revActive", "revInactive", """
|
||||
select c.id, factId from cards c where
|
||||
type = 1 and combinedDue < :lim order by %s
|
||||
queue = 1 and due < :lim order by %s
|
||||
limit %d""" % (self.revOrder(), self.queueLimit)), lim=self.dueCutoff)
|
||||
self.revQueue.reverse()
|
||||
|
||||
|
@ -292,7 +292,7 @@ limit %d""" % (self.revOrder(), self.queueLimit)), lim=self.dueCutoff)
|
|||
self.cardLimit(
|
||||
"newActive", "newInactive", """
|
||||
select c.id, factId from cards c where
|
||||
type = 2 and combinedDue < :lim order by %s
|
||||
queue = 2 and due < :lim order by %s
|
||||
limit %d""" % (self.newOrder(), self.queueLimit)), lim=self.dueCutoff)
|
||||
self.newQueue.reverse()
|
||||
|
||||
|
@ -365,7 +365,7 @@ New type: %s""" % (self.failedSoonCount, self.revCount, self.newCount,
|
|||
def revOrder(self):
|
||||
return ("interval desc",
|
||||
"interval",
|
||||
"combinedDue",
|
||||
"due",
|
||||
"factId, ordinal")[self.revCardOrder]
|
||||
|
||||
def newOrder(self):
|
||||
|
@ -375,18 +375,15 @@ New type: %s""" % (self.failedSoonCount, self.revCount, self.newCount,
|
|||
|
||||
def rebuildTypes(self):
|
||||
"Rebuild the type cache. Only necessary on upgrade."
|
||||
# set canonical type first
|
||||
# set type first
|
||||
self.db.statement("""
|
||||
update cards set
|
||||
relativeDelay = (case
|
||||
update cards set type = (case
|
||||
when successive then 1 when reps then 0 else 2 end)
|
||||
""")
|
||||
# then current type based on that
|
||||
# then queue
|
||||
self.db.statement("""
|
||||
update cards set
|
||||
type = (case
|
||||
when type >= 0 then relativeDelay else relativeDelay - 3 end)
|
||||
""")
|
||||
update cards set queue = type
|
||||
when queue != -1""")
|
||||
|
||||
def updateAllFieldChecksums(self):
|
||||
# zero out
|
||||
|
@ -490,12 +487,12 @@ when type >= 0 then relativeDelay else relativeDelay - 3 end)
|
|||
def _reviewEarlyPreSave(self, card, ease):
|
||||
if ease > 1:
|
||||
# prevent it from appearing in next queue fill
|
||||
card.type += 6
|
||||
card.queue = -3
|
||||
|
||||
def resetAfterReviewEarly(self):
|
||||
"Put temporarily suspended cards back into play. Caller must .reset()"
|
||||
self.db.statement(
|
||||
"update cards set type = type - 6 where type between 6 and 8")
|
||||
"update cards set queue = type where queue = -3")
|
||||
|
||||
def _onReviewEarlyFinished(self):
|
||||
# clean up buried cards
|
||||
|
@ -508,7 +505,7 @@ when type >= 0 then relativeDelay else relativeDelay - 3 end)
|
|||
self.revCount = self.db.scalar(
|
||||
self.cardLimit(
|
||||
"revActive", "revInactive", """
|
||||
select count() from cards c where type = 1 and combinedDue > :now
|
||||
select count() from cards c where queue = 1 and due > :now
|
||||
"""), now=self.dueCutoff)
|
||||
|
||||
def _fillRevEarlyQueue(self):
|
||||
|
@ -516,8 +513,8 @@ select count() from cards c where type = 1 and combinedDue > :now
|
|||
self.revQueue = self.db.all(
|
||||
self.cardLimit(
|
||||
"revActive", "revInactive", """
|
||||
select id, factId from cards c where type = 1 and combinedDue > :lim
|
||||
order by combinedDue limit %d""" % self.queueLimit), lim=self.dueCutoff)
|
||||
select id, factId from cards c where queue = 1 and due > :lim
|
||||
order by due limit %d""" % self.queueLimit), lim=self.dueCutoff)
|
||||
self.revQueue.reverse()
|
||||
|
||||
# Learn more
|
||||
|
@ -533,8 +530,8 @@ order by combinedDue limit %d""" % self.queueLimit), lim=self.dueCutoff)
|
|||
self.newAvail = self.db.scalar(
|
||||
self.cardLimit(
|
||||
"newActive", "newInactive",
|
||||
"select count(*) from cards c where type = 2 "
|
||||
"and combinedDue < :lim"), lim=self.dueCutoff)
|
||||
"select count(*) from cards c where queue = 2 "
|
||||
"and due < :lim"), lim=self.dueCutoff)
|
||||
self.spacedCards = []
|
||||
|
||||
def _updateLearnMoreCountToday(self):
|
||||
|
@ -566,7 +563,7 @@ order by combinedDue limit %d""" % self.queueLimit), lim=self.dueCutoff)
|
|||
def _cramPreSave(self, card, ease):
|
||||
# prevent it from appearing in next queue fill
|
||||
card.lastInterval = self.cramLastInterval
|
||||
card.type += 6
|
||||
card.type = -3
|
||||
|
||||
def _spaceCramCards(self, card):
|
||||
self.spacedFacts[card.factId] = time.time() + self.newSpacing
|
||||
|
@ -634,7 +631,7 @@ order by combinedDue limit %d""" % self.queueLimit), lim=self.dueCutoff)
|
|||
self.revQueue = self.db.all(self.cardLimit(
|
||||
self.activeCramTags, "", """
|
||||
select id, factId from cards c
|
||||
where type between 0 and 2
|
||||
where queue between 0 and 2
|
||||
order by %s
|
||||
limit %s""" % (self.cramOrder, self.queueLimit)))
|
||||
self.revQueue.reverse()
|
||||
|
@ -642,7 +639,7 @@ limit %s""" % (self.cramOrder, self.queueLimit)))
|
|||
def _rebuildCramCount(self):
|
||||
self.revCount = self.db.scalar(self.cardLimit(
|
||||
self.activeCramTags, "",
|
||||
"select count(*) from cards c where type between 0 and 2"))
|
||||
"select count(*) from cards c where queue between 0 and 2"))
|
||||
|
||||
def _rebuildFailedCramCount(self):
|
||||
self.failedSoonCount = len(self.failedCramQueue)
|
||||
|
@ -754,7 +751,7 @@ limit %s""" % (self.cramOrder, self.queueLimit)))
|
|||
if not card.fromDB(self.db, id):
|
||||
return
|
||||
card.deck = self
|
||||
card.genFuzz()
|
||||
#card.genFuzz()
|
||||
card.startTimer()
|
||||
return card
|
||||
|
||||
|
@ -768,7 +765,7 @@ limit %s""" % (self.cramOrder, self.queueLimit)))
|
|||
# old state
|
||||
oldState = self.cardState(card)
|
||||
oldQueue = self.cardQueue(card)
|
||||
lastDelaySecs = time.time() - card.combinedDue
|
||||
lastDelaySecs = time.time() - card.due
|
||||
lastDelay = lastDelaySecs / 86400.0
|
||||
oldSuc = card.successive
|
||||
# update card details
|
||||
|
@ -779,8 +776,6 @@ limit %s""" % (self.cramOrder, self.queueLimit)))
|
|||
# only update if card was not new
|
||||
card.lastDue = card.due
|
||||
card.due = self.nextDue(card, ease, oldState)
|
||||
card.isDue = 0
|
||||
card.lastFactor = card.factor
|
||||
card.spaceUntil = 0
|
||||
if not self.finishScheduler:
|
||||
# don't update factor in custom schedulers
|
||||
|
@ -798,17 +793,17 @@ limit %s""" % (self.cramOrder, self.queueLimit)))
|
|||
else:
|
||||
self.newAvail -= 1
|
||||
# card stats
|
||||
anki.cards.Card.updateStats(card, ease, oldState)
|
||||
self.updateCardStats(card, ease, oldState)
|
||||
# update type & ensure past cutoff
|
||||
card.type = self.cardType(card)
|
||||
card.relativeDelay = card.type
|
||||
card.queue = card.type
|
||||
if ease != 1:
|
||||
card.due = max(card.due, self.dueCutoff+1)
|
||||
# allow custom schedulers to munge the card
|
||||
if self.answerPreSave:
|
||||
self.answerPreSave(card, ease)
|
||||
# save
|
||||
card.combinedDue = card.due
|
||||
card.due = card.due
|
||||
card.toDB(self.db)
|
||||
# review history
|
||||
print "make sure flags is set correctly when reviewing early"
|
||||
|
@ -824,28 +819,39 @@ limit %s""" % (self.cramOrder, self.queueLimit)))
|
|||
runHook("cardAnswered", card.id, isLeech)
|
||||
self.setUndoEnd(undoName)
|
||||
|
||||
def updateCardStats(self, card, ease, state):
|
||||
card.reps += 1
|
||||
if ease == 1:
|
||||
card.successive = 0
|
||||
card.lapses += 1
|
||||
else:
|
||||
card.successive += 1
|
||||
# if not card.firstAnswered:
|
||||
# card.firstAnswered = time.time()
|
||||
card.setModified()
|
||||
|
||||
def _spaceCards(self, card):
|
||||
new = time.time() + self.newSpacing
|
||||
self.db.statement("""
|
||||
update cards set
|
||||
combinedDue = (case
|
||||
when type = 1 then combinedDue + 86400 * (case
|
||||
due = (case
|
||||
when queue = 1 then due + 86400 * (case
|
||||
when interval*:rev < 1 then 0
|
||||
else interval*:rev
|
||||
end)
|
||||
when type = 2 then :new
|
||||
when queue = 2 then :new
|
||||
end),
|
||||
modified = :now, isDue = 0
|
||||
modified = :now
|
||||
where id != :id and factId = :factId
|
||||
and combinedDue < :cut
|
||||
and type between 1 and 2""",
|
||||
and due < :cut
|
||||
and queue between 1 and 2""",
|
||||
id=card.id, now=time.time(), factId=card.factId,
|
||||
cut=self.dueCutoff, new=new, rev=self.revSpacing)
|
||||
# update local cache of seen facts
|
||||
self.spacedFacts[card.factId] = new
|
||||
|
||||
def isLeech(self, card):
|
||||
no = card.noCount
|
||||
no = card.lapses
|
||||
fmax = self.getInt('leechFails')
|
||||
if not fmax:
|
||||
return
|
||||
|
@ -885,8 +891,6 @@ and type between 1 and 2""",
|
|||
factor = card.factor
|
||||
# if cramming / reviewing early
|
||||
if delay < 0:
|
||||
# FIXME: this should recreate lastInterval from interval /
|
||||
# lastFactor, or we lose delay information when reviewing early
|
||||
interval = max(card.lastInterval, card.interval + delay)
|
||||
if interval < self.midIntervalMin:
|
||||
interval = 0
|
||||
|
@ -950,7 +954,7 @@ and type between 1 and 2""",
|
|||
|
||||
def updateFactor(self, card, ease):
|
||||
"Update CARD's factor based on EASE."
|
||||
card.lastFactor = card.factor
|
||||
print "update cardIsBeingLearnt()"
|
||||
if not card.reps:
|
||||
# card is new, inherit beginning factor
|
||||
card.factor = self.averageFactor
|
||||
|
@ -967,24 +971,26 @@ and type between 1 and 2""",
|
|||
"Return an adjusted delay value for CARD based on EASE."
|
||||
if self.cardIsNew(card):
|
||||
return 0
|
||||
if card.reps and not card.successive:
|
||||
return 0
|
||||
if card.combinedDue <= self.dueCutoff:
|
||||
return (self.dueCutoff - card.due) / 86400.0
|
||||
if card.due <= time.time():
|
||||
return (time.time() - card.due) / 86400.0
|
||||
else:
|
||||
return (self.dueCutoff - card.combinedDue) / 86400.0
|
||||
return (time.time() - card.due) / 86400.0
|
||||
|
||||
def resetCards(self, ids):
|
||||
def resetCards(self, ids=None):
|
||||
"Reset progress on cards in IDS."
|
||||
self.db.statement("""
|
||||
update cards set interval = :new, lastInterval = 0, lastDue = 0,
|
||||
factor = 2.5, reps = 0, successive = 0, averageTime = 0, reviewTime = 0,
|
||||
youngEase0 = 0, youngEase1 = 0, youngEase2 = 0, youngEase3 = 0,
|
||||
youngEase4 = 0, matureEase0 = 0, matureEase1 = 0, matureEase2 = 0,
|
||||
matureEase3 = 0,matureEase4 = 0, yesCount = 0, noCount = 0,
|
||||
spaceUntil = 0, type = 2, relativeDelay = 2,
|
||||
combinedDue = created, modified = :now, due = created, isDue = 0
|
||||
where id in %s""" % ids2str(ids), now=time.time(), new=0)
|
||||
print "position in resetCards()"
|
||||
sql = """
|
||||
update cards set modified=:now, position=0, type=2, queue=2, lastInterval=0,
|
||||
interval=0, due=created, factor=2.5, reps=0, successive=0, lapses=0, flags=0"""
|
||||
sql2 = "delete from revlog"
|
||||
if ids is None:
|
||||
lim = ""
|
||||
else:
|
||||
sids = ids2str(ids)
|
||||
sql += " where id in "+sids
|
||||
sql2 += " where cardId in "+sids
|
||||
self.db.statement(sql, now=time.time())
|
||||
self.db.statement(sql2)
|
||||
if self.newCardOrder == NEW_CARDS_RANDOM:
|
||||
# we need to re-randomize now
|
||||
self.randomizeNewCards(ids)
|
||||
|
@ -1004,19 +1010,17 @@ where id in %s""" % ids2str(ids), now=time.time(), new=0)
|
|||
self.db.statements("""
|
||||
update cards
|
||||
set due = :rand + ordinal,
|
||||
combinedDue = :rand + ordinal,
|
||||
modified = :now
|
||||
where factId = :fid
|
||||
and relativeDelay = 2""", data)
|
||||
and type = 2""", data)
|
||||
|
||||
def orderNewCards(self):
|
||||
"Set 'due' to card creation time."
|
||||
self.db.statement("""
|
||||
update cards set
|
||||
due = created,
|
||||
combinedDue = created,
|
||||
modified = :now
|
||||
where relativeDelay = 2""", now=time.time())
|
||||
where type = 2""", now=time.time())
|
||||
|
||||
def rescheduleCards(self, ids, min, max):
|
||||
"Reset cards and schedule with new interval in days (min, max)."
|
||||
|
@ -1034,14 +1038,12 @@ where relativeDelay = 2""", now=time.time())
|
|||
update cards set
|
||||
interval = :int,
|
||||
due = :due,
|
||||
combinedDue = :due,
|
||||
reps = 1,
|
||||
successive = 1,
|
||||
yesCount = 1,
|
||||
firstAnswered = :t,
|
||||
queue = 1,
|
||||
type = 1,
|
||||
relativeDelay = 1,
|
||||
isDue = 0
|
||||
where id = :id""", vals)
|
||||
self.flushMod()
|
||||
|
||||
|
@ -1079,12 +1081,12 @@ This may be in the past if the deck is not finished.
|
|||
If the deck has no (enabled) cards, return None.
|
||||
Ignore new cards."""
|
||||
earliestRev = self.db.scalar(self.cardLimit("revActive", "revInactive", """
|
||||
select combinedDue from cards c where type = 1
|
||||
order by combinedDue
|
||||
select due from cards c where queue = 1
|
||||
order by due
|
||||
limit 1"""))
|
||||
earliestFail = self.db.scalar(self.cardLimit("revActive", "revInactive", """
|
||||
select combinedDue+%d from cards c where type = 0
|
||||
order by combinedDue
|
||||
select due+%d from cards c where queue = 0
|
||||
order by due
|
||||
limit 1""" % self.delay0))
|
||||
if earliestRev and earliestFail:
|
||||
return min(earliestRev, earliestFail)
|
||||
|
@ -1107,16 +1109,16 @@ limit 1""" % self.delay0))
|
|||
return self.db.scalar(
|
||||
self.cardLimit(
|
||||
"revActive", "revInactive",
|
||||
"select count(*) from cards c where type between 0 and 1 "
|
||||
"and combinedDue < :lim"), lim=time)
|
||||
"select count(*) from cards c where queue between 0 and 1 "
|
||||
"and due < :lim"), lim=time)
|
||||
|
||||
def newCardsDueBy(self, time):
|
||||
"Number of new cards due at TIME."
|
||||
return self.db.scalar(
|
||||
self.cardLimit(
|
||||
"newActive", "newInactive",
|
||||
"select count(*) from cards c where type = 2 "
|
||||
"and combinedDue < :lim"), lim=time)
|
||||
"select count(*) from cards c where queue = 2 "
|
||||
"and due < :lim"), lim=time)
|
||||
|
||||
def deckFinishedMsg(self):
|
||||
spaceSusp = ""
|
||||
|
@ -1151,9 +1153,8 @@ limit 1""" % self.delay0))
|
|||
self.startProgress()
|
||||
self.db.statement("""
|
||||
update cards
|
||||
set type = relativeDelay - 3,
|
||||
modified = :t
|
||||
where type >= 0 and id in %s""" % ids2str(ids), t=time.time())
|
||||
set queue = -1, modified = :t
|
||||
where id in %s""" % ids2str(ids), t=time.time())
|
||||
self.flushMod()
|
||||
self.finishProgress()
|
||||
|
||||
|
@ -1161,8 +1162,8 @@ where type >= 0 and id in %s""" % ids2str(ids), t=time.time())
|
|||
"Unsuspend cards. Caller must .reset()"
|
||||
self.startProgress()
|
||||
self.db.statement("""
|
||||
update cards set type = relativeDelay, modified=:t
|
||||
where type between -3 and -1 and id in %s""" %
|
||||
update cards set queue = type, modified=:t
|
||||
where queue = -1 and id in %s""" %
|
||||
ids2str(ids), t=time.time())
|
||||
self.flushMod()
|
||||
self.finishProgress()
|
||||
|
@ -1170,8 +1171,8 @@ where type between -3 and -1 and id in %s""" %
|
|||
def buryFact(self, fact):
|
||||
"Bury all cards for fact until next session. Caller must .reset()"
|
||||
for card in fact.cards:
|
||||
if card.type in (0,1,2):
|
||||
card.type += 3
|
||||
if card.queue in (0,1,2):
|
||||
card.queue = -2
|
||||
self.flushMod()
|
||||
|
||||
# Counts
|
||||
|
@ -1180,14 +1181,16 @@ where type between -3 and -1 and id in %s""" %
|
|||
def hiddenCards(self):
|
||||
"Assumes queue finished. True if some due cards have not been shown."
|
||||
return self.db.scalar("""
|
||||
select 1 from cards where combinedDue < :now
|
||||
and type between 0 and 1 limit 1""", now=self.dueCutoff)
|
||||
select 1 from cards where due < :now
|
||||
and queue between 0 and 1 limit 1""", now=self.dueCutoff)
|
||||
|
||||
def spacedCardCount(self):
|
||||
"Number of spaced cards."
|
||||
print "spacedCardCount"
|
||||
return 0
|
||||
return self.db.scalar("""
|
||||
select count(cards.id) from cards where
|
||||
combinedDue > :now and due < :now""", now=time.time())
|
||||
due > :now and due < :now""", now=time.time())
|
||||
|
||||
def isEmpty(self):
|
||||
return not self.cardCount
|
||||
|
@ -1205,11 +1208,11 @@ combinedDue > :now and due < :now""", now=time.time())
|
|||
def newCountAll(self):
|
||||
"All new cards, including spaced."
|
||||
return self.db.scalar(
|
||||
"select count(id) from cards where relativeDelay = 2")
|
||||
"select count(id) from cards where type = 2")
|
||||
|
||||
def seenCardCount(self):
|
||||
return self.db.scalar(
|
||||
"select count(id) from cards where relativeDelay between 0 and 1")
|
||||
"select count(id) from cards where type between 0 and 1")
|
||||
|
||||
# Card predicates
|
||||
##########################################################################
|
||||
|
@ -1225,10 +1228,6 @@ combinedDue > :now and due < :now""", now=time.time())
|
|||
"True if a card has never been seen before."
|
||||
return card.reps == 0
|
||||
|
||||
def cardIsBeingLearnt(self, card):
|
||||
"True if card should use present intervals."
|
||||
return card.lastInterval < 7
|
||||
|
||||
def cardIsYoung(self, card):
|
||||
"True if card is not new and not mature."
|
||||
return (not self.cardIsNew(card) and
|
||||
|
@ -1290,7 +1289,6 @@ combinedDue > :now and due < :now""", now=time.time())
|
|||
card = anki.cards.Card(fact, cardModel, created)
|
||||
if isRandom:
|
||||
card.due = due
|
||||
card.combinedDue = due
|
||||
self.flushMod()
|
||||
cards.append(card)
|
||||
# update card q/a
|
||||
|
@ -1981,6 +1979,13 @@ and cards.factId = facts.id""")
|
|||
select id, tags from facts
|
||||
where id in %s""" % ids2str(ids))
|
||||
|
||||
def cardHasTag(self, card, tag):
|
||||
id = tagId(self.db, tag, create=False)
|
||||
if id:
|
||||
return self.db.scalar(
|
||||
"select 1 from cardTags where cardId = :c and tagId = :t",
|
||||
c=card.id, t=id)
|
||||
|
||||
# Tags: caching
|
||||
##########################################################################
|
||||
|
||||
|
@ -2542,20 +2547,21 @@ select cardId from cardTags where cardTags.tagId in %s""" % ids2str(ids)
|
|||
n = 0
|
||||
qquery += "select id from cards where type = %d" % n
|
||||
elif token == "delayed":
|
||||
print "delayed"
|
||||
qquery += ("select id from cards where "
|
||||
"due < %d and combinedDue > %d and "
|
||||
"due < %d and due > %d and "
|
||||
"type in (0,1,2)") % (
|
||||
self.dueCutoff, self.dueCutoff)
|
||||
elif token == "suspended":
|
||||
qquery += ("select id from cards where "
|
||||
"type between -3 and -1")
|
||||
"queue = -1")
|
||||
elif token == "leech":
|
||||
qquery += (
|
||||
"select id from cards where noCount >= (select value "
|
||||
"from deckvars where key = 'leechFails')")
|
||||
else: # due
|
||||
qquery += ("select id from cards where "
|
||||
"type in (0,1) and combinedDue < %d") % self.dueCutoff
|
||||
"queue between 0 and 1 and due < %d") % self.dueCutoff
|
||||
elif type == SEARCH_FID:
|
||||
if fidquery:
|
||||
if isNeg:
|
||||
|
@ -3436,17 +3442,15 @@ seq > :s and seq <= :e order by seq desc""", s=start, e=end)
|
|||
def updateDynamicIndices(self):
|
||||
indices = {
|
||||
'intervalDesc':
|
||||
'(type, interval desc, factId, combinedDue)',
|
||||
'(queue, interval desc, factId, due)',
|
||||
'intervalAsc':
|
||||
'(type, interval, factId, combinedDue)',
|
||||
'(queue, interval, factId, due)',
|
||||
'randomOrder':
|
||||
'(type, factId, ordinal, combinedDue)',
|
||||
# new cards are sorted by due, not combinedDue, so that even if
|
||||
# they are spaced, they retain their original sort order
|
||||
'(queue, factId, ordinal, due)',
|
||||
'dueAsc':
|
||||
'(type, due, factId, combinedDue)',
|
||||
'(queue, position, factId, due)',
|
||||
'dueDesc':
|
||||
'(type, due desc, factId, combinedDue)',
|
||||
'(queue, position desc, factId, due)',
|
||||
}
|
||||
# determine required
|
||||
required = []
|
||||
|
@ -3532,7 +3536,7 @@ class DeckStorage(object):
|
|||
metadata.create_all(engine)
|
||||
deck = DeckStorage._init(s)
|
||||
else:
|
||||
ver = upgradeSchema(s)
|
||||
ver = upgradeSchema(engine, s)
|
||||
# add any possibly new tables if we're upgrading
|
||||
if ver < DECK_VERSION:
|
||||
metadata.create_all(engine)
|
||||
|
@ -3601,7 +3605,7 @@ class DeckStorage(object):
|
|||
deck.delay1 = 0
|
||||
# unsuspend buried/rev early
|
||||
deck.db.statement(
|
||||
"update cards set type = relativeDelay where type > 2")
|
||||
"update cards set queue = type where queue between -3 and -2")
|
||||
deck.db.commit()
|
||||
# check if deck has been moved, and disable syncing
|
||||
deck.checkSyncHash()
|
||||
|
|
|
@ -94,38 +94,7 @@ class AnkiExporter(Exporter):
|
|||
res = server.applyPayload(payload)
|
||||
if not self.includeSchedulingInfo:
|
||||
self.deck.updateProgress()
|
||||
self.newDeck.db.statement("""
|
||||
delete from revlog""")
|
||||
self.newDeck.db.statement("""
|
||||
update cards set
|
||||
interval = 0,
|
||||
lastInterval = 0,
|
||||
due = created,
|
||||
lastDue = 0,
|
||||
factor = 2.5,
|
||||
firstAnswered = 0,
|
||||
reps = 0,
|
||||
successive = 0,
|
||||
averageTime = 0,
|
||||
reviewTime = 0,
|
||||
youngEase0 = 0,
|
||||
youngEase1 = 0,
|
||||
youngEase2 = 0,
|
||||
youngEase3 = 0,
|
||||
youngEase4 = 0,
|
||||
matureEase0 = 0,
|
||||
matureEase1 = 0,
|
||||
matureEase2 = 0,
|
||||
matureEase3 = 0,
|
||||
matureEase4 = 0,
|
||||
yesCount = 0,
|
||||
noCount = 0,
|
||||
spaceUntil = 0,
|
||||
type = 2,
|
||||
relativeDelay = 2,
|
||||
combinedDue = created,
|
||||
modified = :now
|
||||
""", now=time.time())
|
||||
self.newDeck.resetCards()
|
||||
# media
|
||||
if self.includeMedia:
|
||||
server.deck.mediaPrefix = ""
|
||||
|
|
|
@ -65,11 +65,11 @@ class DeckGraphs(object):
|
|||
self.endOfDay = self.deck.failedCutoff
|
||||
t = time.time()
|
||||
young = """
|
||||
select interval, combinedDue from cards c
|
||||
where relativeDelay between 0 and 1 and type >= 0 and interval <= 21"""
|
||||
select interval, due from cards c
|
||||
where queue between 0 and 1 and interval <= 21"""
|
||||
mature = """
|
||||
select interval, combinedDue
|
||||
from cards c where relativeDelay = 1 and type >= 0 and interval > 21"""
|
||||
select interval, due
|
||||
from cards c where queue = 1 and interval > 21"""
|
||||
if self.selective:
|
||||
young = self.deck._cardLimit("revActive", "revInactive",
|
||||
young)
|
||||
|
@ -238,8 +238,13 @@ group by day order by day
|
|||
days = {}
|
||||
fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
|
||||
limit = self.endOfDay - (numdays) * 86400
|
||||
if attr == "created":
|
||||
res = self.deck.db.column0("select %s from cards where %s >= %f" %
|
||||
(attr, attr, limit))
|
||||
else:
|
||||
# firstAnswered
|
||||
res = self.deck.db.column0(
|
||||
"select time from revlog where rep = 1")
|
||||
for r in res:
|
||||
d = int((r - self.endOfDay) / 86400.0)
|
||||
days[d] = days.get(d, 0) + 1
|
||||
|
|
|
@ -307,7 +307,7 @@ where factId in (%s)""" % ",".join([str(s) for s in factIds]))
|
|||
else:
|
||||
t = 2
|
||||
data['type'] = t
|
||||
data['relativeDelay'] = t
|
||||
data['queue'] = t
|
||||
return data
|
||||
|
||||
def stripInvalid(self, cards):
|
||||
|
|
|
@ -28,5 +28,5 @@ insert into revlog values (
|
|||
:userTime, :flags)""",
|
||||
created=time.time(), cardId=card.id, ease=ease, rep=card.reps,
|
||||
lastInterval=card.lastInterval, interval=card.interval,
|
||||
factor=card.factor, userTime=card.thinkingTime(),
|
||||
factor=card.factor, userTime=card.userTime(),
|
||||
flags=flags)
|
||||
|
|
|
@ -24,11 +24,13 @@ class CardStats(object):
|
|||
fmtFloat = anki.utils.fmtFloat
|
||||
self.txt = "<table>"
|
||||
self.addLine(_("Added"), self.strTime(c.created))
|
||||
if c.firstAnswered:
|
||||
self.addLine(_("First Review"), self.strTime(c.firstAnswered))
|
||||
first = self.deck.db.scalar(
|
||||
"select time from revlog where rep = 1 and cardId = :id", id=c.id)
|
||||
if first:
|
||||
self.addLine(_("First Review"), self.strTime(first))
|
||||
self.addLine(_("Changed"), self.strTime(c.modified))
|
||||
if c.reps:
|
||||
next = time.time() - c.combinedDue
|
||||
next = time.time() - c.due
|
||||
if next > 0:
|
||||
next = _("%s ago") % fmt(next)
|
||||
else:
|
||||
|
@ -36,20 +38,14 @@ class CardStats(object):
|
|||
self.addLine(_("Due"), next)
|
||||
self.addLine(_("Interval"), fmt(c.interval * 86400))
|
||||
self.addLine(_("Ease"), fmtFloat(c.factor, point=2))
|
||||
if c.lastDue:
|
||||
last = _("%s ago") % fmt(time.time() - c.lastDue)
|
||||
self.addLine(_("Last Due"), last)
|
||||
if c.interval != c.lastInterval:
|
||||
# don't show the last interval if it hasn't been updated yet
|
||||
self.addLine(_("Last Interval"), fmt(c.lastInterval * 86400))
|
||||
self.addLine(_("Last Ease"), fmtFloat(c.lastFactor, point=2))
|
||||
if c.reps:
|
||||
self.addLine(_("Reviews"), "%d/%d (s=%d)" % (
|
||||
c.yesCount, c.reps, c.successive))
|
||||
avg = fmt(c.averageTime, point=2)
|
||||
self.addLine(_("Average Time"),avg)
|
||||
total = fmt(c.reviewTime, point=2)
|
||||
self.addLine(_("Total Time"), total)
|
||||
c.reps-c.lapses, c.reps, c.successive))
|
||||
(cnt, total) = self.deck.db.first(
|
||||
"select count(), sum(userTime) from revlog where cardId = :id", id=c.id)
|
||||
if cnt:
|
||||
self.addLine(_("Average Time"), fmt(total / float(cnt), point=2))
|
||||
self.addLine(_("Total Time"), fmt(total, point=2))
|
||||
self.addLine(_("Model Tags"), c.fact.model.tags)
|
||||
self.addLine(_("Card Template") + " "*5, c.cardModel.name)
|
||||
self.txt += "</table>"
|
||||
|
@ -244,10 +240,10 @@ class DeckStats(object):
|
|||
return (all, yes, yes/float(all)*100)
|
||||
|
||||
def getYoungCorrect(self):
|
||||
return self.getMatureCorrect("lastInterval <= 21 and reps != 1")
|
||||
return self.getMatureCorrect("lastInterval <= 21 and rep != 1")
|
||||
|
||||
def getNewCorrect(self):
|
||||
return self.getMatureCorrect("reps = 1")
|
||||
return self.getMatureCorrect("rep = 1")
|
||||
|
||||
def getDaysReviewed(self, start, finish):
|
||||
today = self.deck.failedCutoff
|
||||
|
@ -308,14 +304,14 @@ where time >= :x and time <= :y""",x=x,y=y, off=self.deck.utcOffset)
|
|||
return self.deck.db.scalar(
|
||||
"select sum(1/round(max(interval, 1)+0.5)) from cards "
|
||||
"where cards.reps > 0 "
|
||||
"and type >= 0") or 0
|
||||
"and queue != -1") or 0
|
||||
|
||||
def getWorkloadPeriod(self, period):
|
||||
cutoff = time.time() + 86400 * period
|
||||
return (self.deck.db.scalar("""
|
||||
select count(id) from cards
|
||||
where combinedDue < :cutoff
|
||||
and type >= 0 and relativeDelay in (0,1)""", cutoff=cutoff) or 0) / float(period)
|
||||
where due < :cutoff
|
||||
and queue != -1 and type between 0 and 1""", cutoff=cutoff) or 0) / float(period)
|
||||
|
||||
def getPastWorkloadPeriod(self, period):
|
||||
cutoff = time.time() - 86400 * period
|
||||
|
|
155
anki/upgrade.py
155
anki/upgrade.py
|
@ -2,12 +2,13 @@
|
|||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
||||
|
||||
DECK_VERSION = 74
|
||||
DECK_VERSION = 75
|
||||
|
||||
from anki.db import *
|
||||
from anki.lang import _
|
||||
from anki.media import rebuildMediaDir
|
||||
|
||||
def upgradeSchema(s):
|
||||
def upgradeSchema(engine, s):
|
||||
"Alter tables prior to ORM initialization."
|
||||
ver = s.scalar("select version from decks limit 1")
|
||||
# add a checksum to fields
|
||||
|
@ -18,19 +19,36 @@ def upgradeSchema(s):
|
|||
"not null default ''")
|
||||
except:
|
||||
pass
|
||||
if ver < 75:
|
||||
# copy cards into new temporary table
|
||||
sql = s.scalar(
|
||||
"select sql from sqlite_master where name = 'cards'")
|
||||
sql = sql.replace("TABLE cards", "temporary table cards2")
|
||||
s.execute(sql)
|
||||
s.execute("insert into cards2 select * from cards")
|
||||
# drop the old cards table and create the new one
|
||||
s.execute("drop table cards")
|
||||
import cards
|
||||
metadata.create_all(engine, tables=[cards.cardsTable])
|
||||
# move data across and delete temp table
|
||||
s.execute("""
|
||||
insert into cards select id, factId, cardModelId, created, modified,
|
||||
question, answer, 0, ordinal, 0, relativeDelay, type, lastInterval, interval,
|
||||
due, factor, reps, successive, noCount from cards2""")
|
||||
s.execute("drop table cards2")
|
||||
return ver
|
||||
|
||||
def updateIndices(deck):
|
||||
"Add indices to the DB."
|
||||
# counts, failed cards
|
||||
# due counts, failed card queue
|
||||
deck.db.statement("""
|
||||
create index if not exists ix_cards_typeCombined on cards
|
||||
(type, combinedDue, factId)""")
|
||||
# scheduler-agnostic type
|
||||
create index if not exists ix_cards_queueDue on cards
|
||||
(queue, due, factId)""")
|
||||
# counting cards of a given type
|
||||
deck.db.statement("""
|
||||
create index if not exists ix_cards_relativeDelay on cards
|
||||
(relativeDelay)""")
|
||||
# index on modified, to speed up sync summaries
|
||||
create index if not exists ix_cards_type on cards
|
||||
(type)""")
|
||||
# sync summaries
|
||||
deck.db.statement("""
|
||||
create index if not exists ix_cards_modified on cards
|
||||
(modified)""")
|
||||
|
@ -88,114 +106,8 @@ def upgradeDeck(deck):
|
|||
oldmod = deck.modified
|
||||
else:
|
||||
prog = False
|
||||
if deck.version < 43:
|
||||
raise Exception("oldDeckVersion")
|
||||
if deck.version < 44:
|
||||
# leaner indices
|
||||
deck.db.statement("drop index if exists ix_cards_factId")
|
||||
deck.version = 44
|
||||
deck.db.commit()
|
||||
if deck.version < 48:
|
||||
deck.updateFieldCache(deck.db.column0("select id from facts"))
|
||||
deck.version = 48
|
||||
deck.db.commit()
|
||||
if deck.version < 52:
|
||||
dname = deck.name()
|
||||
sname = deck.syncName
|
||||
if sname and dname != sname:
|
||||
deck.notify(_("""\
|
||||
When syncing, Anki now uses the same deck name on the server as the deck \
|
||||
name on your computer. Because you had '%(dname)s' set to sync to \
|
||||
'%(sname)s' on the server, syncing has been temporarily disabled.
|
||||
|
||||
If you want to keep your changes to the online version, please use \
|
||||
File>Download>Personal Deck to download the online version.
|
||||
|
||||
If you want to keep the version on your computer, please enable \
|
||||
syncing again via Settings>Deck Properties>Synchronisation.
|
||||
|
||||
If you have syncing disabled in the preferences, you can ignore \
|
||||
this message. (ERR-0101)""") % {
|
||||
'sname':sname, 'dname':dname})
|
||||
deck.disableSyncing()
|
||||
elif sname:
|
||||
deck.enableSyncing()
|
||||
deck.version = 52
|
||||
deck.db.commit()
|
||||
if deck.version < 53:
|
||||
if deck.getBool("perDay"):
|
||||
if deck.hardIntervalMin == 0.333:
|
||||
deck.hardIntervalMin = max(1.0, deck.hardIntervalMin)
|
||||
deck.hardIntervalMax = max(1.1, deck.hardIntervalMax)
|
||||
deck.version = 53
|
||||
deck.db.commit()
|
||||
if deck.version < 54:
|
||||
# broken versions of the DB orm die if this is a bool with a
|
||||
# non-int value
|
||||
deck.db.statement("update fieldModels set editFontFamily = 1");
|
||||
deck.version = 54
|
||||
deck.db.commit()
|
||||
if deck.version < 61:
|
||||
# do our best to upgrade templates to the new style
|
||||
txt = '''\
|
||||
<span style="font-family: %s; font-size: %spx; color: %s; white-space: pre-wrap;">%s</span>'''
|
||||
for m in deck.models:
|
||||
unstyled = []
|
||||
for fm in m.fieldModels:
|
||||
# find which fields had explicit formatting
|
||||
if fm.quizFontFamily or fm.quizFontSize or fm.quizFontColour:
|
||||
pass
|
||||
else:
|
||||
unstyled.append(fm.name)
|
||||
# fill out missing info
|
||||
fm.quizFontFamily = fm.quizFontFamily or u"Arial"
|
||||
fm.quizFontSize = fm.quizFontSize or 20
|
||||
fm.quizFontColour = fm.quizFontColour or "#000000"
|
||||
fm.editFontSize = fm.editFontSize or 20
|
||||
unstyled = set(unstyled)
|
||||
for cm in m.cardModels:
|
||||
# embed the old font information into card templates
|
||||
cm.qformat = txt % (
|
||||
cm.questionFontFamily,
|
||||
cm.questionFontSize,
|
||||
cm.questionFontColour,
|
||||
cm.qformat)
|
||||
cm.aformat = txt % (
|
||||
cm.answerFontFamily,
|
||||
cm.answerFontSize,
|
||||
cm.answerFontColour,
|
||||
cm.aformat)
|
||||
# escape fields that had no previous styling
|
||||
for un in unstyled:
|
||||
cm.qformat = cm.qformat.replace("%("+un+")s", "{{{%s}}}"%un)
|
||||
cm.aformat = cm.aformat.replace("%("+un+")s", "{{{%s}}}"%un)
|
||||
# rebuild q/a for the above & because latex has changed
|
||||
for m in deck.models:
|
||||
deck.updateCardsFromModel(m, dirty=False)
|
||||
# rebuild the media db based on new format
|
||||
rebuildMediaDir(deck, dirty=False)
|
||||
deck.version = 61
|
||||
deck.db.commit()
|
||||
if deck.version < 62:
|
||||
# updated indices
|
||||
deck.db.statement("drop index if exists ix_cards_typeCombined")
|
||||
updateIndices(deck)
|
||||
deck.version = 62
|
||||
deck.db.commit()
|
||||
if deck.version < 64:
|
||||
# remove old static indices, as all clients should be libanki1.2+
|
||||
for d in ("ix_cards_duePriority",
|
||||
"ix_cards_priorityDue"):
|
||||
deck.db.statement("drop index if exists %s" % d)
|
||||
deck.version = 64
|
||||
deck.db.commit()
|
||||
# note: we keep the priority index for now
|
||||
if deck.version < 65:
|
||||
# we weren't correctly setting relativeDelay when answering cards
|
||||
# in previous versions, so ensure everything is set correctly
|
||||
deck.rebuildTypes()
|
||||
deck.version = 65
|
||||
deck.db.commit()
|
||||
raise Exception("oldDeckVersion")
|
||||
# skip a few to allow for updates to stable tree
|
||||
if deck.version < 70:
|
||||
# update dynamic indices given we don't use priority anymore
|
||||
|
@ -228,7 +140,6 @@ this message. (ERR-0101)""") % {
|
|||
deck.db.commit()
|
||||
if deck.version < 73:
|
||||
# remove stats, as it's all in the revlog now
|
||||
deck.db.statement("drop index if exists ix_stats_typeDay")
|
||||
deck.db.statement("drop table if exists stats")
|
||||
deck.version = 73
|
||||
deck.db.commit()
|
||||
|
@ -245,7 +156,15 @@ min(thinkingTime, 60), 0 from reviewHistory""")
|
|||
deck.db.statement("drop index if exists ix_cards_priority")
|
||||
deck.version = 74
|
||||
deck.db.commit()
|
||||
|
||||
if deck.version < 75:
|
||||
# suspended cards don't use ranges anymore
|
||||
deck.db.execute("update cards set queue=-1 where queue between -3 and -1")
|
||||
deck.db.execute("update cards set queue=-2 where queue between 3 and 5")
|
||||
deck.db.execute("update cards set queue=-3 where queue between 6 and 8")
|
||||
# new indices for new cards table
|
||||
updateIndices(deck)
|
||||
deck.version = 75
|
||||
deck.db.commit()
|
||||
|
||||
# executing a pragma here is very slow on large decks, so we store
|
||||
# our own record
|
||||
|
|
Loading…
Reference in a new issue