move deck config into json ala models

- limits are stored separately so we can access them quickly when checking
  deck counts
- data is used to store cssCache and hexCache; these may be refactored or go
  away in the future
This commit is contained in:
Damien Elmes 2011-02-28 16:45:12 +09:00
parent b0b4074cbd
commit d34a76d5a0
4 changed files with 110 additions and 61 deletions

View file

@ -51,17 +51,42 @@ SEARCH_FIELD_EXISTS = 7
SEARCH_QA = 8 SEARCH_QA = 8
SEARCH_PHRASE_WB = 9 SEARCH_PHRASE_WB = 9
deckVarsTable = Table( # selective study
'deckVars', metadata, defaultLim = {
Column('key', UnicodeText, nullable=False, primary_key=True), 'newActive': u"",
Column('value', UnicodeText)) 'newInactive': u"",
'revActive': u"",
'revInactive': u"",
}
# scheduling and other options
defaultConf = {
'utcOffset': -2,
'newCardOrder': 1,
'newCardSpacing': NEW_CARDS_DISTRIBUTE,
'newCardsPerDay': 20,
'revCardOrder': 0,
'collapseTime': 600,
'sessionRepLimit': 0,
'sessionTimeLimit': 600,
'suspendLeeches': True,
'leechFails': 16,
'currentModelId': None,
'mediaURL': "",
'latexPre': """\
\\documentclass[12pt]{article}
\\special{papersize=3in,5in}
\\usepackage[utf8]{inputenc}
\\usepackage{amssymb,amsmath}
\\pagestyle{empty}
\\setlength{\\parindent}{0in}
\\begin{document}
""",
'latexPost': "\\end{document}",
}
# syncName: md5sum of current deck location, to detect if deck was moved or # syncName: md5sum of current deck location, to detect if deck was moved or
# renamed. mobile clients can treat this as a simple boolean # renamed. mobile clients can treat this as a simple boolean
# utcOffset: store independent of tz?
# parts of the code assume we only have one deck
deckTable = Table( deckTable = Table(
'deck', metadata, 'deck', metadata,
Column('id', Integer, nullable=False, primary_key=True), Column('id', Integer, nullable=False, primary_key=True),
@ -69,27 +94,14 @@ deckTable = Table(
Column('modified', Float, nullable=False, default=time.time), Column('modified', Float, nullable=False, default=time.time),
Column('schemaMod', Float, nullable=False, default=0), Column('schemaMod', Float, nullable=False, default=0),
Column('version', Integer, nullable=False, default=DECK_VERSION), Column('version', Integer, nullable=False, default=DECK_VERSION),
Column('currentModelId', Integer, ForeignKey("models.id")),
Column('syncName', UnicodeText, nullable=False, default=u""), Column('syncName', UnicodeText, nullable=False, default=u""),
Column('lastSync', Float, nullable=False, default=0), Column('lastSync', Integer, nullable=False, default=0),
# scheduling
Column('utcOffset', Integer, nullable=False, default=-2), Column('utcOffset', Integer, nullable=False, default=-2),
Column('newCardOrder', Integer, nullable=False, default=1), Column('limits', UnicodeText, nullable=False, default=unicode(
Column('newCardSpacing', Integer, nullable=False, default=NEW_CARDS_DISTRIBUTE), simplejson.dumps(defaultLim))),
Column('newCardsPerDay', Integer, nullable=False, default=20), Column('config', UnicodeText, nullable=False, default=unicode(
Column('revCardOrder', Integer, nullable=False, default=0), simplejson.dumps(defaultConf))),
Column('collapseTime', Integer, nullable=False, default=600), Column('data', UnicodeText, nullable=False, default=u"{}")
# timeboxing
Column('sessionRepLimit', Integer, nullable=False, default=0),
Column('sessionTimeLimit', Integer, nullable=False, default=600),
# leeches
Column('suspendLeeches', Boolean, nullable=False, default=True),
Column('leechFails', Integer, nullable=False, default=16),
# selective study
Column('newActive', UnicodeText, nullable=False, default=u""),
Column('newInactive', UnicodeText, nullable=False, default=u""),
Column('revActive', UnicodeText, nullable=False, default=u""),
Column('revInactive', UnicodeText, nullable=False, default=u""),
) )
class Deck(object): class Deck(object):
@ -108,19 +120,6 @@ class Deck(object):
self.sessionStartReps = 0 self.sessionStartReps = 0
self.sessionStartTime = 0 self.sessionStartTime = 0
self.lastSessionStart = 0 self.lastSessionStart = 0
# if most recent deck var not defined, make sure defaults are set
if not self.db.scalar("select 1 from deckVars where key = 'latexPost'"):
self.setVarDefault("mediaURL", "")
self.setVarDefault("latexPre", """\
\\documentclass[12pt]{article}
\\special{papersize=3in,5in}
\\usepackage[utf8]{inputenc}
\\usepackage{amssymb,amsmath}
\\pagestyle{empty}
\\setlength{\\parindent}{0in}
\\begin{document}
""")
self.setVarDefault("latexPost", "\\end{document}")
self.sched = Scheduler(self) self.sched = Scheduler(self)
def modifiedSinceSave(self): def modifiedSinceSave(self):
@ -733,7 +732,7 @@ select id, null, null, null, questionAlign, 0, 0 from cardModels""")
(hexifyID(row[0]), row[1]) for row in self.db.all(""" (hexifyID(row[0]), row[1]) for row in self.db.all("""
select id, lastFontColour from cardModels""")]) select id, lastFontColour from cardModels""")])
self.css = css self.css = css
self.setVar("cssCache", css, mod=False) self.data['cssCache'] = css
self.addHexCache() self.addHexCache()
return css return css
@ -745,7 +744,7 @@ select id from models""")
cache = {} cache = {}
for id in ids: for id in ids:
cache[id] = hexifyID(id) cache[id] = hexifyID(id)
self.setVar("hexCache", simplejson.dumps(cache), mod=False) self.data['hexCache'] = cache
def copyModel(self, oldModel): def copyModel(self, oldModel):
"Add a new model to DB based on MODEL." "Add a new model to DB based on MODEL."
@ -2163,8 +2162,15 @@ Return new path, relative to media dir."""
if self.lastLoaded == self.modified: if self.lastLoaded == self.modified:
return return
self.lastLoaded = self.modified self.lastLoaded = self.modified
self.flushConfig()
self.db.commit() self.db.commit()
def flushConfig(self):
print "make flushConfig() more intelligent"
deck._config = simplejson.dumps(deck.config)
deck._limits = simplejson.dumps(deck.limits)
deck._data = simplejson.dumps(deck.data)
def close(self): def close(self):
if self.db: if self.db:
self.db.rollback() self.db.rollback()
@ -2225,6 +2231,7 @@ Return new path, relative to media dir."""
def saveAs(self, newPath): def saveAs(self, newPath):
"Returns new deck. Old connection is closed without saving." "Returns new deck. Old connection is closed without saving."
oldMediaDir = self.mediaDir() oldMediaDir = self.mediaDir()
self.flushConfig()
self.db.flush() self.db.flush()
# remove new deck if it exists # remove new deck if it exists
try: try:
@ -2648,6 +2655,8 @@ seq > :s and seq <= :e order by seq desc""", s=start, e=end)
########################################################################## ##########################################################################
def updateDynamicIndices(self): def updateDynamicIndices(self):
print "fix dynamicIndices()"
return
indices = { indices = {
'intervalDesc': 'intervalDesc':
'(queue, interval desc, factId, due)', '(queue, interval desc, factId, due)',
@ -2690,7 +2699,11 @@ seq > :s and seq <= :e order by seq desc""", s=start, e=end)
if analyze: if analyze:
self.db.statement("analyze") self.db.statement("analyze")
mapper(Deck, deckTable) mapper(Deck, deckTable, properties={
'_limits': deckTable.c.limits,
'_config': deckTable.c.config,
'_data': deckTable.c.data,
})
# Shared decks # Shared decks
########################################################################## ##########################################################################
@ -2825,10 +2838,14 @@ class DeckStorage(object):
path = os.path.abspath(path) path = os.path.abspath(path)
create = not os.path.exists(path) create = not os.path.exists(path)
deck = DeckStorage._getDeck(path, create, pool) deck = DeckStorage._getDeck(path, create, pool)
deck.limits = simplejson.loads(deck._limits)
if not rebuild: if not rebuild:
# minimal startup # minimal startup
return deck return deck
oldMod = deck.modified oldMod = deck.modified
# setup config
deck.config = simplejson.loads(deck._config)
deck.data = simplejson.loads(deck._data)
# unsuspend buried/rev early # unsuspend buried/rev early
deck.db.statement( deck.db.statement(
"update cards set queue = type where queue between -3 and -2") "update cards set queue = type where queue between -3 and -2")

