mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
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:
parent
b0b4074cbd
commit
d34a76d5a0
4 changed files with 110 additions and 61 deletions
103
anki/deck.py
103
anki/deck.py
|
@ -51,17 +51,42 @@ SEARCH_FIELD_EXISTS = 7
|
|||
SEARCH_QA = 8
|
||||
SEARCH_PHRASE_WB = 9
|
||||
|
||||
deckVarsTable = Table(
|
||||
'deckVars', metadata,
|
||||
Column('key', UnicodeText, nullable=False, primary_key=True),
|
||||
Column('value', UnicodeText))
|
||||
# selective study
|
||||
defaultLim = {
|
||||
'newActive': u"",
|
||||
'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
|
||||
# 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(
|
||||
'deck', metadata,
|
||||
Column('id', Integer, nullable=False, primary_key=True),
|
||||
|
@ -69,27 +94,14 @@ deckTable = Table(
|
|||
Column('modified', Float, nullable=False, default=time.time),
|
||||
Column('schemaMod', Float, nullable=False, default=0),
|
||||
Column('version', Integer, nullable=False, default=DECK_VERSION),
|
||||
Column('currentModelId', Integer, ForeignKey("models.id")),
|
||||
Column('syncName', UnicodeText, nullable=False, default=u""),
|
||||
Column('lastSync', Float, nullable=False, default=0),
|
||||
# scheduling
|
||||
Column('lastSync', Integer, nullable=False, default=0),
|
||||
Column('utcOffset', Integer, nullable=False, default=-2),
|
||||
Column('newCardOrder', Integer, nullable=False, default=1),
|
||||
Column('newCardSpacing', Integer, nullable=False, default=NEW_CARDS_DISTRIBUTE),
|
||||
Column('newCardsPerDay', Integer, nullable=False, default=20),
|
||||
Column('revCardOrder', Integer, nullable=False, default=0),
|
||||
Column('collapseTime', Integer, nullable=False, default=600),
|
||||
# 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""),
|
||||
Column('limits', UnicodeText, nullable=False, default=unicode(
|
||||
simplejson.dumps(defaultLim))),
|
||||
Column('config', UnicodeText, nullable=False, default=unicode(
|
||||
simplejson.dumps(defaultConf))),
|
||||
Column('data', UnicodeText, nullable=False, default=u"{}")
|
||||
)
|
||||
|
||||
class Deck(object):
|
||||
|
@ -108,19 +120,6 @@ class Deck(object):
|
|||
self.sessionStartReps = 0
|
||||
self.sessionStartTime = 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)
|
||||
|
||||
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("""
|
||||
select id, lastFontColour from cardModels""")])
|
||||
self.css = css
|
||||
self.setVar("cssCache", css, mod=False)
|
||||
self.data['cssCache'] = css
|
||||
self.addHexCache()
|
||||
return css
|
||||
|
||||
|
@ -745,7 +744,7 @@ select id from models""")
|
|||
cache = {}
|
||||
for id in ids:
|
||||
cache[id] = hexifyID(id)
|
||||
self.setVar("hexCache", simplejson.dumps(cache), mod=False)
|
||||
self.data['hexCache'] = cache
|
||||
|
||||
def copyModel(self, oldModel):
|
||||
"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:
|
||||
return
|
||||
self.lastLoaded = self.modified
|
||||
self.flushConfig()
|
||||
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):
|
||||
if self.db:
|
||||
self.db.rollback()
|
||||
|
@ -2225,6 +2231,7 @@ Return new path, relative to media dir."""
|
|||
def saveAs(self, newPath):
|
||||
"Returns new deck. Old connection is closed without saving."
|
||||
oldMediaDir = self.mediaDir()
|
||||
self.flushConfig()
|
||||
self.db.flush()
|
||||
# remove new deck if it exists
|
||||
try:
|
||||
|
@ -2648,6 +2655,8 @@ seq > :s and seq <= :e order by seq desc""", s=start, e=end)
|
|||
##########################################################################
|
||||
|
||||
def updateDynamicIndices(self):
|
||||
print "fix dynamicIndices()"
|
||||
return
|
||||
indices = {
|
||||
'intervalDesc':
|
||||
'(queue, interval desc, factId, due)',
|
||||
|
@ -2690,7 +2699,11 @@ seq > :s and seq <= :e order by seq desc""", s=start, e=end)
|
|||
if 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
|
||||
##########################################################################
|
||||
|
@ -2825,10 +2838,14 @@ class DeckStorage(object):
|
|||
path = os.path.abspath(path)
|
||||
create = not os.path.exists(path)
|
||||
deck = DeckStorage._getDeck(path, create, pool)
|
||||
deck.limits = simplejson.loads(deck._limits)
|
||||
if not rebuild:
|
||||
# minimal startup
|
||||
return deck
|
||||
oldMod = deck.modified
|
||||
# setup config
|
||||
deck.config = simplejson.loads(deck._config)
|
||||
deck.data = simplejson.loads(deck._data)
|
||||
# unsuspend buried/rev early
|
||||
deck.db.statement(
|
||||
"update cards set queue = type where queue between -3 and -2")
|
||||
|
|
|
@ -6,7 +6,8 @@ import time, datetime, simplejson
|
|||
from heapq import *
|
||||
from anki.db import *
|
||||
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 _
|
||||
|
||||
# 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")
|
||||
|
||||
def cardLimit(self, active, inactive, sql):
|
||||
yes = parseTags(getattr(self.deck, active))
|
||||
no = parseTags(getattr(self.deck, inactive))
|
||||
yes = parseTags(self.deck.limits.get(active))
|
||||
no = parseTags(self.deck.limits.get(inactive))
|
||||
if yes:
|
||||
yids = tagIds(self.db, yes).values()
|
||||
nids = tagIds(self.db, no).values()
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
DECK_VERSION = 100
|
||||
|
||||
import time
|
||||
import time, simplejson
|
||||
from anki.db import *
|
||||
from anki.lang import _
|
||||
from anki.media import rebuildMediaDir
|
||||
|
@ -38,7 +38,7 @@ def upgradeSchema(engine, s):
|
|||
metadata.create_all(engine, tables=[cards.cardsTable])
|
||||
s.execute("""
|
||||
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,
|
||||
question, answer, ordinal, 0, relativeDelay, type, due, interval,
|
||||
factor, reps, successive, noCount, 0, 0 from cards2""")
|
||||
|
@ -73,15 +73,7 @@ originalPath from media2""")
|
|||
s.execute("drop table media2")
|
||||
# deck
|
||||
###########
|
||||
import deck
|
||||
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")
|
||||
migrateDeck(s, engine)
|
||||
# models
|
||||
###########
|
||||
moveTable(s, "models")
|
||||
|
@ -89,13 +81,46 @@ sessionTimeLimit, 1, 16, '', '', '', '' from decks
|
|||
metadata.create_all(engine, tables=[models.modelsTable])
|
||||
s.execute("""
|
||||
insert or ignore into models select id, created, modified, name,
|
||||
'[0.5, 3, 10]', '[1, 7, 4]',
|
||||
'[0.5, 3, 10]', '[1, 7, 4]',
|
||||
0, 2.5 from models2""")
|
||||
:c from models2""", {'c':simplejson.dumps(models.defaultConf)})
|
||||
s.execute("drop table models2")
|
||||
|
||||
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):
|
||||
"Add indices to the DB."
|
||||
# due counts, failed card queue
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# coding: utf-8
|
||||
|
||||
import nose, os, re
|
||||
import nose, os, re, tempfile, shutil
|
||||
from tests.shared import assertException, getDeck
|
||||
|
||||
from anki.errors import *
|
||||
|
@ -307,3 +307,9 @@ def test_findCards():
|
|||
c = deck.addFact(f)
|
||||
assert len(deck.findCards('tag:forward')) == 5
|
||||
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)
|
||||
|
|
Loading…
Reference in a new issue