diff --git a/anki/deck.py b/anki/deck.py
index bd70bcae3..1cca3b9cc 100644
--- a/anki/deck.py
+++ b/anki/deck.py
@@ -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 _('''
Congratulations!
You have finished the deck for now.
@@ -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)
diff --git a/anki/sync.py b/anki/sync.py
index b73ab9d85..09f56f200 100644
--- a/anki/sync.py
+++ b/anki/sync.py
@@ -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