diff --git a/anki/deck.py b/anki/deck.py index 2e85ead1e..6639cbe75 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -9,7 +9,6 @@ The Deck __docformat__ = 'restructuredtext' import tempfile, time, os, random, sys, re, stat, shutil, types -from heapq import heapify, heappush, heappop from anki.db import * from anki.lang import _ @@ -18,7 +17,7 @@ from anki.stdmodels import BasicModel from anki.utils import parseTags, tidyHTML, genID, ids2str from anki.history import CardHistoryEntry 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 import anki.models, anki.facts, anki.cards, anki.stats @@ -42,7 +41,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=10), + Column('version', Integer, nullable=False, default=11), Column('currentModelId', Integer, ForeignKey("models.id")), # syncing Column('syncName', UnicodeText), @@ -77,7 +76,9 @@ decksTable = Table( Column('newCardsPerDay', Integer, nullable=False, default=20), # currently unused 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): "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): + self.checkDailyStats() now = time.time() oldState = self.cardState(card) lastDelay = max(0, (time.time() - card.due) / 86400.0) @@ -303,8 +305,8 @@ where isDue = 1""") self.updateRelativeDelays() self.markExpiredCardsDue() # cache global/daily stats - self._globalStats = globalStats(self.s) - self._dailyStats = dailyStats(self.s) + self._globalStats = globalStats(self) + self._dailyStats = dailyStats(self) # invalid card count self._countsDirty = True # determine new card distribution @@ -322,6 +324,11 @@ where isDue = 1""") "select avg(factor) from cards where reps > 0") 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 ########################################################################## @@ -1487,15 +1494,19 @@ class DeckStorage(object): if create: deck = DeckStorage._init(s) else: - if s.scalar("select version from decks limit 1") < 5: - # add missing deck field + ver = s.scalar("select version from decks limit 1") + if ver < 5: + # add missing deck fields s.execute(""" 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(""" alter table decks add column sessionRepLimit integer not null default 100""") s.execute(""" 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) # attach db vars deck.path = path @@ -1793,9 +1804,18 @@ insert into media values ( alter table models add column source integer not null default 0""") deck.version = 10 deck.s.commit() + if deck.version < 11: + DeckStorage._setUTCOffset(deck) + deck.version = 11 + deck.s.commit() return deck _upgradeDeck = staticmethod(_upgradeDeck) + def _setUTCOffset(deck): + # 4am + deck.utcOffset = time.timezone + 60*60*4 + _setUTCOffset = staticmethod(_setUTCOffset) + def backup(modified, path): # need a non-unicode path path = path.encode(sys.getfilesystemencoding()) diff --git a/anki/stats.py b/anki/stats.py index a38e59540..253e8bcbf 100644 --- a/anki/stats.py +++ b/anki/stats.py @@ -25,7 +25,7 @@ statsTable = Table( 'stats', metadata, Column('id', Integer, primary_key=True), 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('averageTime', Float, nullable=False, default=0), Column('reviewTime', Float, nullable=False, default=0), @@ -50,7 +50,7 @@ statsTable = Table( class Stats(object): def __init__(self): - self.day = date.today() + self.day = None self.reps = 0 self.averageTime = 0 self.reviewTime = 0 @@ -139,6 +139,10 @@ where id = :id""", self.__dict__) mapper(Stats, statsTable) +def genToday(deck): + return datetime.datetime.utcfromtimestamp( + time.time() - deck.utcOffset).date() + def updateAllStats(s, gs, ds, card, ease, oldState): "Update global and daily statistics." updateStats(s, gs, card, ease, oldState) @@ -159,9 +163,10 @@ def updateStats(s, stats, card, ease, oldState): setattr(stats, attr, getattr(stats, attr) + 1) stats.toDB(s) -def globalStats(s): +def globalStats(deck): + s = deck.s type = STATS_LIFE - today = date.today() + today = genToday(deck) id = s.scalar("select id from stats where type = :type", type=type) stats = Stats() @@ -173,9 +178,10 @@ def globalStats(s): stats.type = type return stats -def dailyStats(s): +def dailyStats(deck): + s = deck.s type = STATS_DAY - today = date.today() + today = genToday(deck) id = s.scalar("select id from stats where type = :type and day = :day", type=type, day=today) stats = Stats() diff --git a/anki/sync.py b/anki/sync.py index 76e16eb73..617c4c993 100644 --- a/anki/sync.py +++ b/anki/sync.py @@ -30,7 +30,7 @@ from anki.facts import Fact, Field from anki.cards import Card from anki.stats import Stats, globalStats from anki.history import CardHistoryEntry -from anki.stats import dailyStats, globalStats +from anki.stats import globalStats from anki.media import checksum from anki.utils import ids2str from anki.lang import _ @@ -152,7 +152,7 @@ class SyncTools(object): def preSyncRefresh(self): # 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): h = { @@ -538,7 +538,7 @@ values s['day'] = s['day'].toordinal() del s['id'] return s - lastDay = date.fromtimestamp(self.deck.lastSync) + lastDay = date.fromtimestamp(self.deck.lastSync - 60*60*24) ids = self.deck.s.column0( "select id from stats where type = 1 and day >= :day", day=lastDay) stat = Stats() diff --git a/tests/test_sync.py b/tests/test_sync.py index bb3bedbbb..b0dfcb8b6 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -101,8 +101,8 @@ def test_localsync_deck(): c = deck1.getCard() deck1.answerCard(c, 4) client.sync() - assert dailyStats(deck2.s).reps == 1 - assert globalStats(deck2.s).reps == 1 + assert dailyStats(deck2).reps == 1 + assert globalStats(deck2).reps == 1 assert deck2.s.scalar("select count(id) from reviewHistory") == 1 @nose.with_setup(setup_local, teardown)