deck -> collection

This commit is contained in:
Damien Elmes 2011-11-23 17:47:44 +09:00
parent 6e4e8249fb
commit 279a942642
32 changed files with 523 additions and 526 deletions

View file

@ -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

View file

@ -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]

View file

@ -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
##########################################################################

View file

@ -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):

View file

@ -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)

View file

@ -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 = {}

View file

@ -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)."

View file

@ -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),
)

View file

@ -2,13 +2,13 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# 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

View file

@ -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):

View file

@ -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))

View file

@ -15,7 +15,7 @@ import re, unicodedata, time
#import chardet
from anki import Deck
#from anki import Deck
class SmartDict(dict):
"""

View file

@ -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 = '<img src="%s">' % 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:

View file

@ -17,10 +17,10 @@ class MediaManager(object):
regexps = ("(?i)(\[sound:([^]]+)\])",
"(?i)(<img[^>]+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

View file

@ -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

View file

@ -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])

View file

@ -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"))

View file

@ -192,7 +192,7 @@ def stopMplayer(*args):
return
mplayerManager.kill()
addHook("deckClosed", stopMplayer)
addHook("colClosed", stopMplayer)
# PyAudio recording
##########################################################################

View file

@ -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 = "<table width=100%%>"
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 += "</table>"
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"),
_("<b>%(pct)d%%</b> (%(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 <i>ease factor</i> 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 += "<table width=%d><tr><td>%s</td><td>%s</td></table>" % (
self.width,
self._graph(id="cards", data=d, type="pie"),
@ -574,7 +574,7 @@ when you answer "good" on a review.''')
return "<table width=400>" + "".join(i) + "</table>"
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 '<h1>%s</h1>%s' % (title, subtitle)

View file

@ -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

View file

@ -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}))

View file

@ -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
##########################################################################

View file

@ -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())

View file

@ -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()

View file

@ -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)

View file

@ -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")

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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():