View file

@ -6,7 +6,8 @@ import time, datetime, simplejson
from heapq import * from heapq import *
from anki.db import * from anki.db import *
from anki.cards import Card from anki.cards import Card
from anki.utils import parseTags from anki.utils import parseTags, ids2str
from anki.tags import tagIds
from anki.lang import _ from anki.lang import _
# the standard Anki scheduler # the standard Anki scheduler
@ -503,8 +504,8 @@ limit %d""" % (self.newOrder(), self.queueLimit)), lim=self.dayCutoff)
"update cards set queue = type where queue = -3") "update cards set queue = type where queue = -3")
def cardLimit(self, active, inactive, sql): def cardLimit(self, active, inactive, sql):
yes = parseTags(getattr(self.deck, active)) yes = parseTags(self.deck.limits.get(active))
no = parseTags(getattr(self.deck, inactive)) no = parseTags(self.deck.limits.get(inactive))
if yes: if yes:
yids = tagIds(self.db, yes).values() yids = tagIds(self.db, yes).values()
nids = tagIds(self.db, no).values() nids = tagIds(self.db, no).values()

View file

@ -4,7 +4,7 @@
DECK_VERSION = 100 DECK_VERSION = 100
import time import time, simplejson
from anki.db import * from anki.db import *
from anki.lang import _ from anki.lang import _
from anki.media import rebuildMediaDir from anki.media import rebuildMediaDir
@ -38,7 +38,7 @@ def upgradeSchema(engine, s):
metadata.create_all(engine, tables=[cards.cardsTable]) metadata.create_all(engine, tables=[cards.cardsTable])
s.execute(""" s.execute("""
insert into cards select id, factId, insert into cards select id, factId,
(select modelId from facts where facts.id = cards.factId), (select modelId from facts where facts.id = cards2.factId),
cardModelId, created, modified, cardModelId, created, modified,
question, answer, ordinal, 0, relativeDelay, type, due, interval, question, answer, ordinal, 0, relativeDelay, type, due, interval,
factor, reps, successive, noCount, 0, 0 from cards2""") factor, reps, successive, noCount, 0, 0 from cards2""")
@ -73,15 +73,7 @@ originalPath from media2""")
s.execute("drop table media2") s.execute("drop table media2")
# deck # deck
########### ###########
import deck migrateDeck(s, engine)
metadata.create_all(engine, tables=[deck.deckTable])
s.execute("""
insert into deck select id, created, modified, 0, 99, currentModelId,
ifnull(syncName, ""), lastSync, utcOffset, newCardOrder,
newCardSpacing, newCardsPerDay, revCardOrder, 600, sessionRepLimit,
sessionTimeLimit, 1, 16, '', '', '', '' from decks
""")
s.execute("drop table decks")
# models # models
########### ###########
moveTable(s, "models") moveTable(s, "models")
@ -89,13 +81,46 @@ sessionTimeLimit, 1, 16, '', '', '', '' from decks
metadata.create_all(engine, tables=[models.modelsTable]) metadata.create_all(engine, tables=[models.modelsTable])
s.execute(""" s.execute("""
insert or ignore into models select id, created, modified, name, insert or ignore into models select id, created, modified, name,
'[0.5, 3, 10]', '[1, 7, 4]', :c from models2""", {'c':simplejson.dumps(models.defaultConf)})
'[0.5, 3, 10]', '[1, 7, 4]',
0, 2.5 from models2""")
s.execute("drop table models2") s.execute("drop table models2")
return ver return ver
def migrateDeck(s, engine):
import deck
metadata.create_all(engine, tables=[deck.deckTable])
s.execute("""
insert into deck select id, created, modified, 0, 99,
ifnull(syncName, ""), lastSync, utcOffset, "", "", "" from decks""")
# update selective study
lim = deck.defaultLim.copy()
keys = ("newActive", "newInactive", "revActive", "revInactive")
for k in keys:
lim[k] = s.execute("select value from deckVars where key=:k",
{'k':k}).scalar()
s.execute("delete from deckVars where key=:k", {'k':k})
# fetch remaining settings from decks table
conf = deck.defaultConf.copy()
data = {}
keys = ("newCardOrder", "newCardSpacing", "newCardsPerDay",
"revCardOrder", "sessionRepLimit", "sessionTimeLimit")
for k in keys:
conf[k] = s.execute("select %s from decks" % k).scalar()
# add any deck vars and save
dkeys = ("hexCache", "cssCache")
for (k, v) in s.execute("select * from deckVars").fetchall():
if k in dkeys:
data[k] = v
else:
conf[k] = v
s.execute("update deck set limits = :l, config = :c, data = :d",
{'l':simplejson.dumps(lim),
'c':simplejson.dumps(conf),
'd':simplejson.dumps(data)})
# clean up
s.execute("drop table decks")
s.execute("drop table deckVars")
def updateIndices(db): def updateIndices(db):
"Add indices to the DB." "Add indices to the DB."
# due counts, failed card queue # due counts, failed card queue

View file

@ -1,6 +1,6 @@
# coding: utf-8 # coding: utf-8
import nose, os, re import nose, os, re, tempfile, shutil
from tests.shared import assertException, getDeck from tests.shared import assertException, getDeck
from anki.errors import * from anki.errors import *
@ -307,3 +307,9 @@ def test_findCards():
c = deck.addFact(f) c = deck.addFact(f)
assert len(deck.findCards('tag:forward')) == 5 assert len(deck.findCards('tag:forward')) == 5
assert len(deck.findCards('tag:reverse')) == 1 assert len(deck.findCards('tag:reverse')) == 1
def test_upgrade():
src = os.path.expanduser("~/Scratch/upgrade.anki")
(fd, dst) = tempfile.mkstemp(suffix=".anki")
shutil.copy(src, dst)
deck = Deck(dst)