From 279a94264288b63cc9724eafc8f181673c0fd265 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 23 Nov 2011 17:47:44 +0900 Subject: [PATCH] deck -> collection --- anki/__init__.py | 17 +-- anki/cards.py | 24 ++--- anki/{deck.py => collection.py} | 16 +-- anki/cram.py | 8 +- anki/exporting.py | 20 ++-- anki/find.py | 46 ++++---- anki/groups.py | 36 +++---- anki/importing/__init__.py | 2 +- anki/importing/anki2.py | 26 ++--- anki/importing/base.py | 4 +- anki/importing/cardimp.py | 42 ++++---- anki/importing/supermemo_xml.py | 2 +- anki/latex.py | 20 ++-- anki/media.py | 12 +-- anki/models.py | 82 +++++++-------- anki/notes.py | 48 ++++----- anki/sched.py | 154 +++++++++++++-------------- anki/sound.py | 2 +- anki/stats.py | 68 ++++++------ anki/stdmodels.py | 8 +- anki/storage.py | 50 ++++----- anki/sync.py | 180 ++++++++++++++++---------------- anki/tags.py | 22 ++-- anki/upgrade.py | 122 +++++++++++----------- tests/off/test_exporting.py | 8 +- tests/shared.py | 4 +- tests/test_deck.py | 10 +- tests/test_importing.py | 1 - tests/test_media.py | 1 - tests/test_remote_sync.py | 4 +- tests/test_stats.py | 4 +- tests/test_sync.py | 6 +- 32 files changed, 523 insertions(+), 526 deletions(-) rename anki/{deck.py => collection.py} (98%) diff --git a/anki/__init__.py b/anki/__init__.py index c7111a18d..17303394e 100644 --- a/anki/__init__.py +++ b/anki/__init__.py @@ -3,15 +3,15 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html """\ -Open a deck: +Open a collection: - deck = anki.Deck(path) + col = anki.open(path) Get a due card: - card = deck.sched.getCard() + card = col.sched.getCard() if not card: - # deck is finished + # current deck is finished Show the card: @@ -19,11 +19,11 @@ Show the card: Answer the card: - deck.sched.answerCard(card, ease) + col.sched.answerCard(card, ease) Refresh after a change: - deck.reset() + col.reset() Edit the card: @@ -34,7 +34,7 @@ Edit the card: Save & close: - deck.close() + col.close() """ import sys @@ -50,4 +50,5 @@ if not os.path.exists(os.path.expanduser("~/.no-warranty")): raise Exception("Don't use this without reading the forum thread") version = "1.99" -from anki.storage import Deck +from anki.storage import Collection +open = Collection diff --git a/anki/cards.py b/anki/cards.py index 6b2ebfc80..dfa063ab3 100644 --- a/anki/cards.py +++ b/anki/cards.py @@ -18,8 +18,8 @@ from anki.utils import intTime, hexifyID, timestampID class Card(object): - def __init__(self, deck, id=None): - self.deck = deck + def __init__(self, col, id=None): + self.col = col self.timerStarted = None self._qa = None self._rd = None @@ -28,7 +28,7 @@ class Card(object): self.load() else: # to flush, set nid, ord, and due - self.id = timestampID(deck.db, "cards") + self.id = timestampID(col.db, "cards") self.gid = 1 self.crt = intTime() self.type = 0 @@ -59,15 +59,15 @@ class Card(object): self.left, self.edue, self.flags, - self.data) = self.deck.db.first( + self.data) = self.col.db.first( "select * from cards where id = ?", self.id) self._qa = None self._rd = None def flush(self): self.mod = intTime() - self.usn = self.deck.usn() - self.deck.db.execute( + self.usn = self.col.usn() + self.col.db.execute( """ insert or replace into cards values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", @@ -91,8 +91,8 @@ insert or replace into cards values def flushSched(self): self.mod = intTime() - self.usn = self.deck.usn() - self.deck.db.execute( + self.usn = self.col.usn() + self.col.db.execute( """update cards set mod=?, usn=?, type=?, queue=?, due=?, ivl=?, factor=?, reps=?, lapses=?, left=?, edue=? where id = ?""", @@ -111,14 +111,14 @@ lapses=?, left=?, edue=? where id = ?""", f = self.note(); m = self.model() data = [self.id, f.id, m['id'], self.gid, self.ord, f.stringTags(), f.joinedFields()] - self._qa = self.deck._renderQA(data) + self._qa = self.col._renderQA(data) return self._qa def _reviewData(self, reload=False): "Fetch the model and note." if not self._rd or reload: - f = self.deck.getNote(self.nid) - m = self.deck.models.get(f.mid) + f = self.col.getNote(self.nid) + m = self.col.models.get(f.mid) self._rd = [f, m] return self._rd @@ -129,7 +129,7 @@ lapses=?, left=?, edue=? where id = ?""", return self._reviewData()[1] def groupConf(self): - return self.deck.groups.conf(self.gid) + return self.col.groups.conf(self.gid) def template(self): return self._reviewData()[1]['tmpls'][self.ord] diff --git a/anki/deck.py b/anki/collection.py similarity index 98% rename from anki/deck.py rename to anki/collection.py index 3db14dc77..bd98dc659 100644 --- a/anki/deck.py +++ b/anki/collection.py @@ -33,8 +33,8 @@ defaultConf = { 'sortBackwards': False, } -# this is initialized by storage.Deck -class _Deck(object): +# this is initialized by storage.Collection +class _Collection(object): def __init__(self, db, server=False): self.db = db @@ -82,7 +82,7 @@ class _Deck(object): gconf, tags) = self.db.first(""" select crt, mod, scm, dty, usn, ls, -conf, models, groups, gconf, tags from deck""") +conf, models, groups, gconf, tags from col""") self.conf = simplejson.loads(self.conf) self.models.load(models) self.groups.load(groups, gconf) @@ -92,7 +92,7 @@ conf, models, groups, gconf, tags from deck""") "Flush state to DB, updating mod time." self.mod = intTime(1000) if mod is None else mod self.db.execute( - """update deck set + """update col set crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""", self.crt, self.mod, self.scm, self.dty, self._usn, self.ls, simplejson.dumps(self.conf)) @@ -114,7 +114,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""", self.save() def lock(self): - self.db.execute("update deck set mod=mod") + self.db.execute("update col set mod=mod") def close(self, save=True): "Disconnect from DB." @@ -229,7 +229,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""", return anki.notes.Note(self, self.models.current()) def addNote(self, note): - "Add a note to the deck. Return number of new cards." + "Add a note to the collection. Return number of new cards." # check we have card models available, then save cms = self.findTemplates(note) if not cms: @@ -464,8 +464,8 @@ where c.nid == f.id return CardStats(self, card).report() def stats(self): - from anki.stats import DeckStats - return DeckStats(self) + from anki.stats import CollectionStats + return CollectionStats(self) # Timeboxing ########################################################################## diff --git a/anki/cram.py b/anki/cram.py index 67e3d8e39..3eb8fc158 100644 --- a/anki/cram.py +++ b/anki/cram.py @@ -10,8 +10,8 @@ from anki.sched import Scheduler class CramScheduler(Scheduler): name = "cram" - def __init__(self, deck, order, min=0, max=None): - Scheduler.__init__(self, deck) + def __init__(self, col, order, min=0, max=None): + Scheduler.__init__(self, col) # should be the opposite order of what you want self.order = order # days to limit cram to, where tomorrow=0. Max is inclusive. @@ -65,7 +65,7 @@ class CramScheduler(Scheduler): maxlim = "and due <= %d" % (self.today+1+self.max) else: maxlim = "" - self.newQueue = self.deck.db.list(""" + 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(), self.today+1+self.min, @@ -95,7 +95,7 @@ select id from cards where gid in %s and queue = 2 and due >= %d ivl = self._graduatingIvl(card, conf, early) card.due = self.today + ivl # temporarily suspend it - self.deck.setDirty() + self.col.setDirty() card.queue = -3 def _graduatingIvl(self, card, conf, early): diff --git a/anki/exporting.py b/anki/exporting.py index 02f2fb4dd..ba4c92b0d 100644 --- a/anki/exporting.py +++ b/anki/exporting.py @@ -4,15 +4,15 @@ import itertools, time, re, os, HTMLParser from operator import itemgetter -from anki import Deck +#from anki import Deck from anki.cards import Card from anki.sync import SyncClient, SyncServer, copyLocalMedia from anki.lang import _ from anki.utils import parseTags, stripHTML, ids2str class Exporter(object): - def __init__(self, deck): - self.deck = deck + def __init__(self, col): + self.col = col self.limitTags = [] self.limitCardIds = [] @@ -45,10 +45,10 @@ class Exporter(object): if self.limitCardIds: return self.limitCardIds if not self.limitTags: - cards = self.deck.db.column0("select id from cards") + cards = self.col.db.column0("select id from cards") else: - d = tagIds(self.deck.db, self.limitTags, create=False) - cards = self.deck.db.column0( + d = tagIds(self.col.db, self.limitTags, create=False) + cards = self.col.db.column0( "select cardId from cardTags where tagid in %s" % ids2str(d.values())) self.count = len(cards) @@ -56,11 +56,11 @@ class Exporter(object): class AnkiExporter(Exporter): - key = _("Anki Deck (*.anki)") + key = _("Anki Collection (*.anki)") ext = ".anki" - def __init__(self, deck): - Exporter.__init__(self, deck) + def __init__(self, col): + Exporter.__init__(self, col) self.includeSchedulingInfo = False self.includeMedia = True @@ -72,7 +72,7 @@ class AnkiExporter(Exporter): os.unlink(path) except (IOError, OSError): pass - self.newDeck = DeckStorage.Deck(path) + self.newCol = DeckStorage.Deck(path) client = SyncClient(self.deck) server = SyncServer(self.newDeck) client.setServer(server) diff --git a/anki/find.py b/anki/find.py index 6416cb6b2..8ea9cf4ab 100644 --- a/anki/find.py +++ b/anki/find.py @@ -18,10 +18,10 @@ SEARCH_GROUP = 7 # Tools ########################################################################## -def fieldNames(deck, downcase=True): +def fieldNames(col, downcase=True): fields = set() names = [] - for m in deck.models.all(): + for m in col.models.all(): for f in m['flds']: if f['name'].lower() not in fields: names.append(f['name']) @@ -35,8 +35,8 @@ def fieldNames(deck, downcase=True): class Finder(object): - def __init__(self, deck): - self.deck = deck + def __init__(self, col): + self.col = col def findCards(self, query, full=False): "Return a list of card ids for QUERY." @@ -47,8 +47,8 @@ class Finder(object): return [] (q, args) = self._whereClause() query = self._orderedSelect(q) - res = self.deck.db.list(query, **args) - if self.deck.conf['sortBackwards']: + res = self.col.db.list(query, **args) + if self.col.conf['sortBackwards']: res.reverse() return res @@ -65,7 +65,7 @@ class Finder(object): return q, self.lims['args'] def _orderedSelect(self, lim): - type = self.deck.conf['sortType'] + type = self.col.conf['sortType'] if not type: return "select id from cards c where " + lim elif type.startswith("note"): @@ -153,7 +153,7 @@ order by %s""" % (lim, sort) elif val == "suspended": cond = "queue = -1" elif val == "due": - cond = "(queue = 2 and due <= %d)" % self.deck.sched.today + cond = "(queue = 2 and due <= %d)" % self.col.sched.today elif val == "recent": cond = "c.id in (select id from cards order by mod desc limit 100)" if neg: @@ -174,7 +174,7 @@ order by %s""" % (lim, sort) # in the future we may want to apply this at the end to speed up # the case where there are other limits nids = [] - for nid, flds in self.deck.db.execute( + for nid, flds in self.col.db.execute( "select id, flds from notes"): if val in stripHTML(flds): nids.append(nid) @@ -186,14 +186,14 @@ order by %s""" % (lim, sort) def _findModel(self, val, isNeg): extra = "not" if isNeg else "" ids = [] - for m in self.deck.models.all(): + for m in self.col.models.all(): if m['name'].lower() == val: ids.append(m['id']) self.lims['note'].append("mid %s in %s" % (extra, ids2str(ids))) def _findGroup(self, val, isNeg): extra = "!" if isNeg else "" - id = self.deck.groups.id(val, create=False) or 0 + id = self.col.groups.id(val, create=False) or 0 self.lims['card'].append("c.gid %s= %s" % (extra, id)) def _findTemplate(self, val, isNeg): @@ -205,7 +205,7 @@ order by %s""" % (lim, sort) except: num = None lims = [] - for m in self.deck.models.all(): + for m in self.col.models.all(): for t in m['tmpls']: # ordinal number? if num is not None and t['ord'] == num: @@ -228,7 +228,7 @@ order by %s""" % (lim, sort) value = "%" + parts[1].replace("*", "%") + "%" # find models that have that field mods = {} - for m in self.deck.models.all(): + for m in self.col.models.all(): for f in m['flds']: if f['name'].lower() == field: mods[m['id']] = (m, f['ord']) @@ -239,7 +239,7 @@ order by %s""" % (lim, sort) # gather nids regex = value.replace("%", ".*") nids = [] - for (id,mid,flds) in self.deck.db.execute(""" + for (id,mid,flds) in self.col.db.execute(""" select id, mid, flds from notes where mid in %s and flds like ? escape '\\'""" % ( ids2str(mods.keys())), @@ -258,7 +258,7 @@ where mid in %s and flds like ? escape '\\'""" % ( def _parseQuery(self): tokens = [] res = [] - allowedfields = fieldNames(self.deck) + allowedfields = fieldNames(self.col) def addSearchFieldToken(field, value, isNeg): if field.lower() in allowedfields: res.append((field + ':' + value, isNeg, SEARCH_FIELD)) @@ -370,11 +370,11 @@ where mid in %s and flds like ? escape '\\'""" % ( # Find and replace ########################################################################## -def findReplace(deck, nids, src, dst, regex=False, field=None, fold=True): +def findReplace(col, nids, src, dst, regex=False, field=None, fold=True): "Find and replace fields in a note." mmap = {} if field: - for m in deck.models.all(): + for m in col.models.all(): for f in m['flds']: if f['name'] == field: mmap[m['id']] = f['ord'] @@ -389,7 +389,7 @@ def findReplace(deck, nids, src, dst, regex=False, field=None, fold=True): def repl(str): return re.sub(regex, dst, str) d = [] - for nid, mid, flds in deck.db.execute( + for nid, mid, flds in col.db.execute( "select id, mid, flds from notes where id in "+ids2str(nids)): origFlds = flds # does it match? @@ -402,19 +402,19 @@ def findReplace(deck, nids, src, dst, regex=False, field=None, fold=True): sflds[c] = repl(sflds[c]) flds = joinFields(sflds) if flds != origFlds: - d.append(dict(nid=nid,flds=flds,u=deck.usn(),m=intTime())) + d.append(dict(nid=nid,flds=flds,u=col.usn(),m=intTime())) if not d: return 0 # replace - deck.db.executemany("update notes set flds=:flds,mod=:m,usn=:u where id=:nid", d) - deck.updateFieldCache(nids) + col.db.executemany("update notes set flds=:flds,mod=:m,usn=:u where id=:nid", d) + col.updateFieldCache(nids) return len(d) # Find duplicates ########################################################################## -def findDuplicates(deck, fmids): - data = deck.db.all( +def findDuplicates(col, fmids): + data = col.db.all( "select nid, value from fdata where fmid in %s" % ids2str(fmids)) vals = {} diff --git a/anki/groups.py b/anki/groups.py index b1abd37cd..79f54c910 100644 --- a/anki/groups.py +++ b/anki/groups.py @@ -83,8 +83,8 @@ class GroupManager(object): # Registry save/load ############################################################# - def __init__(self, deck): - self.deck = deck + def __init__(self, col): + self.col = col def load(self, groups, gconf): self.groups = simplejson.loads(groups) @@ -95,12 +95,12 @@ class GroupManager(object): "Can be called with either a group or a group configuration." if g: g['mod'] = intTime() - g['usn'] = self.deck.usn() + g['usn'] = self.col.usn() self.changed = True def flush(self): if self.changed: - self.deck.db.execute("update deck set groups=?, gconf=?", + self.col.db.execute("update col set groups=?, gconf=?", simplejson.dumps(self.groups), simplejson.dumps(self.gconf)) @@ -144,10 +144,10 @@ class GroupManager(object): self.rem(id, cardsToo) # delete cards too? if cardsToo: - self.deck.remCards(self.cids(gid)) + self.col.remCards(self.cids(gid)) # delete the group and add a grave del self.groups[str(gid)] - self.deck._logRem([gid], REM_GROUP) + self.col._logRem([gid], REM_GROUP) # ensure we have an active group if gid in self.active(): self.select(int(self.groups.keys()[0])) @@ -239,7 +239,7 @@ class GroupManager(object): def remConf(self, id): "Remove a configuration and update all groups using it." assert int(id) != 1 - self.deck.modSchema() + self.col.modSchema() del self.gconf[str(id)] for g in self.all(): if str(g['conf']) == str(id): @@ -257,9 +257,9 @@ class GroupManager(object): return self.get(gid)['name'] def setGroup(self, cids, gid): - self.deck.db.execute( + self.col.db.execute( "update cards set gid=?,usn=?,mod=? where id in "+ - ids2str(cids), gid, self.deck.usn(), intTime()) + ids2str(cids), gid, self.col.usn(), intTime()) def maybeAddToActive(self): @@ -267,29 +267,29 @@ class GroupManager(object): self.select(self.selected()) def sendHome(self, cids): - self.deck.db.execute(""" + self.col.db.execute(""" update cards set gid=(select gid from notes f where f.id=nid), usn=?,mod=? where id in %s""" % ids2str(cids), - self.deck.usn(), intTime(), gid) + self.col.usn(), intTime(), gid) def cids(self, gid): - return self.deck.db.list("select id from cards where gid=?", gid) + return self.col.db.list("select id from cards where gid=?", gid) # Group selection ############################################################# def top(self): "The current top level group as an object." - g = self.get(self.deck.conf['topGroup']) + g = self.get(self.col.conf['topGroup']) return g def active(self): "The currrently active gids." - return self.deck.conf['activeGroups'] + return self.col.conf['activeGroups'] def selected(self): "The currently selected gid." - return self.deck.conf['curGroup'] + return self.col.conf['curGroup'] def current(self): return self.get(self.selected()) @@ -298,13 +298,13 @@ usn=?,mod=? where id in %s""" % ids2str(cids), "Select a new branch." # save the top level group name = self.groups[str(gid)]['name'] - self.deck.conf['topGroup'] = self._topFor(name) + self.col.conf['topGroup'] = self._topFor(name) # current group - self.deck.conf['curGroup'] = gid + self.col.conf['curGroup'] = gid # and active groups (current + all children) actv = self.children(gid) actv.sort() - self.deck.conf['activeGroups'] = [gid] + [a[1] for a in actv] + self.col.conf['activeGroups'] = [gid] + [a[1] for a in actv] def children(self, gid): "All children of gid, as (name, id)." diff --git a/anki/importing/__init__.py b/anki/importing/__init__.py index a4f2522f1..ccec99873 100644 --- a/anki/importing/__init__.py +++ b/anki/importing/__init__.py @@ -10,7 +10,7 @@ from anki.lang import _ Importers = ( (_("Text separated by tabs or semicolons (*.txt,*.csv)"), TextImporter), - (_("Anki 2.0 Deck (*.anki2)"), Anki2Importer), + (_("Anki 2.0 Collection (*.anki2)"), Anki2Importer), (_("Anki 1.2 Deck (*.anki)"), Anki1Importer), (_("Supermemo XML export (*.xml)"), SupermemoXmlImporter), ) diff --git a/anki/importing/anki2.py b/anki/importing/anki2.py index dc89002f5..3229a0852 100644 --- a/anki/importing/anki2.py +++ b/anki/importing/anki2.py @@ -2,13 +2,13 @@ # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -from anki import Deck +from anki import Collection from anki.utils import intTime from anki.importing.base import Importer # -# Import a .anki2 file into the current deck. Used for migration from 1.x, -# shared decks, and import from a packaged deck. +# Import a .anki2 file into the current collection. Used for migration from +# 1.x, shared decks, and import from a packaged deck. # # We can't rely on internal ids, so we: # - compare notes by guid @@ -24,7 +24,7 @@ class Anki2Importer(Importer): needCards = True def run(self, media=None): - self._prepareDecks() + self._prepareFiles() if media is not None: # Anki1 importer has provided us with a custom media folder self.src.media._dir = media @@ -33,9 +33,9 @@ class Anki2Importer(Importer): finally: self.src.close(save=False) - def _prepareDecks(self): - self.dst = self.deck - self.src = Deck(self.file, queue=False) + def _prepareFiles(self): + self.dst = self.col + self.src = Collection(self.file, queue=False) def _import(self): self._groups = {} @@ -61,7 +61,7 @@ class Anki2Importer(Importer): for id, guid, mod, mid in self.dst.db.execute( "select id, guid, mod, mid from notes"): self._notes[guid] = (id, mod, mid) - # iterate over source deck + # iterate over source collection add = [] dirty = [] for note in self.src.db.execute( @@ -69,7 +69,7 @@ class Anki2Importer(Importer): # turn the db result into a mutable list note = list(note) guid, mid = note[1:3] - # missing from local deck? + # missing from local col? if guid not in self._notes: # get corresponding local model lmid = self._mid(mid) @@ -85,7 +85,7 @@ class Anki2Importer(Importer): self._notes[guid] = (note[0], note[4], note[2]) else: continue #raise Exception("merging notes nyi") - # add to deck + # add to col self.dst.db.executemany( "insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", add) @@ -140,7 +140,7 @@ class Anki2Importer(Importer): ###################################################################### def _gid(self, gid): - "Given gid in src deck, return local id." + "Given gid in src col, return local id." # already converted? if gid in self._groups: return self._groups[gid] @@ -179,7 +179,7 @@ class Anki2Importer(Importer): "select f.guid, f.mid, c.* from cards c, notes f " "where c.nid = f.id"): guid = card[0] - # does the card's note exist in dst deck? + # does the card's note exist in dst col? if guid not in self._notes: continue dnid = self._notes[guid] @@ -188,7 +188,7 @@ class Anki2Importer(Importer): # mid = self._notes[guid][2] # if shash != self._dstModels[mid]: # continue - # does the card already exist in the dst deck? + # does the card already exist in the dst col? ord = card[5] if (guid, ord) in self._cards: # fixme: in future, could update if newer mod time diff --git a/anki/importing/base.py b/anki/importing/base.py index dcc6a7b99..6688e5339 100644 --- a/anki/importing/base.py +++ b/anki/importing/base.py @@ -11,10 +11,10 @@ class Importer(object): needMapper = False - def __init__(self, deck, file): + def __init__(self, col, file): self.file = file self.log = [] - self.deck = deck + self.col = col self.total = 0 def run(self): diff --git a/anki/importing/cardimp.py b/anki/importing/cardimp.py index 8aa56615c..18d980b8d 100644 --- a/anki/importing/cardimp.py +++ b/anki/importing/cardimp.py @@ -30,9 +30,9 @@ class CardImporter(Importer): updateKey = None needDelimiter = False - def __init__(self, deck, file): - Importer.__init__(self, deck, file) - self._model = deck.currentModel + def __init__(self, col, file): + Importer.__init__(self, col, file) + self._model = col.currentModel self.tagsToAdd = u"" self._mapping = None @@ -40,23 +40,23 @@ class CardImporter(Importer): "Import." if self.updateKey is not None: return self.doUpdate() - random = self.deck.newCardOrder == NEW_CARDS_RANDOM + random = self.col.newCardOrder == NEW_CARDS_RANDOM num = 6 if random: num += 1 c = self.foreignCards() if self.importCards(c): - self.deck.updateCardTags(self.cardIds) + self.col.updateCardTags(self.cardIds) if random: - self.deck.randomizeNewCards(self.cardIds) + self.col.randomizeNewCards(self.cardIds) if c: - self.deck.setModified() + self.col.setModified() def doUpdate(self): # grab the data from the external file cards = self.foreignCards() # grab data from db - fields = self.deck.db.all(""" + fields = self.col.db.all(""" select noteId, value from fields where fieldModelId = :id and value != ''""", id=self.updateKey[1]) @@ -101,7 +101,7 @@ and value != ''""", 'v': c.fields[index], 'chk': self.maybeChecksum(c.fields[index], fm.unique)} for (nid, c) in upcards] - self.deck.db.execute(""" + self.col.db.execute(""" update fields set value = :v, chksum = :chk where noteId = :nid and fieldModelId = :fmid""", data) # update tags @@ -109,17 +109,17 @@ and fieldModelId = :fmid""", data) data = [{'nid': nid, 't': c.fields[tagsIdx]} for (nid, c) in upcards] - self.deck.db.execute( + self.col.db.execute( "update notes set tags = :t where id = :nid", data) # rebuild caches - cids = self.deck.db.column0( + cids = self.col.db.column0( "select id from cards where noteId in %s" % ids2str(nids)) - self.deck.updateCardTags(cids) - self.deck.updateCardsFromNoteIds(nids) + self.col.updateCardTags(cids) + self.col.updateCardsFromNoteIds(nids) self.total = len(cards) - self.deck.setModified() + self.col.setModified() def fields(self): "The number of fields." @@ -166,7 +166,7 @@ and fieldModelId = :fmid""", data) model = property(getModel, setModel) def importCards(self, cards): - "Convert each card into a note, apply attributes and add to deck." + "Convert each card into a note, apply attributes and add to col." # ensure all unique and required fields are mapped for fm in self.model.fieldModels: if fm.required or fm.unique: @@ -206,11 +206,11 @@ and fieldModelId = :fmid""", data) d['created'] = tmp[0] noteCreated[d['id']] = d['created'] return d - self.deck.db.execute(notesTable.insert(), + self.col.db.execute(notesTable.insert(), [fudgeCreated({'modelId': self.model.id, 'tags': canonifyTags(self.tagsToAdd + " " + cards[n].tags), 'id': noteIds[n]}) for n in range(len(cards))]) - self.deck.db.execute(""" + self.col.db.execute(""" delete from notesDeleted where noteId in (%s)""" % ",".join([str(s) for s in noteIds])) # add all the fields @@ -230,7 +230,7 @@ where noteId in (%s)""" % ",".join([str(s) for s in noteIds])) cards[m].fields[index] or u"", fm.unique) } for m in range(len(cards))] - self.deck.db.execute(fieldsTable.insert(), + self.col.db.execute(fieldsTable.insert(), data) # and cards active = 0 @@ -246,9 +246,9 @@ where noteId in (%s)""" % ",".join([str(s) for s in noteIds])) 'question': u"", 'answer': u"" },cards[m]) for m in range(len(cards))] - self.deck.db.execute(cardsTable.insert(), + self.col.db.execute(cardsTable.insert(), data) - self.deck.updateCardsFromNoteIds(noteIds) + self.col.updateCardsFromNoteIds(noteIds) self.total = len(noteIds) def addMeta(self, data, card): @@ -297,7 +297,7 @@ where noteId in (%s)""" % ",".join([str(s) for s in noteIds])) def getUniqueCache(self, field): "Return a dict with all fields, to test for uniqueness." - return dict(self.deck.db.all( + return dict(self.col.db.all( "select value, 1 from fields where fieldModelId = :fmid", fmid=field.id)) diff --git a/anki/importing/supermemo_xml.py b/anki/importing/supermemo_xml.py index f4ba099f6..c3ff04ba7 100644 --- a/anki/importing/supermemo_xml.py +++ b/anki/importing/supermemo_xml.py @@ -15,7 +15,7 @@ import re, unicodedata, time #import chardet -from anki import Deck +#from anki import Deck class SmartDict(dict): """ diff --git a/anki/latex.py b/anki/latex.py index 1b5cb108d..565cca0ac 100644 --- a/anki/latex.py +++ b/anki/latex.py @@ -30,22 +30,22 @@ def stripLatex(text): text = text.replace(match.group(), "") return text -def mungeQA(html, type, fields, model, data, deck): +def mungeQA(html, type, fields, model, data, col): "Convert TEXT with embedded latex tags to image links." for match in regexps['standard'].finditer(html): - html = html.replace(match.group(), _imgLink(deck, match.group(1), model)) + html = html.replace(match.group(), _imgLink(col, match.group(1), model)) for match in regexps['expression'].finditer(html): html = html.replace(match.group(), _imgLink( - deck, "$" + match.group(1) + "$", model)) + col, "$" + match.group(1) + "$", model)) for match in regexps['math'].finditer(html): html = html.replace(match.group(), _imgLink( - deck, + col, "\\begin{displaymath}" + match.group(1) + "\\end{displaymath}", model)) return html -def _imgLink(deck, latex, model): +def _imgLink(col, latex, model): "Return an img link for LATEX, creating if necesssary." - txt = _latexFromHtml(deck, latex) + txt = _latexFromHtml(col, latex) fname = "latex-%s.png" % checksum(txt.encode("utf8")) link = '' % fname if os.path.exists(fname): @@ -53,13 +53,13 @@ def _imgLink(deck, latex, model): elif not build: return u"[latex]%s[/latex]" % latex else: - err = _buildImg(deck, txt, fname, model) + err = _buildImg(col, txt, fname, model) if err: return err else: return link -def _latexFromHtml(deck, latex): +def _latexFromHtml(col, latex): "Convert entities and fix newlines." for match in re.compile("&([a-z]+);", re.IGNORECASE).finditer(latex): if match.group(1) in entitydefs: @@ -68,7 +68,7 @@ def _latexFromHtml(deck, latex): latex = stripHTML(latex) return latex -def _buildImg(deck, latex, fname, model): +def _buildImg(col, latex, fname, model): # add header/footer & convert to utf8 latex = (model["latexPre"] + "\n" + latex + "\n" + @@ -83,7 +83,7 @@ def _buildImg(deck, latex, fname, model): texfile = file(namedtmp("tmp.tex"), "w") texfile.write(latex) texfile.close() - mdir = deck.media.dir() + mdir = col.media.dir() oldcwd = os.getcwd() png = namedtmp("tmp.png") try: diff --git a/anki/media.py b/anki/media.py index 62b7b1308..b3b0c990a 100644 --- a/anki/media.py +++ b/anki/media.py @@ -17,10 +17,10 @@ class MediaManager(object): regexps = ("(?i)(\[sound:([^]]+)\])", "(?i)(]+src=[\"']?([^\"'>]+)[\"']?[^>]*>)") - def __init__(self, deck): - self.deck = deck + def __init__(self, col): + self.col = col # media directory - self._dir = re.sub("(?i)\.(anki2)$", ".media", self.deck.path) + self._dir = re.sub("(?i)\.(anki2)$", ".media", self.col.path) if not os.path.exists(self._dir): os.makedirs(self._dir) os.chdir(self._dir) @@ -87,8 +87,8 @@ If the same name exists, compare checksums.""" def filesInStr(self, mid, string, includeRemote=False): l = [] # convert latex first - model = self.deck.models.get(mid) - string = mungeQA(string, None, None, model, None, self.deck) + model = self.col.models.get(mid) + string = mungeQA(string, None, None, model, None, self.col) # extract filenames for reg in self.regexps: for (full, fname) in re.findall(reg, string): @@ -161,7 +161,7 @@ If the same name exists, compare checksums.""" def allMedia(self): "Return a set of all referenced filenames." files = set() - for mid, flds in self.deck.db.execute("select mid, flds from notes"): + for mid, flds in self.col.db.execute("select mid, flds from notes"): for f in self.filesInStr(mid, flds): files.add(f) return files diff --git a/anki/models.py b/anki/models.py index c9055b9a6..bef5ec4d7 100644 --- a/anki/models.py +++ b/anki/models.py @@ -60,8 +60,8 @@ class ModelManager(object): # Saving/loading registry ############################################################# - def __init__(self, deck): - self.deck = deck + def __init__(self, col): + self.col = col def load(self, json): "Load registry from JSON." @@ -72,16 +72,16 @@ class ModelManager(object): "Mark M modified if provided, and schedule registry flush." if m: m['mod'] = intTime() - m['usn'] = self.deck.usn() + m['usn'] = self.col.usn() self._updateRequired(m) if gencards: - self.deck.genCards(self.nids(m)) + self.col.genCards(self.nids(m)) self.changed = True def flush(self): "Flush the registry if any models were changed." if self.changed: - self.deck.db.execute("update deck set models = ?", + self.col.db.execute("update col set models = ?", simplejson.dumps(self.models)) # Retrieving and creating models @@ -90,16 +90,16 @@ class ModelManager(object): def current(self): "Get current model." try: - m = self.get(self.deck.groups.top()['curModel']) + m = self.get(self.col.groups.top()['curModel']) assert m return m except: return self.models.values()[0] def setCurrent(self, m): - t = self.deck.groups.top() + t = self.col.groups.top() t['curModel'] = m['id'] - self.deck.groups.save(t) + self.col.groups.save(t) def get(self, id): "Get model with ID, or None." @@ -130,10 +130,10 @@ class ModelManager(object): def rem(self, m): "Delete model, and all its cards/notes." - self.deck.modSchema() + self.col.modSchema() current = self.current()['id'] == m['id'] # delete notes/cards - self.deck.remCards(self.deck.db.list(""" + self.col.remCards(self.col.db.list(""" select id from cards where nid in (select id from notes where mid = ?)""", m['id'])) # then the model @@ -170,12 +170,12 @@ select id from cards where nid in (select id from notes where mid = ?)""", def nids(self, m): "Note ids for M." - return self.deck.db.list( + return self.col.db.list( "select id from notes where mid = ?", m['id']) def useCount(self, m): "Number of note using M." - return self.deck.db.scalar( + return self.col.db.scalar( "select count() from notes where mid = ?", m['id']) def randomNew(self): @@ -210,9 +210,9 @@ select id from cards where nid in (select id from notes where mid = ?)""", def setSortIdx(self, m, idx): assert idx >= 0 and idx < len(m['flds']) - self.deck.modSchema() + self.col.modSchema() m['sortf'] = idx - self.deck.updateFieldCache(self.nids(m), csum=False) + self.col.updateFieldCache(self.nids(m), csum=False) self.save(m) def addField(self, m, field): @@ -234,7 +234,7 @@ select id from cards where nid in (select id from notes where mid = ?)""", self._transformFields(m, delete) if idx == self.sortIdx(m): # need to rebuild - self.deck.updateFieldCache(self.nids(m), csum=False) + self.col.updateFieldCache(self.nids(m), csum=False) # saves self.renameField(m, field, None) @@ -254,7 +254,7 @@ select id from cards where nid in (select id from notes where mid = ?)""", self._transformFields(m, move) def renameField(self, m, field, newName): - self.deck.modSchema() + self.col.modSchema() for t in m['tmpls']: types = ("{{%s}}", "{{text:%s}}", "{{#%s}}", "{{^%s}}", "{{/%s}}") @@ -273,13 +273,13 @@ select id from cards where nid in (select id from notes where mid = ?)""", f['ord'] = c def _transformFields(self, m, fn): - self.deck.modSchema() + self.col.modSchema() r = [] - for (id, flds) in self.deck.db.execute( + for (id, flds) in self.col.db.execute( "select id, flds from notes where mid = ?", m['id']): r.append((joinFields(fn(splitFields(flds))), - intTime(), self.deck.usn(), id)) - self.deck.db.executemany( + intTime(), self.col.usn(), id)) + self.col.db.executemany( "update notes set flds=?,mod=?,usn=? where id = ?", r) # Templates @@ -291,8 +291,8 @@ select id from cards where nid in (select id from notes where mid = ?)""", return t def addTemplate(self, m, template): - "Note: should deck.genCards() afterwards." - self.deck.modSchema() + "Note: should col.genCards() afterwards." + self.col.modSchema() m['tmpls'].append(template) self._updateTemplOrds(m) self.save(m) @@ -301,12 +301,12 @@ select id from cards where nid in (select id from notes where mid = ?)""", "False if removing template would leave orphan notes." # find cards using this template ord = m['tmpls'].index(template) - cids = self.deck.db.list(""" + cids = self.col.db.list(""" select c.id from cards c, notes f where c.nid=f.id and mid = ? and ord = ?""", m['id'], ord) # all notes with this template must have at least two cards, or we # could end up creating orphaned notes - if self.deck.db.scalar(""" + if self.col.db.scalar(""" select nid, count() from cards where nid in (select nid from cards where id in %s) group by nid @@ -314,13 +314,13 @@ having count() < 2 limit 1""" % ids2str(cids)): return False # ok to proceed; remove cards - self.deck.modSchema() - self.deck.remCards(cids) + self.col.modSchema() + self.col.remCards(cids) # shift ordinals - self.deck.db.execute(""" + self.col.db.execute(""" update cards set ord = ord - 1, usn = ?, mod = ? where nid in (select id from notes where mid = ?) and ord > ?""", - self.deck.usn(), intTime(), m['id'], ord) + self.col.usn(), intTime(), m['id'], ord) m['tmpls'].remove(template) self._updateTemplOrds(m) self.save(m) @@ -344,10 +344,10 @@ update cards set ord = ord - 1, usn = ?, mod = ? map.append("when ord = %d then %d" % (oldidxs[id(t)], t['ord'])) # apply self.save(m) - self.deck.db.execute(""" + self.col.db.execute(""" update cards set ord = (case %s end),usn=?,mod=? where nid in ( select id from notes where mid = ?)""" % " ".join(map), - self.deck.usn(), intTime(), m['id']) + self.col.usn(), intTime(), m['id']) # Model changing ########################################################################## @@ -355,7 +355,7 @@ select id from notes where mid = ?)""" % " ".join(map), # - newModel should be self if model is not changing def change(self, m, nids, newModel, fmap, cmap): - self.deck.modSchema() + self.col.modSchema() assert newModel['id'] == m['id'] or (fmap and cmap) if fmap: self._changeNotes(nids, newModel, fmap) @@ -365,7 +365,7 @@ select id from notes where mid = ?)""" % " ".join(map), def _changeNotes(self, nids, newModel, map): d = [] nfields = len(newModel['flds']) - for (nid, flds) in self.deck.db.execute( + for (nid, flds) in self.col.db.execute( "select id, flds from notes where id in "+ids2str(nids)): newflds = {} flds = splitFields(flds) @@ -376,25 +376,25 @@ select id from notes where mid = ?)""" % " ".join(map), flds.append(newflds.get(c, "")) flds = joinFields(flds) d.append(dict(nid=nid, flds=flds, mid=newModel['id'], - m=intTime(),u=self.deck.usn())) - self.deck.db.executemany( + m=intTime(),u=self.col.usn())) + self.col.db.executemany( "update notes set flds=:flds,mid=:mid,mod=:m,usn=:u where id = :nid", d) - self.deck.updateFieldCache(nids) + self.col.updateFieldCache(nids) def _changeCards(self, nids, newModel, map): d = [] deleted = [] - for (cid, ord) in self.deck.db.execute( + for (cid, ord) in self.col.db.execute( "select id, ord from cards where nid in "+ids2str(nids)): if map[ord] is not None: d.append(dict( - cid=cid,new=map[ord],u=self.deck.usn(),m=intTime())) + cid=cid,new=map[ord],u=self.col.usn(),m=intTime())) else: deleted.append(cid) - self.deck.db.executemany( + self.col.db.executemany( "update cards set ord=:new,usn=:u,mod=:m where id=:cid", d) - self.deck.remCards(deleted) + self.col.remCards(deleted) # Schema hash ########################################################################## @@ -439,7 +439,7 @@ select id from notes where mid = ?)""" % " ".join(map), a.append(cloze if cloze else "1") b.append("") data = [1, 1, m['id'], 1, t['ord'], "", joinFields(b)] - empty = self.deck._renderQA(data)['q'] + empty = self.col._renderQA(data)['q'] start = a req = [] for i in range(len(flds)): @@ -448,7 +448,7 @@ select id from notes where mid = ?)""" % " ".join(map), # blank out this field data[6] = joinFields(a) # if the result is same as empty, field is required - if self.deck._renderQA(data)['q'] == empty: + if self.col._renderQA(data)['q'] == empty: req.append(i) return req, reqstrs diff --git a/anki/notes.py b/anki/notes.py index 1329761e8..22a053f29 100644 --- a/anki/notes.py +++ b/anki/notes.py @@ -9,14 +9,14 @@ from anki.utils import fieldChecksum, intTime, \ class Note(object): - def __init__(self, deck, model=None, id=None): + def __init__(self, col, model=None, id=None): assert not (model and id) - self.deck = deck + self.col = col if id: self.id = id self.load() else: - self.id = timestampID(deck.db, "notes") + self.id = timestampID(col.db, "notes") self.guid = guid64() self._model = model self.gid = model['gid'] @@ -25,7 +25,7 @@ class Note(object): self.fields = [""] * len(self._model['flds']) self.flags = 0 self.data = "" - self._fmap = self.deck.models.fieldMap(self._model) + self._fmap = self.col.models.fieldMap(self._model) def load(self): (self.guid, @@ -36,29 +36,29 @@ class Note(object): self.tags, self.fields, self.flags, - self.data) = self.deck.db.first(""" + self.data) = self.col.db.first(""" select guid, mid, gid, mod, usn, tags, flds, flags, data from notes where id = ?""", self.id) self.fields = splitFields(self.fields) - self.tags = self.deck.tags.split(self.tags) - self._model = self.deck.models.get(self.mid) - self._fmap = self.deck.models.fieldMap(self._model) + self.tags = self.col.tags.split(self.tags) + self._model = self.col.models.get(self.mid) + self._fmap = self.col.models.fieldMap(self._model) def flush(self, mod=None): if self.model()['cloze']: self._clozePreFlush() self.mod = mod if mod else intTime() - self.usn = self.deck.usn() - sfld = stripHTML(self.fields[self.deck.models.sortIdx(self._model)]) + self.usn = self.col.usn() + sfld = stripHTML(self.fields[self.col.models.sortIdx(self._model)]) tags = self.stringTags() - res = self.deck.db.execute(""" + res = self.col.db.execute(""" insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", self.id, self.guid, self.mid, self.gid, self.mod, self.usn, tags, self.joinedFields(), sfld, self.flags, self.data) self.id = res.lastrowid self.updateFieldChecksums() - self.deck.tags.register(self.tags) + self.col.tags.register(self.tags) if self.model()['cloze']: self._clozePostFlush() @@ -66,7 +66,7 @@ insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", return joinFields(self.fields) def updateFieldChecksums(self): - self.deck.db.execute("delete from nsums where nid = ?", self.id) + self.col.db.execute("delete from nsums where nid = ?", self.id) d = [] for (ord, conf) in self._fmap.values(): if not conf['uniq']: @@ -75,10 +75,10 @@ insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", if not val: continue d.append((self.id, self.mid, fieldChecksum(val))) - self.deck.db.executemany("insert into nsums values (?, ?, ?)", d) + self.col.db.executemany("insert into nsums values (?, ?, ?)", d) def cards(self): - return [self.deck.getCard(id) for id in self.deck.db.list( + return [self.col.getCard(id) for id in self.col.db.list( "select id from cards where nid = ? order by ord", self.id)] def model(self): @@ -119,13 +119,13 @@ insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", ################################################## def hasTag(self, tag): - return self.deck.tags.inList(tag, self.tags) + return self.col.tags.inList(tag, self.tags) def stringTags(self): - return self.deck.tags.join(self.deck.tags.canonify(self.tags)) + return self.col.tags.join(self.col.tags.canonify(self.tags)) def setTagsFromStr(self, str): - self.tags = self.deck.tags.split(str) + self.tags = self.col.tags.split(str) def delTag(self, tag): rem = [] @@ -154,14 +154,14 @@ insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", lim = "and nid != :nid" else: lim = "" - nids = self.deck.db.list( + nids = self.col.db.list( "select nid from nsums where csum = ? and nid != ? and mid = ?", csum, self.id or 0, self.mid) if not nids: return True # grab notes with the same checksums, and see if they're actually # duplicates - for flds in self.deck.db.list("select flds from notes where id in "+ + for flds in self.col.db.list("select flds from notes where id in "+ ids2str(nids)): fields = splitFields(flds) if fields[ord] == val: @@ -189,14 +189,14 @@ insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", ################################################## def _clozePreFlush(self): - self.newlyAdded = not self.deck.db.scalar( + self.newlyAdded = not self.col.db.scalar( "select 1 from cards where nid = ?", self.id) - tmpls = self.deck.findTemplates(self) + tmpls = self.col.findTemplates(self) ok = [] for t in tmpls: ok.append(t['ord']) # check if there are cards referencing a deleted cloze - if self.deck.db.scalar( + if self.col.db.scalar( "select 1 from cards where nid = ? and ord not in %s" % ids2str(ok), self.id): # there are; abort, as the UI should have handled this @@ -205,4 +205,4 @@ insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", def _clozePostFlush(self): # generate missing cards if not self.newlyAdded: - self.deck.genCards([self.id]) + self.col.genCards([self.id]) diff --git a/anki/sched.py b/anki/sched.py index 43b2efd1f..3bdf089cf 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -18,8 +18,8 @@ from anki.hooks import runHook # the standard Anki scheduler class Scheduler(object): name = "std" - def __init__(self, deck): - self.deck = deck + def __init__(self, col): + self.col = col self.queueLimit = 50 self.reportLimit = 1000 # fixme: replace reps with group based counts @@ -31,7 +31,7 @@ class Scheduler(object): self._checkDay() id = self._getCardId() if id: - c = self.deck.getCard(id) + c = self.col.getCard(id) c.startTimer() return c @@ -43,7 +43,7 @@ class Scheduler(object): def answerCard(self, card, ease): assert ease >= 1 and ease <= 4 - self.deck.markReview(card) + self.col.markReview(card) self.reps += 1 card.reps += 1 wasNew = (card.queue == 0) and card.type != 2 @@ -65,7 +65,7 @@ class Scheduler(object): raise Exception("Invalid queue") self._updateStats(card, 'time', card.timeTaken()) card.mod = intTime() - card.usn = self.deck.usn() + card.usn = self.col.usn() card.flushSched() def repCounts(self): @@ -76,7 +76,7 @@ class Scheduler(object): def dueForecast(self, days=7): "Return counts over next DAYS. Includes today." - daysd = dict(self.deck.db.all(""" + daysd = dict(self.col.db.all(""" select due, count() from cards where gid in %s and queue = 2 and due between ? and ? @@ -103,7 +103,7 @@ order by due""" % self._groupLimit(), def onClose(self): "Unbury and remove temporary suspends on close." - self.deck.db.execute( + self.col.db.execute( "update cards set queue = type where queue between -3 and -2") # Rev/lrn/time daily stats @@ -111,11 +111,11 @@ order by due""" % self._groupLimit(), def _updateStats(self, card, type, cnt=1): key = type+"Today" - for g in ([self.deck.groups.get(card.gid)] + - self.deck.groups.parents(card.gid)): + for g in ([self.col.groups.get(card.gid)] + + self.col.groups.parents(card.gid)): # add g[key][1] += cnt - self.deck.groups.save(g) + self.col.groups.save(g) # Group counts ########################################################################## @@ -124,12 +124,12 @@ order by due""" % self._groupLimit(), "Returns [groupname, gid, hasDue, hasNew]" # find groups with 1 or more due cards gids = {} - for g in self.deck.groups.all(): + 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.deck.groups.groups.items()] + for (gid, grp) in self.col.groups.groups.items()] def groupCountTree(self): return self._groupChildren(self.groupCounts()) @@ -138,7 +138,7 @@ order by due""" % self._groupLimit(), "Like the count tree without the counts. Faster." return self._groupChildren( [[grp['name'], int(gid), 0, 0, 0] - for (gid, grp) in self.deck.groups.groups.items()]) + for (gid, grp) in self.col.groups.groups.items()]) def _groupChildren(self, grps): # first, split the group names into components @@ -208,13 +208,13 @@ order by due""" % self._groupLimit(), self.newCount = 0 pcounts = {} # for each of the active groups - for gid in self.deck.groups.active(): + for gid in self.col.groups.active(): # get the individual group's limit - lim = self._groupNewLimitSingle(self.deck.groups.get(gid)) + lim = self._groupNewLimitSingle(self.col.groups.get(gid)) if not lim: continue # check the parents - parents = self.deck.groups.parents(gid) + parents = self.col.groups.parents(gid) for p in parents: # add if missing if p['id'] not in pcounts: @@ -222,7 +222,7 @@ order by due""" % self._groupLimit(), # take minimum of child and parent lim = min(pcounts[p['id']], lim) # see how many cards we actually have - cnt = self.deck.db.scalar(""" + cnt = self.col.db.scalar(""" select count() from (select 1 from cards where gid = ? and queue = 0 limit ?)""", gid, lim) # if non-zero, decrement from parent counts @@ -235,7 +235,7 @@ gid = ? and queue = 0 limit ?)""", gid, lim) def _resetNew(self): self._resetNewCount() - self.newGids = self.deck.groups.active() + self.newGids = self.col.groups.active() self._newQueue = [] self._updateNewCardRatio() @@ -249,7 +249,7 @@ gid = ? and queue = 0 limit ?)""", gid, lim) lim = min(self.queueLimit, self._groupNewLimit(gid)) if lim: # fill the queue with the current gid - self._newQueue = self.deck.db.all(""" + self._newQueue = self.col.db.all(""" select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim) if self._newQueue: self._newQueue.reverse() @@ -262,7 +262,7 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim) return (id, due) = self._newQueue.pop() # move any siblings to the end? - conf = self.deck.groups.conf(self.newGids[0]) + conf = self.col.groups.conf(self.newGids[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.deck.groups.top()['newSpread'] == NEW_CARDS_DISTRIBUTE: + if self.col.groups.top()['newSpread'] == NEW_CARDS_DISTRIBUTE: if self.newCount: self.newCardModulus = ( (self.newCount + self.revCount) / self.newCount) @@ -289,9 +289,9 @@ 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.deck.groups.top()['newSpread'] == NEW_CARDS_LAST: + if self.col.groups.top()['newSpread'] == NEW_CARDS_LAST: return False - elif self.deck.groups.top()['newSpread'] == NEW_CARDS_FIRST: + elif self.col.groups.top()['newSpread'] == NEW_CARDS_FIRST: return True elif self.newCardModulus: return self.reps and self.reps % self.newCardModulus == 0 @@ -299,14 +299,14 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim) def _groupHasNew(self, gid): if not self._groupNewLimit(gid): return False - return self.deck.db.scalar( + return self.col.db.scalar( "select 1 from cards where gid = ? and queue = 0 limit 1", gid) def _groupNewLimit(self, gid): - sel = self.deck.groups.get(gid) + sel = self.col.groups.get(gid) lim = -1 # for the group and each of its parents - for g in [sel] + self.deck.groups.parents(gid): + for g in [sel] + self.col.groups.parents(gid): rem = self._groupNewLimitSingle(g) if lim == -1: lim = rem @@ -315,14 +315,14 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim) return lim def _groupNewLimitSingle(self, g): - c = self.deck.groups.conf(g['id']) + c = self.col.groups.conf(g['id']) return max(0, c['new']['perDay'] - g['newToday'][1]) # Learning queue ########################################################################## def _resetLrnCount(self): - (self.lrnCount, self.lrnRepCount) = self.deck.db.first(""" + (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), @@ -338,7 +338,7 @@ gid in %s and queue = 1 and due < ? limit %d)""" % ( return False if self._lrnQueue: return True - self._lrnQueue = self.deck.db.all(""" + 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) @@ -350,7 +350,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) if self._fillLrn(): cutoff = time.time() if collapse: - cutoff += self.deck.groups.top()['collapseTime'] + cutoff += self.col.groups.top()['collapseTime'] if self._lrnQueue[0][0] < cutoff: id = heappop(self._lrnQueue)[1] self.lrnCount -= 1 @@ -436,9 +436,9 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) lastIvl = -(self._delayForGrade(conf, lastLeft)) ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.left)) def log(): - self.deck.db.execute( + self.col.db.execute( "insert into revlog values (?,?,?,?,?,?,?,?,?)", - int(time.time()*1000), card.id, self.deck.usn(), ease, + int(time.time()*1000), card.id, self.col.usn(), ease, ivl, lastIvl, card.factor, card.timeTaken(), type) try: log() @@ -452,33 +452,33 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) extra = "" if ids: extra = " and id in "+ids2str(ids) - self.deck.db.execute(""" + self.col.db.execute(""" update cards set due = edue, queue = 2, mod = %d, usn = %d where queue = 1 and type = 2 %s -""" % (intTime(), self.deck.usn(), extra)) +""" % (intTime(), self.col.usn(), extra)) def _groupHasLrn(self, gid): - return self.deck.db.scalar( + return self.col.db.scalar( "select 1 from cards where gid = ? and queue = 1 " "and due < ? limit 1", - gid, intTime() + self.deck.groups.top()['collapseTime']) + gid, intTime() + self.col.groups.top()['collapseTime']) # Reviews ########################################################################## def _groupHasRev(self, gid): - return self.deck.db.scalar( + return self.col.db.scalar( "select 1 from cards where gid = ? and queue = 2 " "and due <= ? limit 1", gid, self.today) def _resetRevCount(self): - top = self.deck.groups.top() + top = self.col.groups.top() lim = min(self.reportLimit, max(0, top['revLim'] - top['revToday'][1])) - self.revCount = self.deck.db.scalar(""" + 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) @@ -492,12 +492,12 @@ gid in %s and queue = 2 and due <= :day limit %d)""" % ( return False if self._revQueue: return True - self._revQueue = self.deck.db.list(""" + 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), lim=self.today) - if not self.deck.conf['revOrder']: + if not self.col.conf['revOrder']: r = random.Random() r.seed(self.today) r.shuffle(self._revQueue) @@ -509,8 +509,8 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % ( return self._revQueue.pop() def _revOrder(self): - if self.deck.conf['revOrder']: - return "order by %s" % ("ivl desc", "ivl")[self.deck.conf['revOrder']-1] + if self.col.conf['revOrder']: + return "order by %s" % ("ivl desc", "ivl")[self.col.conf['revOrder']-1] return "" # Answering a review card @@ -555,9 +555,9 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % ( def _logRev(self, card, ease): def log(): - self.deck.db.execute( + self.col.db.execute( "insert into revlog values (?,?,?,?,?,?,?,?,?)", - int(time.time()*1000), card.id, self.deck.usn(), ease, + int(time.time()*1000), card.id, self.col.usn(), ease, card.ivl, card.lastIvl, card.factor, card.timeTaken(), 1) try: @@ -604,7 +604,7 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % ( idealDue = self.today + idealIvl conf = self._cardConf(card)['rev'] # find sibling positions - dues = self.deck.db.list( + dues = self.col.db.list( "select due from cards where nid = ? and queue = 2" " and id != ?", card.nid, card.id) if not dues or idealDue not in dues: @@ -653,19 +653,19 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % ( ########################################################################## def _cardConf(self, card): - return self.deck.groups.conf(card.gid) + return self.col.groups.conf(card.gid) def _groupLimit(self): - return ids2str(self.deck.groups.active()) + return ids2str(self.col.groups.active()) # Daily cutoff ########################################################################## def _updateCutoff(self): - # days since deck created - self.today = int((time.time() - self.deck.crt) / 86400) + # days since col created + self.today = int((time.time() - self.col.crt) / 86400) # end of day cutoff - self.dayCutoff = self.deck.crt + (self.today+1)*86400 + self.dayCutoff = self.col.crt + (self.today+1)*86400 # update all selected groups def update(g): save = False @@ -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.deck.groups.save(g) - for gid in self.deck.groups.active(): - update(self.deck.groups.get(gid)) + self.col.groups.save(g) + for gid in self.col.groups.active(): + update(self.col.groups.get(gid)) # update parents too - for grp in self.deck.groups.parents(self.deck.groups.selected()): + for grp in self.col.groups.parents(self.col.groups.selected()): update(grp) def _checkDay(self): @@ -712,14 +712,14 @@ your short-term review workload will become.""")) def revDue(self): "True if there are any rev cards due." - return self.deck.db.scalar( + return self.col.db.scalar( ("select 1 from cards where gid in %s and queue = 2 " "and due <= ? limit 1") % self._groupLimit(), self.today) def newDue(self): "True if there are any new cards due." - return self.deck.db.scalar( + return self.col.db.scalar( ("select 1 from cards where gid in %s and queue = 0 " "limit 1") % self._groupLimit()) @@ -771,34 +771,34 @@ your short-term review workload will become.""")) def suspendCards(self, ids): "Suspend cards." self.removeFailed(ids) - self.deck.db.execute( + self.col.db.execute( "update cards set queue=-1,mod=?,usn=? where id in "+ - ids2str(ids), intTime(), self.deck.usn()) + ids2str(ids), intTime(), self.col.usn()) def unsuspendCards(self, ids): "Unsuspend cards." - self.deck.db.execute( + self.col.db.execute( "update cards set queue=type,mod=?,usn=? " "where queue = -1 and id in "+ ids2str(ids), - intTime(), self.deck.usn()) + intTime(), self.col.usn()) def buryNote(self, nid): "Bury all cards for note until next session." - self.deck.setDirty() + self.col.setDirty() self.removeFailed( - self.deck.db.list("select id from cards where nid = ?", nid)) - self.deck.db.execute("update cards set queue = -2 where nid = ?", nid) + self.col.db.list("select id from cards where nid = ?", nid)) + self.col.db.execute("update cards set queue = -2 where nid = ?", nid) # Resetting ########################################################################## def forgetCards(self, ids): "Put cards at the end of the new queue." - self.deck.db.execute( + self.col.db.execute( "update cards set type=0,queue=0,ivl=0 where id in "+ids2str(ids)) - pmax = self.deck.db.scalar("select max(due) from cards where type=0") + pmax = self.col.db.scalar("select max(due) from cards where type=0") # takes care of mod + usn - self.sortCards(ids, start=pmax+1, shuffle=self.deck.models.randomNew()) + self.sortCards(ids, start=pmax+1, shuffle=self.col.models.randomNew()) def reschedCards(self, ids, imin, imax): "Put cards in review queue with a new interval in days (min, max)." @@ -808,7 +808,7 @@ your short-term review workload will become.""")) for id in ids: r = random.randint(imin, imax) d.append(dict(id=id, due=r+t, ivl=max(1, r), mod=mod)) - self.deck.db.executemany( + self.col.db.executemany( "update cards set type=2,queue=2,ivl=:ivl,due=:due where id=:id", d) @@ -818,7 +818,7 @@ your short-term review workload will become.""")) def sortCards(self, cids, start=1, step=1, shuffle=False, shift=False): scids = ids2str(cids) now = intTime() - nids = self.deck.db.list( + nids = self.col.db.list( ("select distinct nid from cards where type = 0 and id in %s " "order by nid") % scids) if not nids: @@ -833,27 +833,27 @@ your short-term review workload will become.""")) high = start+c*step # shift? if shift: - low = self.deck.db.scalar( + low = self.col.db.scalar( "select min(due) from cards where due >= ? and type = 0 " "and id not in %s" % scids, start) if low is not None: shiftby = high - low + 1 - self.deck.db.execute(""" + self.col.db.execute(""" update cards set mod=?, usn=?, due=due+? where id not in %s -and due >= ?""" % scids, now, self.deck.usn(), shiftby, low) +and due >= ?""" % scids, now, self.col.usn(), shiftby, low) # reorder cards d = [] - for id, nid in self.deck.db.execute( + for id, nid in self.col.db.execute( "select id, nid from cards where type = 0 and id in "+scids): - d.append(dict(now=now, due=due[nid], usn=self.deck.usn(), cid=id)) - self.deck.db.executemany( + d.append(dict(now=now, due=due[nid], usn=self.col.usn(), cid=id)) + self.col.db.executemany( "update cards set due=:due,mod=:now,usn=:usn where id = :cid""", d) # fixme: because it's a model property now, these should be done on a # per-model basis def randomizeCards(self): - self.sortCards(self.deck.db.list("select id from cards"), shuffle=True) + self.sortCards(self.col.db.list("select id from cards"), shuffle=True) def orderCards(self): - self.sortCards(self.deck.db.list("select id from cards")) + self.sortCards(self.col.db.list("select id from cards")) diff --git a/anki/sound.py b/anki/sound.py index f39c804a1..bd6616dbb 100644 --- a/anki/sound.py +++ b/anki/sound.py @@ -192,7 +192,7 @@ def stopMplayer(*args): return mplayerManager.kill() -addHook("deckClosed", stopMplayer) +addHook("colClosed", stopMplayer) # PyAudio recording ########################################################################## diff --git a/anki/stats.py b/anki/stats.py index 444c4d9aa..8440af733 100644 --- a/anki/stats.py +++ b/anki/stats.py @@ -14,8 +14,8 @@ from anki.hooks import runFilter class CardStats(object): - def __init__(self, deck, card): - self.deck = deck + def __init__(self, col, card): + self.col = col self.card = card def report(self): @@ -23,23 +23,23 @@ class CardStats(object): fmt = lambda x, **kwargs: fmtTimeSpan(x, short=True, **kwargs) self.txt = "" self.addLine(_("Added"), self.date(c.id/1000)) - first = self.deck.db.scalar( + first = self.col.db.scalar( "select min(id) from revlog where cid = ?", c.id) - last = self.deck.db.scalar( + last = self.col.db.scalar( "select max(id) from revlog where cid = ?", c.id) if first: self.addLine(_("First Review"), self.date(first/1000)) self.addLine(_("Latest Review"), self.date(last/1000)) if c.queue in (1,2): if c.queue == 2: - next = time.time()+((self.deck.sched.today - c.due)*86400) + next = time.time()+((self.col.sched.today - c.due)*86400) else: next = c.due next = self.date(next) self.addLine(_("Due"), next) self.addLine(_("Interval"), fmt(c.ivl * 86400)) self.addLine(_("Ease"), "%d%%" % (c.factor/10.0)) - (cnt, total) = self.deck.db.first( + (cnt, total) = self.col.db.first( "select count(), sum(time)/1000 from revlog where cid = :id", id=c.id) if cnt: @@ -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.deck.groups.name(c.gid)) - self.addLine(_("Home Group"), self.deck.groups.name(c.note().gid)) + self.addLine(_("Current Group"), self.col.groups.name(c.gid)) + self.addLine(_("Home Group"), self.col.groups.name(c.note().gid)) self.txt += "
" return self.txt @@ -73,7 +73,7 @@ class CardStats(object): str += fmtTimeSpan(tm%60, point=2 if not str else -1, short=True) return str -# Deck stats +# Collection stats ########################################################################## colYoung = "#7c7" @@ -88,10 +88,10 @@ colTime = "#770" colUnseen = "#000" colSusp = "#ff0" -class DeckStats(object): +class CollectionStats(object): - def __init__(self, deck): - self.deck = deck + def __init__(self, col): + self.col = col self._stats = None self.type = 0 self.width = 600 @@ -173,7 +173,7 @@ table * { font-size: 14px; } lim += " and due-:today >= %d" % start if end is not None: lim += " and day < %d" % end - return self.deck.db.all(""" + return self.col.db.all(""" 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 @@ -181,7 +181,7 @@ from cards where gid in %s and queue = 2 %s group by day order by day""" % (self._limit(), lim), - today=self.deck.sched.today, + today=self.col.sched.today, chunk=chunk) # Reps and time spent @@ -248,7 +248,7 @@ group by day order by day""" % (self._limit(), lim), tot = totd[-1][1] period = self._periodDays() if not period: - period = self.deck.sched.today - first + 1 + period = self.col.sched.today - first + 1 i = [] self._line(i, _("Days studied"), _("%(pct)d%% (%(x)s of %(y)s)") % dict( @@ -303,7 +303,7 @@ group by day order by day""" % (self._limit(), lim), lims = [] if num is not None: lims.append("id > %d" % ( - (self.deck.sched.dayCutoff-(num*chunk*86400))*1000)) + (self.col.sched.dayCutoff-(num*chunk*86400))*1000)) lim = self._revlogLimit() if lim: lims.append(lim) @@ -315,7 +315,7 @@ group by day order by day""" % (self._limit(), lim), tf = 60.0 # minutes else: tf = 3600.0 # hours - return self.deck.db.all(""" + return self.col.db.all(""" select (cast((id/1000 - :cut) / 86400.0 as int))/:chunk as day, sum(case when type = 0 then 1 else 0 end), -- lrn count @@ -331,7 +331,7 @@ sum(case when type = 2 then time/1000 else 0 end)/:tf, -- lapse time sum(case when type = 3 then time/1000 else 0 end)/:tf -- cram time from revlog %s group by day order by day""" % lim, - cut=self.deck.sched.dayCutoff, + cut=self.col.sched.dayCutoff, tf=tf, chunk=chunk) @@ -341,7 +341,7 @@ group by day order by day""" % lim, if num: lims.append( "id > %d" % - ((self.deck.sched.dayCutoff-(num*86400))*1000)) + ((self.col.sched.dayCutoff-(num*86400))*1000)) rlim = self._revlogLimit() if rlim: lims.append(rlim) @@ -349,12 +349,12 @@ group by day order by day""" % lim, lim = "where " + " and ".join(lims) else: lim = "" - return self.deck.db.first(""" + return self.col.db.first(""" select count(), abs(min(day)) from (select (cast((id/1000 - :cut) / 86400.0 as int)+1) as day from revlog %s group by day order by day)""" % lim, - cut=self.deck.sched.dayCutoff) + cut=self.col.sched.dayCutoff) # Intervals ###################################################################### @@ -389,12 +389,12 @@ group by day order by day)""" % lim, chunk = 7; lim = " and grp <= 52" else: chunk = 30; lim = "" - data = [self.deck.db.all(""" + data = [self.col.db.all(""" select ivl / :chunk as grp, count() from cards where gid in %s and queue = 2 %s group by grp order by grp""" % (self._limit(), lim), chunk=chunk)] - return data + list(self.deck.db.first(""" + return data + list(self.col.db.first(""" select count(), avg(ivl), max(ivl) from cards where gid in %s and queue = 2""" % self._limit())) @@ -457,7 +457,7 @@ select count(), avg(ivl), max(ivl) from cards where gid in %s and queue = 2""" % lim = self._revlogLimit() if lim: lim = "where " + lim - return self.deck.db.all(""" + return self.col.db.all(""" select (case when type in (0,2) then 0 when lastIvl < 21 then 1 @@ -511,8 +511,8 @@ order by thetype, ease""" % lim) lim = self._revlogLimit() if lim: lim = " and " + lim - sd = datetime.datetime.fromtimestamp(self.deck.crt) - return self.deck.db.all(""" + sd = datetime.datetime.fromtimestamp(self.col.crt) + return self.col.db.all(""" select 23 - ((cast((:cut - id/1000) / 3600.0 as int)) %% 24) as hour, sum(case when ease = 1 then 0 else 1 end) / @@ -520,7 +520,7 @@ cast(count() as float) * 100, count() from revlog where type = 1 %s group by hour having count() > 30 order by hour""" % lim, - cut=self.deck.sched.dayCutoff-(sd.hour*3600)) + cut=self.col.sched.dayCutoff-(sd.hour*3600)) # Cards ###################################################################### @@ -537,7 +537,7 @@ group by hour having count() > 30 order by hour""" % lim, d.append(dict(data=div[c], label=t, color=col)) # text data i = [] - (c, f) = self.deck.db.first(""" + (c, f) = self.col.db.first(""" select count(id), count(distinct nid) from cards where gid in %s """ % self._limit()) self._line(i, _("Total cards"), c) @@ -547,7 +547,7 @@ where gid in %s """ % self._limit()) self._line(i, _("Lowest ease factor"), "%d%%" % low) self._line(i, _("Average ease factor"), "%d%%" % avg) self._line(i, _("Highest ease factor"), "%d%%" % high) - min = self.deck.db.scalar( + min = self.col.db.scalar( "select min(id) from cards where gid in %s " % self._limit()) if min: self._line(i, _("First card created"), _("%s ago") % fmtTimeSpan( @@ -557,7 +557,7 @@ where gid in %s """ % self._limit()) A card's ease factor is the size of the next interval \ when you answer "good" on a review.''') txt = self._title(_("Cards Types"), - _("The division of cards in your deck.")) + _("The division of cards in your deck(s).")) txt += "
%s%s
" % ( self.width, self._graph(id="cards", data=d, type="pie"), @@ -574,7 +574,7 @@ when you answer "good" on a review.''') return "" + "".join(i) + "
" def _factors(self): - return self.deck.db.first(""" + return self.col.db.first(""" select min(factor) / 10.0, avg(factor) / 10.0, @@ -582,7 +582,7 @@ max(factor) / 10.0 from cards where gid in %s and queue = 2""" % self._limit()) def _cards(self): - return self.deck.db.first(""" + return self.col.db.first(""" select 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 @@ -668,11 +668,11 @@ $(function () { data=simplejson.dumps(data), conf=simplejson.dumps(conf))) def _limit(self): - return self.deck.sched._groupLimit() + return self.col.sched._groupLimit() def _revlogLimit(self): return ("cid in (select id from cards where gid in %s)" % - ids2str(self.deck.groups.active())) + ids2str(self.col.groups.active())) def _title(self, title, subtitle=""): return '

