From f7790275ceb86e4f5ccad9d793a7c725223025b8 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 23 Nov 2011 19:28:09 +0900 Subject: [PATCH] groups -> decks --- anki/cards.py | 14 +- anki/collection.py | 38 ++--- anki/consts.py | 2 +- anki/cram.py | 4 +- anki/{groups.py => decks.py} | 186 ++++++++++----------- anki/find.py | 18 +- anki/importing/anki1.py | 2 +- anki/importing/anki2.py | 40 ++--- anki/models.py | 10 +- anki/notes.py | 14 +- anki/sched.py | 168 +++++++++---------- anki/stats.py | 24 +-- anki/storage.py | 22 +-- anki/sync.py | 54 +++--- anki/tags.py | 6 +- anki/upgrade.py | 4 +- tests/{test_deck.py => test_collection.py} | 8 +- tests/test_decks.py | 96 +++++++++++ tests/test_find.py | 8 +- tests/test_groups.py | 96 ----------- tests/test_sched.py | 114 ++++++------- tests/test_sync.py | 36 ++-- 22 files changed, 482 insertions(+), 482 deletions(-) rename anki/{groups.py => decks.py} (58%) rename tests/{test_deck.py => test_collection.py} (95%) create mode 100644 tests/test_decks.py delete mode 100644 tests/test_groups.py diff --git a/anki/cards.py b/anki/cards.py index dfa063ab3..471fe0fdf 100644 --- a/anki/cards.py +++ b/anki/cards.py @@ -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) diff --git a/anki/collection.py b/anki/collection.py index bd98dc659..6c94a7ff0 100644 --- a/anki/collection.py +++ b/anki/collection.py @@ -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) diff --git a/anki/consts.py b/anki/consts.py index bd5c586b5..2c4508dd9 100644 --- a/anki/consts.py +++ b/anki/consts.py @@ -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 diff --git a/anki/cram.py b/anki/cram.py index 3eb8fc158..1155894e1 100644 --- a/anki/cram.py +++ b/anki/cram.py @@ -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, diff --git a/anki/groups.py b/anki/decks.py similarity index 58% rename from anki/groups.py rename to anki/decks.py index 79f54c910..f12d72be1 100644 --- a/anki/groups.py +++ b/anki/decks.py @@ -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]) diff --git a/anki/find.py b/anki/find.py index 8ea9cf4ab..9f69f0eda 100644 --- a/anki/find.py +++ b/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: diff --git a/anki/importing/anki1.py b/anki/importing/anki1.py index 6d6831cf8..a9190bb19 100644 --- a/anki/importing/anki1.py +++ b/anki/importing/anki1.py @@ -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) diff --git a/anki/importing/anki2.py b/anki/importing/anki2.py index 3229a0852..91a6d1720 100644 --- a/anki/importing/anki2.py +++ b/anki/importing/anki2.py @@ -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 diff --git a/anki/models.py b/anki/models.py index bef5ec4d7..c977ca842 100644 --- a/anki/models.py +++ b/anki/models.py @@ -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." diff --git a/anki/notes.py b/anki/notes.py index 22a053f29..910228452 100644 --- a/anki/notes.py +++ b/anki/notes.py @@ -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 diff --git a/anki/sched.py b/anki/sched.py index 3bdf089cf..e7cdb0ecb 100644 --- a/anki/sched.py +++ b/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 ########################################################################## diff --git a/anki/stats.py b/anki/stats.py index 8440af733..459353cd8 100644 --- a/anki/stats.py +++ b/anki/stats.py @@ -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 += "" 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 '

%s

%s' % (title, subtitle) diff --git a/anki/storage.py b/anki/storage.py index 22eec0e08..a9e4b1f05 100644 --- a/anki/storage.py +++ b/anki/storage.py @@ -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 diff --git a/anki/sync.py b/anki/sync.py index c4b1e7fe1..2a52d41ac 100644 --- a/anki/sync.py +++ b/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 ########################################################################## diff --git a/anki/tags.py b/anki/tags.py index a2314c396..8ad9b6d3e 100644 --- a/anki/tags.py +++ b/anki/tags.py @@ -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()) diff --git a/anki/upgrade.py b/anki/upgrade.py index 1f6547d6c..b9f1e701b 100644 --- a/anki/upgrade.py +++ b/anki/upgrade.py @@ -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 diff --git a/tests/test_deck.py b/tests/test_collection.py similarity index 95% rename from tests/test_deck.py rename to tests/test_collection.py index 1075d8268..b77da070e 100644 --- a/tests/test_deck.py +++ b/tests/test_collection.py @@ -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() diff --git a/tests/test_decks.py b/tests/test_decks.py new file mode 100644 index 000000000..6475caa16 --- /dev/null +++ b/tests/test_decks.py @@ -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")) diff --git a/tests/test_find.py b/tests/test_find.py index afbe2950e..152ab9fd6 100644 --- a/tests/test_find.py +++ b/tests/test_find.py @@ -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'helloworld' diff --git a/tests/test_groups.py b/tests/test_groups.py deleted file mode 100644 index 722f8d128..000000000 --- a/tests/test_groups.py +++ /dev/null @@ -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")) diff --git a/tests/test_sched.py b/tests/test_sched.py index e7b4b812a..8c9bb1c1e 100644 --- a/tests/test_sched.py +++ b/tests/test_sched.py @@ -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 diff --git a/tests/test_sync.py b/tests/test_sync.py index 800e310de..c93e9101b 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -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():