mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 08:46:37 -04:00
groups -> decks
This commit is contained in:
parent
279a942642
commit
f7790275ce
22 changed files with 482 additions and 482 deletions
|
@ -29,7 +29,7 @@ class Card(object):
|
|||
else:
|
||||
# to flush, set nid, ord, and due
|
||||
self.id = timestampID(col.db, "cards")
|
||||
self.gid = 1
|
||||
self.did = 1
|
||||
self.crt = intTime()
|
||||
self.type = 0
|
||||
self.queue = 0
|
||||
|
@ -45,7 +45,7 @@ class Card(object):
|
|||
def load(self):
|
||||
(self.id,
|
||||
self.nid,
|
||||
self.gid,
|
||||
self.did,
|
||||
self.ord,
|
||||
self.mod,
|
||||
self.usn,
|
||||
|
@ -73,7 +73,7 @@ insert or replace into cards values
|
|||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
self.id,
|
||||
self.nid,
|
||||
self.gid,
|
||||
self.did,
|
||||
self.ord,
|
||||
self.mod,
|
||||
self.usn,
|
||||
|
@ -109,7 +109,7 @@ lapses=?, left=?, edue=? where id = ?""",
|
|||
def _getQA(self, reload=False):
|
||||
if not self._qa or reload:
|
||||
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()]
|
||||
self._qa = self.col._renderQA(data)
|
||||
return self._qa
|
||||
|
@ -128,8 +128,8 @@ lapses=?, left=?, edue=? where id = ?""",
|
|||
def model(self, reload=False):
|
||||
return self._reviewData()[1]
|
||||
|
||||
def groupConf(self):
|
||||
return self.col.groups.conf(self.gid)
|
||||
def deckConf(self):
|
||||
return self.col.decks.conf(self.did)
|
||||
|
||||
def template(self):
|
||||
return self._reviewData()[1]['tmpls'][self.ord]
|
||||
|
@ -140,4 +140,4 @@ lapses=?, left=?, edue=? where id = ?""",
|
|||
def timeTaken(self):
|
||||
"Time taken to answer card, in integer MS."
|
||||
total = int((time.time() - self.timerStarted)*1000)
|
||||
return min(total, self.groupConf()['maxTaken']*1000)
|
||||
return min(total, self.deckConf()['maxTaken']*1000)
|
||||
|
|
|
@ -10,7 +10,7 @@ from anki.hooks import runHook, runFilter
|
|||
from anki.sched import Scheduler
|
||||
from anki.models import ModelManager
|
||||
from anki.media import MediaManager
|
||||
from anki.groups import GroupManager
|
||||
from anki.decks import DeckManager
|
||||
from anki.tags import TagManager
|
||||
from anki.consts import *
|
||||
from anki.errors import AnkiError
|
||||
|
@ -20,9 +20,9 @@ import anki.cards, anki.notes, anki.template, anki.cram, anki.find
|
|||
|
||||
defaultConf = {
|
||||
# scheduling options
|
||||
'activeGroups': [1],
|
||||
'topGroup': 1,
|
||||
'curGroup': 1,
|
||||
'activeDecks': [1],
|
||||
'topDeck': 1,
|
||||
'curDeck': 1,
|
||||
'revOrder': REV_CARDS_RANDOM,
|
||||
# other config
|
||||
'nextPos': 1,
|
||||
|
@ -44,7 +44,7 @@ class _Collection(object):
|
|||
self.clearUndo()
|
||||
self.media = MediaManager(self)
|
||||
self.models = ModelManager(self)
|
||||
self.groups = GroupManager(self)
|
||||
self.decks = DeckManager(self)
|
||||
self.tags = TagManager(self)
|
||||
self.load()
|
||||
if not self.crt:
|
||||
|
@ -78,14 +78,14 @@ class _Collection(object):
|
|||
self.ls,
|
||||
self.conf,
|
||||
models,
|
||||
groups,
|
||||
gconf,
|
||||
decks,
|
||||
dconf,
|
||||
tags) = self.db.first("""
|
||||
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.models.load(models)
|
||||
self.groups.load(groups, gconf)
|
||||
self.decks.load(decks, dconf)
|
||||
self.tags.load(tags)
|
||||
|
||||
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._usn, self.ls, simplejson.dumps(self.conf))
|
||||
self.models.flush()
|
||||
self.groups.flush()
|
||||
self.decks.flush()
|
||||
self.tags.flush()
|
||||
|
||||
def save(self, name=None, mod=None):
|
||||
|
@ -291,8 +291,8 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
|||
data = []
|
||||
ts = maxID(self.db)
|
||||
now = intTime()
|
||||
for nid, mid, gid, flds in self.db.execute(
|
||||
"select id, mid, gid, flds from notes where id in "+snids):
|
||||
for nid, mid, did, flds in self.db.execute(
|
||||
"select id, mid, did, flds from notes where id in "+snids):
|
||||
model = self.models.get(mid)
|
||||
avail = self.models.availOrds(model, flds)
|
||||
ok = []
|
||||
|
@ -300,7 +300,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
|||
if (nid,t['ord']) in have:
|
||||
continue
|
||||
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))
|
||||
ts += 1
|
||||
# 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.nid = note.id
|
||||
card.ord = template['ord']
|
||||
card.gid = template['gid'] or note.gid
|
||||
card.did = template['did'] or note.did
|
||||
card.due = due
|
||||
if 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):
|
||||
"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
|
||||
flist = splitFields(data[6])
|
||||
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['Tags'] = data[5]
|
||||
fields['Model'] = model['name']
|
||||
fields['Group'] = self.groups.name(data[3])
|
||||
fields['Deck'] = self.decks.name(data[3])
|
||||
template = model['tmpls'][data[4]]
|
||||
fields['Template'] = template['name']
|
||||
# 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
|
||||
|
||||
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("""
|
||||
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
|
||||
where c.nid == f.id
|
||||
%s""" % where)
|
||||
|
@ -503,7 +503,7 @@ where c.nid == f.id
|
|||
self.sched = self._stdSched
|
||||
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.sched = anki.cram.CramScheduler(self, order, min, max)
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ REV_CARDS_NEW_FIRST = 2
|
|||
# removal types
|
||||
REM_CARD = 0
|
||||
REM_NOTE = 1
|
||||
REM_GROUP = 2
|
||||
REM_DECK = 2
|
||||
|
||||
# count display
|
||||
COUNT_ANSWERED = 0
|
||||
|
|
|
@ -66,8 +66,8 @@ class CramScheduler(Scheduler):
|
|||
else:
|
||||
maxlim = ""
|
||||
self.newQueue = self.col.db.list("""
|
||||
select id from cards where gid in %s and queue = 2 and due >= %d
|
||||
%s order by %s limit %d""" % (self._groupLimit(),
|
||||
select id from cards where did in %s and queue = 2 and due >= %d
|
||||
%s order by %s limit %d""" % (self._deckLimit(),
|
||||
self.today+1+self.min,
|
||||
maxlim,
|
||||
self.order,
|
||||
|
|
|
@ -9,21 +9,21 @@ from anki.lang import _
|
|||
|
||||
# fixmes:
|
||||
# - 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)
|
||||
# - 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
|
||||
|
||||
# notes:
|
||||
# - it's difficult to enforce valid gids for models/notes/cards, as we
|
||||
# may update the gid locally only to have it overwritten by a more recent
|
||||
# change from somewhere else. to avoid this, we allow invalid gid
|
||||
# references, and treat any invalid gids as the default group.
|
||||
# - deletions of group config force a full sync
|
||||
# - it's difficult to enforce valid dids for models/notes/cards, as we
|
||||
# may update the did locally only to have it overwritten by a more recent
|
||||
# change from somewhere else. to avoid this, we allow invalid did
|
||||
# references, and treat any invalid dids as the default deck.
|
||||
# - deletions of deck config force a full sync
|
||||
|
||||
# these are a cache of the current day's reviews. they may be wrong after a
|
||||
# sync merge if someone reviewed from two locations
|
||||
defaultGroup = {
|
||||
defaultDeck = {
|
||||
'newToday': [0, 0], # currentDay, count
|
||||
'revToday': [0, 0],
|
||||
'lrnToday': [0, 0],
|
||||
|
@ -31,7 +31,7 @@ defaultGroup = {
|
|||
'conf': 1,
|
||||
}
|
||||
|
||||
# configuration only available to top level groups
|
||||
# configuration only available to top level decks
|
||||
defaultTopConf = {
|
||||
'revLim': 100,
|
||||
'newSpread': NEW_CARDS_DISTRIBUTE,
|
||||
|
@ -41,7 +41,7 @@ defaultTopConf = {
|
|||
'curModel': None,
|
||||
}
|
||||
|
||||
# configuration available to all groups
|
||||
# configuration available to all decks
|
||||
defaultConf = {
|
||||
'name': _("Default"),
|
||||
'new': {
|
||||
|
@ -78,7 +78,7 @@ defaultConf = {
|
|||
'usn': 0,
|
||||
}
|
||||
|
||||
class GroupManager(object):
|
||||
class DeckManager(object):
|
||||
|
||||
# Registry save/load
|
||||
#############################################################
|
||||
|
@ -86,13 +86,13 @@ class GroupManager(object):
|
|||
def __init__(self, col):
|
||||
self.col = col
|
||||
|
||||
def load(self, groups, gconf):
|
||||
self.groups = simplejson.loads(groups)
|
||||
self.gconf = simplejson.loads(gconf)
|
||||
def load(self, decks, dconf):
|
||||
self.decks = simplejson.loads(decks)
|
||||
self.dconf = simplejson.loads(dconf)
|
||||
self.changed = False
|
||||
|
||||
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:
|
||||
g['mod'] = intTime()
|
||||
g['usn'] = self.col.usn()
|
||||
|
@ -100,86 +100,86 @@ class GroupManager(object):
|
|||
|
||||
def flush(self):
|
||||
if self.changed:
|
||||
self.col.db.execute("update col set groups=?, gconf=?",
|
||||
simplejson.dumps(self.groups),
|
||||
simplejson.dumps(self.gconf))
|
||||
self.col.db.execute("update col set decks=?, dconf=?",
|
||||
simplejson.dumps(self.decks),
|
||||
simplejson.dumps(self.dconf))
|
||||
|
||||
# Group save/load
|
||||
# Deck save/load
|
||||
#############################################################
|
||||
|
||||
def id(self, name, create=True):
|
||||
"Add a group with NAME. Reuse group if already exists. Return id as int."
|
||||
for id, g in self.groups.items():
|
||||
"Add a deck with NAME. Reuse deck if already exists. Return id as int."
|
||||
for id, g in self.decks.items():
|
||||
if g['name'].lower() == name.lower():
|
||||
return int(id)
|
||||
if not create:
|
||||
return None
|
||||
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()
|
||||
else:
|
||||
# not top level; ensure all parents exist
|
||||
g = {}
|
||||
self._ensureParents(name)
|
||||
for (k,v) in defaultGroup.items():
|
||||
for (k,v) in defaultDeck.items():
|
||||
g[k] = v
|
||||
g['name'] = name
|
||||
while 1:
|
||||
id = intTime(1000)
|
||||
if str(id) not in self.groups:
|
||||
if str(id) not in self.decks:
|
||||
break
|
||||
g['id'] = id
|
||||
self.groups[str(id)] = g
|
||||
self.decks[str(id)] = g
|
||||
self.save(g)
|
||||
self.maybeAddToActive()
|
||||
return int(id)
|
||||
|
||||
def rem(self, gid, cardsToo=False):
|
||||
"Remove the group. If cardsToo, delete any cards inside."
|
||||
assert gid != 1
|
||||
if not str(gid) in self.groups:
|
||||
def rem(self, did, cardsToo=False):
|
||||
"Remove the deck. If cardsToo, delete any cards inside."
|
||||
assert did != 1
|
||||
if not str(did) in self.decks:
|
||||
return
|
||||
# delete children first
|
||||
for name, id in self.children(gid):
|
||||
for name, id in self.children(did):
|
||||
self.rem(id, cardsToo)
|
||||
# delete cards too?
|
||||
if cardsToo:
|
||||
self.col.remCards(self.cids(gid))
|
||||
# delete the group and add a grave
|
||||
del self.groups[str(gid)]
|
||||
self.col._logRem([gid], REM_GROUP)
|
||||
# ensure we have an active group
|
||||
if gid in self.active():
|
||||
self.select(int(self.groups.keys()[0]))
|
||||
self.col.remCards(self.cids(did))
|
||||
# delete the deck and add a grave
|
||||
del self.decks[str(did)]
|
||||
self.col._logRem([did], REM_DECK)
|
||||
# ensure we have an active deck
|
||||
if did in self.active():
|
||||
self.select(int(self.decks.keys()[0]))
|
||||
self.save()
|
||||
|
||||
def allNames(self):
|
||||
"An unsorted list of all group names."
|
||||
return [x['name'] for x in self.groups.values()]
|
||||
"An unsorted list of all deck names."
|
||||
return [x['name'] for x in self.decks.values()]
|
||||
|
||||
def all(self):
|
||||
"A list of all groups."
|
||||
return self.groups.values()
|
||||
"A list of all decks."
|
||||
return self.decks.values()
|
||||
|
||||
def get(self, gid, default=True):
|
||||
id = str(gid)
|
||||
if id in self.groups:
|
||||
return self.groups[id]
|
||||
def get(self, did, default=True):
|
||||
id = str(did)
|
||||
if id in self.decks:
|
||||
return self.decks[id]
|
||||
elif default:
|
||||
return self.groups['1']
|
||||
return self.decks['1']
|
||||
|
||||
def update(self, g):
|
||||
"Add or update an existing group. Used for syncing and merging."
|
||||
self.groups[str(g['id'])] = g
|
||||
"Add or update an existing deck. Used for syncing and merging."
|
||||
self.decks[str(g['id'])] = g
|
||||
self.maybeAddToActive()
|
||||
# mark registry changed, but don't bump mod time
|
||||
self.save()
|
||||
|
||||
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
|
||||
if newName in self.allNames():
|
||||
raise Exception("Group exists")
|
||||
raise Exception("Deck exists")
|
||||
# rename children
|
||||
for grp in self.all():
|
||||
if grp['name'].startswith(g['name'] + "::"):
|
||||
|
@ -209,18 +209,18 @@ class GroupManager(object):
|
|||
s += "::" + p
|
||||
self.id(s)
|
||||
|
||||
# Group configurations
|
||||
# Deck configurations
|
||||
#############################################################
|
||||
|
||||
def allConf(self):
|
||||
"A list of all group config."
|
||||
return self.gconf.values()
|
||||
"A list of all deck config."
|
||||
return self.dconf.values()
|
||||
|
||||
def conf(self, gid):
|
||||
return self.gconf[str(self.groups[str(gid)]['conf'])]
|
||||
def conf(self, did):
|
||||
return self.dconf[str(self.decks[str(did)]['conf'])]
|
||||
|
||||
def updateConf(self, g):
|
||||
self.gconf[str(g['id'])] = g
|
||||
self.dconf[str(g['id'])] = g
|
||||
self.save()
|
||||
|
||||
def confId(self, name):
|
||||
|
@ -228,19 +228,19 @@ class GroupManager(object):
|
|||
c = copy.deepcopy(defaultConf)
|
||||
while 1:
|
||||
id = intTime(1000)
|
||||
if str(id) not in self.gconf:
|
||||
if str(id) not in self.dconf:
|
||||
break
|
||||
c['id'] = id
|
||||
c['name'] = name
|
||||
self.gconf[str(id)] = c
|
||||
self.dconf[str(id)] = c
|
||||
self.save(c)
|
||||
return 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
|
||||
self.col.modSchema()
|
||||
del self.gconf[str(id)]
|
||||
del self.dconf[str(id)]
|
||||
for g in self.all():
|
||||
if str(g['conf']) == str(id):
|
||||
g['conf'] = 1
|
||||
|
@ -250,16 +250,16 @@ class GroupManager(object):
|
|||
grp['conf'] = id
|
||||
self.save(grp)
|
||||
|
||||
# Group utils
|
||||
# Deck utils
|
||||
#############################################################
|
||||
|
||||
def name(self, gid):
|
||||
return self.get(gid)['name']
|
||||
def name(self, did):
|
||||
return self.get(did)['name']
|
||||
|
||||
def setGroup(self, cids, gid):
|
||||
def setDeck(self, cids, did):
|
||||
self.col.db.execute(
|
||||
"update cards set gid=?,usn=?,mod=? where id in "+
|
||||
ids2str(cids), gid, self.col.usn(), intTime())
|
||||
"update cards set did=?,usn=?,mod=? where id in "+
|
||||
ids2str(cids), did, self.col.usn(), intTime())
|
||||
|
||||
|
||||
def maybeAddToActive(self):
|
||||
|
@ -268,59 +268,59 @@ class GroupManager(object):
|
|||
|
||||
def sendHome(self, cids):
|
||||
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),
|
||||
self.col.usn(), intTime(), gid)
|
||||
self.col.usn(), intTime(), did)
|
||||
|
||||
def cids(self, gid):
|
||||
return self.col.db.list("select id from cards where gid=?", gid)
|
||||
def cids(self, did):
|
||||
return self.col.db.list("select id from cards where did=?", did)
|
||||
|
||||
# Group selection
|
||||
# Deck selection
|
||||
#############################################################
|
||||
|
||||
def top(self):
|
||||
"The current top level group as an object."
|
||||
g = self.get(self.col.conf['topGroup'])
|
||||
"The current top level deck as an object."
|
||||
g = self.get(self.col.conf['topDeck'])
|
||||
return g
|
||||
|
||||
def active(self):
|
||||
"The currrently active gids."
|
||||
return self.col.conf['activeGroups']
|
||||
"The currrently active dids."
|
||||
return self.col.conf['activeDecks']
|
||||
|
||||
def selected(self):
|
||||
"The currently selected gid."
|
||||
return self.col.conf['curGroup']
|
||||
"The currently selected did."
|
||||
return self.col.conf['curDeck']
|
||||
|
||||
def current(self):
|
||||
return self.get(self.selected())
|
||||
|
||||
def select(self, gid):
|
||||
def select(self, did):
|
||||
"Select a new branch."
|
||||
# save the top level group
|
||||
name = self.groups[str(gid)]['name']
|
||||
self.col.conf['topGroup'] = self._topFor(name)
|
||||
# current group
|
||||
self.col.conf['curGroup'] = gid
|
||||
# and active groups (current + all children)
|
||||
actv = self.children(gid)
|
||||
# save the top level deck
|
||||
name = self.decks[str(did)]['name']
|
||||
self.col.conf['topDeck'] = self._topFor(name)
|
||||
# current deck
|
||||
self.col.conf['curDeck'] = did
|
||||
# and active decks (current + all children)
|
||||
actv = self.children(did)
|
||||
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):
|
||||
"All children of gid, as (name, id)."
|
||||
name = self.get(gid)['name']
|
||||
def children(self, did):
|
||||
"All children of did, as (name, id)."
|
||||
name = self.get(did)['name']
|
||||
actv = []
|
||||
for g in self.all():
|
||||
if g['name'].startswith(name + "::"):
|
||||
actv.append((g['name'], g['id']))
|
||||
return actv
|
||||
|
||||
def parents(self, gid):
|
||||
"All parents of gid."
|
||||
path = self.get(gid)['name'].split("::")
|
||||
def parents(self, did):
|
||||
"All parents of did."
|
||||
path = self.get(did)['name'].split("::")
|
||||
return [self.get(x) for x in path[:-1]]
|
||||
|
||||
def _topFor(self, name):
|
||||
"The top level gid for NAME."
|
||||
"The top level did for NAME."
|
||||
path = name.split("::")
|
||||
return self.id(path[0])
|
18
anki/find.py
18
anki/find.py
|
@ -13,7 +13,7 @@ SEARCH_NID = 3
|
|||
SEARCH_TEMPLATE = 4
|
||||
SEARCH_FIELD = 5
|
||||
SEARCH_MODEL = 6
|
||||
SEARCH_GROUP = 7
|
||||
SEARCH_DECK = 7
|
||||
|
||||
# Tools
|
||||
##########################################################################
|
||||
|
@ -121,8 +121,8 @@ order by %s""" % (lim, sort)
|
|||
self._findField(token, isNeg)
|
||||
elif type == SEARCH_MODEL:
|
||||
self._findModel(token, isNeg)
|
||||
elif type == SEARCH_GROUP:
|
||||
self._findGroup(token, isNeg)
|
||||
elif type == SEARCH_DECK:
|
||||
self._findDeck(token, isNeg)
|
||||
else:
|
||||
self._findText(token, isNeg, c)
|
||||
|
||||
|
@ -191,10 +191,10 @@ order by %s""" % (lim, sort)
|
|||
ids.append(m['id'])
|
||||
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 ""
|
||||
id = self.col.groups.id(val, create=False) or 0
|
||||
self.lims['card'].append("c.gid %s= %s" % (extra, id))
|
||||
id = self.col.decks.id(val, create=False) or 0
|
||||
self.lims['card'].append("c.did %s= %s" % (extra, id))
|
||||
|
||||
def _findTemplate(self, val, isNeg):
|
||||
lims = []
|
||||
|
@ -329,9 +329,9 @@ where mid in %s and flds like ? escape '\\'""" % (
|
|||
elif token['value'].startswith("model:"):
|
||||
token['value'] = token['value'][6:].lower()
|
||||
type = SEARCH_MODEL
|
||||
elif token['value'].startswith("group:"):
|
||||
token['value'] = token['value'][6:].lower()
|
||||
type = SEARCH_GROUP
|
||||
elif token['value'].startswith("deck:"):
|
||||
token['value'] = token['value'][5:].lower()
|
||||
type = SEARCH_DECK
|
||||
elif token['value'].startswith("nid:") and len(token['value']) > 4:
|
||||
dec = token['value'][4:]
|
||||
try:
|
||||
|
|
|
@ -27,7 +27,7 @@ class Anki1Importer(Anki2Importer):
|
|||
# merge
|
||||
deck.close()
|
||||
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
|
||||
Anki2Importer.run(self, mdir)
|
||||
|
||||
|
|
|
@ -14,13 +14,13 @@ from anki.importing.base import Importer
|
|||
# - compare notes by guid
|
||||
# - compare models by schema signature
|
||||
# - compare cards by note guid + ordinal
|
||||
# - compare groups by name
|
||||
# - compare decks by name
|
||||
#
|
||||
|
||||
class Anki2Importer(Importer):
|
||||
|
||||
needMapper = False
|
||||
groupPrefix = None
|
||||
deckPrefix = None
|
||||
needCards = True
|
||||
|
||||
def run(self, media=None):
|
||||
|
@ -38,10 +38,10 @@ class Anki2Importer(Importer):
|
|||
self.src = Collection(self.file, queue=False)
|
||||
|
||||
def _import(self):
|
||||
self._groups = {}
|
||||
if self.groupPrefix:
|
||||
id = self.dst.groups.id(self.groupPrefix)
|
||||
self.dst.groups.select(id)
|
||||
self._decks = {}
|
||||
if self.deckPrefix:
|
||||
id = self.dst.decks.id(self.deckPrefix)
|
||||
self.dst.decks.select(id)
|
||||
self._prepareTS()
|
||||
self._prepareModels()
|
||||
self._importNotes()
|
||||
|
@ -76,7 +76,7 @@ class Anki2Importer(Importer):
|
|||
# rewrite internal ids, models, etc
|
||||
note[0] = self.ts()
|
||||
note[2] = lmid
|
||||
note[3] = self._gid(note[3])
|
||||
note[3] = self._did(note[3])
|
||||
note[4] = intTime()
|
||||
note[5] = -1 # usn
|
||||
add.append(note)
|
||||
|
@ -136,27 +136,27 @@ class Anki2Importer(Importer):
|
|||
else:
|
||||
dst['vers'] = [src['id']]
|
||||
|
||||
# Groups
|
||||
# Decks
|
||||
######################################################################
|
||||
|
||||
def _gid(self, gid):
|
||||
"Given gid in src col, return local id."
|
||||
def _did(self, did):
|
||||
"Given did in src col, return local id."
|
||||
# already converted?
|
||||
if gid in self._groups:
|
||||
return self._groups[gid]
|
||||
if did in self._decks:
|
||||
return self._decks[did]
|
||||
# get the name in src
|
||||
g = self.src.groups.get(gid)
|
||||
g = self.src.decks.get(did)
|
||||
name = g['name']
|
||||
# if there's a prefix, replace the top level group
|
||||
if self.groupPrefix:
|
||||
# if there's a prefix, replace the top level deck
|
||||
if self.deckPrefix:
|
||||
tmpname = "::".join(name.split("::")[1:])
|
||||
name = self.groupPrefix
|
||||
name = self.deckPrefix
|
||||
if tmpname:
|
||||
name += "::" + name
|
||||
# create in local
|
||||
newid = self.dst.groups.id(name)
|
||||
# add to group map and return
|
||||
self._groups[gid] = newid
|
||||
newid = self.dst.decks.id(name)
|
||||
# add to deck map and return
|
||||
self._decks[did] = newid
|
||||
return newid
|
||||
|
||||
# Cards
|
||||
|
@ -199,7 +199,7 @@ class Anki2Importer(Importer):
|
|||
# update cid, nid, etc
|
||||
card[0] = self.ts()
|
||||
card[1] = self._notes[guid][0]
|
||||
card[2] = self._gid(card[2])
|
||||
card[2] = self._did(card[2])
|
||||
card[4] = intTime()
|
||||
cards.append(card)
|
||||
# we need to import revlog, rewriting card ids
|
||||
|
|
|
@ -15,7 +15,7 @@ from anki.consts import *
|
|||
|
||||
defaultModel = {
|
||||
'sortf': 0,
|
||||
'gid': 1,
|
||||
'did': 1,
|
||||
'clozectx': False,
|
||||
'newOrder': NEW_CARDS_DUE,
|
||||
'latexPre': """\
|
||||
|
@ -52,7 +52,7 @@ defaultTemplate = {
|
|||
'qfmt': "",
|
||||
'afmt': "",
|
||||
'typeAns': None,
|
||||
'gid': None,
|
||||
'did': None,
|
||||
}
|
||||
|
||||
class ModelManager(object):
|
||||
|
@ -90,16 +90,16 @@ class ModelManager(object):
|
|||
def current(self):
|
||||
"Get current model."
|
||||
try:
|
||||
m = self.get(self.col.groups.top()['curModel'])
|
||||
m = self.get(self.col.decks.top()['curModel'])
|
||||
assert m
|
||||
return m
|
||||
except:
|
||||
return self.models.values()[0]
|
||||
|
||||
def setCurrent(self, m):
|
||||
t = self.col.groups.top()
|
||||
t = self.col.decks.top()
|
||||
t['curModel'] = m['id']
|
||||
self.col.groups.save(t)
|
||||
self.col.decks.save(t)
|
||||
|
||||
def get(self, id):
|
||||
"Get model with ID, or None."
|
||||
|
|
|
@ -19,7 +19,7 @@ class Note(object):
|
|||
self.id = timestampID(col.db, "notes")
|
||||
self.guid = guid64()
|
||||
self._model = model
|
||||
self.gid = model['gid']
|
||||
self.did = model['did']
|
||||
self.mid = model['id']
|
||||
self.tags = []
|
||||
self.fields = [""] * len(self._model['flds'])
|
||||
|
@ -30,14 +30,14 @@ class Note(object):
|
|||
def load(self):
|
||||
(self.guid,
|
||||
self.mid,
|
||||
self.gid,
|
||||
self.did,
|
||||
self.mod,
|
||||
self.usn,
|
||||
self.tags,
|
||||
self.fields,
|
||||
self.flags,
|
||||
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)
|
||||
self.fields = splitFields(self.fields)
|
||||
self.tags = self.col.tags.split(self.tags)
|
||||
|
@ -53,7 +53,7 @@ from notes where id = ?""", self.id)
|
|||
tags = self.stringTags()
|
||||
res = self.col.db.execute("""
|
||||
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.joinedFields(), sfld, self.flags, self.data)
|
||||
self.id = res.lastrowid
|
||||
|
@ -84,10 +84,10 @@ insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|||
def model(self):
|
||||
return self._model
|
||||
|
||||
def updateCardGids(self):
|
||||
def updateCardDids(self):
|
||||
for c in self.cards():
|
||||
if c.gid != self.gid and not c.template()['gid']:
|
||||
c.gid = self.gid
|
||||
if c.did != self.did and not c.template()['did']:
|
||||
c.did = self.did
|
||||
c.flush()
|
||||
|
||||
# Dict interface
|
||||
|
|
168
anki/sched.py
168
anki/sched.py
|
@ -22,7 +22,7 @@ class Scheduler(object):
|
|||
self.col = col
|
||||
self.queueLimit = 50
|
||||
self.reportLimit = 1000
|
||||
# fixme: replace reps with group based counts
|
||||
# fixme: replace reps with deck based counts
|
||||
self.reps = 0
|
||||
self._updateCutoff()
|
||||
|
||||
|
@ -78,10 +78,10 @@ class Scheduler(object):
|
|||
"Return counts over next DAYS. Includes today."
|
||||
daysd = dict(self.col.db.all("""
|
||||
select due, count() from cards
|
||||
where gid in %s and queue = 2
|
||||
where did in %s and queue = 2
|
||||
and due between ? and ?
|
||||
group by due
|
||||
order by due""" % self._groupLimit(),
|
||||
order by due""" % self._deckLimit(),
|
||||
self.today,
|
||||
self.today+days-1))
|
||||
for d in range(days):
|
||||
|
@ -111,34 +111,34 @@ order by due""" % self._groupLimit(),
|
|||
|
||||
def _updateStats(self, card, type, cnt=1):
|
||||
key = type+"Today"
|
||||
for g in ([self.col.groups.get(card.gid)] +
|
||||
self.col.groups.parents(card.gid)):
|
||||
for g in ([self.col.decks.get(card.did)] +
|
||||
self.col.decks.parents(card.did)):
|
||||
# add
|
||||
g[key][1] += cnt
|
||||
self.col.groups.save(g)
|
||||
self.col.decks.save(g)
|
||||
|
||||
# Group counts
|
||||
# Deck counts
|
||||
##########################################################################
|
||||
|
||||
def groupCounts(self):
|
||||
"Returns [groupname, gid, hasDue, hasNew]"
|
||||
# find groups with 1 or more due cards
|
||||
gids = {}
|
||||
for g in self.col.groups.all():
|
||||
hasDue = self._groupHasLrn(g['id']) or self._groupHasRev(g['id'])
|
||||
hasNew = self._groupHasNew(g['id'])
|
||||
gids[g['id']] = [hasDue or 0, hasNew or 0]
|
||||
return [[grp['name'], int(gid)]+gids[int(gid)] #.get(int(gid))
|
||||
for (gid, grp) in self.col.groups.groups.items()]
|
||||
def deckCounts(self):
|
||||
"Returns [deckname, did, hasDue, hasNew]"
|
||||
# find decks with 1 or more due cards
|
||||
dids = {}
|
||||
for g in self.col.decks.all():
|
||||
hasDue = self._deckHasLrn(g['id']) or self._deckHasRev(g['id'])
|
||||
hasNew = self._deckHasNew(g['id'])
|
||||
dids[g['id']] = [hasDue or 0, hasNew or 0]
|
||||
return [[grp['name'], int(did)]+dids[int(did)] #.get(int(did))
|
||||
for (did, grp) in self.col.decks.decks.items()]
|
||||
|
||||
def groupCountTree(self):
|
||||
return self._groupChildren(self.groupCounts())
|
||||
def deckCountTree(self):
|
||||
return self._groupChildren(self.deckCounts())
|
||||
|
||||
def groupTree(self):
|
||||
def deckTree(self):
|
||||
"Like the count tree without the counts. Faster."
|
||||
return self._groupChildren(
|
||||
[[grp['name'], int(gid), 0, 0, 0]
|
||||
for (gid, grp) in self.col.groups.groups.items()])
|
||||
[[grp['name'], int(did), 0, 0, 0]
|
||||
for (did, grp) in self.col.decks.decks.items()])
|
||||
|
||||
def _groupChildren(self, grps):
|
||||
# first, split the group names into components
|
||||
|
@ -156,14 +156,14 @@ order by due""" % self._groupLimit(),
|
|||
return grp[0][0]
|
||||
for (head, tail) in itertools.groupby(grps, key=key):
|
||||
tail = list(tail)
|
||||
gid = None
|
||||
did = None
|
||||
rev = 0
|
||||
new = 0
|
||||
children = []
|
||||
for c in tail:
|
||||
if len(c[0]) == 1:
|
||||
# current node
|
||||
gid = c[1]
|
||||
did = c[1]
|
||||
rev += c[2]
|
||||
new += c[3]
|
||||
else:
|
||||
|
@ -175,7 +175,7 @@ order by due""" % self._groupLimit(),
|
|||
for ch in children:
|
||||
rev += ch[2]
|
||||
new += ch[3]
|
||||
tree.append((head, gid, rev, new, children))
|
||||
tree.append((head, did, rev, new, children))
|
||||
return tuple(tree)
|
||||
|
||||
# Getting the next card
|
||||
|
@ -207,35 +207,35 @@ order by due""" % self._groupLimit(),
|
|||
def _resetNewCount(self):
|
||||
self.newCount = 0
|
||||
pcounts = {}
|
||||
# for each of the active groups
|
||||
for gid in self.col.groups.active():
|
||||
# get the individual group's limit
|
||||
lim = self._groupNewLimitSingle(self.col.groups.get(gid))
|
||||
# for each of the active decks
|
||||
for did in self.col.decks.active():
|
||||
# get the individual deck's limit
|
||||
lim = self._deckNewLimitSingle(self.col.decks.get(did))
|
||||
if not lim:
|
||||
continue
|
||||
# check the parents
|
||||
parents = self.col.groups.parents(gid)
|
||||
parents = self.col.decks.parents(did)
|
||||
for p in parents:
|
||||
# add if missing
|
||||
if p['id'] not in pcounts:
|
||||
pcounts[p['id']] = self._groupNewLimitSingle(p)
|
||||
pcounts[p['id']] = self._deckNewLimitSingle(p)
|
||||
# take minimum of child and parent
|
||||
lim = min(pcounts[p['id']], lim)
|
||||
# see how many cards we actually have
|
||||
cnt = self.col.db.scalar("""
|
||||
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
|
||||
for p in parents:
|
||||
pcounts[p['id']] -= cnt
|
||||
# we may also be a parent
|
||||
pcounts[gid] = lim - cnt
|
||||
pcounts[did] = lim - cnt
|
||||
# and add to running total
|
||||
self.newCount += cnt
|
||||
|
||||
def _resetNew(self):
|
||||
self._resetNewCount()
|
||||
self.newGids = self.col.groups.active()
|
||||
self.newDids = self.col.decks.active()
|
||||
self._newQueue = []
|
||||
self._updateNewCardRatio()
|
||||
|
||||
|
@ -244,25 +244,25 @@ gid = ? and queue = 0 limit ?)""", gid, lim)
|
|||
return True
|
||||
if not self.newCount:
|
||||
return False
|
||||
while self.newGids:
|
||||
gid = self.newGids[0]
|
||||
lim = min(self.queueLimit, self._groupNewLimit(gid))
|
||||
while self.newDids:
|
||||
did = self.newDids[0]
|
||||
lim = min(self.queueLimit, self._deckNewLimit(did))
|
||||
if lim:
|
||||
# fill the queue with the current gid
|
||||
# fill the queue with the current did
|
||||
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:
|
||||
self._newQueue.reverse()
|
||||
return True
|
||||
# nothing left in the group; move to next
|
||||
self.newGids.pop(0)
|
||||
# nothing left in the deck; move to next
|
||||
self.newDids.pop(0)
|
||||
|
||||
def _getNewCard(self):
|
||||
if not self._fillNew():
|
||||
return
|
||||
(id, due) = self._newQueue.pop()
|
||||
# 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:
|
||||
n = len(self._newQueue)
|
||||
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
|
||||
|
||||
def _updateNewCardRatio(self):
|
||||
if self.col.groups.top()['newSpread'] == NEW_CARDS_DISTRIBUTE:
|
||||
if self.col.decks.top()['newSpread'] == NEW_CARDS_DISTRIBUTE:
|
||||
if self.newCount:
|
||||
self.newCardModulus = (
|
||||
(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."
|
||||
if not self.newCount:
|
||||
return False
|
||||
if self.col.groups.top()['newSpread'] == NEW_CARDS_LAST:
|
||||
if self.col.decks.top()['newSpread'] == NEW_CARDS_LAST:
|
||||
return False
|
||||
elif self.col.groups.top()['newSpread'] == NEW_CARDS_FIRST:
|
||||
elif self.col.decks.top()['newSpread'] == NEW_CARDS_FIRST:
|
||||
return True
|
||||
elif self.newCardModulus:
|
||||
return self.reps and self.reps % self.newCardModulus == 0
|
||||
|
||||
def _groupHasNew(self, gid):
|
||||
if not self._groupNewLimit(gid):
|
||||
def _deckHasNew(self, did):
|
||||
if not self._deckNewLimit(did):
|
||||
return False
|
||||
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):
|
||||
sel = self.col.groups.get(gid)
|
||||
def _deckNewLimit(self, did):
|
||||
sel = self.col.decks.get(did)
|
||||
lim = -1
|
||||
# for the group and each of its parents
|
||||
for g in [sel] + self.col.groups.parents(gid):
|
||||
rem = self._groupNewLimitSingle(g)
|
||||
# for the deck and each of its parents
|
||||
for g in [sel] + self.col.decks.parents(did):
|
||||
rem = self._deckNewLimitSingle(g)
|
||||
if lim == -1:
|
||||
lim = rem
|
||||
else:
|
||||
lim = min(rem, lim)
|
||||
return lim
|
||||
|
||||
def _groupNewLimitSingle(self, g):
|
||||
c = self.col.groups.conf(g['id'])
|
||||
def _deckNewLimitSingle(self, g):
|
||||
c = self.col.decks.conf(g['id'])
|
||||
return max(0, c['new']['perDay'] - g['newToday'][1])
|
||||
|
||||
# Learning queue
|
||||
|
@ -324,8 +324,8 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
|
|||
def _resetLrnCount(self):
|
||||
(self.lrnCount, self.lrnRepCount) = self.col.db.first("""
|
||||
select count(), sum(left) from (select left from cards where
|
||||
gid in %s and queue = 1 and due < ? limit %d)""" % (
|
||||
self._groupLimit(), self.reportLimit),
|
||||
did in %s and queue = 1 and due < ? limit %d)""" % (
|
||||
self._deckLimit(), self.reportLimit),
|
||||
self.dayCutoff)
|
||||
self.lrnRepCount = self.lrnRepCount or 0
|
||||
|
||||
|
@ -340,9 +340,9 @@ gid in %s and queue = 1 and due < ? limit %d)""" % (
|
|||
return True
|
||||
self._lrnQueue = self.col.db.all("""
|
||||
select due, id from cards where
|
||||
gid in %s and queue = 1 and due < :lim
|
||||
limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
|
||||
# as it arrives sorted by gid first, we need to sort it
|
||||
did in %s and queue = 1 and due < :lim
|
||||
limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
|
||||
# as it arrives sorted by did first, we need to sort it
|
||||
self._lrnQueue.sort()
|
||||
return self._lrnQueue
|
||||
|
||||
|
@ -350,7 +350,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
|
|||
if self._fillLrn():
|
||||
cutoff = time.time()
|
||||
if collapse:
|
||||
cutoff += self.col.groups.top()['collapseTime']
|
||||
cutoff += self.col.decks.top()['collapseTime']
|
||||
if self._lrnQueue[0][0] < cutoff:
|
||||
id = heappop(self._lrnQueue)[1]
|
||||
self.lrnCount -= 1
|
||||
|
@ -459,29 +459,29 @@ where queue = 1 and type = 2
|
|||
%s
|
||||
""" % (intTime(), self.col.usn(), extra))
|
||||
|
||||
def _groupHasLrn(self, gid):
|
||||
def _deckHasLrn(self, did):
|
||||
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",
|
||||
gid, intTime() + self.col.groups.top()['collapseTime'])
|
||||
did, intTime() + self.col.decks.top()['collapseTime'])
|
||||
|
||||
# Reviews
|
||||
##########################################################################
|
||||
|
||||
def _groupHasRev(self, gid):
|
||||
def _deckHasRev(self, did):
|
||||
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",
|
||||
gid, self.today)
|
||||
did, self.today)
|
||||
|
||||
def _resetRevCount(self):
|
||||
top = self.col.groups.top()
|
||||
top = self.col.decks.top()
|
||||
lim = min(self.reportLimit,
|
||||
max(0, top['revLim'] - top['revToday'][1]))
|
||||
self.revCount = self.col.db.scalar("""
|
||||
select count() from (select id from cards where
|
||||
gid in %s and queue = 2 and due <= :day limit %d)""" % (
|
||||
self._groupLimit(), lim), day=self.today)
|
||||
did in %s and queue = 2 and due <= :day limit %d)""" % (
|
||||
self._deckLimit(), lim), day=self.today)
|
||||
|
||||
def _resetRev(self):
|
||||
self._resetRevCount()
|
||||
|
@ -494,8 +494,8 @@ gid in %s and queue = 2 and due <= :day limit %d)""" % (
|
|||
return True
|
||||
self._revQueue = self.col.db.list("""
|
||||
select id from cards where
|
||||
gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
|
||||
self._groupLimit(), self._revOrder(), self.queueLimit),
|
||||
did in %s and queue = 2 and due <= :lim %s limit %d""" % (
|
||||
self._deckLimit(), self._revOrder(), self.queueLimit),
|
||||
lim=self.today)
|
||||
if not self.col.conf['revOrder']:
|
||||
r = random.Random()
|
||||
|
@ -653,10 +653,10 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
|
|||
##########################################################################
|
||||
|
||||
def _cardConf(self, card):
|
||||
return self.col.groups.conf(card.gid)
|
||||
return self.col.decks.conf(card.did)
|
||||
|
||||
def _groupLimit(self):
|
||||
return ids2str(self.col.groups.active())
|
||||
def _deckLimit(self):
|
||||
return ids2str(self.col.decks.active())
|
||||
|
||||
# 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)
|
||||
# end of day cutoff
|
||||
self.dayCutoff = self.col.crt + (self.today+1)*86400
|
||||
# update all selected groups
|
||||
# update all selected decks
|
||||
def update(g):
|
||||
save = False
|
||||
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
|
||||
g[key] = [self.today, 0]
|
||||
if save:
|
||||
self.col.groups.save(g)
|
||||
for gid in self.col.groups.active():
|
||||
update(self.col.groups.get(gid))
|
||||
self.col.decks.save(g)
|
||||
for did in self.col.decks.active():
|
||||
update(self.col.decks.get(did))
|
||||
# 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)
|
||||
|
||||
def _checkDay(self):
|
||||
|
@ -713,15 +713,15 @@ your short-term review workload will become."""))
|
|||
def revDue(self):
|
||||
"True if there are any rev cards due."
|
||||
return self.col.db.scalar(
|
||||
("select 1 from cards where gid in %s and queue = 2 "
|
||||
"and due <= ? limit 1") % self._groupLimit(),
|
||||
("select 1 from cards where did in %s and queue = 2 "
|
||||
"and due <= ? limit 1") % self._deckLimit(),
|
||||
self.today)
|
||||
|
||||
def newDue(self):
|
||||
"True if there are any new cards due."
|
||||
return self.col.db.scalar(
|
||||
("select 1 from cards where gid in %s and queue = 0 "
|
||||
"limit 1") % self._groupLimit())
|
||||
("select 1 from cards where did in %s and queue = 0 "
|
||||
"limit 1") % self._deckLimit())
|
||||
|
||||
# Next time reports
|
||||
##########################################################################
|
||||
|
|
|
@ -49,8 +49,8 @@ class CardStats(object):
|
|||
self.addLine(_("Position"), c.due)
|
||||
self.addLine(_("Model"), c.model()['name'])
|
||||
self.addLine(_("Template"), c.template()['name'])
|
||||
self.addLine(_("Current Group"), self.col.groups.name(c.gid))
|
||||
self.addLine(_("Home Group"), self.col.groups.name(c.note().gid))
|
||||
self.addLine(_("Current Deck"), self.col.decks.name(c.did))
|
||||
self.addLine(_("Original Deck"), self.col.decks.name(c.note().did))
|
||||
self.txt += "</table>"
|
||||
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) -- mtr
|
||||
from cards
|
||||
where gid in %s and queue = 2
|
||||
where did in %s and queue = 2
|
||||
%s
|
||||
group by day order by day""" % (self._limit(), lim),
|
||||
today=self.col.sched.today,
|
||||
|
@ -391,11 +391,11 @@ group by day order by day)""" % lim,
|
|||
chunk = 30; lim = ""
|
||||
data = [self.col.db.all("""
|
||||
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
|
||||
order by grp""" % (self._limit(), lim), chunk=chunk)]
|
||||
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()))
|
||||
|
||||
# Eases
|
||||
|
@ -539,7 +539,7 @@ group by hour having count() > 30 order by hour""" % lim,
|
|||
i = []
|
||||
(c, f) = self.col.db.first("""
|
||||
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 notes"), f)
|
||||
(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, _("Highest ease factor"), "%d%%" % high)
|
||||
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:
|
||||
self._line(i, _("First card created"), _("%s ago") % fmtTimeSpan(
|
||||
time.time() - (min/1000)))
|
||||
|
@ -579,7 +579,7 @@ select
|
|||
min(factor) / 10.0,
|
||||
avg(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):
|
||||
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=0 then 1 else 0 end), -- new
|
||||
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
|
||||
######################################################################
|
||||
|
@ -668,11 +668,11 @@ $(function () {
|
|||
data=simplejson.dumps(data), conf=simplejson.dumps(conf)))
|
||||
|
||||
def _limit(self):
|
||||
return self.col.sched._groupLimit()
|
||||
return self.col.sched._deckLimit()
|
||||
|
||||
def _revlogLimit(self):
|
||||
return ("cid in (select id from cards where gid in %s)" %
|
||||
ids2str(self.col.groups.active()))
|
||||
return ("cid in (select id from cards where did in %s)" %
|
||||
ids2str(self.col.decks.active()))
|
||||
|
||||
def _title(self, title, subtitle=""):
|
||||
return '<h1>%s</h1>%s' % (title, subtitle)
|
||||
|
|
|
@ -75,8 +75,8 @@ create table if not exists col (
|
|||
ls integer not null,
|
||||
conf text not null,
|
||||
models text not null,
|
||||
groups text not null,
|
||||
gconf text not null,
|
||||
decks text not null,
|
||||
dconf text not null,
|
||||
tags text not null
|
||||
);
|
||||
|
||||
|
@ -84,7 +84,7 @@ create table if not exists notes (
|
|||
id integer primary key,
|
||||
guid integer not null,
|
||||
mid integer not null,
|
||||
gid integer not null,
|
||||
did integer not null,
|
||||
mod integer not null,
|
||||
usn integer not null,
|
||||
tags text not null,
|
||||
|
@ -102,7 +102,7 @@ create table if not exists nsums (
|
|||
create table if not exists cards (
|
||||
id integer primary key,
|
||||
nid integer not null,
|
||||
gid integer not null,
|
||||
did integer not null,
|
||||
ord integer not null,
|
||||
mod integer not null,
|
||||
usn integer not null,
|
||||
|
@ -145,21 +145,21 @@ values(1,0,0,0,%(v)s,0,0,0,'','{}','','','{}');
|
|||
|
||||
def _getColVars(db):
|
||||
import anki.collection
|
||||
import anki.groups
|
||||
g = anki.groups.defaultGroup.copy()
|
||||
for k,v in anki.groups.defaultTopConf.items():
|
||||
import anki.decks
|
||||
g = anki.decks.defaultDeck.copy()
|
||||
for k,v in anki.decks.defaultTopConf.items():
|
||||
g[k] = v
|
||||
g['id'] = 1
|
||||
g['name'] = _("Default")
|
||||
g['conf'] = 1
|
||||
g['mod'] = intTime()
|
||||
gc = anki.groups.defaultConf.copy()
|
||||
gc = anki.decks.defaultConf.copy()
|
||||
gc['id'] = 1
|
||||
return g, gc, anki.collection.defaultConf.copy()
|
||||
|
||||
def _addColVars(db, g, gc, c):
|
||||
db.execute("""
|
||||
update col set conf = ?, groups = ?, gconf = ?""",
|
||||
update col set conf = ?, decks = ?, dconf = ?""",
|
||||
simplejson.dumps(c),
|
||||
simplejson.dumps({'1': g}),
|
||||
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);
|
||||
-- card spacing, etc
|
||||
create index if not exists ix_cards_nid on cards (nid);
|
||||
-- scheduling and group limiting
|
||||
create index if not exists ix_cards_sched on cards (gid, queue, due);
|
||||
-- scheduling and deck limiting
|
||||
create index if not exists ix_cards_sched on cards (did, queue, due);
|
||||
-- revlog by card
|
||||
create index if not exists ix_revlog_cid on revlog (cid);
|
||||
-- field uniqueness check
|
||||
|
|
54
anki/sync.py
54
anki/sync.py
|
@ -95,7 +95,7 @@ class Syncer(object):
|
|||
def changes(self):
|
||||
"Bundle up deletions and small objects, and apply if server."
|
||||
d = dict(models=self.getModels(),
|
||||
groups=self.getGroups(),
|
||||
decks=self.getDecks(),
|
||||
tags=self.getTags(),
|
||||
graves=self.getGraves())
|
||||
if self.lnewer:
|
||||
|
@ -118,7 +118,7 @@ class Syncer(object):
|
|||
self.mergeGraves(rchg['graves'])
|
||||
# then the other objects
|
||||
self.mergeModels(rchg['models'])
|
||||
self.mergeGroups(rchg['groups'])
|
||||
self.mergeDecks(rchg['decks'])
|
||||
self.mergeTags(rchg['tags'])
|
||||
if 'conf' in rchg:
|
||||
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":
|
||||
assert not self.col.db.scalar(
|
||||
"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
|
||||
for t, usn in self.col.tags.allItems():
|
||||
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"),
|
||||
len(self.col.models.all()),
|
||||
len(self.col.tags.all()),
|
||||
len(self.col.groups.all()),
|
||||
len(self.col.groups.allConf()),
|
||||
len(self.col.decks.all()),
|
||||
len(self.col.decks.allConf()),
|
||||
]
|
||||
|
||||
def usnLim(self):
|
||||
|
@ -184,11 +184,11 @@ select id, cid, %d, ease, ivl, lastIvl, factor, time, type
|
|||
from revlog where %s""" % d)
|
||||
elif table == "cards":
|
||||
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)
|
||||
else:
|
||||
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)
|
||||
|
||||
def chunk(self):
|
||||
|
@ -230,7 +230,7 @@ from notes where %s""" % d)
|
|||
def getGraves(self):
|
||||
cards = []
|
||||
notes = []
|
||||
groups = []
|
||||
decks = []
|
||||
if self.col.server:
|
||||
curs = self.col.db.execute(
|
||||
"select oid, type from graves where usn >= ?", self.minUsn)
|
||||
|
@ -243,18 +243,18 @@ from notes where %s""" % d)
|
|||
elif type == REM_NOTE:
|
||||
notes.append(oid)
|
||||
else:
|
||||
groups.append(oid)
|
||||
decks.append(oid)
|
||||
if not self.col.server:
|
||||
self.col.db.execute("update graves set usn=? where usn=-1",
|
||||
self.maxUsn)
|
||||
return dict(cards=cards, notes=notes, groups=groups)
|
||||
return dict(cards=cards, notes=notes, decks=decks)
|
||||
|
||||
def mergeGraves(self, graves):
|
||||
# notes first, so we don't end up with duplicate graves
|
||||
self.col._remNotes(graves['notes'])
|
||||
self.col.remCards(graves['cards'])
|
||||
for oid in graves['groups']:
|
||||
self.col.groups.rem(oid)
|
||||
for oid in graves['decks']:
|
||||
self.col.decks.rem(oid)
|
||||
|
||||
# Models
|
||||
##########################################################################
|
||||
|
@ -276,36 +276,36 @@ from notes where %s""" % d)
|
|||
if not l or r['mod'] > l['mod']:
|
||||
self.col.models.update(r)
|
||||
|
||||
# Groups
|
||||
# Decks
|
||||
##########################################################################
|
||||
|
||||
def getGroups(self):
|
||||
def getDecks(self):
|
||||
if self.col.server:
|
||||
return [
|
||||
[g for g in self.col.groups.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.all() if g['usn'] >= self.minUsn],
|
||||
[g for g in self.col.decks.allConf() if g['usn'] >= self.minUsn]
|
||||
]
|
||||
else:
|
||||
groups = [g for g in self.col.groups.all() if g['usn'] == -1]
|
||||
for g in groups:
|
||||
decks = [g for g in self.col.decks.all() if g['usn'] == -1]
|
||||
for g in decks:
|
||||
g['usn'] = self.maxUsn
|
||||
gconf = [g for g in self.col.groups.allConf() if g['usn'] == -1]
|
||||
for g in gconf:
|
||||
dconf = [g for g in self.col.decks.allConf() if g['usn'] == -1]
|
||||
for g in dconf:
|
||||
g['usn'] = self.maxUsn
|
||||
self.col.groups.save()
|
||||
return [groups, gconf]
|
||||
self.col.decks.save()
|
||||
return [decks, dconf]
|
||||
|
||||
def mergeGroups(self, rchg):
|
||||
def mergeDecks(self, rchg):
|
||||
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 not l or r['mod'] > l['mod']:
|
||||
self.col.groups.update(r)
|
||||
self.col.decks.update(r)
|
||||
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 not l or r['mod'] > l['mod']:
|
||||
self.col.groups.updateConf(r)
|
||||
self.col.decks.updateConf(r)
|
||||
|
||||
# Tags
|
||||
##########################################################################
|
||||
|
|
|
@ -171,8 +171,8 @@ class TagManager(object):
|
|||
query += " where " + lim
|
||||
return self.col.db.list(query, *args)
|
||||
|
||||
def setGroupForTags(self, yes, no, gid):
|
||||
def setDeckForTags(self, yes, no, did):
|
||||
nids = self.selTagNids(yes, no)
|
||||
self.col.db.execute(
|
||||
"update cards set gid=?,mod=?,usn=? where nid in "+ids2str(nids),
|
||||
gid, intTime(), self.col.usn())
|
||||
"update cards set did=?,mod=?,usn=? where nid in "+ids2str(nids),
|
||||
did, intTime(), self.col.usn())
|
||||
|
|
|
@ -288,7 +288,7 @@ yesCount from reviewHistory"""):
|
|||
insert or replace into col select id, cast(created as int), :t,
|
||||
:t, 99, 0, 0, cast(lastSync as int),
|
||||
"", "", "", "", "" 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)
|
||||
# delete old selective study settings, which we can't auto-upgrade easily
|
||||
keys = ("newActive", "newInactive", "revActive", "revInactive")
|
||||
|
@ -599,7 +599,7 @@ and ord = ? limit 1""", m['id'], t['ord']):
|
|||
col.db.execute("""
|
||||
update cards set due = nid where type=0""")
|
||||
# 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.sched.today+1, left)
|
||||
# and due cards
|
||||
|
|
|
@ -144,10 +144,10 @@ def test_selective():
|
|||
assert len(deck.tags.selTagNids(["two", "three"], [])) == 3
|
||||
assert len(deck.tags.selTagNids(["two", "three"], ["one"])) == 1
|
||||
assert len(deck.tags.selTagNids(["one", "three"], ["two", "four"])) == 1
|
||||
deck.tags.setGroupForTags(["three"], [], 3)
|
||||
assert deck.db.scalar("select count() from cards where gid = 3") == 3
|
||||
deck.tags.setGroupForTags(["one"], [], 2)
|
||||
assert deck.db.scalar("select count() from cards where gid = 2") == 2
|
||||
deck.tags.setDeckForTags(["three"], [], 3)
|
||||
assert deck.db.scalar("select count() from cards where did = 3") == 3
|
||||
deck.tags.setDeckForTags(["one"], [], 2)
|
||||
assert deck.db.scalar("select count() from cards where did = 2") == 2
|
||||
|
||||
def test_addDelTags():
|
||||
deck = getEmptyDeck()
|
96
tests/test_decks.py
Normal file
96
tests/test_decks.py
Normal 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"))
|
|
@ -104,10 +104,10 @@ def test_findCards():
|
|||
assert len(deck.findCards("model:basic")) == 5
|
||||
assert len(deck.findCards("-model:basic")) == 0
|
||||
assert len(deck.findCards("-model:foo")) == 5
|
||||
# group
|
||||
assert len(deck.findCards("group:default")) == 5
|
||||
assert len(deck.findCards("-group:default")) == 0
|
||||
assert len(deck.findCards("-group:foo")) == 5
|
||||
# deck
|
||||
assert len(deck.findCards("deck:default")) == 5
|
||||
assert len(deck.findCards("-deck:default")) == 0
|
||||
assert len(deck.findCards("-deck:foo")) == 5
|
||||
# full search
|
||||
f = deck.newNote()
|
||||
f['Front'] = u'hello<b>world</b>'
|
||||
|
|
|
@ -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"))
|
|
@ -54,29 +54,29 @@ def test_new():
|
|||
def test_newLimits():
|
||||
d = getEmptyDeck()
|
||||
# add some notes
|
||||
g2 = d.groups.id("Default::foo")
|
||||
g2 = d.decks.id("Default::foo")
|
||||
for i in range(30):
|
||||
f = d.newNote()
|
||||
f['Front'] = str(i)
|
||||
if i > 4:
|
||||
f.gid = g2
|
||||
f.did = g2
|
||||
d.addNote(f)
|
||||
# give the child group a different configuration
|
||||
c2 = d.groups.confId("new conf")
|
||||
d.groups.setConf(d.groups.get(g2), c2)
|
||||
# give the child deck a different configuration
|
||||
c2 = d.decks.confId("new conf")
|
||||
d.decks.setConf(d.decks.get(g2), c2)
|
||||
d.reset()
|
||||
# both confs have defaulted to a limit of 20
|
||||
assert d.sched.newCount == 20
|
||||
# first card we get comes from parent
|
||||
c = d.sched.getCard()
|
||||
assert c.gid == 1
|
||||
assert c.did == 1
|
||||
# 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
|
||||
d.reset()
|
||||
assert d.sched.newCount == 10
|
||||
# if we limit child to 4, we should get 9
|
||||
conf2 = d.groups.conf(g2)
|
||||
conf2 = d.decks.conf(g2)
|
||||
conf2['new']['perDay'] = 4
|
||||
d.reset()
|
||||
assert d.sched.newCount == 9
|
||||
|
@ -98,7 +98,7 @@ def test_newOrder():
|
|||
# add first half
|
||||
d.addNote(f)
|
||||
# generate second half
|
||||
d.db.execute("update cards set gid = random()")
|
||||
d.db.execute("update cards set did = random()")
|
||||
d.conf['newPerDay'] = 100
|
||||
d.reset()
|
||||
# cards should be sorted by id
|
||||
|
@ -415,8 +415,8 @@ def test_cram():
|
|||
c.startTimer()
|
||||
c.flush()
|
||||
cardcopy = copy.copy(c)
|
||||
d.conf['groups'] = [1]
|
||||
d.cramGroups()
|
||||
d.conf['decks'] = [1]
|
||||
d.cramDecks()
|
||||
# first, test with initial intervals preserved
|
||||
conf = d.sched._lrnConf(c)
|
||||
conf['reset'] = False
|
||||
|
@ -452,7 +452,7 @@ def test_cram():
|
|||
# now try again with ivl rescheduling
|
||||
c = copy.copy(cardcopy)
|
||||
c.flush()
|
||||
d.cramGroups()
|
||||
d.cramDecks()
|
||||
conf = d.sched._lrnConf(c)
|
||||
conf['reset'] = False
|
||||
conf['resched'] = True
|
||||
|
@ -467,7 +467,7 @@ def test_cram():
|
|||
# try with ivl reset
|
||||
c = copy.copy(cardcopy)
|
||||
c.flush()
|
||||
d.cramGroups()
|
||||
d.cramDecks()
|
||||
conf = d.sched._lrnConf(c)
|
||||
conf['reset'] = True
|
||||
conf['resched'] = True
|
||||
|
@ -477,8 +477,8 @@ def test_cram():
|
|||
assert c.ivl == 1
|
||||
assert c.due == d.sched.today + 1
|
||||
# users should be able to cram entire deck too
|
||||
d.conf['groups'] = []
|
||||
d.cramGroups()
|
||||
d.conf['decks'] = []
|
||||
d.cramDecks()
|
||||
assert d.sched.cardCounts()[0] > 0
|
||||
|
||||
def test_cramLimits():
|
||||
|
@ -493,30 +493,30 @@ def test_cramLimits():
|
|||
c.due = d.sched.today + 1 + i
|
||||
c.flush()
|
||||
# the default cram should return all three
|
||||
d.conf['groups'] = [1]
|
||||
d.cramGroups()
|
||||
d.conf['decks'] = [1]
|
||||
d.cramDecks()
|
||||
assert d.sched.cardCounts()[0] == 3
|
||||
# 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
|
||||
# or after 2 days
|
||||
d.cramGroups(min=2)
|
||||
d.cramDecks(min=2)
|
||||
assert d.sched.cardCounts()[0] == 1
|
||||
# we may get nothing
|
||||
d.cramGroups(min=3)
|
||||
d.cramDecks(min=3)
|
||||
assert d.sched.cardCounts()[0] == 0
|
||||
# tomorrow(0) + dayAfter(1) = 2
|
||||
d.cramGroups(max=1)
|
||||
d.cramDecks(max=1)
|
||||
assert d.sched.cardCounts()[0] == 2
|
||||
# if max is tomorrow, we get only one
|
||||
d.cramGroups(max=0)
|
||||
d.cramDecks(max=0)
|
||||
assert d.sched.cardCounts()[0] == 1
|
||||
# both should work
|
||||
d.cramGroups(min=0, max=0)
|
||||
d.cramDecks(min=0, max=0)
|
||||
assert d.sched.cardCounts()[0] == 1
|
||||
d.cramGroups(min=1, max=1)
|
||||
d.cramDecks(min=1, max=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
|
||||
|
||||
def test_adjIvl():
|
||||
|
@ -614,28 +614,28 @@ def test_ordcycle():
|
|||
|
||||
def test_cardcounts():
|
||||
d = getEmptyDeck()
|
||||
# add a second group
|
||||
grp = d.groups.id("Default::new group")
|
||||
# add a second deck
|
||||
grp = d.decks.id("Default::new deck")
|
||||
# for each card type
|
||||
for type in range(3):
|
||||
# and each of the groups
|
||||
for gid in (1,grp):
|
||||
# and each of the decks
|
||||
for did in (1,grp):
|
||||
# create a new note
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
# set type/gid
|
||||
# set type/did
|
||||
c.type = type
|
||||
c.queue = type
|
||||
c.gid = gid
|
||||
c.did = did
|
||||
c.due = 0
|
||||
c.flush()
|
||||
d.reset()
|
||||
# with the default settings, there's no count limit
|
||||
assert d.sched.cardCounts() == (2,2,2)
|
||||
# check limit to one group
|
||||
d.groups.select(grp)
|
||||
# check limit to one deck
|
||||
d.decks.select(grp)
|
||||
d.reset()
|
||||
assert d.sched.cardCounts() == (1,1,1)
|
||||
|
||||
|
@ -753,42 +753,42 @@ def test_collapse():
|
|||
d.sched.answerCard(c, 3)
|
||||
assert not d.sched.getCard()
|
||||
|
||||
def test_groupCounts():
|
||||
def test_deckCounts():
|
||||
d = getEmptyDeck()
|
||||
# add a note with default group
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
d.addNote(f)
|
||||
# and one that's a child
|
||||
f = d.newNote()
|
||||
f['Front'] = u"two"
|
||||
default1 = f.gid = d.groups.id("Default::1")
|
||||
default1 = f.did = d.decks.id("Default::1")
|
||||
d.addNote(f)
|
||||
# make it a review card
|
||||
c = f.cards()[0]
|
||||
c.queue = 2
|
||||
c.due = 0
|
||||
c.flush()
|
||||
# add one more with a new group
|
||||
# add one more with a new deck
|
||||
f = d.newNote()
|
||||
f['Front'] = u"two"
|
||||
foobar = f.gid = d.groups.id("foo::bar")
|
||||
foobar = f.did = d.decks.id("foo::bar")
|
||||
d.addNote(f)
|
||||
# and one that's a sibling
|
||||
f = d.newNote()
|
||||
f['Front'] = u"three"
|
||||
foobaz = f.gid = d.groups.id("foo::baz")
|
||||
foobaz = f.did = d.decks.id("foo::baz")
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert len(d.groups.groups) == 5
|
||||
cnts = d.sched.groupCounts()
|
||||
assert len(d.decks.decks) == 5
|
||||
cnts = d.sched.deckCounts()
|
||||
cnts.sort()
|
||||
assert cnts[0] == ["Default", 1, 0, 1]
|
||||
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[4] == ["foo::baz", foobaz, 0, 1]
|
||||
tree = d.sched.groupCountTree()
|
||||
tree = d.sched.deckCountTree()
|
||||
assert tree[0][0] == "Default"
|
||||
# sum of child and parent
|
||||
assert tree[0][1] == 1
|
||||
|
@ -799,35 +799,35 @@ def test_groupCounts():
|
|||
assert tree[0][4][0][1] == default1
|
||||
assert tree[0][4][0][2] == 1
|
||||
assert tree[0][4][0][3] == 0
|
||||
# code should not fail if a card has an invalid group
|
||||
c.gid = 12345; c.flush()
|
||||
d.sched.groupCounts()
|
||||
d.sched.groupCountTree()
|
||||
# code should not fail if a card has an invalid deck
|
||||
c.did = 12345; c.flush()
|
||||
d.sched.deckCounts()
|
||||
d.sched.deckCountTree()
|
||||
|
||||
def test_groupTree():
|
||||
def test_deckTree():
|
||||
d = getEmptyDeck()
|
||||
d.groups.id("new::b::c")
|
||||
d.groups.id("new2")
|
||||
d.decks.id("new::b::c")
|
||||
d.decks.id("new2")
|
||||
# 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")
|
||||
assert "new" not in names
|
||||
|
||||
def test_groupFlow():
|
||||
def test_deckFlow():
|
||||
d = getEmptyDeck()
|
||||
# add a note with default group
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
d.addNote(f)
|
||||
# and one that's a child
|
||||
f = d.newNote()
|
||||
f['Front'] = u"two"
|
||||
default1 = f.gid = d.groups.id("Default::2")
|
||||
default1 = f.did = d.decks.id("Default::2")
|
||||
d.addNote(f)
|
||||
# and another that's higher up
|
||||
f = d.newNote()
|
||||
f['Front'] = u"three"
|
||||
default1 = f.gid = d.groups.id("Default::1")
|
||||
default1 = f.did = d.decks.id("Default::1")
|
||||
d.addNote(f)
|
||||
# should get top level one first, then ::1, then ::2
|
||||
d.reset()
|
||||
|
@ -839,7 +839,7 @@ def test_groupFlow():
|
|||
|
||||
def test_reorder():
|
||||
d = getEmptyDeck()
|
||||
# add a note with default group
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = u"one"
|
||||
d.addNote(f)
|
||||
|
@ -917,7 +917,7 @@ def test_revlim():
|
|||
for i in range(5):
|
||||
d.sched.answerCard(d.sched.getCard(), 3)
|
||||
assert d.sched.repCounts()[2] == 15
|
||||
t = d.groups.top()
|
||||
t = d.decks.top()
|
||||
t['revLim'] = 10
|
||||
d.reset()
|
||||
assert d.sched.repCounts()[2] == 5
|
||||
|
|
|
@ -65,9 +65,9 @@ def test_sync():
|
|||
for t in ("revlog", "notes", "cards", "nsums"):
|
||||
assert d.db.scalar("select count() from %s" % t) == num
|
||||
assert len(d.models.all()) == num*2
|
||||
# the default group and config have an id of 1, so always 1
|
||||
assert len(d.groups.all()) == 1
|
||||
assert len(d.groups.gconf) == 1
|
||||
# the default deck and config have an id of 1, so always 1
|
||||
assert len(d.decks.all()) == 1
|
||||
assert len(d.decks.dconf) == 1
|
||||
assert len(d.tags.all()) == num
|
||||
check(1)
|
||||
origUsn = deck1.usn()
|
||||
|
@ -173,35 +173,35 @@ def test_tags():
|
|||
assert deck1.tags.all() == deck2.tags.all()
|
||||
|
||||
@nose.with_setup(setup_modified)
|
||||
def test_groups():
|
||||
def test_decks():
|
||||
test_sync()
|
||||
assert len(deck1.groups.all()) == 1
|
||||
assert len(deck1.groups.all()) == len(deck2.groups.all())
|
||||
deck1.groups.id("new")
|
||||
assert len(deck1.groups.all()) != len(deck2.groups.all())
|
||||
assert len(deck1.decks.all()) == 1
|
||||
assert len(deck1.decks.all()) == len(deck2.decks.all())
|
||||
deck1.decks.id("new")
|
||||
assert len(deck1.decks.all()) != len(deck2.decks.all())
|
||||
time.sleep(0.1)
|
||||
deck2.groups.id("new2")
|
||||
deck2.decks.id("new2")
|
||||
deck1.save()
|
||||
deck2.save()
|
||||
assert client.sync() == "success"
|
||||
assert deck1.tags.all() == deck2.tags.all()
|
||||
assert len(deck1.groups.all()) == len(deck2.groups.all())
|
||||
assert len(deck1.groups.all()) == 3
|
||||
assert deck1.groups.conf(1)['maxTaken'] == 60
|
||||
deck2.groups.conf(1)['maxTaken'] = 30
|
||||
deck2.groups.save(deck2.groups.conf(1))
|
||||
assert len(deck1.decks.all()) == len(deck2.decks.all())
|
||||
assert len(deck1.decks.all()) == 3
|
||||
assert deck1.decks.conf(1)['maxTaken'] == 60
|
||||
deck2.decks.conf(1)['maxTaken'] = 30
|
||||
deck2.decks.save(deck2.decks.conf(1))
|
||||
deck2.save()
|
||||
assert client.sync() == "success"
|
||||
assert deck1.groups.conf(1)['maxTaken'] == 30
|
||||
assert deck1.decks.conf(1)['maxTaken'] == 30
|
||||
|
||||
@nose.with_setup(setup_modified)
|
||||
def test_conf():
|
||||
test_sync()
|
||||
assert deck2.conf['topGroup'] == 1
|
||||
deck1.conf['topGroup'] = 2
|
||||
assert deck2.conf['topDeck'] == 1
|
||||
deck1.conf['topDeck'] = 2
|
||||
deck1.save()
|
||||
assert client.sync() == "success"
|
||||
assert deck2.conf['topGroup'] == 2
|
||||
assert deck2.conf['topDeck'] == 2
|
||||
|
||||
@nose.with_setup(setup_modified)
|
||||
def test_threeway():
|
||||
|
|
Loading…
Reference in a new issue