%s

%s' % (title, subtitle) diff --git a/anki/stdmodels.py b/anki/stdmodels.py index 606d4f875..51e61ebb5 100644 --- a/anki/stdmodels.py +++ b/anki/stdmodels.py @@ -9,8 +9,8 @@ models = [] # Basic ########################################################################## -def addBasicModel(deck): - mm = deck.models +def addBasicModel(col): + mm = col.models m = mm.new(_("Basic")) fm = mm.newField(_("Front")) fm['req'] = True @@ -30,8 +30,8 @@ models.append((_("Basic"), addBasicModel)) # Cloze ########################################################################## -def addClozeModel(deck): - mm = deck.models +def addClozeModel(col): + mm = col.models m = mm.new(_("Cloze")) fm = mm.newField(_("Text")) fm['req'] = True diff --git a/anki/storage.py b/anki/storage.py index 6f0a75b45..22eec0e08 100644 --- a/anki/storage.py +++ b/anki/storage.py @@ -6,12 +6,12 @@ import os, simplejson from anki.lang import _ from anki.utils import intTime from anki.db import DB -from anki.deck import _Deck +from anki.collection import _Collection from anki.consts import * from anki.stdmodels import addBasicModel, addClozeModel -def Deck(path, queue=True, lock=True, server=False): - "Open a new or existing deck. Path must be unicode." +def Collection(path, queue=True, lock=True, server=False): + "Open a new or existing collection. Path must be unicode." assert path.endswith(".anki2") path = os.path.abspath(path) create = not os.path.exists(path) @@ -27,30 +27,30 @@ def Deck(path, queue=True, lock=True, server=False): ver = _upgradeSchema(db) db.execute("pragma temp_store = memory") db.execute("pragma cache_size = 10000") - # add db to deck and do any remaining upgrades - deck = _Deck(db, server) + # add db to col and do any remaining upgrades + col = _Collection(db, server) if ver < SCHEMA_VERSION: - _upgradeDeck(deck, ver) + _upgrade(col, ver) elif create: # add in reverse order so basic is default - addClozeModel(deck) - addBasicModel(deck) - deck.save() + addClozeModel(col) + addBasicModel(col) + col.save() if lock: - deck.lock() + col.lock() if not queue: - return deck + return col # rebuild queue - deck.reset() - return deck + col.reset() + return col # no upgrades necessary at the moment def _upgradeSchema(db): return SCHEMA_VERSION -def _upgradeDeck(deck, ver): +def _upgrade(col, ver): return -# Creating a new deck +# Creating a new collection ###################################################################### def _createDB(db): @@ -62,9 +62,9 @@ def _createDB(db): db.execute("analyze") return SCHEMA_VERSION -def _addSchema(db, setDeckConf=True): +def _addSchema(db, setColConf=True): db.executescript(""" -create table if not exists deck ( +create table if not exists col ( id integer primary key, crt integer not null, mod integer not null, @@ -137,14 +137,14 @@ create table if not exists graves ( type integer not null ); -insert or ignore into deck +insert or ignore into col values(1,0,0,0,%(v)s,0,0,0,'','{}','','','{}'); """ % ({'v':SCHEMA_VERSION})) - import anki.deck - if setDeckConf: - _addDeckVars(db, *_getDeckVars(db)) + if setColConf: + _addColVars(db, *_getColVars(db)) -def _getDeckVars(db): +def _getColVars(db): + import anki.collection import anki.groups g = anki.groups.defaultGroup.copy() for k,v in anki.groups.defaultTopConf.items(): @@ -155,11 +155,11 @@ def _getDeckVars(db): g['mod'] = intTime() gc = anki.groups.defaultConf.copy() gc['id'] = 1 - return g, gc, anki.deck.defaultConf.copy() + return g, gc, anki.collection.defaultConf.copy() -def _addDeckVars(db, g, gc, c): +def _addColVars(db, g, gc, c): db.execute(""" -update deck set conf = ?, groups = ?, gconf = ?""", +update col set conf = ?, groups = ?, gconf = ?""", simplejson.dumps(c), simplejson.dumps({'1': g}), simplejson.dumps({'1': gc})) diff --git a/anki/sync.py b/anki/sync.py index 041e65c95..c4b1e7fe1 100644 --- a/anki/sync.py +++ b/anki/sync.py @@ -23,8 +23,8 @@ if simplejson.__version__ < "1.7.3": # todo: # - ensure all urllib references are converted to urllib2 for proxies # - ability to cancel -# - need to make sure syncing doesn't bump the deck modified time if nothing was -# changed, since by default closing the deck bumps the mod time +# - need to make sure syncing doesn't bump the col modified time if nothing was +# changed, since by default closing the col bumps the mod time # - ensure the user doesn't add foreign chars to passsword # Incremental syncing @@ -34,8 +34,8 @@ from anki.consts import * class Syncer(object): - def __init__(self, deck, server=None): - self.deck = deck + def __init__(self, col, server=None): + self.col = col self.server = server def status(self, type): @@ -90,7 +90,7 @@ class Syncer(object): return "success" def meta(self): - return (self.deck.mod, self.deck.scm, self.deck._usn, intTime(), None) + return (self.col.mod, self.col.scm, self.col._usn, intTime(), None) def changes(self): "Bundle up deletions and small objects, and apply if server." @@ -104,7 +104,7 @@ class Syncer(object): def applyChanges(self, minUsn, lnewer, changes): # we're the server; save info - self.maxUsn = self.deck._usn + self.maxUsn = self.col._usn self.minUsn = minUsn self.lnewer = not lnewer self.rchg = changes @@ -127,33 +127,33 @@ class Syncer(object): def sanityCheck(self): # some basic checks to ensure the sync went ok. this is slow, so will # be removed before official release - assert not self.deck.db.scalar(""" + assert not self.col.db.scalar(""" select count() from cards where nid not in (select id from notes)""") - assert not self.deck.db.scalar(""" + assert not self.col.db.scalar(""" select count() from notes where id not in (select distinct nid from cards)""") for t in "cards", "notes", "revlog", "graves": - assert not self.deck.db.scalar( + assert not self.col.db.scalar( "select count() from %s where usn = -1" % t) - for g in self.deck.groups.all(): + for g in self.col.groups.all(): assert g['usn'] != -1 - for t, usn in self.deck.tags.allItems(): + for t, usn in self.col.tags.allItems(): assert usn != -1 - for m in self.deck.models.all(): + for m in self.col.models.all(): assert m['usn'] != -1 return [ - self.deck.db.scalar("select count() from cards"), - self.deck.db.scalar("select count() from notes"), - self.deck.db.scalar("select count() from revlog"), - self.deck.db.scalar("select count() from nsums"), - self.deck.db.scalar("select count() from graves"), - len(self.deck.models.all()), - len(self.deck.tags.all()), - len(self.deck.groups.all()), - len(self.deck.groups.allConf()), + self.col.db.scalar("select count() from cards"), + self.col.db.scalar("select count() from notes"), + self.col.db.scalar("select count() from revlog"), + self.col.db.scalar("select count() from nsums"), + 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()), ] def usnLim(self): - if self.deck.server: + if self.col.server: return "usn >= %d" % self.minUsn else: return "usn = -1" @@ -162,9 +162,9 @@ select count() from notes where id not in (select distinct nid from cards)""") if not mod: # server side; we decide new mod time mod = intTime(1000) - self.deck.ls = mod - self.deck._usn = self.maxUsn + 1 - self.deck.save(mod=mod) + self.col.ls = mod + self.col._usn = self.maxUsn + 1 + self.col.save(mod=mod) return mod # Chunked syncing @@ -176,7 +176,7 @@ select count() from notes where id not in (select distinct nid from cards)""") def cursorForTable(self, table): lim = self.usnLim() - x = self.deck.db.execute + x = self.col.db.execute d = (self.maxUsn, lim) if table == "revlog": return x(""" @@ -206,8 +206,8 @@ from notes where %s""" % d) self.tablesLeft.pop(0) self.cursor = None # if we're the client, mark the objects as having been sent - if not self.deck.server: - self.deck.db.execute( + if not self.col.server: + self.col.db.execute( "update %s set usn=? where usn=-1"%curTable, self.maxUsn) buf[curTable] = rows @@ -231,11 +231,11 @@ from notes where %s""" % d) cards = [] notes = [] groups = [] - if self.deck.server: - curs = self.deck.db.execute( + if self.col.server: + curs = self.col.db.execute( "select oid, type from graves where usn >= ?", self.minUsn) else: - curs = self.deck.db.execute( + curs = self.col.db.execute( "select oid, type from graves where usn = -1") for oid, type in curs: if type == REM_CARD: @@ -244,100 +244,100 @@ from notes where %s""" % d) notes.append(oid) else: groups.append(oid) - if not self.deck.server: - self.deck.db.execute("update graves set usn=? where usn=-1", + 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) def mergeGraves(self, graves): # notes first, so we don't end up with duplicate graves - self.deck._remNotes(graves['notes']) - self.deck.remCards(graves['cards']) + self.col._remNotes(graves['notes']) + self.col.remCards(graves['cards']) for oid in graves['groups']: - self.deck.groups.rem(oid) + self.col.groups.rem(oid) # Models ########################################################################## def getModels(self): - if self.deck.server: - return [m for m in self.deck.models.all() if m['usn'] >= self.minUsn] + if self.col.server: + return [m for m in self.col.models.all() if m['usn'] >= self.minUsn] else: - mods = [m for m in self.deck.models.all() if m['usn'] == -1] + mods = [m for m in self.col.models.all() if m['usn'] == -1] for m in mods: m['usn'] = self.maxUsn - self.deck.models.save() + self.col.models.save() return mods def mergeModels(self, rchg): for r in rchg: - l = self.deck.models.get(r['id']) + l = self.col.models.get(r['id']) # if missing locally or server is newer, update if not l or r['mod'] > l['mod']: - self.deck.models.update(r) + self.col.models.update(r) # Groups ########################################################################## def getGroups(self): - if self.deck.server: + if self.col.server: return [ - [g for g in self.deck.groups.all() if g['usn'] >= self.minUsn], - [g for g in self.deck.groups.allConf() if g['usn'] >= self.minUsn] + [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] ] else: - groups = [g for g in self.deck.groups.all() if g['usn'] == -1] + groups = [g for g in self.col.groups.all() if g['usn'] == -1] for g in groups: g['usn'] = self.maxUsn - gconf = [g for g in self.deck.groups.allConf() if g['usn'] == -1] + gconf = [g for g in self.col.groups.allConf() if g['usn'] == -1] for g in gconf: g['usn'] = self.maxUsn - self.deck.groups.save() + self.col.groups.save() return [groups, gconf] def mergeGroups(self, rchg): for r in rchg[0]: - l = self.deck.groups.get(r['id'], False) + l = self.col.groups.get(r['id'], False) # if missing locally or server is newer, update if not l or r['mod'] > l['mod']: - self.deck.groups.update(r) + self.col.groups.update(r) for r in rchg[1]: - l = self.deck.groups.conf(r['id']) + l = self.col.groups.conf(r['id']) # if missing locally or server is newer, update if not l or r['mod'] > l['mod']: - self.deck.groups.updateConf(r) + self.col.groups.updateConf(r) # Tags ########################################################################## def getTags(self): - if self.deck.server: - return [t for t, usn in self.deck.tags.allItems() + if self.col.server: + return [t for t, usn in self.col.tags.allItems() if usn >= self.minUsn] else: tags = [] - for t, usn in self.deck.tags.allItems(): + for t, usn in self.col.tags.allItems(): if usn == -1: - self.deck.tags.tags[t] = self.maxUsn + self.col.tags.tags[t] = self.maxUsn tags.append(t) - self.deck.tags.save() + self.col.tags.save() return tags def mergeTags(self, tags): - self.deck.tags.register(tags, usn=self.maxUsn) + self.col.tags.register(tags, usn=self.maxUsn) # Cards/notes/revlog ########################################################################## def mergeRevlog(self, logs): - self.deck.db.executemany( + self.col.db.executemany( "insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)", logs) def newerRows(self, data, table, modIdx): ids = (r[0] for r in data) lmods = {} - for id, mod in self.deck.db.execute( + for id, mod in self.col.db.execute( "select id, mod from %s where id in %s and %s" % ( table, ids2str(ids), self.usnLim())): lmods[id] = mod @@ -348,26 +348,26 @@ from notes where %s""" % d) return update def mergeCards(self, cards): - self.deck.db.executemany( + self.col.db.executemany( "insert or replace into cards values " "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", self.newerRows(cards, "cards", 4)) def mergeNotes(self, notes): rows = self.newerRows(notes, "notes", 4) - self.deck.db.executemany( + self.col.db.executemany( "insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", rows) - self.deck.updateFieldCache([f[0] for f in rows]) + self.col.updateFieldCache([f[0] for f in rows]) - # Deck config + # Col config ########################################################################## def getConf(self): - return self.deck.conf + return self.col.conf def mergeConf(self, conf): - self.deck.conf = conf + self.col.conf = conf # Local syncing for unit tests ########################################################################## @@ -375,7 +375,7 @@ from notes where %s""" % d) class LocalServer(Syncer): # serialize/deserialize payload, so we don't end up sharing objects - # between decks + # between cols def applyChanges(self, minUsn, lnewer, changes): l = simplejson.loads; d = simplejson.dumps return l(d(Syncer.applyChanges(self, minUsn, lnewer, l(d(changes))))) @@ -499,30 +499,30 @@ class RemoteServer(Syncer, HttpSyncer): class FullSyncer(HttpSyncer): - def __init__(self, deck, hkey): - self.deck = deck + def __init__(self, col, hkey): + self.col = col self.hkey = hkey def _con(self): return httplib2.Http(timeout=60) def download(self): - self.deck.close() + self.col.close() resp, cont = self._con().request( SYNC_URL+"download?" + urllib.urlencode(self._vars())) if resp['status'] != '200': raise Exception("Invalid response code: %s" % resp['status']) - tpath = self.deck.path + ".tmp" + tpath = self.col.path + ".tmp" open(tpath, "wb").write(cont) - os.unlink(self.deck.path) - os.rename(tpath, self.deck.path) - d = DB(self.deck.path) + os.unlink(self.col.path) + os.rename(tpath, self.col.path) + d = DB(self.col.path) assert d.scalar("pragma integrity_check") == "ok" - self.deck = None + self.col = None def upload(self): - self.deck.beforeUpload() - assert self.postData(self._con(), "upload", open(self.deck.path, "rb"), + self.col.beforeUpload() + assert self.postData(self._con(), "upload", open(self.col.path, "rb"), self._vars(), comp=6) == "OK" # Media syncing @@ -530,16 +530,16 @@ class FullSyncer(HttpSyncer): class MediaSyncer(object): - def __init__(self, deck, server=None): - self.deck = deck + def __init__(self, col, server=None): + self.col = col self.server = server self.added = None def sync(self, mediaUsn): # step 1: check if there have been any changes - self.deck.media.findChanges() - lusn = self.deck.media.usn() - if lusn == mediaUsn and not self.deck.media.hasChanged(): + self.col.media.findChanges() + lusn = self.col.media.usn() + if lusn == mediaUsn and not self.col.media.hasChanged(): return "noChanges" # step 2: send/recv deletions runHook("mediaSync", "remove") @@ -563,30 +563,30 @@ class MediaSyncer(object): # when server has run out of files, it returns bumped usn break # step 5: finalize - self.deck.media.setUsn(usn) - self.deck.media.clearLog() + self.col.media.setUsn(usn) + self.col.media.clearLog() # clear cursor so successive calls work self.added = None return "success" def removed(self): - return self.deck.media.removed() + return self.col.media.removed() def remove(self, fnames, minUsn=None): - self.deck.media.syncRemove(fnames) + self.col.media.syncRemove(fnames) if minUsn is not None: # we're the server self.minUsn = minUsn - return self.deck.media.removed() + return self.col.media.removed() def files(self): if not self.added: - self.added = self.deck.media.added() - return self.deck.media.zipFromAdded(self.added) + self.added = self.col.media.added() + return self.col.media.zipFromAdded(self.added) def addFiles(self, zip): "True if zip is the last in set. Server returns new usn instead." - return self.deck.media.syncAdd(zip) + return self.col.media.syncAdd(zip) # Remote media syncing ########################################################################## diff --git a/anki/tags.py b/anki/tags.py index 49e9b64ea..a2314c396 100644 --- a/anki/tags.py +++ b/anki/tags.py @@ -18,8 +18,8 @@ class TagManager(object): # Registry save/load ############################################################# - def __init__(self, deck): - self.deck = deck + def __init__(self, col): + self.col = col def load(self, json): self.tags = simplejson.loads(json) @@ -27,7 +27,7 @@ class TagManager(object): def flush(self): if self.changed: - self.deck.db.execute("update deck set tags=?", + self.col.db.execute("update col set tags=?", simplejson.dumps(self.tags)) # Registering and fetching tags @@ -39,7 +39,7 @@ class TagManager(object): # versions of the same tag if they ignore the qt autocomplete. for t in tags: if t not in self.tags: - self.tags[t] = self.deck.usn() if usn is None else usn + self.tags[t] = self.col.usn() if usn is None else usn self.changed = True def all(self): @@ -55,7 +55,7 @@ class TagManager(object): self.tags = {} self.changed = True self.register(set(self.split( - " ".join(self.deck.db.list("select distinct tags from notes"+lim))))) + " ".join(self.col.db.list("select distinct tags from notes"+lim))))) def allItems(self): return self.tags.items() @@ -82,7 +82,7 @@ class TagManager(object): fn = self.remFromStr lim = " or ".join( [l+"like :_%d" % c for c, t in enumerate(newTags)]) - res = self.deck.db.all( + res = self.col.db.all( "select id, tags from notes where id in %s and %s" % ( ids2str(ids), lim), **dict([("_%d" % x, '%% %s %%' % y) @@ -92,8 +92,8 @@ class TagManager(object): def fix(row): nids.append(row[0]) return {'id': row[0], 't': fn(tags, row[1]), 'n':intTime(), - 'u':self.deck.usn()} - self.deck.db.executemany( + 'u':self.col.usn()} + self.col.db.executemany( "update notes set tags=:t,mod=:n,usn=:u where id = :id", [fix(row) for row in res]) @@ -169,10 +169,10 @@ class TagManager(object): lim = lim2 args += ['%% %s %%' % t for t in no] query += " where " + lim - return self.deck.db.list(query, *args) + return self.col.db.list(query, *args) def setGroupForTags(self, yes, no, gid): nids = self.selTagNids(yes, no) - self.deck.db.execute( + self.col.db.execute( "update cards set gid=?,mod=?,usn=? where nid in "+ids2str(nids), - gid, intTime(), self.deck.usn()) + gid, intTime(), self.col.usn()) diff --git a/anki/upgrade.py b/anki/upgrade.py index d3cad567e..1f6547d6c 100644 --- a/anki/upgrade.py +++ b/anki/upgrade.py @@ -6,9 +6,9 @@ import os, time, simplejson, re, datetime, shutil from anki.lang import _ from anki.utils import intTime, tmpfile, ids2str, splitFields from anki.db import DB -from anki.deck import _Deck +from anki.collection import _Collection from anki.consts import * -from anki.storage import _addSchema, _getDeckVars, _addDeckVars, \ +from anki.storage import _addSchema, _getColVars, _addColVars, \ _updateIndices # @@ -31,9 +31,9 @@ class Upgrader(object): self.path = path self._openDB(path) self._upgradeSchema() - self._openDeck() - self._upgradeDeck() - return self.deck + self._openCol() + self._upgradeRest() + return self.col # Integrity checking ###################################################################### @@ -117,8 +117,8 @@ analyze;""") shutil.copy(path, self.tmppath) self.db = DB(self.tmppath) - def _openDeck(self): - self.deck = _Deck(self.db) + def _openCol(self): + self.col = _Collection(self.db) # Schema upgrade ###################################################################### @@ -270,7 +270,7 @@ yesCount from reviewHistory"""): tags = {} for t in db.list("select tag from tags"): tags[t] = intTime() - db.execute("update deck set tags = ?", simplejson.dumps(tags)) + db.execute("update col set tags = ?", simplejson.dumps(tags)) db.execute("drop table tags") db.execute("drop table cardTags") @@ -282,15 +282,14 @@ yesCount from reviewHistory"""): _updateIndices(db) def _migrateDeckTbl(self): - import anki.deck db = self.db - db.execute("delete from deck") + db.execute("delete from col") db.execute(""" -insert or replace into deck select id, cast(created as int), :t, +insert or replace into col select id, cast(created as int), :t, :t, 99, 0, 0, cast(lastSync as int), "", "", "", "", "" from decks""", t=intTime()) # prepare a group to store the old deck options - g, gc, conf = _getDeckVars(db) + g, gc, conf = _getColVars(db) # delete old selective study settings, which we can't auto-upgrade easily keys = ("newActive", "newInactive", "revActive", "revInactive") for k in keys: @@ -311,7 +310,7 @@ insert or replace into deck select id, cast(created as int), :t, pass else: conf[k] = v - _addDeckVars(db, g, gc, conf) + _addColVars(db, g, gc, conf) # clean up db.execute("drop table decks") db.execute("drop table deckVars") @@ -338,7 +337,7 @@ insert or replace into deck select id, cast(created as int), :t, mods[m['id']] = m db.execute("update notes set mid = ? where mid = ?", t, row[0]) # save and clean up - db.execute("update deck set models = ?", simplejson.dumps(mods)) + db.execute("update col set models = ?", simplejson.dumps(mods)) db.execute("drop table fieldModels") db.execute("drop table cardModels") db.execute("drop table models") @@ -416,7 +415,7 @@ order by ordinal""", mid)): # explicit on upgrade. # - likewise with alignment and background color def _upgradeTemplates(self): - d = self.deck + d = self.col for m in d.models.all(): # cache field styles styles = {} @@ -470,7 +469,7 @@ order by ordinal""", mid)): # process, we automatically convert the references to new fields. def _rewriteMediaRefs(self): - deck = self.deck + col = self.col def rewriteRef(key): all, fname = match if all in state['mflds']: @@ -487,7 +486,7 @@ order by ordinal""", mid)): pre, ofld, suf = m2.groups() # get index of field name try: - idx = deck.models.fieldMap(m)[ofld][0] + idx = col.models.fieldMap(m)[ofld][0] except: # invalid field or tag reference; don't rewrite return @@ -495,33 +494,33 @@ order by ordinal""", mid)): while 1: state['fields'] += 1 fld = "Media %d" % state['fields'] - if fld not in deck.models.fieldMap(m).keys(): + if fld not in col.models.fieldMap(m).keys(): break # add the new field - f = deck.models.newField(fld) - deck.models.addField(m, f) + f = col.models.newField(fld) + col.models.addField(m, f) # loop through notes and write reference into new field data = [] - for id, flds in self.deck.db.execute( + for id, flds in self.col.db.execute( "select id, flds from notes where id in "+ - ids2str(deck.models.nids(m))): + ids2str(col.models.nids(m))): sflds = splitFields(flds) ref = all.replace(fname, pre+sflds[idx]+suf) data.append((flds+ref, id)) # update notes - deck.db.executemany("update notes set flds=? where id=?", + col.db.executemany("update notes set flds=? where id=?", data) # note field for future state['mflds'][fname] = fld new = fld # rewrite reference in template t[key] = t[key].replace(all, "{{{%s}}}" % new) - regexps = deck.media.regexps + ( + regexps = col.media.regexps + ( r"(\[latex\](.+?)\[/latex\])", r"(\[\$\](.+?)\[/\$\])", r"(\[\$\$\](.+?)\[/\$\$\])") # process each model - for m in deck.models.all(): + for m in col.models.all(): state = dict(mflds={}, fields=0) for t in m['tmpls']: for r in regexps: @@ -530,7 +529,7 @@ order by ordinal""", mid)): for match in re.findall(r, t['afmt']): rewriteRef('afmt') if state['fields']: - deck.models.save(m) + col.models.save(m) # Inactive templates ###################################################################### @@ -538,7 +537,7 @@ order by ordinal""", mid)): # marked inactive and have no dependent cards. def _removeInactive(self): - d = self.deck + d = self.col for m in d.models.all(): remove = [] for t in m['tmpls']: @@ -552,15 +551,14 @@ and ord = ? limit 1""", m['id'], t['ord']): m['tmpls'].remove(t) d.models.save(m) - # Upgrading deck + # Post-schema upgrade ###################################################################### - def _upgradeDeck(self): + def _upgradeRest(self): "Handle the rest of the upgrade to 2.0." - import anki.deck - deck = self.deck + col = self.col # make sure we have a current model id - deck.models.setCurrent(deck.models.models.values()[0]) + col.models.setCurrent(col.models.models.values()[0]) # remove unused templates that were marked inactive self._removeInactive() # rewrite media references in card template @@ -568,58 +566,58 @@ and ord = ? limit 1""", m['id'], t['ord']): # template handling has changed self._upgradeTemplates() # set new card order - for m in deck.models.all(): - m['newOrder'] = deck.conf['oldNewOrder'] - deck.models.save(m) - del deck.conf['oldNewOrder'] + for m in col.models.all(): + m['newOrder'] = col.conf['oldNewOrder'] + col.models.save(m) + del col.conf['oldNewOrder'] # fix creation time - deck.sched._updateCutoff() + col.sched._updateCutoff() d = datetime.datetime.today() d -= datetime.timedelta(hours=4) d = datetime.datetime(d.year, d.month, d.day) d += datetime.timedelta(hours=4) - d -= datetime.timedelta(days=1+int((time.time()-deck.crt)/86400)) - deck.crt = int(time.mktime(d.timetuple())) - deck.sched._updateCutoff() + d -= datetime.timedelta(days=1+int((time.time()-col.crt)/86400)) + col.crt = int(time.mktime(d.timetuple())) + col.sched._updateCutoff() # update uniq cache - deck.updateFieldCache(deck.db.list("select id from notes")) + col.updateFieldCache(col.db.list("select id from notes")) # remove old views for v in ("failedCards", "revCardsOld", "revCardsNew", "revCardsDue", "revCardsRandom", "acqCardsRandom", "acqCardsOld", "acqCardsNew"): - deck.db.execute("drop view if exists %s" % v) + col.db.execute("drop view if exists %s" % v) # remove stats, as it's all in the revlog now - deck.db.execute("drop table if exists stats") + col.db.execute("drop table if exists stats") # suspended cards don't use ranges anymore - deck.db.execute("update cards set queue=-1 where queue between -3 and -1") - deck.db.execute("update cards set queue=-2 where queue between 3 and 5") - deck.db.execute("update cards set queue=-3 where queue between 6 and 8") + col.db.execute("update cards set queue=-1 where queue between -3 and -1") + col.db.execute("update cards set queue=-2 where queue between 3 and 5") + col.db.execute("update cards set queue=-3 where queue between 6 and 8") # remove old deleted tables for t in ("cards", "notes", "models", "media"): - deck.db.execute("drop table if exists %sDeleted" % t) + col.db.execute("drop table if exists %sDeleted" % t) # rewrite due times for new cards - deck.db.execute(""" + col.db.execute(""" update cards set due = nid where type=0""") # and failed cards - left = len(deck.groups.conf(1)['new']['delays']) - deck.db.execute("update cards set edue = ?, left=? where type = 1", - deck.sched.today+1, left) + left = len(col.groups.conf(1)['new']['delays']) + col.db.execute("update cards set edue = ?, left=? where type = 1", + col.sched.today+1, left) # and due cards - deck.db.execute(""" + col.db.execute(""" update cards set due = cast( (case when due < :stamp then 0 else 1 end) + ((due-:stamp)/86400) as int)+:today where type = 2 -""", stamp=deck.sched.dayCutoff, today=deck.sched.today) +""", stamp=col.sched.dayCutoff, today=col.sched.today) # possibly re-randomize - if deck.models.randomNew(): - deck.sched.randomizeCards() + if col.models.randomNew(): + col.sched.randomizeCards() # update insertion id - deck.conf['nextPos'] = deck.db.scalar("select max(id) from notes")+1 - deck.save() + col.conf['nextPos'] = col.db.scalar("select max(id) from notes")+1 + col.save() # optimize and finish - deck.db.commit() - deck.db.execute("vacuum") - deck.db.execute("analyze") - deck.db.execute("update deck set ver = ?", SCHEMA_VERSION) - deck.save() + col.db.commit() + col.db.execute("vacuum") + col.db.execute("analyze") + col.db.execute("update col set ver = ?", SCHEMA_VERSION) + col.save() diff --git a/tests/off/test_exporting.py b/tests/off/test_exporting.py index c369f7b35..02de303dc 100644 --- a/tests/off/test_exporting.py +++ b/tests/off/test_exporting.py @@ -2,7 +2,7 @@ import nose, os, tempfile import anki -from anki import Deck +from anki import open as aopen from anki.exporting import * from anki.stdmodels import * @@ -12,7 +12,7 @@ testDir = os.path.dirname(__file__) def setup1(): global deck - deck = Deck() + deck = aopen() deck.addModel(BasicModel()) deck.currentModel.cardModels[1].active = True f = deck.newNote() @@ -33,14 +33,14 @@ def test_export_anki(): e.exportInto(newname) assert deck.modified == oldTime # connect to new deck - d2 = Deck(newname, backup=False) + d2 = aopen(newname, backup=False) assert d2.cardCount() == 4 # try again, limited to a tag newname = unicode(tempfile.mkstemp(prefix="ankitest")[1]) os.unlink(newname) e.limitTags = ['tag'] e.exportInto(newname) - d2 = Deck(newname, backup=False) + d2 = aopen(newname, backup=False) assert d2.cardCount() == 2 @nose.with_setup(setup1) diff --git a/tests/shared.py b/tests/shared.py index 8fe3f8d73..ac57cf3b9 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -1,5 +1,5 @@ import tempfile, os, shutil -from anki import Deck +from anki import open as aopen def assertException(exception, func): found = False @@ -12,7 +12,7 @@ def assertException(exception, func): def getEmptyDeck(**kwargs): (fd, nam) = tempfile.mkstemp(suffix=".anki2") os.unlink(nam) - return Deck(nam, **kwargs) + return aopen(nam, **kwargs) def getUpgradeDeckPath(): src = os.path.join(testDir, "support", "anki12.anki") diff --git a/tests/test_deck.py b/tests/test_deck.py index f75ecdb07..1075d8268 100644 --- a/tests/test_deck.py +++ b/tests/test_deck.py @@ -6,7 +6,7 @@ from tests.shared import assertException, getEmptyDeck, testDir, \ from anki.stdmodels import addBasicModel from anki.consts import * -from anki import Deck +from anki import open as aopen newPath = None newMod = None @@ -18,7 +18,7 @@ def test_create(): os.unlink(path) except OSError: pass - deck = Deck(path) + deck = aopen(path) # for open() newPath = deck.path deck.close() @@ -26,18 +26,18 @@ def test_create(): del deck def test_open(): - deck = Deck(newPath) + deck = aopen(newPath) assert deck.mod == newMod deck.close() def test_openReadOnly(): # non-writeable dir assertException(Exception, - lambda: Deck("/attachroot.anki2")) + lambda: aopen("/attachroot.anki2")) # reuse tmp file from before, test non-writeable file os.chmod(newPath, 0) assertException(Exception, - lambda: Deck(newPath)) + lambda: aopen(newPath)) os.chmod(newPath, 0666) os.unlink(newPath) diff --git a/tests/test_importing.py b/tests/test_importing.py index 0437e8be4..38aae7ce5 100644 --- a/tests/test_importing.py +++ b/tests/test_importing.py @@ -5,7 +5,6 @@ from tests.shared import assertException, getUpgradeDeckPath, getEmptyDeck from anki.upgrade import Upgrader from anki.utils import ids2str from anki.errors import * -from anki import Deck from anki.importing import Anki1Importer, Anki2Importer, TextImporter, \ SupermemoXmlImporter from anki.notes import Note diff --git a/tests/test_media.py b/tests/test_media.py index a41f2cb5d..72cf564fc 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -1,7 +1,6 @@ # coding: utf-8 import tempfile, os, time -from anki import Deck from anki.utils import checksum from shared import getEmptyDeck, testDir diff --git a/tests/test_remote_sync.py b/tests/test_remote_sync.py index b5360ce75..8e853d0cb 100644 --- a/tests/test_remote_sync.py +++ b/tests/test_remote_sync.py @@ -4,13 +4,13 @@ import nose, os, tempfile, shutil, time from tests.shared import assertException from anki.errors import * -from anki import Deck from anki.utils import intTime from anki.sync import Syncer, FullSyncer, LocalServer, RemoteServer, \ MediaSyncer, RemoteMediaServer from anki.notes import Note from anki.cards import Card from tests.shared import getEmptyDeck +from anki import open as aopen deck1=None deck2=None @@ -91,7 +91,7 @@ def test_remoteSync(): lmod = ts.client.deck.mod f = FullSyncer(ts.client.deck, TEST_HKEY) f.download() - d = Deck(ts.client.deck.path) + d = aopen(ts.client.deck.path) assert d.mod == lmod # Remote media tests diff --git a/tests/test_stats.py b/tests/test_stats.py index cd8433c56..e0b6e7175 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -24,8 +24,8 @@ def test_graphs_empty(): assert d.stats().report() def test_graphs(): - from anki import Deck - d = Deck(os.path.expanduser("~/test.anki2")) + from anki import open as aopen + d = aopen(os.path.expanduser("~/test.anki2")) g = d.stats() rep = g.report() open(os.path.expanduser("~/test.html"), "w").write(rep) diff --git a/tests/test_sync.py b/tests/test_sync.py index eb35d2b32..800e310de 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -4,7 +4,7 @@ import nose, os, tempfile, shutil, time from tests.shared import assertException from anki.errors import * -from anki import Deck +from anki import open as aopen from anki.utils import intTime from anki.sync import Syncer, FullSyncer, LocalServer, RemoteServer, \ MediaSyncer, RemoteMediaServer @@ -210,7 +210,7 @@ def test_threeway(): d3path = deck1.path.replace(".anki", "2.anki") shutil.copy2(deck1.path, d3path) deck1.reopen() - deck3 = Deck(d3path) + deck3 = aopen(d3path) client2 = Syncer(deck3, server) assert client2.sync() == "noChanges" # client 1 adds a card at time 1 @@ -233,7 +233,7 @@ def test_threeway(): def _test_speed(): t = time.time() - deck1 = Deck(os.path.expanduser("~/rapid.anki")) + deck1 = aopen(os.path.expanduser("~/rapid.anki")) for tbl in "revlog", "cards", "notes", "graves": deck1.db.execute("update %s set usn = -1 where usn != -1"%tbl) for m in deck1.models.all():