mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 06:52:21 -04:00
support 4am rollover
This commit is contained in:
parent
23379cd600
commit
e25feed0af
4 changed files with 46 additions and 20 deletions
38
anki/deck.py
38
anki/deck.py
|
@ -9,7 +9,6 @@ The Deck
|
||||||
__docformat__ = 'restructuredtext'
|
__docformat__ = 'restructuredtext'
|
||||||
|
|
||||||
import tempfile, time, os, random, sys, re, stat, shutil, types
|
import tempfile, time, os, random, sys, re, stat, shutil, types
|
||||||
from heapq import heapify, heappush, heappop
|
|
||||||
|
|
||||||
from anki.db import *
|
from anki.db import *
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
@ -18,7 +17,7 @@ from anki.stdmodels import BasicModel
|
||||||
from anki.utils import parseTags, tidyHTML, genID, ids2str
|
from anki.utils import parseTags, tidyHTML, genID, ids2str
|
||||||
from anki.history import CardHistoryEntry
|
from anki.history import CardHistoryEntry
|
||||||
from anki.models import Model, CardModel
|
from anki.models import Model, CardModel
|
||||||
from anki.stats import dailyStats, globalStats
|
from anki.stats import dailyStats, globalStats, genToday
|
||||||
|
|
||||||
# ensure all the metadata in other files is loaded before proceeding
|
# ensure all the metadata in other files is loaded before proceeding
|
||||||
import anki.models, anki.facts, anki.cards, anki.stats
|
import anki.models, anki.facts, anki.cards, anki.stats
|
||||||
|
@ -42,7 +41,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=10),
|
Column('version', Integer, nullable=False, default=11),
|
||||||
Column('currentModelId', Integer, ForeignKey("models.id")),
|
Column('currentModelId', Integer, ForeignKey("models.id")),
|
||||||
# syncing
|
# syncing
|
||||||
Column('syncName', UnicodeText),
|
Column('syncName', UnicodeText),
|
||||||
|
@ -77,7 +76,9 @@ decksTable = Table(
|
||||||
Column('newCardsPerDay', Integer, nullable=False, default=20),
|
Column('newCardsPerDay', Integer, nullable=False, default=20),
|
||||||
# currently unused
|
# currently unused
|
||||||
Column('sessionRepLimit', Integer, nullable=False, default=100),
|
Column('sessionRepLimit', Integer, nullable=False, default=100),
|
||||||
Column('sessionTimeLimit', Integer, nullable=False, default=1800))
|
Column('sessionTimeLimit', Integer, nullable=False, default=1800),
|
||||||
|
# stats offset
|
||||||
|
Column('utcOffset', Float, nullable=False, default=0))
|
||||||
|
|
||||||
class Deck(object):
|
class Deck(object):
|
||||||
"Top-level object. Manages facts, cards and scheduling information."
|
"Top-level object. Manages facts, cards and scheduling information."
|
||||||
|
@ -220,6 +221,7 @@ Caller is responsible for ensuring cards are not spaced."""
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def answerCard(self, card, ease):
|
def answerCard(self, card, ease):
|
||||||
|
self.checkDailyStats()
|
||||||
now = time.time()
|
now = time.time()
|
||||||
oldState = self.cardState(card)
|
oldState = self.cardState(card)
|
||||||
lastDelay = max(0, (time.time() - card.due) / 86400.0)
|
lastDelay = max(0, (time.time() - card.due) / 86400.0)
|
||||||
|
@ -303,8 +305,8 @@ where isDue = 1""")
|
||||||
self.updateRelativeDelays()
|
self.updateRelativeDelays()
|
||||||
self.markExpiredCardsDue()
|
self.markExpiredCardsDue()
|
||||||
# cache global/daily stats
|
# cache global/daily stats
|
||||||
self._globalStats = globalStats(self.s)
|
self._globalStats = globalStats(self)
|
||||||
self._dailyStats = dailyStats(self.s)
|
self._dailyStats = dailyStats(self)
|
||||||
# invalid card count
|
# invalid card count
|
||||||
self._countsDirty = True
|
self._countsDirty = True
|
||||||
# determine new card distribution
|
# determine new card distribution
|
||||||
|
@ -322,6 +324,11 @@ where isDue = 1""")
|
||||||
"select avg(factor) from cards where reps > 0")
|
"select avg(factor) from cards where reps > 0")
|
||||||
or Deck.initialFactor)
|
or Deck.initialFactor)
|
||||||
|
|
||||||
|
def checkDailyStats(self):
|
||||||
|
# check if the day has rolled over
|
||||||
|
if genToday(self) != self._dailyStats.day:
|
||||||
|
self._dailyStats = dailyStats(self)
|
||||||
|
|
||||||
# Interval management
|
# Interval management
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -1487,15 +1494,19 @@ class DeckStorage(object):
|
||||||
if create:
|
if create:
|
||||||
deck = DeckStorage._init(s)
|
deck = DeckStorage._init(s)
|
||||||
else:
|
else:
|
||||||
if s.scalar("select version from decks limit 1") < 5:
|
ver = s.scalar("select version from decks limit 1")
|
||||||
# add missing deck field
|
if ver < 5:
|
||||||
|
# add missing deck fields
|
||||||
s.execute("""
|
s.execute("""
|
||||||
alter table decks add column newCardsPerDay integer not null default 20""")
|
alter table decks add column newCardsPerDay integer not null default 20""")
|
||||||
if s.scalar("select version from decks limit 1") < 6:
|
if ver < 6:
|
||||||
s.execute("""
|
s.execute("""
|
||||||
alter table decks add column sessionRepLimit integer not null default 100""")
|
alter table decks add column sessionRepLimit integer not null default 100""")
|
||||||
s.execute("""
|
s.execute("""
|
||||||
alter table decks add column sessionTimeLimit integer not null default 1800""")
|
alter table decks add column sessionTimeLimit integer not null default 1800""")
|
||||||
|
if ver < 11:
|
||||||
|
s.execute("""
|
||||||
|
alter table decks add column utcOffset numeric(10, 2) not null default 0""")
|
||||||
deck = s.query(Deck).get(1)
|
deck = s.query(Deck).get(1)
|
||||||
# attach db vars
|
# attach db vars
|
||||||
deck.path = path
|
deck.path = path
|
||||||
|
@ -1793,9 +1804,18 @@ insert into media values (
|
||||||
alter table models add column source integer not null default 0""")
|
alter table models add column source integer not null default 0""")
|
||||||
deck.version = 10
|
deck.version = 10
|
||||||
deck.s.commit()
|
deck.s.commit()
|
||||||
|
if deck.version < 11:
|
||||||
|
DeckStorage._setUTCOffset(deck)
|
||||||
|
deck.version = 11
|
||||||
|
deck.s.commit()
|
||||||
return deck
|
return deck
|
||||||
_upgradeDeck = staticmethod(_upgradeDeck)
|
_upgradeDeck = staticmethod(_upgradeDeck)
|
||||||
|
|
||||||
|
def _setUTCOffset(deck):
|
||||||
|
# 4am
|
||||||
|
deck.utcOffset = time.timezone + 60*60*4
|
||||||
|
_setUTCOffset = staticmethod(_setUTCOffset)
|
||||||
|
|
||||||
def backup(modified, path):
|
def backup(modified, path):
|
||||||
# need a non-unicode path
|
# need a non-unicode path
|
||||||
path = path.encode(sys.getfilesystemencoding())
|
path = path.encode(sys.getfilesystemencoding())
|
||||||
|
|
|
@ -25,7 +25,7 @@ statsTable = Table(
|
||||||
'stats', metadata,
|
'stats', metadata,
|
||||||
Column('id', Integer, primary_key=True),
|
Column('id', Integer, primary_key=True),
|
||||||
Column('type', Integer, nullable=False),
|
Column('type', Integer, nullable=False),
|
||||||
Column('day', Date, nullable=False, default=date.today),
|
Column('day', Date, nullable=False),
|
||||||
Column('reps', Integer, nullable=False, default=0),
|
Column('reps', Integer, nullable=False, default=0),
|
||||||
Column('averageTime', Float, nullable=False, default=0),
|
Column('averageTime', Float, nullable=False, default=0),
|
||||||
Column('reviewTime', Float, nullable=False, default=0),
|
Column('reviewTime', Float, nullable=False, default=0),
|
||||||
|
@ -50,7 +50,7 @@ statsTable = Table(
|
||||||
|
|
||||||
class Stats(object):
|
class Stats(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.day = date.today()
|
self.day = None
|
||||||
self.reps = 0
|
self.reps = 0
|
||||||
self.averageTime = 0
|
self.averageTime = 0
|
||||||
self.reviewTime = 0
|
self.reviewTime = 0
|
||||||
|
@ -139,6 +139,10 @@ where id = :id""", self.__dict__)
|
||||||
|
|
||||||
mapper(Stats, statsTable)
|
mapper(Stats, statsTable)
|
||||||
|
|
||||||
|
def genToday(deck):
|
||||||
|
return datetime.datetime.utcfromtimestamp(
|
||||||
|
time.time() - deck.utcOffset).date()
|
||||||
|
|
||||||
def updateAllStats(s, gs, ds, card, ease, oldState):
|
def updateAllStats(s, gs, ds, card, ease, oldState):
|
||||||
"Update global and daily statistics."
|
"Update global and daily statistics."
|
||||||
updateStats(s, gs, card, ease, oldState)
|
updateStats(s, gs, card, ease, oldState)
|
||||||
|
@ -159,9 +163,10 @@ def updateStats(s, stats, card, ease, oldState):
|
||||||
setattr(stats, attr, getattr(stats, attr) + 1)
|
setattr(stats, attr, getattr(stats, attr) + 1)
|
||||||
stats.toDB(s)
|
stats.toDB(s)
|
||||||
|
|
||||||
def globalStats(s):
|
def globalStats(deck):
|
||||||
|
s = deck.s
|
||||||
type = STATS_LIFE
|
type = STATS_LIFE
|
||||||
today = date.today()
|
today = genToday(deck)
|
||||||
id = s.scalar("select id from stats where type = :type",
|
id = s.scalar("select id from stats where type = :type",
|
||||||
type=type)
|
type=type)
|
||||||
stats = Stats()
|
stats = Stats()
|
||||||
|
@ -173,9 +178,10 @@ def globalStats(s):
|
||||||
stats.type = type
|
stats.type = type
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
def dailyStats(s):
|
def dailyStats(deck):
|
||||||
|
s = deck.s
|
||||||
type = STATS_DAY
|
type = STATS_DAY
|
||||||
today = date.today()
|
today = genToday(deck)
|
||||||
id = s.scalar("select id from stats where type = :type and day = :day",
|
id = s.scalar("select id from stats where type = :type and day = :day",
|
||||||
type=type, day=today)
|
type=type, day=today)
|
||||||
stats = Stats()
|
stats = Stats()
|
||||||
|
|
|
@ -30,7 +30,7 @@ from anki.facts import Fact, Field
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.stats import Stats, globalStats
|
from anki.stats import Stats, globalStats
|
||||||
from anki.history import CardHistoryEntry
|
from anki.history import CardHistoryEntry
|
||||||
from anki.stats import dailyStats, globalStats
|
from anki.stats import globalStats
|
||||||
from anki.media import checksum
|
from anki.media import checksum
|
||||||
from anki.utils import ids2str
|
from anki.utils import ids2str
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
@ -152,7 +152,7 @@ class SyncTools(object):
|
||||||
|
|
||||||
def preSyncRefresh(self):
|
def preSyncRefresh(self):
|
||||||
# ensure global stats are available (queue may not be built)
|
# ensure global stats are available (queue may not be built)
|
||||||
self.deck._globalStats = globalStats(self.deck.s)
|
self.deck._globalStats = globalStats(self.deck)
|
||||||
|
|
||||||
def payloadChanges(self, payload):
|
def payloadChanges(self, payload):
|
||||||
h = {
|
h = {
|
||||||
|
@ -538,7 +538,7 @@ values
|
||||||
s['day'] = s['day'].toordinal()
|
s['day'] = s['day'].toordinal()
|
||||||
del s['id']
|
del s['id']
|
||||||
return s
|
return s
|
||||||
lastDay = date.fromtimestamp(self.deck.lastSync)
|
lastDay = date.fromtimestamp(self.deck.lastSync - 60*60*24)
|
||||||
ids = self.deck.s.column0(
|
ids = self.deck.s.column0(
|
||||||
"select id from stats where type = 1 and day >= :day", day=lastDay)
|
"select id from stats where type = 1 and day >= :day", day=lastDay)
|
||||||
stat = Stats()
|
stat = Stats()
|
||||||
|
|
|
@ -101,8 +101,8 @@ def test_localsync_deck():
|
||||||
c = deck1.getCard()
|
c = deck1.getCard()
|
||||||
deck1.answerCard(c, 4)
|
deck1.answerCard(c, 4)
|
||||||
client.sync()
|
client.sync()
|
||||||
assert dailyStats(deck2.s).reps == 1
|
assert dailyStats(deck2).reps == 1
|
||||||
assert globalStats(deck2.s).reps == 1
|
assert globalStats(deck2).reps == 1
|
||||||
assert deck2.s.scalar("select count(id) from reviewHistory") == 1
|
assert deck2.s.scalar("select count(id) from reviewHistory") == 1
|
||||||
|
|
||||||
@nose.with_setup(setup_local, teardown)
|
@nose.with_setup(setup_local, teardown)
|
||||||
|
|
Loading…
Reference in a new issue