groups -> decks

This commit is contained in:
Damien Elmes 2011-11-23 19:28:09 +09:00
parent 279a942642
commit f7790275ce
22 changed files with 482 additions and 482 deletions

View file

@ -29,7 +29,7 @@ class Card(object):
else: else:
# to flush, set nid, ord, and due # to flush, set nid, ord, and due
self.id = timestampID(col.db, "cards") self.id = timestampID(col.db, "cards")
self.gid = 1 self.did = 1
self.crt = intTime() self.crt = intTime()
self.type = 0 self.type = 0
self.queue = 0 self.queue = 0
@ -45,7 +45,7 @@ class Card(object):
def load(self): def load(self):
(self.id, (self.id,
self.nid, self.nid,
self.gid, self.did,
self.ord, self.ord,
self.mod, self.mod,
self.usn, self.usn,
@ -73,7 +73,7 @@ insert or replace into cards values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
self.id, self.id,
self.nid, self.nid,
self.gid, self.did,
self.ord, self.ord,
self.mod, self.mod,
self.usn, self.usn,
@ -109,7 +109,7 @@ lapses=?, left=?, edue=? where id = ?""",
def _getQA(self, reload=False): def _getQA(self, reload=False):
if not self._qa or reload: if not self._qa or reload:
f = self.note(); m = self.model() f = self.note(); m = self.model()
data = [self.id, f.id, m['id'], self.gid, self.ord, f.stringTags(), data = [self.id, f.id, m['id'], self.did, self.ord, f.stringTags(),
f.joinedFields()] f.joinedFields()]
self._qa = self.col._renderQA(data) self._qa = self.col._renderQA(data)
return self._qa return self._qa
@ -128,8 +128,8 @@ lapses=?, left=?, edue=? where id = ?""",
def model(self, reload=False): def model(self, reload=False):
return self._reviewData()[1] return self._reviewData()[1]
def groupConf(self): def deckConf(self):
return self.col.groups.conf(self.gid) return self.col.decks.conf(self.did)
def template(self): def template(self):
return self._reviewData()[1]['tmpls'][self.ord] return self._reviewData()[1]['tmpls'][self.ord]
@ -140,4 +140,4 @@ lapses=?, left=?, edue=? where id = ?""",
def timeTaken(self): def timeTaken(self):
"Time taken to answer card, in integer MS." "Time taken to answer card, in integer MS."
total = int((time.time() - self.timerStarted)*1000) total = int((time.time() - self.timerStarted)*1000)
return min(total, self.groupConf()['maxTaken']*1000) return min(total, self.deckConf()['maxTaken']*1000)

View file

@ -10,7 +10,7 @@ from anki.hooks import runHook, runFilter
from anki.sched import Scheduler from anki.sched import Scheduler
from anki.models import ModelManager from anki.models import ModelManager
from anki.media import MediaManager from anki.media import MediaManager
from anki.groups import GroupManager from anki.decks import DeckManager
from anki.tags import TagManager from anki.tags import TagManager
from anki.consts import * from anki.consts import *
from anki.errors import AnkiError from anki.errors import AnkiError
@ -20,9 +20,9 @@ import anki.cards, anki.notes, anki.template, anki.cram, anki.find
defaultConf = { defaultConf = {
# scheduling options # scheduling options
'activeGroups': [1], 'activeDecks': [1],
'topGroup': 1, 'topDeck': 1,
'curGroup': 1, 'curDeck': 1,
'revOrder': REV_CARDS_RANDOM, 'revOrder': REV_CARDS_RANDOM,
# other config # other config
'nextPos': 1, 'nextPos': 1,
@ -44,7 +44,7 @@ class _Collection(object):
self.clearUndo() self.clearUndo()
self.media = MediaManager(self) self.media = MediaManager(self)
self.models = ModelManager(self) self.models = ModelManager(self)
self.groups = GroupManager(self) self.decks = DeckManager(self)
self.tags = TagManager(self) self.tags = TagManager(self)
self.load() self.load()
if not self.crt: if not self.crt:
@ -78,14 +78,14 @@ class _Collection(object):
self.ls, self.ls,
self.conf, self.conf,
models, models,
groups, decks,
gconf, dconf,
tags) = self.db.first(""" tags) = self.db.first("""
select crt, mod, scm, dty, usn, ls, select crt, mod, scm, dty, usn, ls,
conf, models, groups, gconf, tags from col""") conf, models, decks, dconf, tags from col""")
self.conf = simplejson.loads(self.conf) self.conf = simplejson.loads(self.conf)
self.models.load(models) self.models.load(models)
self.groups.load(groups, gconf) self.decks.load(decks, dconf)
self.tags.load(tags) self.tags.load(tags)
def flush(self, mod=None): def flush(self, mod=None):
@ -97,7 +97,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
self.crt, self.mod, self.scm, self.dty, self.crt, self.mod, self.scm, self.dty,
self._usn, self.ls, simplejson.dumps(self.conf)) self._usn, self.ls, simplejson.dumps(self.conf))
self.models.flush() self.models.flush()
self.groups.flush() self.decks.flush()
self.tags.flush() self.tags.flush()
def save(self, name=None, mod=None): def save(self, name=None, mod=None):
@ -291,8 +291,8 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
data = [] data = []
ts = maxID(self.db) ts = maxID(self.db)
now = intTime() now = intTime()
for nid, mid, gid, flds in self.db.execute( for nid, mid, did, flds in self.db.execute(
"select id, mid, gid, flds from notes where id in "+snids): "select id, mid, did, flds from notes where id in "+snids):
model = self.models.get(mid) model = self.models.get(mid)
avail = self.models.availOrds(model, flds) avail = self.models.availOrds(model, flds)
ok = [] ok = []
@ -300,7 +300,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
if (nid,t['ord']) in have: if (nid,t['ord']) in have:
continue continue
if t['ord'] in avail: if t['ord'] in avail:
data.append((ts, nid, t['gid'] or gid, t['ord'], data.append((ts, nid, t['did'] or did, t['ord'],
now, nid)) now, nid))
ts += 1 ts += 1
# bulk update # bulk update
@ -330,7 +330,7 @@ insert into cards values (?,?,?,?,?,-1,0,0,?,0,0,0,0,0,0,0,"")""",
card = anki.cards.Card(self) card = anki.cards.Card(self)
card.nid = note.id card.nid = note.id
card.ord = template['ord'] card.ord = template['ord']
card.gid = template['gid'] or note.gid card.did = template['did'] or note.did
card.due = due card.due = due
if flush: if flush:
card.flush() card.flush()
@ -407,7 +407,7 @@ select id from notes where id in %s and id not in (select nid from cards)""" %
def _renderQA(self, data): def _renderQA(self, data):
"Returns hash of id, question, answer." "Returns hash of id, question, answer."
# data is [cid, nid, mid, gid, ord, tags, flds] # data is [cid, nid, mid, did, ord, tags, flds]
# unpack fields and create dict # unpack fields and create dict
flist = splitFields(data[6]) flist = splitFields(data[6])
fields = {} fields = {}
@ -416,7 +416,7 @@ select id from notes where id in %s and id not in (select nid from cards)""" %
fields[name] = flist[idx] fields[name] = flist[idx]
fields['Tags'] = data[5] fields['Tags'] = data[5]
fields['Model'] = model['name'] fields['Model'] = model['name']
fields['Group'] = self.groups.name(data[3]) fields['Deck'] = self.decks.name(data[3])
template = model['tmpls'][data[4]] template = model['tmpls'][data[4]]
fields['Template'] = template['name'] fields['Template'] = template['name']
# render q & a # render q & a
@ -437,9 +437,9 @@ select id from notes where id in %s and id not in (select nid from cards)""" %
return d return d
def _qaData(self, where=""): def _qaData(self, where=""):
"Return [cid, nid, mid, gid, ord, tags, flds] db query" "Return [cid, nid, mid, did, ord, tags, flds] db query"
return self.db.execute(""" return self.db.execute("""
select c.id, f.id, f.mid, c.gid, c.ord, f.tags, f.flds select c.id, f.id, f.mid, c.did, c.ord, f.tags, f.flds
from cards c, notes f from cards c, notes f
where c.nid == f.id where c.nid == f.id
%s""" % where) %s""" % where)
@ -503,7 +503,7 @@ where c.nid == f.id
self.sched = self._stdSched self.sched = self._stdSched
return True return True
def cramGroups(self, order="mod desc", min=0, max=None): def cramDecks(self, order="mod desc", min=0, max=None):
self.stdSched() self.stdSched()
self.sched = anki.cram.CramScheduler(self, order, min, max) self.sched = anki.cram.CramScheduler(self, order, min, max)

View file

@ -25,7 +25,7 @@ REV_CARDS_NEW_FIRST = 2
# removal types # removal types
REM_CARD = 0 REM_CARD = 0
REM_NOTE = 1 REM_NOTE = 1
REM_GROUP = 2 REM_DECK = 2
# count display # count display
COUNT_ANSWERED = 0 COUNT_ANSWERED = 0

View file

@ -66,8 +66,8 @@ class CramScheduler(Scheduler):
else: else:
maxlim = "" maxlim = ""
self.newQueue = self.col.db.list(""" self.newQueue = self.col.db.list("""
select id from cards where gid in %s and queue = 2 and due >= %d select id from cards where did in %s and queue = 2 and due >= %d
%s order by %s limit %d""" % (self._groupLimit(), %s order by %s limit %d""" % (self._deckLimit(),
self.today+1+self.min, self.today+1+self.min,
maxlim, maxlim,
self.order, self.order,

View file

@ -9,21 +9,21 @@ from anki.lang import _
# fixmes: # fixmes:
# - make sure users can't set grad interval < 1 # - make sure users can't set grad interval < 1
# - make sure lists like new[delays] are not being shared by multiple groups # - make sure lists like new[delays] are not being shared by multiple decks
# - make sure all children have parents (create as necessary) # - make sure all children have parents (create as necessary)
# - when renaming a group, top level properties should be added or removed as # - when renaming a deck, top level properties should be added or removed as
# appropriate # appropriate
# notes: # notes:
# - it's difficult to enforce valid gids for models/notes/cards, as we # - it's difficult to enforce valid dids for models/notes/cards, as we
# may update the gid locally only to have it overwritten by a more recent # may update the did locally only to have it overwritten by a more recent
# change from somewhere else. to avoid this, we allow invalid gid # change from somewhere else. to avoid this, we allow invalid did
# references, and treat any invalid gids as the default group. # references, and treat any invalid dids as the default deck.
# - deletions of group config force a full sync # - deletions of deck config force a full sync
# these are a cache of the current day's reviews. they may be wrong after a # these are a cache of the current day's reviews. they may be wrong after a
# sync merge if someone reviewed from two locations # sync merge if someone reviewed from two locations
defaultGroup = { defaultDeck = {
'newToday': [0, 0], # currentDay, count 'newToday': [0, 0], # currentDay, count
'revToday': [0, 0], 'revToday': [0, 0],
'lrnToday': [0, 0], 'lrnToday': [0, 0],
@ -31,7 +31,7 @@ defaultGroup = {
'conf': 1, 'conf': 1,
} }
# configuration only available to top level groups # configuration only available to top level decks
defaultTopConf = { defaultTopConf = {
'revLim': 100, 'revLim': 100,
'newSpread': NEW_CARDS_DISTRIBUTE, 'newSpread': NEW_CARDS_DISTRIBUTE,
@ -41,7 +41,7 @@ defaultTopConf = {
'curModel': None, 'curModel': None,
} }
# configuration available to all groups # configuration available to all decks
defaultConf = { defaultConf = {
'name': _("Default"), 'name': _("Default"),
'new': { 'new': {
@ -78,7 +78,7 @@ defaultConf = {
'usn': 0, 'usn': 0,
} }
class GroupManager(object): class DeckManager(object):
# Registry save/load # Registry save/load
############################################################# #############################################################
@ -86,13 +86,13 @@ class GroupManager(object):
def __init__(self, col): def __init__(self, col):
self.col = col self.col = col
def load(self, groups, gconf): def load(self, decks, dconf):
self.groups = simplejson.loads(groups) self.decks = simplejson.loads(decks)
self.gconf = simplejson.loads(gconf) self.dconf = simplejson.loads(dconf)
self.changed = False self.changed = False
def save(self, g=None): def save(self, g=None):
"Can be called with either a group or a group configuration." "Can be called with either a deck or a deck configuration."
if g: if g:
g['mod'] = intTime() g['mod'] = intTime()
g['usn'] = self.col.usn() g['usn'] = self.col.usn()
@ -100,86 +100,86 @@ class GroupManager(object):
def flush(self): def flush(self):
if self.changed: if self.changed:
self.col.db.execute("update col set groups=?, gconf=?", self.col.db.execute("update col set decks=?, dconf=?",
simplejson.dumps(self.groups), simplejson.dumps(self.decks),
simplejson.dumps(self.gconf)) simplejson.dumps(self.dconf))
# Group save/load # Deck save/load
############################################################# #############################################################
def id(self, name, create=True): def id(self, name, create=True):
"Add a group with NAME. Reuse group if already exists. Return id as int." "Add a deck with NAME. Reuse deck if already exists. Return id as int."
for id, g in self.groups.items(): for id, g in self.decks.items():
if g['name'].lower() == name.lower(): if g['name'].lower() == name.lower():
return int(id) return int(id)
if not create: if not create:
return None return None
if "::" not in name: if "::" not in name:
# if it's a top level group, it gets the top level config # if it's a top level deck, it gets the top level config
g = defaultTopConf.copy() g = defaultTopConf.copy()
else: else:
# not top level; ensure all parents exist # not top level; ensure all parents exist
g = {} g = {}
self._ensureParents(name) self._ensureParents(name)
for (k,v) in defaultGroup.items(): for (k,v) in defaultDeck.items():
g[k] = v g[k] = v
g['name'] = name g['name'] = name
while 1: while 1:
id = intTime(1000) id = intTime(1000)
if str(id) not in self.groups: if str(id) not in self.decks:
break break
g['id'] = id g['id'] = id
self.groups[str(id)] = g self.decks[str(id)] = g
self.save(g) self.save(g)
self.maybeAddToActive() self.maybeAddToActive()
return int(id) return int(id)
def rem(self, gid, cardsToo=False): def rem(self, did, cardsToo=False):
"Remove the group. If cardsToo, delete any cards inside." "Remove the deck. If cardsToo, delete any cards inside."
assert gid != 1 assert did != 1
if not str(gid) in self.groups: if not str(did) in self.decks:
return return
# delete children first # delete children first
for name, id in self.children(gid): for name, id in self.children(did):
self.rem(id, cardsToo) self.rem(id, cardsToo)
# delete cards too? # delete cards too?
if cardsToo: if cardsToo:
self.col.remCards(self.cids(gid)) self.col.remCards(self.cids(did))
# delete the group and add a grave # delete the deck and add a grave
del self.groups[str(gid)] del self.decks[str(did)]
self.col._logRem([gid], REM_GROUP) self.col._logRem([did], REM_DECK)
# ensure we have an active group # ensure we have an active deck
if gid in self.active(): if did in self.active():
self.select(int(self.groups.keys()[0])) self.select(int(self.decks.keys()[0]))
self.save() self.save()
def allNames(self): def allNames(self):
"An unsorted list of all group names." "An unsorted list of all deck names."
return [x['name'] for x in self.groups.values()] return [x['name'] for x in self.decks.values()]
def all(self): def all(self):
"A list of all groups." "A list of all decks."
return self.groups.values() return self.decks.values()
def get(self, gid, default=True): def get(self, did, default=True):
id = str(gid) id = str(did)
if id in self.groups: if id in self.decks:
return self.groups[id] return self.decks[id]
elif default: elif default:
return self.groups['1'] return self.decks['1']
def update(self, g): def update(self, g):
"Add or update an existing group. Used for syncing and merging." "Add or update an existing deck. Used for syncing and merging."
self.groups[str(g['id'])] = g self.decks[str(g['id'])] = g
self.maybeAddToActive() self.maybeAddToActive()
# mark registry changed, but don't bump mod time # mark registry changed, but don't bump mod time
self.save() self.save()
def rename(self, g, newName): def rename(self, g, newName):
"Rename group prefix to NAME if not exists. Updates children." "Rename deck prefix to NAME if not exists. Updates children."
# make sure target node doesn't already exist # make sure target node doesn't already exist
if newName in self.allNames(): if newName in self.allNames():
raise Exception("Group exists") raise Exception("Deck exists")
# rename children # rename children
for grp in self.all(): for grp in self.all():
if grp['name'].startswith(g['name'] + "::"): if grp['name'].startswith(g['name'] + "::"):
@ -209,18 +209,18 @@ class GroupManager(object):
s += "::" + p s += "::" + p
self.id(s) self.id(s)
# Group configurations # Deck configurations
############################################################# #############################################################
def allConf(self): def allConf(self):
"A list of all group config." "A list of all deck config."
return self.gconf.values() return self.dconf.values()
def conf(self, gid): def conf(self, did):
return self.gconf[str(self.groups[str(gid)]['conf'])] return self.dconf[str(self.decks[str(did)]['conf'])]
def updateConf(self, g): def updateConf(self, g):
self.gconf[str(g['id'])] = g self.dconf[str(g['id'])] = g
self.save() self.save()
def confId(self, name): def confId(self, name):
@ -228,19 +228,19 @@ class GroupManager(object):
c = copy.deepcopy(defaultConf) c = copy.deepcopy(defaultConf)
while 1: while 1:
id = intTime(1000) id = intTime(1000)
if str(id) not in self.gconf: if str(id) not in self.dconf:
break break
c['id'] = id c['id'] = id
c['name'] = name c['name'] = name
self.gconf[str(id)] = c self.dconf[str(id)] = c
self.save(c) self.save(c)
return id return id
def remConf(self, id): def remConf(self, id):
"Remove a configuration and update all groups using it." "Remove a configuration and update all decks using it."
assert int(id) != 1 assert int(id) != 1
self.col.modSchema() self.col.modSchema()
del self.gconf[str(id)] del self.dconf[str(id)]
for g in self.all(): for g in self.all():
if str(g['conf']) == str(id): if str(g['conf']) == str(id):
g['conf'] = 1 g['conf'] = 1
@ -250,16 +250,16 @@ class GroupManager(object):
grp['conf'] = id grp['conf'] = id
self.save(grp) self.save(grp)
# Group utils # Deck utils
############################################################# #############################################################
def name(self, gid): def name(self, did):
return self.get(gid)['name'] return self.get(did)['name']
def setGroup(self, cids, gid): def setDeck(self, cids, did):
self.col.db.execute( self.col.db.execute(
"update cards set gid=?,usn=?,mod=? where id in "+ "update cards set did=?,usn=?,mod=? where id in "+
ids2str(cids), gid, self.col.usn(), intTime()) ids2str(cids), did, self.col.usn(), intTime())
def maybeAddToActive(self): def maybeAddToActive(self):
@ -268,59 +268,59 @@ class GroupManager(object):
def sendHome(self, cids): def sendHome(self, cids):
self.col.db.execute(""" self.col.db.execute("""
update cards set gid=(select gid from notes f where f.id=nid), update cards set did=(select did from notes f where f.id=nid),
usn=?,mod=? where id in %s""" % ids2str(cids), usn=?,mod=? where id in %s""" % ids2str(cids),
self.col.usn(), intTime(), gid) self.col.usn(), intTime(), did)
def cids(self, gid): def cids(self, did):
return self.col.db.list("select id from cards where gid=?", gid) return self.col.db.list("select id from cards where did=?", did)
# Group selection # Deck selection
############################################################# #############################################################
def top(self): def top(self):
"The current top level group as an object." "The current top level deck as an object."
g = self.get(self.col.conf['topGroup']) g = self.get(self.col.conf['topDeck'])
return g return g
def active(self): def active(self):
"The currrently active gids." "The currrently active dids."
return self.col.conf['activeGroups'] return self.col.conf['activeDecks']
def selected(self): def selected(self):
"The currently selected gid." "The currently selected did."
return self.col.conf['curGroup'] return self.col.conf['curDeck']
def current(self): def current(self):
return self.get(self.selected()) return self.get(self.selected())
def select(self, gid): def select(self, did):
"Select a new branch." "Select a new branch."
# save the top level group # save the top level deck
name = self.groups[str(gid)]['name'] name = self.decks[str(did)]['name']
self.col.conf['topGroup'] = self._topFor(name) self.col.conf['topDeck'] = self._topFor(name)
# current group # current deck
self.col.conf['curGroup'] = gid self.col.conf['curDeck'] = did
# and active groups (current + all children) # and active decks (current + all children)
actv = self.children(gid) actv = self.children(did)
actv.sort() actv.sort()
self.col.conf['activeGroups'] = [gid] + [a[1] for a in actv] self.col.conf['activeDecks'] = [did] + [a[1] for a in actv]
def children(self, gid): def children(self, did):
"All children of gid, as (name, id)." "All children of did, as (name, id)."
name = self.get(gid)['name'] name = self.get(did)['name']
actv = [] actv = []
for g in self.all(): for g in self.all():
if g['name'].startswith(name + "::"): if g['name'].startswith(name + "::"):
actv.append((g['name'], g['id'])) actv.append((g['name'], g['id']))
return actv return actv
def parents(self, gid): def parents(self, did):
"All parents of gid." "All parents of did."
path = self.get(gid)['name'].split("::") path = self.get(did)['name'].split("::")
return [self.get(x) for x in path[:-1]] return [self.get(x) for x in path[:-1]]
def _topFor(self, name): def _topFor(self, name):
"The top level gid for NAME." "The top level did for NAME."
path = name.split("::") path = name.split("::")
return self.id(path[0]) return self.id(path[0])

View file

@ -13,7 +13,7 @@ SEARCH_NID = 3
SEARCH_TEMPLATE = 4 SEARCH_TEMPLATE = 4
SEARCH_FIELD = 5 SEARCH_FIELD = 5
SEARCH_MODEL = 6 SEARCH_MODEL = 6
SEARCH_GROUP = 7 SEARCH_DECK = 7
# Tools # Tools
########################################################################## ##########################################################################
@ -121,8 +121,8 @@ order by %s""" % (lim, sort)
self._findField(token, isNeg) self._findField(token, isNeg)
elif type == SEARCH_MODEL: elif type == SEARCH_MODEL:
self._findModel(token, isNeg) self._findModel(token, isNeg)
elif type == SEARCH_GROUP: elif type == SEARCH_DECK:
self._findGroup(token, isNeg) self._findDeck(token, isNeg)
else: else:
self._findText(token, isNeg, c) self._findText(token, isNeg, c)
@ -191,10 +191,10 @@ order by %s""" % (lim, sort)
ids.append(m['id']) ids.append(m['id'])
self.lims['note'].append("mid %s in %s" % (extra, ids2str(ids))) self.lims['note'].append("mid %s in %s" % (extra, ids2str(ids)))
def _findGroup(self, val, isNeg): def _findDeck(self, val, isNeg):
extra = "!" if isNeg else "" extra = "!" if isNeg else ""
id = self.col.groups.id(val, create=False) or 0 id = self.col.decks.id(val, create=False) or 0
self.lims['card'].append("c.gid %s= %s" % (extra, id)) self.lims['card'].append("c.did %s= %s" % (extra, id))
def _findTemplate(self, val, isNeg): def _findTemplate(self, val, isNeg):
lims = [] lims = []
@ -329,9 +329,9 @@ where mid in %s and flds like ? escape '\\'""" % (
elif token['value'].startswith("model:"): elif token['value'].startswith("model:"):
token['value'] = token['value'][6:].lower() token['value'] = token['value'][6:].lower()
type = SEARCH_MODEL type = SEARCH_MODEL
elif token['value'].startswith("group:"): elif token['value'].startswith("deck:"):
token['value'] = token['value'][6:].lower() token['value'] = token['value'][5:].lower()
type = SEARCH_GROUP type = SEARCH_DECK
elif token['value'].startswith("nid:") and len(token['value']) > 4: elif token['value'].startswith("nid:") and len(token['value']) > 4:
dec = token['value'][4:] dec = token['value'][4:]
try: try:

View file

@ -27,7 +27,7 @@ class Anki1Importer(Anki2Importer):
# merge # merge
deck.close() deck.close()
mdir = self.file.replace(".anki", ".media") mdir = self.file.replace(".anki", ".media")
self.groupPrefix = os.path.basename(self.file).replace(".anki", "") self.deckPrefix = os.path.basename(self.file).replace(".anki", "")
self.file = deck.path self.file = deck.path
Anki2Importer.run(self, mdir) Anki2Importer.run(self, mdir)

View file

@ -14,13 +14,13 @@ from anki.importing.base import Importer
# - compare notes by guid # - compare notes by guid
# - compare models by schema signature # - compare models by schema signature
# - compare cards by note guid + ordinal # - compare cards by note guid + ordinal
# - compare groups by name # - compare decks by name
# #
class Anki2Importer(Importer): class Anki2Importer(Importer):
needMapper = False needMapper = False
groupPrefix = None deckPrefix = None
needCards = True needCards = True
def run(self, media=None): def run(self, media=None):
@ -38,10 +38,10 @@ class Anki2Importer(Importer):
self.src = Collection(self.file, queue=False) self.src = Collection(self.file, queue=False)
def _import(self): def _import(self):
self._groups = {} self._decks = {}
if self.groupPrefix: if self.deckPrefix:
id = self.dst.groups.id(self.groupPrefix) id = self.dst.decks.id(self.deckPrefix)
self.dst.groups.select(id) self.dst.decks.select(id)
self._prepareTS() self._prepareTS()
self._prepareModels() self._prepareModels()
self._importNotes() self._importNotes()
@ -76,7 +76,7 @@ class Anki2Importer(Importer):
# rewrite internal ids, models, etc # rewrite internal ids, models, etc
note[0] = self.ts() note[0] = self.ts()
note[2] = lmid note[2] = lmid
note[3] = self._gid(note[3]) note[3] = self._did(note[3])
note[4] = intTime() note[4] = intTime()
note[5] = -1 # usn note[5] = -1 # usn
add.append(note) add.append(note)
@ -136,27 +136,27 @@ class Anki2Importer(Importer):
else: else:
dst['vers'] = [src['id']] dst['vers'] = [src['id']]
# Groups # Decks
###################################################################### ######################################################################
def _gid(self, gid): def _did(self, did):
"Given gid in src col, return local id." "Given did in src col, return local id."
# already converted? # already converted?
if gid in self._groups: if did in self._decks:
return self._groups[gid] return self._decks[did]
# get the name in src # get the name in src
g = self.src.groups.get(gid) g = self.src.decks.get(did)
name = g['name'] name = g['name']
# if there's a prefix, replace the top level group # if there's a prefix, replace the top level deck
if self.groupPrefix: if self.deckPrefix:
tmpname = "::".join(name.split("::")[1:]) tmpname = "::".join(name.split("::")[1:])
name = self.groupPrefix name = self.deckPrefix
if tmpname: if tmpname:
name += "::" + name name += "::" + name
# create in local # create in local
newid = self.dst.groups.id(name) newid = self.dst.decks.id(name)
# add to group map and return # add to deck map and return
self._groups[gid] = newid self._decks[did] = newid
return newid return newid
# Cards # Cards
@ -199,7 +199,7 @@ class Anki2Importer(Importer):
# update cid, nid, etc # update cid, nid, etc
card[0] = self.ts() card[0] = self.ts()
card[1] = self._notes[guid][0] card[1] = self._notes[guid][0]
card[2] = self._gid(card[2]) card[2] = self._did(card[2])
card[4] = intTime() card[4] = intTime()
cards.append(card) cards.append(card)
# we need to import revlog, rewriting card ids # we need to import revlog, rewriting card ids

View file

@ -15,7 +15,7 @@ from anki.consts import *
defaultModel = { defaultModel = {
'sortf': 0, 'sortf': 0,
'gid': 1, 'did': 1,
'clozectx': False, 'clozectx': False,
'newOrder': NEW_CARDS_DUE, 'newOrder': NEW_CARDS_DUE,
'latexPre': """\ 'latexPre': """\
@ -52,7 +52,7 @@ defaultTemplate = {
'qfmt': "", 'qfmt': "",
'afmt': "", 'afmt': "",
'typeAns': None, 'typeAns': None,
'gid': None, 'did': None,
} }
class ModelManager(object): class ModelManager(object):
@ -90,16 +90,16 @@ class ModelManager(object):
def current(self): def current(self):
"Get current model." "Get current model."
try: try:
m = self.get(self.col.groups.top()['curModel']) m = self.get(self.col.decks.top()['curModel'])
assert m assert m
return m return m
except: except:
return self.models.values()[0] return self.models.values()[0]
def setCurrent(self, m): def setCurrent(self, m):
t = self.col.groups.top() t = self.col.decks.top()
t['curModel'] = m['id'] t['curModel'] = m['id']
self.col.groups.save(t) self.col.decks.save(t)
def get(self, id): def get(self, id):
"Get model with ID, or None." "Get model with ID, or None."

View file

@ -19,7 +19,7 @@ class Note(object):
self.id = timestampID(col.db, "notes") self.id = timestampID(col.db, "notes")
self.guid = guid64() self.guid = guid64()
self._model = model self._model = model
self.gid = model['gid'] self.did = model['did']
self.mid = model['id'] self.mid = model['id']
self.tags = [] self.tags = []
self.fields = [""] * len(self._model['flds']) self.fields = [""] * len(self._model['flds'])
@ -30,14 +30,14 @@ class Note(object):
def load(self): def load(self):
(self.guid, (self.guid,
self.mid, self.mid,
self.gid, self.did,
self.mod, self.mod,
self.usn, self.usn,
self.tags, self.tags,
self.fields, self.fields,
self.flags, self.flags,
self.data) = self.col.db.first(""" self.data) = self.col.db.first("""
select guid, mid, gid, mod, usn, tags, flds, flags, data select guid, mid, did, mod, usn, tags, flds, flags, data
from notes where id = ?""", self.id) from notes where id = ?""", self.id)
self.fields = splitFields(self.fields) self.fields = splitFields(self.fields)
self.tags = self.col.tags.split(self.tags) self.tags = self.col.tags.split(self.tags)
@ -53,7 +53,7 @@ from notes where id = ?""", self.id)
tags = self.stringTags() tags = self.stringTags()
res = self.col.db.execute(""" res = self.col.db.execute("""
insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
self.id, self.guid, self.mid, self.gid, self.id, self.guid, self.mid, self.did,
self.mod, self.usn, tags, self.mod, self.usn, tags,
self.joinedFields(), sfld, self.flags, self.data) self.joinedFields(), sfld, self.flags, self.data)
self.id = res.lastrowid self.id = res.lastrowid
@ -84,10 +84,10 @@ insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
def model(self): def model(self):
return self._model return self._model
def updateCardGids(self): def updateCardDids(self):
for c in self.cards(): for c in self.cards():
if c.gid != self.gid and not c.template()['gid']: if c.did != self.did and not c.template()['did']:
c.gid = self.gid c.did = self.did
c.flush() c.flush()
# Dict interface # Dict interface

View file

@ -22,7 +22,7 @@ class Scheduler(object):
self.col = col self.col = col
self.queueLimit = 50 self.queueLimit = 50
self.reportLimit = 1000 self.reportLimit = 1000
# fixme: replace reps with group based counts # fixme: replace reps with deck based counts
self.reps = 0 self.reps = 0
self._updateCutoff() self._updateCutoff()
@ -78,10 +78,10 @@ class Scheduler(object):
"Return counts over next DAYS. Includes today." "Return counts over next DAYS. Includes today."
daysd = dict(self.col.db.all(""" daysd = dict(self.col.db.all("""
select due, count() from cards select due, count() from cards
where gid in %s and queue = 2 where did in %s and queue = 2
and due between ? and ? and due between ? and ?
group by due group by due
order by due""" % self._groupLimit(), order by due""" % self._deckLimit(),
self.today, self.today,
self.today+days-1)) self.today+days-1))
for d in range(days): for d in range(days):
@ -111,34 +111,34 @@ order by due""" % self._groupLimit(),
def _updateStats(self, card, type, cnt=1): def _updateStats(self, card, type, cnt=1):
key = type+"Today" key = type+"Today"
for g in ([self.col.groups.get(card.gid)] + for g in ([self.col.decks.get(card.did)] +
self.col.groups.parents(card.gid)): self.col.decks.parents(card.did)):
# add # add
g[key][1] += cnt g[key][1] += cnt
self.col.groups.save(g) self.col.decks.save(g)
# Group counts # Deck counts
########################################################################## ##########################################################################
def groupCounts(self): def deckCounts(self):
"Returns [groupname, gid, hasDue, hasNew]" "Returns [deckname, did, hasDue, hasNew]"
# find groups with 1 or more due cards # find decks with 1 or more due cards
gids = {} dids = {}
for g in self.col.groups.all(): for g in self.col.decks.all():
hasDue = self._groupHasLrn(g['id']) or self._groupHasRev(g['id']) hasDue = self._deckHasLrn(g['id']) or self._deckHasRev(g['id'])
hasNew = self._groupHasNew(g['id']) hasNew = self._deckHasNew(g['id'])
gids[g['id']] = [hasDue or 0, hasNew or 0] dids[g['id']] = [hasDue or 0, hasNew or 0]
return [[grp['name'], int(gid)]+gids[int(gid)] #.get(int(gid)) return [[grp['name'], int(did)]+dids[int(did)] #.get(int(did))
for (gid, grp) in self.col.groups.groups.items()] for (did, grp) in self.col.decks.decks.items()]
def groupCountTree(self): def deckCountTree(self):
return self._groupChildren(self.groupCounts()) return self._groupChildren(self.deckCounts())
def groupTree(self): def deckTree(self):
"Like the count tree without the counts. Faster." "Like the count tree without the counts. Faster."
return self._groupChildren( return self._groupChildren(
[[grp['name'], int(gid), 0, 0, 0] [[grp['name'], int(did), 0, 0, 0]
for (gid, grp) in self.col.groups.groups.items()]) for (did, grp) in self.col.decks.decks.items()])
def _groupChildren(self, grps): def _groupChildren(self, grps):
# first, split the group names into components # first, split the group names into components
@ -156,14 +156,14 @@ order by due""" % self._groupLimit(),
return grp[0][0] return grp[0][0]
for (head, tail) in itertools.groupby(grps, key=key): for (head, tail) in itertools.groupby(grps, key=key):
tail = list(tail) tail = list(tail)
gid = None did = None
rev = 0 rev = 0
new = 0 new = 0
children = [] children = []
for c in tail: for c in tail:
if len(c[0]) == 1: if len(c[0]) == 1:
# current node # current node
gid = c[1] did = c[1]
rev += c[2] rev += c[2]
new += c[3] new += c[3]
else: else:
@ -175,7 +175,7 @@ order by due""" % self._groupLimit(),
for ch in children: for ch in children:
rev += ch[2] rev += ch[2]
new += ch[3] new += ch[3]
tree.append((head, gid, rev, new, children)) tree.append((head, did, rev, new, children))
return tuple(tree) return tuple(tree)
# Getting the next card # Getting the next card
@ -207,35 +207,35 @@ order by due""" % self._groupLimit(),
def _resetNewCount(self): def _resetNewCount(self):
self.newCount = 0 self.newCount = 0
pcounts = {} pcounts = {}
# for each of the active groups # for each of the active decks
for gid in self.col.groups.active(): for did in self.col.decks.active():
# get the individual group's limit # get the individual deck's limit
lim = self._groupNewLimitSingle(self.col.groups.get(gid)) lim = self._deckNewLimitSingle(self.col.decks.get(did))
if not lim: if not lim:
continue continue
# check the parents # check the parents
parents = self.col.groups.parents(gid) parents = self.col.decks.parents(did)
for p in parents: for p in parents:
# add if missing # add if missing
if p['id'] not in pcounts: if p['id'] not in pcounts:
pcounts[p['id']] = self._groupNewLimitSingle(p) pcounts[p['id']] = self._deckNewLimitSingle(p)
# take minimum of child and parent # take minimum of child and parent
lim = min(pcounts[p['id']], lim) lim = min(pcounts[p['id']], lim)
# see how many cards we actually have # see how many cards we actually have
cnt = self.col.db.scalar(""" cnt = self.col.db.scalar("""
select count() from (select 1 from cards where select count() from (select 1 from cards where
gid = ? and queue = 0 limit ?)""", gid, lim) did = ? and queue = 0 limit ?)""", did, lim)
# if non-zero, decrement from parent counts # if non-zero, decrement from parent counts
for p in parents: for p in parents:
pcounts[p['id']] -= cnt pcounts[p['id']] -= cnt
# we may also be a parent # we may also be a parent
pcounts[gid] = lim - cnt pcounts[did] = lim - cnt
# and add to running total # and add to running total
self.newCount += cnt self.newCount += cnt
def _resetNew(self): def _resetNew(self):
self._resetNewCount() self._resetNewCount()
self.newGids = self.col.groups.active() self.newDids = self.col.decks.active()
self._newQueue = [] self._newQueue = []
self._updateNewCardRatio() self._updateNewCardRatio()
@ -244,25 +244,25 @@ gid = ? and queue = 0 limit ?)""", gid, lim)
return True return True
if not self.newCount: if not self.newCount:
return False return False
while self.newGids: while self.newDids:
gid = self.newGids[0] did = self.newDids[0]
lim = min(self.queueLimit, self._groupNewLimit(gid)) lim = min(self.queueLimit, self._deckNewLimit(did))
if lim: if lim:
# fill the queue with the current gid # fill the queue with the current did
self._newQueue = self.col.db.all(""" self._newQueue = self.col.db.all("""
select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim) select id, due from cards where did = ? and queue = 0 limit ?""", did, lim)
if self._newQueue: if self._newQueue:
self._newQueue.reverse() self._newQueue.reverse()
return True return True
# nothing left in the group; move to next # nothing left in the deck; move to next
self.newGids.pop(0) self.newDids.pop(0)
def _getNewCard(self): def _getNewCard(self):
if not self._fillNew(): if not self._fillNew():
return return
(id, due) = self._newQueue.pop() (id, due) = self._newQueue.pop()
# move any siblings to the end? # move any siblings to the end?
conf = self.col.groups.conf(self.newGids[0]) conf = self.col.decks.conf(self.newDids[0])
if conf['new']['order'] == NEW_TODAY_ORD: if conf['new']['order'] == NEW_TODAY_ORD:
n = len(self._newQueue) n = len(self._newQueue)
while self._newQueue and self._newQueue[-1][1] == due: while self._newQueue and self._newQueue[-1][1] == due:
@ -275,7 +275,7 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
return id return id
def _updateNewCardRatio(self): def _updateNewCardRatio(self):
if self.col.groups.top()['newSpread'] == NEW_CARDS_DISTRIBUTE: if self.col.decks.top()['newSpread'] == NEW_CARDS_DISTRIBUTE:
if self.newCount: if self.newCount:
self.newCardModulus = ( self.newCardModulus = (
(self.newCount + self.revCount) / self.newCount) (self.newCount + self.revCount) / self.newCount)
@ -289,33 +289,33 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
"True if it's time to display a new card when distributing." "True if it's time to display a new card when distributing."
if not self.newCount: if not self.newCount:
return False return False
if self.col.groups.top()['newSpread'] == NEW_CARDS_LAST: if self.col.decks.top()['newSpread'] == NEW_CARDS_LAST:
return False return False
elif self.col.groups.top()['newSpread'] == NEW_CARDS_FIRST: elif self.col.decks.top()['newSpread'] == NEW_CARDS_FIRST:
return True return True
elif self.newCardModulus: elif self.newCardModulus:
return self.reps and self.reps % self.newCardModulus == 0 return self.reps and self.reps % self.newCardModulus == 0
def _groupHasNew(self, gid): def _deckHasNew(self, did):
if not self._groupNewLimit(gid): if not self._deckNewLimit(did):
return False return False
return self.col.db.scalar( return self.col.db.scalar(
"select 1 from cards where gid = ? and queue = 0 limit 1", gid) "select 1 from cards where did = ? and queue = 0 limit 1", did)
def _groupNewLimit(self, gid): def _deckNewLimit(self, did):
sel = self.col.groups.get(gid) sel = self.col.decks.get(did)
lim = -1 lim = -1
# for the group and each of its parents # for the deck and each of its parents
for g in [sel] + self.col.groups.parents(gid): for g in [sel] + self.col.decks.parents(did):
rem = self._groupNewLimitSingle(g) rem = self._deckNewLimitSingle(g)
if lim == -1: if lim == -1:
lim = rem lim = rem
else: else:
lim = min(rem, lim) lim = min(rem, lim)
return lim return lim
def _groupNewLimitSingle(self, g): def _deckNewLimitSingle(self, g):
c = self.col.groups.conf(g['id']) c = self.col.decks.conf(g['id'])
return max(0, c['new']['perDay'] - g['newToday'][1]) return max(0, c['new']['perDay'] - g['newToday'][1])
# Learning queue # Learning queue
@ -324,8 +324,8 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
def _resetLrnCount(self): def _resetLrnCount(self):
(self.lrnCount, self.lrnRepCount) = self.col.db.first(""" (self.lrnCount, self.lrnRepCount) = self.col.db.first("""
select count(), sum(left) from (select left from cards where select count(), sum(left) from (select left from cards where
gid in %s and queue = 1 and due < ? limit %d)""" % ( did in %s and queue = 1 and due < ? limit %d)""" % (
self._groupLimit(), self.reportLimit), self._deckLimit(), self.reportLimit),
self.dayCutoff) self.dayCutoff)
self.lrnRepCount = self.lrnRepCount or 0 self.lrnRepCount = self.lrnRepCount or 0
@ -340,9 +340,9 @@ gid in %s and queue = 1 and due < ? limit %d)""" % (
return True return True
self._lrnQueue = self.col.db.all(""" self._lrnQueue = self.col.db.all("""
select due, id from cards where select due, id from cards where
gid in %s and queue = 1 and due < :lim did in %s and queue = 1 and due < :lim
limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
# as it arrives sorted by gid first, we need to sort it # as it arrives sorted by did first, we need to sort it
self._lrnQueue.sort() self._lrnQueue.sort()
return self._lrnQueue return self._lrnQueue
@ -350,7 +350,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
if self._fillLrn(): if self._fillLrn():
cutoff = time.time() cutoff = time.time()
if collapse: if collapse:
cutoff += self.col.groups.top()['collapseTime'] cutoff += self.col.decks.top()['collapseTime']
if self._lrnQueue[0][0] < cutoff: if self._lrnQueue[0][0] < cutoff:
id = heappop(self._lrnQueue)[1] id = heappop(self._lrnQueue)[1]
self.lrnCount -= 1 self.lrnCount -= 1
@ -459,29 +459,29 @@ where queue = 1 and type = 2
%s %s
""" % (intTime(), self.col.usn(), extra)) """ % (intTime(), self.col.usn(), extra))
def _groupHasLrn(self, gid): def _deckHasLrn(self, did):
return self.col.db.scalar( return self.col.db.scalar(
"select 1 from cards where gid = ? and queue = 1 " "select 1 from cards where did = ? and queue = 1 "
"and due < ? limit 1", "and due < ? limit 1",
gid, intTime() + self.col.groups.top()['collapseTime']) did, intTime() + self.col.decks.top()['collapseTime'])
# Reviews # Reviews
########################################################################## ##########################################################################
def _groupHasRev(self, gid): def _deckHasRev(self, did):
return self.col.db.scalar( return self.col.db.scalar(
"select 1 from cards where gid = ? and queue = 2 " "select 1 from cards where did = ? and queue = 2 "
"and due <= ? limit 1", "and due <= ? limit 1",
gid, self.today) did, self.today)
def _resetRevCount(self): def _resetRevCount(self):
top = self.col.groups.top() top = self.col.decks.top()
lim = min(self.reportLimit, lim = min(self.reportLimit,
max(0, top['revLim'] - top['revToday'][1])) max(0, top['revLim'] - top['revToday'][1]))
self.revCount = self.col.db.scalar(""" self.revCount = self.col.db.scalar("""
select count() from (select id from cards where select count() from (select id from cards where
gid in %s and queue = 2 and due <= :day limit %d)""" % ( did in %s and queue = 2 and due <= :day limit %d)""" % (
self._groupLimit(), lim), day=self.today) self._deckLimit(), lim), day=self.today)
def _resetRev(self): def _resetRev(self):
self._resetRevCount() self._resetRevCount()
@ -494,8 +494,8 @@ gid in %s and queue = 2 and due <= :day limit %d)""" % (
return True return True
self._revQueue = self.col.db.list(""" self._revQueue = self.col.db.list("""
select id from cards where select id from cards where
gid in %s and queue = 2 and due <= :lim %s limit %d""" % ( did in %s and queue = 2 and due <= :lim %s limit %d""" % (
self._groupLimit(), self._revOrder(), self.queueLimit), self._deckLimit(), self._revOrder(), self.queueLimit),
lim=self.today) lim=self.today)
if not self.col.conf['revOrder']: if not self.col.conf['revOrder']:
r = random.Random() r = random.Random()
@ -653,10 +653,10 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
########################################################################## ##########################################################################
def _cardConf(self, card): def _cardConf(self, card):
return self.col.groups.conf(card.gid) return self.col.decks.conf(card.did)
def _groupLimit(self): def _deckLimit(self):
return ids2str(self.col.groups.active()) return ids2str(self.col.decks.active())
# Daily cutoff # Daily cutoff
########################################################################## ##########################################################################
@ -666,7 +666,7 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
self.today = int((time.time() - self.col.crt) / 86400) self.today = int((time.time() - self.col.crt) / 86400)
# end of day cutoff # end of day cutoff
self.dayCutoff = self.col.crt + (self.today+1)*86400 self.dayCutoff = self.col.crt + (self.today+1)*86400
# update all selected groups # update all selected decks
def update(g): def update(g):
save = False save = False
for t in "new", "rev", "lrn", "time": for t in "new", "rev", "lrn", "time":
@ -675,11 +675,11 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
save = True save = True
g[key] = [self.today, 0] g[key] = [self.today, 0]
if save: if save:
self.col.groups.save(g) self.col.decks.save(g)
for gid in self.col.groups.active(): for did in self.col.decks.active():
update(self.col.groups.get(gid)) update(self.col.decks.get(did))
# update parents too # update parents too
for grp in self.col.groups.parents(self.col.groups.selected()): for grp in self.col.decks.parents(self.col.decks.selected()):
update(grp) update(grp)
def _checkDay(self): def _checkDay(self):
@ -713,15 +713,15 @@ your short-term review workload will become."""))
def revDue(self): def revDue(self):
"True if there are any rev cards due." "True if there are any rev cards due."
return self.col.db.scalar( return self.col.db.scalar(
("select 1 from cards where gid in %s and queue = 2 " ("select 1 from cards where did in %s and queue = 2 "
"and due <= ? limit 1") % self._groupLimit(), "and due <= ? limit 1") % self._deckLimit(),
self.today) self.today)
def newDue(self): def newDue(self):
"True if there are any new cards due." "True if there are any new cards due."
return self.col.db.scalar( return self.col.db.scalar(
("select 1 from cards where gid in %s and queue = 0 " ("select 1 from cards where did in %s and queue = 0 "
"limit 1") % self._groupLimit()) "limit 1") % self._deckLimit())
# Next time reports # Next time reports
########################################################################## ##########################################################################

View file

@ -49,8 +49,8 @@ class CardStats(object):
self.addLine(_("Position"), c.due) self.addLine(_("Position"), c.due)
self.addLine(_("Model"), c.model()['name']) self.addLine(_("Model"), c.model()['name'])
self.addLine(_("Template"), c.template()['name']) self.addLine(_("Template"), c.template()['name'])
self.addLine(_("Current Group"), self.col.groups.name(c.gid)) self.addLine(_("Current Deck"), self.col.decks.name(c.did))
self.addLine(_("Home Group"), self.col.groups.name(c.note().gid)) self.addLine(_("Original Deck"), self.col.decks.name(c.note().did))
self.txt += "</table>" self.txt += "</table>"
return self.txt return self.txt
@ -178,7 +178,7 @@ select (due-:today)/:chunk as day,
sum(case when ivl < 21 then 1 else 0 end), -- yng sum(case when ivl < 21 then 1 else 0 end), -- yng
sum(case when ivl >= 21 then 1 else 0 end) -- mtr sum(case when ivl >= 21 then 1 else 0 end) -- mtr
from cards from cards
where gid in %s and queue = 2 where did in %s and queue = 2
%s %s
group by day order by day""" % (self._limit(), lim), group by day order by day""" % (self._limit(), lim),
today=self.col.sched.today, today=self.col.sched.today,
@ -391,11 +391,11 @@ group by day order by day)""" % lim,
chunk = 30; lim = "" chunk = 30; lim = ""
data = [self.col.db.all(""" data = [self.col.db.all("""
select ivl / :chunk as grp, count() from cards select ivl / :chunk as grp, count() from cards
where gid in %s and queue = 2 %s where did in %s and queue = 2 %s
group by grp group by grp
order by grp""" % (self._limit(), lim), chunk=chunk)] order by grp""" % (self._limit(), lim), chunk=chunk)]
return data + list(self.col.db.first(""" return data + list(self.col.db.first("""
select count(), avg(ivl), max(ivl) from cards where gid in %s and queue = 2""" % select count(), avg(ivl), max(ivl) from cards where did in %s and queue = 2""" %
self._limit())) self._limit()))
# Eases # Eases
@ -539,7 +539,7 @@ group by hour having count() > 30 order by hour""" % lim,
i = [] i = []
(c, f) = self.col.db.first(""" (c, f) = self.col.db.first("""
select count(id), count(distinct nid) from cards select count(id), count(distinct nid) from cards
where gid in %s """ % self._limit()) where did in %s """ % self._limit())
self._line(i, _("Total cards"), c) self._line(i, _("Total cards"), c)
self._line(i, _("Total notes"), f) self._line(i, _("Total notes"), f)
(low, avg, high) = self._factors() (low, avg, high) = self._factors()
@ -548,7 +548,7 @@ where gid in %s """ % self._limit())
self._line(i, _("Average ease factor"), "%d%%" % avg) self._line(i, _("Average ease factor"), "%d%%" % avg)
self._line(i, _("Highest ease factor"), "%d%%" % high) self._line(i, _("Highest ease factor"), "%d%%" % high)
min = self.col.db.scalar( min = self.col.db.scalar(
"select min(id) from cards where gid in %s " % self._limit()) "select min(id) from cards where did in %s " % self._limit())
if min: if min:
self._line(i, _("First card created"), _("%s ago") % fmtTimeSpan( self._line(i, _("First card created"), _("%s ago") % fmtTimeSpan(
time.time() - (min/1000))) time.time() - (min/1000)))
@ -579,7 +579,7 @@ select
min(factor) / 10.0, min(factor) / 10.0,
avg(factor) / 10.0, avg(factor) / 10.0,
max(factor) / 10.0 max(factor) / 10.0
from cards where gid in %s and queue = 2""" % self._limit()) from cards where did in %s and queue = 2""" % self._limit())
def _cards(self): def _cards(self):
return self.col.db.first(""" return self.col.db.first("""
@ -588,7 +588,7 @@ sum(case when queue=2 and ivl >= 21 then 1 else 0 end), -- mtr
sum(case when queue=1 or (queue=2 and ivl < 21) then 1 else 0 end), -- yng/lrn sum(case when queue=1 or (queue=2 and ivl < 21) then 1 else 0 end), -- yng/lrn
sum(case when queue=0 then 1 else 0 end), -- new sum(case when queue=0 then 1 else 0 end), -- new
sum(case when queue=-1 then 1 else 0 end) -- susp sum(case when queue=-1 then 1 else 0 end) -- susp
from cards where gid in %s""" % self._limit()) from cards where did in %s""" % self._limit())
# Tools # Tools
###################################################################### ######################################################################
@ -668,11 +668,11 @@ $(function () {
data=simplejson.dumps(data), conf=simplejson.dumps(conf))) data=simplejson.dumps(data), conf=simplejson.dumps(conf)))
def _limit(self): def _limit(self):
return self.col.sched._groupLimit() return self.col.sched._deckLimit()
def _revlogLimit(self): def _revlogLimit(self):
return ("cid in (select id from cards where gid in %s)" % return ("cid in (select id from cards where did in %s)" %
ids2str(self.col.groups.active())) ids2str(self.col.decks.active()))
def _title(self, title, subtitle=""): def _title(self, title, subtitle=""):
return '<h1>%s</h1>%s' % (title, subtitle) return '<h1>%s</h1>%s' % (title, subtitle)

View file

@ -75,8 +75,8 @@ create table if not exists col (
ls integer not null, ls integer not null,
conf text not null, conf text not null,
models text not null, models text not null,
groups text not null, decks text not null,
gconf text not null, dconf text not null,
tags text not null tags text not null
); );
@ -84,7 +84,7 @@ create table if not exists notes (
id integer primary key, id integer primary key,
guid integer not null, guid integer not null,
mid integer not null, mid integer not null,
gid integer not null, did integer not null,
mod integer not null, mod integer not null,
usn integer not null, usn integer not null,
tags text not null, tags text not null,
@ -102,7 +102,7 @@ create table if not exists nsums (
create table if not exists cards ( create table if not exists cards (
id integer primary key, id integer primary key,
nid integer not null, nid integer not null,
gid integer not null, did integer not null,
ord integer not null, ord integer not null,
mod integer not null, mod integer not null,
usn integer not null, usn integer not null,
@ -145,21 +145,21 @@ values(1,0,0,0,%(v)s,0,0,0,'','{}','','','{}');
def _getColVars(db): def _getColVars(db):
import anki.collection import anki.collection
import anki.groups import anki.decks
g = anki.groups.defaultGroup.copy() g = anki.decks.defaultDeck.copy()
for k,v in anki.groups.defaultTopConf.items(): for k,v in anki.decks.defaultTopConf.items():
g[k] = v g[k] = v
g['id'] = 1 g['id'] = 1
g['name'] = _("Default") g['name'] = _("Default")
g['conf'] = 1 g['conf'] = 1
g['mod'] = intTime() g['mod'] = intTime()
gc = anki.groups.defaultConf.copy() gc = anki.decks.defaultConf.copy()
gc['id'] = 1 gc['id'] = 1
return g, gc, anki.collection.defaultConf.copy() return g, gc, anki.collection.defaultConf.copy()
def _addColVars(db, g, gc, c): def _addColVars(db, g, gc, c):
db.execute(""" db.execute("""
update col set conf = ?, groups = ?, gconf = ?""", update col set conf = ?, decks = ?, dconf = ?""",
simplejson.dumps(c), simplejson.dumps(c),
simplejson.dumps({'1': g}), simplejson.dumps({'1': g}),
simplejson.dumps({'1': gc})) simplejson.dumps({'1': gc}))
@ -173,8 +173,8 @@ create index if not exists ix_cards_usn on cards (usn);
create index if not exists ix_revlog_usn on revlog (usn); create index if not exists ix_revlog_usn on revlog (usn);
-- card spacing, etc -- card spacing, etc
create index if not exists ix_cards_nid on cards (nid); create index if not exists ix_cards_nid on cards (nid);
-- scheduling and group limiting -- scheduling and deck limiting
create index if not exists ix_cards_sched on cards (gid, queue, due); create index if not exists ix_cards_sched on cards (did, queue, due);
-- revlog by card -- revlog by card
create index if not exists ix_revlog_cid on revlog (cid); create index if not exists ix_revlog_cid on revlog (cid);
-- field uniqueness check -- field uniqueness check

View file

@ -95,7 +95,7 @@ class Syncer(object):
def changes(self): def changes(self):
"Bundle up deletions and small objects, and apply if server." "Bundle up deletions and small objects, and apply if server."
d = dict(models=self.getModels(), d = dict(models=self.getModels(),
groups=self.getGroups(), decks=self.getDecks(),
tags=self.getTags(), tags=self.getTags(),
graves=self.getGraves()) graves=self.getGraves())
if self.lnewer: if self.lnewer:
@ -118,7 +118,7 @@ class Syncer(object):
self.mergeGraves(rchg['graves']) self.mergeGraves(rchg['graves'])
# then the other objects # then the other objects
self.mergeModels(rchg['models']) self.mergeModels(rchg['models'])
self.mergeGroups(rchg['groups']) self.mergeDecks(rchg['decks'])
self.mergeTags(rchg['tags']) self.mergeTags(rchg['tags'])
if 'conf' in rchg: if 'conf' in rchg:
self.mergeConf(rchg['conf']) self.mergeConf(rchg['conf'])
@ -134,7 +134,7 @@ select count() from notes where id not in (select distinct nid from cards)""")
for t in "cards", "notes", "revlog", "graves": for t in "cards", "notes", "revlog", "graves":
assert not self.col.db.scalar( assert not self.col.db.scalar(
"select count() from %s where usn = -1" % t) "select count() from %s where usn = -1" % t)
for g in self.col.groups.all(): for g in self.col.decks.all():
assert g['usn'] != -1 assert g['usn'] != -1
for t, usn in self.col.tags.allItems(): for t, usn in self.col.tags.allItems():
assert usn != -1 assert usn != -1
@ -148,8 +148,8 @@ select count() from notes where id not in (select distinct nid from cards)""")
self.col.db.scalar("select count() from graves"), self.col.db.scalar("select count() from graves"),
len(self.col.models.all()), len(self.col.models.all()),
len(self.col.tags.all()), len(self.col.tags.all()),
len(self.col.groups.all()), len(self.col.decks.all()),
len(self.col.groups.allConf()), len(self.col.decks.allConf()),
] ]
def usnLim(self): def usnLim(self):
@ -184,11 +184,11 @@ select id, cid, %d, ease, ivl, lastIvl, factor, time, type
from revlog where %s""" % d) from revlog where %s""" % d)
elif table == "cards": elif table == "cards":
return x(""" return x("""
select id, nid, gid, ord, mod, %d, type, queue, due, ivl, factor, reps, select id, nid, did, ord, mod, %d, type, queue, due, ivl, factor, reps,
lapses, left, edue, flags, data from cards where %s""" % d) lapses, left, edue, flags, data from cards where %s""" % d)
else: else:
return x(""" return x("""
select id, guid, mid, gid, mod, %d, tags, flds, '', flags, data select id, guid, mid, did, mod, %d, tags, flds, '', flags, data
from notes where %s""" % d) from notes where %s""" % d)
def chunk(self): def chunk(self):
@ -230,7 +230,7 @@ from notes where %s""" % d)
def getGraves(self): def getGraves(self):
cards = [] cards = []
notes = [] notes = []
groups = [] decks = []
if self.col.server: if self.col.server:
curs = self.col.db.execute( curs = self.col.db.execute(
"select oid, type from graves where usn >= ?", self.minUsn) "select oid, type from graves where usn >= ?", self.minUsn)
@ -243,18 +243,18 @@ from notes where %s""" % d)
elif type == REM_NOTE: elif type == REM_NOTE:
notes.append(oid) notes.append(oid)
else: else:
groups.append(oid) decks.append(oid)
if not self.col.server: if not self.col.server:
self.col.db.execute("update graves set usn=? where usn=-1", self.col.db.execute("update graves set usn=? where usn=-1",
self.maxUsn) self.maxUsn)
return dict(cards=cards, notes=notes, groups=groups) return dict(cards=cards, notes=notes, decks=decks)
def mergeGraves(self, graves): def mergeGraves(self, graves):
# notes first, so we don't end up with duplicate graves # notes first, so we don't end up with duplicate graves
self.col._remNotes(graves['notes']) self.col._remNotes(graves['notes'])
self.col.remCards(graves['cards']) self.col.remCards(graves['cards'])
for oid in graves['groups']: for oid in graves['decks']:
self.col.groups.rem(oid) self.col.decks.rem(oid)
# Models # Models
########################################################################## ##########################################################################
@ -276,36 +276,36 @@ from notes where %s""" % d)
if not l or r['mod'] > l['mod']: if not l or r['mod'] > l['mod']:
self.col.models.update(r) self.col.models.update(r)
# Groups # Decks
########################################################################## ##########################################################################
def getGroups(self): def getDecks(self):
if self.col.server: if self.col.server:
return [ return [
[g for g in self.col.groups.all() if g['usn'] >= self.minUsn], [g for g in self.col.decks.all() if g['usn'] >= self.minUsn],
[g for g in self.col.groups.allConf() if g['usn'] >= self.minUsn] [g for g in self.col.decks.allConf() if g['usn'] >= self.minUsn]
] ]
else: else:
groups = [g for g in self.col.groups.all() if g['usn'] == -1] decks = [g for g in self.col.decks.all() if g['usn'] == -1]
for g in groups: for g in decks:
g['usn'] = self.maxUsn g['usn'] = self.maxUsn
gconf = [g for g in self.col.groups.allConf() if g['usn'] == -1] dconf = [g for g in self.col.decks.allConf() if g['usn'] == -1]
for g in gconf: for g in dconf:
g['usn'] = self.maxUsn g['usn'] = self.maxUsn
self.col.groups.save() self.col.decks.save()
return [groups, gconf] return [decks, dconf]
def mergeGroups(self, rchg): def mergeDecks(self, rchg):
for r in rchg[0]: for r in rchg[0]:
l = self.col.groups.get(r['id'], False) l = self.col.decks.get(r['id'], False)
# if missing locally or server is newer, update # if missing locally or server is newer, update
if not l or r['mod'] > l['mod']: if not l or r['mod'] > l['mod']:
self.col.groups.update(r) self.col.decks.update(r)
for r in rchg[1]: for r in rchg[1]:
l = self.col.groups.conf(r['id']) l = self.col.decks.conf(r['id'])
# if missing locally or server is newer, update # if missing locally or server is newer, update
if not l or r['mod'] > l['mod']: if not l or r['mod'] > l['mod']:
self.col.groups.updateConf(r) self.col.decks.updateConf(r)
# Tags # Tags
########################################################################## ##########################################################################

View file

@ -171,8 +171,8 @@ class TagManager(object):
query += " where " + lim query += " where " + lim
return self.col.db.list(query, *args) return self.col.db.list(query, *args)
def setGroupForTags(self, yes, no, gid): def setDeckForTags(self, yes, no, did):
nids = self.selTagNids(yes, no) nids = self.selTagNids(yes, no)
self.col.db.execute( self.col.db.execute(
"update cards set gid=?,mod=?,usn=? where nid in "+ids2str(nids), "update cards set did=?,mod=?,usn=? where nid in "+ids2str(nids),
gid, intTime(), self.col.usn()) did, intTime(), self.col.usn())

View file

@ -288,7 +288,7 @@ yesCount from reviewHistory"""):
insert or replace into col select id, cast(created as int), :t, insert or replace into col select id, cast(created as int), :t,
:t, 99, 0, 0, cast(lastSync as int), :t, 99, 0, 0, cast(lastSync as int),
"", "", "", "", "" from decks""", t=intTime()) "", "", "", "", "" from decks""", t=intTime())
# prepare a group to store the old deck options # prepare a deck to store the old deck options
g, gc, conf = _getColVars(db) g, gc, conf = _getColVars(db)
# delete old selective study settings, which we can't auto-upgrade easily # delete old selective study settings, which we can't auto-upgrade easily
keys = ("newActive", "newInactive", "revActive", "revInactive") keys = ("newActive", "newInactive", "revActive", "revInactive")
@ -599,7 +599,7 @@ and ord = ? limit 1""", m['id'], t['ord']):
col.db.execute(""" col.db.execute("""
update cards set due = nid where type=0""") update cards set due = nid where type=0""")
# and failed cards # and failed cards
left = len(col.groups.conf(1)['new']['delays']) left = len(col.decks.conf(1)['new']['delays'])
col.db.execute("update cards set edue = ?, left=? where type = 1", col.db.execute("update cards set edue = ?, left=? where type = 1",
col.sched.today+1, left) col.sched.today+1, left)
# and due cards # and due cards

View file

@ -144,10 +144,10 @@ def test_selective():
assert len(deck.tags.selTagNids(["two", "three"], [])) == 3 assert len(deck.tags.selTagNids(["two", "three"], [])) == 3
assert len(deck.tags.selTagNids(["two", "three"], ["one"])) == 1 assert len(deck.tags.selTagNids(["two", "three"], ["one"])) == 1
assert len(deck.tags.selTagNids(["one", "three"], ["two", "four"])) == 1 assert len(deck.tags.selTagNids(["one", "three"], ["two", "four"])) == 1
deck.tags.setGroupForTags(["three"], [], 3) deck.tags.setDeckForTags(["three"], [], 3)
assert deck.db.scalar("select count() from cards where gid = 3") == 3 assert deck.db.scalar("select count() from cards where did = 3") == 3
deck.tags.setGroupForTags(["one"], [], 2) deck.tags.setDeckForTags(["one"], [], 2)
assert deck.db.scalar("select count() from cards where gid = 2") == 2 assert deck.db.scalar("select count() from cards where did = 2") == 2
def test_addDelTags(): def test_addDelTags():
deck = getEmptyDeck() deck = getEmptyDeck()

96
tests/test_decks.py Normal file
View file

@ -0,0 +1,96 @@
# coding: utf-8
from tests.shared import assertException, getEmptyDeck, testDir
def test_basic():
deck = getEmptyDeck()
# we start with a standard deck
assert len(deck.decks.decks) == 1
# it should have an id of 1
assert deck.decks.name(1)
# create a new deck
parentId = deck.decks.id("new deck")
assert parentId
assert len(deck.decks.decks) == 2
# should get the same id
assert deck.decks.id("new deck") == parentId
# we start with the default deck selected
assert deck.decks.selected() == 1
assert deck.decks.active() == [1]
assert deck.decks.top()['id'] == 1
# we can select a different deck
deck.decks.select(parentId)
assert deck.decks.selected() == parentId
assert deck.decks.active() == [parentId]
assert deck.decks.top()['id'] == parentId
# let's create a child
childId = deck.decks.id("new deck::child")
# it should have been added to the active list
assert deck.decks.selected() == parentId
assert deck.decks.active() == [parentId, childId]
assert deck.decks.top()['id'] == parentId
# we can select the child individually too
deck.decks.select(childId)
assert deck.decks.selected() == childId
assert deck.decks.active() == [childId]
assert deck.decks.top()['id'] == parentId
def test_remove():
deck = getEmptyDeck()
# can't remove the default deck
assertException(AssertionError, lambda: deck.decks.rem(1))
# create a new deck, and add a note/card to it
g1 = deck.decks.id("g1")
f = deck.newNote()
f['Front'] = u"1"
f.did = g1
deck.addNote(f)
c = f.cards()[0]
assert c.did == g1
# by default deleting the deck leaves the cards with an invalid did
assert deck.cardCount() == 1
deck.decks.rem(g1)
assert deck.cardCount() == 1
c.load()
assert c.did == g1
# but if we try to get it, we get the default
assert deck.decks.name(c.did) == "Default"
# let's create another deck and explicitly set the card to it
g2 = deck.decks.id("g2")
c.did = g2; c.flush()
# this time we'll delete the card/note too
deck.decks.rem(g2, cardsToo=True)
assert deck.cardCount() == 0
assert deck.noteCount() == 0
def test_rename():
d = getEmptyDeck()
id = d.decks.id("hello::world")
# should be able to rename into a completely different branch, creating
# parents as necessary
d.decks.rename(d.decks.get(id), "foo::bar")
assert "foo" in d.decks.allNames()
assert "foo::bar" in d.decks.allNames()
assert "hello::world" not in d.decks.allNames()
# create another deck
id = d.decks.id("tmp")
# we can't rename it if it conflicts
assertException(
Exception, lambda: d.decks.rename(d.decks.get(id), "foo"))
# when renaming, the children should be renamed too
d.decks.id("one::two::three")
id = d.decks.id("one")
d.decks.rename(d.decks.get(id), "yo")
for n in "yo", "yo::two", "yo::two::three":
assert n in d.decks.allNames()
def test_topRename():
d = getEmptyDeck()
id = d.decks.id("hello::world")
# when moving to or from top level, properties should be updated
assert 'newSpread' in d.decks.get(d.decks.id("hello"))
assert 'newSpread' not in d.decks.get(d.decks.id("hello::world"))
d.decks.rename(d.decks.get(d.decks.id("hello")), "foo::bar")
assert 'newSpread' not in d.decks.get(d.decks.id("foo::bar"))
d.decks.rename(d.decks.get(d.decks.id("foo::bar")), "hello")
assert 'newSpread' in d.decks.get(d.decks.id("hello"))

View file

@ -104,10 +104,10 @@ def test_findCards():
assert len(deck.findCards("model:basic")) == 5 assert len(deck.findCards("model:basic")) == 5
assert len(deck.findCards("-model:basic")) == 0 assert len(deck.findCards("-model:basic")) == 0
assert len(deck.findCards("-model:foo")) == 5 assert len(deck.findCards("-model:foo")) == 5
# group # deck
assert len(deck.findCards("group:default")) == 5 assert len(deck.findCards("deck:default")) == 5
assert len(deck.findCards("-group:default")) == 0 assert len(deck.findCards("-deck:default")) == 0
assert len(deck.findCards("-group:foo")) == 5 assert len(deck.findCards("-deck:foo")) == 5
# full search # full search
f = deck.newNote() f = deck.newNote()
f['Front'] = u'hello<b>world</b>' f['Front'] = u'hello<b>world</b>'

View file

@ -1,96 +0,0 @@
# coding: utf-8
from tests.shared import assertException, getEmptyDeck, testDir
def test_basic():
deck = getEmptyDeck()
# we start with a standard group
assert len(deck.groups.groups) == 1
# it should have an id of 1
assert deck.groups.name(1)
# create a new group
parentId = deck.groups.id("new group")
assert parentId
assert len(deck.groups.groups) == 2
# should get the same id
assert deck.groups.id("new group") == parentId
# we start with the default group selected
assert deck.groups.selected() == 1
assert deck.groups.active() == [1]
assert deck.groups.top()['id'] == 1
# we can select a different group
deck.groups.select(parentId)
assert deck.groups.selected() == parentId
assert deck.groups.active() == [parentId]
assert deck.groups.top()['id'] == parentId
# let's create a child
childId = deck.groups.id("new group::child")
# it should have been added to the active list
assert deck.groups.selected() == parentId
assert deck.groups.active() == [parentId, childId]
assert deck.groups.top()['id'] == parentId
# we can select the child individually too
deck.groups.select(childId)
assert deck.groups.selected() == childId
assert deck.groups.active() == [childId]
assert deck.groups.top()['id'] == parentId
def test_remove():
deck = getEmptyDeck()
# can't remove the default group
assertException(AssertionError, lambda: deck.groups.rem(1))
# create a new group, and add a note/card to it
g1 = deck.groups.id("g1")
f = deck.newNote()
f['Front'] = u"1"
f.gid = g1
deck.addNote(f)
c = f.cards()[0]
assert c.gid == g1
# by default deleting the group leaves the cards with an invalid gid
assert deck.cardCount() == 1
deck.groups.rem(g1)
assert deck.cardCount() == 1
c.load()
assert c.gid == g1
# but if we try to get it, we get the default
assert deck.groups.name(c.gid) == "Default"
# let's create another group and explicitly set the card to it
g2 = deck.groups.id("g2")
c.gid = g2; c.flush()
# this time we'll delete the card/note too
deck.groups.rem(g2, cardsToo=True)
assert deck.cardCount() == 0
assert deck.noteCount() == 0
def test_rename():
d = getEmptyDeck()
id = d.groups.id("hello::world")
# should be able to rename into a completely different branch, creating
# parents as necessary
d.groups.rename(d.groups.get(id), "foo::bar")
assert "foo" in d.groups.allNames()
assert "foo::bar" in d.groups.allNames()
assert "hello::world" not in d.groups.allNames()
# create another group
id = d.groups.id("tmp")
# we can't rename it if it conflicts
assertException(
Exception, lambda: d.groups.rename(d.groups.get(id), "foo"))
# when renaming, the children should be renamed too
d.groups.id("one::two::three")
id = d.groups.id("one")
d.groups.rename(d.groups.get(id), "yo")
for n in "yo", "yo::two", "yo::two::three":
assert n in d.groups.allNames()
def test_topRename():
d = getEmptyDeck()
id = d.groups.id("hello::world")
# when moving to or from top level, properties should be updated
assert 'newSpread' in d.groups.get(d.groups.id("hello"))
assert 'newSpread' not in d.groups.get(d.groups.id("hello::world"))
d.groups.rename(d.groups.get(d.groups.id("hello")), "foo::bar")
assert 'newSpread' not in d.groups.get(d.groups.id("foo::bar"))
d.groups.rename(d.groups.get(d.groups.id("foo::bar")), "hello")
assert 'newSpread' in d.groups.get(d.groups.id("hello"))

View file

@ -54,29 +54,29 @@ def test_new():
def test_newLimits(): def test_newLimits():
d = getEmptyDeck() d = getEmptyDeck()
# add some notes # add some notes
g2 = d.groups.id("Default::foo") g2 = d.decks.id("Default::foo")
for i in range(30): for i in range(30):
f = d.newNote() f = d.newNote()
f['Front'] = str(i) f['Front'] = str(i)
if i > 4: if i > 4:
f.gid = g2 f.did = g2
d.addNote(f) d.addNote(f)
# give the child group a different configuration # give the child deck a different configuration
c2 = d.groups.confId("new conf") c2 = d.decks.confId("new conf")
d.groups.setConf(d.groups.get(g2), c2) d.decks.setConf(d.decks.get(g2), c2)
d.reset() d.reset()
# both confs have defaulted to a limit of 20 # both confs have defaulted to a limit of 20
assert d.sched.newCount == 20 assert d.sched.newCount == 20
# first card we get comes from parent # first card we get comes from parent
c = d.sched.getCard() c = d.sched.getCard()
assert c.gid == 1 assert c.did == 1
# limit the parent to 10 cards, meaning we get 10 in total # limit the parent to 10 cards, meaning we get 10 in total
conf1 = d.groups.conf(1) conf1 = d.decks.conf(1)
conf1['new']['perDay'] = 10 conf1['new']['perDay'] = 10
d.reset() d.reset()
assert d.sched.newCount == 10 assert d.sched.newCount == 10
# if we limit child to 4, we should get 9 # if we limit child to 4, we should get 9
conf2 = d.groups.conf(g2) conf2 = d.decks.conf(g2)
conf2['new']['perDay'] = 4 conf2['new']['perDay'] = 4
d.reset() d.reset()
assert d.sched.newCount == 9 assert d.sched.newCount == 9
@ -98,7 +98,7 @@ def test_newOrder():
# add first half # add first half
d.addNote(f) d.addNote(f)
# generate second half # generate second half
d.db.execute("update cards set gid = random()") d.db.execute("update cards set did = random()")
d.conf['newPerDay'] = 100 d.conf['newPerDay'] = 100
d.reset() d.reset()
# cards should be sorted by id # cards should be sorted by id
@ -415,8 +415,8 @@ def test_cram():
c.startTimer() c.startTimer()
c.flush() c.flush()
cardcopy = copy.copy(c) cardcopy = copy.copy(c)
d.conf['groups'] = [1] d.conf['decks'] = [1]
d.cramGroups() d.cramDecks()
# first, test with initial intervals preserved # first, test with initial intervals preserved
conf = d.sched._lrnConf(c) conf = d.sched._lrnConf(c)
conf['reset'] = False conf['reset'] = False
@ -452,7 +452,7 @@ def test_cram():
# now try again with ivl rescheduling # now try again with ivl rescheduling
c = copy.copy(cardcopy) c = copy.copy(cardcopy)
c.flush() c.flush()
d.cramGroups() d.cramDecks()
conf = d.sched._lrnConf(c) conf = d.sched._lrnConf(c)
conf['reset'] = False conf['reset'] = False
conf['resched'] = True conf['resched'] = True
@ -467,7 +467,7 @@ def test_cram():
# try with ivl reset # try with ivl reset
c = copy.copy(cardcopy) c = copy.copy(cardcopy)
c.flush() c.flush()
d.cramGroups() d.cramDecks()
conf = d.sched._lrnConf(c) conf = d.sched._lrnConf(c)
conf['reset'] = True conf['reset'] = True
conf['resched'] = True conf['resched'] = True
@ -477,8 +477,8 @@ def test_cram():
assert c.ivl == 1 assert c.ivl == 1
assert c.due == d.sched.today + 1 assert c.due == d.sched.today + 1
# users should be able to cram entire deck too # users should be able to cram entire deck too
d.conf['groups'] = [] d.conf['decks'] = []
d.cramGroups() d.cramDecks()
assert d.sched.cardCounts()[0] > 0 assert d.sched.cardCounts()[0] > 0
def test_cramLimits(): def test_cramLimits():
@ -493,30 +493,30 @@ def test_cramLimits():
c.due = d.sched.today + 1 + i c.due = d.sched.today + 1 + i
c.flush() c.flush()
# the default cram should return all three # the default cram should return all three
d.conf['groups'] = [1] d.conf['decks'] = [1]
d.cramGroups() d.cramDecks()
assert d.sched.cardCounts()[0] == 3 assert d.sched.cardCounts()[0] == 3
# if we start from the day after tomorrow, it should be 2 # if we start from the day after tomorrow, it should be 2
d.cramGroups(min=1) d.cramDecks(min=1)
assert d.sched.cardCounts()[0] == 2 assert d.sched.cardCounts()[0] == 2
# or after 2 days # or after 2 days
d.cramGroups(min=2) d.cramDecks(min=2)
assert d.sched.cardCounts()[0] == 1 assert d.sched.cardCounts()[0] == 1
# we may get nothing # we may get nothing
d.cramGroups(min=3) d.cramDecks(min=3)
assert d.sched.cardCounts()[0] == 0 assert d.sched.cardCounts()[0] == 0
# tomorrow(0) + dayAfter(1) = 2 # tomorrow(0) + dayAfter(1) = 2
d.cramGroups(max=1) d.cramDecks(max=1)
assert d.sched.cardCounts()[0] == 2 assert d.sched.cardCounts()[0] == 2
# if max is tomorrow, we get only one # if max is tomorrow, we get only one
d.cramGroups(max=0) d.cramDecks(max=0)
assert d.sched.cardCounts()[0] == 1 assert d.sched.cardCounts()[0] == 1
# both should work # both should work
d.cramGroups(min=0, max=0) d.cramDecks(min=0, max=0)
assert d.sched.cardCounts()[0] == 1 assert d.sched.cardCounts()[0] == 1
d.cramGroups(min=1, max=1) d.cramDecks(min=1, max=1)
assert d.sched.cardCounts()[0] == 1 assert d.sched.cardCounts()[0] == 1
d.cramGroups(min=0, max=1) d.cramDecks(min=0, max=1)
assert d.sched.cardCounts()[0] == 2 assert d.sched.cardCounts()[0] == 2
def test_adjIvl(): def test_adjIvl():
@ -614,28 +614,28 @@ def test_ordcycle():
def test_cardcounts(): def test_cardcounts():
d = getEmptyDeck() d = getEmptyDeck()
# add a second group # add a second deck
grp = d.groups.id("Default::new group") grp = d.decks.id("Default::new deck")
# for each card type # for each card type
for type in range(3): for type in range(3):
# and each of the groups # and each of the decks
for gid in (1,grp): for did in (1,grp):
# create a new note # create a new note
f = d.newNote() f = d.newNote()
f['Front'] = u"one" f['Front'] = u"one"
d.addNote(f) d.addNote(f)
c = f.cards()[0] c = f.cards()[0]
# set type/gid # set type/did
c.type = type c.type = type
c.queue = type c.queue = type
c.gid = gid c.did = did
c.due = 0 c.due = 0
c.flush() c.flush()
d.reset() d.reset()
# with the default settings, there's no count limit # with the default settings, there's no count limit
assert d.sched.cardCounts() == (2,2,2) assert d.sched.cardCounts() == (2,2,2)
# check limit to one group # check limit to one deck
d.groups.select(grp) d.decks.select(grp)
d.reset() d.reset()
assert d.sched.cardCounts() == (1,1,1) assert d.sched.cardCounts() == (1,1,1)
@ -753,42 +753,42 @@ def test_collapse():
d.sched.answerCard(c, 3) d.sched.answerCard(c, 3)
assert not d.sched.getCard() assert not d.sched.getCard()
def test_groupCounts(): def test_deckCounts():
d = getEmptyDeck() d = getEmptyDeck()
# add a note with default group # add a note with default deck
f = d.newNote() f = d.newNote()
f['Front'] = u"one" f['Front'] = u"one"
d.addNote(f) d.addNote(f)
# and one that's a child # and one that's a child
f = d.newNote() f = d.newNote()
f['Front'] = u"two" f['Front'] = u"two"
default1 = f.gid = d.groups.id("Default::1") default1 = f.did = d.decks.id("Default::1")
d.addNote(f) d.addNote(f)
# make it a review card # make it a review card
c = f.cards()[0] c = f.cards()[0]
c.queue = 2 c.queue = 2
c.due = 0 c.due = 0
c.flush() c.flush()
# add one more with a new group # add one more with a new deck
f = d.newNote() f = d.newNote()
f['Front'] = u"two" f['Front'] = u"two"
foobar = f.gid = d.groups.id("foo::bar") foobar = f.did = d.decks.id("foo::bar")
d.addNote(f) d.addNote(f)
# and one that's a sibling # and one that's a sibling
f = d.newNote() f = d.newNote()
f['Front'] = u"three" f['Front'] = u"three"
foobaz = f.gid = d.groups.id("foo::baz") foobaz = f.did = d.decks.id("foo::baz")
d.addNote(f) d.addNote(f)
d.reset() d.reset()
assert len(d.groups.groups) == 5 assert len(d.decks.decks) == 5
cnts = d.sched.groupCounts() cnts = d.sched.deckCounts()
cnts.sort() cnts.sort()
assert cnts[0] == ["Default", 1, 0, 1] assert cnts[0] == ["Default", 1, 0, 1]
assert cnts[1] == ["Default::1", default1, 1, 0] assert cnts[1] == ["Default::1", default1, 1, 0]
assert cnts[2] == ["foo", d.groups.id("foo"), 0, 0] assert cnts[2] == ["foo", d.decks.id("foo"), 0, 0]
assert cnts[3] == ["foo::bar", foobar, 0, 1] assert cnts[3] == ["foo::bar", foobar, 0, 1]
assert cnts[4] == ["foo::baz", foobaz, 0, 1] assert cnts[4] == ["foo::baz", foobaz, 0, 1]
tree = d.sched.groupCountTree() tree = d.sched.deckCountTree()
assert tree[0][0] == "Default" assert tree[0][0] == "Default"
# sum of child and parent # sum of child and parent
assert tree[0][1] == 1 assert tree[0][1] == 1
@ -799,35 +799,35 @@ def test_groupCounts():
assert tree[0][4][0][1] == default1 assert tree[0][4][0][1] == default1
assert tree[0][4][0][2] == 1 assert tree[0][4][0][2] == 1
assert tree[0][4][0][3] == 0 assert tree[0][4][0][3] == 0
# code should not fail if a card has an invalid group # code should not fail if a card has an invalid deck
c.gid = 12345; c.flush() c.did = 12345; c.flush()
d.sched.groupCounts() d.sched.deckCounts()
d.sched.groupCountTree() d.sched.deckCountTree()
def test_groupTree(): def test_deckTree():
d = getEmptyDeck() d = getEmptyDeck()
d.groups.id("new::b::c") d.decks.id("new::b::c")
d.groups.id("new2") d.decks.id("new2")
# new should not appear twice in tree # new should not appear twice in tree
names = [x[0] for x in d.sched.groupCountTree()] names = [x[0] for x in d.sched.deckCountTree()]
names.remove("new") names.remove("new")
assert "new" not in names assert "new" not in names
def test_groupFlow(): def test_deckFlow():
d = getEmptyDeck() d = getEmptyDeck()
# add a note with default group # add a note with default deck
f = d.newNote() f = d.newNote()
f['Front'] = u"one" f['Front'] = u"one"
d.addNote(f) d.addNote(f)
# and one that's a child # and one that's a child
f = d.newNote() f = d.newNote()
f['Front'] = u"two" f['Front'] = u"two"
default1 = f.gid = d.groups.id("Default::2") default1 = f.did = d.decks.id("Default::2")
d.addNote(f) d.addNote(f)
# and another that's higher up # and another that's higher up
f = d.newNote() f = d.newNote()
f['Front'] = u"three" f['Front'] = u"three"
default1 = f.gid = d.groups.id("Default::1") default1 = f.did = d.decks.id("Default::1")
d.addNote(f) d.addNote(f)
# should get top level one first, then ::1, then ::2 # should get top level one first, then ::1, then ::2
d.reset() d.reset()
@ -839,7 +839,7 @@ def test_groupFlow():
def test_reorder(): def test_reorder():
d = getEmptyDeck() d = getEmptyDeck()
# add a note with default group # add a note with default deck
f = d.newNote() f = d.newNote()
f['Front'] = u"one" f['Front'] = u"one"
d.addNote(f) d.addNote(f)
@ -917,7 +917,7 @@ def test_revlim():
for i in range(5): for i in range(5):
d.sched.answerCard(d.sched.getCard(), 3) d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.repCounts()[2] == 15 assert d.sched.repCounts()[2] == 15
t = d.groups.top() t = d.decks.top()
t['revLim'] = 10 t['revLim'] = 10
d.reset() d.reset()
assert d.sched.repCounts()[2] == 5 assert d.sched.repCounts()[2] == 5

View file

@ -65,9 +65,9 @@ def test_sync():
for t in ("revlog", "notes", "cards", "nsums"): for t in ("revlog", "notes", "cards", "nsums"):
assert d.db.scalar("select count() from %s" % t) == num assert d.db.scalar("select count() from %s" % t) == num
assert len(d.models.all()) == num*2 assert len(d.models.all()) == num*2
# the default group and config have an id of 1, so always 1 # the default deck and config have an id of 1, so always 1
assert len(d.groups.all()) == 1 assert len(d.decks.all()) == 1
assert len(d.groups.gconf) == 1 assert len(d.decks.dconf) == 1
assert len(d.tags.all()) == num assert len(d.tags.all()) == num
check(1) check(1)
origUsn = deck1.usn() origUsn = deck1.usn()
@ -173,35 +173,35 @@ def test_tags():
assert deck1.tags.all() == deck2.tags.all() assert deck1.tags.all() == deck2.tags.all()
@nose.with_setup(setup_modified) @nose.with_setup(setup_modified)
def test_groups(): def test_decks():
test_sync() test_sync()
assert len(deck1.groups.all()) == 1 assert len(deck1.decks.all()) == 1
assert len(deck1.groups.all()) == len(deck2.groups.all()) assert len(deck1.decks.all()) == len(deck2.decks.all())
deck1.groups.id("new") deck1.decks.id("new")
assert len(deck1.groups.all()) != len(deck2.groups.all()) assert len(deck1.decks.all()) != len(deck2.decks.all())
time.sleep(0.1) time.sleep(0.1)
deck2.groups.id("new2") deck2.decks.id("new2")
deck1.save() deck1.save()
deck2.save() deck2.save()
assert client.sync() == "success" assert client.sync() == "success"
assert deck1.tags.all() == deck2.tags.all() assert deck1.tags.all() == deck2.tags.all()
assert len(deck1.groups.all()) == len(deck2.groups.all()) assert len(deck1.decks.all()) == len(deck2.decks.all())
assert len(deck1.groups.all()) == 3 assert len(deck1.decks.all()) == 3
assert deck1.groups.conf(1)['maxTaken'] == 60 assert deck1.decks.conf(1)['maxTaken'] == 60
deck2.groups.conf(1)['maxTaken'] = 30 deck2.decks.conf(1)['maxTaken'] = 30
deck2.groups.save(deck2.groups.conf(1)) deck2.decks.save(deck2.decks.conf(1))
deck2.save() deck2.save()
assert client.sync() == "success" assert client.sync() == "success"
assert deck1.groups.conf(1)['maxTaken'] == 30 assert deck1.decks.conf(1)['maxTaken'] == 30
@nose.with_setup(setup_modified) @nose.with_setup(setup_modified)
def test_conf(): def test_conf():
test_sync() test_sync()
assert deck2.conf['topGroup'] == 1 assert deck2.conf['topDeck'] == 1
deck1.conf['topGroup'] = 2 deck1.conf['topDeck'] = 2
deck1.save() deck1.save()
assert client.sync() == "success" assert client.sync() == "success"
assert deck2.conf['topGroup'] == 2 assert deck2.conf['topDeck'] == 2
@nose.with_setup(setup_modified) @nose.with_setup(setup_modified)
def test_threeway(): def test_threeway():