mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -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_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")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue